summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
commit586acc5fe142f498261f52c66862fa417c3d52d2 (patch)
treec98b3417a883f2477029c8cd5888f4078681e24e
parenta814a8d55429605fe6d7045045cd25b6bf624580 (diff)
downloadchromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.zip
chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.gz
chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.bz2
Add net to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/SConscript345
-rw-r--r--net/SConstruct3
-rw-r--r--net/base/address_list.cc46
-rw-r--r--net/base/address_list.h61
-rw-r--r--net/base/auth.h76
-rw-r--r--net/base/auth_cache.cc69
-rw-r--r--net/base/auth_cache.h84
-rw-r--r--net/base/auth_cache_unittest.cc72
-rw-r--r--net/base/base64.cc65
-rw-r--r--net/base/base64.h43
-rw-r--r--net/base/base64_unittest.cc54
-rw-r--r--net/base/bzip2_filter.cc124
-rw-r--r--net/base/bzip2_filter.h108
-rw-r--r--net/base/bzip2_filter_unittest.cc394
-rw-r--r--net/base/cert_status_flags.h63
-rw-r--r--net/base/client_socket.h71
-rw-r--r--net/base/client_socket_factory.cc57
-rw-r--r--net/base/client_socket_factory.h57
-rw-r--r--net/base/completion_callback.h53
-rw-r--r--net/base/cookie_monster.cc1043
-rw-r--r--net/base/cookie_monster.h331
-rw-r--r--net/base/cookie_monster_perftest.cc120
-rw-r--r--net/base/cookie_monster_unittest.cc849
-rw-r--r--net/base/cookie_policy.cc66
-rw-r--r--net/base/cookie_policy.h72
-rw-r--r--net/base/cookie_policy_unittest.cc115
-rw-r--r--net/base/data_url.cc121
-rw-r--r--net/base/data_url.h63
-rw-r--r--net/base/data_url_unittest.cc176
-rw-r--r--net/base/dir_header.html69
-rw-r--r--net/base/directory_lister.cc163
-rw-r--r--net/base/directory_lister.h92
-rw-r--r--net/base/directory_lister_unittest.cc85
-rw-r--r--net/base/dns_resolution_observer.cc85
-rw-r--r--net/base/dns_resolution_observer.h80
-rw-r--r--net/base/effective_tld_names.dat3124
-rw-r--r--net/base/escape.cc272
-rw-r--r--net/base/escape.h141
-rw-r--r--net/base/escape_unittest.cc229
-rw-r--r--net/base/ev_root_ca_metadata.cc205
-rw-r--r--net/base/ev_root_ca_metadata.h76
-rw-r--r--net/base/filter.cc182
-rw-r--r--net/base/filter.h167
-rw-r--r--net/base/gzip_filter.cc303
-rw-r--r--net/base/gzip_filter.h158
-rw-r--r--net/base/gzip_filter_unittest.cc417
-rw-r--r--net/base/gzip_header.cc199
-rw-r--r--net/base/gzip_header.h120
-rw-r--r--net/base/host_resolver.cc161
-rw-r--r--net/base/host_resolver.h72
-rw-r--r--net/base/listen_socket.cc188
-rw-r--r--net/base/listen_socket.h99
-rw-r--r--net/base/listen_socket_unittest.cc67
-rw-r--r--net/base/listen_socket_unittest.h291
-rw-r--r--net/base/load_flags.h88
-rw-r--r--net/base/load_states.h90
-rw-r--r--net/base/mime_sniffer.cc623
-rw-r--r--net/base/mime_sniffer.h62
-rw-r--r--net/base/mime_sniffer_unittest.cc324
-rw-r--r--net/base/mime_util.cc305
-rw-r--r--net/base/mime_util.h71
-rw-r--r--net/base/mime_util_unittest.cc114
-rw-r--r--net/base/net_error_list.h214
-rw-r--r--net/base/net_errors.cc55
-rw-r--r--net/base/net_errors.h65
-rw-r--r--net/base/net_module.cc44
-rw-r--r--net/base/net_module.h60
-rw-r--r--net/base/net_resources.h4
-rw-r--r--net/base/net_resources.rc20
-rw-r--r--net/base/net_util.cc993
-rw-r--r--net/base/net_util.h153
-rw-r--r--net/base/net_util_unittest.cc671
-rw-r--r--net/base/registry_controlled_domain.cc351
-rw-r--r--net/base/registry_controlled_domain.h298
-rw-r--r--net/base/registry_controlled_domain_unittest.cc296
-rw-r--r--net/base/socket.h61
-rw-r--r--net/base/ssl_client_socket.cc502
-rw-r--r--net/base/ssl_client_socket.h125
-rw-r--r--net/base/ssl_client_socket_unittest.cc190
-rw-r--r--net/base/ssl_config_service.cc129
-rw-r--r--net/base/ssl_config_service.h96
-rw-r--r--net/base/ssl_config_service_unittest.cc108
-rw-r--r--net/base/ssl_info.h102
-rw-r--r--net/base/tcp_client_socket.cc281
-rw-r--r--net/base/tcp_client_socket.h92
-rw-r--r--net/base/tcp_client_socket_unittest.cc185
-rw-r--r--net/base/telnet_server.cc275
-rw-r--r--net/base/telnet_server.h78
-rw-r--r--net/base/telnet_server_unittest.cc99
-rw-r--r--net/base/test_completion_callback.h79
-rw-r--r--net/base/upload_data.cc71
-rw-r--r--net/base/upload_data.h125
-rw-r--r--net/base/upload_data_stream.cc151
-rw-r--r--net/base/upload_data_stream.h95
-rw-r--r--net/base/wininet_util.cc95
-rw-r--r--net/base/wininet_util.h50
-rw-r--r--net/base/wininet_util_unittest.cc64
-rw-r--r--net/base/winsock_init.cc57
-rw-r--r--net/base/winsock_init.h56
-rw-r--r--net/base/x509_certificate.cc569
-rw-r--r--net/base/x509_certificate.h213
-rw-r--r--net/build/convert_tld_data.rules19
-rw-r--r--net/build/crash_cache.vcproj155
-rw-r--r--net/build/net.vcproj895
-rw-r--r--net/build/net_perftests.vcproj175
-rw-r--r--net/build/net_unittests.vcproj382
-rw-r--r--net/build/precompiled_net.cc33
-rw-r--r--net/build/precompiled_net.h50
-rw-r--r--net/build/stress_cache.vcproj155
-rw-r--r--net/build/tld_cleanup.vcproj151
-rw-r--r--net/data/cache_tests/bad_entry/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/bad_entry/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/bad_entry/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/bad_entry/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/bad_entry/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/bad_rankings/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/bad_rankings/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/bad_rankings/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/bad_rankings/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/bad_rankings/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_empty1/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_empty1/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_empty1/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_empty1/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_empty1/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_empty2/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_empty2/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_empty2/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_empty2/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_empty2/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_empty3/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_empty3/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_empty3/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_empty3/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_empty3/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_load1/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_load1/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_load1/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_load1/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_load1/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_load2/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_load2/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_load2/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_load2/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_load2/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_one1/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_one1/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_one1/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_one1/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_one1/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_one2/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_one2/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_one2/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_one2/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_one2/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/insert_one3/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/insert_one3/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/insert_one3/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_one3/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/insert_one3/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/list_loop/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/list_loop/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/list_loop/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/list_loop/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/list_loop/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_head1/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_head1/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_head1/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head1/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head1/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_head2/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_head2/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_head2/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head2/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head2/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_head3/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_head3/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_head3/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head3/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head3/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_head4/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_head4/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_head4/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head4/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_head4/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_load1/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_load1/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_load1/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_load1/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_load1/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_load2/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_load2/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_load2/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_load2/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_load2/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_load3/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_load3/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_load3/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_load3/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_load3/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_one1/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_one1/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_one1/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one1/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one1/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_one2/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_one2/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_one2/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one2/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one2/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_one3/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_one3/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_one3/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one3/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one3/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_one4/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_one4/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_one4/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one4/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_one4/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_tail1/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_tail1/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_tail1/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_tail1/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_tail1/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_tail2/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_tail2/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_tail2/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_tail2/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_tail2/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/remove_tail3/data_0bin0 -> 45056 bytes
-rw-r--r--net/data/cache_tests/remove_tail3/data_1bin0 -> 270336 bytes
-rw-r--r--net/data/cache_tests/remove_tail3/data_2bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_tail3/data_3bin0 -> 8192 bytes
-rw-r--r--net/data/cache_tests/remove_tail3/indexbin0 -> 262208 bytes
-rw-r--r--net/data/cache_tests/wrong_version/indexbin0 -> 262176 bytes
-rw-r--r--net/data/filter_unittests/google.txt19
-rw-r--r--net/data/filter_unittests/google.txt.bz2bin0 -> 1507 bytes
-rw-r--r--net/data/purify/net_unittests.exe_FIM.txt11
-rw-r--r--net/data/purify/net_unittests.exe_FIM_flakey.txt0
-rw-r--r--net/data/purify/net_unittests.exe_MLK.txt159
-rw-r--r--net/data/purify/net_unittests.exe_MLK_flakey.txt51
-rw-r--r--net/data/purify/net_unittests.exe_PAR.txt7
-rw-r--r--net/data/purify/net_unittests.exe_PAR_flakey.txt8
-rw-r--r--net/data/purify/net_unittests.exe_UMR.txt0
-rw-r--r--net/data/purify/net_unittests.exe_UMR_flakey.txt50
-rw-r--r--net/data/url_request_unittest/content-type-normalization.html8
-rw-r--r--net/data/url_request_unittest/content-type-normalization.html.mock-http-headers5
-rw-r--r--net/data/url_request_unittest/redirect-test.html1
-rw-r--r--net/data/url_request_unittest/redirect-test.html.mock-http-headers2
-rw-r--r--net/data/url_request_unittest/redirect-to-file.html1
-rw-r--r--net/data/url_request_unittest/redirect-to-file.html.mock-http-headers2
-rw-r--r--net/data/url_request_unittest/with-headers.html1
-rw-r--r--net/data/url_request_unittest/with-headers.html.mock-http-headers5
-rw-r--r--net/disk_cache/addr.h178
-rw-r--r--net/disk_cache/addr_unittest.cc61
-rw-r--r--net/disk_cache/backend_impl.cc1169
-rw-r--r--net/disk_cache/backend_impl.h218
-rw-r--r--net/disk_cache/backend_unittest.cc944
-rw-r--r--net/disk_cache/block_files.cc441
-rw-r--r--net/disk_cache/block_files.h105
-rw-r--r--net/disk_cache/block_files_unittest.cc125
-rw-r--r--net/disk_cache/disk_cache.h181
-rw-r--r--net/disk_cache/disk_cache_perftest.cc236
-rw-r--r--net/disk_cache/disk_cache_test_base.cc126
-rw-r--r--net/disk_cache/disk_cache_test_base.h92
-rw-r--r--net/disk_cache/disk_cache_test_util.cc163
-rw-r--r--net/disk_cache/disk_cache_test_util.h127
-rw-r--r--net/disk_cache/disk_format.h192
-rw-r--r--net/disk_cache/entry_impl.cc779
-rw-r--r--net/disk_cache/entry_impl.h168
-rw-r--r--net/disk_cache/entry_unittest.cc713
-rw-r--r--net/disk_cache/errors.h52
-rw-r--r--net/disk_cache/file.cc313
-rw-r--r--net/disk_cache/file.h112
-rw-r--r--net/disk_cache/file_block.h56
-rw-r--r--net/disk_cache/file_lock.cc52
-rw-r--r--net/disk_cache/file_lock.h70
-rw-r--r--net/disk_cache/hash.cc67
-rw-r--r--net/disk_cache/hash.h55
-rw-r--r--net/disk_cache/mapped_file.cc78
-rw-r--r--net/disk_cache/mapped_file.h77
-rw-r--r--net/disk_cache/mapped_file_unittest.cc139
-rw-r--r--net/disk_cache/mem_backend_impl.cc276
-rw-r--r--net/disk_cache/mem_backend_impl.h104
-rw-r--r--net/disk_cache/mem_entry_impl.cc200
-rw-r--r--net/disk_cache/mem_entry_impl.h110
-rw-r--r--net/disk_cache/mem_rankings.cc87
-rw-r--r--net/disk_cache/mem_rankings.h69
-rw-r--r--net/disk_cache/rankings.cc697
-rw-r--r--net/disk_cache/rankings.h178
-rw-r--r--net/disk_cache/stats.cc258
-rw-r--r--net/disk_cache/stats.h98
-rw-r--r--net/disk_cache/storage_block-inl.h152
-rw-r--r--net/disk_cache/storage_block.h107
-rw-r--r--net/disk_cache/storage_block_unittest.cc96
-rw-r--r--net/disk_cache/stress_cache.cc221
-rw-r--r--net/disk_cache/trace.cc146
-rw-r--r--net/disk_cache/trace.h67
-rw-r--r--net/http/cert_status_cache.cc89
-rw-r--r--net/http/cert_status_cache.h74
-rw-r--r--net/http/http_atom_list.h86
-rw-r--r--net/http/http_cache.cc1359
-rw-r--r--net/http/http_cache.h200
-rw-r--r--net/http/http_cache_unittest.cc1012
-rw-r--r--net/http/http_chunked_decoder.cc136
-rw-r--r--net/http/http_chunked_decoder.h107
-rw-r--r--net/http/http_chunked_decoder_unittest.cc111
-rw-r--r--net/http/http_connection.cc64
-rw-r--r--net/http/http_connection.h94
-rw-r--r--net/http/http_connection_manager.cc199
-rw-r--r--net/http/http_connection_manager.h136
-rw-r--r--net/http/http_connection_manager_unittest.cc277
-rw-r--r--net/http/http_network_layer.cc99
-rw-r--r--net/http/http_network_layer.h68
-rw-r--r--net/http/http_network_layer_unittest.cc87
-rw-r--r--net/http/http_network_session.h62
-rw-r--r--net/http/http_network_transaction.cc673
-rw-r--r--net/http/http_network_transaction.h179
-rw-r--r--net/http/http_network_transaction_unittest.cc425
-rw-r--r--net/http/http_proxy_resolver_fixed.cc48
-rw-r--r--net/http/http_proxy_resolver_fixed.h54
-rw-r--r--net/http/http_proxy_resolver_winhttp.cc185
-rw-r--r--net/http/http_proxy_resolver_winhttp.h64
-rw-r--r--net/http/http_proxy_service.cc496
-rw-r--r--net/http/http_proxy_service.h303
-rw-r--r--net/http/http_proxy_service_unittest.cc299
-rw-r--r--net/http/http_request_info.h66
-rw-r--r--net/http/http_response_headers.cc936
-rw-r--r--net/http/http_response_headers.h295
-rw-r--r--net/http/http_response_headers_unittest.cc965
-rw-r--r--net/http/http_response_info.h67
-rw-r--r--net/http/http_transaction.h111
-rw-r--r--net/http/http_transaction_factory.h61
-rw-r--r--net/http/http_transaction_unittest.cc183
-rw-r--r--net/http/http_transaction_unittest.h346
-rw-r--r--net/http/http_transaction_winhttp.cc1807
-rw-r--r--net/http/http_transaction_winhttp.h222
-rw-r--r--net/http/http_transaction_winhttp_unittest.cc80
-rw-r--r--net/http/http_util.cc358
-rw-r--r--net/http/http_util.h173
-rw-r--r--net/http/http_util_unittest.cc159
-rw-r--r--net/http/http_vary_data.cc167
-rw-r--r--net/http/http_vary_data.h109
-rw-r--r--net/http/http_vary_data_unittest.cc154
-rw-r--r--net/http/winhttp_request_throttle.cc200
-rw-r--r--net/http/winhttp_request_throttle.h128
-rw-r--r--net/http/winhttp_request_throttle_unittest.cc136
-rw-r--r--net/net.sln162
-rw-r--r--net/tools/crash_cache/crash_cache.cc337
-rw-r--r--net/tools/testserver/dist/_socket.pydbin0 -> 49152 bytes
-rw-r--r--net/tools/testserver/dist/_ssl.pydbin0 -> 499712 bytes
-rw-r--r--net/tools/testserver/testserver.py943
-rw-r--r--net/tools/tld_cleanup/SConscript121
-rw-r--r--net/tools/tld_cleanup/tld_cleanup.cc266
-rw-r--r--net/url_request/mime_sniffer_proxy.cc94
-rw-r--r--net/url_request/mime_sniffer_proxy.h100
-rw-r--r--net/url_request/url_request.cc360
-rw-r--r--net/url_request/url_request.h542
-rw-r--r--net/url_request/url_request_about_job.cc62
-rw-r--r--net/url_request/url_request_about_job.h49
-rw-r--r--net/url_request/url_request_context.h105
-rw-r--r--net/url_request/url_request_error_job.cc46
-rw-r--r--net/url_request/url_request_error_job.h49
-rw-r--r--net/url_request/url_request_file_dir_job.cc207
-rw-r--r--net/url_request/url_request_file_dir_job.h85
-rw-r--r--net/url_request/url_request_file_job.cc349
-rw-r--r--net/url_request/url_request_file_job.h98
-rw-r--r--net/url_request/url_request_filter.cc156
-rw-r--r--net/url_request/url_request_filter.h105
-rw-r--r--net/url_request/url_request_ftp_job.cc547
-rw-r--r--net/url_request/url_request_ftp_job.h133
-rw-r--r--net/url_request/url_request_http_cache_job.cc539
-rw-r--r--net/url_request/url_request_http_cache_job.h112
-rw-r--r--net/url_request/url_request_inet_job.cc462
-rw-r--r--net/url_request/url_request_inet_job.h184
-rw-r--r--net/url_request/url_request_job.cc497
-rw-r--r--net/url_request/url_request_job.h336
-rw-r--r--net/url_request/url_request_job_manager.cc178
-rw-r--r--net/url_request/url_request_job_manager.h100
-rw-r--r--net/url_request/url_request_job_metrics.cc57
-rw-r--r--net/url_request/url_request_job_metrics.h74
-rw-r--r--net/url_request/url_request_job_tracker.cc82
-rw-r--r--net/url_request/url_request_job_tracker.h117
-rw-r--r--net/url_request/url_request_simple_job.cc81
-rw-r--r--net/url_request/url_request_simple_job.h60
-rw-r--r--net/url_request/url_request_status.h91
-rw-r--r--net/url_request/url_request_test_job.cc204
-rw-r--r--net/url_request/url_request_test_job.h110
-rw-r--r--net/url_request/url_request_unittest.cc792
-rw-r--r--net/url_request/url_request_unittest.h380
-rw-r--r--net/url_request/url_request_view_cache_job.cc194
-rw-r--r--net/url_request/url_request_view_cache_job.h56
393 files changed, 56326 insertions, 0 deletions
diff --git a/net/SConscript b/net/SConscript
new file mode 100644
index 0000000..4ec434e
--- /dev/null
+++ b/net/SConscript
@@ -0,0 +1,345 @@
+# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('env')
+
+env_res = env.Clone()
+env_tests = env.Clone()
+env = env.Clone()
+
+env.Prepend(
+ CPPPATH = [
+ '$ZLIB_DIR',
+ '$ICU38_DIR/public/common',
+ '$ICU38_DIR/public/i18n',
+ '..',
+ ],
+)
+
+env.Append(
+ CPPDEFINES = [
+ 'U_STATIC_IMPLEMENTATION',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ ],
+ CCFLAGS = [
+ '/wd4503',
+ '/wd4819',
+ ],
+)
+
+input_files = [
+ 'base/address_list.cc',
+ 'base/auth_cache.cc',
+ 'base/base64.cc',
+ 'base/bzip2_filter.cc',
+ 'base/client_socket_factory.cc',
+ 'base/cookie_monster.cc',
+ 'base/cookie_policy.cc',
+ 'base/data_url.cc',
+ 'base/directory_lister.cc',
+ 'base/dns_resolution_observer.cc',
+ 'base/escape.cc',
+ 'base/ev_root_ca_metadata.cc',
+ 'base/filter.cc',
+ 'base/gzip_filter.cc',
+ 'base/gzip_header.cc',
+ 'base/host_resolver.cc',
+ 'base/listen_socket.cc',
+ 'base/mime_sniffer.cc',
+ 'base/mime_util.cc',
+ 'base/net_errors.cc',
+ 'base/net_module.cc',
+ 'base/net_util.cc',
+ 'base/registry_controlled_domain.cc',
+ 'base/ssl_client_socket.cc',
+ 'base/ssl_config_service.cc',
+ 'base/tcp_client_socket.cc',
+ 'base/telnet_server.cc',
+ 'base/upload_data.cc',
+ 'base/upload_data_stream.cc',
+ 'base/wininet_util.cc',
+ 'base/winsock_init.cc',
+ 'base/x509_certificate.cc',
+ 'disk_cache/backend_impl.cc',
+ 'disk_cache/block_files.cc',
+ 'disk_cache/entry_impl.cc',
+ 'disk_cache/file.cc',
+ 'disk_cache/file_lock.cc',
+ 'disk_cache/hash.cc',
+ 'disk_cache/mapped_file.cc',
+ 'disk_cache/mem_backend_impl.cc',
+ 'disk_cache/mem_entry_impl.cc',
+ 'disk_cache/mem_rankings.cc',
+ 'disk_cache/rankings.cc',
+ 'disk_cache/stats.cc',
+ 'disk_cache/trace.cc',
+ 'http/cert_status_cache.cc',
+ 'http/http_chunked_decoder.cc',
+ 'http/http_connection.cc',
+ 'http/http_connection_manager.cc',
+ 'http/http_cache.cc',
+ 'http/http_network_layer.cc',
+ 'http/http_network_transaction.cc',
+ 'http/http_proxy_resolver_fixed.cc',
+ 'http/http_proxy_resolver_winhttp.cc',
+ 'http/http_proxy_service.cc',
+ 'http/http_response_headers.cc',
+ 'http/http_transaction_winhttp.cc',
+ 'http/http_util.cc',
+ 'http/http_vary_data.cc',
+ 'http/winhttp_request_throttle.cc',
+ 'url_request/mime_sniffer_proxy.cc',
+ 'url_request/url_request.cc',
+ 'url_request/url_request_about_job.cc',
+ 'url_request/url_request_error_job.cc',
+ 'url_request/url_request_file_dir_job.cc',
+ 'url_request/url_request_file_job.cc',
+ 'url_request/url_request_filter.cc',
+ 'url_request/url_request_ftp_job.cc',
+ 'url_request/url_request_http_cache_job.cc',
+ 'url_request/url_request_inet_job.cc',
+ 'url_request/url_request_job.cc',
+ 'url_request/url_request_job_manager.cc',
+ 'url_request/url_request_job_metrics.cc',
+ 'url_request/url_request_job_tracker.cc',
+ 'url_request/url_request_simple_job.cc',
+ 'url_request/url_request_test_job.cc',
+ 'url_request/url_request_view_cache_job.cc',
+]
+
+#env_p = env.Clone(
+# PCHSTOP='precompiled_net.h',
+# PDB = 'vc80.pdb',
+#)
+#pch, obj = env_p.PCH(['net.pch', 'precompiled_net.obj'], 'precompiled_net.cc')
+#env_p['PCH'] = pch
+
+#env.StaticLibrary('net', input_files + [obj])
+
+# TODO(bradnelson): This step generates file precompiled_net.pch.ib_tag
+# possibly only on incredibuild, scons doesn't know this.
+env_p = env.Clone()
+env_p.Append(CCFLAGS='/Ylnet')
+pch, obj = env_p.PCH('precompiled_net.pch', 'build/precompiled_net.cc')
+env['PCH'] = pch
+env['PCHSTOP'] = 'precompiled_net.h'
+env.Append(CCPCHFLAGS = ['/FIprecompiled_net.h'])
+
+env.StaticLibrary('net', input_files + [obj])
+
+
+env_tests.Prepend(
+ CPPPATH = [
+ '..',
+ ],
+ CPPDEFINES = [
+ 'UNIT_TEST',
+ '_WIN32_WINNT=0x0600',
+ 'WINVER=0x0600',
+ '_HAS_EXCEPTIONS=0',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ 'WIN32_LEAN_AND_MEAN',
+ ],
+ CCFLAGS = [
+ '/WX',
+ '/Wp64',
+ '/TP',
+
+ '/wd4503',
+ '/wd4819',
+ ],
+ LINKFLAGS = [
+ '/DELAYLOAD:"dwmapi.dll"',
+ '/DELAYLOAD:"uxtheme.dll"',
+ '/MACHINE:X86',
+ '/FIXED:No',
+ '/safeseh',
+ '/dynamicbase',
+ '/ignore:4199',
+ '/nxcompat',
+ ],
+ LIBS = [
+ 'advapi32.lib',
+ 'comdlg32.lib',
+ 'DelayImp.lib',
+ 'gdi32.lib',
+ 'kernel32.lib',
+ 'msimg32.lib',
+ 'odbc32.lib',
+ 'odbccp32.lib',
+ 'ole32.lib',
+ 'oleaut32.lib',
+ 'psapi.lib',
+ 'shell32.lib',
+ 'user32.lib',
+ 'usp10.lib',
+ 'uuid.lib',
+ 'version.lib',
+ 'wininet.lib',
+ 'winspool.lib',
+ 'ws2_32.lib',
+ ],
+)
+
+env_tests.Append(
+ CPPPATH = [
+ '$GTEST_DIR/include',
+ ],
+)
+
+libs = [
+ '$GOOGLEURL_DIR/googleurl.lib',
+ '$BASE_DIR/base.lib',
+ '$TESTING_DIR/gtest.lib',
+ '../third_party/bzip2/bzip2.lib',
+ '$ICU38_DIR/icuuc.lib',
+ '$MODP_B64_DIR/modp_b64.lib',
+ '$ZLIB_DIR/zlib.lib',
+ 'net.lib',
+]
+
+
+unittest_files = [
+ 'base/auth_cache_unittest.cc',
+ 'base/base64_unittest.cc',
+ 'base/bzip2_filter_unittest.cc',
+ 'base/cookie_monster_unittest.cc',
+ 'base/cookie_policy_unittest.cc',
+ 'base/data_url_unittest.cc',
+ 'base/directory_lister_unittest.cc',
+ 'base/escape_unittest.cc',
+ 'base/gzip_filter_unittest.cc',
+ 'base/mime_sniffer_unittest.cc',
+ 'base/mime_util_unittest.cc',
+ 'base/net_util_unittest.cc',
+ 'base/registry_controlled_domain_unittest.cc',
+ 'base/ssl_config_service_unittest.cc',
+ 'base/ssl_client_socket_unittest.cc',
+ 'base/tcp_client_socket_unittest.cc',
+ 'base/wininet_util_unittest.cc',
+ 'disk_cache/addr_unittest.cc',
+ 'disk_cache/backend_unittest.cc',
+ 'disk_cache/block_files_unittest.cc',
+ 'disk_cache/disk_cache_test_base.cc',
+ 'disk_cache/disk_cache_test_util.cc',
+ 'disk_cache/entry_unittest.cc',
+ 'disk_cache/mapped_file_unittest.cc',
+ 'disk_cache/storage_block_unittest.cc',
+ 'http/http_cache_unittest.cc',
+ 'http/http_connection_manager_unittest.cc',
+ 'http/http_network_layer_unittest.cc',
+ 'http/http_network_transaction_unittest.cc',
+ 'http/http_response_headers_unittest.cc',
+ 'http/http_transaction_unittest.cc',
+ 'http/http_transaction_winhttp_unittest.cc',
+ 'http/http_util_unittest.cc',
+ 'http/http_vary_data_unittest.cc',
+ 'http/winhttp_request_throttle_unittest.cc',
+ 'url_request/url_request_unittest.cc',
+ '$BASE_DIR/run_all_unittests.obj',
+]
+
+net_unittests = env_tests.Program(
+ ['net_unittests.exe',
+ 'net_unittests.ilk',
+ 'net_unittests.pdb'],
+ unittest_files + libs
+)
+
+
+
+stress_cache = env_tests.Program(
+ ['stress_cache.exe',
+ 'stress_cache.ilk',
+ 'stress_cache.pdb'],
+ ['disk_cache/stress_cache.cc',
+ 'disk_cache/disk_cache_test_util.cc'] + libs
+)
+
+
+crash_cache = env_tests.Program(
+ ['crash_cache.exe',
+ 'crash_cache.ilk',
+ 'crash_cache.pdb'],
+ ['tools/crash_cache/crash_cache.cc',
+ 'disk_cache/disk_cache_test_util.cc'] + libs
+)
+
+
+net_perftests = env_tests.Program(
+ ['net_perftests.exe',
+ 'net_perftests.ilk',
+ 'net_perftests.pdb'],
+ ['disk_cache/disk_cache_test_util.cc',
+ 'disk_cache/disk_cache_perftest.cc',
+ 'base/cookie_monster_perftest.cc',
+ # TODO(sgk): avoid using .cc from base directly
+ '$BASE_DIR/run_all_perftests$OBJSUFFIX',
+ '$BASE_DIR/perftimer$OBJSUFFIX'] + libs
+)
+
+
+# Create install of tests.
+installed_tests = env.Install(
+ '$TARGET_ROOT',
+ net_unittests + stress_cache + crash_cache + net_perftests
+)
+
+
+env_res.Append(
+ CPPPATH = [
+ '..',
+ ],
+ RCFLAGS = [
+ ['/l', '0x409'],
+ ],
+)
+
+# This dat file needed by net_resources is generated.
+tld_names_clean = env_res.Command('net/effective_tld_names_clean.dat',
+ ['base/effective_tld_names.dat',
+ 'tools/tld_cleanup/tld_cleanup.exe'],
+ '${SOURCES[1]} ${SOURCES[0]} $TARGET')
+rc = env_res.Command('net_resources.rc',
+ 'base/net_resources.rc',
+ Copy('$TARGET', '$SOURCE'))
+net_resources = env_res.RES(rc)
+env_res.Depends(rc, tld_names_clean)
+
+
+sconscript_files = [
+ 'tools/tld_cleanup/SConscript',
+]
+
+SConscript(sconscript_files, exports=['env'])
+
+
+# Setup alias for building all parts of net.
+env.Alias('net', ['.', installed_tests, '../icudt38.dll'])
+
diff --git a/net/SConstruct b/net/SConstruct
new file mode 100644
index 0000000..6ea69a0
--- /dev/null
+++ b/net/SConstruct
@@ -0,0 +1,3 @@
+build_component = 'net'
+SConscript('../build/SConscript.main',
+ exports=['build_component'])
diff --git a/net/base/address_list.cc b/net/base/address_list.cc
new file mode 100644
index 0000000..4281a7e
--- /dev/null
+++ b/net/base/address_list.cc
@@ -0,0 +1,46 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/address_list.h"
+
+#include <ws2tcpip.h>
+#include <wspiapi.h> // Needed for Win2k compat.
+
+namespace net {
+
+void AddressList::Adopt(struct addrinfo* head) {
+ data_ = new Data();
+ data_->head = head;
+}
+
+AddressList::Data::~Data() {
+ freeaddrinfo(head);
+}
+
+} // namespace net
diff --git a/net/base/address_list.h b/net/base/address_list.h
new file mode 100644
index 0000000..d91137f
--- /dev/null
+++ b/net/base/address_list.h
@@ -0,0 +1,61 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_ADDRESS_LIST_H_
+#define NET_BASE_ADDRESS_LIST_H_
+
+#include "base/ref_counted.h"
+
+struct addrinfo;
+
+namespace net {
+
+// An AddressList object contains a linked list of addrinfo structures. This
+// class is designed to be copied around by value.
+class AddressList {
+ public:
+ // Adopt the given addrinfo list in place of the existing one if any. This
+ // hands over responsibility for freeing the addrinfo list to the AddressList
+ // object.
+ void Adopt(struct addrinfo* head);
+
+ // Get access to the head of the addrinfo list.
+ const struct addrinfo* head() const { return data_->head; }
+
+ private:
+ struct Data : public base::RefCountedThreadSafe<Data> {
+ ~Data();
+ struct addrinfo* head;
+ };
+ scoped_refptr<Data> data_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_ADDRESS_LIST_H_
diff --git a/net/base/auth.h b/net/base/auth.h
new file mode 100644
index 0000000..92bc315
--- /dev/null
+++ b/net/base/auth.h
@@ -0,0 +1,76 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_AUTH_H__
+#define NET_BASE_AUTH_H__
+
+#include <string>
+
+#include "base/ref_counted.h"
+
+// Holds info about an authentication challenge that we may want to display
+// to the user.
+class AuthChallengeInfo :
+ public base::RefCountedThreadSafe<AuthChallengeInfo> {
+ public:
+ bool is_proxy; // true for Proxy-Authenticate, false for WWW-Authenticate.
+ std::wstring host; // the domain name of the server asking for auth
+ // (could be the proxy).
+ std::wstring scheme; // "Basic", "Digest", or whatever other method is used.
+ std::wstring realm; // the realm provided by the server, if there is one.
+
+ private:
+ friend base::RefCountedThreadSafe<AuthChallengeInfo>;
+ ~AuthChallengeInfo() {}
+};
+
+//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.
+ std::wstring scheme; // the authentication scheme.
+ std::wstring username; // the username supplied to us for auth.
+ std::wstring password; // the password supplied to us for auth.
+
+ // We wouldn't instantiate this class if we didn't need authentication.
+ AuthData() : state(AUTH_STATE_NEED_AUTH) {}
+
+ private:
+ friend base::RefCountedThreadSafe<AuthData>;
+ ~AuthData() {}
+};
+
+#endif // NET_BASE_AUTH_H__
diff --git a/net/base/auth_cache.cc b/net/base/auth_cache.cc
new file mode 100644
index 0000000..daa3afc
--- /dev/null
+++ b/net/base/auth_cache.cc
@@ -0,0 +1,69 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/auth_cache.h"
+
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+
+// Create an AuthCacheKey from url and auth_info.
+//
+// The cache key is made up of two components, separated by a slash /.
+// 1. The host (proxy or server) requesting authentication. For a server,
+// this component also includes the scheme (protocol) and port (if not
+// the default port for the protocol) to distinguish between multiple
+// servers running on the same computer.
+// 2. The realm.
+//
+// The format of the cache key for proxy auth is:
+// proxy-host/auth-realm
+// The format of the cache key for server auth is:
+// url-scheme://url-host[:url-port]/auth-realm
+
+// static
+AuthCache::AuthCacheKey AuthCache::HttpKey(
+ const GURL& url,
+ const AuthChallengeInfo& auth_info) {
+ AuthCacheKey auth_cache_key;
+ if (auth_info.is_proxy) {
+ auth_cache_key = WideToASCII(auth_info.host);
+ auth_cache_key.append("/");
+ } else {
+ // Take scheme, host, and port from the url.
+ auth_cache_key = url.GetOrigin().spec();
+ // This ends with a "/".
+ }
+ auth_cache_key.append(WideToUTF8(auth_info.realm));
+ return auth_cache_key;
+}
+
+AuthData* AuthCache::Lookup(const AuthCacheKey& key) {
+ AuthCacheMap::iterator iter = cache_.find(key);
+ return (iter == cache_.end()) ? NULL : iter->second;
+}
diff --git a/net/base/auth_cache.h b/net/base/auth_cache.h
new file mode 100644
index 0000000..d4357f6
--- /dev/null
+++ b/net/base/auth_cache.h
@@ -0,0 +1,84 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_AUTH_CACHE_H__
+#define NET_BASE_AUTH_CACHE_H__
+
+#include <string>
+#include <map>
+
+#include "net/base/auth.h"
+
+class GURL;
+
+// TODO(wtc): move AuthCache into the net namespace.
+
+// The AuthCache class is a simple cache structure to store authentication
+// information for ftp or http/https sites. Provides lookup, addition, and
+// validation of entries.
+class AuthCache {
+ public:
+ AuthCache() {}
+ ~AuthCache() {}
+
+ typedef std::string AuthCacheKey;
+
+ // Return the key for looking up the auth data in the auth cache for HTTP,
+ // consisting of the scheme, host, and port of the request URL and the
+ // realm in the auth challenge.
+ static AuthCacheKey HttpKey(const GURL& url,
+ const AuthChallengeInfo& auth_info);
+
+ // Check if we have authentication data for given key. The key parameter
+ // is input, consisting of the hostname and any other info (such as realm)
+ // appropriate for the protocol. Return the address of corresponding
+ // AuthData object (if found) or NULL (if not found).
+ AuthData* Lookup(const AuthCacheKey& key);
+
+ // Add to the cache. If key already exists, this will overwrite. Both
+ // parameters are IN only.
+ void Add(const AuthCacheKey& key, AuthData* value) {
+ cache_[key] = value;
+ }
+
+ // Called when we have an auth failure to remove
+ // the likely invalid credentials.
+ void Remove(const AuthCacheKey& key) {
+ cache_.erase(key);
+ }
+
+ private:
+ typedef scoped_refptr<AuthData> AuthCacheValue;
+ typedef std::map<AuthCacheKey,AuthCacheValue> AuthCacheMap;
+
+ // internal representation of cache, an STL map.
+ AuthCacheMap cache_;
+};
+
+#endif // NET_BASE_AUTH_CACHE_H__
diff --git a/net/base/auth_cache_unittest.cc b/net/base/auth_cache_unittest.cc
new file mode 100644
index 0000000..b43b9ce
--- /dev/null
+++ b/net/base/auth_cache_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "googleurl/src/gurl.h"
+#include "net/base/auth_cache.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class AuthCacheTest : public testing::Test {
+};
+
+} // namespace
+
+TEST(AuthCacheTest, HttpKey) {
+ scoped_refptr<AuthChallengeInfo> auth_info = new AuthChallengeInfo;
+ auth_info->is_proxy = false; // server auth
+ // auth_info->host is intentionally left empty.
+ auth_info->scheme = L"Basic";
+ auth_info->realm = L"WallyWorld";
+
+ std::string url[] = {
+ "https://www.nowhere.org/dir/index.html",
+ "https://www.nowhere.org:443/dir/index.html", // default port
+ "https://www.nowhere.org:8443/dir/index.html", // non-default port
+ "https://www.nowhere.org", // no trailing slash
+ "https://foo:bar@www.nowhere.org/dir/index.html", // username:password
+ "https://www.nowhere.org/dir/index.html?id=965362", // query
+ "https://www.nowhere.org/dir/index.html#toc", // reference
+ };
+
+ std::string expected[] = {
+ "https://www.nowhere.org/WallyWorld",
+ "https://www.nowhere.org/WallyWorld",
+ "https://www.nowhere.org:8443/WallyWorld",
+ "https://www.nowhere.org/WallyWorld",
+ "https://www.nowhere.org/WallyWorld",
+ "https://www.nowhere.org/WallyWorld",
+ "https://www.nowhere.org/WallyWorld"
+ };
+
+ for (int i = 0; i < arraysize(url); i++) {
+ std::string key = AuthCache::HttpKey(GURL(url[i]), *auth_info);
+ EXPECT_EQ(expected[i], key);
+ }
+}
diff --git a/net/base/base64.cc b/net/base/base64.cc
new file mode 100644
index 0000000..dcb5781
--- /dev/null
+++ b/net/base/base64.cc
@@ -0,0 +1,65 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/base64.h"
+
+#pragma warning(push)
+#pragma warning(disable: 4267)
+#include "third_party/modp_b64/modp_b64.h"
+#pragma warning(pop)
+
+bool Base64Encode(const std::string& input, std::string* output) {
+ std::string temp;
+ temp.resize(modp_b64_encode_len(input.size())); // makes room for null byte
+
+ // null terminates result since result is base64 text!
+ int input_size = static_cast<int>(input.size());
+ int output_size= modp_b64_encode(&(temp[0]), input.data(), input_size);
+ if (output_size < 0)
+ return false;
+
+ temp.resize(output_size); // strips off null byte
+ output->swap(temp);
+ return true;
+}
+
+bool Base64Decode(const std::string& input, std::string* output) {
+ std::string temp;
+ temp.resize(modp_b64_decode_len(input.size()));
+
+ // does not null terminate result since result is binary data!
+ int input_size = static_cast<int>(input.size());
+ int output_size = modp_b64_decode(&(temp[0]), input.data(), input_size);
+ if (output_size < 0)
+ return false;
+
+ temp.resize(output_size);
+ output->swap(temp);
+ return true;
+}
diff --git a/net/base/base64.h b/net/base/base64.h
new file mode 100644
index 0000000..83511d8
--- /dev/null
+++ b/net/base/base64.h
@@ -0,0 +1,43 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_BASE64_H__
+#define NET_BASE_BASE64_H__
+
+#include <string>
+
+// Encodes the input string in base64. Returns true if successful and false
+// otherwise. The output string is only modified if successful.
+bool Base64Encode(const std::string& input, std::string* output);
+
+// Decodes the base64 input string. Returns true if successful and false
+// otherwise. The output string is only modified if successful.
+bool Base64Decode(const std::string& input, std::string* output);
+
+#endif // NET_BASE_BASE64_H__
diff --git a/net/base/base64_unittest.cc b/net/base/base64_unittest.cc
new file mode 100644
index 0000000..58b3d26
--- /dev/null
+++ b/net/base/base64_unittest.cc
@@ -0,0 +1,54 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/base64.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class Base64Test : public testing::Test {
+};
+
+} // namespace
+
+TEST(Base64Test, Basic) {
+ const std::string kText = "hello world";
+ const std::string kBase64Text = "aGVsbG8gd29ybGQ=";
+
+ std::string encoded, decoded;
+ bool ok;
+
+ ok = Base64Encode(kText, &encoded);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kBase64Text, encoded);
+
+ ok = Base64Decode(encoded, &decoded);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kText, decoded);
+}
diff --git a/net/base/bzip2_filter.cc b/net/base/bzip2_filter.cc
new file mode 100644
index 0000000..4ebcba6
--- /dev/null
+++ b/net/base/bzip2_filter.cc
@@ -0,0 +1,124 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <minmax.h>
+
+#include "base/logging.h"
+#include "net/base/bzip2_filter.h"
+
+BZip2Filter::BZip2Filter()
+ : decoding_status_(DECODING_UNINITIALIZED),
+ bzip2_data_stream_(NULL) {
+}
+
+BZip2Filter::~BZip2Filter() {
+ if (bzip2_data_stream_.get() &&
+ decoding_status_ != DECODING_UNINITIALIZED) {
+ BZ2_bzDecompressEnd(bzip2_data_stream_.get());
+ }
+}
+
+bool BZip2Filter::InitDecoding(bool use_small_memory) {
+ if (decoding_status_ != DECODING_UNINITIALIZED)
+ return false;
+
+ // Initialize zlib control block
+ bzip2_data_stream_.reset(new bz_stream);
+ if (!bzip2_data_stream_.get())
+ return false;
+ memset(bzip2_data_stream_.get(), 0, sizeof(bz_stream));
+
+ int result = BZ2_bzDecompressInit(bzip2_data_stream_.get(),
+ 0,
+ use_small_memory ? 1 : 0);
+
+ if (result != BZ_OK)
+ return false;
+
+ decoding_status_ = DECODING_IN_PROGRESS;
+ return true;
+}
+
+Filter::FilterStatus BZip2Filter::ReadFilteredData(char* dest_buffer,
+ int* dest_len) {
+ Filter::FilterStatus status = Filter::FILTER_ERROR;
+
+ // check output
+ if (!dest_buffer || !dest_len || *dest_len <= 0)
+ return status;
+
+ if (DECODING_DONE == decoding_status_) {
+ // this logic just follow gzip_filter, which be used to deal wth some
+ // server might send extra data after finish sending compress data
+ return CopyOut(dest_buffer, dest_len);
+ }
+
+ if (decoding_status_ != DECODING_IN_PROGRESS)
+ return status;
+
+ // Make sure we have valid input data
+ if (!next_stream_data_ || stream_data_len_ <= 0)
+ return status;
+
+ // Fill in bzip2 control block
+ int ret, output_len = *dest_len;
+ *dest_len = 0;
+
+ bzip2_data_stream_->next_in = next_stream_data_;
+ bzip2_data_stream_->avail_in = stream_data_len_;
+ bzip2_data_stream_->next_out = dest_buffer;
+ bzip2_data_stream_->avail_out = output_len;
+
+ ret = BZ2_bzDecompress(bzip2_data_stream_.get());
+
+ // get real output length, rest data and rest data length
+ *dest_len = output_len - bzip2_data_stream_->avail_out;
+
+ if (0 == bzip2_data_stream_->avail_in) {
+ next_stream_data_ = NULL;
+ stream_data_len_ = 0;
+ } else {
+ next_stream_data_ = bzip2_data_stream_->next_in;
+ stream_data_len_ = bzip2_data_stream_->avail_in;
+ }
+
+ if (BZ_OK == ret) {
+ if (stream_data_len_)
+ status = Filter::FILTER_OK;
+ else
+ status = Filter::FILTER_NEED_MORE_DATA;
+ } else if (BZ_STREAM_END == ret) {
+ status = Filter::FILTER_DONE;
+ decoding_status_ = DECODING_DONE;
+ } else {
+ decoding_status_ = DECODING_ERROR;
+ }
+
+ return status;
+}
diff --git a/net/base/bzip2_filter.h b/net/base/bzip2_filter.h
new file mode 100644
index 0000000..69e5eeb
--- /dev/null
+++ b/net/base/bzip2_filter.h
@@ -0,0 +1,108 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// BZip2Filter applies bzip2 content encoding/decoding to a datastream.
+// Since it is a new feature, and no specification said what 's bzip2 content
+// composed of in http protocol. So I assume with bzip2 encoding the content
+// is full format, which means the content should carry complete bzip2 head,
+// such as inlcude magic number 1(BZh), block size bit, magic number 2(0x31,
+// 0x41, 0x59, 0x26, 0xx53, 0x59)
+// Maybe need to inserts a bzlib2 header to the data stream before calling
+// decompression functionality, but at now I do not meet this sort of real
+// scenarios. So let's see the further requests.
+//
+// This BZip2Filter internally uses third_party/bzip2 library to do decoding.
+//
+// BZip2Filter is also a subclass of Filter. See the latter's header file filter.h
+// for sample usage.
+
+#ifndef NET_BASE_BZIP2_FILTER_H__
+#define NET_BASE_BZIP2_FILTER_H__
+
+#include "base/scoped_ptr.h"
+#include "net/base/filter.h"
+#include "third_party/bzip2/bzlib.h"
+
+class BZip2Filter : public Filter {
+ public:
+ BZip2Filter();
+
+ virtual ~BZip2Filter();
+
+ // Initializes filter decoding mode and internal control blocks.
+ // Parameter use_small_memory specifies whether use small memory
+ // to decompresss data. If small is nonzero, the bzip2 library will
+ // use an alternative decompression algorithm which uses less memory
+ // but at the cost of decompressing more slowly (roughly speaking,
+ // half the speed, but the maximum memory requirement drops to
+ // around 2300k). For more information, see doc in http://www.bzip.org.
+ // The function returns true if success and false otherwise.
+ // The filter can only be initialized once.
+ bool InitDecoding(bool use_small_memory);
+
+ // 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.
+ //
+ // Since BZ2_bzDecompress need a full BZip header for decompression, so
+ // the incoming data should have the full BZip header, otherwise this
+ // function will give you nothing with FILTER_ERROR.
+ //
+ // 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);
+
+ private:
+ enum DecodingStatus {
+ DECODING_UNINITIALIZED,
+ DECODING_IN_PROGRESS,
+ DECODING_DONE,
+ DECODING_ERROR
+ };
+
+ // Tracks the status of decoding.
+ // This variable is initialized by InitDecoding and updated only by
+ // ReadFilteredData.
+ DecodingStatus decoding_status_;
+
+ // The control block of bzip which actually does the decoding.
+ // This data structure is initialized by InitDecoding and updated in
+ // ReadFilteredData.
+ scoped_ptr<bz_stream> bzip2_data_stream_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(BZip2Filter);
+};
+
+#endif // NET_BASE_BZIP2_FILTER_H__
diff --git a/net/base/bzip2_filter_unittest.cc b/net/base/bzip2_filter_unittest.cc
new file mode 100644
index 0000000..5196e62
--- /dev/null
+++ b/net/base/bzip2_filter_unittest.cc
@@ -0,0 +1,394 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "minmax.h"
+
+#include <fstream>
+#include <iostream>
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_ptr.h"
+#include "net/base/bzip2_filter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/bzip2/bzlib.h"
+
+namespace {
+
+const char* kExtraData = "Test Data, More Test Data, Even More Data of Test";
+const int kExtraDataBufferSize = 49;
+const int kDefaultBufferSize = 4096;
+const int kSmallBufferSize = 128;
+const int kMaxBufferSize = 1048576; // 1048576 == 2^20 == 1 MB
+
+const char kApplicationOctetStream[] = "application/octet-stream";
+
+class BZip2FilterUnitTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ bzip2_encode_buffer_ = NULL;
+
+ // Get the path of source data file.
+ std::wstring file_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
+ file_util::AppendToPath(&file_path, L"net");
+ file_util::AppendToPath(&file_path, L"data");
+ file_util::AppendToPath(&file_path, L"filter_unittests");
+ file_util::AppendToPath(&file_path, L"google.txt");
+
+ // Read data from the file into buffer.
+ file_util::ReadFileToString(file_path, &source_buffer_);
+
+ // Append the extra data to end of source
+ source_buffer_.append(kExtraData, kExtraDataBufferSize);
+
+ // Encode the whole data with bzip2 for next testing
+ bzip2_data_stream_.reset(new bz_stream);
+ ASSERT_TRUE(bzip2_data_stream_.get());
+ memset(bzip2_data_stream_.get(), 0, sizeof(bz_stream));
+
+ int result = BZ2_bzCompressInit(bzip2_data_stream_.get(),
+ 9, // 900k block size
+ 0, // quiet
+ 0); // default work factor
+ ASSERT_EQ(BZ_OK, result);
+
+ bzip2_encode_buffer_ = new char[kDefaultBufferSize];
+ ASSERT_TRUE(bzip2_encode_buffer_ != NULL);
+ bzip2_encode_len_ = kDefaultBufferSize;
+
+ bzip2_data_stream_->next_in = const_cast<char*>(source_buffer());
+ bzip2_data_stream_->avail_in = source_len();
+ bzip2_data_stream_->next_out = bzip2_encode_buffer_;
+ bzip2_data_stream_->avail_out = bzip2_encode_len_;
+ do {
+ result = BZ2_bzCompress(bzip2_data_stream_.get(), BZ_FINISH);
+ } while (result == BZ_FINISH_OK);
+
+ ASSERT_EQ(BZ_STREAM_END, result);
+ result = BZ2_bzCompressEnd(bzip2_data_stream_.get());
+ ASSERT_EQ(BZ_OK, result);
+ bzip2_encode_len_ = bzip2_data_stream_->total_out_lo32;
+
+ // Make sure we wrote something; otherwise not sure what to expect
+ ASSERT_GT(bzip2_encode_len_, 0);
+ ASSERT_LE(bzip2_encode_len_, kDefaultBufferSize);
+ }
+
+ virtual void TearDown() {
+ delete[] bzip2_encode_buffer_;
+ bzip2_encode_buffer_ = NULL;
+ }
+
+ // 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.
+ // get_extra_data specifies whether get the extra data because maybe some server
+ // might send extra data after finish sending compress data
+ void DecodeAndCompareWithFilter(Filter* filter,
+ const char* source,
+ int source_len,
+ const char* encoded_source,
+ int encoded_source_len,
+ int output_buffer_size,
+ bool get_extra_data) {
+ // Make sure we have enough space to hold the decoding output.
+ ASSERT_LE(source_len, kDefaultBufferSize);
+ ASSERT_LE(output_buffer_size, kDefaultBufferSize);
+
+ int total_output_len = kDefaultBufferSize;
+ if (get_extra_data)
+ total_output_len += kExtraDataBufferSize;
+ char decode_buffer[kDefaultBufferSize + kExtraDataBufferSize];
+ char* decode_next = decode_buffer;
+ int decode_avail_size = total_output_len;
+
+ const char* encode_next = encoded_source;
+ int encode_avail_size = encoded_source_len;
+
+ Filter::FilterStatus code = Filter::FILTER_OK;
+ while (code != Filter::FILTER_DONE) {
+ int encode_data_len;
+ if (get_extra_data && !encode_avail_size)
+ break;
+ encode_data_len = min(encode_avail_size, filter->stream_buffer_size());
+ memcpy(filter->stream_buffer(), 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 = min(decode_avail_size, output_buffer_size);
+
+ code = filter->ReadFilteredData(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) {
+ if (code == Filter::FILTER_DONE && get_extra_data)
+ code = Filter::FILTER_OK;
+ else
+ break;
+ }
+ }
+ }
+
+ // Compare the decoding result with source data
+ int decode_total_data_len = total_output_len - 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.
+ Filter::FilterStatus DecodeAllWithFilter(Filter* filter,
+ const char* source,
+ int source_len,
+ char* dest,
+ int* dest_len) {
+ memcpy(filter->stream_buffer(), source, source_len);
+ filter->FlushStreamBuffer(source_len);
+ return filter->ReadFilteredData(dest, dest_len);
+ }
+
+ const char* source_buffer() const { return source_buffer_.data(); }
+ int source_len() const { return static_cast<int>(source_buffer_.size()) - kExtraDataBufferSize; }
+
+ std::string source_buffer_;
+
+ scoped_ptr<bz_stream> bzip2_data_stream_;
+ char* bzip2_encode_buffer_;
+ int bzip2_encode_len_;
+};
+
+}; // namespace
+
+// Basic scenario: decoding bzip2 data with big enough buffer.
+TEST_F(BZip2FilterUnitTest, DecodeBZip2) {
+ // Decode the compressed data with filter
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ memcpy(filter->stream_buffer(), bzip2_encode_buffer_, bzip2_encode_len_);
+ filter->FlushStreamBuffer(bzip2_encode_len_);
+
+ char bzip2_decode_buffer[kDefaultBufferSize];
+ int bzip2_decode_size = kDefaultBufferSize;
+ Filter::FilterStatus result =
+ filter->ReadFilteredData(bzip2_decode_buffer, &bzip2_decode_size);
+ ASSERT_EQ(Filter::FILTER_DONE, result);
+
+ // Compare the decoding result with source data
+ EXPECT_TRUE(bzip2_decode_size == source_len());
+ EXPECT_EQ(memcmp(source_buffer(), bzip2_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(BZip2FilterUnitTest, DecodeWithSmallInputBuffer) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, kSmallBufferSize));
+ ASSERT_TRUE(filter.get());
+ DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
+ bzip2_encode_buffer_, bzip2_encode_len_,
+ kDefaultBufferSize, false);
+}
+
+// Tests we can decode when caller has small buffer to read out from filter.
+TEST_F(BZip2FilterUnitTest, DecodeWithSmallOutputBuffer) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
+ bzip2_encode_buffer_, bzip2_encode_len_,
+ kSmallBufferSize, false);
+}
+
+// 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 BZip2
+// header correctly. (2) Sometimes the filter will consume input without
+// generating output. Verify filter can handle it correctly.
+TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputBuffer) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, 1));
+ ASSERT_TRUE(filter.get());
+ DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
+ bzip2_encode_buffer_, bzip2_encode_len_,
+ kDefaultBufferSize, false);
+}
+
+// Tests we can still decode with just 1 byte buffer in the filter and just 1
+// byte buffer in the caller.
+TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputAndOutputBuffer) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, 1));
+ ASSERT_TRUE(filter.get());
+ DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
+ bzip2_encode_buffer_, bzip2_encode_len_, 1, false);
+}
+
+// Decoding bzip2 stream with corrupted data.
+TEST_F(BZip2FilterUnitTest, DecodeCorruptedData) {
+ char corrupt_data[kDefaultBufferSize];
+ int corrupt_data_len = bzip2_encode_len_;
+ memcpy(corrupt_data, bzip2_encode_buffer_, bzip2_encode_len_);
+
+ char corrupt_decode_buffer[kDefaultBufferSize];
+ int corrupt_decode_size = kDefaultBufferSize;
+
+ // Decode the correct data with filter
+ scoped_ptr<Filter> filter1(
+ Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter1.get());
+
+ Filter::FilterStatus code = DecodeAllWithFilter(filter1.get(),
+ corrupt_data,
+ corrupt_data_len,
+ corrupt_decode_buffer,
+ &corrupt_decode_size);
+
+ // Expect failures
+ EXPECT_TRUE(code == Filter::FILTER_DONE);
+
+ // Decode the corrupted data with filter
+ scoped_ptr<Filter> filter2(
+ Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter2.get());
+
+ int pos = corrupt_data_len / 2;
+ corrupt_data[pos] = !corrupt_data[pos];
+
+ code = DecodeAllWithFilter(filter2.get(),
+ corrupt_data,
+ corrupt_data_len,
+ corrupt_decode_buffer,
+ &corrupt_decode_size);
+
+ // Expect failures
+ EXPECT_TRUE(code != Filter::FILTER_DONE);
+}
+
+// Decoding bzip2 stream with missing data.
+TEST_F(BZip2FilterUnitTest, DecodeMissingData) {
+ char corrupt_data[kDefaultBufferSize];
+ int corrupt_data_len = bzip2_encode_len_;
+ memcpy(corrupt_data, bzip2_encode_buffer_, bzip2_encode_len_);
+
+ int pos = corrupt_data_len / 2;
+ int len = corrupt_data_len - pos - 1;
+ memcpy(&corrupt_data[pos], &corrupt_data[pos+1], len);
+ --corrupt_data_len;
+
+ // Decode the corrupted data with filter
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ char corrupt_decode_buffer[kDefaultBufferSize];
+ int corrupt_decode_size = kDefaultBufferSize;
+
+ Filter::FilterStatus code = DecodeAllWithFilter(filter.get(),
+ corrupt_data,
+ corrupt_data_len,
+ corrupt_decode_buffer,
+ &corrupt_decode_size);
+ // Expect failures
+ EXPECT_TRUE(code != Filter::FILTER_DONE);
+}
+
+// Decoding bzip2 stream with corrupted header.
+TEST_F(BZip2FilterUnitTest, DecodeCorruptedHeader) {
+ char corrupt_data[kDefaultBufferSize];
+ int corrupt_data_len = bzip2_encode_len_;
+ memcpy(corrupt_data, bzip2_encode_buffer_, bzip2_encode_len_);
+
+ corrupt_data[2] = !corrupt_data[2];
+
+ // Decode the corrupted data with filter
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ char corrupt_decode_buffer[kDefaultBufferSize];
+ int corrupt_decode_size = kDefaultBufferSize;
+
+ Filter::FilterStatus code = DecodeAllWithFilter(filter.get(),
+ corrupt_data,
+ corrupt_data_len,
+ corrupt_decode_buffer,
+ &corrupt_decode_size);
+
+ // Expect failures
+ EXPECT_TRUE(code == Filter::FILTER_ERROR);
+}
+
+// Tests we can decode all compress data and get extra data which is
+// appended to compress data stream by some server when it finish
+// sending compress data.
+TEST_F(BZip2FilterUnitTest, DecodeWithExtraDataAndSmallOutputBuffer) {
+ char more_data[kDefaultBufferSize + kExtraDataBufferSize];
+ int more_data_len = bzip2_encode_len_ + kExtraDataBufferSize;
+ memcpy(more_data, bzip2_encode_buffer_, bzip2_encode_len_);
+ memcpy(more_data + bzip2_encode_len_, kExtraData, kExtraDataBufferSize);
+
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ DecodeAndCompareWithFilter(filter.get(),
+ source_buffer(), source_len() + kExtraDataBufferSize,
+ more_data,
+ more_data_len,
+ kSmallBufferSize,
+ true);
+}
+
+TEST_F(BZip2FilterUnitTest, DecodeWithExtraDataAndSmallInputBuffer) {
+ char more_data[kDefaultBufferSize + kExtraDataBufferSize];
+ int more_data_len = bzip2_encode_len_ + kExtraDataBufferSize;
+ memcpy(more_data, bzip2_encode_buffer_, bzip2_encode_len_);
+ memcpy(more_data + bzip2_encode_len_, kExtraData, kExtraDataBufferSize);
+
+ scoped_ptr<Filter> filter(
+ Filter::Factory("bzip2", kApplicationOctetStream, kSmallBufferSize));
+ ASSERT_TRUE(filter.get());
+ DecodeAndCompareWithFilter(filter.get(),
+ source_buffer(), source_len() + kExtraDataBufferSize,
+ more_data,
+ more_data_len,
+ kDefaultBufferSize,
+ true);
+}
diff --git a/net/base/cert_status_flags.h b/net/base/cert_status_flags.h
new file mode 100644
index 0000000..0812eb0
--- /dev/null
+++ b/net/base/cert_status_flags.h
@@ -0,0 +1,63 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_CERT_STATUS_FLAGS_H__
+#define NET_BASE_CERT_STATUS_FLAGS_H__
+
+namespace net {
+
+// Status flags, such as errors and extended validation.
+enum {
+ // Bits 0 to 15 are for errors.
+ CERT_STATUS_ALL_ERRORS = 0xFFFF,
+ CERT_STATUS_COMMON_NAME_INVALID = 1 << 0,
+ CERT_STATUS_DATE_INVALID = 1 << 1,
+ CERT_STATUS_AUTHORITY_INVALID = 1 << 2,
+ // 1 << 3 is reserved for ERR_CERT_CONTAINS_ERRORS (not useful with WinHTTP).
+ CERT_STATUS_NO_REVOCATION_MECHANISM = 1 << 4,
+ CERT_STATUS_UNABLE_TO_CHECK_REVOCATION = 1 << 5,
+ CERT_STATUS_REVOKED = 1 << 6,
+ CERT_STATUS_INVALID = 1 << 7,
+
+ // Bits 16 to 30 are for non-error statuses.
+ CERT_STATUS_IS_EV = 1 << 16,
+ CERT_STATUS_REV_CHECKING_ENABLED = 1 << 17,
+
+ // 1 << 31 (the sign bit) is reserved so that the cert status will never be
+ // negative.
+};
+
+// Returns true if the specified cert status has an error set.
+static bool IsCertStatusError(int status) {
+ return (CERT_STATUS_ALL_ERRORS & status) != 0;
+}
+
+} // namespace net
+
+#endif // NET_BASE_CERT_STATUS_FLAGS_H__
diff --git a/net/base/client_socket.h b/net/base/client_socket.h
new file mode 100644
index 0000000..8661adc
--- /dev/null
+++ b/net/base/client_socket.h
@@ -0,0 +1,71 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_CLIENT_SOCKET_H_
+#define NET_BASE_CLIENT_SOCKET_H_
+
+#include "net/base/socket.h"
+
+namespace net {
+
+class ClientSocket : public Socket {
+ public:
+ // 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 be notified 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 Close method.
+ //
+ virtual int Connect(CompletionCallback* callback) = 0;
+
+ // If a non-fatal error occurs during Connect, the consumer can call this
+ // method to re-Connect ignoring the error that occured. This call is only
+ // valid for certain errors.
+ virtual int ReconnectIgnoringLastError(CompletionCallback* callback) = 0;
+
+ // Called to disconnect a connected socket. Does nothing if the socket is
+ // already disconnected. After calling Disconnect it is possible to call
+ // Connect again to establish a new connection.
+ virtual void Disconnect() = 0;
+
+ // Called to test if the socket is connected.
+ virtual bool IsConnected() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_BASE_CLIENT_SOCKET_H_
diff --git a/net/base/client_socket_factory.cc b/net/base/client_socket_factory.cc
new file mode 100644
index 0000000..932fc0a0
--- /dev/null
+++ b/net/base/client_socket_factory.cc
@@ -0,0 +1,57 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/client_socket_factory.h"
+
+#include "base/singleton.h"
+#include "net/base/ssl_client_socket.h"
+#include "net/base/tcp_client_socket.h"
+
+namespace net {
+
+class DefaultClientSocketFactory : public ClientSocketFactory {
+ public:
+ virtual ClientSocket* CreateTCPClientSocket(
+ const AddressList& addresses) {
+ return new TCPClientSocket(addresses);
+ }
+
+ virtual ClientSocket* CreateSSLClientSocket(
+ ClientSocket* transport_socket,
+ const std::string& hostname) {
+ return new SSLClientSocket(transport_socket, hostname);
+ }
+};
+
+// static
+ClientSocketFactory* ClientSocketFactory::GetDefaultFactory() {
+ return Singleton<DefaultClientSocketFactory>::get();
+}
+
+} // namespace net
diff --git a/net/base/client_socket_factory.h b/net/base/client_socket_factory.h
new file mode 100644
index 0000000..9a1b412
--- /dev/null
+++ b/net/base/client_socket_factory.h
@@ -0,0 +1,57 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_CLIENT_SOCKET_FACTORY_H_
+#define NET_BASE_CLIENT_SOCKET_FACTORY_H_
+
+#include <string>
+
+namespace net {
+
+class AddressList;
+class ClientSocket;
+
+// An interface used to instantiate ClientSocket objects. Used to facilitate
+// testing code with mock socket implementations.
+class ClientSocketFactory {
+ public:
+ virtual ClientSocket* CreateTCPClientSocket(
+ const AddressList& addresses) = 0;
+
+ virtual ClientSocket* CreateSSLClientSocket(
+ ClientSocket* transport_socket,
+ const std::string& hostname) = 0;
+
+ // Returns the default ClientSocketFactory.
+ static ClientSocketFactory* GetDefaultFactory();
+};
+
+} // namespace net
+
+#endif // NET_BASE_CLIENT_SOCKET_FACTORY_H_
diff --git a/net/base/completion_callback.h b/net/base/completion_callback.h
new file mode 100644
index 0000000..f9c600c
--- /dev/null
+++ b/net/base/completion_callback.h
@@ -0,0 +1,53 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_COMPLETION_CALLBACK_H__
+#define NET_BASE_COMPLETION_CALLBACK_H__
+
+#include "base/task.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 Callback1<int>::Type CompletionCallback;
+
+// Used to implement a CompletionCallback.
+template <class T>
+class CompletionCallbackImpl :
+ public CallbackImpl< T, void (T::*)(int), Tuple1<int> > {
+ public:
+ CompletionCallbackImpl(T* obj, void (T::* meth)(int))
+ : CallbackImpl< T, void (T::*)(int), Tuple1<int> >::CallbackImpl(obj, meth) {
+ }
+};
+
+} // namespace net
+
+#endif // NET_BASE_COMPLETION_CALLBACK_H__
diff --git a/net/base/cookie_monster.cc b/net/base/cookie_monster.cc
new file mode 100644
index 0000000..0483acb
--- /dev/null
+++ b/net/base/cookie_monster.cc
@@ -0,0 +1,1043 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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/base/cookie_monster.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_canon.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domain.h"
+
+// #define COOKIE_LOGGING_ENABLED
+#ifdef COOKIE_LOGGING_ENABLED
+#define COOKIE_DLOG(severity) DLOG_IF(INFO, 1)
+#else
+#define COOKIE_DLOG(severity) DLOG_IF(INFO, 0)
+#endif
+
+/*static*/ bool CookieMonster::enable_file_scheme_ = false;
+
+// static
+void CookieMonster::EnableFileScheme() {
+ enable_file_scheme_ = true;
+}
+
+CookieMonster::CookieMonster()
+ : initialized_(false),
+ store_(NULL) {
+}
+
+CookieMonster::CookieMonster(PersistentCookieStore* store)
+ : initialized_(false),
+ store_(store) {
+}
+
+CookieMonster::~CookieMonster() {
+ DeleteAll(false);
+}
+
+void CookieMonster::InitStore() {
+ DCHECK(store_) << "Store must exist to initialize";
+
+ // 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.
+ std::vector<KeyedCanonicalCookie> cookies;
+ store_->Load(&cookies);
+ for (std::vector<KeyedCanonicalCookie>::const_iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ InternalInsertCookie(it->first, it->second, false);
+ }
+}
+
+// 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));
+}
+
+// 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
+Time CookieMonster::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 !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~";
+
+ Time::Exploded exploded = {0};
+
+ 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 (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 &&
+ sscanf_s(token.c_str(), "%2hu:%2hu:%2hu", &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 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 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 Time();
+}
+
+// Determine the cookie domain key to use for setting the specified cookie.
+// On success returns true, and sets cookie_domain_key to either a
+// -host cookie key (ex: "google.com")
+// -domain cookie key (ex: ".google.com")
+static bool GetCookieDomainKey(const GURL& url,
+ const CookieMonster::ParsedCookie& pc,
+ std::string* cookie_domain_key) {
+ const std::string url_host(url.host());
+ if (!pc.HasDomain() || pc.Domain().empty()) {
+ // No domain was specified in cookie -- default to host cookie.
+ *cookie_domain_key = url_host;
+ DCHECK((*cookie_domain_key)[0] != '.');
+ return true;
+ }
+
+ // Get the normalized domain specified in cookie line.
+ // Note: The RFC says we can reject a cookie if the domain
+ // attribute does not start with a dot. IE/FF/Safari however, allow a cookie
+ // of the form domain=my.domain.com, treating it the same as
+ // domain=.my.domain.com -- for compatibility we do the same here. Firefox
+ // also treats domain=.....my.domain.com like domain=.my.domain.com, but
+ // neither IE nor Safari do this, and we don't either.
+ std::string cookie_domain(net_util::CanonicalizeHost(pc.Domain(), NULL));
+ 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_domain_and_registry(
+ RegistryControlledDomainService::GetDomainAndRegistry(url));
+ if (url_domain_and_registry.empty())
+ return false; // IP addresses/intranet hosts can't set domain cookies.
+ const std::string cookie_domain_and_registry(
+ RegistryControlledDomainService::GetDomainAndRegistry(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.
+ if ((url_host.length() < cookie_domain.length()) ?
+ (cookie_domain != ("." + url_host)) :
+ url_host.compare(url_host.length() - cookie_domain.length(),
+ cookie_domain.length(), cookie_domain))
+ return false;
+
+
+ *cookie_domain_key = cookie_domain;
+ return true;
+}
+
+static std::string CanonPath(const GURL& url,
+ const CookieMonster::ParsedCookie& pc) {
+ // 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 (pc.HasPath() && !pc.Path().empty() && pc.Path()[0] == '/')
+ return pc.Path();
+
+ // 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();
+
+ std::string::size_type 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);
+}
+
+static Time CanonExpiration(const CookieMonster::ParsedCookie& pc,
+ const Time& current) {
+ // First, try the Max-Age attribute.
+ uint64 max_age = 0;
+ if (pc.HasMaxAge() &&
+ sscanf_s(pc.MaxAge().c_str(), " %I64u", &max_age) == 1) {
+ return current + TimeDelta::FromSeconds(max_age);
+ }
+
+ // Try the Expires attribute.
+ if (pc.HasExpires())
+ return CookieMonster::ParseCookieTime(pc.Expires());
+
+ // Invalid or no expiration, persistent cookie.
+ return Time();
+}
+
+static bool HasCookieableScheme(const GURL& url) {
+ static const char* kCookieableSchemes[] = { "http", "https", "file" };
+ static const int kCookieableSchemesLen = arraysize(kCookieableSchemes);
+ static const int kCookieableSchemesFileIndex = 2;
+
+ // Make sure the request is on a cookie-able url scheme.
+ for (int i = 0; i < kCookieableSchemesLen; ++i) {
+ // We matched a scheme.
+ if (url.SchemeIs(kCookieableSchemes[i])) {
+ // This is file:// scheme
+ if (i == kCookieableSchemesFileIndex)
+ return CookieMonster::enable_file_scheme_;
+ // We've matched a supported scheme.
+ return true;
+ }
+ }
+
+ // The scheme didn't match any in our whitelist.
+ COOKIE_DLOG(WARNING) << "Unsupported cookie scheme: " << url.scheme();
+ return false;
+}
+
+bool CookieMonster::SetCookie(const GURL& url,
+ const std::string& cookie_line) {
+ Time creation_date = CurrentTime();
+ last_time_seen_ = creation_date;
+ return SetCookieWithCreationTime(url, cookie_line, creation_date);
+}
+
+bool CookieMonster::SetCookieWithCreationTime(const GURL& url,
+ const std::string& cookie_line,
+ const Time& creation_time) {
+ DCHECK(!creation_time.is_null());
+
+ if (!HasCookieableScheme(url)) {
+ DLOG(WARNING) << "Unsupported cookie scheme: " << url.scheme();
+ return false;
+ }
+
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ COOKIE_DLOG(INFO) << "SetCookie() line: " << cookie_line;
+
+ // Parse the cookie.
+ ParsedCookie pc(cookie_line);
+
+ if (!pc.IsValid()) {
+ COOKIE_DLOG(WARNING) << "Couldn't parse cookie";
+ return false;
+ }
+
+ std::string cookie_domain;
+ if (!GetCookieDomainKey(url, pc, &cookie_domain)) {
+ return false;
+ }
+
+ std::string cookie_path = CanonPath(url, pc);
+
+ scoped_ptr<CanonicalCookie> cc;
+ Time cookie_expires = CanonExpiration(pc, creation_time);
+
+ cc.reset(new CanonicalCookie(pc.Name(), pc.Value(), cookie_path,
+ pc.IsSecure(), pc.IsHttpOnly(),
+ creation_time, !cookie_expires.is_null(),
+ cookie_expires));
+
+ if (!cc.get()) {
+ COOKIE_DLOG(WARNING) << "Failed to allocate CanonicalCookie";
+ return false;
+ }
+
+ // We should have only purged at most one matching cookie.
+ int num_deleted = DeleteEquivalentCookies(cookie_domain, *cc);
+
+ COOKIE_DLOG(INFO) << "SetCookie() 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 (!cc->IsExpired(creation_time))
+ InternalInsertCookie(cookie_domain, cc.release(), true);
+
+ // 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, cookie_domain);
+
+ return true;
+}
+
+void CookieMonster::SetCookies(const GURL& url,
+ const std::vector<std::string>& cookies) {
+ for (std::vector<std::string>::const_iterator iter = cookies.begin();
+ iter != cookies.end(); ++iter)
+ SetCookie(url, *iter);
+}
+
+void CookieMonster::InternalInsertCookie(const std::string& key,
+ CanonicalCookie* cc,
+ bool sync_to_store) {
+ if (cc->IsPersistent() && store_ && sync_to_store)
+ store_->AddCookie(key, *cc);
+ cookies_.insert(CookieMap::value_type(key, cc));
+}
+
+void CookieMonster::InternalDeleteCookie(CookieMap::iterator it,
+ bool sync_to_store) {
+ CanonicalCookie* cc = it->second;
+ COOKIE_DLOG(INFO) << "InternalDeleteCookie() cc: " << cc->DebugString();
+ if (cc->IsPersistent() && store_ && sync_to_store)
+ store_->DeleteCookie(*cc);
+ cookies_.erase(it);
+ delete cc;
+}
+
+int CookieMonster::DeleteEquivalentCookies(const std::string& key,
+ const CanonicalCookie& ecc) {
+ int num_deleted = 0;
+ for (CookieMapItPair its = cookies_.equal_range(key);
+ its.first != its.second; ) {
+ CookieMap::iterator curit = its.first;
+ CanonicalCookie* cc = curit->second;
+ ++its.first;
+
+ // TODO while we're here, we might as well purge expired cookies too.
+
+ if (ecc.IsEquivalent(*cc)) {
+ InternalDeleteCookie(curit, true);
+ ++num_deleted;
+#ifdef NDEBUG
+ // We should only ever find a single equivalent cookie
+ break;
+#endif
+ }
+ }
+
+ // Our internal state should be consistent, we should never have more
+ // than one equivalent cookie, since they should overwrite each other.
+ DCHECK(num_deleted <= 1);
+
+ return num_deleted;
+}
+
+// TODO we should be sorting by last access time, however, right now
+// we're not saving an access time, so we're sorting by creation time.
+static bool OldestCookieSorter(const CookieMonster::CookieMap::iterator& it1,
+ const CookieMonster::CookieMap::iterator& it2) {
+ return it1->second->CreationDate() < it2->second->CreationDate();
+}
+
+// is vector::size_type always going to be size_t?
+int CookieMonster::GarbageCollectRange(const Time& current,
+ const CookieMapItPair& itpair,
+ size_t num_max, size_t num_purge) {
+ int num_deleted = 0;
+
+ // First, walk through and delete anything that's expired.
+ // Save a list of iterators to the ones that weren't expired
+ std::vector<CookieMap::iterator> cookie_its;
+ for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) {
+ CookieMap::iterator curit = it;
+ CanonicalCookie* cc = curit->second;
+ ++it;
+
+ if (cc->IsExpired(current)) {
+ InternalDeleteCookie(curit, true);
+ ++num_deleted;
+ } else {
+ cookie_its.push_back(curit);
+ }
+ }
+
+ if (cookie_its.size() > num_max) {
+ COOKIE_DLOG(INFO) << "GarbageCollectRange() Deep Garbage Collect.";
+ num_purge += cookie_its.size() - num_max;
+ // Sort the top N we want to purge.
+ std::partial_sort(cookie_its.begin(), cookie_its.begin() + num_purge,
+ cookie_its.end(), OldestCookieSorter);
+
+ // TODO should probably use an iterator and not an index.
+ for (size_t i = 0; i < num_purge; ++i) {
+ InternalDeleteCookie(cookie_its[i], true);
+ ++num_deleted;
+ }
+ }
+
+ return num_deleted;
+}
+
+// TODO Whenever we delete, check last_cur_utc_...
+int CookieMonster::GarbageCollect(const Time& current,
+ const std::string& key) {
+ // Based off of the Mozilla defaults
+ // 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 amount of cookies. We're just
+ // going through the garbage collection process less often.
+ static const size_t kNumCookiesPerHost = 70; // ~50 cookies
+ static const size_t kNumCookiesPerHostPurge = 20;
+ static const size_t kNumCookiesTotal = 1100; // ~1000 cookies
+ static const size_t kNumCookiesTotalPurge = 100;
+
+ int num_deleted = 0;
+
+ // Collect garbage for this key.
+ if (cookies_.count(key) > kNumCookiesPerHost) {
+ COOKIE_DLOG(INFO) << "GarbageCollect() key: " << key;
+ num_deleted += GarbageCollectRange(current, cookies_.equal_range(key),
+ kNumCookiesPerHost,
+ kNumCookiesPerHostPurge);
+ }
+
+ // Collect garbage for everything.
+ if (cookies_.size() > kNumCookiesTotal) {
+ COOKIE_DLOG(INFO) << "GarbageCollect() everything";
+ num_deleted += GarbageCollectRange(current,
+ CookieMapItPair(cookies_.begin(),
+ cookies_.end()),
+ kNumCookiesTotal, kNumCookiesTotalPurge);
+ }
+
+ return num_deleted;
+}
+
+int CookieMonster::DeleteAll(bool sync_to_store) {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ int num_deleted = 0;
+ for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
+ CookieMap::iterator curit = it;
+ ++it;
+ InternalDeleteCookie(curit, sync_to_store);
+ ++num_deleted;
+ }
+
+ return num_deleted;
+}
+
+int CookieMonster::DeleteAllCreatedBetween(const Time& delete_begin,
+ const Time& delete_end,
+ bool sync_to_store) {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ 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, sync_to_store);
+ ++num_deleted;
+ }
+ }
+
+ return num_deleted;
+}
+
+int CookieMonster::DeleteAllCreatedAfter(const Time& delete_begin,
+ bool sync_to_store) {
+ return DeleteAllCreatedBetween(delete_begin, Time(), sync_to_store);
+}
+
+bool CookieMonster::DeleteCookie(const std::string& domain,
+ const CanonicalCookie& cookie,
+ bool sync_to_store) {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ for (CookieMapItPair its = cookies_.equal_range(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, sync_to_store);
+ return true;
+ }
+ }
+ return false;
+}
+
+// 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.
+static bool CookieSorter(CookieMonster::CanonicalCookie* cc1,
+ CookieMonster::CanonicalCookie* cc2) {
+ if (cc1->Path().length() == cc2->Path().length())
+ return cc1->CreationDate() < cc2->CreationDate();
+ return cc1->Path().length() > cc2->Path().length();
+}
+
+std::string CookieMonster::GetCookies(const GURL& url) {
+ return GetCookiesWithOptions(url, NORMAL);
+}
+
+// Currently our cookie datastructure is based on Mozilla's approach. We have a
+// hash keyed on the cookie's domain, and for any query we walk down the domain
+// components and probe for cookies until we reach the TLD, where we stop.
+// For example, a.b.blah.com, we would probe
+// - a.b.blah.com
+// - .a.b.blah.com (TODO should we check this first or second?)
+// - .b.blah.com
+// - .blah.com
+// There are some alternative datastructures we could try, like a
+// search/prefix trie, where we reverse the hostname and query for all
+// keys that are a prefix of our hostname. I think the hash probing
+// should be fast and simple enough for now.
+std::string CookieMonster::GetCookiesWithOptions(const GURL& url,
+ CookieOptions options) {
+ if (!HasCookieableScheme(url)) {
+ DLOG(WARNING) << "Unsupported cookie scheme: " << url.scheme();
+ return std::string();
+ }
+
+ // Get the cookies for this host and its domain(s).
+ std::vector<CanonicalCookie*> cookies;
+ FindCookiesForHostAndDomain(url, options, &cookies);
+ std::sort(cookies.begin(), cookies.end(), CookieSorter);
+
+ std::string cookie_line;
+ for (std::vector<CanonicalCookie*>::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();
+ }
+
+ COOKIE_DLOG(INFO) << "GetCookies() result: " << cookie_line;
+
+ return cookie_line;
+}
+
+// TODO(deanm): We could have expired cookies that haven't been purged yet,
+// and exporting these would be inaccurate, for example in the cookie manager
+// it might show cookies that are actually expired already. We should do
+// a full garbage collection before ... There actually isn't a way to do
+// this right now (a forceful full GC), so we'll have to live with the
+// possibility of showing the user expired cookies. This shouldn't be very
+// common since most persistent cookies have a long lifetime.
+CookieMonster::CookieList CookieMonster::GetAllCookies() {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ CookieList cookie_list;
+
+ for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end(); ++it) {
+ cookie_list.push_back(CookieListPair(it->first, *it->second));
+ }
+
+ return cookie_list;
+}
+
+void CookieMonster::FindCookiesForHostAndDomain(
+ const GURL& url,
+ CookieOptions options,
+ std::vector<CanonicalCookie*>* cookies) {
+ AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ const Time current_time(CurrentTime());
+
+ // Query for the full host, For example: 'a.c.blah.com'.
+ std::string key(url.host());
+ FindCookiesForKey(key, url, options, current_time, cookies);
+
+ // See if we can search for domain cookies, i.e. if the host has a TLD + 1.
+ const std::string domain(
+ RegistryControlledDomainService::GetDomainAndRegistry(key));
+ if (domain.empty())
+ return;
+ DCHECK_LE(domain.length(), key.length());
+ DCHECK_EQ(0, key.compare(key.length() - domain.length(), domain.length(),
+ domain));
+
+ // Walk through the string and query at the dot points (GURL should have
+ // canonicalized the dots, so this should be safe). Stop once we reach the
+ // domain + registry; we can't write cookies past this point, and with some
+ // registrars other domains can, in which case we don't want to read their
+ // cookies.
+ for (key = "." + key; key.length() > domain.length(); ) {
+ FindCookiesForKey(key, url, options, current_time, cookies);
+ const size_t next_dot = key.find('.', 1); // Skip over leading dot.
+ key.erase(0, next_dot);
+ }
+}
+
+void CookieMonster::FindCookiesForKey(
+ const std::string& key,
+ const GURL& url,
+ CookieOptions options,
+ const Time& current,
+ std::vector<CanonicalCookie*>* cookies) {
+ bool secure = url.SchemeIsSecure();
+
+ 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)) {
+ InternalDeleteCookie(curit, true);
+ continue;
+ }
+
+ // Filter out HttpOnly cookies unless they where explicitly requested.
+ if ((options & INCLUDE_HTTPONLY) == 0 && cc->IsHttpOnly())
+ continue;
+
+ // Filter out secure cookies unless we're https.
+ if (!secure && cc->IsSecure())
+ continue;
+
+ if (!cc->IsOnPath(url.path()))
+ continue;
+
+ // Congratulations Charlie, you passed the test!
+ cookies->push_back(cc);
+ }
+}
+
+
+CookieMonster::ParsedCookie::ParsedCookie(const std::string& cookie_line)
+ : is_valid_(false),
+ path_index_(0),
+ domain_index_(0),
+ expires_index_(0),
+ maxage_index_(0),
+ secure_index_(0),
+ httponly_index_(0) {
+
+ if (cookie_line.size() > kMaxCookieSize) {
+ LOG(INFO) << "Not parsing cookie, too large: " << cookie_line.size();
+ return;
+ }
+
+ ParseTokenValuePairs(cookie_line);
+ if (pairs_.size() > 0) {
+ is_valid_ = true;
+ SetupAttributes();
+ }
+}
+
+// Returns true if |c| occurs in |chars|
+// TODO maybe make this take an iterator, could check for end also?
+static 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.
+static 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.
+static 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;
+}
+static 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;
+}
+
+// Parse all token/value pairs and populate pairs_.
+void CookieMonster::ParsedCookie::ParseTokenValuePairs(
+ const std::string& cookie_line) {
+ static const char kTerminator[] = "\n\r\0";
+ static const int kTerminatorLen = sizeof(kTerminator) - 1;
+ static const char kWhitespace[] = " \t";
+ static const char kQuoteTerminator[] = "\"";
+ static const char kValueSeparator[] = ";";
+ static const char kTokenSeparator[] = ";=";
+
+ 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 end = cookie_line.end();
+ std::string::const_iterator it = start;
+
+ // TODO Make sure we're stripping \r\n in the network code. Then we
+ // can log any unexpected terminators.
+ std::string::size_type term_pos = cookie_line.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 = start + term_pos;
+ }
+
+ for (int pair_num = 0; pair_num < kMaxPairs && it != end; ++pair_num) {
+ TokenValuePair pair;
+ std::string::const_iterator token_start, token_real_end, token_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))
+ break; // 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;
+
+ 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;
+
+ // 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;
+
+ // The value is double quoted, process <quoted-string>.
+ if (it != end && *it == '"') {
+ // Skip over the first double quote, and parse until
+ // a terminating double quote or the end.
+ for (++it; it != end && !CharIsA(*it, kQuoteTerminator); ++it) {
+ // Allow an escaped \" in a double quoted string.
+ if (*it == '\\') {
+ ++it;
+ if (it == end)
+ break;
+ }
+ }
+
+ SeekTo(&it, end, kValueSeparator);
+ // We could seek to the end, that's ok.
+ value_end = it;
+ } else {
+ // The value is non-quoted, process <token-value>.
+ // Just look for ';' to terminate ('=' allowed).
+ // We can hit the end, maybe they didn't terminate.
+ SeekTo(&it, end, kValueSeparator);
+
+ // Ignore any whitespace between the value and the value separator
+ if (it != value_start) { // Could have an empty value
+ --it;
+ SeekBackPast(&it, value_start, kWhitespace);
+ ++it;
+ }
+
+ value_end = it;
+ }
+
+ // OK, we're finished with a Token/Value.
+ pair.second = std::string(value_start, value_end);
+ // 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;
+ }
+}
+
+void CookieMonster::ParsedCookie::SetupAttributes() {
+ static const char kPathTokenName[] = "path";
+ static const char kDomainTokenName[] = "domain";
+ static const char kExpiresTokenName[] = "expires";
+ static const char kMaxAgeTokenName[] = "max-age";
+ static const char kSecureTokenName[] = "secure";
+ static const char kHttpOnlyTokenName[] = "httponly";
+
+ // 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 { /* some attribute we don't know or don't care about. */ }
+ }
+}
+
+// Create a cookie-line for the cookie. For debugging only!
+// If we want to use this for something more than debugging, we
+// should rewrite it better...
+std::string CookieMonster::ParsedCookie::DebugString() const {
+ std::string out;
+ for (PairList::const_iterator it = pairs_.begin();
+ it != pairs_.end(); ++it) {
+ out.append(it->first);
+ out.append("=");
+ out.append(it->second);
+ out.append("; ");
+ }
+ return out;
+}
+
+bool CookieMonster::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 it into 3 cases, if it's strings lengths
+ // are less than, equal, or greater. I think this 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;
+}
+
+std::string CookieMonster::CanonicalCookie::DebugString() const {
+ return StringPrintf("name: %s value: %s path: %s creation: %llu",
+ name_.c_str(), value_.c_str(), path_.c_str(),
+ creation_date_.ToTimeT());
+}
diff --git a/net/base/cookie_monster.h b/net/base/cookie_monster.h
new file mode 100644
index 0000000..cf7f2a6
--- /dev/null
+++ b/net/base/cookie_monster.h
@@ -0,0 +1,331 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Brought to you by the letter D and the number 2.
+
+#ifndef NET_BASE_COOKIE_MONSTER_H__
+#define NET_BASE_COOKIE_MONSTER_H__
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/lock.h"
+#include "base/time.h"
+
+class GURL;
+
+// 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.
+//
+// TODO(deanm) Implement CookieMonster, the cookie database.
+// - Verify that our domain enforcement and non-dotted handling is correct
+// - Currently garbage collection is done on oldest CreationUTC, Mozilla
+// purges cookies on last access time, which would require adding and
+// keeping track of access times on a CanonicalCookie
+class CookieMonster {
+ public:
+ class ParsedCookie;
+ class CanonicalCookie;
+ class PersistentCookieStore;
+
+ // 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.
+ typedef std::multimap<std::string, CanonicalCookie*> CookieMap;
+ typedef std::pair<CookieMap::iterator, CookieMap::iterator> CookieMapItPair;
+ typedef std::pair<std::string, CanonicalCookie*> KeyedCanonicalCookie;
+ typedef std::pair<std::string, CanonicalCookie> CookieListPair;
+ typedef std::vector<CookieListPair> CookieList;
+
+ enum CookieOptions {
+ // Normal cookie behavior, decides which cookies to return based on
+ // the URL and whether it's https, etc. Never returns HttpOnly cookies
+ NORMAL = 0,
+ // Include HttpOnly cookies
+ INCLUDE_HTTPONLY = 1,
+ };
+
+ CookieMonster();
+
+ // 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.
+ CookieMonster(PersistentCookieStore* store);
+
+ ~CookieMonster();
+
+ // Parse the string with the cookie time (very forgivingly).
+ static Time ParseCookieTime(const std::string& time_string);
+
+ // Set a single cookie. Expects a cookie line, like "a=1; domain=b.com".
+ bool SetCookie(const GURL& url, const std::string& cookie_line);
+ // Sets a single cookie with a specific creation date. To set a cookie with
+ // a creation date of Now() use SetCookie() instead (it calls this function
+ // internally).
+ bool SetCookieWithCreationTime(const GURL& url,
+ const std::string& cookie_line,
+ const Time& creation_time);
+ // Set a vector of response cookie values for the same URL.
+ void SetCookies(const GURL& url, const std::vector<std::string>& cookies);
+
+ // 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?
+ // Simple interface, get a cookie string "a=b; c=d" for the given URL.
+ // It will _not_ return httponly cookies, see GetCookiesWithOptions
+ std::string GetCookies(const GURL& url);
+ std::string GetCookiesWithOptions(const GURL& url, CookieOptions options);
+ // Returns all the cookies, for use in management UI, etc.
+ CookieList GetAllCookies();
+
+ // Delete all of the cookies.
+ int DeleteAll(bool sync_to_store);
+ // Delete all of the cookies that have a creation_date greater than or equal
+ // to |delete_begin| and less than |delete_end|
+ int DeleteAllCreatedBetween(const Time& delete_begin,
+ const Time& delete_end,
+ bool sync_to_store);
+ // Delete all of the cookies that have a creation_date more recent than the
+ // one passed into the function via |delete_after|.
+ int DeleteAllCreatedAfter(const Time& delete_begin, bool sync_to_store);
+
+ // Delete one specific cookie.
+ bool DeleteCookie(const std::string& domain,
+ const CanonicalCookie& cookie,
+ bool sync_to_store);
+
+ // 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.
+ static void EnableFileScheme();
+ static bool enable_file_scheme_;
+
+ private:
+ // 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_)
+ InitStore();
+ initialized_ = true;
+ }
+ }
+
+ // Initializes the backing store and reads existing cookies from it.
+ // Should only be called by InitIfNecessary().
+ void InitStore();
+
+ void FindCookiesForHostAndDomain(const GURL& url,
+ CookieOptions options,
+ std::vector<CanonicalCookie*>* cookies);
+
+ void FindCookiesForKey(const std::string& key,
+ const GURL& url,
+ CookieOptions options,
+ const Time& current,
+ std::vector<CanonicalCookie*>* cookies);
+
+ int DeleteEquivalentCookies(const std::string& key,
+ const CanonicalCookie& ecc);
+
+ void InternalInsertCookie(const std::string& key,
+ CanonicalCookie* cc,
+ bool sync_to_store);
+
+ void InternalDeleteCookie(CookieMap::iterator it, bool sync_to_store);
+
+ // Enforce cookie maximum limits, purging expired and old cookies if needed
+ int GarbageCollect(const Time& current, const std::string& key);
+ int GarbageCollectRange(const Time& current,
+ const CookieMapItPair& itpair,
+ size_t num_max,
+ size_t num_purge);
+
+ CookieMap cookies_;
+
+ // Indicates whether the cookie store has been initialized. This happens
+ // lazily in InitStoreIfNecessary().
+ bool initialized_;
+
+ PersistentCookieStore* store_;
+
+ // The resolution of our time isn't enough, so we do something
+ // ugly and increment when we've seen the same time twice.
+ Time CurrentTime();
+ Time last_time_seen_;
+
+ // Lock for thread-safety
+ Lock lock_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CookieMonster);
+};
+
+class CookieMonster::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 int 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 on the class if !IsValid
+ bool IsValid() const { return is_valid_; }
+
+ 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; }
+
+ // For debugging only!
+ std::string DebugString() const;
+
+ private:
+ void ParseTokenValuePairs(const std::string& cookie_line);
+ void SetupAttributes();
+
+ 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_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CookieMonster::ParsedCookie);
+};
+
+
+class CookieMonster::CanonicalCookie {
+ public:
+ CanonicalCookie(const std::string& name, const std::string& value,
+ const std::string& path, bool secure,
+ bool httponly, const Time& creation,
+ bool has_expires, const Time& expires)
+ : name_(name),
+ value_(value),
+ path_(path),
+ secure_(secure),
+ httponly_(httponly),
+ creation_date_(creation),
+ has_expires_(has_expires),
+ expiry_date_(expires) {
+ }
+
+ // Supports the default copy constructor.
+
+ const std::string& Name() const { return name_; }
+ const std::string& Value() const { return value_; }
+ const std::string& Path() const { return path_; }
+ const Time& CreationDate() const { return creation_date_; }
+ bool DoesExpire() const { return has_expires_; }
+ bool IsPersistent() const { return DoesExpire(); }
+ const Time& ExpiryDate() const { return expiry_date_; }
+ bool IsSecure() const { return secure_; }
+ bool IsHttpOnly() const { return httponly_; }
+
+ bool IsExpired(const Time& current) {
+ return has_expires_ && current >= expiry_date_;
+ }
+
+ // Are the cookies considered equivalent in the eyes of the RFC.
+ // This says that the domain and path should string match identically.
+ 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.
+ return name_ == ecc.Name() && path_ == ecc.Path();
+ }
+
+ bool IsOnPath(const std::string& url_path) const;
+
+ std::string DebugString() const;
+ private:
+ std::string name_;
+ std::string value_;
+ std::string path_;
+ Time creation_date_;
+ bool has_expires_;
+ Time expiry_date_;
+ bool secure_;
+ bool httponly_;
+};
+
+class CookieMonster::PersistentCookieStore {
+ public:
+ virtual ~PersistentCookieStore() { }
+
+ // Initializes the store and retrieves the existing cookies. This will be
+ // called only once at startup.
+ virtual bool Load(std::vector<CookieMonster::KeyedCanonicalCookie>*) = 0;
+
+ virtual void AddCookie(const std::string&, const CanonicalCookie&) = 0;
+ virtual void DeleteCookie(const CanonicalCookie&) = 0;
+
+ protected:
+ PersistentCookieStore() { }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(CookieMonster::PersistentCookieStore);
+};
+
+#endif // NET_BASE_COOKIE_MONSTER_H__
diff --git a/net/base/cookie_monster_perftest.cc b/net/base/cookie_monster_perftest.cc
new file mode 100644
index 0000000..d5d93a7
--- /dev/null
+++ b/net/base/cookie_monster_perftest.cc
@@ -0,0 +1,120 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/perftimer.h"
+#include "base/string_util.h"
+#include "net/base/cookie_monster.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+ class ParsedCookieTest : public testing::Test { };
+ class CookieMonsterTest : public testing::Test { };
+}
+
+static const int kNumCookies = 20000;
+static const char kCookieLine[] = "A = \"b=;\\\"\" ;secure;;; httponly";
+
+TEST(ParsedCookieTest, TestParseCookies) {
+ std::string cookie(kCookieLine);
+ PerfTimeLogger timer("Parsed_cookie_parse_cookies");
+ for (int i = 0; i < kNumCookies; ++i) {
+ CookieMonster::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) {
+ CookieMonster::ParsedCookie pc(cookie);
+ EXPECT_TRUE(pc.IsValid());
+ }
+ timer.Done();
+}
+
+static const GURL kUrlGoogle("http://www.google.izzle");
+
+TEST(CookieMonsterTest, TestAddCookiesOnSingleHost) {
+ CookieMonster cm;
+ std::vector<std::string> cookies;
+ for (int i = 0; i < kNumCookies; i++) {
+ cookies.push_back(StringPrintf("a%03d=b", i));
+ }
+
+ // 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) {
+ EXPECT_TRUE(cm.SetCookie(kUrlGoogle, *it));
+ }
+ timer.Done();
+
+ PerfTimeLogger timer2("Cookie_monster_query_single_host");
+ for (std::vector<std::string>::const_iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ cm.GetCookies(kUrlGoogle);
+ }
+ timer2.Done();
+
+ PerfTimeLogger timer3("Cookie_monster_deleteall_single_host");
+ cm.DeleteAll(false);
+ timer3.Done();
+}
+
+TEST(CookieMonsterTest, TestAddCookieOnManyHosts) {
+ CookieMonster cm;
+ std::string cookie(kCookieLine);
+ std::vector<GURL> gurls; // just wanna have ffffuunnn
+ for (int i = 0; i < kNumCookies; ++i) {
+ gurls.push_back(GURL(StringPrintf("http://a%04d.izzle", i)));
+ }
+
+ // 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) {
+ EXPECT_TRUE(cm.SetCookie(*it, cookie));
+ }
+ timer.Done();
+
+ PerfTimeLogger timer2("Cookie_monster_query_many_hosts");
+ for (std::vector<GURL>::const_iterator it = gurls.begin();
+ it != gurls.end(); ++it) {
+ cm.GetCookies(*it);
+ }
+ timer2.Done();
+
+ PerfTimeLogger timer3("Cookie_monster_deleteall_many_hosts");
+ cm.DeleteAll(false);
+ timer3.Done();
+}
diff --git a/net/base/cookie_monster_unittest.cc b/net/base/cookie_monster_unittest.cc
new file mode 100644
index 0000000..fbe1019
--- /dev/null
+++ b/net/base/cookie_monster_unittest.cc
@@ -0,0 +1,849 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <windows.h>
+#include <time.h>
+
+#include <string>
+
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/cookie_monster.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+ class ParsedCookieTest : public testing::Test { };
+ class CookieMonsterTest : public testing::Test { };
+}
+
+
+TEST(ParsedCookieTest, TestBasic) {
+ CookieMonster::ParsedCookie pc("a=b");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_EQ(pc.Name(), "a");
+ EXPECT_EQ(pc.Value(), "b");
+}
+
+TEST(ParsedCookieTest, TestQuoted) {
+ CookieMonster::ParsedCookie pc("a=\"b=;\"; path=\"/\"");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ(pc.Name(), "a");
+ EXPECT_EQ(pc.Value(), "\"b=;\"");
+ // 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) {
+ CookieMonster::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(pc.Value(), "BLAHHH");
+}
+
+TEST(ParsedCookieTest, TestAttributeCase) {
+ CookieMonster::ParsedCookie pc("BLAHHH; Path=/; sECuRe; httpONLY");
+ 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(pc.Value(), "BLAHHH");
+}
+
+TEST(ParsedCookieTest, TestDoubleQuotedNameless) {
+ CookieMonster::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(pc.Value(), "\"BLA\\\"HHH\"");
+}
+
+TEST(ParsedCookieTest, QuoteOffTheEnd) {
+ CookieMonster::ParsedCookie pc("a=\"B");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ(pc.Name(), "a");
+ EXPECT_EQ(pc.Value(), "\"B");
+}
+
+TEST(ParsedCookieTest, MissingName) {
+ CookieMonster::ParsedCookie pc("=ABC");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ(pc.Name(), "");
+ EXPECT_EQ(pc.Value(), "ABC");
+}
+
+TEST(ParsedCookieTest, MissingValue) {
+ CookieMonster::ParsedCookie pc("ABC=; path = /wee");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ(pc.Name(), "ABC");
+ EXPECT_EQ(pc.Value(), "");
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ(pc.Path(), "/wee");
+}
+
+TEST(ParsedCookieTest, Whitespace) {
+ CookieMonster::ParsedCookie pc(" A = BC ;secure;;; httponly");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ(pc.Name(), "A");
+ EXPECT_EQ(pc.Value(), "BC");
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+}
+TEST(ParsedCookieTest, MultipleEquals) {
+ CookieMonster::ParsedCookie pc(" A=== BC ;secure;;; httponly");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ(pc.Name(), "A");
+ EXPECT_EQ(pc.Value(), "== BC");
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+}
+
+TEST(ParsedCookieTest, TrailingWhitespace) {
+ CookieMonster::ParsedCookie pc("ANCUUID=zohNumRKgI0oxyhSsV3Z7D; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; "
+ "path=/ ; ");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ(pc.Name(), "ANCUUID");
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ(pc.Path(), "/");
+ // TODO should export like NumAttributes() and make sure that the
+ // trailing whitespace doesn't end up as an empty attribute or something.
+}
+
+TEST(ParsedCookieTest, TooManyPairs) {
+ std::string blankpairs;
+ blankpairs.resize(CookieMonster::ParsedCookie::kMaxPairs - 1, ';');
+
+ CookieMonster::ParsedCookie pc1(blankpairs + "secure");
+ EXPECT_TRUE(pc1.IsValid());
+ EXPECT_TRUE(pc1.IsSecure());
+
+ CookieMonster::ParsedCookie pc2(blankpairs + ";secure");
+ EXPECT_TRUE(pc2.IsValid());
+ EXPECT_FALSE(pc2.IsSecure());
+}
+
+// TODO some better test cases for invalid cookies.
+TEST(ParsedCookieTest, InvalidWhitespace) {
+ CookieMonster::ParsedCookie pc(" ");
+ EXPECT_FALSE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, InvalidTooLong) {
+ std::string maxstr;
+ maxstr.resize(CookieMonster::ParsedCookie::kMaxCookieSize, 'a');
+
+ CookieMonster::ParsedCookie pc1(maxstr);
+ EXPECT_TRUE(pc1.IsValid());
+
+ CookieMonster::ParsedCookie pc2(maxstr + "A");
+ EXPECT_FALSE(pc2.IsValid());
+}
+
+TEST(ParsedCookieTest, InvalidEmpty) {
+ CookieMonster::ParsedCookie pc("");
+ EXPECT_FALSE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, EmbeddedTerminator) {
+ CookieMonster::ParsedCookie pc1("AAA=BB\0ZYX");
+ CookieMonster::ParsedCookie pc2("AAA=BB\rZYX");
+ CookieMonster::ParsedCookie pc3("AAA=BB\nZYX");
+ EXPECT_TRUE(pc1.IsValid());
+ EXPECT_EQ(pc1.Name(), "AAA");
+ EXPECT_EQ(pc1.Value(), "BB");
+ EXPECT_TRUE(pc2.IsValid());
+ EXPECT_EQ(pc2.Name(), "AAA");
+ EXPECT_EQ(pc2.Value(), "BB");
+ EXPECT_TRUE(pc3.IsValid());
+ EXPECT_EQ(pc3.Name(), "AAA");
+ EXPECT_EQ(pc3.Value(), "BB");
+}
+
+static const char kUrlGoogle[] = "http://www.google.izzle";
+static const char kUrlGoogleSecure[] = "https://www.google.izzle";
+static const char kUrlFtp[] = "ftp://ftp.google.izzle/";
+static const char kValidCookieLine[] = "A=B; path=/";
+static const char kValidDomainCookieLine[] = "A=B; path=/; domain=google.izzle";
+
+TEST(CookieMonsterTest, DomainTest) {
+ GURL url_google(kUrlGoogle);
+
+ CookieMonster cm;
+ EXPECT_TRUE(cm.SetCookie(url_google, "A=B"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+ EXPECT_TRUE(cm.SetCookie(url_google, "C=D; domain=.google.izzle"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B; C=D");
+
+ // Verify that A=B was set as a host cookie rather than a domain
+ // cookie -- should not be accessible from a sub sub-domain.
+ EXPECT_EQ(cm.GetCookies(GURL("http://foo.www.google.izzle")), "C=D");
+
+ // Test and make sure we find domain cookies on the same domain.
+ EXPECT_TRUE(cm.SetCookie(url_google, "E=F; domain=.www.google.izzle"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B; C=D; E=F");
+
+ // 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(cm.SetCookie(url_google, "G=H; domain=www.google.izzle"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B; C=D; E=F; G=H");
+
+ // Test domain enforcement, should fail on a sub-domain or something too deep.
+ EXPECT_FALSE(cm.SetCookie(url_google, "I=J; domain=.izzle"));
+ EXPECT_EQ(cm.GetCookies(GURL("http://a.izzle")), "");
+ EXPECT_FALSE(cm.SetCookie(url_google, "K=L; domain=.bla.www.google.izzle"));
+ EXPECT_EQ(cm.GetCookies(GURL("http://bla.www.google.izzle")),
+ "C=D; E=F; G=H");
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B; C=D; E=F; G=H");
+}
+
+// FireFox recognizes domains containing trailing periods as valid.
+// IE and Safari do not. Assert the expected policy here.
+TEST(CookieMonsterTest, DomainWithTrailingDotTest) {
+ CookieMonster cm;
+ GURL url_google("http://www.google.com");
+
+ EXPECT_FALSE(cm.SetCookie(url_google, "a=1; domain=.www.google.com."));
+ EXPECT_FALSE(cm.SetCookie(url_google, "b=2; domain=.www.google.com.."));
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+}
+
+// Test that cookies can bet set on higher level domains.
+// http://b/issue?id=896491
+TEST(CookieMonsterTest, ValidSubdomainTest) {
+ CookieMonster cm;
+ 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(cm.SetCookie(url_abcd, "a=1; domain=.a.b.c.d.com"));
+ EXPECT_TRUE(cm.SetCookie(url_abcd, "b=2; domain=.b.c.d.com"));
+ EXPECT_TRUE(cm.SetCookie(url_abcd, "c=3; domain=.c.d.com"));
+ EXPECT_TRUE(cm.SetCookie(url_abcd, "d=4; domain=.d.com"));
+
+ EXPECT_EQ(cm.GetCookies(url_abcd), "a=1; b=2; c=3; d=4");
+ EXPECT_EQ(cm.GetCookies(url_bcd), "b=2; c=3; d=4");
+ EXPECT_EQ(cm.GetCookies(url_cd), "c=3; d=4");
+ EXPECT_EQ(cm.GetCookies(url_d), "d=4");
+
+ // Check that the same cookie can exist on different sub-domains.
+ EXPECT_TRUE(cm.SetCookie(url_bcd, "X=bcd; domain=.b.c.d.com"));
+ EXPECT_TRUE(cm.SetCookie(url_bcd, "X=cd; domain=.c.d.com"));
+ EXPECT_EQ(cm.GetCookies(url_bcd), "b=2; c=3; d=4; X=bcd; X=cd");
+ EXPECT_EQ(cm.GetCookies(url_cd), "c=3; d=4; X=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
+TEST(CookieMonsterTest, InvalidDomainTest) {
+ {
+ CookieMonster cm;
+ GURL url_foobar("http://foo.bar.com");
+
+ // More specific sub-domain than allowed.
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "a=1; domain=.yo.foo.bar.com"));
+
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "b=2; domain=.foo.com"));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "c=3; domain=.bar.foo.com"));
+
+ // Different TLD, but the rest is a substring.
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "d=4; domain=.foo.bar.com.net"));
+
+ // A substring that isn't really a parent domain.
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "e=5; domain=ar.com"));
+
+ // Completely invalid domains:
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "f=6; domain=."));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "g=7; domain=/"));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "h=8; domain=http://foo.bar.com"));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "i=9; domain=..foo.bar.com"));
+ EXPECT_FALSE(cm.SetCookie(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(cm.SetCookie(url_foobar, "k=11; domain=.foo.bar.com?blah"));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "l=12; domain=.foo.bar.com/blah"));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "m=13; domain=.foo.bar.com:80"));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "n=14; domain=.foo.bar.com:"));
+ EXPECT_FALSE(cm.SetCookie(url_foobar, "o=15; domain=.foo.bar.com#sup"));
+
+ EXPECT_EQ(cm.GetCookies(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.
+ CookieMonster cm;
+ GURL url_foocom("http://foo.com.com");
+ EXPECT_FALSE(cm.SetCookie(url_foocom, "a=1; domain=.foo.com.com.com"));
+ EXPECT_EQ(cm.GetCookies(url_foocom), "");
+ }
+}
+
+// Test the behavior of omitting dot prefix from domain, should
+// function the same as FireFox.
+// http://b/issue?id=889898
+TEST(CookieMonsterTest, DomainWithoutLeadingDotTest) {
+ { // The omission of dot results in setting a domain cookie.
+ CookieMonster cm;
+ GURL url_hosted("http://manage.hosted.filefront.com");
+ GURL url_filefront("http://www.filefront.com");
+ EXPECT_TRUE(cm.SetCookie(url_hosted, "sawAd=1; domain=filefront.com"));
+ EXPECT_EQ(cm.GetCookies(url_hosted), "sawAd=1");
+ EXPECT_EQ(cm.GetCookies(url_filefront), "sawAd=1");
+ }
+
+ { // Even when the domains match exactly, don't consider it host cookie.
+ CookieMonster cm;
+ GURL url("http://www.google.com");
+ EXPECT_TRUE(cm.SetCookie(url, "a=1; domain=www.google.com"));
+ EXPECT_EQ(cm.GetCookies(url), "a=1");
+ EXPECT_EQ(cm.GetCookies(GURL("http://sub.www.google.com")), "a=1");
+ EXPECT_EQ(cm.GetCookies(GURL("http://something-else.com")), "");
+ }
+}
+
+// Test that the domain specified in cookie string is treated case-insensitive
+// http://b/issue?id=896475.
+TEST(CookieMonsterTest, CaseInsensitiveDomainTest) {
+ CookieMonster cm;
+ GURL url_google("http://www.google.com");
+ EXPECT_TRUE(cm.SetCookie(url_google, "a=1; domain=.GOOGLE.COM"));
+ EXPECT_TRUE(cm.SetCookie(url_google, "b=2; domain=.wWw.gOOgLE.coM"));
+ EXPECT_EQ(cm.GetCookies(url_google), "a=1; b=2");
+}
+
+TEST(CookieMonsterTest, TestIpAddress) {
+ GURL url_ip("http://1.2.3.4/weee");
+ {
+ CookieMonster cm;
+ EXPECT_TRUE(cm.SetCookie(url_ip, kValidCookieLine));
+ EXPECT_EQ(cm.GetCookies(url_ip), "A=B");
+ }
+
+ { // IP addresses should not be able to set domain cookies.
+ CookieMonster cm;
+ EXPECT_FALSE(cm.SetCookie(url_ip, "b=2; domain=.1.2.3.4"));
+ EXPECT_FALSE(cm.SetCookie(url_ip, "c=3; domain=.3.4"));
+ EXPECT_EQ(cm.GetCookies(url_ip), "");
+ }
+}
+
+// Test host cookies, and setting of cookies on TLD.
+TEST(CookieMonsterTest, TestNonDottedAndTLD) {
+ {
+ CookieMonster cm;
+ GURL url("http://com/");
+ // Allow setting on "com", (but only as a host cookie).
+ EXPECT_TRUE(cm.SetCookie(url, "a=1"));
+ EXPECT_FALSE(cm.SetCookie(url, "b=2; domain=.com"));
+ EXPECT_FALSE(cm.SetCookie(url, "c=3; domain=com"));
+ EXPECT_EQ(cm.GetCookies(url), "a=1");
+ // Make sure it doesn't show up for a normal .com, it should be a host
+ // not a domain cookie.
+ EXPECT_EQ(cm.GetCookies(GURL("http://hopefully-no-cookies.com/")), "");
+ EXPECT_EQ(cm.GetCookies(GURL("http://.com/")), "");
+ }
+
+ { // http://com. should be treated the same as http://com.
+ CookieMonster cm;
+ GURL url("http://com./index.html");
+ EXPECT_TRUE(cm.SetCookie(url, "a=1"));
+ EXPECT_EQ(cm.GetCookies(url), "a=1");
+ EXPECT_EQ(cm.GetCookies(GURL("http://hopefully-no-cookies.com./")), "");
+ }
+
+ { // Should not be able to set host cookie from a subdomain.
+ CookieMonster cm;
+ GURL url("http://a.b");
+ EXPECT_FALSE(cm.SetCookie(url, "a=1; domain=.b"));
+ EXPECT_FALSE(cm.SetCookie(url, "b=2; domain=b"));
+ EXPECT_EQ(cm.GetCookies(url), "");
+ }
+
+ { // Same test as above, but explicitly on a known TLD (com).
+ CookieMonster cm;
+ GURL url("http://google.com");
+ EXPECT_FALSE(cm.SetCookie(url, "a=1; domain=.com"));
+ EXPECT_FALSE(cm.SetCookie(url, "b=2; domain=com"));
+ EXPECT_EQ(cm.GetCookies(url), "");
+ }
+
+ { // Make sure can't set cookie on TLD which is dotted.
+ CookieMonster cm;
+ GURL url("http://google.co.uk");
+ EXPECT_FALSE(cm.SetCookie(url, "a=1; domain=.co.uk"));
+ EXPECT_FALSE(cm.SetCookie(url, "b=2; domain=.uk"));
+ EXPECT_EQ(cm.GetCookies(url), "");
+ EXPECT_EQ(cm.GetCookies(GURL("http://something-else.co.uk")), "");
+ EXPECT_EQ(cm.GetCookies(GURL("http://something-else.uk")), "");
+ }
+
+ { // Intranet URLs should only be able to set host cookies.
+ CookieMonster cm;
+ GURL url("http://b");
+ EXPECT_TRUE(cm.SetCookie(url, "a=1"));
+ EXPECT_FALSE(cm.SetCookie(url, "b=2; domain=.b"));
+ EXPECT_FALSE(cm.SetCookie(url, "c=3; domain=b"));
+ EXPECT_EQ(cm.GetCookies(url), "a=1");
+ }
+}
+
+// Test reading/writing cookies when the domain ends with a period,
+// as in "www.google.com."
+TEST(CookieMonsterTest, TestHostEndsWithDot) {
+ CookieMonster cm;
+ GURL url("http://www.google.com");
+ GURL url_with_dot("http://www.google.com.");
+ EXPECT_TRUE(cm.SetCookie(url, "a=1"));
+ EXPECT_EQ(cm.GetCookies(url), "a=1");
+
+ // 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(cm.SetCookie(url, "b=2; domain=.www.google.com."));
+ EXPECT_EQ(cm.GetCookies(url), "a=1");
+
+ EXPECT_TRUE(cm.SetCookie(url_with_dot, "b=2; domain=.google.com."));
+ EXPECT_EQ(cm.GetCookies(url_with_dot), "b=2");
+
+ // Make sure there weren't any side effects.
+ EXPECT_EQ(cm.GetCookies(GURL("http://hopefully-no-cookies.com/")), "");
+ EXPECT_EQ(cm.GetCookies(GURL("http://.com/")), "");
+}
+
+TEST(CookieMonsterTest, InvalidScheme) {
+ CookieMonster cm;
+ EXPECT_FALSE(cm.SetCookie(GURL(kUrlFtp), kValidCookieLine));
+}
+
+TEST(CookieMonsterTest, InvalidScheme_Read) {
+ CookieMonster cm;
+ EXPECT_TRUE(cm.SetCookie(GURL(kUrlGoogle), kValidDomainCookieLine));
+ EXPECT_EQ(cm.GetCookies(GURL(kUrlFtp)), "");
+}
+
+TEST(CookieMonsterTest, PathTest) {
+ std::string url("http://www.google.izzle");
+ CookieMonster cm;
+ EXPECT_TRUE(cm.SetCookie(GURL(url), "A=B; path=/wee"));
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/wee")), "A=B");
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/wee/")), "A=B");
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/wee/war")), "A=B");
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/wee/war/more/more")), "A=B");
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/weehee")), "");
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/")), "");
+
+ // If we add a 0 length path, it should default to /
+ EXPECT_TRUE(cm.SetCookie(GURL(url), "A=C; path="));
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/wee")), "A=B; A=C");
+ EXPECT_EQ(cm.GetCookies(GURL(url + "/")), "A=C");
+}
+
+TEST(CookieMonsterTest, HttpOnlyTest) {
+ GURL url_google(kUrlGoogle);
+ CookieMonster cm;
+ EXPECT_TRUE(cm.SetCookie(url_google, "A=B; httponly"));
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+ EXPECT_EQ(cm.GetCookiesWithOptions(url_google,
+ CookieMonster::INCLUDE_HTTPONLY), "A=B");
+}
+
+// From: http://support.microsoft.com/kb/167296.
+static void UnixTimeToFileTime(time_t t, LPFILETIME pft) {
+ uint64 ll;
+
+ ll = Int32x32To64(t, 10000000) + 116444736000000000;
+ pft->dwLowDateTime = (DWORD)ll;
+ pft->dwHighDateTime = (DWORD)(ll >> 32);
+}
+
+static uint64 UnixTimeToUTC(time_t t) {
+ FILETIME ftime;
+ LARGE_INTEGER li;
+ UnixTimeToFileTime(t, &ftime);
+ li.LowPart = ftime.dwLowDateTime;
+ li.HighPart = ftime.dwHighDateTime;
+ return li.QuadPart;
+}
+
+TEST(CookieMonsterTest, 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 },
+ };
+
+ Time parsed_time;
+ for (int i = 0; i < arraysize(tests); ++i) {
+ parsed_time = CookieMonster::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(parsed_time.ToTimeT(), tests[i].epoch) << tests[i].str;
+ }
+}
+
+TEST(CookieMonsterTest, TestCookieDeletion) {
+ GURL url_google(kUrlGoogle);
+ CookieMonster cm;
+
+ // Create a session cookie.
+ EXPECT_TRUE(cm.SetCookie(url_google, kValidCookieLine));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+ // Delete it via Max-Age.
+ EXPECT_TRUE(cm.SetCookie(url_google,
+ std::string(kValidCookieLine) + "; max-age=0"));
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+
+ // Create a session cookie.
+ EXPECT_TRUE(cm.SetCookie(url_google, kValidCookieLine));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+ // Delete it via Expires.
+ EXPECT_TRUE(cm.SetCookie(url_google,
+ std::string(kValidCookieLine) +
+ "; expires=Mon, 18-Apr-1977 22:50:13 GMT"));
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(cm.SetCookie(url_google,
+ std::string(kValidCookieLine) +
+ "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+ // Delete it via Max-Age.
+ EXPECT_TRUE(cm.SetCookie(url_google,
+ std::string(kValidCookieLine) + "; max-age=0"));
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(cm.SetCookie(url_google,
+ std::string(kValidCookieLine) +
+ "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+ // Delete it via Expires.
+ EXPECT_TRUE(cm.SetCookie(url_google,
+ std::string(kValidCookieLine) +
+ "; expires=Mon, 18-Apr-1977 22:50:13 GMT"));
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+}
+
+TEST(CookieMonsterTest, TestCookieDeleteAll) {
+ GURL url_google(kUrlGoogle);
+ CookieMonster cm;
+
+ EXPECT_TRUE(cm.SetCookie(url_google, kValidCookieLine));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+
+ EXPECT_TRUE(cm.SetCookie(url_google, "C=D"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B; C=D");
+
+ EXPECT_EQ(cm.DeleteAll(false), 2);
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+}
+
+TEST(CookieMonsterTest, TestCookieDeleteAllCreatedAfterTimestamp) {
+ GURL url_google(kUrlGoogle);
+ CookieMonster cm;
+ Time now = Time::Now();
+
+ // Nothing has been added so nothing should be deleted.
+ EXPECT_EQ(0, cm.DeleteAllCreatedAfter(now - TimeDelta::FromDays(99), false));
+
+ // 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)));
+
+ // Try to delete everything from now onwards.
+ EXPECT_EQ(1, cm.DeleteAllCreatedAfter(now, false));
+ // Now delete the one cookie created in the last day.
+ EXPECT_EQ(1, cm.DeleteAllCreatedAfter(now - TimeDelta::FromDays(1), false));
+ // Now effectively delete all cookies just created (1 is remaining).
+ EXPECT_EQ(1, cm.DeleteAllCreatedAfter(now - TimeDelta::FromDays(99), false));
+
+ // Make sure everything is gone.
+ EXPECT_EQ(0, cm.DeleteAllCreatedAfter(Time(), false));
+ // Really make sure everything is gone.
+ EXPECT_EQ(0, cm.DeleteAll(false));
+}
+
+TEST(CookieMonsterTest, TestCookieDeleteAllCreatedBetweenTimestamps) {
+ GURL url_google(kUrlGoogle);
+ CookieMonster cm;
+ Time now = Time::Now();
+
+ // Nothing has been added so nothing should be deleted.
+ EXPECT_EQ(0, cm.DeleteAllCreatedAfter(now - TimeDelta::FromDays(99), false));
+
+ // 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, cm.DeleteAllCreatedBetween(now - TimeDelta::FromDays(3),
+ now - TimeDelta::FromDays(1),
+ false));
+
+ // Try to delete yesterday, also make sure that delete_end is not
+ // inclusive.
+ EXPECT_EQ(1, cm.DeleteAllCreatedBetween(now - TimeDelta::FromDays(2),
+ now,
+ false));
+
+ // Make sure the delete_begin is inclusive.
+ EXPECT_EQ(1, cm.DeleteAllCreatedBetween(now - TimeDelta::FromDays(7),
+ now,
+ false));
+
+ // Delete the last (now) item.
+ EXPECT_EQ(1, cm.DeleteAllCreatedAfter(Time(), false));
+
+ // Really make sure everything is gone.
+ EXPECT_EQ(0, cm.DeleteAll(false));
+}
+
+TEST(CookieMonsterTest, TestSecure) {
+ GURL url_google(kUrlGoogle);
+ GURL url_google_secure(kUrlGoogleSecure);
+ CookieMonster cm;
+
+ EXPECT_TRUE(cm.SetCookie(url_google, "A=B"));
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+ EXPECT_EQ(cm.GetCookies(url_google_secure), "A=B");
+
+ EXPECT_TRUE(cm.SetCookie(url_google_secure, "A=B; secure"));
+ // The secure should overwrite the non-secure.
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+ EXPECT_EQ(cm.GetCookies(url_google_secure), "A=B");
+
+ EXPECT_TRUE(cm.SetCookie(url_google_secure, "D=E; secure"));
+ EXPECT_EQ(cm.GetCookies(url_google), "");
+ EXPECT_EQ(cm.GetCookies(url_google_secure), "A=B; D=E");
+
+ EXPECT_TRUE(cm.SetCookie(url_google_secure, "A=B"));
+ // The non-secure should overwrite the secure.
+ EXPECT_EQ(cm.GetCookies(url_google), "A=B");
+ EXPECT_EQ(cm.GetCookies(url_google_secure), "D=E; A=B");
+}
+
+static int CountInString(const std::string& str, char c) {
+ int count = 0;
+ for (std::string::const_iterator it = str.begin();
+ it != str.end(); ++it) {
+ if (*it == c)
+ ++count;
+ }
+ return count;
+}
+
+TEST(CookieMonsterTest, TestHostGarbageCollection) {
+ GURL url_google(kUrlGoogle);
+ CookieMonster cm;
+ // Add a bunch of cookies on a single host, should purge them.
+ for (int i = 0; i < 101; i++) {
+ std::string cookie = StringPrintf("a%03d=b", i);
+ EXPECT_TRUE(cm.SetCookie(url_google, cookie));
+ std::string cookies = cm.GetCookies(url_google);
+ // Make sure we find it in the cookies.
+ EXPECT_TRUE(cookies.find(cookie) != std::string::npos);
+ // Count the number of cookies.
+ EXPECT_LE(CountInString(cookies, '='), 70);
+ }
+}
+
+TEST(CookieMonsterTest, TestTotalGarbageCollection) {
+ CookieMonster cm;
+ // Add a bunch of cookies on a bunch of host, some should get purged.
+ for (int i = 0; i < 2000; ++i) {
+ GURL url(StringPrintf("http://a%04d.izzle", i));
+ EXPECT_TRUE(cm.SetCookie(url, "a=b"));
+ EXPECT_EQ(cm.GetCookies(url), "a=b");
+ }
+
+ // Check that cookies that still exist.
+ for (int i = 0; i < 2000; ++i) {
+ GURL url(StringPrintf("http://a%04d.izzle", i));
+ if (i < 900) {
+ // Cookies should have gotten purged.
+ EXPECT_TRUE(cm.GetCookies(url).empty());
+ } else if (i > 1100) {
+ // Cookies should still be around.
+ EXPECT_FALSE(cm.GetCookies(url).empty());
+ }
+ }
+}
+
+// Formerly NetUtilTest.CookieTest back when we used wininet's cookie handling.
+TEST(CookieMonsterTest, NetUtilCookieTest) {
+ const GURL test_url(L"http://mojo.jojo.google.izzle/");
+
+ CookieMonster cm;
+
+ EXPECT_TRUE(cm.SetCookie(test_url, "foo=bar"));
+ std::string value = cm.GetCookies(test_url);
+ EXPECT_EQ("foo=bar", value);
+
+ // test that we can retrieve all cookies:
+ EXPECT_TRUE(cm.SetCookie(test_url, "x=1"));
+ EXPECT_TRUE(cm.SetCookie(test_url, "y=2"));
+
+ std::string result = cm.GetCookies(test_url);
+ EXPECT_FALSE(result.empty());
+ EXPECT_TRUE(result.find("x=1") != std::string::npos) << result;
+ EXPECT_TRUE(result.find("y=2") != std::string::npos) << result;
+}
+
+static bool FindAndDeleteCookie(CookieMonster& cm, const std::string& domain,
+ const std::string& name) {
+ CookieMonster::CookieList cookies = cm.GetAllCookies();
+ for (CookieMonster::CookieList::iterator it = cookies.begin();
+ it != cookies.end(); ++it)
+ if (it->first == domain && it->second.Name() == name)
+ return cm.DeleteCookie(domain, it->second, false);
+ return false;
+}
+
+TEST(CookieMonsterTest, TestDeleteSingleCookie) {
+ GURL url_google(kUrlGoogle);
+
+ CookieMonster cm;
+ EXPECT_TRUE(cm.SetCookie(url_google, "A=B"));
+ EXPECT_TRUE(cm.SetCookie(url_google, "C=D"));
+ EXPECT_TRUE(cm.SetCookie(url_google, "E=F"));
+ EXPECT_EQ("A=B; C=D; E=F", cm.GetCookies(url_google));
+
+ EXPECT_TRUE(FindAndDeleteCookie(cm, url_google.host(), "C"));
+ EXPECT_EQ("A=B; E=F", cm.GetCookies(url_google));
+
+ EXPECT_FALSE(FindAndDeleteCookie(cm, "random.host", "E"));
+ EXPECT_EQ("A=B; E=F", cm.GetCookies(url_google));
+}
+
+// TODO test overwrite cookie
diff --git a/net/base/cookie_policy.cc b/net/base/cookie_policy.cc
new file mode 100644
index 0000000..f0b2199e
--- /dev/null
+++ b/net/base/cookie_policy.cc
@@ -0,0 +1,66 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/logging.h"
+#include "net/base/cookie_policy.h"
+#include "net/base/registry_controlled_domain.h"
+
+bool CookiePolicy::CanGetCookies(const GURL& url, const GURL& policy_url) {
+ switch (type_) {
+ case CookiePolicy::ALLOW_ALL_COOKIES:
+ return true;
+ case CookiePolicy::BLOCK_THIRD_PARTY_COOKIES:
+ return true;
+ case CookiePolicy::BLOCK_ALL_COOKIES:
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool CookiePolicy::CanSetCookie(const GURL& url, const GURL& policy_url) {
+ switch (type_) {
+ case CookiePolicy::ALLOW_ALL_COOKIES:
+ return true;
+ case CookiePolicy::BLOCK_THIRD_PARTY_COOKIES:
+ if (policy_url.is_empty())
+ return true; // Empty policy URL should indicate a first-party request
+
+ return RegistryControlledDomainService::SameDomainOrHost(url, policy_url);
+ case CookiePolicy::BLOCK_ALL_COOKIES:
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+CookiePolicy::CookiePolicy()
+ : type_(CookiePolicy::ALLOW_ALL_COOKIES) { } \ No newline at end of file
diff --git a/net/base/cookie_policy.h b/net/base/cookie_policy.h
new file mode 100644
index 0000000..0523c95
--- /dev/null
+++ b/net/base/cookie_policy.h
@@ -0,0 +1,72 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_COOKIE_POLICY_H__
+#define NET_BASE_COOKIE_POLICY_H__
+
+#include "googleurl/src/gurl.h"
+
+// The CookiePolicy class implements third-party cookie blocking.
+class CookiePolicy {
+ public:
+ // Consult the user's third-party cookie blocking preferences to determine
+ // whether the URL's cookies can be read if the top-level window is policy_url
+ bool CanGetCookies(const GURL& url, const GURL& policy_url);
+
+ // Consult the user's third-party cookie blocking preferences to determine
+ // whether the URL's cookies can be set if the top-level window is policy_url
+ bool CanSetCookie(const GURL& url, const GURL& policy_url);
+
+ enum Type {
+ ALLOW_ALL_COOKIES = 0, // do not perform any cookie blocking
+ BLOCK_THIRD_PARTY_COOKIES, // prevent third-party cookies from being sent
+ BLOCK_ALL_COOKIES // disable cookies
+ };
+
+ static bool ValidType(int32 type) {
+ return type >= ALLOW_ALL_COOKIES && type <= BLOCK_ALL_COOKIES;
+ }
+
+ static Type FromInt(int32 type) {
+ return static_cast<Type>(type);
+ }
+
+ // Sets the current policy to enforce. This should be called when the user's
+ // preferences change.
+ void SetType(Type type) { type_ = type; }
+
+ CookiePolicy();
+
+ private:
+ Type type_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CookiePolicy);
+};
+
+#endif // NET_BASE_COOKIE_POLICY_H__
diff --git a/net/base/cookie_policy_unittest.cc b/net/base/cookie_policy_unittest.cc
new file mode 100644
index 0000000..e63ec24
--- /dev/null
+++ b/net/base/cookie_policy_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/cookie_policy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class CookiePolicyTest : public testing::Test {
+ public:
+ CookiePolicyTest()
+ : 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") { }
+ protected:
+ GURL url_google_;
+ GURL url_google_secure_;
+ GURL url_google_mail_;
+ GURL url_google_analytics_;
+};
+
+} // namespace
+
+TEST_F(CookiePolicyTest, DefaultPolicyTest) {
+ CookiePolicy cp;
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, GURL()));
+
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, GURL()));
+}
+
+TEST_F(CookiePolicyTest, AllowAllCookiesTest) {
+ CookiePolicy cp;
+ cp.SetType(CookiePolicy::ALLOW_ALL_COOKIES);
+
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, GURL()));
+
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, GURL()));
+}
+
+TEST_F(CookiePolicyTest, BlockThirdPartyCookiesTest) {
+ CookiePolicy cp;
+ cp.SetType(CookiePolicy::BLOCK_THIRD_PARTY_COOKIES);
+
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_TRUE(cp.CanGetCookies(url_google_, GURL()));
+
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_FALSE(cp.CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_TRUE(cp.CanSetCookie(url_google_, GURL()));
+}
+
+TEST_F(CookiePolicyTest, BlockAllCookiesTest) {
+ CookiePolicy cp;
+ cp.SetType(CookiePolicy::BLOCK_ALL_COOKIES);
+
+ EXPECT_FALSE(cp.CanGetCookies(url_google_, url_google_));
+ EXPECT_FALSE(cp.CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_FALSE(cp.CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_FALSE(cp.CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_FALSE(cp.CanGetCookies(url_google_, GURL()));
+
+ EXPECT_FALSE(cp.CanSetCookie(url_google_, url_google_));
+ EXPECT_FALSE(cp.CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_FALSE(cp.CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_FALSE(cp.CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_FALSE(cp.CanSetCookie(url_google_, GURL()));
+} \ No newline at end of file
diff --git a/net/base/data_url.cc b/net/base/data_url.cc
new file mode 100644
index 0000000..cf8e239
--- /dev/null
+++ b/net/base/data_url.cc
@@ -0,0 +1,121 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// NOTE: based loosely on mozilla's nsDataChannel.cpp
+
+#include <algorithm>
+
+#include "net/base/data_url.h"
+
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/base64.h"
+#include "net/base/escape.h"
+
+/*static*/
+bool DataURL::Parse(const GURL& url, std::string* mime_type,
+ std::string* charset, std::string* data) {
+ 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;
+
+ // first, find the start of the data
+ std::string::const_iterator comma = std::find(after_colon, end, ',');
+ if (comma == end)
+ return false;
+
+ const char kBase64Tag[] = ";base64";
+ std::string::const_iterator it =
+ std::search(after_colon, comma, kBase64Tag,
+ kBase64Tag + sizeof(kBase64Tag)-1);
+
+ bool base64_encoded = (it != comma);
+
+ if (comma != after_colon) {
+ // everything else is content type
+ std::string::const_iterator semi_colon = std::find(after_colon, comma, ';');
+ if (semi_colon != after_colon) {
+ mime_type->assign(after_colon, semi_colon);
+ StringToLowerASCII(mime_type);
+ }
+ if (semi_colon != comma) {
+ const char kCharsetTag[] = "charset=";
+ it = std::search(semi_colon + 1, comma, kCharsetTag,
+ kCharsetTag + sizeof(kCharsetTag)-1);
+ if (it != comma)
+ charset->assign(it + sizeof(kCharsetTag)-1, comma);
+ }
+ }
+
+ // 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");
+
+ // 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::PERCENTS);
+ }
+
+ // 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::PERCENTS);
+ }
+
+ if (base64_encoded)
+ return Base64Decode(temp_data, data);
+
+ temp_data.swap(*data);
+ return true;
+}
diff --git a/net/base/data_url.h b/net/base/data_url.h
new file mode 100644
index 0000000..06c3ab1
--- /dev/null
+++ b/net/base/data_url.h
@@ -0,0 +1,63 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <string>
+
+class GURL;
+
+// 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 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.
+ //
+ static bool Parse(const GURL& url,
+ std::string* mime_type,
+ std::string* charset,
+ std::string* data);
+};
diff --git a/net/base/data_url_unittest.cc b/net/base/data_url_unittest.cc
new file mode 100644
index 0000000..99865df
--- /dev/null
+++ b/net/base/data_url_unittest.cc
@@ -0,0 +1,176 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/data_url.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+ class DataURLTest : public testing::Test {
+ };
+}
+
+TEST(DataURLTest, Parse) {
+ const struct {
+ const char* url;
+ bool is_valid;
+ const char* mime_type;
+ const char* charset;
+ const char* data;
+ } 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: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" },
+
+ // 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 = 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/net/base/dir_header.html b/net/base/dir_header.html
new file mode 100644
index 0000000..ec4eb3a
--- /dev/null
+++ b/net/base/dir_header.html
@@ -0,0 +1,69 @@
+<html id="t">
+<head>
+
+<script>
+function addRow(name, url, isdir, size, date_modified) {
+ if (name == ".")
+ return;
+
+ var table = document.getElementById("table");
+ var row = document.createElement("tr");
+ var file_cell = document.createElement("td");
+ var link = document.createElement("a");
+ if (name == "..") {
+ link.href = "..";
+ link.innerText = document.getElementById("parentDirText").innerText;
+ size = "";
+ date_modified = "";
+ } else {
+ if (isdir) {
+ name = name + "/";
+ url = url + "/";
+ size = "";
+ }
+ link.innerText = name;
+ link.href = "./" + url;
+ }
+ file_cell.appendChild(link);
+
+ row.appendChild(file_cell);
+ row.appendChild(createCell(size));
+ row.appendChild(createCell(date_modified));
+
+ table.appendChild(row);
+}
+
+function createCell(text) {
+ var cell = document.createElement("td");
+ cell.setAttribute("class", "sizeColumn");
+ 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;
+}
+</script>
+
+<title id="title"></title>
+<style>
+ h1 { white-space: nowrap; }
+ td.sizeColumn { text-align: right; padding-left: 30px; }
+</style>
+</head>
+<body>
+<span id="parentDirText" style="display:none" jscontent="parentDirText"></span>
+<h1 id="header" jscontent="header"></h1>
+<hr/>
+<table id="table">
+<tr style="font-weight: bold">
+ <td jscontent="headerName"></td>
+ <td class="sizeColumn" jscontent="headerSize"></td>
+ <td class="sizeColumn" jscontent="headerDateModified"></td>
+</tr>
+</table>
+</body>
+</html>
diff --git a/net/base/directory_lister.cc b/net/base/directory_lister.cc
new file mode 100644
index 0000000..44dd251
--- /dev/null
+++ b/net/base/directory_lister.cc
@@ -0,0 +1,163 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <process.h>
+
+#include "net/base/directory_lister.h"
+
+#include "base/message_loop.h"
+
+static const int kFilesPerEvent = 8;
+
+class DirectoryDataEvent : public Task {
+ public:
+ explicit DirectoryDataEvent(DirectoryLister* d)
+ : lister(d), count(0), error(0) {
+ }
+
+ void Run() {
+ if (count) {
+ lister->OnReceivedData(data, count);
+ } else {
+ lister->OnDone(error);
+ }
+ }
+
+ scoped_refptr<DirectoryLister> lister;
+ WIN32_FIND_DATA data[kFilesPerEvent];
+ int count;
+ DWORD error;
+};
+
+/*static*/
+unsigned __stdcall DirectoryLister::ThreadFunc(void* param) {
+ DirectoryLister* self = reinterpret_cast<DirectoryLister*>(param);
+
+ std::wstring pattern = self->directory();
+ if (pattern[pattern.size()-1] != '\\') {
+ pattern.append(L"\\*");
+ } else {
+ pattern.append(L"*");
+ }
+
+ DirectoryDataEvent* e = new DirectoryDataEvent(self);
+
+ HANDLE handle = FindFirstFile(pattern.c_str(), &e->data[e->count]);
+ if (handle == INVALID_HANDLE_VALUE) {
+ e->error = GetLastError();
+ self->message_loop_->PostTask(FROM_HERE, e);
+ e = NULL;
+ } else {
+ do {
+ if (++e->count == kFilesPerEvent) {
+ self->message_loop_->PostTask(FROM_HERE, e);
+ e = new DirectoryDataEvent(self);
+ }
+ } while (!self->was_canceled() && FindNextFile(handle, &e->data[e->count]));
+
+ FindClose(handle);
+
+ if (e->count > 0) {
+ self->message_loop_->PostTask(FROM_HERE, e);
+ e = NULL;
+ }
+
+ // Notify done
+ e = new DirectoryDataEvent(self);
+ self->message_loop_->PostTask(FROM_HERE, e);
+ }
+
+ self->Release();
+ return 0;
+}
+
+DirectoryLister::DirectoryLister(const std::wstring& dir, Delegate* delegate)
+ : dir_(dir),
+ message_loop_(NULL),
+ delegate_(delegate),
+ thread_(NULL),
+ canceled_(false) {
+ DCHECK(!dir.empty());
+}
+
+DirectoryLister::~DirectoryLister() {
+ if (thread_)
+ CloseHandle(thread_);
+}
+
+bool DirectoryLister::Start() {
+ // spawn a thread to enumerate the specified directory
+
+ // pass events back to the current thread
+ message_loop_ = MessageLoop::current();
+ DCHECK(message_loop_) << "calling thread must have a message loop";
+
+ AddRef(); // the thread will release us when it is done
+
+ unsigned thread_id;
+ thread_ = reinterpret_cast<HANDLE>(
+ _beginthreadex(NULL, 0, DirectoryLister::ThreadFunc, this, 0,
+ &thread_id));
+
+ if (!thread_) {
+ Release();
+ return false;
+ }
+
+ return true;
+}
+
+void DirectoryLister::Cancel() {
+ canceled_ = true;
+
+ if (thread_) {
+ WaitForSingleObject(thread_, INFINITE);
+ CloseHandle(thread_);
+ thread_ = NULL;
+ }
+}
+
+void DirectoryLister::OnReceivedData(const WIN32_FIND_DATA* data, int count) {
+ // Since the delegate can clear itself during the OnListFile callback, we
+ // need to null check it during each iteration of the loop. Similarly, it is
+ // necessary to check the canceled_ flag to avoid sending data to a delegate
+ // who doesn't want anymore.
+ for (int i = 0; !canceled_ && delegate_ && i < count; ++i)
+ delegate_->OnListFile(data[i]);
+}
+
+void DirectoryLister::OnDone(int error) {
+ // If canceled, we need to report some kind of error, but don't overwrite the
+ // error condition if it is already set.
+ if (!error && canceled_)
+ error = ERROR_OPERATION_ABORTED;
+
+ if (delegate_)
+ delegate_->OnListDone(error);
+}
diff --git a/net/base/directory_lister.h b/net/base/directory_lister.h
new file mode 100644
index 0000000..b3b0949
--- /dev/null
+++ b/net/base/directory_lister.h
@@ -0,0 +1,92 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_DIRECTORY_LISTER_H__
+#define NET_BASE_DIRECTORY_LISTER_H__
+
+#include <windows.h>
+#include <string>
+
+#include "base/ref_counted.h"
+
+class MessageLoop;
+
+//
+// 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 DirectoryLister : public base::RefCountedThreadSafe<DirectoryLister> {
+ public:
+ // Implement this class to receive directory entries.
+ class Delegate {
+ public:
+ virtual void OnListFile(const WIN32_FIND_DATA& data) = 0;
+ virtual void OnListDone(int error) = 0;
+ };
+
+ DirectoryLister(const std::wstring& dir, Delegate* delegate);
+ ~DirectoryLister();
+
+ // Call this method to start the directory enumeration thread.
+ bool Start();
+
+ // Call this method to asynchronously stop directory enumeration. The
+ // delegate will receive the OnListDone notification with an error code of
+ // ERROR_OPERATION_ABORTED.
+ void Cancel();
+
+ // The delegate pointer may be modified at any time.
+ Delegate* delegate() const { return delegate_; }
+ void set_delegate(Delegate* d) { delegate_ = d; }
+
+ // Returns the directory being enumerated.
+ const std::wstring& directory() const { return dir_; }
+
+ // Returns true if the directory enumeration was canceled.
+ bool was_canceled() const { return canceled_; }
+
+ private:
+ friend class DirectoryDataEvent;
+
+ void OnReceivedData(const WIN32_FIND_DATA* data, int count);
+ void OnDone(int error);
+
+ static unsigned __stdcall ThreadFunc(void* param);
+
+ std::wstring dir_;
+ Delegate* delegate_;
+ MessageLoop* message_loop_;
+ HANDLE thread_;
+ bool canceled_;
+};
+
+#endif // NET_BASE_DIRECTORY_LISTER_H__
diff --git a/net/base/directory_lister_unittest.cc b/net/base/directory_lister_unittest.cc
new file mode 100644
index 0000000..b1cbc4a
--- /dev/null
+++ b/net/base/directory_lister_unittest.cc
@@ -0,0 +1,85 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "net/base/directory_lister.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+ class DirectoryListerTest : public testing::Test {
+ };
+}
+
+class DirectoryListerDelegate : public DirectoryLister::Delegate {
+ public:
+ DirectoryListerDelegate() : error_(-1) {
+ }
+ void OnListFile(const WIN32_FIND_DATA& data) {
+ }
+ void OnListDone(int error) {
+ error_ = error;
+ MessageLoop::current()->Quit();
+ }
+ int error() const { return error_; }
+ private:
+ int error_;
+};
+
+TEST(DirectoryListerTest, BigDirTest) {
+ std::wstring windows_path;
+ ASSERT_TRUE(PathService::Get(base::DIR_WINDOWS, &windows_path));
+
+ DirectoryListerDelegate delegate;
+ scoped_refptr<DirectoryLister> lister =
+ new DirectoryLister(windows_path, &delegate);
+
+ lister->Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(delegate.error(), ERROR_SUCCESS);
+}
+
+TEST(DirectoryListerTest, CancelTest) {
+ std::wstring windows_path;
+ ASSERT_TRUE(PathService::Get(base::DIR_WINDOWS, &windows_path));
+
+ DirectoryListerDelegate delegate;
+ scoped_refptr<DirectoryLister> lister =
+ new DirectoryLister(windows_path, &delegate);
+
+ lister->Start();
+ lister->Cancel();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(delegate.error(), ERROR_OPERATION_ABORTED);
+ EXPECT_EQ(lister->was_canceled(), true);
+}
diff --git a/net/base/dns_resolution_observer.cc b/net/base/dns_resolution_observer.cc
new file mode 100644
index 0000000..c66be8f
--- /dev/null
+++ b/net/base/dns_resolution_observer.cc
@@ -0,0 +1,85 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file supports network stack independent notification of progress
+// towards resolving a hostname.
+
+#include "net/base/dns_resolution_observer.h"
+
+#include <string>
+
+#include "base/atomic.h"
+#include "base/logging.h"
+
+namespace net {
+
+static DnsResolutionObserver* dns_resolution_observer;
+
+void AddDnsResolutionObserver(DnsResolutionObserver* new_observer) {
+ if (new_observer == dns_resolution_observer)
+ return; // Facilitate unit tests that init/teardown repeatedly.
+ DCHECK(!dns_resolution_observer);
+ if (InterlockedCompareExchangePointer(
+ reinterpret_cast<PVOID*>(&dns_resolution_observer),
+ new_observer, NULL))
+ DCHECK(0) << "Second attempt to setup observer";
+}
+
+DnsResolutionObserver* RemoveDnsResolutionObserver() {
+ // We really need to check that the entire network subsystem is shutting down,
+ // and hence no additional calls can even *possibly* still be lingering in the
+ // notification path that includes our observer. Until we have a way to
+ // really assert that fact, we will outlaw the calling of this function.
+ // Darin suggested that the caller use a static initializer for the observer,
+ // so that it can safely be destroyed after process termination, and without
+ // inducing a memory leak.
+ // Bottom line: Don't call this function! You will crash for now.
+ CHECK(0);
+ DnsResolutionObserver* old_observer = dns_resolution_observer;
+ dns_resolution_observer = NULL;
+ return old_observer;
+}
+
+// Locking access to dns_resolution_observer is not really critical... but we
+// should test the value of dns_resolution_observer that we use.
+// Worst case, we'll get an "out of date" value... which is no big deal for the
+// DNS prefetching system (the most common (only?) observer).
+void DidStartDnsResolution(const std::string& name, void* context) {
+ DnsResolutionObserver* current_observer = dns_resolution_observer;
+ if (current_observer)
+ current_observer->OnStartResolution(name, context);
+}
+
+void DidFinishDnsResolutionWithStatus(bool was_resolved, void* context) {
+ DnsResolutionObserver* current_observer = dns_resolution_observer;
+ if (current_observer) {
+ current_observer->OnFinishResolutionWithStatus(was_resolved, context);
+ }
+}
+} // namspace net
diff --git a/net/base/dns_resolution_observer.h b/net/base/dns_resolution_observer.h
new file mode 100644
index 0000000..08ef6bf
--- /dev/null
+++ b/net/base/dns_resolution_observer.h
@@ -0,0 +1,80 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file supports network stack independent notification of progress
+// towards resolving a hostname.
+
+// The observer class supports exactly one active (Add'ed) instance, and in
+// typical usage, that observer will be Add'ed during process startup, and
+// Remove'd during process termination.
+
+
+#ifndef NET_BASE_DNS_RESOLUTION_OBSERVER_H__
+#define NET_BASE_DNS_RESOLUTION_OBSERVER_H__
+
+#include <string>
+
+namespace net {
+
+class DnsResolutionObserver {
+ public:
+ // For each OnStartResolution() notification, there should be a later
+ // OnFinishResolutionWithStatus() indicating completion of the resolution
+ // activity.
+ // Related pairs of notification will arrive with matching context values.
+ // A caller may use the context values to match up these asynchronous calls
+ // from among a larger call stream.
+ // Once a matching pair of notifications has been provided (i.e., a pair with
+ // identical context values), and the notification methods (below) have
+ // returned, the context values *might* be reused.
+ virtual void OnStartResolution(const std::string& name, void* context) = 0;
+ virtual void OnFinishResolutionWithStatus(bool was_resolved,
+ void* context) = 0;
+};
+
+
+// Note that *exactly* one observer is currently supported, and any attempt to
+// add a second observer via AddDnsResolutionObserver() before removing the
+// first DnsResolutionObserver will induce a DCHECK() assertion.
+void AddDnsResolutionObserver(DnsResolutionObserver* new_observer);
+
+// Note that the RemoveDnsResolutionObserver() will NOT perform any delete
+// operations, and it is the responsibility of the code that called
+// AddDnsResolutionObserver() to make a corresponding call to
+// RemoveDnsResolutionObserver() and then delete the returned
+// DnsResolutionObserver instance.
+DnsResolutionObserver* RemoveDnsResolutionObserver();
+
+// The following functions are expected to be called only by network stack
+// implementations. This above observer class will relay the notifications
+// to any registered observer.
+void DidStartDnsResolution(const std::string& name, void* context);
+void DidFinishDnsResolutionWithStatus(bool was_resolved, void* context);
+} // namspace net
+#endif // NET_BASE_DNS_RESOLUTION_OBSERVER_H__
diff --git a/net/base/effective_tld_names.dat b/net/base/effective_tld_names.dat
new file mode 100644
index 0000000..578a30c
--- /dev/null
+++ b/net/base/effective_tld_names.dat
@@ -0,0 +1,3124 @@
+// Google note: this is Mozilla's version 1.4 of this file, retrieved using
+// cvs -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot co \
+// mozilla/netwerk/dns/src/effective_tld_names.dat
+// The file itself contains no license, but may be presumed to be covered at
+// most by Mozilla's standard tri-license: see http://www.mozilla.org/MPL/
+
+
+// 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
+ae
+net.ae
+gov.ae
+ac.ae
+sch.ae
+org.ae
+mil.ae
+pro.ae
+name.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.inima.al/Domains.html
+gov.al
+edu.al
+org.al
+com.al
+net.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
+// list of 2nd level TLDs ?
+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
+e164.arpa
+in-addr.arpa
+ip6.arpa
+uri.arpa
+urn.arpa
+
+// as : http://en.wikipedia.org/wiki/.as
+as
+
+// at : http://en.wikipedia.org/wiki/.at
+at
+gv.at
+ac.at
+co.at
+or.at
+
+// au : http://en.wikipedia.org/wiki/.au
+*.au
+// au geographical names (vic.au etc... are covered above)
+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
+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
+com.bb
+edu.bb
+gov.bb
+net.bb
+org.bb
+
+// bd : http://en.wikipedia.org/wiki/.bd
+*.bd
+
+// be : http://en.wikipedia.org/wiki/.be
+be
+ac.be
+
+// bf : http://en.wikipedia.org/wiki/.bf
+bf
+
+// bg : http://en.wikipedia.org/wiki/.bg
+bg
+
+// bh : http://en.wikipedia.org/wiki/.bh
+// list of 2nd level tlds ?
+bh
+
+// bi : http://en.wikipedia.org/wiki/.bi
+// list of 2nd level tlds ?
+bi
+
+// biz : http://en.wikipedia.org/wiki/.biz
+biz
+
+// bj : http://en.wikipedia.org/wiki/.bj
+// list of 2nd level tlds ?
+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://en.wikipedia.org/wiki/.br
+// http://registro.br/info/dpn.html
+br
+adm.br
+adv.br
+agr.br
+am.br
+arq.br
+art.br
+ato.br
+bio.br
+blog.br
+bmd.br
+cim.br
+cng.br
+cnt.br
+com.br
+coop.br
+ecn.br
+edu.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
+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
+rec.br
+slg.br
+srv.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
+
+// bw : http://en.wikipedia.org/wiki/.bw
+// list of 2nd level tlds ?
+bw
+
+// by : http://en.wikipedia.org/wiki/.by
+// list of 2nd level tlds ?
+by
+
+// bz : http://en.wikipedia.org/wiki/.bz
+// list of 2nd level tlds ?
+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
+
+// cat : http://en.wikipedia.org/wiki/.cat
+cat
+
+// cc : http://en.wikipedia.org/wiki/.cc
+cc
+
+// cd : http://en.wikipedia.org/wiki/.cd
+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
+// list of 2nd level tlds ?
+ci
+
+// ck : http://en.wikipedia.org/wiki/.ck
+*.ck
+
+// cl : http://en.wikipedia.org/wiki/.cl
+cl
+
+// cm : http://en.wikipedia.org/wiki/.cm
+cm
+
+// cn : http://en.wikipedia.org/wiki/.cn
+cn
+ac.cn
+com.cn
+edu.cn
+gov.cn
+net.cn
+org.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
+
+// co : http://en.wikipedia.org/wiki/.co
+*.co
+
+// com : http://en.wikipedia.org/wiki/.com
+com
+
+// coop : http://en.wikipedia.org/wiki/.coop
+coop
+
+// cr : http://en.wikipedia.org/wiki/.cr
+*.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
+
+// cx : http://en.wikipedia.org/wiki/.cx
+cx
+
+// cy : http://en.wikipedia.org/wiki/.cy
+*.cy
+
+// cz : http://en.wikipedia.org/wiki/.cz
+cz
+
+// de : http://en.wikipedia.org/wiki/.de
+de
+
+// dj : http://en.wikipedia.org/wiki/.dj
+dj
+
+// dk : http://en.wikipedia.org/wiki/.dk
+dk
+
+// dm : http://en.wikipedia.org/wiki/.dm
+dm
+com.dm
+net.dm
+org.dm
+
+// do : http://en.wikipedia.org/wiki/.do
+*.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
+ec
+com.ec
+info.ec
+net.ec
+fin.ec
+med.ec
+pro.ec
+org.ec
+edu.ec
+gov.ec
+mil.ec
+
+// edu : http://en.wikipedia.org/wiki/.edu
+edu
+
+// ee : http://www3.eenet.ee/ee/application.html
+ee
+com.ee
+org.ee
+fie.ee
+pri.ee
+
+// eg : http://en.wikipedia.org/wiki/.eg
+*.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
+
+// 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/
+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
+
+// 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/tandc.shtml
+gg
+co.gg
+org.gg
+net.gg
+sch.gg
+gov.gg
+
+// gh : http://www.ghana.com/domain.htm
+*.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
+gl
+
+// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm
+gm
+
+// gn : http://psg.com/dns/gn/gn.txt
+*.gn
+
+// gov : http://en.wikipedia.org/wiki/.gov
+gov
+
+// gp : http://www.nic.gp/index_en.php?url=charte_en.php
+gp
+com.gp
+net.gp
+edu.gp
+org.gp
+
+// gq : http://en.wikipedia.org/wiki/.gq
+gq
+
+// gr : https://grweb.ics.forth.gr/english/1617-B-2002.html
+gr
+com.gr
+edu.gr
+net.gr
+org.gr
+gov.gr
+
+// gs : http://en.wikipedia.org/wiki/.gs
+gs
+
+// gt : http://www.gt/politicas.html
+*.gt
+
+// gu : http://gadao.gov.gu/registration.txt
+*.gu
+
+// gw : http://en.wikipedia.org/wiki/.gw
+gw
+
+// gy : http://en.wikipedia.org/wiki/.gy
+gy
+
+// hk : http://en.wikipedia.org/wiki/.hk
+hk
+com.hk
+edu.hk
+gov.hk
+idv.hk
+net.hk
+org.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
+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 : http://en.wikipedia.org/wiki/.id
+*.id
+
+// ie : http://en.wikipedia.org/wiki/.ie
+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
+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
+int
+eu.int
+
+// io : http://www.nic.io/rules.html
+// list of 2nd level tlds ?
+io
+
+// iq : http://en.wikipedia.org/wiki/.iq
+// no registrar website found, but google shows .gov.iq and .edu.iq websites
+iq
+gov.iq
+edu.iq
+
+// ir : http://www.nic.ir/ascii/Appendix1.htm
+ir
+ac.ir
+co.ir
+gov.ir
+id.ir
+net.ir
+org.ir
+sch.ir
+
+// is : http://www.isnic.is/domain/rules.php
+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
+// geo-names found at http://www.nic.it/RA/en/domini/regole/nomi-riservati.pdf
+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
+barlettaandriatrani.it
+barletta-andria-trani.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
+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
+enna.it
+en.it
+fermo.it
+ferrara.it
+fe.it
+firenze.it
+florence.it
+fi.it
+foggia.it
+fg.it
+forli-cesena.it
+forlicesena.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
+ms.it
+matera.it
+mt.it
+messina.it
+me.it
+milano.it
+milan.it
+mi.it
+modena.it
+mo.it
+monza.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
+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
+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/tandc.shtml
+je
+co.je
+org.je
+net.je
+sch.je
+gov.je
+
+// jm : http://www.com.jm/register.html
+*.jm
+
+// jo : http://www.nis.gov.jo/dns/reg.html
+jo
+com.jo
+org.jo
+net.jo
+edu.jo
+gov.jo
+mil.jo
+myname.jo
+
+// jobs : http://en.wikipedia.org/wiki/.jobs
+jobs
+
+// jp : http://en.wikipedia.org/wiki/.jp
+jp
+ac.jp
+ad.jp
+co.jp
+ed.jp
+go.jp
+gr.jp
+lg.jp
+ne.jp
+or.jp
+// jp geographical names
+// I can't find an official English explanation, but used https://bugzilla.mozilla.org/show_bug.cgi?id=252342#c31
+*.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
+*.kawasaki.jp
+*.kitakyushu.jp
+*.kobe.jp
+*.kochi.jp
+*.kumamoto.jp
+*.kyoto.jp
+*.mie.jp
+*.miyagi.jp
+*.miyazaki.jp
+*.nagano.jp
+*.nagasaki.jp
+*.nagoya.jp
+*.nara.jp
+*.niigata.jp
+*.oita.jp
+*.okayama.jp
+*.okinawa.jp
+*.osaka.jp
+*.saga.jp
+*.saitama.jp
+*.sapporo.jp
+*.sendai.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
+*.yokohama.jp
+!metro.tokyo.jp
+!pref.aichi.jp
+!pref.akita.jp
+!pref.aomori.jp
+!pref.chiba.jp
+!pref.ehime.jp
+!pref.fukui.jp
+!pref.fukuoka.jp
+!pref.fukushima.jp
+!pref.gifu.jp
+!pref.gunma.jp
+!pref.hiroshima.jp
+!pref.hokkaido.jp
+!pref.hyogo.jp
+!pref.ibaraki.jp
+!pref.ishikawa.jp
+!pref.iwate.jp
+!pref.kagawa.jp
+!pref.kagoshima.jp
+!pref.kanagawa.jp
+!pref.kochi.jp
+!pref.kumamoto.jp
+!pref.kyoto.jp
+!pref.mie.jp
+!pref.miyagi.jp
+!pref.miyazaki.jp
+!pref.nagano.jp
+!pref.nagasaki.jp
+!pref.nara.jp
+!pref.niigata.jp
+!pref.oita.jp
+!pref.okayama.jp
+!pref.okinawa.jp
+!pref.osaka.jp
+!pref.saga.jp
+!pref.saitama.jp
+!pref.shiga.jp
+!pref.shimane.jp
+!pref.shizuoka.jp
+!pref.tochigi.jp
+!pref.tokushima.jp
+!pref.tottori.jp
+!pref.toyama.jp
+!pref.wakayama.jp
+!pref.yamagata.jp
+!pref.yamaguchi.jp
+!pref.yamanashi.jp
+!city.chiba.jp
+!city.fukuoka.jp
+!city.hiroshima.jp
+!city.kawasaki.jp
+!city.kitakyushu.jp
+!city.kobe.jp
+!city.kyoto.jp
+!city.nagoya.jp
+!city.osaka.jp
+!city.saitama.jp
+!city.sapporo.jp
+!city.sendai.jp
+!city.shizuoka.jp
+!city.yokohama.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
+km
+
+// kn : http://en.wikipedia.org/wiki/.kn
+kn
+
+// kr : http://domain.nida.or.kr/eng/structure.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
+// http://en.wikipedia.org/wiki/.kr
+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
+ky
+edu.ky
+gov.ky
+com.ky
+org.ky
+net.ky
+
+// kz : http://en.wikipedia.org/wiki/.kz
+kz
+org.kz
+edu.kz
+net.kz
+gov.kz
+mil.kz
+com.kz
+
+// la : http://en.wikipedia.org/wiki/.la
+la
+
+// lb : http://en.wikipedia.org/wiki/.lb
+*.lb
+
+// lc : http://en.wikipedia.org/wiki/.lc
+lc
+com.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
+*.lr
+
+// ls : http://en.wikipedia.org/wiki/.ls
+ls
+co.ls
+org.ls
+
+// lt : http://en.wikipedia.org/wiki/.lt
+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
+// list of 2nd level tlds ?
+ma
+co.ma
+net.ma
+gov.ma
+org.ma
+
+// mc : http://www.nic.mc/
+mc
+tm.mc
+asso.mc
+
+// md : http://en.wikipedia.org/wiki/.md
+md
+
+// 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
+// list of 2nd level tlds ?
+mk
+com.mk
+gov.mk
+org.mk
+net.mk
+edu.mk
+
+// ml : http://www.gobin.info/domainname/ml-template.doc
+*.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/
+mp
+
+// mq : http://en.wikipedia.org/wiki/.mq
+mq
+
+// mr : http://en.wikipedia.org/wiki/.mr
+mr
+
+// ms : http://en.wikipedia.org/wiki/.ms
+ms
+
+// mt : https://www.nic.org.mt/dotmt/
+*.mt
+
+// mu : http://en.wikipedia.org/wiki/.mu
+// list of 2nd level tlds ?
+mu
+
+// museum : http://about.museum/naming/
+// there are 2nd-level TLD's, but there's no list
+museum
+
+// mv : http://en.wikipedia.org/wiki/.mv
+*.mv
+
+// mw : http://www.registrar.mw/
+mw
+ac.mw
+biz.mw
+co.mw
+com.mw
+coop.mw
+edu.mw
+gov.mw
+int.mw
+net.mw
+org.mw
+
+// mx : http://www.nic.mx/
+*.mx
+
+// my : http://www.mynic.net.my/
+*.my
+
+// mz : http://www.gobin.info/domainname/mz-template.doc
+*.mz
+
+// na : http://www.na-nic.com.na/
+// list of 2nd level tlds ?
+na
+
+// name : has 2nd-level tlds, but there's no list of them
+name
+
+// nc : http://www.cctld.nc/
+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/
+ng
+
+// ni : http://www.nic.ni/dominios.htm
+*.ni
+
+// nl : http://www.domain-registry.nl/ace.php/c,728,122,,,,Home.html
+nl
+
+// no : http://www.norid.no/regelverk/index.en.html
+no
+fhs.no
+vgs.no
+fylkesbibl.no
+folkebibl.no
+museum.no
+idrett.no
+mil.no
+stat.no
+dep.no
+kommune.no
+herad.no
+priv.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
+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
+cá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
+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
+
+// org : http://en.wikipedia.org/wiki/.og
+org
+
+// pa : http://www.nic.pa/
+*.pa
+
+// pe : http://www.nic.pe/normas-proced-i.htm
+*.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
+// list of 2nd level tlds ?
+ph
+com.ph
+net.ph
+org.ph
+gov.ph
+edu.ph
+ngo.ph
+mil.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
+goa.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
+med.pl
+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
+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
+sopot.pl
+// other geographical domains
+gliwice.pl
+krakow.pl
+poznan.pl
+wroc.pl
+zakopane.pl
+
+// pn : http://www.government.pn/PnRegistry/policies.htm
+pn
+gov.pn
+co.pn
+org.pn
+edu.pn
+net.pn
+
+// 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
+// list of 2nd level tlds ?
+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
+
+// py : http://www.nic.py/faq_a.html#faq_b
+*.py
+
+// qa : http://www.qatar.net.qa/services/virtual.htm
+*.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
+
+// ru : http://en.wikipedia.org/wiki/.ru
+ru
+com.ru
+net.ru
+org.ru
+pp.ru
+int.ru
+// there should be geo-names like msk.ru, but I didn't find a list
+
+// 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.saudinic.net.sa/page.php?page=1&lang=1
+*.sa
+
+// sb : http://www.sbnic.net.sb/
+*.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
+sd
+com.sd
+net.sd
+org.sd
+edu.sd
+med.sd
+tv.sd
+gov.sd
+info.sd
+
+// se : http://en.wikipedia.org/wiki/.se
+se
+org.se
+pp.se
+tm.se
+parti.se
+press.se
+mil.se
+// se geographical names
+ab.se
+c.se
+d.se
+e.se
+f.se
+g.se
+h.se
+i.se
+k.se
+m.se
+n.se
+o.se
+s.se
+t.se
+u.se
+w.se
+x.se
+y.se
+z.se
+ac.se
+bd.se
+
+// sg : http://www.nic.net.sg/sub_policies_agreement/2ld.html
+sg
+com.sg
+net.sg
+org.sg
+gov.sg
+edu.sg
+per.sg
+
+// sh : http://www.nic.sh/rules.html
+// list of 2nd level domains ?
+sh
+
+// si : http://en.wikipedia.org/wiki/.si
+si
+
+// sk : http://en.wikipedia.org/wiki/.sk
+sk
+
+// sl : http://en.wikipedia.org/wiki/.sl
+// list of 2nd level domains ?
+sl
+
+// sm : http://en.wikipedia.org/wiki/.sm
+sm
+
+// sn : http://en.wikipedia.org/wiki/.sn
+// list of 2nd level domains ?
+sn
+
+// sr : http://en.wikipedia.org/wiki/.sr
+sr
+
+// st : http://www.nic.st/html/policyrules/
+st
+
+// su : http://en.wikipedia.org/wiki/.su
+su
+
+// sv : http://www.svnet.org.sv/svpolicy.html
+*.sv
+
+// sy : http://www.gobin.info/domainname/sy.doc
+*.sy
+
+// sz : http://en.wikipedia.org/wiki/.sz
+// list of 2nd level domains ?
+sz
+
+// tc : http://en.wikipedia.org/wiki/.tc
+tc
+
+// td : http://en.wikipedia.org/wiki/.td
+td
+
+// tf : http://en.wikipedia.org/wiki/.tf
+tf
+
+// tg : http://en.wikipedia.org/wiki/.tg
+// list of 2nd level domains ?
+tg
+
+// th : http://en.wikipedia.org/wiki/.th
+*.th
+
+// tj : http://www.nic.tj/policy.htm
+tj
+ac.tj
+biz.tj
+com.tj
+co.tj
+edu.tj
+int.tj
+name.tj
+net.tj
+org.tj
+web.tj
+gov.tj
+go.tj
+mil.tj
+
+// tk : http://en.wikipedia.org/wiki/.tk
+tk
+
+// tl : http://en.wikipedia.org/wiki/.tl
+// list of 2nd level tlds ?
+tl
+
+// tm : http://www.nic.tm/rules.html
+// list of 2nd level tlds ?
+tm
+
+// tn : http://en.wikipedia.org/wiki/.tn
+// list of 2nd level tlds ?
+tn
+
+// to : http://en.wikipedia.org/wiki/.to
+// list of 2nd level tlds ?
+to
+
+// tr : http://en.wikipedia.org/wiki/.tr
+*.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
+// list of 2nd level tlds ?
+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://en.wikipedia.org/wiki/.tz
+*.tz
+
+// ua : http://www.nic.net.ua/
+ua
+com.ua
+edu.ua
+gov.ua
+net.ua
+org.ua
+// ua geo-names
+cherkassy.ua
+chernigov.ua
+chernovtsy.ua
+ck.ua
+cn.ua
+crimea.ua
+cv.ua
+dn.ua
+dnepropetrovsk.ua
+donetsk.ua
+dp.ua
+if.ua
+ivano-frankivsk.ua
+kh.ua
+kharkov.ua
+kherson.ua
+kiev.ua
+kirovograd.ua
+km.ua
+kr.ua
+ks.ua
+lg.ua
+lugansk.ua
+lutsk.ua
+lviv.ua
+mk.ua
+nikolaev.ua
+od.ua
+odessa.ua
+pl.ua
+poltava.ua
+rovno.ua
+rv.ua
+sebastopol.ua
+sumy.ua
+te.ua
+ternopil.ua
+vinnica.ua
+vn.ua
+zaporizhzhe.ua
+zp.ua
+uz.ua
+uzhgorod.ua
+zhitomir.ua
+zt.ua
+
+// ug : http://www.registry.co.ug/
+ug
+co.ug
+ac.ug
+sc.ug
+go.ug
+ne.ug
+or.ug
+
+// uk : http://en.wikipedia.org/wiki/.uk
+*.uk
+*.sch.uk
+!bl.uk
+!british-library.uk
+!icnet.uk
+!jet.uk
+!nel.uk
+!nls.uk
+!national-library-scotland.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 following rules would be only valid under the geo-name, but we can't express that
+// *.*.us cities, counties, parishes, and townships (locality.state.us)
+// !ci.*.*.us city government agencies (subdomain under locality)
+// !town.*.*.us town government agencies (subdomain under locality)
+// !co.*.*.us county government agencies (subdomain under locality)
+// k12.*.us public school districts
+// pvt.k12.*.us private schools
+// cc.*.us community colleges
+// tec.*.us technical and vocational schools
+// lib.*.us state, regional, city, and county libraries
+// state.*.us state government agencies
+// gen.*.us general independent entities (groups not fitting into the above categories)
+
+// uy : http://www.antel.com.uy/
+*.uy
+
+// uz : http://www.reg.uz/registerr.html
+// are there other 2nd level tlds ?
+uz
+com.uz
+co.uz
+
+// va : http://en.wikipedia.org/wiki/.va
+va
+
+// vc : http://en.wikipedia.org/wiki/.vc
+// list of 2nd level tlds ?
+vc
+
+// ve : http://registro.nic.ve/nicve/registro/index.html
+*.ve
+
+// vg : http://en.wikipedia.org/wiki/.vg
+vg
+
+// vi : http://www.nic.vi/Domain_Rules/body_domain_rules.html
+vi
+com.vi
+org.vi
+edu.vi
+gov.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
+
+// ws : http://en.wikipedia.org/wiki/.ws
+ws
+
+// ye : http://www.y.net.ye/services/domain_name.htm
+*.ye
+
+// yu : http://www.nic.yu/pravilnik-e.html
+*.yu
+
+// 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
+
diff --git a/net/base/escape.cc b/net/base/escape.cc
new file mode 100644
index 0000000..bd4aa95
--- /dev/null
+++ b/net/base/escape.cc
@@ -0,0 +1,272 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+
+#include "net/base/escape.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+
+namespace {
+
+template <class char_type>
+inline bool IsHex(char_type ch) {
+ return (ch >= '0' && ch <= '9') ||
+ (ch >= 'A' && ch <= 'F') ||
+ (ch >= 'a' && ch <= 'f');
+}
+
+template <class char_type>
+inline char_type HexToInt(char_type ch) {
+ if (ch >= '0' && ch <= '9')
+ return ch - '0';
+ if (ch >= 'A' && ch <= 'F')
+ return ch - 'A' + 10;
+ if (ch >= 'a' && ch <= 'f')
+ return ch - 'a' + 10;
+ NOTREACHED();
+ return 0;
+}
+
+static const char* const kHexString = "0123456789ABCDEF";
+inline char IntToHex(int i) {
+ DCHECK(i >= 0 && 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.
+class Charmap {
+ public:
+ Charmap(uint32 b0, uint32 b1, uint32 b2, uint32 b3,
+ uint32 b4, uint32 b5, uint32 b6, uint32 b7) {
+ map_[0] = b0; map_[1] = b1; map_[2] = b2; map_[3] = b3;
+ map_[4] = b4; map_[5] = b5; map_[6] = b6; map_[7] = b7;
+ }
+
+ bool Contains(unsigned char c) const {
+ return (map_[c >> 5] & (1 << (c & 31))) ? true : false;
+ }
+
+ private:
+ 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.
+const 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;
+}
+
+std::string UnescapeURLImpl(const std::string& escaped_text,
+ UnescapeRule::Type rules) {
+ // 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.
+ std::string result;
+ result.reserve(escaped_text.length());
+
+ for (size_t i = 0, max = escaped_text.size(), max_digit_index = max - 2;
+ i < max; ++i) {
+ if (escaped_text[i] == '%' && i < max_digit_index) {
+ const std::string::value_type most_sig_digit(escaped_text[i + 1]);
+ const std::string::value_type least_sig_digit(escaped_text[i + 2]);
+ if (IsHex(most_sig_digit) && IsHex(least_sig_digit)) {
+ unsigned char value = HexToInt(most_sig_digit) * 16 +
+ HexToInt(least_sig_digit);
+ if (((rules & UnescapeRule::PERCENTS) || value != '%') &&
+ ((rules & UnescapeRule::SPACES) || value != ' ')) {
+ // Use the unescaped version of the character.
+ result.push_back(value);
+ i += 2;
+ } else {
+ result.push_back('%');
+ }
+ } else {
+ result.push_back('%');
+ }
+ } else if ((rules & UnescapeRule::REPLACE_PLUS_WITH_SPACE) &&
+ escaped_text[i] == '+') {
+ result.push_back(' ');
+ } else {
+ result.push_back(escaped_text[i]);
+ }
+ }
+
+ return result;
+}
+
+} // namespace
+
+// 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);
+
+std::string EscapeQueryParamValue(const std::string& text) {
+ return Escape(text, kQueryCharmap, true);
+}
+
+// Convert the string to a sequence of bytes and then % escape anything
+// except alphanumerics and !'()*-._~
+std::wstring EscapeQueryParamValueUTF8(const std::wstring& text) {
+ return UTF8ToWide(Escape(WideToUTF8(text), kQueryCharmap, true));
+}
+
+// non-printable, non-7bit, and (including space) "#%:<>?[\]^`{|}
+static const Charmap kPathCharmap(
+ 0xffffffffL, 0xd400002dL, 0x78000000L, 0xb8000001L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL);
+
+std::string EscapePath(const std::string& path) {
+ return Escape(path, kPathCharmap, false);
+}
+
+// non-7bit
+static const Charmap kNonASCIICharmap(
+ 0x00000000L, 0x00000000L, 0x00000000L, 0x00000000L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL);
+
+std::string EscapeNonASCII(const std::string& input) {
+ return Escape(input, kNonASCIICharmap, false);
+}
+
+// Everything except alphanumerics, the reserved characters(;/?:@&=+$,) and
+// !'()*-._~%
+static const Charmap kExternalHandlerCharmap(
+ 0xffffffffL, 0x5000080dL, 0x68000000L, 0xb8000001L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL);
+
+std::string EscapeExternalHandlerValue(const std::string& text) {
+ return Escape(text, kExternalHandlerCharmap, false);
+}
+
+bool EscapeQueryParamValue(const std::wstring& text, const char* codepage,
+ std::wstring* escaped) {
+ // TODO(brettw) bug 1201094: this function should be removed, this "SKIP"
+ // behavior is wrong when the character can't be encoded properly.
+ std::string encoded;
+ if (!WideToCodepage(text, codepage,
+ OnStringUtilConversionError::SKIP, &encoded))
+ return false;
+
+ // It's safe to use UTF8ToWide here because Escape should only return
+ // alphanumerics and !'()*-._~
+ escaped->assign(UTF8ToWide(Escape(encoded, kQueryCharmap, true)));
+ return true;
+}
+
+std::wstring UnescapeAndDecodeURLComponent(const std::string& text,
+ const char* codepage,
+ UnescapeRule::Type rules) {
+ std::wstring result;
+ if (CodepageToWide(UnescapeURLImpl(text, rules), codepage,
+ OnStringUtilConversionError::FAIL, &result))
+ return result; // Character set looks like it's valid.
+ return UTF8ToWide(text); // Return the escaped version when it's not.
+}
+
+std::string UnescapeURLComponent(const std::string& escaped_text,
+ UnescapeRule::Type rules) {
+ return UnescapeURLImpl(escaped_text, rules);
+}
+
+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(kCharsToEscape); ++k) {
+ if (c == kCharsToEscape[k].key) {
+ const char* p = kCharsToEscape[k].replacement;
+ while (*p)
+ output->push_back(*p++);
+ break;
+ }
+ }
+ if (k == arraysize(kCharsToEscape))
+ output->push_back(c);
+}
+
+void AppendEscapedCharForHTML(char c, std::string* output) {
+ AppendEscapedCharForHTMLImpl(c, output);
+}
+
+void AppendEscapedCharForHTML(wchar_t c, std::wstring* output) {
+ AppendEscapedCharForHTMLImpl(c, output);
+}
+
+template <class str>
+str EscapeForHTMLImpl(const str& input) {
+ str result;
+ result.reserve(input.size()); // optimize for no escaping
+
+ for (str::const_iterator it = input.begin(); it != input.end(); ++it)
+ AppendEscapedCharForHTMLImpl(*it, &result);
+
+ return result;
+}
+
+std::string EscapeForHTML(const std::string& input) {
+ return EscapeForHTMLImpl(input);
+}
+
+std::wstring EscapeForHTML(const std::wstring& input) {
+ return EscapeForHTMLImpl(input);
+}
diff --git a/net/base/escape.h b/net/base/escape.h
new file mode 100644
index 0000000..220eebc
--- /dev/null
+++ b/net/base/escape.h
@@ -0,0 +1,141 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_ESCAPE_H__
+#define NET_BASE_ESCAPE_H__
+
+#include <string>
+
+#include "base/basictypes.h"
+
+// Escaping --------------------------------------------------------------------
+
+// Escape a file or url path. This includes:
+// non-printable, non-7bit, and (including space) "#%:<>?[\]^`{|}
+std::string EscapePath(const std::string& path);
+
+// Escape all non-ASCII input.
+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 (;/?:@&=+$,).
+std::string EscapeExternalHandlerValue(const std::string& text);
+
+// Append the given character to the output string, escaping the character if
+// the character would be interpretted as an HTML delimiter.
+void AppendEscapedCharForHTML(char c, std::string* output);
+
+// Escape chars that might cause this text to be interpretted as HTML tags.
+std::string EscapeForHTML(const std::string& 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 special, but all normal unescaping will happen.
+ // This is a placeholder and can't be combined with other flags (since it's
+ // just the absense of them). Things like escaped letters, digits, and most
+ // symbols will get unescaped with this mode.
+ NORMAL = 0,
+
+ // 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 = 1,
+
+ // Unescapes "%25" to "%". This must not be used when the resulting string
+ // will need to be interpreted as a URL again, since we won't know what
+ // should be escaped and what shouldn't. For example, "%2520" would be
+ // converted to "%20" which would have different meaning than the origina.
+ // 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.
+ PERCENTS = 2,
+
+ // URL queries use "+" for space. This flag controls that replacement.
+ REPLACE_PLUS_WITH_SPACE = 4,
+ };
+};
+
+// 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 wide chars. This function won't tell you if any
+// conversions need to take place, it only unescapes.
+std::string UnescapeURLComponent(const std::string& escaped_text,
+ UnescapeRule::Type rules);
+
+// Unescapes the given substring as a URL, and then tries to interpret the
+// result as being encoded in the given code page. If the result is convertable
+// into the code page, it will be returned as converted. If it is not, the
+// original escaped string will be converted into a wide string and returned.
+std::wstring UnescapeAndDecodeURLComponent(const std::string& text,
+ const char* codepage,
+ UnescapeRule::Type rules);
+inline std::wstring UnescapeAndDecodeUTF8URLComponent(
+ const std::string& text,
+ UnescapeRule::Type rules) {
+ return UnescapeAndDecodeURLComponent(text, "UTF-8", rules);
+}
+
+// Deprecated ------------------------------------------------------------------
+
+// Escapes characters in text suitable for use as a query parameter value.
+// We %XX everything except alphanumerics and -_.!~*'()
+// This is basically the same as encodeURIComponent in javascript.
+// For the wstring version, we do a conversion to charset before encoding the
+// string. If the charset doesn't exist, we return false.
+//
+// TODO(brettw) bug 1201094: This function should be removed. See the bug for
+// why and what callers should do instead.
+std::string EscapeQueryParamValue(const std::string& text);
+bool EscapeQueryParamValue(const std::wstring& text, const char* codepage,
+ std::wstring* escaped);
+
+// A specialized version of EscapeQueryParamValue for wide strings that
+// assumes the codepage is UTF8. This is provided as a convenience.
+//
+// TODO(brettw) bug 1201094: This function should be removed. See the bug for
+// why and what callers should do instead.
+std::wstring EscapeQueryParamValueUTF8(const std::wstring& text);
+
+#endif // #ifndef NET_BASE_ESCAPE_H__
diff --git a/net/base/escape_unittest.cc b/net/base/escape_unittest.cc
new file mode 100644
index 0000000..d2d0288
--- /dev/null
+++ b/net/base/escape_unittest.cc
@@ -0,0 +1,229 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <string>
+
+#include "net/base/escape.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+struct unescape_case {
+ const char* input;
+ const char* output;
+};
+
+TEST(Escape, EscapeTextForFormSubmission) {
+ struct escape_case {
+ const wchar_t* input;
+ const wchar_t* output;
+ } escape_cases[] = {
+ {L"foo", L"foo"},
+ {L"foo bar", L"foo+bar"},
+ {L"foo++", L"foo%2B%2B"}
+ };
+ for (int i = 0; i < arraysize(escape_cases); ++i) {
+ escape_case value = escape_cases[i];
+ EXPECT_EQ(value.output, EscapeQueryParamValueUTF8(value.input));
+ }
+
+ // 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);
+ 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
+ char buf[4];
+ sprintf_s(buf, 4, "%%%02X", i);
+ EXPECT_EQ(std::string(buf), out);
+ } else {
+ // No change for things in the no_escape list.
+ EXPECT_EQ(out, in);
+ }
+ }
+
+ // Check to see if EscapeQueryParamValueUTF8 is the same as
+ // EscapeQueryParamValue(..., kCodepageUTF8,)
+ std::wstring test_str;
+ test_str.reserve(5000);
+ for (int i = 1; i < 5000; ++i) {
+ test_str.push_back(i);
+ }
+ std::wstring wide;
+ EXPECT_TRUE(EscapeQueryParamValue(test_str, kCodepageUTF8, &wide));
+ EXPECT_EQ(wide, EscapeQueryParamValueUTF8(test_str));
+}
+
+TEST(Escape, 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(Escape, UnescapeURLComponent) {
+ struct UnescapeCase {
+ const char* input;
+ UnescapeRule::Type rules;
+ const char* output;
+ } 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%3bOK", UnescapeRule::NORMAL, "Some%20random text %25;OK"},
+ {"Some%20random text %25%3bOK", UnescapeRule::SPACES, "Some random text %25;OK"},
+ {"Some%20random text %25%3bOK", UnescapeRule::PERCENTS, "Some%20random text %;OK"},
+ {"Some%20random text %25%3bOK", UnescapeRule::SPACES | UnescapeRule::PERCENTS, "Some random text %;OK"},
+ {"%01%02%03%04%05%06%07%08%09", UnescapeRule::NORMAL, "\x01\x02\x03\x04\x05\x06\x07\x08\x09"},
+ {"%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"}
+ };
+
+ for (int 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 escaping (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");
+
+ std::string expected("Null");
+ expected.push_back(0);
+ expected.push_back(0);
+ expected.append("9Test");
+
+ EXPECT_EQ(expected, UnescapeURLComponent(input, UnescapeRule::NORMAL));
+}
+
+TEST(Escape, UnescapeAndDecodeURLComponent) {
+ struct UnescapeCase {
+ const char* encoding;
+ 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;
+ } unescape_cases[] = {
+ {"UTF8", "+", "+", " ", L"+"},
+ {"UTF8", "%2+", "%2+", "%2 ", L"%2+"},
+ {"UTF8", "+%%%+%%%", "+%%%+%%%", " %%% %%%", L"+%%%+%%%"},
+ {"UTF8", "Don't escape anything",
+ "Don't escape anything",
+ "Don't escape anything",
+ L"Don't escape anything"},
+ {"UTF8", "+Invalid %escape %2+",
+ "+Invalid %escape %2+",
+ " Invalid %escape %2 ",
+ L"+Invalid %escape %2+"},
+ {"UTF8", "Some random text %25%3bOK",
+ "Some random text %25;OK",
+ "Some random text %25;OK",
+ L"Some random text %25;OK"},
+ {"UTF8", "%01%02%03%04%05%06%07%08%09",
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09",
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09",
+ L"\x01\x02\x03\x04\x05\x06\x07\x08\x09"},
+ {"UTF8", "%E4%BD%A0+%E5%A5%BD",
+ "\xE4\xBD\xA0+\xE5\xA5\xBD",
+ "\xE4\xBD\xA0 \xE5\xA5\xBD",
+ L"\x4f60+\x597d"},
+ {"BIG5", "%A7A%A6n",
+ "\xA7\x41\xA6n",
+ "\xA7\x41\xA6n",
+ L"\x4f60\x597d"},
+ {"UTF8", "%ED%ED", // Invalid UTF-8.
+ "\xED\xED",
+ "\xED\xED",
+ L"%ED%ED"}, // Invalid UTF-8 -> kept unescaped.
+ };
+
+ for (int 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.
+ std::wstring decoded = UnescapeAndDecodeURLComponent(
+ unescape_cases[i].input, unescape_cases[i].encoding,
+ UnescapeRule::NORMAL);
+ EXPECT_EQ(std::wstring(unescape_cases[i].decoded), decoded);
+ }
+}
+
+TEST(Escape, EscapeForHTML) {
+ static const struct {
+ const char* input;
+ const char* expected_output;
+ } 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);
+ }
+}
+
diff --git a/net/base/ev_root_ca_metadata.cc b/net/base/ev_root_ca_metadata.cc
new file mode 100644
index 0000000..062e29e
--- /dev/null
+++ b/net/base/ev_root_ca_metadata.cc
@@ -0,0 +1,205 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/ev_root_ca_metadata.h"
+
+namespace net {
+
+// Raw metadata.
+struct EVMetadata {
+ // The SHA-1 fingerprint of the root CA certificate, used as a unique
+ // identifier for a root CA certificate.
+ X509Certificate::Fingerprint fingerprint;
+
+ // The EV policy OID of the root CA.
+ // Note: a root CA may have multiple EV policies. When that actually
+ // happens, we'll need to support that.
+ const char* policy_oid;
+};
+
+static const EVMetadata ev_root_ca_metadata[] = {
+ // 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"
+ },
+ // 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"
+ },
+ // 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"
+ },
+ // 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"
+ },
+ // 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"
+ },
+ // 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"
+ },
+ // 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"
+ },
+ // 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"
+ },
+ // 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"
+ },
+ // ValiCert Class 2 Policy Validation Authority
+ // TODO(wtc): bug 1165107: this CA has another policy OID
+ // "2.16.840.1.114414.1.7.23.3".
+ { { 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"
+ },
+ // 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 - 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"
+ },
+ // 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
+EVRootCAMetadata* EVRootCAMetadata::instance_;
+
+// static
+EVRootCAMetadata* EVRootCAMetadata::GetInstance() {
+ if (!instance_) {
+ EVRootCAMetadata* new_instance = new EVRootCAMetadata;
+ if (InterlockedCompareExchangePointer(
+ reinterpret_cast<PVOID*>(&instance_), new_instance, NULL))
+ delete new_instance;
+ }
+ return instance_;
+}
+
+bool EVRootCAMetadata::GetPolicyOID(
+ const X509Certificate::Fingerprint& fingerprint,
+ std::string* policy_oid) const {
+ StringMap::const_iterator iter = ev_policy_.find(fingerprint);
+ if (iter == ev_policy_.end())
+ return false;
+ *policy_oid = iter->second;
+ return true;
+}
+
+EVRootCAMetadata::EVRootCAMetadata() {
+ // Constructs the object from the raw metadata in ev_root_ca_metadata.
+ num_policy_oids_ = arraysize(ev_root_ca_metadata);
+ policy_oids_.reset(new const char*[num_policy_oids_]);
+ for (int i = 0; i < arraysize(ev_root_ca_metadata); i++) {
+ const EVMetadata& metadata = ev_root_ca_metadata[i];
+ ev_policy_[metadata.fingerprint] = metadata.policy_oid;
+ // Multiple root CA certs may use the same EV policy OID. Having
+ // duplicates in the policy_oids_ array does no harm, so we don't
+ // bother detecting duplicates.
+ policy_oids_[i] = metadata.policy_oid;
+ }
+}
+
+} // namespace net
diff --git a/net/base/ev_root_ca_metadata.h b/net/base/ev_root_ca_metadata.h
new file mode 100644
index 0000000..a849a92
--- /dev/null
+++ b/net/base/ev_root_ca_metadata.h
@@ -0,0 +1,76 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_EV_ROOT_CA_METADATA_H__
+#define NET_BASE_EV_ROOT_CA_METADATA_H__
+
+#include <map>
+
+#include "net/base/x509_certificate.h"
+
+namespace net {
+
+// A singleton. This class stores the meta data of the root CAs that issue
+// extended-validation (EV) certificates.
+class EVRootCAMetadata {
+ public:
+ static EVRootCAMetadata* GetInstance();
+
+ // If the root CA cert has an EV policy OID, returns true and stores the
+ // policy OID in *policy_oid. Otherwise, returns false.
+ bool GetPolicyOID(const X509Certificate::Fingerprint& fingerprint,
+ std::string* policy_oid) const;
+
+ const char* const* GetPolicyOIDs() const { return policy_oids_.get(); }
+ int NumPolicyOIDs() const { return num_policy_oids_; }
+
+ private:
+ EVRootCAMetadata();
+ ~EVRootCAMetadata() { }
+
+ static EVRootCAMetadata* instance_;
+
+ typedef std::map<X509Certificate::Fingerprint, std::string,
+ X509Certificate::FingerprintLessThan> StringMap;
+
+ // Maps an EV root CA cert's SHA-1 fingerprint to its EV policy OID.
+ StringMap ev_policy_;
+
+ // Contains dotted-decimal OID strings (in ASCII). This is a C array of
+ // C strings so that it can be passed directly to Windows CryptoAPI as
+ // LPSTR*.
+ scoped_array<const char*> policy_oids_;
+ int num_policy_oids_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(EVRootCAMetadata);
+};
+
+} // namespace net
+
+#endif // NET_BASE_EV_ROOT_CA_METADATA_H__ \ No newline at end of file
diff --git a/net/base/filter.cc b/net/base/filter.cc
new file mode 100644
index 0000000..508e2d4
--- /dev/null
+++ b/net/base/filter.cc
@@ -0,0 +1,182 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/filter.h"
+
+#include "base/string_util.h"
+#include "net/base/gzip_filter.h"
+#include "net/base/bzip2_filter.h"
+
+namespace {
+
+// Filter types:
+const char kDeflate[] = "deflate";
+const char kGZip[] = "gzip";
+const char kXGZip[] = "x-gzip";
+const char kBZip2[] = "bzip2";
+const char kXBZip2[] = "x-bzip2";
+// 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";
+
+} // namespace
+
+Filter* Filter::Factory(const std::string& filter_type,
+ const std::string& mime_type,
+ int buffer_size) {
+ if (filter_type.empty() || buffer_size < 0)
+ return NULL;
+
+ FilterType type_id;
+ if (LowerCaseEqualsASCII(filter_type, kDeflate)) {
+ type_id = FILTER_TYPE_DEFLATE;
+ } else if (LowerCaseEqualsASCII(filter_type, kGZip) ||
+ LowerCaseEqualsASCII(filter_type, kXGZip)) {
+ 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.
+ type_id = FILTER_TYPE_UNSUPPORTED;
+ } else {
+ type_id = FILTER_TYPE_GZIP;
+ }
+ } else if (LowerCaseEqualsASCII(filter_type, kBZip2) ||
+ LowerCaseEqualsASCII(filter_type, kXBZip2)) {
+ type_id = FILTER_TYPE_BZIP2;
+ } else {
+ // Note we also consider "identity" and "uncompressed" UNSUPPORTED as
+ // filter should be disabled in such cases.
+ type_id = FILTER_TYPE_UNSUPPORTED;
+ }
+
+ switch (type_id) {
+ case FILTER_TYPE_DEFLATE:
+ case FILTER_TYPE_GZIP: {
+ scoped_ptr<GZipFilter> gz_filter(new GZipFilter());
+ if (gz_filter->InitBuffer(buffer_size)) {
+ if (gz_filter->InitDecoding(type_id)) {
+ return gz_filter.release();
+ }
+ }
+ break;
+ }
+ case FILTER_TYPE_BZIP2: {
+ scoped_ptr<BZip2Filter> bzip2_filter(new BZip2Filter());
+ if (bzip2_filter->InitBuffer(buffer_size)) {
+ if (bzip2_filter->InitDecoding(false)) {
+ return bzip2_filter.release();
+ }
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+Filter::Filter()
+ : stream_buffer_(NULL),
+ stream_buffer_size_(0),
+ next_stream_data_(NULL),
+ stream_data_len_(0) {
+}
+
+Filter::~Filter() {}
+
+bool Filter::InitBuffer(int buffer_size) {
+ if (buffer_size < 0 || stream_buffer())
+ return false;
+
+ stream_buffer_.reset(new char[buffer_size]);
+
+ if (stream_buffer()) {
+ stream_buffer_size_ = buffer_size;
+ return true;
+ }
+
+ return false;
+}
+
+
+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;
+ }
+}
+
+
+Filter::FilterStatus Filter::ReadFilteredData(char* dest_buffer,
+ int* dest_len) {
+ return Filter::FILTER_ERROR;
+}
+
+bool Filter::FlushStreamBuffer(int stream_data_len) {
+ if (stream_data_len <= 0 || stream_data_len > stream_buffer_size_)
+ return false;
+
+ // bail out if there are more data in the stream buffer to be filtered.
+ if (!stream_buffer() || stream_data_len_)
+ return false;
+
+ next_stream_data_ = stream_buffer();
+ stream_data_len_ = stream_data_len;
+ return true;
+}
diff --git a/net/base/filter.h b/net/base/filter.h
new file mode 100644
index 0000000..01be1ed
--- /dev/null
+++ b/net/base/filter.h
@@ -0,0 +1,167 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// 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 filters 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 "base/basictypes.h"
+#include "base/scoped_ptr.h"
+
+class Filter {
+ public:
+ // Creates a Filter object.
+ // Parameters: Filter_type specifies the type of filter created; Buffer_size
+ // specifies the size (in number of chars) of the buffer the filter should
+ // allocate to hold pre-filter data.
+ // If success, the function returns the pointer to the Filter object created.
+ // If failed or a filter is not needed, the function returns NULL.
+ static Filter* Factory(const std::string& filter_type,
+ const std::string& mime_type,
+ int buffer_size);
+
+ virtual ~Filter();
+
+ // 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
+ };
+
+ // 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);
+
+ // Returns a pointer to the beginning of stream_buffer_.
+ char* 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 have 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 empty
+ // 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);
+
+ protected:
+ Filter();
+
+ // Copy pre-filter data directly to destination buffer without decoding.
+ FilterStatus CopyOut(char* dest_buffer, int* dest_len);
+
+ // Specifies type of filters that can be created.
+ enum FilterType {
+ FILTER_TYPE_DEFLATE,
+ FILTER_TYPE_GZIP,
+ FILTER_TYPE_BZIP2,
+ FILTER_TYPE_UNSUPPORTED
+ };
+
+ // Allocates and initializes stream_buffer_.
+ // Buffer_size is the maximum size of stream_buffer_ in number of chars.
+ bool InitBuffer(int buffer_size);
+
+ // Buffer to hold the data to be filtered.
+ scoped_array<char> 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_;
+
+ // Filter can be chained
+ // TODO (huanr)
+ // Filter* next_filter_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Filter);
+};
+
+#endif // NET_BASE_FILTER_H__
diff --git a/net/base/gzip_filter.cc b/net/base/gzip_filter.cc
new file mode 100644
index 0000000..6c009ac
--- /dev/null
+++ b/net/base/gzip_filter.cc
@@ -0,0 +1,303 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <minmax.h>
+
+#include "net/base/gzip_filter.h"
+
+#include "base/logging.h"
+#include "net/base/gzip_header.h"
+#include "third_party/zlib/zlib.h"
+
+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) {
+}
+
+GZipFilter::~GZipFilter() {
+ if (decoding_status_ != DECODING_UNINITIALIZED) {
+ MOZ_Z_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: {
+ 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) {
+ // Some server might send extra data after the gzip footer. We just copy
+ // them out. Mozilla does this too.
+ SkipGZipFooter();
+ 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;
+ 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: {
+ 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
+ return Filter::FILTER_ERROR;
+
+ // 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 = MOZ_Z_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;
+
+ MOZ_Z_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 = MOZ_Z_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 = 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;
+ }
+}
diff --git a/net/base/gzip_filter.h b/net/base/gzip_filter.h
new file mode 100644
index 0000000..ae8b644
--- /dev/null
+++ b/net/base/gzip_filter.h
@@ -0,0 +1,158 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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/scoped_ptr.h"
+#include "net/base/filter.h"
+
+class GZipHeader;
+typedef struct z_stream_s z_stream;
+
+class GZipFilter : public Filter {
+ public:
+ GZipFilter();
+
+ 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);
+
+ 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;
+
+ // 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_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(GZipFilter);
+};
+
+#endif // NET_BASE_GZIP_FILTER_H__
diff --git a/net/base/gzip_filter_unittest.cc b/net/base/gzip_filter_unittest.cc
new file mode 100644
index 0000000..911bd4e
--- /dev/null
+++ b/net/base/gzip_filter_unittest.cc
@@ -0,0 +1,417 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "minmax.h"
+
+#include <fstream>
+#include <iostream>
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_ptr.h"
+#include "net/base/gzip_filter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/zlib.h"
+
+namespace {
+
+const int kDefaultBufferSize = 4096;
+const int kSmallBufferSize = 128;
+const int kMaxBufferSize = 1048576; // 1048576 == 2^20 == 1 MB
+
+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.
+};
+
+class GZipUnitTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ deflate_encode_buffer_ = NULL;
+ gzip_encode_buffer_ = NULL;
+
+ // Get the path of source data file.
+ std::wstring file_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
+ file_util::AppendToPath(&file_path, L"net");
+ file_util::AppendToPath(&file_path, L"data");
+ file_util::AppendToPath(&file_path, L"filter_unittests");
+ file_util::AppendToPath(&file_path, L"google.txt");
+
+ // Read data from the file into buffer.
+ 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_TRUE(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_TRUE(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;
+ }
+
+ // 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 = MOZ_Z_deflate(&zlib_stream, Z_FINISH);
+ *dest_len = *dest_len - zlib_stream.avail_out;
+
+ MOZ_Z_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 = min(encode_avail_size, filter->stream_buffer_size());
+ memcpy(filter->stream_buffer(), 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 = min(decode_avail_size, output_buffer_size);
+
+ code = filter->ReadFilteredData(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(), source, source_len);
+ filter->FlushStreamBuffer(source_len);
+ return filter->ReadFilteredData(dest, dest_len);
+ }
+
+ const char* source_buffer() const { return source_buffer_.data(); }
+ int source_len() const { return static_cast<int>(source_buffer_.size()); }
+
+ std::string source_buffer_;
+
+ char* deflate_encode_buffer_;
+ int deflate_encode_len_;
+
+ char* gzip_encode_buffer_;
+ int gzip_encode_len_;
+};
+
+}; // namespace
+
+// Basic scenario: decoding deflate data with big enough buffer.
+TEST_F(GZipUnitTest, DecodeDeflate) {
+ // Decode the compressed data with filter
+ scoped_ptr<Filter> filter(
+ Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ memcpy(filter->stream_buffer(), deflate_encode_buffer_, deflate_encode_len_);
+ filter->FlushStreamBuffer(deflate_encode_len_);
+
+ char deflate_decode_buffer[kDefaultBufferSize];
+ int deflate_decode_size = kDefaultBufferSize;
+ filter->ReadFilteredData(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
+ scoped_ptr<Filter> filter(
+ Filter::Factory("gzip", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ memcpy(filter->stream_buffer(), gzip_encode_buffer_, gzip_encode_len_);
+ filter->FlushStreamBuffer(gzip_encode_len_);
+
+ char gzip_decode_buffer[kDefaultBufferSize];
+ int gzip_decode_size = kDefaultBufferSize;
+ filter->ReadFilteredData(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) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("deflate", kApplicationOctetStream, kSmallBufferSize));
+ ASSERT_TRUE(filter.get());
+ 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) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("gzip", kApplicationOctetStream, 1));
+ ASSERT_TRUE(filter.get());
+ 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) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ 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) {
+ scoped_ptr<Filter> filter(
+ Filter::Factory("gzip", kApplicationOctetStream, 1));
+ ASSERT_TRUE(filter.get());
+ 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
+ scoped_ptr<Filter> filter(
+ Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ 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;
+ memcpy(&corrupt_data[pos], &corrupt_data[pos+1], len);
+ --corrupt_data_len;
+
+ // Decode the corrupted data with filter
+ scoped_ptr<Filter> filter(
+ Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ 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 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
+ scoped_ptr<Filter> filter(
+ Filter::Factory("gzip", kApplicationOctetStream, kDefaultBufferSize));
+ ASSERT_TRUE(filter.get());
+ 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);
+}
+
+TEST_F(GZipUnitTest, ApacheWorkaround) {
+ const int kBufferSize = kDefaultBufferSize; // To fit in 80 cols.
+ scoped_ptr<Filter> filter;
+
+ filter.reset(Filter::Factory("gzip", kApplicationXGzip, kBufferSize));
+ EXPECT_FALSE(filter.get());
+ filter.reset(Filter::Factory("gzip", kApplicationGzip, kBufferSize));
+ EXPECT_FALSE(filter.get());
+ filter.reset(Filter::Factory("gzip", kApplicationXGunzip, kBufferSize));
+ EXPECT_FALSE(filter.get());
+
+ filter.reset(Filter::Factory("x-gzip", kApplicationXGzip, kBufferSize));
+ EXPECT_FALSE(filter.get());
+ filter.reset(Filter::Factory("x-gzip", kApplicationGzip, kBufferSize));
+ EXPECT_FALSE(filter.get());
+ filter.reset(Filter::Factory("x-gzip", kApplicationXGunzip, kBufferSize));
+ EXPECT_FALSE(filter.get());
+}
diff --git a/net/base/gzip_header.cc b/net/base/gzip_header.cc
new file mode 100644
index 0000000..db810dc
--- /dev/null
+++ b/net/base/gzip_header.cc
@@ -0,0 +1,199 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <minmax.h>
+
+#include "net/base/gzip_header.h"
+
+#include "base/logging.h"
+#include "third_party/zlib/zlib.h" // for Z_DEFAULT_COMPRESSION
+
+const uint8 GZipHeader::magic[] = { 0x1f, 0x8b };
+
+// ----------------------------------------------------------------------
+// GZipHeader::ReadMore()
+// Attempt to parse the beginning of the given buffer as a gzip
+// header. If these bytes do not constitute a complete gzip header,
+// return INCOMPLETE_HEADER. If these bytes do not constitute a
+// *valid* gzip header, return INVALID_HEADER. If we find a
+// complete header, return COMPLETE_HEADER and set the pointer
+// pointed to by header_end to the first byte beyond the gzip header.
+// ----------------------------------------------------------------------
+
+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>(min(
+ 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;
+ }
+}
diff --git a/net/base/gzip_header.h b/net/base/gzip_header.h
new file mode 100644
index 0000000..63ea5a9
--- /dev/null
+++ b/net/base/gzip_header.h
@@ -0,0 +1,120 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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_GZIPHEADER_H__
+#define NET_BASE_GZIPHEADER_H__
+
+#include "base/basictypes.h"
+
+class GZipHeader {
+ public:
+ GZipHeader() {
+ Reset();
+ }
+ ~GZipHeader() {
+ }
+
+ // Wipe the slate clean and start from scratch.
+ void Reset() {
+ state_ = IN_HEADER_ID1;
+ flags_ = 0;
+ extra_length_ = 0;
+ }
+
+ enum Status {
+ INCOMPLETE_HEADER, // don't have all the bits yet...
+ COMPLETE_HEADER, // complete, valid header
+ INVALID_HEADER, // found something invalid in the header
+ };
+
+ // 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:
+
+ static const uint8 magic[]; // gzip magic header
+
+ 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,
+ };
+
+ 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
+};
+
+#endif // NET_BASE_GZIPHEADER_H__
diff --git a/net/base/host_resolver.cc b/net/base/host_resolver.cc
new file mode 100644
index 0000000..f3091a6
--- /dev/null
+++ b/net/base/host_resolver.cc
@@ -0,0 +1,161 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/host_resolver.h"
+
+#include <ws2tcpip.h>
+#include <wspiapi.h> // Needed for Win2k compat.
+
+#include "base/string_util.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/winsock_init.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+static int ResolveAddrInfo(const std::string& host, const std::string& port,
+ struct addrinfo** results) {
+ struct addrinfo hints = {0};
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+ // Restrict result set to only this socket type to avoid duplicates.
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err = getaddrinfo(host.c_str(), port.c_str(), &hints, results);
+ return err ? ERR_NAME_NOT_RESOLVED : OK;
+}
+
+//-----------------------------------------------------------------------------
+
+struct HostResolver::Request :
+ public base::RefCountedThreadSafe<HostResolver::Request> {
+ Request() : error(OK), results(NULL) {
+ DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
+ GetCurrentProcess(), &origin_thread,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ }
+ ~Request() {
+ CloseHandle(origin_thread);
+ }
+
+ // Only used on the origin thread (where Resolve was called).
+ AddressList* addresses;
+ CompletionCallback* callback;
+
+ // Set on the origin thread, read on the worker thread.
+ std::string host;
+ std::string port;
+ HANDLE origin_thread;
+
+ // Assigned on the worker thread.
+ int error;
+ struct addrinfo* results;
+
+ static void CALLBACK ReturnResults(ULONG_PTR param) {
+ Request* r = reinterpret_cast<Request*>(param);
+ // The HostResolver may have gone away.
+ if (r->addresses) {
+ DCHECK(r->addresses);
+ r->addresses->Adopt(r->results);
+ if (r->callback)
+ r->callback->Run(r->error);
+ } else if (r->results) {
+ freeaddrinfo(r->results);
+ }
+ r->Release();
+ }
+
+ static DWORD CALLBACK DoLookup(void* param) {
+ Request* r = static_cast<Request*>(param);
+
+ r->error = ResolveAddrInfo(r->host, r->port, &r->results);
+
+ if (!QueueUserAPC(ReturnResults, r->origin_thread,
+ reinterpret_cast<ULONG_PTR>(param))) {
+ // The origin thread must have gone away.
+ if (r->results)
+ freeaddrinfo(r->results);
+ r->Release();
+ }
+ return 0;
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+HostResolver::HostResolver() {
+ EnsureWinsockInit();
+}
+
+HostResolver::~HostResolver() {
+ if (request_) {
+ request_->addresses = NULL;
+ request_->callback = NULL;
+ }
+}
+
+int HostResolver::Resolve(const std::string& hostname, int port,
+ AddressList* addresses,
+ CompletionCallback* callback) {
+ DCHECK(!request_);
+
+ const std::string& port_str = IntToString(port);
+
+ // Do a synchronous resolution?
+ if (!callback) {
+ struct addrinfo* results;
+ int rv = ResolveAddrInfo(hostname, port_str, &results);
+ if (rv == OK)
+ addresses->Adopt(results);
+ return rv;
+ }
+
+ // Dispatch to worker thread...
+ request_ = new Request();
+ request_->host = hostname;
+ request_->port = port_str;
+ request_->addresses = addresses;
+ request_->callback = callback;
+
+ // Balanced in Request::ReturnResults (or DoLookup if there is an error).
+ request_->AddRef();
+ if (!QueueUserWorkItem(Request::DoLookup, request_, WT_EXECUTELONGFUNCTION)) {
+ DLOG(ERROR) << "QueueUserWorkItem failed: " << GetLastError();
+ request_->Release();
+ request_ = NULL;
+ return ERR_FAILED;
+ }
+
+ return ERR_IO_PENDING;
+}
+
+} // namespace net
diff --git a/net/base/host_resolver.h b/net/base/host_resolver.h
new file mode 100644
index 0000000..327e019
--- /dev/null
+++ b/net/base/host_resolver.h
@@ -0,0 +1,72 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_HOST_RESOLVER_H__
+#define NET_BASE_HOST_RESOLVER_H__
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "net/base/completion_callback.h"
+
+namespace net {
+
+class AddressList;
+
+// This class represents the task of resolving a single hostname. To resolve
+// multiple hostnames, a new resolver will need to be created for each.
+class HostResolver {
+ public:
+ HostResolver();
+
+ // If a completion callback is pending when the resolver is destroyed, the
+ // host resolution is cancelled, and the completion callback will not be
+ // called.
+ ~HostResolver();
+
+ // Resolves the given hostname, filling out the |addresses| object upon
+ // success. The |port| parameter is optional (will be set as the sin_port
+ // field of the sockaddr_in{6} struct). Returns OK if successful or an error
+ // code upon failure.
+ //
+ // When callback is non-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.
+ //
+ int Resolve(const std::string& hostname, int port,
+ AddressList* addresses, CompletionCallback* callback);
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(HostResolver);
+ struct Request;
+ scoped_refptr<Request> request_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_HOST_RESOLVER_H__
diff --git a/net/base/listen_socket.cc b/net/base/listen_socket.cc
new file mode 100644
index 0000000..4893dcc
--- /dev/null
+++ b/net/base/listen_socket.cc
@@ -0,0 +1,188 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// winsock2.h must be included first in order to ensure it is included before
+// windows.h.
+#include <winsock2.h>
+
+#include "net/base/listen_socket.h"
+
+#include "base/thread.h"
+
+#define READ_BUF_SIZE 200
+
+ListenSocket::ListenSocket(SOCKET s, ListenSocketDelegate *del,
+ MessageLoop *loop)
+ : socket_(s), socket_delegate_(del), loop_(loop) {
+ socket_event_ = WSACreateEvent();
+ WSAEventSelect(socket_, socket_event_, FD_ACCEPT | FD_CLOSE | FD_READ);
+ loop_->WatchObject(socket_event_, this);
+}
+
+ListenSocket::~ListenSocket() {
+ DCHECK(MessageLoop::current() == loop_);
+ if (socket_event_) {
+ loop_->WatchObject(socket_event_, NULL);
+ WSACloseEvent(socket_event_);
+ }
+ if (socket_) {
+ closesocket(socket_);
+ }
+}
+
+SOCKET ListenSocket::Listen(std::string ip, int port) {
+ SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (s != INVALID_SOCKET) {
+ sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(ip.c_str());
+ addr.sin_port = htons(port);
+ if (bind(s, reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr))) {
+ closesocket(s);
+ s = INVALID_SOCKET;
+ }
+ }
+ return s;
+}
+
+ListenSocket* ListenSocket::Listen(std::string ip, int port,
+ ListenSocketDelegate* del, MessageLoop* l) {
+ SOCKET s = Listen(ip, port);
+ if (s == INVALID_SOCKET) {
+ // TODO(erikkay): error handling
+ } else {
+ ListenSocket* sock = new ListenSocket(s, del, l);
+ sock->Listen();
+ return sock;
+ }
+ return NULL;
+}
+
+void ListenSocket::Listen() {
+ DCHECK(MessageLoop::current() == loop_);
+ int backlog = 10; // TODO(erikkay): maybe don't allow any backlog?
+ listen(socket_, backlog);
+ // TODO(erikkay): handle error
+}
+
+SOCKET ListenSocket::Accept(SOCKET s) {
+ sockaddr_in from;
+ int from_len = sizeof(from);
+ SOCKET conn = accept(s, reinterpret_cast<SOCKADDR*>(&from), &from_len);
+ if (conn != INVALID_SOCKET) {
+ // a non-blocking socket
+ unsigned long no_block = 1;
+ ioctlsocket(conn, FIONBIO, &no_block);
+ }
+ return conn;
+}
+
+void ListenSocket::Accept() {
+ SOCKET conn = Accept(socket_);
+ if (conn == INVALID_SOCKET) {
+ // TODO
+ } else {
+ scoped_refptr<ListenSocket> sock =
+ new ListenSocket(conn, socket_delegate_, loop_);
+ // it's up to the delegate to AddRef if it wants to keep it around
+ socket_delegate_->DidAccept(this, sock);
+ }
+}
+
+void ListenSocket::Read() {
+ char buf[READ_BUF_SIZE+1];
+ int len;
+ do {
+ len = recv(socket_, buf, READ_BUF_SIZE, 0);
+ if (len == SOCKET_ERROR) {
+ int err = WSAGetLastError();
+ if (err == WSAEWOULDBLOCK) {
+ break;
+ } else {
+ // TODO - error
+ break;
+ }
+ } else if (len == 0) {
+ // socket closed, ignore
+ } else {
+ // TODO(erikkay): maybe change DidRead to take a length instead
+ DCHECK(len > 0 && len <= READ_BUF_SIZE);
+ buf[len] = 0;
+ socket_delegate_->DidRead(this, buf);
+ }
+ } while (len == READ_BUF_SIZE);
+}
+
+void ListenSocket::Close() {
+ socket_delegate_->DidClose(this);
+}
+
+// MessageLoop watcher callback
+void ListenSocket::OnObjectSignaled(HANDLE object) {
+ WSANETWORKEVENTS ev;
+ if (SOCKET_ERROR == WSAEnumNetworkEvents(socket_, socket_event_, &ev)) {
+ // TODO
+ return;
+ }
+ 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) {
+ Read();
+ }
+ if (ev.lNetworkEvents & FD_CLOSE) {
+ Close();
+ }
+}
+
+void ListenSocket::SendInternal(const char* bytes, int len) {
+ DCHECK(MessageLoop::current() == loop_);
+ int sent = send(socket_, bytes, len, 0);
+ if (sent == SOCKET_ERROR) {
+ // TODO
+ } else if (sent != len) {
+ // TODO
+ }
+}
+
+void ListenSocket::Send(const char* bytes, int len, bool append_linefeed) {
+ SendInternal(bytes, len);
+ if (append_linefeed) {
+ SendInternal("\r\n", 2);
+ }
+}
+
+void ListenSocket::Send(const std::string& str, bool append_linefeed) {
+ Send(str.data(), static_cast<int>(str.length()), append_linefeed);
+}
diff --git a/net/base/listen_socket.h b/net/base/listen_socket.h
new file mode 100644
index 0000000..92df390
--- /dev/null
+++ b/net/base/listen_socket.h
@@ -0,0 +1,99 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// TCP/IP server that handles IO asynchronously in the specified MessageLoop.
+// These objects are NOT thread safe. They use 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
+// constructors and destructors) should also be called from the same thread.
+
+#ifndef NET_BASE_SOCKET_H__
+#define NET_BASE_SOCKET_H__
+
+#include "base/basictypes.h"
+#include "base/message_loop.h"
+#include "base/ref_counted.h"
+
+#include <winsock2.h>
+
+// Implements a raw socket interface
+class ListenSocket : public base::RefCountedThreadSafe<ListenSocket>,
+ public MessageLoop::Watcher {
+ 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 ListenSocketDelegate {
+ 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(ListenSocket *server, ListenSocket *connection) = 0;
+ virtual void DidRead(ListenSocket *connection, const std::string& data) = 0;
+ virtual void DidClose(ListenSocket *sock) = 0;
+ };
+
+ // Listen on port for the specified IP address. Use 127.0.0.1 to only
+ // accept local connections.
+ static ListenSocket* Listen(std::string ip, int port,
+ ListenSocketDelegate* del,
+ MessageLoop* loop);
+ virtual ~ListenSocket();
+
+ // 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);
+
+ protected:
+ ListenSocket(SOCKET s, ListenSocketDelegate* del, MessageLoop* loop);
+ static SOCKET Listen(std::string ip, int port);
+ // if valid, returned SOCKET is non-blocking
+ static SOCKET Accept(SOCKET s);
+
+ virtual void SendInternal(const char* bytes, int len);
+
+ // MessageLoop watcher callback
+ virtual void OnObjectSignaled(HANDLE object);
+
+ virtual void Listen();
+ virtual void Accept();
+ virtual void Read();
+ virtual void Close();
+
+ SOCKET socket_;
+ HANDLE socket_event_;
+ ListenSocketDelegate *socket_delegate_;
+ MessageLoop* loop_;
+
+ private:
+
+ DISALLOW_EVIL_CONSTRUCTORS(ListenSocket);
+};
+
+#endif // BASE_SOCKET_H__
diff --git a/net/base/listen_socket_unittest.cc b/net/base/listen_socket_unittest.cc
new file mode 100644
index 0000000..f8b2609
--- /dev/null
+++ b/net/base/listen_socket_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Tests ListenSocket.
+
+#include "net/base/listen_socket_unittest.h"
+
+namespace {
+
+class ListenSocketTest: public testing::Test {
+public:
+ ListenSocketTest() {
+ tester_ = NULL;
+ }
+
+ virtual void SetUp() {
+ tester_ = new ListenSocketTester();
+ tester_->SetUp();
+ }
+
+ virtual void TearDown() {
+ tester_->TearDown();
+ tester_ = NULL;
+ }
+
+ scoped_refptr<ListenSocketTester> tester_;
+};
+
+} // namespace
+
+TEST_F(ListenSocketTest, ClientSend) {
+ tester_->TestClientSend();
+}
+
+TEST_F(ListenSocketTest, ClientSendLong) {
+ tester_->TestClientSendLong();
+}
+
+TEST_F(ListenSocketTest, ServerSend) {
+ tester_->TestServerSend();
+}
diff --git a/net/base/listen_socket_unittest.h b/net/base/listen_socket_unittest.h
new file mode 100644
index 0000000..8160ade
--- /dev/null
+++ b/net/base/listen_socket_unittest.h
@@ -0,0 +1,291 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef LISTEN_SOCKET_UNITTEST_H__
+#define LISTEN_SOCKET_UNITTEST_H__
+
+#include <winsock2.h>
+
+#include <deque>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "net/base/listen_socket.h"
+#include "net/base/winsock_init.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int TEST_PORT = 9999;
+const std::string HELLO_WORLD("HELLO, WORLD");
+const int MAX_QUEUE_SIZE = 20;
+
+enum ActionType {
+ ACTION_NONE = 0,
+ ACTION_LISTEN = 1,
+ ACTION_ACCEPT = 2,
+ ACTION_READ = 3,
+ ACTION_SEND = 4,
+ ACTION_CLOSE = 5,
+};
+
+class ListenSocketTestAction {
+ public:
+ ListenSocketTestAction() : action_(ACTION_NONE) {}
+ explicit ListenSocketTestAction(ActionType action) : action_(action) {}
+ ListenSocketTestAction(ActionType action, std::string data)
+ : action_(action),
+ data_(data) {}
+
+ const std::string data() const { return data_; }
+ const ActionType type() const { return action_; }
+
+ private:
+ std::string data_;
+ ActionType action_;
+};
+
+// This had to be split out into a separate class because I couldn't
+// make a the testing::Test class refcounted.
+class ListenSocketTester :
+ public ListenSocket::ListenSocketDelegate,
+ public base::RefCountedThreadSafe<ListenSocketTester> {
+ protected:
+ virtual ListenSocket* DoListen() {
+ return ListenSocket::Listen("127.0.0.1", TEST_PORT, this, loop_);
+ }
+
+ public:
+ ListenSocketTester()
+ : server_(NULL),
+ connection_(NULL),
+ thread_(NULL),
+ loop_(NULL) {
+ }
+
+ virtual ~ListenSocketTester() {
+ }
+
+ virtual void SetUp() {
+ InitializeCriticalSection(&lock_);
+ semaphore_ = CreateSemaphore(NULL, 0, MAX_QUEUE_SIZE, NULL);
+ server_ = NULL;
+ WinsockInit::Init();
+
+ thread_.reset(new Thread("socketio_test"));
+ thread_->Start();
+ loop_ = thread_->message_loop();
+
+ loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ListenSocketTester::Listen));
+
+ // verify Listen succeeded
+ ASSERT_TRUE(NextAction());
+ ASSERT_FALSE(server_ == 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);
+ struct sockaddr_in client;
+ client.sin_family = AF_INET;
+ client.sin_addr.s_addr = inet_addr("127.0.0.1");
+ client.sin_port = htons(TEST_PORT);
+ int ret = connect(test_socket_,
+ reinterpret_cast<SOCKADDR*>(&client), sizeof(client));
+ ASSERT_NE(ret, SOCKET_ERROR);
+ // non-blocking socket
+ unsigned long no_block = 1;
+ ioctlsocket(test_socket_, FIONBIO, &no_block);
+ ASSERT_TRUE(NextAction());
+ ASSERT_EQ(ACTION_ACCEPT, last_action_.type());
+ }
+
+ virtual void TearDown() {
+ // verify close
+ closesocket(test_socket_);
+ ASSERT_TRUE(NextAction(5000));
+ ASSERT_EQ(ACTION_CLOSE, last_action_.type());
+
+ CloseHandle(semaphore_);
+ semaphore_ = 0;
+ DeleteCriticalSection(&lock_);
+ if (connection_) {
+ loop_->ReleaseSoon(FROM_HERE, connection_);
+ connection_ = NULL;
+ }
+ if (server_) {
+ loop_->ReleaseSoon(FROM_HERE, server_);
+ server_ = NULL;
+ }
+ thread_.reset();
+ loop_ = NULL;
+ WinsockInit::Cleanup();
+ }
+
+ void ReportAction(const ListenSocketTestAction& action) {
+ EnterCriticalSection(&lock_);
+ queue_.push_back(action);
+ LeaveCriticalSection(&lock_);
+ ReleaseSemaphore(semaphore_, 1, NULL);
+ }
+
+ bool NextAction(int timeout = 5000) {
+ DWORD ret = ::WaitForSingleObject(semaphore_, timeout);
+ if (ret != WAIT_OBJECT_0)
+ return false;
+ EnterCriticalSection(&lock_);
+ if (queue_.size() == 0)
+ return false;
+ last_action_ = queue_.front();
+ queue_.pop_front();
+ LeaveCriticalSection(&lock_);
+ return true;
+ }
+
+ // read all pending data from the test socket
+ int ClearTestSocket() {
+ char buf[1024];
+ int len = 0;
+ do {
+ int ret = recv(test_socket_, buf, 1024, 0);
+ if (ret < 0) {
+ int err = WSAGetLastError();
+ if (err == WSAEWOULDBLOCK) {
+ break;
+ }
+ } else {
+ len += ret;
+ }
+ } while (true);
+ return len;
+ }
+
+ void Listen() {
+ server_ = DoListen();
+ if (server_) {
+ server_->AddRef();
+ ReportAction(ListenSocketTestAction(ACTION_LISTEN));
+ }
+ }
+
+ void SendFromTester() {
+ connection_->Send(HELLO_WORLD);
+ ReportAction(ListenSocketTestAction(ACTION_SEND));
+ }
+
+ virtual void DidAccept(ListenSocket *server, ListenSocket *connection) {
+ connection_ = connection;
+ connection_->AddRef();
+ ReportAction(ListenSocketTestAction(ACTION_ACCEPT));
+ }
+
+ virtual void DidRead(ListenSocket *connection, const std::string& data) {
+ ReportAction(ListenSocketTestAction(ACTION_READ, data));
+ }
+
+ virtual void DidClose(ListenSocket *sock) {
+ ReportAction(ListenSocketTestAction(ACTION_CLOSE));
+ }
+
+ virtual bool Send(SOCKET sock, const std::string& str) {
+ int len = static_cast<int>(str.length());
+ int send_len = send(sock, str.data(), len, 0);
+ if (send_len != len) {
+ return false;
+ }
+ return true;
+ }
+
+ // verify the send/read from client to server
+ void TestClientSend() {
+ ASSERT_TRUE(Send(test_socket_, HELLO_WORLD));
+ ASSERT_TRUE(NextAction());
+ ASSERT_EQ(ACTION_READ, last_action_.type());
+ ASSERT_EQ(last_action_.data(), HELLO_WORLD);
+ }
+
+ // verify send/read of a longer string
+ void TestClientSendLong() {
+ int hello_len = static_cast<int>(HELLO_WORLD.length());
+ std::string long_string;
+ int long_len = 0;
+ for (int i = 0; i < 200; i++) {
+ long_string += HELLO_WORLD;
+ long_len += hello_len;
+ }
+ ASSERT_TRUE(Send(test_socket_, long_string));
+ int read_len = 0;
+ while (read_len < long_len) {
+ ASSERT_TRUE(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 += static_cast<int>(last_data.length());
+ }
+ ASSERT_EQ(read_len, long_len);
+ }
+
+ // verify a send/read from server to client
+ void TestServerSend() {
+ loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ListenSocketTester::SendFromTester));
+ ASSERT_TRUE(NextAction());
+ ASSERT_EQ(ACTION_SEND, last_action_.type());
+ // TODO(erikkay): Without this sleep, the recv seems to fail a small amount
+ // of the time. I could fix this by making the socket blocking, but then
+ // this test might hang in the case of errors. It would be nice to do
+ // something that felt more reliable here.
+ Sleep(10);
+ const int buf_len = 200;
+ char buf[buf_len+1];
+ int recv_len = recv(test_socket_, buf, buf_len, 0);
+ buf[recv_len] = 0;
+ ASSERT_EQ(buf, HELLO_WORLD);
+ }
+
+ scoped_ptr<Thread> thread_;
+ MessageLoop* loop_;
+ ListenSocket* server_;
+ ListenSocket* connection_;
+ CRITICAL_SECTION lock_;
+ HANDLE semaphore_;
+ ListenSocketTestAction last_action_;
+ std::deque<ListenSocketTestAction> queue_;
+ SOCKET test_socket_;
+};
+
+} // namespace
+
+#endif // LISTEN_SOCKET_UNITTEST_H__
diff --git a/net/base/load_flags.h b/net/base/load_flags.h
new file mode 100644
index 0000000..9a33b37
--- /dev/null
+++ b/net/base/load_flags.h
@@ -0,0 +1,88 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#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 {
+ LOAD_NORMAL = 0,
+
+ // This is "normal reload", meaning an if-none-match/if-modified-since query
+ LOAD_VALIDATE_CACHE = 1 << 0,
+
+ // This is "shift-reload", meaning a "pragma: no-cache" end-to-end fetch
+ LOAD_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_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_ONLY_FROM_CACHE = 1 << 3,
+
+ // This is a navigation that will not use the cache at all. It does not
+ // impact the HTTP request headers.
+ LOAD_DISABLE_CACHE = 1 << 4,
+
+ // This is a navigation that will not be intercepted by any registered
+ // URLRequest::Interceptors.
+ LOAD_DISABLE_INTERCEPT = 1 << 5,
+
+ // If present, upload progress messages should be provided to initiator.
+ LOAD_ENABLE_UPLOAD_PROGRESS = 1 << 6,
+
+ // If present, ignores certificate mismatches with the domain name.
+ // (The default behavior is to trigger an OnSSLCertificateError callback.)
+ LOAD_IGNORE_CERT_COMMON_NAME_INVALID = 1 << 8,
+
+ // If present, ignores certificate expiration dates
+ // (The default behavior is to trigger an OnSSLCertificateError callback).
+ LOAD_IGNORE_CERT_DATE_INVALID = 1 << 9,
+
+ // If present, trusts all certificate authorities
+ // (The default behavior is to trigger an OnSSLCertificateError callback).
+ LOAD_IGNORE_CERT_AUTHORITY_INVALID = 1 << 10,
+
+ // If present, ignores certificate revocation
+ // (The default behavior is to trigger an OnSSLCertificateError callback).
+ LOAD_IGNORE_CERT_REVOCATION = 1 << 11,
+
+ // If present, ignores wrong key usage of the certificate
+ // (The default behavior is to trigger an OnSSLCertificateError callback).
+ LOAD_IGNORE_CERT_WRONG_USAGE = 1 << 12,
+};
+
+} // namespace net
+
+#endif // NET_BASE_LOAD_FLAGS_H__
diff --git a/net/base/load_states.h b/net/base/load_states.h
new file mode 100644
index 0000000..719026e
--- /dev/null
+++ b/net/base/load_states.h
@@ -0,0 +1,90 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_LOAD_STATES_H__
+#define NET_BASE_LOAD_STATES_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 {
+ // 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,
+
+ // 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 a
+ // proxy autoconfig script to return a proxy server to use. This state may
+ // take a while if the proxy script needs to resolve the IP address of the
+ // host before deciding what proxy to use.
+ LOAD_STATE_RESOLVING_PROXY_FOR_URL,
+
+ // 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 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,
+};
+
+} // namespace net
+
+#endif // NET_BASE_LOAD_STATES_H__
diff --git a/net/base/mime_sniffer.cc b/net/base/mime_sniffer.cc
new file mode 100644
index 0000000..12aec1d
--- /dev/null
+++ b/net/base/mime_sniffer.cc
@@ -0,0 +1,623 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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
+//
+// Given our previous decisions, this decision is more or less clear.
+// => Chrome: Render as GIF
+//
+// 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/histogram.h"
+#include "base/logging.h"
+#include "base/registry.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/mime_util.h"
+
+namespace {
+
+class SnifferHistogram : public LinearHistogram {
+ public:
+ SnifferHistogram(const wchar_t* name, int array_size)
+ : LinearHistogram(name, 0, array_size - 1, array_size) {
+ SetFlags(kUmaTargetedHistogramFlag);
+ }
+};
+
+} // namespace
+
+namespace mime_util {
+
+// We aren't interested in looking at more than 512 bytes of content
+static const size_t kMaxBytesToSniff = 512;
+
+// 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;
+};
+
+#define MAGIC_NUMBER(mime_type, magic) \
+ { (mime_type), (magic), sizeof(magic)-1, false },
+
+// Magic strings are case insensitive and must not include '\0' characters
+#define MAGIC_STRING(mime_type, magic) \
+ { (mime_type), (magic), sizeof(magic)-1, true },
+
+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("application/postscript", "%! PS-Adobe-")
+ // Mozilla uses "\x4a47????00" for image/x-jg, but we use stronger pattern
+ MAGIC_NUMBER("image/x-icon", "\x00\x00\x10\x00")
+ MAGIC_NUMBER("image/x-icon", "\x00\x00\x20\x00")
+ MAGIC_NUMBER("image/x-xbitmap", "#define ")
+ 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("image/x-rgb", "\x01\xDA\x01\x01\x00\x03")
+ MAGIC_NUMBER("application/x-gzip", "\x1F\x8B\x08")
+ MAGIC_NUMBER("application/x-compress", "\x1F\x9D\x90") // tar.Z
+ 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("application/winhlp", "?_\x03")
+ MAGIC_NUMBER("application/winhlp", "LN\x02\x00")
+ MAGIC_NUMBER("application/x-bzip2", "BZ")
+ MAGIC_NUMBER("image/tiff", "I I")
+ MAGIC_NUMBER("image/tiff", "II*")
+ MAGIC_NUMBER("image/tiff", "MM\x00*")
+ MAGIC_NUMBER("audio/mpeg", "ID3")
+ // 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("image/x-jg", "\x4A\x47\x03\x0E\x00\x00\x00")
+ MAGIC_NUMBER("image/x-jg", "\x4A\x47\x04\x0E\x00\x00\x00")
+ MAGIC_NUMBER("image/x-portable-graymap", "P4\x0A")
+ MAGIC_NUMBER("application/zip", "PK\x03\x04")
+ MAGIC_NUMBER("application/x-rar-compressed", "Rar!\x1A\x07\x00")
+ MAGIC_NUMBER("application/rtf", "{\\rtf1")
+ MAGIC_NUMBER("application/postscript", "\xC5\xD0\xD3\xC6")
+ MAGIC_NUMBER("application/x-msmetafile", "\xD7\xCD\xC6\x9A")
+ MAGIC_NUMBER("application/octet-stream", "\x7F" "ELF") // ELF
+ MAGIC_NUMBER("application/octet-stream", "\xE8") // COM, SYS
+ MAGIC_NUMBER("application/octet-stream", "\xE9") // COM, SYS
+ MAGIC_NUMBER("application/octet-stream", "\xEB") // COM, SYS
+ 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.
+};
+
+// 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 web documents with a
+ // sniffable mime type (as measured in 2007).
+ MAGIC_HTML_TAG("html") // HTML5 spec, Mozilla
+ MAGIC_HTML_TAG("head") // HTML5 spec, Mozilla
+ MAGIC_HTML_TAG("script") // HTML5 spec, Mozilla
+ MAGIC_HTML_TAG("tr")
+ MAGIC_HTML_TAG("link") // Mozilla
+ MAGIC_HTML_TAG("meta") // Mozilla
+ MAGIC_HTML_TAG("title") // Mozilla
+ MAGIC_HTML_TAG("pre") // Mozilla
+ MAGIC_HTML_TAG("table") // Mozilla
+ MAGIC_HTML_TAG("basefont")
+ // Not HTML: "xml"
+ MAGIC_HTML_TAG("p") // Mozilla
+ MAGIC_HTML_TAG("div") // Mozilla
+ MAGIC_HTML_TAG("base") // Mozilla
+ // Not HTML: "metadata"
+ MAGIC_HTML_TAG("body") // Mozilla
+ // Not HTML: "asx"
+ MAGIC_HTML_TAG("frameset") // Mozilla
+ // Not HTML: "sami"
+ MAGIC_HTML_TAG("a") // Mozilla
+ MAGIC_HTML_TAG("style") // Mozilla
+ // Not HTML: "rss"
+ MAGIC_HTML_TAG("br")
+ MAGIC_HTML_TAG("center") // Mozilla
+ MAGIC_HTML_TAG("b") // Mozilla
+ MAGIC_HTML_TAG("iframe") // Mozilla
+ MAGIC_HTML_TAG("img") // Mozilla
+ MAGIC_HTML_TAG("h1") // Mozilla
+ MAGIC_HTML_TAG("td")
+ // Not HTML: "printer"
+ MAGIC_HTML_TAG("font") // Mozilla
+ // Not HTML: "htlm"
+ MAGIC_HTML_TAG("form") // Mozilla
+ // Not HTML: "master"
+ MAGIC_HTML_TAG("h3") // Mozilla
+ MAGIC_HTML_TAG("h2") // Mozilla
+ // Plus a long tail, but we need to stop somewhere.
+ //
+ // We also include all the other tags that Mozilla sniffs:
+ MAGIC_HTML_TAG("!--")
+ MAGIC_HTML_TAG("applet")
+ MAGIC_HTML_TAG("isindex")
+ MAGIC_HTML_TAG("h4")
+ MAGIC_HTML_TAG("h5")
+ MAGIC_HTML_TAG("h6")
+};
+
+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(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) ? (end - content) : size;
+
+ bool match = false;
+ if (magic_entry->is_string) {
+ if (content_strlen >= len) {
+ // String comparisons are case-insensitive
+ match = (_strnicmp(magic_entry->magic, content, len) == 0);
+ }
+ } else {
+ if (size >= len)
+ match = (memcmp(magic_entry->magic, content, len) == 0);
+ }
+
+ 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,
+ Histogram* counter, std::string* result) {
+ for (size_t i = 0; i < magic_len; ++i) {
+ if (MatchMagicNumber(content, size, &(magic[i]), result)) {
+ counter->Add(static_cast<int>(i));
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool SniffForHTML(const char* content, size_t size,
+ std::string* result) {
+ // 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 SnifferHistogram counter(L"mime_sniffer.kSniffableTags",
+ arraysize(kSniffableTags));
+ // |pos| now points to first non-whitespace character (or at end).
+ return CheckForMagicNumbers(pos, end - pos,
+ kSniffableTags, arraysize(kSniffableTags),
+ &counter, result);
+}
+
+static bool SniffForMagicNumbers(const char* content, size_t size,
+ std::string* result) {
+ // Check our big table of Magic Numbers
+ static SnifferHistogram counter(L"mime_sniffer.kMagicNumbers",
+ arraysize(kMagicNumbers));
+ return CheckForMagicNumbers(content, size,
+ kMagicNumbers, arraysize(kMagicNumbers),
+ &counter, result);
+}
+
+// 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
+};
+
+// Sniff an XML document to judge whether it contains XHTML or a feed.
+// Returns true if it has seen enough content to make a definitive decision.
+// 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, std::string* result) {
+ // We allow at most kFirstTagBytes bytes of content before we expect the
+ // opening tag.
+ const size_t kFeedAllowedHeaderBytes = 300;
+ const char* const end = content + std::min(size, kFeedAllowedHeaderBytes);
+ const char* pos = content;
+
+ // 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 SnifferHistogram counter(L"mime_sniffer.kMagicXML",
+ 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 (_strnicmp(pos, "<?xml", sizeof("<?xml")-1) == 0) {
+ // Skip XML declarations.
+ ++pos;
+ continue;
+ } else if (_strnicmp(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
+ MAGIC_NUMBER("text/plain", "\x00\x00\xFE\xFF") // UCS-4BE
+};
+
+// 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, 0, 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
+};
+
+static bool LooksBinary(const char* content, size_t size) {
+ // First, we look for a BOM.
+ static SnifferHistogram counter(L"mime_sniffer.kByteOrderMark",
+ 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.
+ 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])])
+ return true;
+ }
+
+ // No evidence either way, default to non-binary.
+ return false;
+}
+
+static bool IsUnknownMimeType(const std::string& mime_type) {
+ // TODO(tc): Maybe reuse some code in net/http/http_response_headers.* here.
+ 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 SnifferHistogram counter(L"mime_sniffer.kUnknownMimeTypes",
+ arraysize(kUnknownMimeTypes) + 1);
+ for (int 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;
+}
+
+bool ShouldSniffMimeType(const GURL& url, const std::string& mime_type) {
+ // We are willing to sniff the mime type for HTTP, HTTPS, and FTP
+ bool sniffable_scheme = url.is_empty() ||
+ url.SchemeIs("http") ||
+ url.SchemeIs("https") ||
+ url.SchemeIs("ftp");
+ if (!sniffable_scheme)
+ return false;
+
+ static const char* kSniffableTypes[] = {
+ // Many web servers are misconfigured to send text/plain for many
+ // different types of content.
+ "text/plain",
+ // IIS 4.0 and 5.0 send application/octet-stream when serving .xhtml
+ // files. Firefox 2.0 does not sniff xhtml here, but Safari 3,
+ // Opera 9, and IE do.
+ "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",
+ };
+ static SnifferHistogram counter(L"mime_sniffer.kSniffableTypes",
+ arraysize(kSniffableTypes) + 1);
+ for (int i = 0; i < arraysize(kSniffableTypes); ++i) {
+ if (mime_type == kSniffableTypes[i]) {
+ counter.Add(i);
+ 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));
+ return true;
+ }
+ 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'll return the type hint.
+ result->assign(type_hint);
+
+ // Flag for tracking whether our decision was limited by content_size. We
+ // probably have enough content if we can use all our magic numbers.
+ const bool have_enough_content = content_size >= kBytesRequiredForMagic;
+
+ // We have an upper limit on the number of bytes we will consider.
+ if (content_size > kMaxBytesToSniff)
+ content_size = kMaxBytesToSniff;
+
+ // 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, result))
+ return true; // We succeeded in sniffing HTML. No more content needed.
+ }
+
+ // We'll reuse this information later
+ const bool hint_is_text_plain = (type_hint == "text/plain");
+ const bool looks_binary = LooksBinary(content, content_size);
+
+ if (hint_is_text_plain && !looks_binary) {
+ // The server said the content was text/plain and we don't really have any
+ // evidence otherwise.
+ result->assign("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.
+ return SniffXML(content, content_size, result);
+ }
+
+ // 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, result))
+ return true; // We've matched a magic number. No more content needed.
+
+ // Having failed thus far, we're willing to override unknown mime types and
+ // text/plain.
+ if (hint_is_unknown_mime_type || hint_is_text_plain) {
+ if (looks_binary)
+ result->assign("application/octet-stream");
+ else
+ result->assign("text/plain");
+ // We could change our mind if a binary-looking byte appears later in
+ // the content, so we only have enough content if we have the max.
+ return content_size >= kMaxBytesToSniff;
+ }
+
+ return have_enough_content;
+}
+
+} // namespace mime_util
diff --git a/net/base/mime_sniffer.h b/net/base/mime_sniffer.h
new file mode 100644
index 0000000..352343c
--- /dev/null
+++ b/net/base/mime_sniffer.h
@@ -0,0 +1,62 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_MIME_SNIFFER_H__
+#define NET_BASE_MIME_SNIFFER_H__
+
+#include <string>
+
+class GURL;
+
+namespace mime_util {
+
+// 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.
+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.
+bool SniffMimeType(const char* content, size_t content_size,
+ const GURL& url, const std::string& type_hint,
+ std::string* result);
+
+} // namespace mime_util
+
+#endif // NET_BASE_MIME_SNIFFER_H__
diff --git a/net/base/mime_sniffer_unittest.cc b/net/base/mime_sniffer_unittest.cc
new file mode 100644
index 0000000..0d1011f
--- /dev/null
+++ b/net/base/mime_sniffer_unittest.cc
@@ -0,0 +1,324 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/mime_sniffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+ class MimeSnifferTest : public testing::Test {
+ };
+}
+
+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) {
+ mime_util::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;
+ mime_util::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;
+
+ mime_util::SniffMimeType(buf, 0, url, type_hint, &mime_type);
+ EXPECT_EQ("text/plain", mime_type);
+ mime_util::SniffMimeType(buf, 1, url, type_hint, &mime_type);
+ EXPECT_EQ("text/plain", mime_type);
+ mime_util::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", "image/png" },
+ { "\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, 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\x20\x00", sizeof("\x00\x00\x30\x00")-1,
+ "http://www.example.com/favicon",
+ "", "image/x-icon" },
+ { "\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", "", "text/xml"));
+ // Don't sniff out of plain text.
+ EXPECT_EQ("text/plain",
+ SniffMimeType("<?xml?><feed", "", "text/plain"));
+ // Simple RSS.
+ EXPECT_EQ("application/rss+xml",
+ SniffMimeType("<?xml version='1.0'?>\r\n<rss", "", "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, "", "text/xml"));
+ EXPECT_EQ("text/plain",
+ SniffMimeType(kCNNRSS, "", "text/plain"));
+
+ // Don't sniff random XML as something different.
+ EXPECT_EQ("text/xml",
+ SniffMimeType("<?xml?><notafeed", "", "text/xml"));
+ // Don't sniff random plain-text as something different.
+ EXPECT_EQ("text/plain",
+ SniffMimeType("<?xml?><notafeed", "", "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\">",
+ "", "text/xml"));
+ EXPECT_EQ("application/xhtml+xml",
+ SniffMimeType("<html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ "", "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\">",
+ "", "text/plain"));
+ EXPECT_EQ("application/rss+xml",
+ SniffMimeType("<html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ "", "application/rss+xml"));
+
+ // Don't sniff other HTML-looking bits as HTML.
+ EXPECT_EQ("text/xml",
+ SniffMimeType("<html><head>", "", "text/xml"));
+ EXPECT_EQ("text/xml",
+ SniffMimeType("<foo><html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ "", "text/xml"));
+
+}
diff --git a/net/base/mime_util.cc b/net/base/mime_util.cc
new file mode 100644
index 0000000..71421f9
--- /dev/null
+++ b/net/base/mime_util.cc
@@ -0,0 +1,305 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <hash_set>
+#include <string.h>
+
+#include "net/base/mime_util.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/registry.h"
+#include "base/string_util.h"
+
+using std::string;
+using std::wstring;
+
+namespace mime_util {
+
+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/png", "png" },
+ { "application/xhtml+xml", "xhtml,xht" }
+};
+
+static const MimeInfo secondary_mappings[] = {
+ { "application/octet-stream", "exe,com,bin" },
+ { "application/gzip", "gz" },
+ { "application/pdf", "pdf" },
+ { "application/postscript", "ps,eps,ai" },
+ { "application/x-javascript", "js" },
+ { "image/bmp", "bmp" },
+ { "image/x-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" }
+};
+
+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 && _strnicmp(extensions, ext, ext_len) == 0)
+ return mappings[i].mime_type;
+ extensions += end_pos;
+ if (!*extensions)
+ break;
+ extensions += 1; // skip over comma
+ }
+ }
+ return NULL;
+}
+
+bool GetMimeTypeFromExtension(const wstring& ext, string* result) {
+ // 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.
+
+ string ext_utf8 = WideToUTF8(ext);
+ const char* mime_type;
+
+ mime_type = FindMimeType(primary_mappings, arraysize(primary_mappings),
+ ext_utf8.c_str());
+ if (mime_type) {
+ *result = mime_type;
+ return true;
+ }
+
+ // check windows registry for file extension's mime type (registry key
+ // names are not case-sensitive).
+ wstring value, key = L"." + ext;
+ RegKey(HKEY_CLASSES_ROOT, key.c_str()).ReadValue(L"Content Type", &value);
+ if (!value.empty()) {
+ *result = WideToUTF8(value);
+ return true;
+ }
+
+ mime_type = FindMimeType(secondary_mappings, arraysize(secondary_mappings),
+ ext_utf8.c_str());
+ if (mime_type) {
+ *result = mime_type;
+ return true;
+ }
+
+ return false;
+}
+
+bool GetMimeTypeFromFile(const wstring& file_path, string* result) {
+ wstring::size_type dot = file_path.find_last_of('.');
+ if (dot == wstring::npos)
+ return false;
+ return GetMimeTypeFromExtension(file_path.substr(dot + 1), result);
+}
+
+bool GetPreferredExtensionForMimeType(const std::string& mime_type,
+ std::wstring* ext) {
+ wstring key(L"MIME\\Database\\Content Type\\" + UTF8ToWide(mime_type));
+ return RegKey(HKEY_CLASSES_ROOT, key.c_str()).ReadValue(L"Extension", ext);
+}
+
+
+// From WebKit's WebCore/platform/MIMETypeRegistry.cpp:
+
+static const char* supported_image_types[] = {
+ "image/jpeg",
+ "image/jpg",
+ "image/png",
+ "image/gif",
+ "image/bmp",
+ "image/x-icon", // ico
+ "image/x-xbitmap" // xbm
+};
+
+// Note: does not include javascript types list (see supported_javascript_types)
+static const char* supported_non_image_types[] = {
+ "text/html",
+ "text/xml",
+ "text/xsl",
+ "text/plain",
+ "text/",
+ "image/svg+xml", // SVG is text-based XML, even though it has an image/ type
+ "application/xml",
+ "application/xhtml+xml",
+ "application/rss+xml",
+ "application/atom+xml",
+ "multipart/x-mixed-replace"
+};
+
+// 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* 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"
+};
+
+static const char* view_source_types[] = {
+ "text/xml",
+ "text/xsl",
+ "application/xml",
+ "application/rss+xml",
+ "application/atom+xml",
+ "image/svg+xml"
+};
+
+// For faster lookup
+static stdext::hash_set<string>* image_map = NULL;
+static stdext::hash_set<string>* non_image_map = NULL;
+static stdext::hash_set<string>* javascript_map = NULL;
+static stdext::hash_set<string>* view_source_map = NULL;
+
+static void InitializeMimeTypeMaps() {
+ image_map = new stdext::hash_set<string>;
+ non_image_map = new stdext::hash_set<string>;
+ javascript_map = new stdext::hash_set<string>;
+ view_source_map = new stdext::hash_set<string>;
+
+ for (int i = 0; i < arraysize(supported_image_types); ++i)
+ image_map->insert(supported_image_types[i]);
+
+ // Initialize the supported non-image types
+ for (int i = 0; i < arraysize(supported_non_image_types); ++i)
+ non_image_map->insert(supported_non_image_types[i]);
+ for (int i = 0; i < arraysize(supported_javascript_types); ++i)
+ non_image_map->insert(supported_javascript_types[i]);
+
+ for (int i = 0; i < arraysize(supported_javascript_types); ++i)
+ javascript_map->insert(supported_javascript_types[i]);
+
+ for (int i = 0; i < arraysize(view_source_types); ++i)
+ view_source_map->insert(view_source_types[i]);
+}
+
+bool IsSupportedImageMimeType(const char* mime_type) {
+ if (!image_map)
+ InitializeMimeTypeMaps();
+ return image_map->find(mime_type) != image_map->end();
+}
+
+bool IsSupportedNonImageMimeType(const char* mime_type) {
+ if (!non_image_map)
+ InitializeMimeTypeMaps();
+ return non_image_map->find(mime_type) != non_image_map->end();
+}
+
+bool IsSupportedJavascriptMimeType(const char* mime_type) {
+ if (!javascript_map)
+ InitializeMimeTypeMaps();
+ return javascript_map->find(mime_type) != javascript_map->end();
+}
+
+bool IsViewSourceMimeType(const char* mime_type) {
+ if (!view_source_map)
+ InitializeMimeTypeMaps();
+ return view_source_map->find(mime_type) != view_source_map->end();
+}
+
+// Mirrors WebViewImpl::CanShowMIMEType()
+bool IsSupportedMimeType(const std::string& mime_type) {
+ if (mime_type.compare(0, 5, "text/") == 0 ||
+ (mime_type.compare(0, 6, "image/") == 0 &&
+ mime_util::IsSupportedImageMimeType(mime_type.c_str())) ||
+ mime_util::IsSupportedNonImageMimeType(mime_type.c_str()))
+ return true;
+ return false;
+}
+
+bool MatchesMimeType(const std::string &mime_type_pattern,
+ const std::string &mime_type) {
+ // verify caller is passing lowercase
+ DCHECK(mime_type_pattern == StringToLowerASCII(mime_type_pattern));
+ DCHECK(mime_type == StringToLowerASCII(mime_type));
+
+ // This comparison handles absolute maching and also basic
+ // wildcards. The plugin mime types could be:
+ // application/x-foo
+ // application/*
+ // application/*+xml
+ // *
+ if (mime_type_pattern.empty())
+ return false;
+
+ const std::string::size_type star = mime_type_pattern.find('*');
+
+ if (star == std::string::npos)
+ return mime_type_pattern == mime_type;
+
+ // Test length to prevent overlap between |left| and |right|.
+ if (mime_type.length() < mime_type_pattern.length() - 1)
+ return false;
+
+ const std::string left(mime_type_pattern.substr(0, star));
+ const std::string right(mime_type_pattern.substr(star + 1));
+
+ if (mime_type.find(left) != 0)
+ return false;
+
+ if (!right.empty() &&
+ mime_type.rfind(right) != mime_type.length() - right.length())
+ return false;
+
+ return true;
+}
+
+} // namespace mime_util
diff --git a/net/base/mime_util.h b/net/base/mime_util.h
new file mode 100644
index 0000000..047e79c
--- /dev/null
+++ b/net/base/mime_util.h
@@ -0,0 +1,71 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_MIME_UTIL_H__
+#define NET_BASE_MIME_UTIL_H__
+
+#include <string>
+
+namespace mime_util {
+
+// Get the mime type (if any) that is associated with the given file extension.
+// Returns true if a corresponding mime type exists.
+bool GetMimeTypeFromExtension(const std::wstring& 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.
+bool GetMimeTypeFromFile(const std::wstring& 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 with a prefixed dot (as stored in the registry), ex ".avi".
+bool GetPreferredExtensionForMimeType(const std::string& mime_type,
+ std::wstring* extension);
+
+// Check to see if a particular MIME type is in our list.
+bool IsSupportedImageMimeType(const char* mime_type);
+bool IsSupportedNonImageMimeType(const char* mime_type);
+bool IsSupportedJavascriptMimeType(const char* mime_type);
+
+// Get whether this mime type should be displayed in view-source mode.
+// (For example, XML.)
+bool IsViewSourceMimeType(const char* mime_type);
+
+// Convenience function.
+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.
+bool MatchesMimeType(const std::string &mime_type_pattern,
+ const std::string &mime_type);
+
+} // namespace mime_util
+
+#endif // NET_BASE_MIME_UTIL_H__
diff --git a/net/base/mime_util_unittest.cc b/net/base/mime_util_unittest.cc
new file mode 100644
index 0000000..3031d1b
--- /dev/null
+++ b/net/base/mime_util_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "net/base/mime_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+ class MimeUtilTest : public testing::Test {
+ };
+}
+
+TEST(MimeUtilTest, ExtensionTest) {
+ const struct {
+ const wchar_t* extension;
+ const char* mime_type;
+ bool valid;
+ } tests[] = {
+ { L"png", "image/png", true },
+ { L"css", "text/css", true },
+ { L"pjp", "image/jpeg", true },
+ { L"pjpeg", "image/jpeg", true },
+ { L"not an extension / for sure", "", false },
+ };
+
+ std::string mime_type;
+ bool rv;
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ rv = mime_util::GetMimeTypeFromExtension(tests[i].extension, &mime_type);
+ EXPECT_EQ(rv, tests[i].valid);
+ if (rv)
+ EXPECT_EQ(mime_type, tests[i].mime_type);
+ }
+}
+
+TEST(MimeUtilTest, FileTest) {
+ const struct {
+ const wchar_t* file_path;
+ const char* mime_type;
+ bool valid;
+ } tests[] = {
+ { L"c:\\foo\\bar.css", "text/css", true },
+ { L"c:\\blah", "", false },
+ { L"c:\\blah.", "", false },
+ };
+
+ std::string mime_type;
+ bool rv;
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ rv = mime_util::GetMimeTypeFromFile(tests[i].file_path, &mime_type);
+ EXPECT_EQ(rv, tests[i].valid);
+ if (rv)
+ EXPECT_EQ(mime_type, tests[i].mime_type);
+ }
+}
+
+TEST(MimeUtilTest, LookupTypes) {
+ EXPECT_EQ(true, mime_util::IsSupportedImageMimeType("image/jpeg"));
+ EXPECT_EQ(false, mime_util::IsSupportedImageMimeType("image/lolcat"));
+ EXPECT_EQ(true, mime_util::IsSupportedNonImageMimeType("text/html"));
+ EXPECT_EQ(false, mime_util::IsSupportedNonImageMimeType("text/virus"));
+}
+
+TEST(MimeUtilTest, MatchesMimeType) {
+ EXPECT_EQ(true, mime_util::MatchesMimeType("*", "video/x-mpeg"));
+ EXPECT_EQ(true, mime_util::MatchesMimeType("video/*", "video/x-mpeg"));
+ EXPECT_EQ(true, mime_util::MatchesMimeType("video/x-mpeg", "video/x-mpeg"));
+ EXPECT_EQ(true, mime_util::MatchesMimeType("application/*+xml",
+ "application/html+xml"));
+ EXPECT_EQ(true, mime_util::MatchesMimeType("application/*+xml",
+ "application/+xml"));
+ EXPECT_EQ(true, mime_util::MatchesMimeType("aaa*aaa",
+ "aaaaaa"));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("video/", "video/x-mpeg"));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("", "video/x-mpeg"));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("", ""));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("video/x-mpeg", ""));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("application/*+xml",
+ "application/xml"));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("application/*+xml",
+ "application/html+xmlz"));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("application/*+xml",
+ "applcation/html+xml"));
+ EXPECT_EQ(false, mime_util::MatchesMimeType("aaa*aaa",
+ "aaaaa"));
+}
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
new file mode 100644
index 0000000..1b12af4
--- /dev/null
+++ b/net/base/net_error_list.h
@@ -0,0 +1,214 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file contains the list of network 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 occured.
+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)
+
+// 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)
+
+// 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."
+//
+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)
+
+// 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, -208)
+
+// 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)
+
+// The cache does not have the requested entry.
+NET_ERROR(CACHE_MISS, -400)
+
+// The server's response was insecure (e.g. there was a cert error).
+NET_ERROR(INSECURE_RESPONSE, -501)
diff --git a/net/base/net_errors.cc b/net/base/net_errors.cc
new file mode 100644
index 0000000..02a2797
--- /dev/null
+++ b/net/base/net_errors.cc
@@ -0,0 +1,55 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/net_errors.h"
+
+#include "base/basictypes.h"
+
+#define STRINGIZE(x) #x
+
+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(ERR_ ## label);
+#include "net/base/net_error_list.h"
+#undef NET_ERROR
+ default:
+ return "net::<unknown>";
+ }
+}
+
+} // namespace net
diff --git a/net/base/net_errors.h b/net/base/net_errors.h
new file mode 100644
index 0000000..71c19614
--- /dev/null
+++ b/net/base/net_errors.h
@@ -0,0 +1,65 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_NET_ERRORS_H__
+#define NET_BASE_NET_ERRORS_H__
+
+#include "base/basictypes.h"
+
+namespace net {
+
+// Error domain of the net module's error codes.
+extern const char kErrorDomain[];
+
+// Error values are negative.
+enum {
+ // 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.
+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;
+}
+
+} // namespace net
+
+#endif // NET_BASE_NET_ERRORS_H__
diff --git a/net/base/net_module.cc b/net/base/net_module.cc
new file mode 100644
index 0000000..7fa21f2
--- /dev/null
+++ b/net/base/net_module.cc
@@ -0,0 +1,44 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/net_module.h"
+
+static NetModule::ResourceProvider resource_provider;
+
+// static
+void NetModule::SetResourceProvider(ResourceProvider func) {
+ resource_provider = func;
+}
+
+// static
+std::string NetModule::GetResource(int key) {
+ // avoid thread safety issues by copying provider address to a local var
+ ResourceProvider func = resource_provider;
+ return func ? func(key) : std::string();
+}
diff --git a/net/base/net_module.h b/net/base/net_module.h
new file mode 100644
index 0000000..37698cc
--- /dev/null
+++ b/net/base/net_module.h
@@ -0,0 +1,60 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_NET_MODULE_H__
+#define NET_BASE_NET_MODULE_H__
+
+#include <string>
+
+#include "base/basictypes.h"
+
+// 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 NetModule {
+ public:
+ typedef std::string (*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 std::string GetResource(int key);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(NetModule);
+};
+
+#endif // NET_BASE_NET_MODULE_H__
diff --git a/net/base/net_resources.h b/net/base/net_resources.h
new file mode 100644
index 0000000..d0b9f8b
--- /dev/null
+++ b/net/base/net_resources.h
@@ -0,0 +1,4 @@
+// TODO(tc): Come up with a way to automate the generation of these
+// IDs so they don't collide with other rc files.
+#define IDR_DIR_HEADER_HTML 400
+#define IDR_EFFECTIVE_TLD_NAMES 401
diff --git a/net/base/net_resources.rc b/net/base/net_resources.rc
new file mode 100644
index 0000000..a85f4d3
--- /dev/null
+++ b/net/base/net_resources.rc
@@ -0,0 +1,20 @@
+// Resources used by the net module. This rc file is meant to be included by
+// the application rc file (e.g., app/chrome_dll.rc).
+//
+// Paths in this file are relative to SolutionDir (//trunk/chrome/).
+
+#ifdef APSTUDIO_INVOKED
+ #error // Don't open in the Visual Studio resource editor!
+#endif //APSTUDIO_INVOKED
+
+#include "net\\base\\net_resources.h"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// data resources
+//
+
+IDR_DIR_HEADER_HTML BINDATA "net\\base\\dir_header.html"
+
+// The converted file is generated, so we need to use a path relative to "obj".
+IDR_EFFECTIVE_TLD_NAMES BINDATA "net\\effective_tld_names_clean.dat"
diff --git a/net/base/net_util.cc b/net/base/net_util.cc
new file mode 100644
index 0000000..416252c
--- /dev/null
+++ b/net/base/net_util.cc
@@ -0,0 +1,993 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+#include <unicode/ucnv.h>
+#include <unicode/uidna.h>
+#include <unicode/ulocdata.h>
+#include <unicode/uniset.h>
+#include <unicode/uscript.h>
+#include <unicode/uset.h>
+#include <windows.h>
+#include <wininet.h>
+
+#include "net/base/net_util.h"
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/scoped_ptr.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/string_escape.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_canon.h"
+#include "googleurl/src/url_parse.h"
+#include "net/base/escape.h"
+#include "net/base/net_module.h"
+#include "net/base/net_resources.h"
+#include "net/base/base64.h"
+#include "unicode/datefmt.h"
+
+namespace {
+
+// what we prepend to get a file URL
+static const wchar_t kFileURLPrefix[] = L"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
+ 4045, // lockd
+ 6000, // X11
+};
+
+// FTP overrides the following restricted ports.
+static const int kAllowedFtpPorts[] = {
+ 21, // ftp data
+ 22, // ssh
+};
+
+template<typename STR>
+STR GetSpecificHeaderT(const STR& headers, const STR& 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 STR();
+
+ STR match;
+ match.push_back('\n');
+ match.append(name);
+ match.push_back(':');
+
+ STR::const_iterator begin =
+ search(headers.begin(), headers.end(), match.begin(), match.end(),
+ CaseInsensitiveCompareASCII<STR::value_type>());
+
+ if (begin == headers.end())
+ return STR();
+
+ begin += match.length();
+
+ STR::const_iterator end = find(begin, headers.end(), '\n');
+
+ STR ret;
+ TrimWhitespace(STR(begin, end), TRIM_ALL, &ret);
+ return ret;
+}
+
+// TODO(jungshik): We have almost identical hex-decoding code else where.
+// Consider refactoring and moving it somewhere(base?). Bug 1224311
+inline bool IsHexDigit(unsigned char c) {
+ return ('0' <= c && c <= '9' || 'A' <= c && c <= 'F' || 'a' <= c && c <= 'f');
+}
+
+inline unsigned char HexToInt(unsigned char c) {
+ DCHECK(IsHexDigit(c));
+ static unsigned char kOffset[4] = {0, 0x30u, 0x37u, 0x57u};
+ return c - kOffset[c / 0x20];
+}
+
+// Similar to Base64Decode. Decodes a Q-encoded string to a sequence
+// of bytes. If input is invalid, return false.
+bool QPDecode(const std::string& input, std::string* output) {
+ std::string temp;
+ temp.reserve(input.size());
+ std::string::const_iterator it = input.begin();
+ while (it != input.end()) {
+ if (*it == '_') {
+ temp.push_back(' ');
+ } else if (*it == '=') {
+ if (input.end() - it < 3) {
+ return false;
+ }
+ if (IsHexDigit(static_cast<unsigned char>(*(it + 1))) &&
+ IsHexDigit(static_cast<unsigned char>(*(it + 2)))) {
+ unsigned char ch = HexToInt(*(it + 1)) * 16 + HexToInt(*(it + 2));
+ temp.push_back(static_cast<char>(ch));
+ ++it;
+ ++it;
+ } else {
+ return false;
+ }
+ } else if (0x20 < *it && *it < 0x7F) {
+ // In a Q-encoded word, only printable ASCII characters
+ // represent themselves. Besides, space, '=', '_' and '?' are
+ // not allowed, but they're already filtered out.
+ DCHECK(*it != 0x3D && *it != 0x5F && *it != 0x3F);
+ temp.push_back(*it);
+ } else {
+ return false;
+ }
+ ++it;
+ }
+ output->swap(temp);
+ return true;
+}
+
+enum RFC2047EncodingType {Q_ENCODING, B_ENCODING};
+bool DecodeBQEncoding(const std::string& part, RFC2047EncodingType enc_type,
+ const std::string& charset, std::string* output) {
+ std::string decoded;
+ if (enc_type == B_ENCODING) {
+ if (!Base64Decode(part, &decoded)) {
+ return false;
+ }
+ } else {
+ if (!QPDecode(part, &decoded)) {
+ return false;
+ }
+ }
+
+ 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.
+ int length = static_cast<int>(decoded.length());
+ char* buf = WriteInto(output, length * 3);
+ length = ucnv_toAlgorithmic(UCNV_UTF8, converter, buf, length * 3,
+ decoded.data(), length, &err);
+ ucnv_close(converter);
+ if (U_FAILURE(err)) {
+ return false;
+ }
+ output->resize(length);
+ return true;
+}
+
+bool DecodeWord(const std::string& encoded_word,
+ bool *is_rfc2047,
+ std::string* output) {
+ // TODO(jungshik) : Revisit this later. Do we want to pass through non-ASCII
+ // strings which can be mozibake? WinHTTP converts a raw 8bit string
+ // UTF-16 assuming it's in the OS default encoding.
+ if (!IsStringASCII(encoded_word)) {
+ // Try falling back to the NativeMB encoding if the raw input is not UTF-8.
+ if (IsStringUTF8(encoded_word.c_str())) {
+ *output = encoded_word;
+ } else {
+ *output = WideToUTF8(NativeMBToWide(encoded_word));
+ }
+ *is_rfc2047 = false;
+ 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 tmp;
+ *is_rfc2047 = true;
+ int part_index = 0;
+ std::string charset;
+ 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, &tmp);
+ 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(tmp);
+ 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. We could extend this to
+ // support a rudimentary form of RFC 2231 with charset label, but
+ // it'd gain us little in terms of compatibility.
+ tmp = UnescapeURLComponent(encoded_word, UnescapeRule::SPACES);
+ if (IsStringUTF8(tmp.c_str())) {
+ output->swap(tmp);
+ 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;
+}
+
+bool DecodeParamValue(const std::string& input, std::string* output) {
+ std::string tmp;
+ // Tokenize with whitespace characters.
+ StringTokenizer t(input, " \t\n\r");
+ t.set_options(StringTokenizer::RETURN_DELIMS);
+ bool is_previous_token_rfc2047 = true;
+ 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) {
+ tmp.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(), &is_previous_token_rfc2047, &decoded))
+ return false;
+ tmp.append(decoded);
+ }
+ output->swap(tmp);
+ return true;
+}
+
+// TODO(mpcomplete): This is a quick and dirty implementation for now. I'm
+// sure this doesn't properly handle all (most?) cases.
+template<typename STR>
+STR GetHeaderParamValueT(const STR& header, const STR& param_name) {
+ // This assumes args are formatted exactly like "bla; arg1=value; arg2=value".
+ STR::const_iterator param_begin =
+ search(header.begin(), header.end(), param_name.begin(), param_name.end(),
+ CaseInsensitiveCompareASCII<STR::value_type>());
+
+ if (param_begin == header.end())
+ return STR();
+ param_begin += param_name.length();
+
+ STR whitespace;
+ whitespace.push_back(' ');
+ whitespace.push_back('\t');
+ const STR::size_type equals_offset =
+ header.find_first_not_of(whitespace, param_begin - header.begin());
+ if (equals_offset == STR::npos || header.at(equals_offset) != '=')
+ return STR();
+
+ param_begin = header.begin() + equals_offset + 1;
+ if (param_begin == header.end())
+ return STR();
+
+ STR::const_iterator param_end;
+ if (*param_begin == '"') {
+ param_end = find(param_begin+1, header.end(), '"');
+ if (param_end == header.end())
+ return STR(); // poorly formatted param?
+
+ ++param_begin; // skip past the quote.
+ } else {
+ param_end = find(param_begin+1, header.end(), ';');
+ }
+
+ return STR(param_begin, param_end);
+}
+
+// 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 wchar_t* str, int str_len) {
+ UScriptCode first_script;
+ 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");
+}
+
+// Returns true if the given Unicode host component is safe to display to the
+// user.
+bool IsIDNComponentSafe(const wchar_t* str,
+ int str_len,
+ const std::wstring& 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
+ UnicodeSet dangerous_characters(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);
+#else
+ UnicodeSet dangerous_characters(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);
+#endif
+ DCHECK(U_SUCCESS(status));
+ UnicodeSet component_characters;
+ component_characters.addAll(UnicodeString(str, str_len));
+ if (dangerous_characters.containsSome(component_characters))
+ 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.
+ 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);
+
+ USet *lang_set = uset_open(1, 0); // create an empty set
+ UnicodeSet ascii_letters(0x61, 0x7a); // [a-z]
+ bool safe = false;
+ std::string languages_list(WideToASCII(languages));
+ StringTokenizer t(languages_list, ",");
+ while (t.GetNext()) {
+ std::string lang = t.token();
+ status = U_ZERO_ERROR;
+ // TODO(jungshik) Cache exemplar sets for locales.
+ ULocaleData* uld = ulocdata_open(lang.c_str(), &status);
+ if (U_SUCCESS(status)) {
+ // Should we use auxiliary set, instead?
+ ulocdata_getExemplarSet(uld, lang_set, 0, ULOCDATA_ES_STANDARD, &status);
+ ulocdata_close(uld);
+ if (U_SUCCESS(status)) {
+ UnicodeSet* allowed_characters =
+ reinterpret_cast<UnicodeSet*>(lang_set);
+ // If |lang| is compatible with ASCII Latin letters, add them.
+ if (IsCompatibleWithASCIILetters(lang))
+ allowed_characters->addAll(ascii_letters);
+ if (allowed_characters->containsAll(component_characters)) {
+ safe = true;
+ break;
+ }
+ }
+ }
+ }
+ uset_close(lang_set);
+ return safe;
+}
+
+// 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.
+void IDNToUnicodeOneComponent(const wchar_t* comp,
+ int comp_len,
+ const std::wstring& languages,
+ std::wstring* out) {
+ DCHECK(comp_len >= 0);
+ if (comp_len == 0)
+ return;
+
+ // Expand the output string to make room for a possibly longer string
+ // (we'll expand if it's still not big enough below).
+ int extra_space = 64;
+ size_t host_begin_in_output = out->size();
+
+ // Just copy the input if it can't be an IDN component.
+ if (comp_len < 4 || wcsncmp(comp, L"xn--", 4)) {
+ out->resize(host_begin_in_output + comp_len);
+ for (int i = 0; i < comp_len; i++)
+ (*out)[host_begin_in_output + i] = comp[i];
+ return;
+ }
+
+ while (true) {
+ out->resize(out->size() + extra_space);
+ UErrorCode status = U_ZERO_ERROR;
+ int output_chars = uidna_IDNToUnicode(
+ comp, comp_len, &(*out)[host_begin_in_output], extra_space,
+ UIDNA_DEFAULT, NULL, &status);
+ if (status == U_ZERO_ERROR) {
+ // Converted successfully.
+ out->resize(host_begin_in_output + output_chars);
+ if (!IsIDNComponentSafe(&out->data()[host_begin_in_output],
+ output_chars,
+ languages))
+ break; // The error handling below will undo the IDN.
+ return;
+ }
+ if (status != U_BUFFER_OVERFLOW_ERROR)
+ break;
+
+ // Need to loop again with a bigger buffer. 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.
+ extra_space *= 2;
+ }
+
+ // We get here on error, in which case we replace anything that was added
+ // with the literal input.
+ out->resize(host_begin_in_output + comp_len);
+ for (int i = 0; i < comp_len; i++)
+ (*out)[host_begin_in_output + i] = comp[i];
+}
+
+// Convert a FILETIME to a localized string. |filetime| may be NULL.
+// TODO(tc): Remove this once bug 1164516 is fixed.
+std::wstring LocalizedDateTime(const FILETIME* filetime) {
+ if (!filetime)
+ return std::wstring();
+
+ Time time = Time::FromFileTime(*filetime);
+ scoped_ptr<DateFormat> formatter(DateFormat::createDateTimeInstance(
+ DateFormat::kShort));
+ UnicodeString date_string;
+ formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string);
+
+ std::wstring formatted;
+ int capacity = date_string.length() + 1;
+ UErrorCode error = U_ZERO_ERROR;
+ date_string.extract(static_cast<UChar*>(WriteInto(&formatted, capacity)),
+ capacity, error);
+ return formatted;
+}
+
+} // namespace
+
+namespace net_util {
+
+GURL FilePathToFileURL(const std::wstring& file_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"
+ std::wstring url_str(kFileURLPrefix);
+ url_str.append(file_path);
+
+ // 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_str, 0, L"%", L"%25");
+
+ // semicolon is supposed to be some kind of separator according to RFC 2396
+ ReplaceSubstringsAfterOffset(&url_str, 0, L";", L"%3B");
+
+ ReplaceSubstringsAfterOffset(&url_str, 0, L"#", L"%23");
+
+ return GURL(url_str);
+}
+
+bool FileURLToFilePath(const GURL& url, std::wstring* file_path) {
+ file_path->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::PERCENTS);
+
+ if (!IsStringUTF8(path.c_str())) {
+ // 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 = NativeMBToWide(path);
+ return !file_path->empty();
+ }
+ file_path->assign(UTF8ToWide(path));
+
+ // Now we have an unescaped filename, but are still not sure about its
+ // encoding. For example, each character could be part of a UTF-8 string.
+ if (file_path->empty() || !IsString8Bit(*file_path)) {
+ // assume our 16-bit encoding is correct if it won't fit into an 8-bit
+ // string
+ return true;
+ }
+
+ // Convert our narrow string into the native wide path.
+ std::string narrow;
+ if (!WideToLatin1(*file_path, &narrow)) {
+ NOTREACHED() << "Should have filtered out non-8-bit strings above.";
+ return false;
+ }
+ if (IsStringUTF8(narrow.c_str())) {
+ // Our string actually looks like it could be UTF-8, convert to 8-bit
+ // UTF-8 and then to the corresponding wide string.
+ *file_path = UTF8ToWide(narrow);
+ } else {
+ // Our wide string contains only 8-bit characters and it's not UTF-8, so
+ // we assume it's in the native codepage.
+ *file_path = NativeMBToWide(narrow);
+ }
+
+ // Fail if 8-bit -> wide conversion failed and gave us an empty string back
+ // (we already filtered out empty strings above).
+ return !file_path->empty();
+}
+
+std::wstring GetSpecificHeader(const std::wstring& headers,
+ const std::wstring& name) {
+ return GetSpecificHeaderT(headers, name);
+}
+
+std::string GetSpecificHeader(const std::string& headers,
+ const std::string& name) {
+ return GetSpecificHeaderT(headers, name);
+}
+
+std::wstring GetFileNameFromCD(const std::string& header) {
+ std::string param_value = GetHeaderParamValue(header, "filename");
+ if (param_value.empty()) {
+ // Some servers use 'name' parameter.
+ param_value = GetHeaderParamValue(header, "name");
+ }
+ if (param_value.empty())
+ return std::wstring();
+ std::string decoded;
+ if (DecodeParamValue(param_value, &decoded))
+ return UTF8ToWide(decoded);
+ return std::wstring();
+}
+
+std::wstring GetHeaderParamValue(const std::wstring& field,
+ const std::wstring& param_name) {
+ return GetHeaderParamValueT(field, param_name);
+}
+
+std::string GetHeaderParamValue(const std::string& field,
+ const std::string& param_name) {
+ return GetHeaderParamValueT(field, param_name);
+}
+
+// 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.
+void IDNToUnicode(const char* host,
+ int host_len,
+ const std::wstring& languages,
+ std::wstring* out) {
+ // Convert the ASCII input to a wide string for ICU.
+ std::wstring wide_input;
+ wide_input.reserve(host_len);
+ for (int i = 0; i < host_len; i++)
+ wide_input.push_back(host[i]);
+
+ // Do each component of the host separately, since we enforce script matching
+ // on a per-component basis.
+ size_t cur_begin = 0; // Beginning of the current component (inclusive).
+ while (cur_begin < wide_input.size()) {
+ // Find the next dot or the end of the string.
+ size_t next_dot = wide_input.find_first_of('.', cur_begin);
+ if (next_dot == std::wstring::npos)
+ next_dot = wide_input.size(); // For getting the last component.
+
+ if (next_dot > cur_begin) {
+ // Add the substring that we just found.
+ IDNToUnicodeOneComponent(&wide_input[cur_begin],
+ static_cast<int>(next_dot - cur_begin),
+ languages,
+ out);
+ }
+
+ // Need to add the dot we just found (if we found one). This needs to be
+ // done before we break out below in case the URL ends in a dot.
+ if (next_dot < wide_input.size())
+ out->push_back('.');
+ else
+ break; // No more components left.
+
+ cur_begin = next_dot + 1;
+ }
+}
+
+template <typename str>
+std::string CanonicalizeHost(const str& host, bool* is_ip_address) {
+ // 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_parse::Component canon_host_component;
+ if (!url_canon::CanonicalizeHost(host.c_str(), raw_host_component,
+ &canon_host_output, &canon_host_component)) {
+ if (is_ip_address)
+ *is_ip_address = false;
+ return std::string();
+ }
+ canon_host_output.Complete();
+
+ if (is_ip_address) {
+ // See if the host is an IP address.
+ url_canon::RawCanonOutputT<char, 128> ignored_output;
+ url_parse::Component ignored_component;
+ *is_ip_address = url_canon::CanonicalizeIPAddress(canon_host.c_str(),
+ canon_host_component,
+ &ignored_output,
+ &ignored_component);
+ }
+
+ // Return the host as a string, stripping any unnecessary bits off the ends.
+ if ((canon_host_component.begin == 0) &&
+ (canon_host_component.len == canon_host.length()))
+ return canon_host;
+ return canon_host.substr(canon_host_component.begin,
+ canon_host_component.len);
+}
+
+// Forcibly instantiate narrow and wide versions of this function so we don't
+// need to put the function definition in the header.
+template std::string CanonicalizeHost<std::string>(const std::string& host,
+ bool* is_ip_address);
+template std::string CanonicalizeHost<std::wstring>(const std::wstring& host,
+ bool* is_ip_address);
+
+std::string GetDirectoryListingHeader(const std::string& title) {
+ std::string result = NetModule::GetResource(IDR_DIR_HEADER_HTML);
+ if (result.empty()) {
+ NOTREACHED() << "expected resource not found";
+ }
+
+ result.append("<script>start(");
+ string_escape::JavascriptDoubleQuote(title, true, &result);
+ result.append(");</script>\n");
+
+ return result;
+}
+
+std::string GetDirectoryListingEntry(const std::string& name,
+ DWORD attrib,
+ int64 size,
+ const FILETIME* modified) {
+ std::string result;
+ result.append("<script>addRow(");
+ string_escape::JavascriptDoubleQuote(name, true, &result);
+ result.append(",");
+ string_escape::JavascriptDoubleQuote(
+ EscapePath(name), true, &result);
+ if (attrib & FILE_ATTRIBUTE_DIRECTORY) {
+ result.append(",1,");
+ } else {
+ result.append(",0,");
+ }
+
+ string_escape::JavascriptDoubleQuote(
+ FormatBytes(size, GetByteDisplayUnits(size), true), true, &result);
+
+ result.append(",");
+
+ string_escape::JavascriptDoubleQuote(
+ LocalizedDateTime(modified), true, &result);
+
+ result.append(");</script>\n");
+
+ return result;
+}
+
+std::wstring StripWWW(const std::wstring& text) {
+ const std::wstring www(L"www.");
+ return (text.compare(0, www.length(), www) == 0) ?
+ text.substr(www.length()) : text;
+}
+
+std::wstring GetSuggestedFilename(const GURL& url,
+ const std::string& content_disposition,
+ const std::wstring& default_name) {
+ std::wstring filename = GetFileNameFromCD(content_disposition);
+ if (!filename.empty()) {
+ // Remove any path information the server may have sent, take the name
+ // only.
+ filename = file_util::GetFilenameFromPath(filename);
+ // Next, remove "." from the beginning and end of the file name to avoid
+ // tricks with hidden files, "..", and "."
+ TrimString(filename, L".", &filename);
+ }
+ if (filename.empty()) {
+ if (url.is_valid())
+ filename = UnescapeAndDecodeUTF8URLComponent(
+ url.ExtractFileName(), UnescapeRule::SPACES | UnescapeRule::PERCENTS);
+ }
+
+ // Trim '.' once more.
+ TrimString(filename, L".", &filename);
+ // If there's no filename or it gets trimed to be empty, use
+ // the URL hostname or default_name
+ if (filename.empty()) {
+ if (!default_name.empty())
+ filename = default_name;
+ else if (url.is_valid()) {
+ // Some schemes (e.g. file) do not have a hostname. Even though it's
+ // not likely to reach here, let's hardcode the last fallback name.
+ // TODO(jungshik) : Decode a 'punycoded' IDN hostname. (bug 1264451)
+ filename = url.host().empty() ? L"download" : UTF8ToWide(url.host());
+ } else
+ NOTREACHED();
+ }
+
+ file_util::ReplaceIllegalCharacters(&filename, '-');
+ return filename;
+}
+
+std::wstring GetSuggestedFilename(const GURL& url,
+ const std::wstring& content_disposition,
+ const std::wstring& default_name) {
+ return GetSuggestedFilename(
+ url, WideToUTF8(content_disposition), default_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);
+}
+
+} // namespace net_util
diff --git a/net/base/net_util.h b/net/base/net_util.h
new file mode 100644
index 0000000..d0955a1
--- /dev/null
+++ b/net/base/net_util.h
@@ -0,0 +1,153 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_NET_UTIL_H__
+#define NET_BASE_NET_UTIL_H__
+
+#include <string>
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "googleurl/src/url_canon.h"
+#include "googleurl/src/url_parse.h"
+
+class GURL;
+
+namespace net_util {
+
+// Given the full path to a file name, creates a file: URL. The returned URL
+// may not be valid if the input is malformed.
+GURL FilePathToFileURL(const std::wstring& file_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.
+bool FileURLToFilePath(const GURL& url, std::wstring* file_path);
+
+// 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.
+std::wstring GetSpecificHeader(const std::wstring& headers,
+ const std::wstring& name);
+std::string GetSpecificHeader(const std::string& headers,
+ const std::string& name);
+
+// Return the value of the HTTP response header field's parameter named
+// 'param_name'. Returns the empty string if the parameter is not found or is
+// improperly formatted.
+std::wstring GetHeaderParamValue(const std::wstring& field,
+ const std::wstring& param_name);
+std::string GetHeaderParamValue(const std::string& field,
+ const std::string& param_name);
+
+// Return the filename extracted from Content-Disposition header. Only two
+// formats are supported: a. %-escaped UTF-8 b. RFC 2047.
+//
+// A non-ASCII param value is just returned as it is (assuming a NativeMB
+// encoding). When a param value is ASCII, but is not in one of two forms
+// supported, it is returned as it is unless it's pretty close to two supported
+// formats but not well-formed. In that case, an empty string is returned.
+//
+// In any case, a caller must check for the empty return value and resort to
+// another means to get a filename (e.g. url).
+//
+// This function does not do any escaping and callers are responsible for
+// escaping 'unsafe' characters (e.g. (back)slash, colon) as they see fit.
+//
+// TODO(jungshik): revisit this issue. At the moment, the only caller
+// net_util::GetSuggestedFilename and it calls ReplaceIllegalCharacters. The
+// other caller is a unit test. Need to figure out expose this function only to
+// net_util_unittest.
+//
+std::wstring GetFileNameFromCD(const std::string& header);
+
+// Converts the given host name to unicode characters, APPENDING them to the
+// the given output string. This can be called for any host name, if the
+// input is not IDN or is invalid in some way, we'll just append the ASCII
+// source to the output so it is still usable.
+//
+// The input should be the canonicalized ASCII host name from GURL. This
+// function does NOT accept UTF-8! Its length must also be given (this is
+// designed to work on the substring of the host out of a URL spec).
+//
+// |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.
+void IDNToUnicode(const char* host,
+ int host_len,
+ const std::wstring& languages,
+ std::wstring* out);
+
+// Canonicalizes |host| and returns it. If |is_ip_address| is non-NULL, sets it
+// to true if |host| is an IP address.
+template <typename str>
+std::string CanonicalizeHost(const str& host, bool* is_ip_address);
+
+// Call these functions to get the html for a directory listing.
+// They will pass non-7bit-ascii characters unescaped, allowing
+// the browser to interpret the encoding (utf8, etc).
+std::string GetDirectoryListingHeader(const std::string& title);
+std::string GetDirectoryListingEntry(const std::string& name, DWORD attrib,
+ int64 size, const FILETIME* modified);
+
+// If text starts with "www." it is removed, otherwise text is returned
+// unmodified.
+std::wstring StripWWW(const std::wstring& text);
+
+// Gets the filename from the raw Content-Disposition header (as read from the
+// network). Otherwise uses the last path component name or hostname from
+// |url|. Note: it's possible for the suggested filename to be empty (e.g.,
+// file:/// or view-cache:).
+std::wstring GetSuggestedFilename(const GURL& url,
+ const std::string& content_disposition,
+ const std::wstring& default_name);
+
+// DEPRECATED: Please use the above version of this method.
+std::wstring GetSuggestedFilename(const GURL& url,
+ const std::wstring& content_disposition,
+ const std::wstring& default_name);
+
+// Checks the given port against a list of ports which are restricted by
+// default. Returns true if the port is allowed, false if it is restricted.
+bool IsPortAllowedByDefault(int port);
+
+// Checks the given port against a list of ports which are restricted by the
+// FTP protocol. Returns true if the port is allowed, false if it is
+// restricted.
+bool IsPortAllowedByFtp(int port);
+
+} // namespace net_util
+
+#endif // NET_BASE_NET_UTIL_H__
diff --git a/net/base/net_util_unittest.cc b/net/base/net_util_unittest.cc
new file mode 100644
index 0000000..c0b1f7c
--- /dev/null
+++ b/net/base/net_util_unittest.cc
@@ -0,0 +1,671 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+ class NetUtilTest : public testing::Test {
+ };
+}
+
+TEST(NetUtilTest, FileURLConversion) {
+ // a list of test file names and the corresponding URLs
+ const struct FileCase {
+ const wchar_t* file;
+ const wchar_t* url;
+ } round_trip_cases[] = {
+ {L"C:\\foo\\bar.txt", L"file:///C:/foo/bar.txt"},
+ {L"\\\\some computer\\foo\\bar.txt", L"file://some%20computer/foo/bar.txt"}, // UNC
+ {L"D:\\Name;with%some symbols*#", L"file:///D:/Name%3Bwith%25some%20symbols*%23"},
+ {L"D:\\Chinese\\\x6240\x6709\x4e2d\x6587\x7f51\x9875.doc", L"file:///D:/Chinese/%E6%89%80%E6%9C%89%E4%B8%AD%E6%96%87%E7%BD%91%E9%A1%B5.doc"},
+ };
+
+ // First, we'll test that we can round-trip all of the above cases of URLs
+ std::wstring output;
+ for (int i = 0; i < arraysize(round_trip_cases); i++) {
+ // convert to the file URL
+ GURL file_url(net_util::FilePathToFileURL(round_trip_cases[i].file));
+ EXPECT_EQ(std::wstring(round_trip_cases[i].url),
+ UTF8ToWide(file_url.spec()));
+
+ // Back to the filename.
+ EXPECT_TRUE(net_util::FileURLToFilePath(file_url, &output));
+ EXPECT_EQ(std::wstring(round_trip_cases[i].file), output);
+ }
+
+ // Test that various file: URLs get decoded into the correct file type
+ FileCase url_cases[] = {
+ {L"C:\\foo\\bar.txt", L"file:c|/foo\\bar.txt"},
+ {L"C:\\foo\\bar.txt", L"file:/c:/foo/bar.txt"},
+ {L"\\\\foo\\bar.txt", L"file://foo\\bar.txt"},
+ {L"C:\\foo\\bar.txt", L"file:///c:/foo/bar.txt"},
+ {L"\\\\foo\\bar.txt", L"file:////foo\\bar.txt"},
+ {L"\\\\foo\\bar.txt", L"file:/foo/bar.txt"},
+ {L"\\\\foo\\bar.txt", L"file://foo\\bar.txt"},
+ {L"C:\\foo\\bar.txt", L"file:\\\\\\c:/foo/bar.txt"},
+ };
+ for (int i = 0; i < arraysize(url_cases); i++) {
+ net_util::FileURLToFilePath(GURL(url_cases[i].url), &output);
+ EXPECT_EQ(std::wstring(url_cases[i].file), output);
+ }
+
+ // Here, we test that UTF-8 encoded strings get decoded properly, even when
+ // they might be stored with wide characters
+ const wchar_t utf8[] = L"file:///d:/Chinese/\xe6\x89\x80\xe6\x9c\x89\xe4\xb8\xad\xe6\x96\x87\xe7\xbd\x91\xe9\xa1\xb5.doc";
+ const wchar_t wide[] = L"D:\\Chinese\\\x6240\x6709\x4e2d\x6587\x7f51\x9875.doc";
+ EXPECT_TRUE(net_util::FileURLToFilePath(GURL(utf8), &output));
+ EXPECT_EQ(std::wstring(wide), 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(net_util::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(net_util::FileURLToFilePath(GURL("filefoobar"), &output));
+}
+
+// Just a bunch of fake headers.
+const wchar_t* google_headers =
+ L"HTTP/1.1 200 OK\n"
+ L"Content-TYPE: text/html; charset=utf-8\n"
+ L"Content-disposition: attachment; filename=\"download.pdf\"\n"
+ L"Content-Length: 378557\n"
+ L"X-Google-Google1: 314159265\n"
+ L"X-Google-Google2: aaaa2:7783,bbb21:9441\n"
+ L"X-Google-Google4: home\n"
+ L"Transfer-Encoding: chunked\n"
+ L"Set-Cookie: HEHE_AT=6666x66beef666x6-66xx6666x66; Path=/mail\n"
+ L"Set-Cookie: HEHE_HELP=owned:0;Path=/\n"
+ L"Set-Cookie: S=gmail=Xxx-beefbeefbeef_beefb:gmail_yj=beefbeef000beefbeefbee:gmproxy=bee-fbeefbe; Domain=.google.com; Path=/\n"
+ L"X-Google-Google2: /one/two/three/four/five/six/seven-height/nine:9411\n"
+ L"Server: GFE/1.3\n"
+ L"Transfer-Encoding: chunked\n"
+ L"Date: Mon, 13 Nov 2006 21:38:09 GMT\n"
+ L"Expires: Tue, 14 Nov 2006 19:23:58 GMT\n"
+ L"X-Malformed: bla; arg=test\"\n"
+ L"X-Malformed2: bla; arg=\n"
+ L"X-Test: bla; arg1=val1; arg2=val2";
+
+TEST(NetUtilTest, GetSpecificHeader) {
+ const struct {
+ const wchar_t* header_name;
+ const wchar_t* expected;
+ } tests[] = {
+ {L"content-type", L"text/html; charset=utf-8"},
+ {L"CONTENT-LENGTH", L"378557"},
+ {L"Date", L"Mon, 13 Nov 2006 21:38:09 GMT"},
+ {L"Bad-Header", L""},
+ {L"", L""},
+ };
+
+ // Test first with google_headers.
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ std::wstring result = net_util::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(tests); ++i) {
+ std::wstring result = net_util::GetSpecificHeader(L"", tests[i].header_name);
+ EXPECT_EQ(result, std::wstring());
+ }
+}
+
+TEST(NetUtilTest, GetHeaderParamValue) {
+ const struct {
+ const wchar_t* header_name;
+ const wchar_t* param_name;
+ const wchar_t* expected;
+ } tests[] = {
+ {L"Content-type", L"charset", L"utf-8"},
+ {L"content-disposition", L"filename", L"download.pdf"},
+ {L"Content-Type", L"badparam", L""},
+ {L"X-Malformed", L"arg", L"test\""},
+ {L"X-Malformed2", L"arg", L""},
+ {L"X-Test", L"arg1", L"val1"},
+ {L"X-Test", L"arg2", L"val2"},
+ {L"Bad-Header", L"badparam", L""},
+ {L"Bad-Header", L"", L""},
+ {L"", L"badparam", L""},
+ {L"", L"", L""},
+ };
+ // TODO(mpcomplete): add tests for other formats of headers.
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ std::wstring header_value = net_util::GetSpecificHeader(google_headers,
+ tests[i].header_name);
+ std::wstring result = net_util::GetHeaderParamValue(header_value,
+ tests[i].param_name);
+ EXPECT_EQ(result, tests[i].expected);
+ }
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ std::wstring header_value = net_util::GetSpecificHeader(L"",
+ tests[i].header_name);
+ std::wstring result = net_util::GetHeaderParamValue(header_value,
+ tests[i].param_name);
+ EXPECT_EQ(result, std::wstring());
+ }
+}
+
+TEST(NetUtilTest, GetFileNameFromCD) {
+ const struct {
+ const char* header_field;
+ const wchar_t* expected;
+ } tests[] = {
+ // Test various forms of C-D header fields emitted by web servers.
+ {"content-disposition: inline; filename=\"abcde.pdf\"", L"abcde.pdf"},
+ {"content-disposition: inline; name=\"abcde.pdf\"", L"abcde.pdf"},
+ {"content-disposition: attachment; filename=abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: attachment; name=abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: attachment; filename=abc,de.pdf", L"abc,de.pdf"},
+ {"content-disposition: filename=abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: filename= abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: filename =abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: filename = abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: filename\t=abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: filename \t\t =abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: name=abcde.pdf", L"abcde.pdf"},
+ {"content-disposition: inline; filename=\"abc%20de.pdf\"", L"abc de.pdf"},
+ // Whitespaces are converted to a space.
+ {"content-disposition: inline; filename=\"abc \t\nde.pdf\"", L"abc de.pdf"},
+ // %-escaped UTF-8
+ {"Content-Disposition: attachment; filename=\"%EC%98%88%EC%88%A0%20"
+ "%EC%98%88%EC%88%A0.jpg\"", L"\xc608\xc220 \xc608\xc220.jpg"},
+ {"Content-Disposition: attachment; filename=\"%F0%90%8C%B0%F0%90%8C%B1"
+ "abc.jpg\"", L"\U00010330\U00010331abc.jpg"},
+ {"Content-Disposition: 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
+ {"Content-Disposition: attachment; filename=\"=?EUC-JP?Q?=B7=DD=BD="
+ "D13=2Epng?=\"", L"\x82b8\x8853" L"3.png"},
+ {"Content-Disposition: attachment; filename==?eUc-Kr?b?v7m8+iAzLnBuZw==?=",
+ L"\xc608\xc220 3.png"},
+ {"Content-Disposition: attachment; filename==?utf-8?Q?=E8=8A=B8=E8"
+ "=A1=93_3=2Epng?=", L"\x82b8\x8853 3.png"},
+ {"Content-Disposition: attachment; filename==?utf-8?Q?=F0=90=8C=B0"
+ "_3=2Epng?=", L"\U00010330 3.png"},
+ {"Content-Disposition: inline; filename=\"=?iso88591?Q?caf=e3_=2epng?=\"",
+ L"caf\x00e3 .png"},
+ // Space after an encode word should be removed.
+ {"Content-Disposition: inline; filename=\"=?iso88591?Q?caf=E3_?= .png\"",
+ L"caf\x00e3 .png"},
+ // Two encoded words with different charsets (not very likely to be emitted
+ // by web servers in the wild). Spaces between them are removed.
+ {"Content-Disposition: inline; filename=\"=?euc-kr?b?v7m8+iAz?="
+ " =?ksc5601?q?=BF=B9=BC=FA=2Epng?=\"", L"\xc608\xc220 3\xc608\xc220.png"},
+ {"Content-Disposition: attachment; filename=\"=?windows-1252?Q?caf=E3?="
+ " =?iso-8859-7?b?4eI=?= .png\"", L"caf\x00e3\x03b1\x03b2.png"},
+ // Non-ASCII string is passed through (and treated as UTF-8).
+ {"Content-Disposition: attachment; filename=caf\xc3\xa3.png",
+ L"caf\x00e3.png"},
+ // Failure cases
+ // Invalid hex-digit "G"
+ {"Content-Disposition: attachment; filename==?iiso88591?Q?caf=EG?=", L""},
+ // Incomplete RFC 2047 encoded-word (missing '='' at the end)
+ {"Content-Disposition: attachment; filename==?iso88591?Q?caf=E3?", L""},
+ // Extra character at the end of an encoded word
+ {"Content-Disposition: attachment; filename==?iso88591?Q?caf=E3?==", L""},
+ // Extra token at the end of an encoded word
+ {"Content-Disposition: attachment; filename==?iso88591?Q?caf=E3?=?", L""},
+ {"Content-Disposition: attachment; filename==?iso88591?Q?caf=E3?=?=", L""},
+ // Incomplete hex-escaped chars
+ {"Content-Disposition: attachment; filename==?windows-1252?Q?=63=61=E?=",
+ L""},
+ {"Content-Disposition: attachment; filename=%EC%98%88%EC%88%A", L""},
+ // %-escaped non-UTF-8 encoding is an "error"
+ {"Content-Disposition: attachment; filename=%B7%DD%BD%D1.png", L""},
+ // Two RFC 2047 encoded words in a row without a space is an error.
+ {"Content-Disposition: attachment; filename==?windows-1252?Q?caf=E3?="
+ "=?iso-8859-7?b?4eIucG5nCg==?=", L""},
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ EXPECT_EQ(tests[i].expected,
+ net_util::GetFileNameFromCD(tests[i].header_field));
+ }
+}
+
+TEST(NetUtilTest, IDNToUnicode) {
+ // 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 wchar_t* languages[] = {
+ L"", L"en", L"zh-CN", L"ja", L"ko",
+ L"he", L"ar", L"ru", L"el", L"fr",
+ L"de", L"pt", L"se", L"th", L"hi",
+ L"de,en", L"el,en", L"zh,zh-TW,en", L"ko,ja", L"he,ru,en",
+ L"zh,ru,en"};
+ struct IDNTest {
+ const char* input;
+ const wchar_t* unicode_output;
+ const bool unicode_allowed[arraysize(languages)];
+ } 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 (Chinese)
+ {"xn--1lq90i.cn", L"\x5317\x4eac.cn",
+ {true, false, true, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, true, true, 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
+ {"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, true, 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}},
+ #if 0
+ // U+30FC is not a part of the Japanese exemplar set.
+ // Enable this after 'fixing' ICU data or locally working around it.
+ // Katakana + Latin (Japanese)
+ {"xn--e-efusa1mzf.jp", L"e\x30b3\x30de\x30fc\x30b9.jp",
+ {true, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ }},
+ #endif
+ // 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, true, 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, false, 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, false, 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
+ {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ {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,
+ }},
+#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
+ };
+
+ for (int i = 0; i < arraysize(idn_cases); i++) {
+ for (int j = 0; j < arraysize(languages); j++) {
+ std::wstring output;
+ net_util::IDNToUnicode(idn_cases[i].input,
+ static_cast<int>(strlen(idn_cases[i].input)),
+ languages[j],
+ &output);
+ std::wstring expected(idn_cases[i].unicode_allowed[j] ?
+ idn_cases[i].unicode_output :
+ ASCIIToWide(idn_cases[i].input));
+ EXPECT_EQ(expected, output);
+ }
+ }
+}
+
+TEST(NetUtilTest, StripWWW) {
+ EXPECT_EQ(L"", net_util::StripWWW(L""));
+ EXPECT_EQ(L"", net_util::StripWWW(L"www."));
+ EXPECT_EQ(L"blah", net_util::StripWWW(L"www.blah"));
+ EXPECT_EQ(L"blah", net_util::StripWWW(L"blah"));
+}
+
+TEST(NetUtilTest, GetSuggestedFilename) {
+ struct FilenameTest {
+ const char* url;
+ const wchar_t* content_disp_header;
+ const wchar_t* default_filename;
+ const wchar_t* expected_filename;
+ } test_cases[] = {
+ {"http://www.google.com/",
+ L"Content-disposition: attachment; filename=test.html",
+ L"",
+ L"test.html"},
+ {"http://www.google.com/",
+ L"Content-disposition: attachment; filename=\"test.html\"",
+ L"",
+ L"test.html"},
+ {"http://www.google.com/path/test.html",
+ L"Content-disposition: attachment",
+ L"",
+ L"test.html"},
+ {"http://www.google.com/path/test.html",
+ L"Content-disposition: attachment;",
+ L"",
+ L"test.html"},
+ {"http://www.google.com/",
+ L"",
+ L"",
+ L"www.google.com"},
+ {"http://www.google.com/test.html",
+ L"",
+ L"",
+ L"test.html"},
+ // Now that we use googleurl's ExtractFileName, this case falls back
+ // to the hostname. If this behavior is not desirable, we'd better
+ // change ExtractFileName (in url_parse).
+ {"http://www.google.com/path/",
+ L"",
+ L"",
+ L"www.google.com"},
+ {"http://www.google.com/path",
+ L"",
+ L"",
+ L"path"},
+ {"file:///",
+ L"",
+ L"",
+ L"download"},
+ {"view-cache:",
+ L"",
+ L"",
+ L"download"},
+ {"http://www.google.com/",
+ L"Content-disposition: attachment; filename =\"test.html\"",
+ L"download",
+ L"test.html"},
+ {"http://www.google.com/",
+ L"",
+ L"download",
+ L"download"},
+ {"http://www.google.com/",
+ L"Content-disposition: attachment; filename=\"../test.html\"",
+ L"",
+ L"test.html"},
+ {"http://www.google.com/",
+ L"Content-disposition: attachment; filename=\"..\"",
+ L"download",
+ L"download"},
+ {"http://www.google.com/test.html",
+ L"Content-disposition: attachment; filename=\"..\"",
+ L"download",
+ L"test.html"},
+ // Below is a small subset of cases taken from GetFileNameFromCD test above.
+ {"http://www.google.com/",
+ L"Content-Disposition: attachment; filename=\"%EC%98%88%EC%88%A0%20"
+ L"%EC%98%88%EC%88%A0.jpg\"",
+ L"",
+ L"\uc608\uc220 \uc608\uc220.jpg"},
+ {"http://www.google.com/%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg",
+ L"",
+ L"download",
+ L"\uc608\uc220 \uc608\uc220.jpg"},
+ {"http://www.google.com/",
+ L"Content-disposition: attachment;",
+ L"\uB2E4\uC6B4\uB85C\uB4DC",
+ L"\uB2E4\uC6B4\uB85C\uB4DC"},
+ {"http://www.google.com/",
+ L"Content-Disposition: attachment; filename=\"=?EUC-JP?Q?=B7=DD=BD="
+ L"D13=2Epng?=\"",
+ L"download",
+ L"\u82b8\u88533.png"},
+ // Invalid C-D header. Extracts filename from url.
+ {"http://www.google.com/test.html",
+ L"Content-Disposition: attachment; filename==?iiso88591?Q?caf=EG?=",
+ L"",
+ L"test.html"},
+ };
+ for (int i = 0; i < arraysize(test_cases); ++i) {
+ std::wstring filename = net_util::GetSuggestedFilename(
+ GURL(test_cases[i].url), test_cases[i].content_disp_header,
+ test_cases[i].default_filename);
+ EXPECT_EQ(std::wstring(test_cases[i].expected_filename), filename);
+ }
+}
diff --git a/net/base/registry_controlled_domain.cc b/net/base/registry_controlled_domain.cc
new file mode 100644
index 0000000..b573d17
--- /dev/null
+++ b/net/base/registry_controlled_domain.cc
@@ -0,0 +1,351 @@
+//* -*- 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 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 <windows.h>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_parse.h"
+#include "net/base/net_module.h"
+#include "net/base/net_resources.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domain.h"
+
+// This list of rules is used by unit tests and any other time that the main
+// resource file is not available. It should be kept exceedingly short to
+// avoid impacting unit test performance.
+static const char kDefaultDomainData[] = "com\n"
+ "edu\n"
+ "gov\n"
+ "net\n"
+ "org\n"
+ "co.uk\n";
+
+// static
+std::string RegistryControlledDomainService::GetDomainAndRegistry(
+ const GURL& gurl) {
+ 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));
+}
+
+// static
+std::string RegistryControlledDomainService::GetDomainAndRegistry(
+ const std::string& host) {
+ bool is_ip_address;
+ const std::string canon_host(net_util::CanonicalizeHost(host,
+ &is_ip_address));
+ if (canon_host.empty() || is_ip_address)
+ return std::string();
+ return GetDomainAndRegistryImpl(canon_host);
+}
+
+// static
+std::string RegistryControlledDomainService::GetDomainAndRegistry(
+ const std::wstring& host) {
+ bool is_ip_address;
+ const std::string canon_host(net_util::CanonicalizeHost(host,
+ &is_ip_address));
+ if (canon_host.empty() || is_ip_address)
+ return std::string();
+ return GetDomainAndRegistryImpl(canon_host);
+}
+
+// static
+bool RegistryControlledDomainService::SameDomainOrHost(const GURL& gurl1,
+ const GURL& gurl2) {
+ // See if both URLs have a known domain + registry, and those values are the
+ // same.
+ const std::string domain1(GetDomainAndRegistry(gurl1));
+ const std::string domain2(GetDomainAndRegistry(gurl2));
+ 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);
+}
+
+// static
+size_t RegistryControlledDomainService::GetRegistryLength(
+ const GURL& gurl,
+ bool allow_unknown_registries) {
+ 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 GetInstance()->GetRegistryLengthImpl(
+ std::string(gurl.possibly_invalid_spec().data() + host.begin, host.len),
+ allow_unknown_registries);
+}
+
+// static
+size_t RegistryControlledDomainService::GetRegistryLength(
+ const std::string& host,
+ bool allow_unknown_registries) {
+ bool is_ip_address;
+ const std::string canon_host(net_util::CanonicalizeHost(host,
+ &is_ip_address));
+ if (canon_host.empty())
+ return std::string::npos;
+ if (is_ip_address)
+ return 0;
+ return GetInstance()->GetRegistryLengthImpl(canon_host,
+ allow_unknown_registries);
+}
+
+// static
+size_t RegistryControlledDomainService::GetRegistryLength(
+ const std::wstring& host,
+ bool allow_unknown_registries) {
+ bool is_ip_address;
+ const std::string canon_host(net_util::CanonicalizeHost(host,
+ &is_ip_address));
+ if (canon_host.empty())
+ return std::string::npos;
+ if (is_ip_address)
+ return 0;
+ return GetInstance()->GetRegistryLengthImpl(canon_host,
+ allow_unknown_registries);
+}
+
+// static
+std::string RegistryControlledDomainService::GetDomainAndRegistryImpl(
+ const std::string& host) {
+ DCHECK(!host.empty());
+
+ // Find the length of the registry for this host.
+ const size_t registry_length =
+ GetInstance()->GetRegistryLengthImpl(host, true);
+ 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.
+ 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);
+}
+
+size_t RegistryControlledDomainService::GetRegistryLengthImpl(
+ const std::string& host,
+ bool allow_unknown_registries) {
+ 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.
+ StringSegment match;
+ 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) {
+ match.Set(host.data(), curr_start, host_check_len - curr_start);
+ DomainMap::iterator iter = domain_map_.find(match);
+ if (iter != domain_map_.end()) {
+ DomainEntry entry = iter->second;
+ // Exception rules override wildcard rules when the domain is an exact
+ // match, but wildcards take precedence when there's a subdomain.
+ if (entry.wildcard && (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 (entry.exception) {
+ 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 allow_unknown_registries ? (host.length() - curr_start) : 0;
+}
+
+RegistryControlledDomainService* RegistryControlledDomainService::instance_ =
+ NULL;
+
+// static
+RegistryControlledDomainService* RegistryControlledDomainService::GetInstance()
+{
+ if (!instance_) {
+ RegistryControlledDomainService* s = new RegistryControlledDomainService();
+ s->Init();
+ // TODO(darin): use fix_wp64.h once it lives in base/
+ if (InterlockedCompareExchangePointer(
+ reinterpret_cast<PVOID*>(&instance_), s, NULL)) {
+ // Oops, another thread initialized instance_ out from under us.
+ delete s;
+ }
+ }
+ return instance_;
+}
+
+// static
+void RegistryControlledDomainService::UseDomainData(const std::string& data) {
+ RegistryControlledDomainService* instance = GetInstance();
+ instance->domain_data_ = data;
+ instance->ParseDomainData();
+}
+
+void RegistryControlledDomainService::Init() {
+ domain_data_ = NetModule::GetResource(IDR_EFFECTIVE_TLD_NAMES);
+ if (domain_data_.empty()) {
+ // The resource file isn't present for some unit tests, for example. Fall
+ // back to a tiny, basic list of rules in that case.
+ domain_data_ = kDefaultDomainData;
+ }
+ ParseDomainData();
+}
+
+void RegistryControlledDomainService::ParseDomainData() {
+ domain_map_.clear();
+
+ StringSegment rule;
+ size_t line_end = 0;
+ size_t line_start = 0;
+ while (line_start < domain_data_.size()) {
+ line_end = domain_data_.find('\n', line_start);
+ if (line_end == std::string::npos)
+ line_end = domain_data_.size();
+ rule.Set(domain_data_.data(), line_start, line_end - line_start);
+ AddRule(&rule);
+ line_start = line_end + 1;
+ }
+}
+
+void RegistryControlledDomainService::AddRule(StringSegment* rule) {
+ // Determine rule properties.
+ size_t property_offset = 0;
+ bool exception = false;
+ bool wild = false;
+
+ // Valid rules may be either wild or exceptions, but not both.
+ if (rule->CharAt(0) == '!') {
+ exception = true;
+ property_offset = 1;
+ } else if (rule->CharAt(0) == '*' && rule->CharAt(1) == '.') {
+ wild = true;
+ property_offset = 2;
+ }
+
+ // Find or create an entry for this host.
+ rule->TrimFromStart(property_offset);
+ DomainEntry entry;
+ DomainMap::iterator iter = domain_map_.find(*rule);
+ if (iter != domain_map_.end())
+ entry = iter->second;
+
+ entry.exception |= exception;
+ entry.wildcard |= wild;
+ domain_map_[*rule] = entry;
+}
+
+bool RegistryControlledDomainService::StringSegment::operator<(
+ const StringSegment &other) const {
+ // If the segments are of equal length, compare their contents; otherwise,
+ // the shorter segment is "less than" the longer one.
+ if (len_ == other.len_) {
+ int comparison = strncmp(data_ + begin_, other.data_ + other.begin_, len_);
+ return (comparison < 0);
+ }
+ return (len_ < other.len_);
+}
diff --git a/net/base/registry_controlled_domain.h b/net/base/registry_controlled_domain.h
new file mode 100644
index 0000000..6b5adbc
--- /dev/null
+++ b/net/base/registry_controlled_domain.h
@@ -0,0 +1,298 @@
+//* -*- 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 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 ***** */
+
+// NB: Modelled after Mozilla's code (originally written by Pamela Greene,
+// later modified by others), but almost entirely rewritten for Chrome.
+
+/*
+ (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_DOMAIN_H__
+#define NET_BASE_REGISTRY_CONTROLLED_DOMAIN_H__
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+
+class GURL;
+
+// This class is a singleton.
+class RegistryControlledDomainService {
+ public:
+ // 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)
+ static std::string GetDomainAndRegistry(const GURL& gurl);
+
+ // Like the GURL version, but takes a host (which is canonicalized internally)
+ // instead of a full GURL.
+ static std::string GetDomainAndRegistry(const std::string& host);
+ static std::string GetDomainAndRegistry(const std::wstring& host);
+
+ // 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".
+ static bool SameDomainOrHost(const GURL& gurl1, const GURL& gurl2);
+
+ // 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. 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
+ // |allow_unknown_registries| is false, or the length of the last subcomponent
+ // if |allow_unknown_registries| is true.
+ //
+ // 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)
+ static size_t GetRegistryLength(const GURL& gurl,
+ bool allow_unknown_registries);
+
+ // Like the GURL version, but takes a host (which is canonicalized internally)
+ // instead of a full GURL.
+ static size_t GetRegistryLength(const std::string& host,
+ bool allow_unknown_registries);
+ static size_t GetRegistryLength(const std::wstring& host,
+ bool allow_unknown_registries);
+
+ protected:
+ // The entire protected API is only for unit testing. I mean it. Don't make
+ // me come over there!
+ RegistryControlledDomainService() { }
+ ~RegistryControlledDomainService() { }
+
+ // Clears the static singleton instance. This is used by unit tests to
+ // create a new instance for each test, to help ensure test independence.
+ static void ResetInstance() {
+ delete instance_;
+ instance_ = NULL;
+ }
+
+ // Sets the domain_data_ of the current instance (creating one, if necessary),
+ // then parses it.
+ static void UseDomainData(const std::string& data);
+
+ private:
+ // Using the StringSegment class, we can compare portions of strings without
+ // needing to allocate or copy them.
+ class StringSegment {
+ public:
+ StringSegment() : data_(0), begin_(0), len_(0) { }
+ ~StringSegment() { }
+
+ void Set(const char* data, size_t begin, size_t len) {
+ data_ = data;
+ begin_ = begin;
+ len_ = len;
+ }
+
+ // Returns the character at the given offset from the start of the segment,
+ // or '\0' if the offset lies outside the segment.
+ char CharAt(size_t offset) const {
+ return (offset < len_) ? data_[begin_ + offset] : '\0';
+ }
+
+ // Removes a maximum of |trimmed| number of characters, up to the length of
+ // the segment, from the start of the StringSegment.
+ void TrimFromStart(size_t trimmed) {
+ if (trimmed > len_)
+ trimmed = len_;
+ begin_ += trimmed;
+ len_ -= trimmed;
+ }
+
+ const char* data() const { return data_; }
+
+ // This comparator is needed by std::map. Note that since we don't care
+ // about the exact sorting, we use a somewhat less intuitive, but efficient,
+ // comparison.
+ bool operator<(const StringSegment& other) const;
+
+ private:
+ const char* data_;
+ size_t begin_;
+ size_t len_;
+ };
+
+ // The full domain rule data, loaded from a resource or set by a unit test.
+ std::string domain_data_;
+
+ // An entry in the map of domain specifications, describing the properties
+ // that apply to that domain rule.
+ struct DomainEntry {
+ DomainEntry() : exception(false), wildcard(false) { }
+ bool exception;
+ bool wildcard;
+ };
+ typedef std::map<StringSegment, DomainEntry> DomainMap;
+
+ // A map from a StringSegment holding a domain name (rule) to its DomainEntry.
+ // The StringSegments in the domain_map_ hold pointers to the domain_data_
+ // data; that's cheaper than copying the string data itself.
+ // TODO(pamg): Since all the domain_map_ entries have the same data_, it's
+ // redundant. Is it worth subclassing StringSegment to avoid that?
+ DomainMap domain_map_;
+
+ // Parses a list of effective-TLD rules, building the domain_map_. Rules are
+ // assumed to be syntactically valid.
+ void ParseDomainData();
+
+ // The class's singleton instance.
+ static RegistryControlledDomainService* instance_;
+
+ // Returns the singleton instance, after attempting to initialize it.
+ // NOTE that if the effective-TLD data resource can't be found, the instance
+ // will be initialized and continue operation with an empty domain_map_.
+ static RegistryControlledDomainService* GetInstance();
+
+ // Loads and parses the effective-TLD data resource.
+ void Init();
+
+ // Adds one rule, assumed to be valid, to the domain_map_.
+ // WARNING: As implied by the non-const status of the incoming rule, this
+ // method may MODIFY that rule (in particular, change its start and length).
+ // This is a performance optimization.
+ void AddRule(StringSegment* rule);
+
+ // Internal workings of the static public methods. See above.
+ static std::string GetDomainAndRegistryImpl(const std::string& host);
+ size_t GetRegistryLengthImpl(const std::string& host,
+ bool allow_unknown_registries);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RegistryControlledDomainService);
+};
+
+#endif // NET_BASE_REGISTRY_CONTROLLED_DOMAIN_H__
diff --git a/net/base/registry_controlled_domain_unittest.cc b/net/base/registry_controlled_domain_unittest.cc
new file mode 100644
index 0000000..cd74fc5
--- /dev/null
+++ b/net/base/registry_controlled_domain_unittest.cc
@@ -0,0 +1,296 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "googleurl/src/gurl.h"
+#include "net/base/registry_controlled_domain.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestRegistryControlledDomainService;
+static TestRegistryControlledDomainService* test_instance_;
+
+class TestRegistryControlledDomainService :
+ public RegistryControlledDomainService {
+ public:
+
+ // Deletes the instance so a new one will be created.
+ static void ResetInstance() {
+ RegistryControlledDomainService::ResetInstance();
+ }
+
+ // Sets and parses the given data.
+ static void UseDomainData(const std::string& data) {
+ RegistryControlledDomainService::UseDomainData(data);
+ }
+
+ private:
+ TestRegistryControlledDomainService::TestRegistryControlledDomainService() { }
+ TestRegistryControlledDomainService::~TestRegistryControlledDomainService() {
+ }
+};
+
+class RegistryControlledDomainTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ TestRegistryControlledDomainService::ResetInstance();
+ }
+};
+
+// Convenience functions to shorten the names for repeated use below.
+void SetTestData(const std::string& data) {
+ TestRegistryControlledDomainService::UseDomainData(data);
+}
+
+std::string GetDomainFromURL(const std::string& url) {
+ return TestRegistryControlledDomainService::GetDomainAndRegistry(GURL(url));
+}
+
+std::string GetDomainFromHost(const std::wstring& host) {
+ return TestRegistryControlledDomainService::GetDomainAndRegistry(host);
+}
+
+size_t GetRegistryLengthFromURL(const std::string& url,
+ bool allow_unknown_registries) {
+ return TestRegistryControlledDomainService::GetRegistryLength(GURL(url),
+ allow_unknown_registries);
+}
+
+size_t GetRegistryLengthFromHost(const std::wstring& host,
+ bool allow_unknown_registries) {
+ return TestRegistryControlledDomainService::GetRegistryLength(host,
+ allow_unknown_registries);
+}
+
+bool CompareDomains(const std::string& url1, const std::string& url2) {
+ GURL g1 = GURL(url1);
+ GURL g2 = GURL(url2);
+ return TestRegistryControlledDomainService::SameDomainOrHost(g1, g2);
+}
+
+} // namespace
+
+TEST_F(RegistryControlledDomainTest, TestParsing) {
+ // Ensure that various simple and pathological cases parse without hanging or
+ // crashing. Testing the correctness of the parsing directly would require
+ // opening the singleton class up more.
+ SetTestData("com");
+ SetTestData("abc.com\n");
+ SetTestData("abc.com\ndef.com\n*.abc.com\n!foo.abc.com");
+ SetTestData("abc.com.\n");
+ SetTestData("");
+ SetTestData("*.");
+ SetTestData("!");
+ SetTestData(".");
+}
+
+static const char kTestData[] = "jp\n" // 1
+ "ac.jp\n" // 2
+ "*.bar.jp\n" // 3
+ "*.baz.bar.jp\n" // 4
+ "*.foo.bar.jp\n" // 5
+ "!foo.bar.jp\n" // 6
+ "!pref.bar.jp\n" // 7
+ "bar.baz.com\n" // 8
+ "*.c\n" // 9
+ "!b.c"; // 10
+
+TEST_F(RegistryControlledDomainTest, TestGetDomainAndRegistry) {
+ SetTestData(kTestData);
+
+ // 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("foo.bar.jp", GetDomainFromURL("http://foo.bar.jp")); // 3 5 6
+ EXPECT_EQ("pref.bar.jp", GetDomainFromURL("http://baz.pref.bar.jp")); // 7
+ EXPECT_EQ("b.bar.baz.com.", GetDomainFromURL("http://a.b.bar.baz.com."));
+ // 8
+ EXPECT_EQ("a.d.c", GetDomainFromURL("http://a.d.c")); // 9
+ EXPECT_EQ("a.d.c", GetDomainFromURL("http://.a.d.c")); // 9
+ EXPECT_EQ("a.d.c", GetDomainFromURL("http://..a.d.c")); // 9
+ EXPECT_EQ("b.c", GetDomainFromURL("http://a.b.c")); // 9 10
+ EXPECT_EQ("baz.com", GetDomainFromURL("http://baz.com")); // none
+ EXPECT_EQ("baz.com.", GetDomainFromURL("http://baz.com.")); // none
+
+ EXPECT_EQ("", GetDomainFromURL(""));
+ 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::wstring 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(L"a.baz.jp")); // 1
+ EXPECT_EQ("baz.jp.", GetDomainFromHost(L"a.baz.jp.")); // 1
+ EXPECT_EQ("", GetDomainFromHost(L"ac.jp")); // 2
+ EXPECT_EQ("", GetDomainFromHost(L"a.bar.jp")); // 3
+ EXPECT_EQ("", GetDomainFromHost(L"bar.jp")); // 3
+ EXPECT_EQ("", GetDomainFromHost(L"baz.bar.jp")); // 3 4
+ EXPECT_EQ("a.b.baz.bar.jp", GetDomainFromHost(L"a.b.baz.bar.jp")); // 3 4
+ EXPECT_EQ("foo.bar.jp", GetDomainFromHost(L"foo.bar.jp")); // 3 5 6
+ EXPECT_EQ("pref.bar.jp", GetDomainFromHost(L"baz.pref.bar.jp")); // 7
+ EXPECT_EQ("b.bar.baz.com.", GetDomainFromHost(L"a.b.bar.baz.com.")); // 8
+ EXPECT_EQ("a.d.c", GetDomainFromHost(L"a.d.c")); // 9
+ EXPECT_EQ("a.d.c", GetDomainFromHost(L".a.d.c")); // 9
+ EXPECT_EQ("a.d.c", GetDomainFromHost(L"..a.d.c")); // 9
+ EXPECT_EQ("b.c", GetDomainFromHost(L"a.b.c")); // 9 10
+ EXPECT_EQ("baz.com", GetDomainFromHost(L"baz.com")); // none
+ EXPECT_EQ("baz.com.", GetDomainFromHost(L"baz.com.")); // none
+
+ EXPECT_EQ("", GetDomainFromHost(L""));
+ EXPECT_EQ("", GetDomainFromHost(L"foo.com.."));
+ EXPECT_EQ("", GetDomainFromHost(L"..."));
+ EXPECT_EQ("", GetDomainFromHost(L"192.168.0.1"));
+ EXPECT_EQ("", GetDomainFromHost(L"localhost."));
+ EXPECT_EQ("", GetDomainFromHost(L".localhost."));
+}
+
+TEST_F(RegistryControlledDomainTest, TestGetRegistryLength) {
+ SetTestData(kTestData);
+
+ // Test GURL version of GetRegistryLength().
+ EXPECT_EQ(2, GetRegistryLengthFromURL("http://a.baz.jp/file.html", false));
+ // 1
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://a.baz.jp./file.html", false));
+ // 1
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://ac.jp", false)); // 2
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://a.bar.jp", false)); // 3
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://bar.jp", false)); // 3
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://baz.bar.jp", false)); // 3 4
+ EXPECT_EQ(12, GetRegistryLengthFromURL("http://a.b.baz.bar.jp", false));
+ // 4
+ EXPECT_EQ(6, GetRegistryLengthFromURL("http://foo.bar.jp", false)); // 3 5 6
+ EXPECT_EQ(6, GetRegistryLengthFromURL("http://baz.pref.bar.jp", false));
+ // 7
+ EXPECT_EQ(11, GetRegistryLengthFromURL("http://a.b.bar.baz.com", false));
+ // 8
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://a.d.c", false)); // 9
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://.a.d.c", false)); // 9
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://..a.d.c", false)); // 9
+ EXPECT_EQ(1, GetRegistryLengthFromURL("http://a.b.c", false)); // 9 10
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://baz.com", false)); // none
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://baz.com.", false)); // none
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://baz.com", true)); // none
+ EXPECT_EQ(4, GetRegistryLengthFromURL("http://baz.com.", true)); // none
+
+ EXPECT_EQ(std::string::npos, GetRegistryLengthFromURL("", false));
+ EXPECT_EQ(std::string::npos, GetRegistryLengthFromURL("http://", false));
+ EXPECT_EQ(std::string::npos,
+ GetRegistryLengthFromURL("file:///C:/file.html", false));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://foo.com..", false));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://...", false));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://192.168.0.1", false));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://localhost", false));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://localhost", true));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://localhost.", false));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://localhost.", true));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http:////Comment", false));
+
+ // Test std::wstring version of GetRegistryLength(). Uses the same
+ // underpinnings as the GURL version, so this is really more of a check of
+ // CanonicalizeHost().
+ EXPECT_EQ(2, GetRegistryLengthFromHost(L"a.baz.jp", false)); // 1
+ EXPECT_EQ(3, GetRegistryLengthFromHost(L"a.baz.jp.", false)); // 1
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"ac.jp", false)); // 2
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"a.bar.jp", false)); // 3
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"bar.jp", false)); // 3
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"baz.bar.jp", false)); // 3 4
+ EXPECT_EQ(12, GetRegistryLengthFromHost(L"a.b.baz.bar.jp", false)); // 4
+ EXPECT_EQ(6, GetRegistryLengthFromHost(L"foo.bar.jp", false)); // 3 5 6
+ EXPECT_EQ(6, GetRegistryLengthFromHost(L"baz.pref.bar.jp", false)); // 7
+ EXPECT_EQ(11, GetRegistryLengthFromHost(L"a.b.bar.baz.com", false)); // 8
+ EXPECT_EQ(3, GetRegistryLengthFromHost(L"a.d.c", false)); // 9
+ EXPECT_EQ(3, GetRegistryLengthFromHost(L".a.d.c", false)); // 9
+ EXPECT_EQ(3, GetRegistryLengthFromHost(L"..a.d.c", false)); // 9
+ EXPECT_EQ(1, GetRegistryLengthFromHost(L"a.b.c", false)); // 9 10
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"baz.com", false)); // none
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"baz.com.", false)); // none
+ EXPECT_EQ(3, GetRegistryLengthFromHost(L"baz.com", true)); // none
+ EXPECT_EQ(4, GetRegistryLengthFromHost(L"baz.com.", true)); // none
+
+ EXPECT_EQ(std::string::npos, GetRegistryLengthFromHost(L"", false));
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"foo.com..", false));
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"..", false));
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"192.168.0.1", false));
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"localhost", false));
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"localhost", true));
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"localhost.", false));
+ EXPECT_EQ(0, GetRegistryLengthFromHost(L"localhost.", true));
+}
+
+TEST_F(RegistryControlledDomainTest, TestSameDomainOrHost) {
+ SetTestData("jp\nbar.jp");
+
+ EXPECT_EQ(true, CompareDomains("http://a.b.bar.jp/file.html",
+ "http://a.b.bar.jp/file.html")); // b.bar.jp
+ EXPECT_EQ(true, CompareDomains("http://a.b.bar.jp/file.html",
+ "http://b.b.bar.jp/file.html")); // b.bar.jp
+ EXPECT_EQ(false, CompareDomains("http://a.foo.jp/file.html", // foo.jp
+ "http://a.not.jp/file.html")); // not.jp
+ EXPECT_EQ(false, CompareDomains("http://a.foo.jp/file.html", // foo.jp
+ "http://a.foo.jp./file.html")); // foo.jp.
+ EXPECT_EQ(false, CompareDomains("http://a.com/file.html", // a.com
+ "http://b.com/file.html")); // b.com
+ EXPECT_EQ(true, CompareDomains("http://a.x.com/file.html",
+ "http://b.x.com/file.html")); // x.com
+ EXPECT_EQ(true, CompareDomains("http://a.x.com/file.html",
+ "http://.x.com/file.html")); // x.com
+ EXPECT_EQ(true, CompareDomains("http://a.x.com/file.html",
+ "http://..b.x.com/file.html")); // x.com
+ EXPECT_EQ(true, CompareDomains("http://intranet/file.html",
+ "http://intranet/file.html")); // intranet
+ EXPECT_EQ(true, CompareDomains("http://127.0.0.1/file.html",
+ "http://127.0.0.1/file.html")); // 127.0.0.1
+ EXPECT_EQ(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_EQ(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(3, GetRegistryLengthFromURL("http://google.com", false));
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://stanford.edu", false));
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://ustreas.gov", false));
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://icann.net", false));
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://ferretcentral.org", false));
+ EXPECT_EQ(0, GetRegistryLengthFromURL("http://nowhere.foo", false));
+ EXPECT_EQ(3, GetRegistryLengthFromURL("http://nowhere.foo", true));
+}
diff --git a/net/base/socket.h b/net/base/socket.h
new file mode 100644
index 0000000..11f0906e
--- /dev/null
+++ b/net/base/socket.h
@@ -0,0 +1,61 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_SOCKET_H_
+#define NET_BASE_SOCKET_H_
+
+#include "net/base/completion_callback.h"
+
+namespace net {
+
+// Represents a read/write socket.
+class Socket {
+ public:
+ virtual ~Socket() {}
+
+ // Read data, up to buf_len bytes, from the socket. The number of bytes read
+ // is returned, or an error is returned upon failure. Zero is returned to
+ // indicate end-of-file. 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.
+ virtual int Read(char* buf, int buf_len,
+ CompletionCallback* callback) = 0;
+
+ // Writes data, up to buf_len bytes, to the socket. Note: only part of the
+ // data may be written! The number of bytes written is returned, or an error
+ // is returned upon failure. 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.
+ virtual int Write(const char* buf, int buf_len,
+ CompletionCallback* callback) = 0;
+};
+
+} // namespace net
+
+#endif // NET_BASE_SOCKET_H_
diff --git a/net/base/ssl_client_socket.cc b/net/base/ssl_client_socket.cc
new file mode 100644
index 0000000..711a114
--- /dev/null
+++ b/net/base/ssl_client_socket.cc
@@ -0,0 +1,502 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/ssl_client_socket.h"
+
+#include <schnlsp.h>
+
+#include "base/singleton.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+class SChannelLib {
+ public:
+ SecurityFunctionTable funcs;
+
+ SChannelLib() {
+ memset(&funcs, 0, sizeof(funcs));
+ lib_ = LoadLibrary(L"SCHANNEL.DLL");
+ if (lib_) {
+ INIT_SECURITY_INTERFACE init_security_interface =
+ reinterpret_cast<INIT_SECURITY_INTERFACE>(
+ GetProcAddress(lib_, "InitSecurityInterfaceW"));
+ if (init_security_interface) {
+ PSecurityFunctionTable funcs_ptr = init_security_interface();
+ if (funcs_ptr)
+ memcpy(&funcs, funcs_ptr, sizeof(funcs));
+ }
+ }
+ }
+
+ ~SChannelLib() {
+ FreeLibrary(lib_);
+ }
+
+ private:
+ HMODULE lib_;
+};
+
+static inline SecurityFunctionTable& SChannel() {
+ return Singleton<SChannelLib>()->funcs;
+}
+
+//-----------------------------------------------------------------------------
+
+static const int kRecvBufferSize = 0x10000;
+
+SSLClientSocket::SSLClientSocket(ClientSocket* transport_socket,
+ const std::string& hostname)
+#pragma warning(suppress: 4355)
+ : io_callback_(this, &SSLClientSocket::OnIOComplete),
+ transport_(transport_socket),
+ hostname_(hostname),
+ user_callback_(NULL),
+ user_buf_(NULL),
+ user_buf_len_(0),
+ next_state_(STATE_NONE),
+ bytes_sent_(0),
+ bytes_received_(0),
+ completed_handshake_(false) {
+ memset(&stream_sizes_, 0, sizeof(stream_sizes_));
+ memset(&send_buffer_, 0, sizeof(send_buffer_));
+ memset(&creds_, 0, sizeof(creds_));
+ memset(&ctxt_, 0, sizeof(ctxt_));
+}
+
+SSLClientSocket::~SSLClientSocket() {
+ Disconnect();
+}
+
+int SSLClientSocket::Connect(CompletionCallback* callback) {
+ DCHECK(transport_.get());
+ DCHECK(next_state_ == STATE_NONE);
+ DCHECK(!user_callback_);
+
+ next_state_ = STATE_CONNECT;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int SSLClientSocket::ReconnectIgnoringLastError(CompletionCallback* callback) {
+ // TODO(darin): implement me!
+ return ERR_FAILED;
+}
+
+void SSLClientSocket::Disconnect() {
+ transport_->Disconnect();
+
+ if (send_buffer_.pvBuffer) {
+ SChannel().FreeContextBuffer(send_buffer_.pvBuffer);
+ memset(&send_buffer_, 0, sizeof(send_buffer_));
+ }
+ if (creds_.dwLower || creds_.dwUpper) {
+ SChannel().FreeCredentialsHandle(&creds_);
+ memset(&creds_, 0, sizeof(creds_));
+ }
+ if (ctxt_.dwLower || ctxt_.dwUpper) {
+ SChannel().DeleteSecurityContext(&ctxt_);
+ memset(&ctxt_, 0, sizeof(ctxt_));
+ }
+}
+
+bool SSLClientSocket::IsConnected() const {
+ return completed_handshake_ && transport_->IsConnected();
+}
+
+int SSLClientSocket::Read(char* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(completed_handshake_);
+ DCHECK(next_state_ == STATE_NONE);
+ DCHECK(!user_callback_);
+
+ user_buf_ = buf;
+ user_buf_len_ = buf_len;
+
+ next_state_ = STATE_PAYLOAD_READ;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int SSLClientSocket::Write(const char* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(completed_handshake_);
+ DCHECK(next_state_ == STATE_NONE);
+ DCHECK(!user_callback_);
+
+ user_buf_ = const_cast<char*>(buf);
+ user_buf_len_ = buf_len;
+
+ next_state_ = STATE_PAYLOAD_WRITE;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+void SSLClientSocket::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(user_callback_);
+
+ // since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(rv);
+}
+
+void SSLClientSocket::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int SSLClientSocket::DoLoop(int last_io_result) {
+ DCHECK(next_state_ != STATE_NONE);
+ int rv = last_io_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_CONNECT:
+ rv = DoConnect();
+ break;
+ case STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ case STATE_HANDSHAKE_READ:
+ rv = DoHandshakeRead();
+ break;
+ case STATE_HANDSHAKE_READ_COMPLETE:
+ rv = DoHandshakeReadComplete(rv);
+ break;
+ case STATE_HANDSHAKE_WRITE:
+ rv = DoHandshakeWrite();
+ break;
+ case STATE_HANDSHAKE_WRITE_COMPLETE:
+ rv = DoHandshakeWriteComplete(rv);
+ break;
+ case STATE_PAYLOAD_READ:
+ rv = DoPayloadRead();
+ break;
+ case STATE_PAYLOAD_READ_COMPLETE:
+ rv = DoPayloadReadComplete(rv);
+ break;
+ case STATE_PAYLOAD_WRITE:
+ rv = DoPayloadWrite();
+ break;
+ case STATE_PAYLOAD_WRITE_COMPLETE:
+ rv = DoPayloadWriteComplete(rv);
+ break;
+ default:
+ rv = ERR_FAILED;
+ NOTREACHED() << "unexpected state";
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+int SSLClientSocket::DoConnect() {
+ next_state_ = STATE_CONNECT_COMPLETE;
+ return transport_->Connect(&io_callback_);
+}
+
+int SSLClientSocket::DoConnectComplete(int result) {
+ if (result < 0)
+ return result;
+
+ memset(&ctxt_, 0, sizeof(ctxt_));
+ memset(&creds_, 0, sizeof(creds_));
+
+ SCHANNEL_CRED schannel_cred = {0};
+ schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
+ schannel_cred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS |
+ SCH_CRED_NO_SYSTEM_MAPPER |
+ SCH_CRED_REVOCATION_CHECK_CHAIN;
+ TimeStamp expiry;
+ SECURITY_STATUS status;
+
+ status = SChannel().AcquireCredentialsHandle(
+ NULL,
+ UNISP_NAME,
+ SECPKG_CRED_OUTBOUND,
+ NULL,
+ &schannel_cred,
+ NULL,
+ NULL,
+ &creds_,
+ &expiry);
+ if (status != SEC_E_OK) {
+ DLOG(ERROR) << "AcquireCredentialsHandle failed: " << status;
+ return ERR_FAILED;
+ }
+
+ SecBufferDesc buffer_desc;
+ DWORD out_flags;
+ DWORD flags = ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_REPLAY_DETECT |
+ ISC_REQ_CONFIDENTIALITY |
+ ISC_RET_EXTENDED_ERROR |
+ ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_STREAM;
+
+ send_buffer_.pvBuffer = NULL;
+ send_buffer_.BufferType = SECBUFFER_TOKEN;
+ send_buffer_.cbBuffer = 0;
+
+ buffer_desc.cBuffers = 1;
+ buffer_desc.pBuffers = &send_buffer_;
+ buffer_desc.ulVersion = SECBUFFER_VERSION;
+
+ status = SChannel().InitializeSecurityContext(
+ &creds_,
+ NULL,
+ const_cast<wchar_t*>(ASCIIToWide(hostname_).c_str()),
+ flags,
+ 0,
+ SECURITY_NATIVE_DREP,
+ NULL,
+ 0,
+ &ctxt_,
+ &buffer_desc,
+ &out_flags,
+ &expiry);
+ if (status != SEC_I_CONTINUE_NEEDED) {
+ DLOG(ERROR) << "InitializeSecurityContext failed: " << status;
+ return ERR_FAILED;
+ }
+
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ return OK;
+}
+
+int SSLClientSocket::DoHandshakeRead() {
+ next_state_ = STATE_HANDSHAKE_READ_COMPLETE;
+
+ if (!recv_buffer_.get())
+ recv_buffer_.reset(new char[kRecvBufferSize]);
+
+ char* buf = recv_buffer_.get() + bytes_received_;
+ int buf_len = kRecvBufferSize - bytes_received_;
+
+ if (buf_len <= 0) {
+ NOTREACHED() << "Receive buffer is too small!";
+ return ERR_FAILED;
+ }
+
+ return transport_->Read(buf, buf_len, &io_callback_);
+}
+
+int SSLClientSocket::DoHandshakeReadComplete(int result) {
+ if (result < 0)
+ return result;
+ if (result == 0)
+ return ERR_FAILED; // Incomplete response :(
+
+ bytes_received_ += result;
+
+ // Process the contents of recv_buffer_.
+ SECURITY_STATUS status;
+ TimeStamp expiry;
+ DWORD out_flags;
+
+ DWORD flags = ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_REPLAY_DETECT |
+ ISC_REQ_CONFIDENTIALITY |
+ ISC_RET_EXTENDED_ERROR |
+ ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_STREAM;
+
+ SecBufferDesc in_buffer_desc, out_buffer_desc;
+ SecBuffer in_buffers[2];
+
+ in_buffer_desc.cBuffers = 2;
+ in_buffer_desc.pBuffers = in_buffers;
+ in_buffer_desc.ulVersion = SECBUFFER_VERSION;
+
+ in_buffers[0].pvBuffer = &recv_buffer_[0];
+ in_buffers[0].cbBuffer = bytes_received_;
+ in_buffers[0].BufferType = SECBUFFER_TOKEN;
+
+ in_buffers[1].pvBuffer = NULL;
+ in_buffers[1].cbBuffer = 0;
+ in_buffers[1].BufferType = SECBUFFER_EMPTY;
+
+ out_buffer_desc.cBuffers = 1;
+ out_buffer_desc.pBuffers = &send_buffer_;
+ out_buffer_desc.ulVersion = SECBUFFER_VERSION;
+
+ send_buffer_.pvBuffer = NULL;
+ send_buffer_.BufferType = SECBUFFER_TOKEN;
+ send_buffer_.cbBuffer = 0;
+
+ status = SChannel().InitializeSecurityContext(
+ &creds_,
+ &ctxt_,
+ NULL,
+ flags,
+ 0,
+ SECURITY_NATIVE_DREP,
+ &in_buffer_desc,
+ 0,
+ NULL,
+ &out_buffer_desc,
+ &out_flags,
+ &expiry);
+
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ next_state_ = STATE_HANDSHAKE_READ;
+ return OK;
+ }
+
+ // OK, all of the received data was consumed.
+ bytes_received_ = 0;
+
+ if (send_buffer_.cbBuffer != 0 &&
+ (status == SEC_E_OK ||
+ status == SEC_I_CONTINUE_NEEDED ||
+ FAILED(status) && (out_flags & ISC_RET_EXTENDED_ERROR))) {
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ return OK;
+ }
+
+ if (status == SEC_E_OK) {
+ if (in_buffers[1].BufferType == SECBUFFER_EXTRA) {
+ // TODO(darin) need to save this data for later.
+ NOTREACHED() << "should not occur for HTTPS traffic";
+ }
+ return DidCompleteHandshake();
+ }
+
+ if (FAILED(status))
+ return ERR_FAILED;
+
+ next_state_ = STATE_HANDSHAKE_READ;
+ return OK;
+}
+
+int SSLClientSocket::DoHandshakeWrite() {
+ next_state_ = STATE_HANDSHAKE_WRITE_COMPLETE;
+
+ // We should have something to send.
+ DCHECK(send_buffer_.pvBuffer);
+ DCHECK(send_buffer_.cbBuffer > 0);
+
+ const char* buf = static_cast<char*>(send_buffer_.pvBuffer) + bytes_sent_;
+ int buf_len = send_buffer_.cbBuffer - bytes_sent_;
+
+ return transport_->Write(buf, buf_len, &io_callback_);
+}
+
+int SSLClientSocket::DoHandshakeWriteComplete(int result) {
+ if (result < 0)
+ return result;
+
+ DCHECK(result != 0);
+
+ // TODO(darin): worry about overflow?
+ bytes_sent_ += result;
+ DCHECK(bytes_sent_ <= static_cast<int>(send_buffer_.cbBuffer));
+
+ if (bytes_sent_ == static_cast<int>(send_buffer_.cbBuffer)) {
+ SChannel().FreeContextBuffer(send_buffer_.pvBuffer);
+ memset(&send_buffer_, 0, sizeof(send_buffer_));
+ bytes_sent_ = 0;
+ next_state_ = STATE_HANDSHAKE_READ;
+ } else {
+ // Send the remaining bytes.
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ }
+
+ return OK;
+}
+
+int SSLClientSocket::DoPayloadRead() {
+ next_state_ = STATE_PAYLOAD_READ_COMPLETE;
+
+ return ERR_FAILED;
+}
+
+int SSLClientSocket::DoPayloadReadComplete(int result) {
+ return ERR_FAILED;
+}
+
+int SSLClientSocket::DoPayloadWrite() {
+ DCHECK(user_buf_);
+ DCHECK(user_buf_len_ > 0);
+
+ next_state_ = STATE_PAYLOAD_WRITE_COMPLETE;
+
+ size_t message_len = std::min(
+ stream_sizes_.cbMaximumMessage, static_cast<ULONG>(user_buf_len_));
+ size_t alloc_len =
+ message_len + stream_sizes_.cbHeader + stream_sizes_.cbTrailer;
+
+ /*
+ SecBuffer buffers[4];
+ buffers[0].
+
+ SecBufferDesc buffer_desc;
+ buffer_desc.cBuffers = 4;
+ buffer_desc.pBuffers = //XXX
+ buffer_desc.ulVersion = SECBUFFER_VERSION;
+
+ SECURITY_STATUS status = SChannel().EncryptMessage(
+ &ctxt_, 0, &buffer_desc, 0);
+ */
+
+ return ERR_FAILED;
+}
+
+int SSLClientSocket::DoPayloadWriteComplete(int result) {
+ return ERR_FAILED;
+}
+
+int SSLClientSocket::DidCompleteHandshake() {
+ SECURITY_STATUS status = SChannel().QueryContextAttributes(
+ &ctxt_, SECPKG_ATTR_STREAM_SIZES, &stream_sizes_);
+ if (status != SEC_E_OK) {
+ DLOG(ERROR) << "QueryContextAttributes failed: " << status;
+ return ERR_FAILED;
+ }
+
+ // We expect not to have to worry about message padding.
+ DCHECK(stream_sizes_.cbBlockSize == 1);
+
+ completed_handshake_ = true;
+ return OK;
+}
+
+} // namespace net
diff --git a/net/base/ssl_client_socket.h b/net/base/ssl_client_socket.h
new file mode 100644
index 0000000..599b488
--- /dev/null
+++ b/net/base/ssl_client_socket.h
@@ -0,0 +1,125 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_SSL_CLIENT_SOCKET_H_
+#define NET_BASE_SSL_CLIENT_SOCKET_H_
+
+#define SECURITY_WIN32 // Needs to be defined before including security.h
+
+#include <windows.h>
+#include <security.h>
+
+#include "base/scoped_ptr.h"
+#include "net/base/client_socket.h"
+#include "net/base/completion_callback.h"
+
+namespace net {
+
+// 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. The consumer may choose to ignore certain SSL errors,
+// such as a name mismatch, by calling ReconnectIgnoringLastError.
+//
+class SSLClientSocket : public ClientSocket {
+ 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.
+ SSLClientSocket(ClientSocket* transport_socket, const std::string& hostname);
+ ~SSLClientSocket();
+
+ // ClientSocket methods:
+ virtual int Connect(CompletionCallback* callback);
+ virtual int ReconnectIgnoringLastError(CompletionCallback* callback);
+ virtual void Disconnect();
+ virtual bool IsConnected() const;
+
+ // Socket methods:
+ virtual int Read(char* buf, int buf_len, CompletionCallback* callback);
+ virtual int Write(const char* buf, int buf_len, CompletionCallback* callback);
+
+ private:
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ int DoLoop(int last_io_result);
+ int DoConnect();
+ int DoConnectComplete(int result);
+ int DoHandshakeRead();
+ int DoHandshakeReadComplete(int result);
+ int DoHandshakeWrite();
+ int DoHandshakeWriteComplete(int result);
+ int DoPayloadRead();
+ int DoPayloadReadComplete(int result);
+ int DoPayloadWrite();
+ int DoPayloadWriteComplete(int result);
+
+ int DidCompleteHandshake();
+
+ CompletionCallbackImpl<SSLClientSocket> io_callback_;
+ scoped_ptr<ClientSocket> transport_;
+ std::string hostname_;
+
+ CompletionCallback* user_callback_;
+
+ // Used by both Read and Write functions.
+ char* user_buf_;
+ int user_buf_len_;
+
+ enum State {
+ STATE_NONE,
+ STATE_CONNECT,
+ STATE_CONNECT_COMPLETE,
+ STATE_HANDSHAKE_READ,
+ STATE_HANDSHAKE_READ_COMPLETE,
+ STATE_HANDSHAKE_WRITE,
+ STATE_HANDSHAKE_WRITE_COMPLETE,
+ STATE_PAYLOAD_WRITE,
+ STATE_PAYLOAD_WRITE_COMPLETE,
+ STATE_PAYLOAD_READ,
+ STATE_PAYLOAD_READ_COMPLETE,
+ };
+ State next_state_;
+
+ SecPkgContext_StreamSizes stream_sizes_;
+
+ CredHandle creds_;
+ CtxtHandle ctxt_;
+ SecBuffer send_buffer_;
+ int bytes_sent_;
+
+ scoped_array<char> recv_buffer_;
+ int bytes_received_;
+
+ bool completed_handshake_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_SSL_CLIENT_SOCKET_H_
diff --git a/net/base/ssl_client_socket_unittest.cc b/net/base/ssl_client_socket_unittest.cc
new file mode 100644
index 0000000..a465563
--- /dev/null
+++ b/net/base/ssl_client_socket_unittest.cc
@@ -0,0 +1,190 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/host_resolver.h"
+#include "net/base/ssl_client_socket.h"
+#include "net/base/tcp_client_socket.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class SSLClientSocketTest : public testing::Test {
+};
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+TEST_F(SSLClientSocketTest, Connect) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ std::string hostname = "www.verisign.com";
+ int rv = resolver.Resolve(hostname, 443, &addr, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ net::SSLClientSocket sock(new net::TCPClientSocket(addr), hostname);
+
+ EXPECT_FALSE(sock.IsConnected());
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ EXPECT_TRUE(sock.IsConnected());
+
+ sock.Disconnect();
+ EXPECT_FALSE(sock.IsConnected());
+}
+
+#if 0
+TEST_F(SSLClientSocketTest, Read) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ std::string hostname = "www.google.com";
+ int rv = resolver.Resolve(hostname, 443, &addr, &callback);
+ EXPECT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ net::SSLClientSocket sock(new net::TCPClientSocket(addr), hostname);
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ rv = sock.Write(request_text, arraysize(request_text)-1, &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING) {
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, arraysize(request_text)-1);
+ }
+
+ char buf[4096];
+ for (;;) {
+ rv = sock.Read(buf, sizeof(buf), &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ if (rv == 0)
+ break;
+ }
+}
+
+TEST_F(TCPClientSocketTest, Read_SmallChunks) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ int rv = resolver.Resolve("www.google.com", 80, &addr, NULL);
+ EXPECT_EQ(rv, net::OK);
+
+ net::TCPClientSocket sock(addr);
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ rv = sock.Write(request_text, arraysize(request_text)-1, &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING) {
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, arraysize(request_text)-1);
+ }
+
+ char buf[1];
+ for (;;) {
+ rv = sock.Read(buf, sizeof(buf), &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ if (rv == 0)
+ break;
+ }
+}
+
+TEST_F(TCPClientSocketTest, Read_Interrupted) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ int rv = resolver.Resolve("www.google.com", 80, &addr, NULL);
+ EXPECT_EQ(rv, net::OK);
+
+ net::TCPClientSocket sock(addr);
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ rv = sock.Write(request_text, arraysize(request_text)-1, &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING) {
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, arraysize(request_text)-1);
+ }
+
+ // Do a partial read and then exit. This test should not crash!
+ char buf[512];
+ rv = sock.Read(buf, sizeof(buf), &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_TRUE(rv != 0);
+}
+#endif
diff --git a/net/base/ssl_config_service.cc b/net/base/ssl_config_service.cc
new file mode 100644
index 0000000..14dad5f
--- /dev/null
+++ b/net/base/ssl_config_service.cc
@@ -0,0 +1,129 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/ssl_config_service.h"
+
+#include "base/registry.h"
+
+namespace net {
+
+static const int kConfigUpdateInterval = 10; // seconds
+
+static const wchar_t kInternetSettingsSubKeyName[] =
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
+
+static const wchar_t kRevocationValueName[] = L"CertificateRevocation";
+
+static const wchar_t kProtocolsValueName[] = L"SecureProtocols";
+
+// In SecureProtocols, each SSL version is represented by a bit:
+// SSL 2.0: 0x08
+// SSL 3.0: 0x20
+// TLS 1.0: 0x80
+// The bits are OR'ed to form the DWORD value. So 0xa0 means SSL 3.0 and
+// TLS 1.0.
+enum {
+ SSL2 = 0x08,
+ SSL3 = 0x20,
+ TLS1 = 0x80
+};
+
+// If CertificateRevocation or SecureProtocols is missing, IE uses a default
+// value. Unfortunately the default is IE version specific. We use WinHTTP's
+// default.
+enum {
+ REVOCATION_DEFAULT = 0,
+ PROTOCOLS_DEFAULT = SSL3 | TLS1
+};
+
+SSLConfigService::SSLConfigService() {
+ UpdateConfig(TimeTicks::Now());
+}
+
+SSLConfigService::SSLConfigService(TimeTicks now) {
+ UpdateConfig(now);
+}
+
+void SSLConfigService::GetSSLConfigAt(SSLConfig* config, TimeTicks now) {
+ if (now - config_time_ > TimeDelta::FromSeconds(kConfigUpdateInterval))
+ UpdateConfig(now);
+ *config = config_info_;
+}
+
+// static
+bool SSLConfigService::GetSSLConfigNow(SSLConfig* config) {
+ RegKey internet_settings;
+ if (!internet_settings.Open(HKEY_CURRENT_USER, kInternetSettingsSubKeyName,
+ KEY_READ))
+ return false;
+
+ DWORD revocation;
+ if (!internet_settings.ReadValueDW(kRevocationValueName, &revocation))
+ revocation = REVOCATION_DEFAULT;
+
+ DWORD protocols;
+ if (!internet_settings.ReadValueDW(kProtocolsValueName, &protocols))
+ protocols = PROTOCOLS_DEFAULT;
+
+ config->rev_checking_enabled = (revocation != 0);
+ config->ssl2_enabled = ((protocols & SSL2) != 0);
+ config->ssl3_enabled = ((protocols & SSL3) != 0);
+ config->tls1_enabled = ((protocols & TLS1) != 0);
+
+ return true;
+}
+
+// static
+void SSLConfigService::SetRevCheckingEnabled(bool enabled) {
+ DWORD value = enabled;
+ RegKey internet_settings(HKEY_CURRENT_USER, kInternetSettingsSubKeyName,
+ KEY_WRITE);
+ internet_settings.WriteValue(kRevocationValueName, value);
+}
+
+// static
+void SSLConfigService::SetSSL2Enabled(bool enabled) {
+ RegKey internet_settings(HKEY_CURRENT_USER, kInternetSettingsSubKeyName,
+ KEY_READ | KEY_WRITE);
+ DWORD value;
+ if (!internet_settings.ReadValueDW(kProtocolsValueName, &value))
+ value = PROTOCOLS_DEFAULT;
+ if (enabled)
+ value |= SSL2;
+ else
+ value &= ~SSL2;
+ internet_settings.WriteValue(kProtocolsValueName, value);
+}
+
+void SSLConfigService::UpdateConfig(TimeTicks now) {
+ GetSSLConfigNow(&config_info_);
+ config_time_ = now;
+}
+
+} // namespace net
diff --git a/net/base/ssl_config_service.h b/net/base/ssl_config_service.h
new file mode 100644
index 0000000..e563f2d
--- /dev/null
+++ b/net/base/ssl_config_service.h
@@ -0,0 +1,96 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_SSL_CONFIG_SERVICE_H__
+#define NET_BASE_SSL_CONFIG_SERVICE_H__
+
+#include "base/time.h"
+
+namespace net {
+
+// A collection of SSL-related configuration settings.
+struct SSLConfig {
+ // Default to no revocation checking.
+ // Default to SSL 2.0 off, SSL 3.0 on, and TLS 1.0 on.
+ SSLConfig()
+ : rev_checking_enabled(false), ssl2_enabled(false),
+ ssl3_enabled(true), tls1_enabled(true) {
+ }
+
+ bool rev_checking_enabled; // True if server certificate revocation
+ // checking is enabled.
+ bool ssl2_enabled; // True if SSL 2.0 is enabled.
+ bool ssl3_enabled; // True if SSL 3.0 is enabled.
+ bool tls1_enabled; // True if TLS 1.0 is enabled.
+};
+
+// This class is responsible for getting and setting the SSL configuration.
+//
+// We think the SSL configuration settings should apply to all applications
+// used by the user. We consider IE's Internet Options as the de facto
+// system-wide network configuration settings, so we just use the values
+// from IE's Internet Settings registry key.
+class SSLConfigService {
+ public:
+ SSLConfigService();
+ explicit SSLConfigService(TimeTicks now); // Used for testing.
+ ~SSLConfigService() { }
+
+ // Get the current SSL configuration settings. Can be called on any
+ // thread.
+ static bool GetSSLConfigNow(SSLConfig* config);
+
+ // Setters. Can be called on any thread.
+ static void SetRevCheckingEnabled(bool enabled);
+ static void SetSSL2Enabled(bool enabled);
+
+ // Get the (cached) SSL configuration settings that are fresh within 10
+ // seconds. This is cheaper than GetSSLConfigNow and is suitable when
+ // we don't need the absolutely current configuration settings. This
+ // method is not thread-safe, so it must be called on the same thread.
+ void GetSSLConfig(SSLConfig* config) {
+ GetSSLConfigAt(config, TimeTicks::Now());
+ }
+
+ // Used for testing.
+ void GetSSLConfigAt(SSLConfig* config, TimeTicks now);
+
+ private:
+ void UpdateConfig(TimeTicks now);
+
+ // We store the IE SSL config and the time that we fetched it.
+ SSLConfig config_info_;
+ TimeTicks config_time_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SSLConfigService);
+};
+
+} // namespace net
+
+#endif // NET_BASE_SSL_CONFIG_SERVICE_H__
diff --git a/net/base/ssl_config_service_unittest.cc b/net/base/ssl_config_service_unittest.cc
new file mode 100644
index 0000000..d16085b
--- /dev/null
+++ b/net/base/ssl_config_service_unittest.cc
@@ -0,0 +1,108 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/ssl_config_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class SSLConfigServiceTest : public testing::Test {
+};
+
+} // namespace
+
+TEST(SSLConfigServiceTest, GetNowTest) {
+ // Verify that the constructor sets the correct default values.
+ net::SSLConfig config;
+ EXPECT_EQ(false, config.rev_checking_enabled);
+ EXPECT_EQ(false, config.ssl2_enabled);
+ EXPECT_EQ(true, config.ssl3_enabled);
+ EXPECT_EQ(true, config.tls1_enabled);
+
+ bool rv = net::SSLConfigService::GetSSLConfigNow(&config);
+ EXPECT_TRUE(rv);
+}
+
+TEST(SSLConfigServiceTest, SetTest) {
+ // Save the current settings so we can restore them after the tests.
+ net::SSLConfig config_save;
+ bool rv = net::SSLConfigService::GetSSLConfigNow(&config_save);
+ EXPECT_TRUE(rv);
+
+ net::SSLConfig config;
+
+ // Test SetRevCheckingEnabled.
+ net::SSLConfigService::SetRevCheckingEnabled(true);
+ rv = net::SSLConfigService::GetSSLConfigNow(&config);
+ EXPECT_TRUE(rv);
+ EXPECT_TRUE(config.rev_checking_enabled);
+
+ net::SSLConfigService::SetRevCheckingEnabled(false);
+ rv = net::SSLConfigService::GetSSLConfigNow(&config);
+ EXPECT_TRUE(rv);
+ EXPECT_FALSE(config.rev_checking_enabled);
+
+ net::SSLConfigService::SetRevCheckingEnabled(
+ config_save.rev_checking_enabled);
+
+ // Test SetSSL2Enabled.
+ net::SSLConfigService::SetSSL2Enabled(true);
+ rv = net::SSLConfigService::GetSSLConfigNow(&config);
+ EXPECT_TRUE(rv);
+ EXPECT_TRUE(config.ssl2_enabled);
+
+ net::SSLConfigService::SetSSL2Enabled(false);
+ rv = net::SSLConfigService::GetSSLConfigNow(&config);
+ EXPECT_TRUE(rv);
+ EXPECT_FALSE(config.ssl2_enabled);
+
+ net::SSLConfigService::SetSSL2Enabled(config_save.ssl2_enabled);
+}
+
+TEST(SSLConfigServiceTest, GetTest) {
+ TimeTicks now = TimeTicks::Now();
+ TimeTicks now_1 = now + TimeDelta::FromSeconds(1);
+ TimeTicks now_11 = now + TimeDelta::FromSeconds(11);
+
+ net::SSLConfig config, config_1, config_11;
+ net::SSLConfigService config_service(now);
+ config_service.GetSSLConfigAt(&config, now);
+
+ // Flip rev_checking_enabled.
+ net::SSLConfigService::SetRevCheckingEnabled(!config.rev_checking_enabled);
+
+ config_service.GetSSLConfigAt(&config_1, now_1);
+ EXPECT_EQ(config.rev_checking_enabled, config_1.rev_checking_enabled);
+
+ config_service.GetSSLConfigAt(&config_11, now_11);
+ EXPECT_EQ(!config.rev_checking_enabled, config_11.rev_checking_enabled);
+
+ // Restore the original value.
+ net::SSLConfigService::SetRevCheckingEnabled(config.rev_checking_enabled);
+}
diff --git a/net/base/ssl_info.h b/net/base/ssl_info.h
new file mode 100644
index 0000000..07655eb
--- /dev/null
+++ b/net/base/ssl_info.h
@@ -0,0 +1,102 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_SSL_INFO_H__
+#define NET_BASE_SSL_INFO_H__
+
+#include "net/base/cert_status_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/x509_certificate.h"
+
+namespace net {
+
+// SSL connection info.
+// This is really a struct. All members are public.
+class SSLInfo {
+ public:
+ SSLInfo() : cert_status(0), security_bits(-1) { }
+
+ void Reset() {
+ cert = NULL;
+ security_bits = -1;
+ cert_status = 0;
+ }
+
+ bool is_valid() const { return cert != NULL; }
+
+ // Adds the specified |error| to the cert status.
+ void SetCertError(int error) {
+ int error_flag = 0;
+ switch (error) {
+ case ERR_CERT_COMMON_NAME_INVALID:
+ error_flag = CERT_STATUS_COMMON_NAME_INVALID;
+ break;
+ case ERR_CERT_DATE_INVALID:
+ error_flag = CERT_STATUS_DATE_INVALID;
+ break;
+ case ERR_CERT_AUTHORITY_INVALID:
+ error_flag = CERT_STATUS_AUTHORITY_INVALID;
+ break;
+ case ERR_CERT_NO_REVOCATION_MECHANISM:
+ error_flag = CERT_STATUS_NO_REVOCATION_MECHANISM;
+ break;
+ case ERR_CERT_UNABLE_TO_CHECK_REVOCATION:
+ error_flag = CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+ break;
+ case ERR_CERT_REVOKED:
+ error_flag = CERT_STATUS_REVOKED;
+ break;
+ case ERR_CERT_CONTAINS_ERRORS:
+ case ERR_CERT_INVALID:
+ error_flag = CERT_STATUS_INVALID;
+ break;
+ default:
+ NOTREACHED();
+ return;
+ }
+ cert_status |= error_flag;
+ }
+
+ // 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.
+ int 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;
+};
+
+} // namespace net
+
+#endif // NET_BASE_SSL_INFO_H__
diff --git a/net/base/tcp_client_socket.cc b/net/base/tcp_client_socket.cc
new file mode 100644
index 0000000..fad4f2f0
--- /dev/null
+++ b/net/base/tcp_client_socket.cc
@@ -0,0 +1,281 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/tcp_client_socket.h"
+
+#include "net/base/net_errors.h"
+#include "net/base/winsock_init.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+static int MapWinsockError(DWORD err) {
+ // There are numerous Winsock error codes, but these are the ones we thus far
+ // find interesting.
+ switch (err) {
+ case WSAENETDOWN:
+ return ERR_INTERNET_DISCONNECTED;
+ case WSAETIMEDOUT:
+ return ERR_TIMED_OUT;
+ case WSAECONNRESET:
+ case WSAENETRESET:
+ return ERR_CONNECTION_RESET;
+ case WSAECONNABORTED:
+ return ERR_CONNECTION_ABORTED;
+ case WSAECONNREFUSED:
+ return ERR_CONNECTION_REFUSED;
+ case WSAEDISCON:
+ return ERR_CONNECTION_CLOSED;
+ case WSAEHOSTUNREACH:
+ case WSAENETUNREACH:
+ return ERR_ADDRESS_UNREACHABLE;
+ case WSAEADDRNOTAVAIL:
+ return ERR_ADDRESS_INVALID;
+ case ERROR_SUCCESS:
+ return OK;
+ default:
+ return ERR_FAILED;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+TCPClientSocket::TCPClientSocket(const AddressList& addresses)
+ : socket_(INVALID_SOCKET),
+ addresses_(addresses),
+ current_ai_(addresses_.head()),
+ wait_state_(NOT_WAITING) {
+ memset(&overlapped_, 0, sizeof(overlapped_));
+ EnsureWinsockInit();
+}
+
+TCPClientSocket::~TCPClientSocket() {
+ Disconnect();
+}
+
+int TCPClientSocket::Connect(CompletionCallback* callback) {
+ // If already connected, then just return OK.
+ if (socket_ != INVALID_SOCKET)
+ return OK;
+
+ const struct addrinfo* ai = current_ai_;
+ DCHECK(ai);
+
+ int rv = CreateSocket(ai);
+ if (rv != OK)
+ return rv;
+
+ if (!connect(socket_, ai->ai_addr, static_cast<int>(ai->ai_addrlen))) {
+ // Connected without waiting!
+ return OK;
+ }
+
+ DWORD err = WSAGetLastError();
+ if (err != WSAEWOULDBLOCK) {
+ LOG(ERROR) << "connect failed: " << err;
+ return MapWinsockError(err);
+ }
+
+ overlapped_.hEvent = WSACreateEvent();
+ WSAEventSelect(socket_, overlapped_.hEvent, FD_CONNECT);
+
+ MessageLoop::current()->WatchObject(overlapped_.hEvent, this);
+ wait_state_ = WAITING_CONNECT;
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int TCPClientSocket::ReconnectIgnoringLastError(CompletionCallback* callback) {
+ // No ignorable errors!
+ return ERR_FAILED;
+}
+
+void TCPClientSocket::Disconnect() {
+ if (socket_ == INVALID_SOCKET)
+ return;
+
+ // Make sure the message loop is not watching this object anymore.
+ MessageLoop::current()->WatchObject(overlapped_.hEvent, NULL);
+
+ // This cancels any pending IO.
+ closesocket(socket_);
+ socket_ = INVALID_SOCKET;
+
+ WSACloseEvent(overlapped_.hEvent);
+ overlapped_.hEvent = NULL;
+
+ // Reset for next time.
+ current_ai_ = addresses_.head();
+}
+
+bool TCPClientSocket::IsConnected() const {
+ if (socket_ == INVALID_SOCKET || wait_state_ == WAITING_CONNECT)
+ return false;
+
+ // 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;
+}
+
+int TCPClientSocket::Read(char* buf, int buf_len, CompletionCallback* callback) {
+ DCHECK(socket_ != INVALID_SOCKET);
+ DCHECK(wait_state_ == NOT_WAITING);
+ DCHECK(!callback_);
+
+ buffer_.len = buf_len;
+ buffer_.buf = buf;
+
+ DWORD num, flags = 0;
+ int rv = WSARecv(socket_, &buffer_, 1, &num, &flags, &overlapped_, NULL);
+ if (rv == 0)
+ return static_cast<int>(num);
+ if (rv == SOCKET_ERROR && WSAGetLastError() == WSA_IO_PENDING) {
+ MessageLoop::current()->WatchObject(overlapped_.hEvent, this);
+ wait_state_ = WAITING_READ;
+ callback_ = callback;
+ return ERR_IO_PENDING;
+ }
+ return MapWinsockError(WSAGetLastError());
+}
+
+int TCPClientSocket::Write(const char* buf, int buf_len, CompletionCallback* callback) {
+ DCHECK(socket_ != INVALID_SOCKET);
+ DCHECK(wait_state_ == NOT_WAITING);
+ DCHECK(!callback_);
+
+ buffer_.len = buf_len;
+ buffer_.buf = const_cast<char*>(buf);
+
+ DWORD num;
+ int rv = WSASend(socket_, &buffer_, 1, &num, 0, &overlapped_, NULL);
+ if (rv == 0)
+ return static_cast<int>(num);
+ if (rv == SOCKET_ERROR && WSAGetLastError() == WSA_IO_PENDING) {
+ MessageLoop::current()->WatchObject(overlapped_.hEvent, this);
+ wait_state_ = WAITING_WRITE;
+ callback_ = callback;
+ return ERR_IO_PENDING;
+ }
+ return MapWinsockError(WSAGetLastError());
+}
+
+int TCPClientSocket::CreateSocket(const struct addrinfo* ai) {
+ socket_ = WSASocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, NULL, 0,
+ WSA_FLAG_OVERLAPPED);
+ if (socket_ == INVALID_SOCKET) {
+ LOG(ERROR) << "WSASocket failed: " << WSAGetLastError();
+ return ERR_FAILED;
+ }
+
+ // Configure non-blocking mode.
+ u_long non_blocking_mode = 1;
+ if (ioctlsocket(socket_, FIONBIO, &non_blocking_mode)) {
+ LOG(ERROR) << "ioctlsocket failed: " << WSAGetLastError();
+ return ERR_FAILED;
+ }
+
+ return OK;
+}
+
+void TCPClientSocket::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(callback_);
+
+ // since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback* c = callback_;
+ callback_ = NULL;
+ c->Run(rv);
+}
+
+void TCPClientSocket::DidCompleteConnect() {
+ int result;
+
+ wait_state_ = NOT_WAITING;
+
+ WSANETWORKEVENTS events;
+ WSAEnumNetworkEvents(socket_, overlapped_.hEvent, &events);
+ if (events.lNetworkEvents & FD_CONNECT) {
+ wait_state_ = NOT_WAITING;
+ DWORD error_code = static_cast<DWORD>(events.iErrorCode[FD_CONNECT_BIT]);
+ if (current_ai_->ai_next && (
+ error_code == WSAEADDRNOTAVAIL ||
+ error_code == WSAEAFNOSUPPORT ||
+ error_code == WSAECONNREFUSED ||
+ error_code == WSAENETUNREACH ||
+ error_code == WSAEHOSTUNREACH ||
+ error_code == WSAETIMEDOUT)) {
+ // Try using the next address.
+ const struct addrinfo* next = current_ai_->ai_next;
+ Disconnect();
+ current_ai_ = next;
+ result = Connect(callback_);
+ } else {
+ result = MapWinsockError(error_code);
+ }
+ } else {
+ NOTREACHED();
+ result = ERR_FAILED;
+ }
+
+ if (result != ERR_IO_PENDING)
+ DoCallback(result);
+}
+
+void TCPClientSocket::DidCompleteIO() {
+ DWORD num_bytes, flags;
+ BOOL ok = WSAGetOverlappedResult(
+ socket_, &overlapped_, &num_bytes, FALSE, &flags);
+ wait_state_ = NOT_WAITING;
+ DoCallback(ok ? num_bytes : MapWinsockError(WSAGetLastError()));
+}
+
+void TCPClientSocket::OnObjectSignaled(HANDLE object) {
+ DCHECK(object == overlapped_.hEvent);
+
+ MessageLoop::current()->WatchObject(overlapped_.hEvent, NULL);
+
+ switch (wait_state_) {
+ case WAITING_CONNECT:
+ DidCompleteConnect();
+ break;
+ case WAITING_READ:
+ case WAITING_WRITE:
+ DidCompleteIO();
+ break;
+ }
+}
+
+} // namespace net
diff --git a/net/base/tcp_client_socket.h b/net/base/tcp_client_socket.h
new file mode 100644
index 0000000..1ed37a7
--- /dev/null
+++ b/net/base/tcp_client_socket.h
@@ -0,0 +1,92 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_TCP_CLIENT_SOCKET_H_
+#define NET_BASE_TCP_CLIENT_SOCKET_H_
+
+#include <ws2tcpip.h>
+
+#include "base/message_loop.h"
+#include "net/base/address_list.h"
+#include "net/base/client_socket.h"
+
+namespace net {
+
+class TCPClientSocket : public ClientSocket, public MessageLoop::Watcher {
+ 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.
+ TCPClientSocket(const AddressList& addresses);
+
+ ~TCPClientSocket();
+
+ // ClientSocket methods:
+ virtual int Connect(CompletionCallback* callback);
+ virtual int ReconnectIgnoringLastError(CompletionCallback* callback);
+ virtual void Disconnect();
+ virtual bool IsConnected() const;
+
+ // Socket methods:
+ virtual int Read(char* buf, int buf_len, CompletionCallback* callback);
+ virtual int Write(const char* buf, int buf_len, CompletionCallback* callback);
+
+ private:
+ int CreateSocket(const struct addrinfo* ai);
+ void DoCallback(int rv);
+ void DidCompleteConnect();
+ void DidCompleteIO();
+
+ virtual void OnObjectSignaled(HANDLE object);
+
+ SOCKET socket_;
+ OVERLAPPED overlapped_;
+ WSABUF buffer_;
+
+ CompletionCallback* callback_;
+
+ // Stored outside of the context so we can both lazily construct the context
+ // as well as construct a new one if Connect is called after Close.
+ AddressList addresses_;
+
+ // The addrinfo that we are attempting to use or NULL if uninitialized.
+ const struct addrinfo* current_ai_;
+
+ enum WaitState {
+ NOT_WAITING,
+ WAITING_CONNECT,
+ WAITING_READ,
+ WAITING_WRITE
+ };
+ WaitState wait_state_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_TCP_CLIENT_SOCKET_H_
diff --git a/net/base/tcp_client_socket_unittest.cc b/net/base/tcp_client_socket_unittest.cc
new file mode 100644
index 0000000..b0de697
--- /dev/null
+++ b/net/base/tcp_client_socket_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/host_resolver.h"
+#include "net/base/tcp_client_socket.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class TCPClientSocketTest : public testing::Test {
+};
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+TEST_F(TCPClientSocketTest, Connect) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ int rv = resolver.Resolve("www.google.com", 80, &addr, NULL);
+ EXPECT_EQ(rv, net::OK);
+
+ net::TCPClientSocket sock(addr);
+
+ EXPECT_FALSE(sock.IsConnected());
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ EXPECT_TRUE(sock.IsConnected());
+
+ sock.Disconnect();
+ EXPECT_FALSE(sock.IsConnected());
+}
+
+TEST_F(TCPClientSocketTest, Read) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ int rv = resolver.Resolve("www.google.com", 80, &addr, &callback);
+ EXPECT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ net::TCPClientSocket sock(addr);
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ rv = sock.Write(request_text, arraysize(request_text)-1, &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING) {
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, arraysize(request_text)-1);
+ }
+
+ char buf[4096];
+ for (;;) {
+ rv = sock.Read(buf, sizeof(buf), &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ if (rv == 0)
+ break;
+ }
+}
+
+TEST_F(TCPClientSocketTest, Read_SmallChunks) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ int rv = resolver.Resolve("www.google.com", 80, &addr, NULL);
+ EXPECT_EQ(rv, net::OK);
+
+ net::TCPClientSocket sock(addr);
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ rv = sock.Write(request_text, arraysize(request_text)-1, &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING) {
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, arraysize(request_text)-1);
+ }
+
+ char buf[1];
+ for (;;) {
+ rv = sock.Read(buf, sizeof(buf), &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ if (rv == 0)
+ break;
+ }
+}
+
+TEST_F(TCPClientSocketTest, Read_Interrupted) {
+ net::AddressList addr;
+ net::HostResolver resolver;
+ TestCompletionCallback callback;
+
+ int rv = resolver.Resolve("www.google.com", 80, &addr, NULL);
+ EXPECT_EQ(rv, net::OK);
+
+ net::TCPClientSocket sock(addr);
+
+ rv = sock.Connect(&callback);
+ ASSERT_EQ(rv, net::ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, net::OK);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ rv = sock.Write(request_text, arraysize(request_text)-1, &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING) {
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, arraysize(request_text)-1);
+ }
+
+ // Do a partial read and then exit. This test should not crash!
+ char buf[512];
+ rv = sock.Read(buf, sizeof(buf), &callback);
+ EXPECT_TRUE(rv >= 0 || rv == net::ERR_IO_PENDING);
+
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_TRUE(rv != 0);
+}
diff --git a/net/base/telnet_server.cc b/net/base/telnet_server.cc
new file mode 100644
index 0000000..75d9361
--- /dev/null
+++ b/net/base/telnet_server.cc
@@ -0,0 +1,275 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// winsock2.h must be included first in order to ensure it is included before
+// windows.h.
+#include <winsock2.h>
+
+#include "net/base/telnet_server.h"
+
+#define READ_BUF_SIZE 200
+
+// Telnet protocol constants.
+class TelnetProtocol {
+ public:
+ // Telnet command definitions (from arpa/telnet.h).
+ enum Commands {
+ IAC = 255, // Interpret as command.
+ DONT = 254, // You are not to use option.
+ DO = 253, // Please, you use option.
+ WONT = 252, // I won't use option.
+ WILL = 251, // I will use option.
+ SB = 250, // Interpret as subnegotiation.
+ GA = 249, // You may reverse the line.
+ EL = 248, // Erase the current line.
+ EC = 247, // Erase the current character.
+ AYT = 246, // Are you there.
+ AO = 245, // Abort output--but let prog finish.
+ IP = 244, // Interrupt process--permanently.
+ BREAK = 243, // Break.
+ DM = 242, // Data mark--for connect. cleaning.
+ NOP = 241, // Nop.
+ SE = 240, // End sub negotiation.
+ EOR = 239, // End of record (transparent mode).
+ ABORT = 238, // Abort process.
+ SUSP = 237, // Suspend process.
+ XEOF = 236 // End of file: EOF is already used...
+ };
+
+ // Telnet options (from arpa/telnet.h).
+ enum Options {
+ BINARY = 0, // 8-bit data path.
+ ECHO = 1, // Echo.
+ SGA = 3, // Suppress go ahead.
+ NAWS = 31, // Window size.
+ LFLOW = 33 // Remote flow control.
+ };
+
+ // Fixed character definitions mentioned in RFC 854.
+ enum Characters {
+ NUL = 0x00,
+ LF = 0x0A,
+ CR = 0x0D,
+ BELL = 0x07,
+ BS = 0x08,
+ HT = 0x09,
+ VT = 0x0B,
+ FF = 0x0C,
+ DEL = 0x7F,
+ ESC = 0x1B
+ };
+};
+
+
+///////////////////////
+
+// must run in the IO thread
+TelnetServer::TelnetServer(SOCKET s, ListenSocketDelegate* del, MessageLoop *l)
+ : ListenSocket(s, del, l) {
+ input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
+}
+
+// must run in the IO thread
+TelnetServer::~TelnetServer() {
+}
+
+void TelnetServer::SendIAC(int command, int option) {
+ char data[3];
+ data[0] = static_cast<unsigned char>(TelnetProtocol::IAC);
+ data[1] = static_cast<unsigned char>(command);
+ data[2] = option;
+ Send(data, 3);
+}
+
+// always fixup \n to \r\n
+void TelnetServer::SendInternal(const char* data, int len) {
+ int begin_index = 0;
+ for (int i = 0; i < len; i++) {
+ if (data[i] == TelnetProtocol::LF) {
+ // Send CR before LF if missing before.
+ if (i == 0 || data[i - 1] != TelnetProtocol::CR) {
+ // Send til before LF.
+ ListenSocket::SendInternal(data + begin_index, i - begin_index);
+ // Send CRLF.
+ ListenSocket::SendInternal("\r\n", 2);
+ // Continue after LF.
+ begin_index = i + 1;
+ }
+ }
+ }
+ // Send what is left (the whole string is sent here if CRLF was ok)
+ ListenSocket::SendInternal(data + begin_index, len - begin_index);
+}
+
+void TelnetServer::Accept() {
+ SOCKET conn = ListenSocket::Accept(socket_);
+ if (conn == INVALID_SOCKET) {
+ // TODO
+ } else {
+ scoped_refptr<TelnetServer> sock =
+ new TelnetServer(conn, socket_delegate_, loop_);
+
+ // Setup the way we want to communicate
+ sock->SendIAC(TelnetProtocol::DO, TelnetProtocol::ECHO);
+ sock->SendIAC(TelnetProtocol::DO, TelnetProtocol::NAWS);
+ sock->SendIAC(TelnetProtocol::DO, TelnetProtocol::LFLOW);
+ sock->SendIAC(TelnetProtocol::WILL, TelnetProtocol::ECHO);
+ sock->SendIAC(TelnetProtocol::WILL, TelnetProtocol::SGA);
+
+ // it's up to the delegate to AddRef if it wants to keep it around
+ socket_delegate_->DidAccept(this, sock);
+ }
+}
+
+TelnetServer* TelnetServer::Listen(std::string ip, int port,
+ ListenSocketDelegate *del, MessageLoop* l) {
+ SOCKET s = ListenSocket::Listen(ip, port);
+ if (s == INVALID_SOCKET) {
+ // TODO
+ } else {
+ TelnetServer *serv = new TelnetServer(s, del, l);
+ serv->Listen();
+ return serv;
+ }
+ return NULL;
+}
+
+void TelnetServer::StateMachineStep(unsigned char c) {
+ switch (input_state_) {
+ case NOT_IN_IAC_OR_ESC_SEQUENCE:
+ if (c == TelnetProtocol::IAC) {
+ // Expect IAC command
+ input_state_ = EXPECTING_COMMAND;
+ } else if (c == TelnetProtocol::ESC) {
+ // Expect left suare bracket
+ input_state_ = EXPECTING_FIRST_ESC_CHARACTER;
+ } else {
+ char data[1];
+ data[0] = c;
+ // handle backspace specially
+ if (c == TelnetProtocol::DEL) {
+ if (!command_line_.empty()) {
+ command_line_.erase(--command_line_.end());
+ Send(data, 1);
+ }
+ } else {
+ // Collect command
+ if (c >= ' ')
+ command_line_ += c;
+ // Echo character to client (for now ignore control characters).
+ if (c >= ' ' || c == TelnetProtocol::CR) {
+ Send(data, 1);
+ }
+ // Check for line termination
+ if (c == TelnetProtocol::CR)
+ input_state_ = EXPECTING_NEW_LINE;
+ }
+ }
+ break;
+ case EXPECTING_NEW_LINE:
+ if (c == TelnetProtocol::LF) {
+ Send("\n", 1);
+ socket_delegate_->DidRead(this, command_line_);
+ command_line_ = "";
+ }
+ input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
+ break;
+ case EXPECTING_COMMAND:
+ // Read command, expect option.
+ iac_command_ = c;
+ input_state_ = EXPECTING_OPTION;
+ break;
+ case EXPECTING_OPTION:
+ // Read option
+ iac_option_ = c;
+ // check for subnegoating if not done reading IAC.
+ if (iac_command_ != TelnetProtocol::SB) {
+ input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
+ } else {
+ input_state_ = SUBNEGOTIATION_EXPECTING_IAC;
+ }
+ break;
+ case SUBNEGOTIATION_EXPECTING_IAC:
+ // Currently ignore content of subnegotiation.
+ if (c == TelnetProtocol::IAC)
+ input_state_ = SUBNEGOTIATION_EXPECTING_SE;
+ break;
+ case SUBNEGOTIATION_EXPECTING_SE:
+ // Character must be SE and subnegotiation is finished.
+ input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
+ break;
+ case EXPECTING_FIRST_ESC_CHARACTER:
+ if (c == '[') {
+ // Expect ESC sequence content.
+ input_state_ = EXPECTING_NUMBER_SEMICOLON_OR_END;
+ } else if (c == 'O') {
+ // VT100 "ESC O" sequence.
+ input_state_ = EXPECTING_SECOND_ESC_CHARACTER;
+ } else {
+ // Unknown ESC sequence - ignore.
+ }
+ break;
+ case EXPECTING_SECOND_ESC_CHARACTER:
+ // Ignore ESC sequence content for now.
+ input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
+ break;
+ case EXPECTING_NUMBER_SEMICOLON_OR_END:
+ if (isdigit(c) || c ==';') {
+ // Ignore ESC sequence content for now.
+ } else {
+ // Final character in ESC sequence.
+ input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
+ }
+ break;
+ }
+}
+
+void TelnetServer::Read() {
+ char buf[READ_BUF_SIZE];
+ int len;
+ do {
+ len = recv(socket_, buf, READ_BUF_SIZE, 0);
+ if (len == SOCKET_ERROR) {
+ int err = WSAGetLastError();
+ if (err == WSAEWOULDBLOCK) {
+ break;
+ } else {
+ // TODO - error
+ break;
+ }
+ } else {
+ const char *data = buf;
+ for (int i = 0; i < len; ++i) {
+ unsigned char c = static_cast<unsigned char>(*data);
+ StateMachineStep(c);
+ data++;
+ }
+ }
+ } while (len == READ_BUF_SIZE);
+}
diff --git a/net/base/telnet_server.h b/net/base/telnet_server.h
new file mode 100644
index 0000000..8b509be
--- /dev/null
+++ b/net/base/telnet_server.h
@@ -0,0 +1,78 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_TELNET_SERVER_H_
+#define NET_BASE_TELNET_SERVER_H_
+
+#include "net/base/listen_socket.h"
+
+// Implements the telnet protocol on top of the raw socket interface.
+// DidRead calls to the delegate are buffered on a line by line basis.
+// (for now this means that basic line editing is handled in this object)
+class TelnetServer : public ListenSocket {
+public:
+ static TelnetServer* Listen(std::string ip, int port,
+ ListenSocketDelegate *del,
+ MessageLoop* loop);
+ virtual ~TelnetServer();
+
+protected:
+ void Listen() { ListenSocket::Listen(); }
+ virtual void Read();
+ virtual void Accept();
+ virtual void SendInternal(const char* bytes, int len);
+
+private:
+ enum TelnetInputState {
+ NOT_IN_IAC_OR_ESC_SEQUENCE, // Currently not processing any IAC or ESC sequence.
+ EXPECTING_NEW_LINE, // Received carriage return (CR) expecting new line (LF).
+ EXPECTING_COMMAND, // Processing IAC expecting command.
+ EXPECTING_OPTION, // Processing IAC expecting option.
+ SUBNEGOTIATION_EXPECTING_IAC, // Inside subnegoation IAC,SE will end it.
+ SUBNEGOTIATION_EXPECTING_SE, // Ending subnegoation expecting SE.
+ EXPECTING_FIRST_ESC_CHARACTER, // Processing ESC sequence.
+ EXPECTING_SECOND_ESC_CHARACTER, // Processing ESC sequence with two characters
+ EXPECTING_NUMBER_SEMICOLON_OR_END // Processing "ESC [" sequence.
+ };
+
+ TelnetServer(SOCKET s, ListenSocketDelegate* del, MessageLoop* loop);
+
+ // telnet commands
+ void SendIAC(int command, int option);
+ void StateMachineStep(unsigned char c);
+
+ TelnetInputState input_state_;
+ int iac_command_; // Last command read.
+ int iac_option_; // Last option read.
+ std::string command_line_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TelnetServer);
+};
+
+#endif // BASE_TELNET_SERVER_H_ \ No newline at end of file
diff --git a/net/base/telnet_server_unittest.cc b/net/base/telnet_server_unittest.cc
new file mode 100644
index 0000000..2ec1164
--- /dev/null
+++ b/net/base/telnet_server_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Tests TelnetServer.
+
+#include "net/base/listen_socket_unittest.h"
+#include "net/base/telnet_server.h"
+
+namespace {
+
+const std::string CRLF("\r\n");
+
+class TelnetServerTester : public ListenSocketTester {
+public:
+ virtual ListenSocket* DoListen() {
+ return TelnetServer::Listen("127.0.0.1", TEST_PORT, this, loop_);
+ }
+
+ virtual void SetUp() {
+ ListenSocketTester::SetUp();
+ // With TelnetServer, there's some control codes sent at connect time,
+ // so we need to eat those to avoid affecting the subsequent tests.
+ // TODO(erikkay): Unfortunately, without the sleep, we don't seem to
+ // reliably get the 15 bytes without an EWOULDBLOCK. It would be nice if
+ // there were a more reliable mechanism here.
+ Sleep(10);
+ ASSERT_EQ(ClearTestSocket(), 15);
+ }
+
+ virtual bool Send(SOCKET sock, const std::string& str) {
+ if (ListenSocketTester::Send(sock, str)) {
+ // TelnetServer currently calls DidRead after a CRLF, so we need to
+ // append one to the end of the data that we send.
+ if (ListenSocketTester::Send(sock, CRLF)) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+class TelnetServerTest: public testing::Test {
+protected:
+ TelnetServerTest() {
+ tester_ = NULL;
+ }
+
+ virtual void SetUp() {
+ tester_ = new TelnetServerTester();
+ tester_->SetUp();
+ }
+
+ virtual void TearDown() {
+ tester_->TearDown();
+ tester_ = NULL;
+ }
+
+ scoped_refptr<TelnetServerTester> tester_;
+};
+
+} // namespace
+
+TEST_F(TelnetServerTest, ServerClientSend) {
+ tester_->TestClientSend();
+}
+
+TEST_F(TelnetServerTest, ClientSendLong) {
+ tester_->TestClientSendLong();
+}
+
+TEST_F(TelnetServerTest, ServerSend) {
+ tester_->TestServerSend();
+}
diff --git a/net/base/test_completion_callback.h b/net/base/test_completion_callback.h
new file mode 100644
index 0000000..7d1f8a2
--- /dev/null
+++ b/net/base/test_completion_callback.h
@@ -0,0 +1,79 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_TEST_COMPLETION_CALLBACK_H_
+#define NET_BASE_TEST_COMPLETION_CALLBACK_H_
+
+#include "base/message_loop.h"
+#include "net/base/completion_callback.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.
+//
+class TestCompletionCallback : public CallbackRunner< Tuple1<int> > {
+ public:
+ TestCompletionCallback()
+ : result_(0),
+ have_result_(false),
+ waiting_for_result_(false) {
+ }
+
+ int WaitForResult() {
+ DCHECK(!waiting_for_result_);
+ while (!have_result_) {
+ waiting_for_result_ = true;
+ MessageLoop::current()->Run();
+ waiting_for_result_ = false;
+ }
+ have_result_ = false; // auto-reset for next callback
+ return result_;
+ }
+
+ private:
+ virtual void RunWithParams(const Tuple1<int>& params) {
+ result_ = params.a;
+ have_result_ = true;
+ if (waiting_for_result_)
+ MessageLoop::current()->Quit();
+ }
+
+ int result_;
+ bool have_result_;
+ bool waiting_for_result_;
+};
+
+#endif // NET_BASE_TEST_COMPLETION_CALLBACK_H_
diff --git a/net/base/upload_data.cc b/net/base/upload_data.cc
new file mode 100644
index 0000000..ec15435
--- /dev/null
+++ b/net/base/upload_data.cc
@@ -0,0 +1,71 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <windows.h>
+
+#include "net/base/upload_data.h"
+
+namespace net {
+
+uint64 UploadData::GetContentLength() const {
+ uint64 len = 0;
+ std::vector<Element>::const_iterator it = elements_.begin();
+ for (; it != elements_.end(); ++it)
+ len += (*it).GetContentLength();
+ return len;
+}
+
+uint64 UploadData::Element::GetContentLength() const {
+ if (type_ == TYPE_BYTES)
+ return static_cast<uint64>(bytes_.size());
+
+ DCHECK(type_ == TYPE_FILE);
+
+ // NOTE: wininet is unable to upload files larger than 4GB, but we'll let the
+ // http layer worry about that.
+ // TODO(darin): This size calculation could be out of sync with the state of
+ // the file when we get around to reading it. We should probably find a way
+ // to lock the file or somehow protect against this error condition.
+
+ WIN32_FILE_ATTRIBUTE_DATA info;
+ if (!GetFileAttributesEx(file_path_.c_str(), GetFileExInfoStandard, &info)) {
+ DLOG(WARNING) << "GetFileAttributesEx failed: " << GetLastError();
+ return 0;
+ }
+
+ uint64 length = static_cast<uint64>(info.nFileSizeHigh) << 32 |
+ info.nFileSizeLow;
+ if (file_range_offset_ >= length)
+ return 0; // range is beyond eof
+
+ // compensate for the offset and clip file_range_length_ to eof
+ return std::min(length - file_range_offset_, file_range_length_);
+}
+
+} // namespace net
diff --git a/net/base/upload_data.h b/net/base/upload_data.h
new file mode 100644
index 0000000..71b8f3d
--- /dev/null
+++ b/net/base/upload_data.h
@@ -0,0 +1,125 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_UPLOAD_DATA_H__
+#define NET_BASE_UPLOAD_DATA_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+
+namespace net {
+
+class UploadData : public base::RefCounted<UploadData> {
+ public:
+ UploadData() {}
+
+ enum Type {
+ TYPE_BYTES,
+ TYPE_FILE
+ };
+
+ class Element {
+ public:
+ Element() : type_(TYPE_BYTES), file_range_offset_(0),
+ file_range_length_(kuint64max) {
+ }
+
+ Type type() const { return type_; }
+ const std::vector<char>& bytes() const { return bytes_; }
+ const std::wstring& file_path() const { return file_path_; }
+ uint64 file_range_offset() const { return file_range_offset_; }
+ uint64 file_range_length() const { return file_range_length_; }
+
+ void SetToBytes(const char* bytes, int bytes_len) {
+ type_ = TYPE_BYTES;
+ bytes_.assign(bytes, bytes + bytes_len);
+ }
+
+ void SetToFilePath(const std::wstring& path) {
+ SetToFilePathRange(path, 0, kuint64max);
+ }
+
+ void SetToFilePathRange(const std::wstring& path,
+ uint64 offset, uint64 length) {
+ type_ = TYPE_FILE;
+ file_path_ = path;
+ file_range_offset_ = offset;
+ file_range_length_ = length;
+ }
+
+ // Returns the byte-length of the element. For files that do not exist, 0
+ // is returned. This is done for consistency with Mozilla.
+ uint64 GetContentLength() const;
+
+ private:
+ Type type_;
+ std::vector<char> bytes_;
+ std::wstring file_path_;
+ uint64 file_range_offset_;
+ uint64 file_range_length_;
+ };
+
+ void AppendBytes(const char* bytes, int bytes_len) {
+ if (bytes_len > 0) {
+ elements_.push_back(Element());
+ elements_.back().SetToBytes(bytes, bytes_len);
+ }
+ }
+
+ void AppendFile(const std::wstring& file_path) {
+ elements_.push_back(Element());
+ elements_.back().SetToFilePath(file_path);
+ }
+
+ void AppendFileRange(const std::wstring& file_path,
+ uint64 offset, uint64 length) {
+ elements_.push_back(Element());
+ elements_.back().SetToFilePathRange(file_path, offset, length);
+ }
+
+ // Returns the total size in bytes of the data to upload.
+ uint64 GetContentLength() const;
+
+ const std::vector<Element>& elements() const {
+ return elements_;
+ }
+
+ void set_elements(const std::vector<Element>& elements) {
+ elements_ = elements;
+ }
+
+ private:
+ std::vector<Element> elements_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_DATA_H__
diff --git a/net/base/upload_data_stream.cc b/net/base/upload_data_stream.cc
new file mode 100644
index 0000000..18ed05e
--- /dev/null
+++ b/net/base/upload_data_stream.cc
@@ -0,0 +1,151 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/upload_data_stream.h"
+
+namespace net {
+
+UploadDataStream::UploadDataStream(const UploadData* data)
+ : data_(data),
+ next_element_handle_(INVALID_HANDLE_VALUE),
+ total_size_(data->GetContentLength()) {
+ Reset();
+ FillBuf();
+}
+
+UploadDataStream::~UploadDataStream() {
+ if (next_element_handle_ != INVALID_HANDLE_VALUE)
+ CloseHandle(next_element_handle_);
+}
+
+void UploadDataStream::DidConsume(size_t num_bytes) {
+ DCHECK(num_bytes <= buf_len_);
+
+ buf_len_ -= num_bytes;
+ if (buf_len_)
+ memmove(buf_, buf_ + num_bytes, buf_len_);
+
+ FillBuf();
+
+ current_position_ += num_bytes;
+}
+
+void UploadDataStream::Reset() {
+ if (next_element_handle_ != INVALID_HANDLE_VALUE) {
+ CloseHandle(next_element_handle_);
+ next_element_handle_ = INVALID_HANDLE_VALUE;
+ }
+ buf_len_ = 0;
+ next_element_ = data_->elements().begin();
+ next_element_offset_ = 0;
+ next_element_remaining_ = 0;
+ current_position_ = 0;
+}
+
+void UploadDataStream::FillBuf() {
+ std::vector<UploadData::Element>::const_iterator end =
+ data_->elements().end();
+
+ while (buf_len_ < kBufSize && next_element_ != end) {
+ bool advance_to_next_element = false;
+
+ size_t size_remaining = kBufSize - buf_len_;
+ if ((*next_element_).type() == UploadData::TYPE_BYTES) {
+ const std::vector<char>& d = (*next_element_).bytes();
+ size_t count = d.size() - next_element_offset_;
+
+ size_t bytes_copied = std::min(count, size_remaining);
+
+ memcpy(buf_ + buf_len_, &d[next_element_offset_], bytes_copied);
+ buf_len_ += bytes_copied;
+
+ if (bytes_copied == count) {
+ advance_to_next_element = true;
+ } else {
+ next_element_offset_ += bytes_copied;
+ }
+ } else {
+ DCHECK((*next_element_).type() == UploadData::TYPE_FILE);
+
+ if (next_element_handle_ == INVALID_HANDLE_VALUE) {
+ next_element_handle_ = CreateFile((*next_element_).file_path().c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ // If the file does not exist, that's technically okay.. we'll just
+ // upload an empty file. This is for consistency with Mozilla.
+ DLOG_IF(WARNING, next_element_handle_ == INVALID_HANDLE_VALUE) <<
+ "Unable to open file \"" << (*next_element_).file_path() <<
+ "\" for reading: " << GetLastError();
+
+ next_element_remaining_ = (*next_element_).file_range_length();
+
+ if ((*next_element_).file_range_offset()) {
+ LARGE_INTEGER offset;
+ offset.QuadPart = (*next_element_).file_range_offset();
+ if (!SetFilePointerEx(next_element_handle_, offset,
+ NULL, FILE_BEGIN)) {
+ DLOG(WARNING) <<
+ "Unable to set file position for file \"" <<
+ (*next_element_).file_path() << "\": " << GetLastError();
+ next_element_remaining_ = 0;
+ }
+ }
+ }
+
+ // ReadFile will happily fail if given an invalid handle.
+ BOOL ok = FALSE;
+ DWORD bytes_read = 0;
+ uint64 amount_to_read = std::min(static_cast<uint64>(size_remaining),
+ next_element_remaining_);
+ if ((amount_to_read > 0) &&
+ (ok = ReadFile(next_element_handle_, buf_ + buf_len_,
+ static_cast<DWORD>(amount_to_read), &bytes_read,
+ NULL))) {
+ buf_len_ += bytes_read;
+ next_element_remaining_ -= bytes_read;
+ }
+
+ if (!ok || bytes_read == 0)
+ advance_to_next_element = true;
+ }
+
+ if (advance_to_next_element) {
+ ++next_element_;
+ next_element_offset_ = 0;
+ if (next_element_handle_ != INVALID_HANDLE_VALUE) {
+ CloseHandle(next_element_handle_);
+ next_element_handle_ = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+}
+
+} // namespace net
diff --git a/net/base/upload_data_stream.h b/net/base/upload_data_stream.h
new file mode 100644
index 0000000..53b237c
--- /dev/null
+++ b/net/base/upload_data_stream.h
@@ -0,0 +1,95 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_UPLOAD_DATA_STREAM_H_
+#define NET_BASE_UPLOAD_DATA_STREAM_H_
+
+#include "net/base/upload_data.h"
+
+namespace net {
+
+class UploadDataStream {
+ public:
+ UploadDataStream(const UploadData* data);
+ ~UploadDataStream();
+
+ // Returns the stream's buffer and buffer length.
+ const char* buf() const { return buf_; }
+ size_t buf_len() const { return buf_len_; }
+
+ // Call to indicate that a portion of the stream's buffer was consumed. This
+ // call modifies the stream's buffer so that it contains the next segment of
+ // the upload data to be consumed.
+ void DidConsume(size_t num_bytes);
+
+ // Call to reset the stream position to the beginning.
+ void Reset();
+
+ // Returns the total size of the data stream and the current position.
+ uint64 size() const { return total_size_; }
+ uint64 position() const { return current_position_; }
+
+ private:
+ void FillBuf();
+
+ const UploadData* data_;
+
+ // This buffer is filled with data to be uploaded. The data to be sent is
+ // always at the front of the buffer. If we cannot send all of the buffer at
+ // once, then we memmove the remaining portion and back-fill the buffer for
+ // the next "write" call. buf_len_ indicates how much data is in the buffer.
+ enum { kBufSize = 16384 };
+ char buf_[kBufSize];
+ size_t buf_len_;
+
+ // Iterator to the upload element to be written to the send buffer next.
+ std::vector<UploadData::Element>::const_iterator next_element_;
+
+ // The byte offset into next_element_'s data buffer if the next element is
+ // a TYPE_BYTES element.
+ size_t next_element_offset_;
+
+ // A handle to the currently open file (or INVALID_HANDLE_VALUE) for
+ // next_element_ if the next element is a TYPE_FILE element.
+ HANDLE next_element_handle_;
+
+ // The number of bytes remaining to be read from the currently open file
+ // if the next element is of TYPE_FILE.
+ uint64 next_element_remaining_;
+
+ // Size and current read position within the stream.
+ uint64 total_size_;
+ uint64 current_position_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(UploadDataStream);
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_DATA_STREAM_H_
diff --git a/net/base/wininet_util.cc b/net/base/wininet_util.cc
new file mode 100644
index 0000000..2c68891
--- /dev/null
+++ b/net/base/wininet_util.cc
@@ -0,0 +1,95 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/wininet_util.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// static
+int WinInetUtil::OSErrorToNetError(DWORD os_error) {
+ // Optimize the common case.
+ if (os_error == ERROR_IO_PENDING)
+ return net::ERR_IO_PENDING;
+
+ switch (os_error) {
+ case ERROR_SUCCESS:
+ return net::OK;
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ return net::ERR_FILE_NOT_FOUND;
+ case ERROR_HANDLE_EOF: // TODO(wtc): return net::OK?
+ return net::ERR_CONNECTION_CLOSED;
+ case ERROR_INVALID_HANDLE:
+ return net::ERR_INVALID_HANDLE;
+ case ERROR_INVALID_PARAMETER:
+ return net::ERR_INVALID_ARGUMENT;
+
+ case ERROR_INTERNET_CANNOT_CONNECT:
+ return net::ERR_CONNECTION_FAILED;
+ case ERROR_INTERNET_CONNECTION_RESET:
+ return net::ERR_CONNECTION_RESET;
+ case ERROR_INTERNET_DISCONNECTED:
+ return net::ERR_INTERNET_DISCONNECTED;
+ case ERROR_INTERNET_INVALID_URL:
+ return net::ERR_INVALID_URL;
+ case ERROR_INTERNET_NAME_NOT_RESOLVED:
+ return net::ERR_NAME_NOT_RESOLVED;
+ case ERROR_INTERNET_OPERATION_CANCELLED:
+ return net::ERR_ABORTED;
+ case ERROR_INTERNET_UNRECOGNIZED_SCHEME:
+ return net::ERR_UNKNOWN_URL_SCHEME;
+
+ // SSL certificate errors
+ case ERROR_INTERNET_SEC_CERT_CN_INVALID:
+ return net::ERR_CERT_COMMON_NAME_INVALID;
+ case ERROR_INTERNET_SEC_CERT_DATE_INVALID:
+ return net::ERR_CERT_DATE_INVALID;
+ case ERROR_INTERNET_INVALID_CA:
+ return net::ERR_CERT_AUTHORITY_INVALID;
+ case ERROR_INTERNET_SEC_CERT_NO_REV:
+ return net::ERR_CERT_NO_REVOCATION_MECHANISM;
+ case ERROR_INTERNET_SEC_CERT_REV_FAILED:
+ return net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION;
+ case ERROR_INTERNET_SEC_CERT_REVOKED:
+ return net::ERR_CERT_REVOKED;
+ case ERROR_INTERNET_SEC_CERT_ERRORS:
+ return net::ERR_CERT_CONTAINS_ERRORS;
+ case ERROR_INTERNET_SEC_INVALID_CERT:
+ return net::ERR_CERT_INVALID;
+
+ case ERROR_INTERNET_EXTENDED_ERROR:
+ default:
+ return net::ERR_FAILED;
+ }
+}
+
+} // namespace net
diff --git a/net/base/wininet_util.h b/net/base/wininet_util.h
new file mode 100644
index 0000000..9db9433
--- /dev/null
+++ b/net/base/wininet_util.h
@@ -0,0 +1,50 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_BASE_WININET_UTIL_H__
+#define NET_BASE_WININET_UTIL_H__
+
+#include <windows.h>
+#include <wininet.h>
+
+#include <string>
+
+namespace net {
+
+// Global functions and variables for using WinInet.
+class WinInetUtil {
+ public:
+ // Maps Windows error codes (returned by GetLastError()) to net::ERR_xxx
+ // error codes.
+ static int OSErrorToNetError(DWORD os_error);
+};
+
+} // namespace net
+
+#endif // NET_BASE_WININET_UTIL_H__
diff --git a/net/base/wininet_util_unittest.cc b/net/base/wininet_util_unittest.cc
new file mode 100644
index 0000000..affe1c4
--- /dev/null
+++ b/net/base/wininet_util_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <windows.h>
+#include <wininet.h>
+
+#include "net/base/net_errors.h"
+#include "net/base/wininet_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::WinInetUtil;
+
+namespace {
+ class WinInetUtilTest : public testing::Test {
+ };
+}
+
+TEST(WinInetUtilTest, ErrorCodeConversion) {
+ // a list of Windows error codes and the corresponding
+ // net::ERR_xxx error codes
+ static const struct {
+ DWORD os_error;
+ int net_error;
+ } error_cases[] = {
+ {ERROR_SUCCESS, net::OK},
+ {ERROR_IO_PENDING, net::ERR_IO_PENDING},
+ {ERROR_INTERNET_OPERATION_CANCELLED, net::ERR_ABORTED},
+ {ERROR_INTERNET_CANNOT_CONNECT, net::ERR_CONNECTION_FAILED},
+ {ERROR_INTERNET_NAME_NOT_RESOLVED, net::ERR_NAME_NOT_RESOLVED},
+ {ERROR_INTERNET_INVALID_CA, net::ERR_CERT_AUTHORITY_INVALID},
+ {999999, net::ERR_FAILED},
+ };
+
+ for (int i = 0; i < arraysize(error_cases); i++) {
+ EXPECT_EQ(error_cases[i].net_error,
+ WinInetUtil::OSErrorToNetError(error_cases[i].os_error));
+ }
+}
diff --git a/net/base/winsock_init.cc b/net/base/winsock_init.cc
new file mode 100644
index 0000000..e164b92
--- /dev/null
+++ b/net/base/winsock_init.cc
@@ -0,0 +1,57 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <winsock2.h>
+
+#include "net/base/winsock_init.h"
+
+#include "base/singleton.h"
+
+WinsockInit::WinsockInit() : did_init_(false) {
+ did_init_ = Init();
+}
+
+bool WinsockInit::Init() {
+ WORD winsock_ver = MAKEWORD(2,2);
+ WSAData wsa_data;
+ return (WSAStartup(winsock_ver, &wsa_data) == 0);
+}
+
+void WinsockInit::Cleanup() {
+ WSACleanup();
+}
+
+WinsockInit::~WinsockInit() {
+ if (did_init_)
+ Cleanup();
+}
+
+void EnsureWinsockInit() {
+ Singleton<WinsockInit>::get();
+}
diff --git a/net/base/winsock_init.h b/net/base/winsock_init.h
new file mode 100644
index 0000000..40d0557
--- /dev/null
+++ b/net/base/winsock_init.h
@@ -0,0 +1,56 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Winsock initialization must happen before any Winsock calls are made. This
+// class provides a wrapper for WSAStartup and WSACleanup. There are 3 ways to
+// use it: either allocate a new WinsockInit object at startup and delete when
+// shutting down, manually call Init and Cleanup, or use the EnsureWinsockInit
+// method, which may be called multiple times. In the second case, Cleanup
+// should only be called if Init was successful.
+
+#ifndef NET_BASE_WINSOCK_INIT_H_
+#define NET_BASE_WINSOCK_INIT_H_
+
+class WinsockInit {
+ public:
+ WinsockInit();
+ ~WinsockInit();
+
+ static bool Init();
+ static void Cleanup();
+
+ private:
+ bool did_init_;
+};
+
+// Force there to be a global WinsockInit object that gets created once and
+// destroyed at application exit. This may be called multiple times.
+void EnsureWinsockInit();
+
+#endif // NET_BASE_WINSOCK_INIT_H_
diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc
new file mode 100644
index 0000000..445f128
--- /dev/null
+++ b/net/base/x509_certificate.cc
@@ -0,0 +1,569 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/x509_certificate.h"
+
+#include <map>
+
+#include "base/histogram.h"
+#include "base/lock.h"
+#include "base/pickle.h"
+#include "base/singleton.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "net/base/cert_status_flags.h"
+#include "net/base/ev_root_ca_metadata.h"
+
+#pragma comment(lib, "crypt32.lib")
+
+namespace {
+
+// Returns true if this cert fingerprint is the null (all zero) fingerprint.
+// We use this as a bogus fingerprint value.
+bool IsNullFingerprint(const X509Certificate::Fingerprint& fingerprint) {
+ for (int i = 0; i < arraysize(fingerprint.data); ++i) {
+ if (fingerprint.data[i] != 0)
+ return false;
+ }
+ return true;
+}
+
+// Calculates the SHA-1 fingerprint of the certificate. Returns an empty
+// (all zero) fingerprint on failure.
+X509Certificate::Fingerprint CalculateFingerprint(PCCERT_CONTEXT cert) {
+ DCHECK(NULL != cert->pbCertEncoded);
+ DCHECK(0 != cert->cbCertEncoded);
+
+ BOOL rv;
+ X509Certificate::Fingerprint 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;
+}
+
+// Wrappers of malloc and free for CRYPT_DECODE_PARA, which requires the
+// WINAPI calling convention.
+void* WINAPI MyCryptAlloc(size_t size) {
+ return malloc(size);
+}
+
+void WINAPI MyCryptFree(void* p) {
+ free(p);
+}
+
+// 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 = MyCryptAlloc;
+ decode_para.pfnFree = MyCryptFree;
+ 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);
+}
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Functions used by X509Certificate::IsEV
+//
+///////////////////////////////////////////////////////////////////////////
+
+// Constructs a certificate chain starting from the end certificate
+// 'cert_context', matching any of the certificate policies.
+//
+// Returns the certificate chain context on success, or NULL on failure.
+// The caller is responsible for freeing the certificate chain context with
+// CertFreeCertificateChain.
+PCCERT_CHAIN_CONTEXT ConstructCertChain(
+ PCCERT_CONTEXT cert_context,
+ const char* const* policies,
+ int num_policies) {
+ CERT_CHAIN_PARA chain_para;
+ memset(&chain_para, 0, sizeof(chain_para));
+ chain_para.cbSize = sizeof(chain_para);
+ chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
+ chain_para.RequestedUsage.Usage.cUsageIdentifier = 0;
+ chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = NULL; // LPSTR*
+ chain_para.RequestedIssuancePolicy.dwType = USAGE_MATCH_TYPE_OR;
+ chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = num_policies;
+ chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier =
+ const_cast<char**>(policies);
+ PCCERT_CHAIN_CONTEXT chain_context;
+ if (!CertGetCertificateChain(
+ NULL, // default chain engine, HCCE_CURRENT_USER
+ cert_context,
+ NULL, // current system time
+ cert_context->hCertStore, // search this store
+ &chain_para,
+ CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT |
+ CERT_CHAIN_CACHE_END_CERT,
+ NULL, // reserved
+ &chain_context)) {
+ return NULL;
+ }
+ return chain_context;
+}
+
+// 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 = MyCryptAlloc;
+ decode_para.pfnFree = MyCryptFree;
+ 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);
+}
+
+// Returns true if the policy is in the array of CERT_POLICY_INFO in
+// the CERT_POLICIES_INFO structure.
+bool ContainsPolicy(const CERT_POLICIES_INFO* policies_info,
+ const char* policy) {
+ int num_policies = policies_info->cPolicyInfo;
+ for (int i = 0; i < num_policies; i++) {
+ if (!strcmp(policies_info->rgPolicyInfo[i].pszPolicyIdentifier, policy))
+ return true;
+ }
+ return false;
+}
+
+// This class wraps the CertFreeCertificateChain function in a class that can
+// be passed as a template argument to scoped_ptr_malloc.
+class ScopedPtrMallocFreeCertChain {
+ public:
+ void operator()(const CERT_CHAIN_CONTEXT* x) const {
+ CertFreeCertificateChain(x);
+ }
+};
+
+typedef scoped_ptr_malloc<const CERT_CHAIN_CONTEXT,
+ ScopedPtrMallocFreeCertChain> ScopedCertChainContext;
+
+} // namespace
+
+bool X509Certificate::FingerprintLessThan::operator()(
+ const Fingerprint& lhs,
+ const Fingerprint& rhs) const {
+ for (int i = 0; i < sizeof(lhs.data); ++i) {
+ if (lhs.data[i] < rhs.data[i])
+ return true;
+ if (lhs.data[i] > rhs.data[i])
+ return false;
+ }
+ return false;
+}
+
+bool X509Certificate::LessThan::operator()(X509Certificate* lhs,
+ X509Certificate* rhs) const {
+ if (lhs == rhs)
+ return false;
+
+ X509Certificate::FingerprintLessThan fingerprint_functor;
+ return fingerprint_functor(lhs->fingerprint_, rhs->fingerprint_);
+}
+
+// A thread-safe cache for X509Certificate objects.
+//
+// The cache does not hold a reference to the certificate objects. The objects
+// must |Remove| themselves from the cache upon destruction (or else the cache
+// will be holding dead pointers to the objects).
+class X509Certificate::Cache {
+ public:
+ // Get the singleton object for the cache.
+ static X509Certificate::Cache* GetInstance() {
+ return Singleton<X509Certificate::Cache>::get();
+ }
+
+ // Insert |cert| into the cache. The cache does NOT AddRef |cert|. The cache
+ // must not already contain a certificate with the same fingerprint.
+ void Insert(X509Certificate* cert) {
+ AutoLock lock(lock_);
+
+ DCHECK(!IsNullFingerprint(cert->fingerprint())) <<
+ "Only insert certs with real fingerprints.";
+ DCHECK(cache_.find(cert->fingerprint()) == cache_.end());
+ cache_[cert->fingerprint()] = cert;
+ };
+
+ // Remove |cert| from the cache. The cache does not assume that |cert| is
+ // already in the cache.
+ void Remove(X509Certificate* cert) {
+ AutoLock lock(lock_);
+
+ CertMap::iterator pos(cache_.find(cert->fingerprint()));
+ if (pos == cache_.end())
+ return; // It is not an error to remove a cert that is not in the cache.
+ cache_.erase(pos);
+ };
+
+ // Find a certificate in the cache with the given fingerprint. If one does
+ // not exist, this method returns NULL.
+ X509Certificate* Find(const Fingerprint& fingerprint) {
+ AutoLock lock(lock_);
+
+ CertMap::iterator pos(cache_.find(fingerprint));
+ if (pos == cache_.end())
+ return NULL;
+
+ return pos->second;
+ };
+
+ private:
+ typedef std::map<Fingerprint, X509Certificate*, FingerprintLessThan> CertMap;
+
+ // Obtain an instance of X509Certificate::Cache via GetInstance().
+ Cache() { }
+ friend DefaultSingletonTraits<X509Certificate::Cache>;
+
+ // You must acquire this lock before using any private data of this object.
+ // You must not block while holding this lock.
+ Lock lock_;
+
+ // The certificate cache. You must acquire |lock_| before using |cache_|.
+ CertMap cache_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(X509Certificate::Cache);
+};
+
+void X509Certificate::Initialize() {
+ std::wstring subject_info;
+ std::wstring issuer_info;
+ DWORD name_size;
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Subject,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ NULL, 0);
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Subject,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ WriteInto(&subject_info, name_size), name_size);
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Issuer,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ NULL, 0);
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Issuer,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ WriteInto(&issuer_info, name_size), name_size);
+ ParsePrincipal(WideToUTF8(subject_info), &subject_);
+ ParsePrincipal(WideToUTF8(issuer_info), &issuer_);
+
+ valid_start_ = Time::FromFileTime(cert_handle_->pCertInfo->NotBefore);
+ valid_expiry_ = Time::FromFileTime(cert_handle_->pCertInfo->NotAfter);
+
+ fingerprint_ = CalculateFingerprint(cert_handle_);
+
+ // Store the certificate in the cache in case we need it later.
+ X509Certificate::Cache::GetInstance()->Insert(this);
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromHandle(OSCertHandle cert_handle) {
+ DCHECK(cert_handle);
+
+ // Check if we already have this certificate in memory.
+ X509Certificate::Cache* cache = X509Certificate::Cache::GetInstance();
+ X509Certificate* cert = cache->Find(CalculateFingerprint(cert_handle));
+ if (cert) {
+ // We've found a certificate with the same fingerprint in our cache. We own
+ // the |cert_handle|, which makes it our job to free it.
+ CertFreeCertificateContext(cert_handle);
+ DHISTOGRAM_COUNTS(L"X509CertificateReuseCount", 1);
+ return cert;
+ }
+ // Otherwise, allocate a new object.
+ return new X509Certificate(cert_handle);
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromPickle(const Pickle& pickle,
+ void** pickle_iter) {
+ const char* data;
+ int length;
+ if (!pickle.ReadData(pickle_iter, &data, &length))
+ return NULL;
+
+ OSCertHandle cert_handle = NULL;
+ if (!CertAddSerializedElementToStore(
+ NULL, // the cert won't be persisted in any cert store
+ reinterpret_cast<const BYTE*>(data), length,
+ CERT_STORE_ADD_USE_EXISTING, 0, CERT_STORE_CERTIFICATE_CONTEXT_FLAG,
+ NULL, reinterpret_cast<const void **>(&cert_handle)))
+ return NULL;
+
+ return CreateFromHandle(cert_handle);
+}
+
+X509Certificate::X509Certificate(OSCertHandle cert_handle)
+ : cert_handle_(cert_handle) {
+ Initialize();
+}
+
+X509Certificate::X509Certificate(std::string subject, std::string issuer,
+ Time start_date, 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));
+}
+
+void X509Certificate::Persist(Pickle* pickle) {
+ DWORD length;
+ if (!CertSerializeCertificateStoreElement(cert_handle_, 0,
+ NULL, &length)) {
+ NOTREACHED();
+ return;
+ }
+ BYTE* data = reinterpret_cast<BYTE*>(pickle->BeginWriteData(length));
+ if (!CertSerializeCertificateStoreElement(cert_handle_, 0,
+ data, &length)) {
+ NOTREACHED();
+ length = 0;
+ }
+ pickle->TrimWriteData(length);
+}
+
+X509Certificate::~X509Certificate() {
+ // We might not be in the cache, but it is safe to remove ourselves anyway.
+ X509Certificate::Cache::GetInstance()->Remove(this);
+ if (cert_handle_)
+ CertFreeCertificateContext(cert_handle_);
+}
+
+void X509Certificate::GetDNSNames(std::vector<std::string>* dns_names) const {
+ dns_names->clear();
+ 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.
+ if (alt_name->rgAltEntry[i].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME)
+ dns_names->push_back(WideToASCII(alt_name->rgAltEntry[i].pwszDNSName));
+ }
+ }
+ if (dns_names->empty())
+ dns_names->push_back(subject_.common_name);
+}
+
+bool X509Certificate::HasExpired() const {
+ return Time::Now() > valid_expiry();
+}
+
+// Returns true if the certificate is an extended-validation certificate.
+//
+// The certificate has already been verified by the HTTP library. cert_status
+// represents the result of that verification. This function performs
+// additional checks of 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 X509Certificate::IsEV(int cert_status) const {
+ if (net::IsCertStatusError(cert_status) ||
+ (cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED) == 0)
+ return false;
+
+ net::EVRootCAMetadata* metadata = net::EVRootCAMetadata::GetInstance();
+
+ PCCERT_CHAIN_CONTEXT chain_context = ConstructCertChain(cert_handle_,
+ metadata->GetPolicyOIDs(), metadata->NumPolicyOIDs());
+ if (!chain_context)
+ return false;
+ ScopedCertChainContext scoped_chain_context(chain_context);
+
+ DCHECK(chain_context->cChain != 0);
+ // 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;
+ DWORD info_status = chain_context->TrustStatus.dwInfoStatus;
+ 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;
+ X509Certificate::Fingerprint fingerprint = CalculateFingerprint(root_cert);
+ std::string ev_policy_oid;
+ if (!metadata->GetPolicyOID(fingerprint, &ev_policy_oid))
+ return false;
+ DCHECK(!ev_policy_oid.empty());
+
+ // Get the certificatePolicies extension of the end certificate.
+ PCCERT_CONTEXT end_cert = element[0]->pCertContext;
+ scoped_ptr_malloc<CERT_POLICIES_INFO> policies_info;
+ GetCertPoliciesInfo(end_cert, &policies_info);
+ if (!policies_info.get())
+ return false;
+
+ return ContainsPolicy(policies_info.get(), ev_policy_oid.c_str());
+}
+
+// static
+void X509Certificate::ParsePrincipal(const std::string& description,
+ Principal* principal) {
+ // The description of the principal is a string with each LDAP value on
+ // a separate line.
+ const std::string kDelimiters("\r\n");
+
+ std::vector<std::string> common_names, locality_names, state_names,
+ country_names, street_addresses;
+
+ // TODO(jcampan): add business_category and serial_number.
+ const std::string kPrefixes[8] = { std::string("CN="),
+ std::string("L="),
+ std::string("S="),
+ std::string("C="),
+ std::string("STREET="),
+ std::string("O="),
+ std::string("OU="),
+ std::string("DC=") };
+
+ std::vector<std::string>* values[8] = {
+ &common_names, &locality_names,
+ &state_names, &country_names,
+ &(principal->street_addresses),
+ &(principal->organization_names),
+ &(principal->organization_unit_names),
+ &(principal->domain_components) };
+ DCHECK(arraysize(kPrefixes) == arraysize(values));
+
+ StringTokenizer str_tok(description, kDelimiters);
+ while (str_tok.GetNext()) {
+ std::string entry = str_tok.token();
+ for (int i = 0; i < arraysize(kPrefixes); i++) {
+ if (!entry.compare(0, kPrefixes[i].length(), kPrefixes[i])) {
+ std::string value = entry.substr(kPrefixes[i].length());
+ // Remove enclosing double-quotes if any.
+ if (value.size() >= 2 &&
+ value[0] == '"' && value[value.size() - 1] == '"')
+ value = value.substr(1, value.size() - 2);
+ values[i]->push_back(value);
+ break;
+ }
+ }
+ }
+
+ // We don't expect to have more than one CN, L, S, and C.
+ std::vector<std::string>* single_value_lists[4] = {
+ &common_names, &locality_names, &state_names, &country_names };
+ std::string* single_values[4] = {
+ &principal->common_name, &principal->locality_name,
+ &principal->state_or_province_name, &principal->country_name };
+ for (int i = 0; i < arraysize(single_value_lists); ++i) {
+ int length = static_cast<int>(single_value_lists[i]->size());
+ DCHECK(single_value_lists[i]->size() <= 1);
+ if (single_value_lists[i]->size() > 0)
+ *(single_values[i]) = (*(single_value_lists[i]))[0];
+ }
+}
+
+X509Certificate::Policy::Judgment X509Certificate::Policy::Check(
+ X509Certificate* cert) const {
+ // It shouldn't matter which set we check first, but we check denied first
+ // in case something strange has happened.
+
+ if (denied_.find(cert->fingerprint()) != denied_.end()) {
+ // DCHECK that the order didn't matter.
+ DCHECK(allowed_.find(cert->fingerprint()) == allowed_.end());
+ return DENIED;
+ }
+
+ if (allowed_.find(cert->fingerprint()) != allowed_.end()) {
+ // DCHECK that the order didn't matter.
+ DCHECK(denied_.find(cert->fingerprint()) == denied_.end());
+ return ALLOWED;
+ }
+
+ // We don't have a policy for this cert.
+ return UNKNOWN;
+}
+
+void X509Certificate::Policy::Allow(X509Certificate* cert) {
+ // Put the cert in the allowed set and (maybe) remove it from the denied set.
+ denied_.erase(cert->fingerprint());
+ allowed_.insert(cert->fingerprint());
+}
+
+void X509Certificate::Policy::Deny(X509Certificate* cert) {
+ // Put the cert in the denied set and (maybe) remove it from the allowed set.
+ allowed_.erase(cert->fingerprint());
+ denied_.insert(cert->fingerprint());
+}
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h
new file mode 100644
index 0000000..217b331
--- /dev/null
+++ b/net/base/x509_certificate.h
@@ -0,0 +1,213 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_COMMON_NET_X509_CERTIFICATE_H__
+#define CHROME_COMMON_NET_X509_CERTIFICATE_H__
+
+#include <windows.h>
+#include <wincrypt.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "base/time.h"
+
+class Pickle;
+
+// X509Certificate represents an X.509 certificate used by SSL.
+class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
+ public:
+ // SHA-1 fingerprint (160 bits) of a certificate.
+ struct Fingerprint {
+ unsigned char data[20];
+ };
+
+ class FingerprintLessThan
+ : public std::binary_function<Fingerprint, Fingerprint, bool> {
+ public:
+ bool operator() (const Fingerprint& lhs, const Fingerprint& rhs) const;
+ };
+
+ // Predicate functor used in maps when X509Certificate is used as the key.
+ class LessThan
+ : public std::binary_function<X509Certificate*, X509Certificate*, bool> {
+ public:
+ bool operator() (X509Certificate* lhs, X509Certificate* rhs) const;
+ };
+
+ typedef PCCERT_CONTEXT OSCertHandle;
+
+ // Principal represent an X.509 principal.
+ struct Principal {
+ Principal() { }
+ explicit Principal(std::string name) : common_name(name) { }
+
+ // The different attributes for a principal. 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 Policy {
+ 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,
+ };
+
+ // Returns the judgment this policy makes about this certificate.
+ Judgment Check(X509Certificate* cert) const;
+
+ // Causes the policy to allow this certificate.
+ void Allow(X509Certificate* cert);
+
+ // Causes the policy to deny this certificate.
+ void Deny(X509Certificate* cert);
+
+ private:
+ // The set of fingerprints of allowed certificates.
+ std::set<Fingerprint, FingerprintLessThan> allowed_;
+
+ // The set of fingerprints of denied certificates.
+ std::set<Fingerprint, FingerprintLessThan> denied_;
+ };
+
+ // Create an X509Certificate from a handle to the certificate object
+ // in the underlying crypto library.
+ static X509Certificate* CreateFromHandle(OSCertHandle cert_handle);
+
+ // 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.
+ static X509Certificate* CreateFromPickle(const Pickle& pickle,
+ void** pickle_iter);
+
+ // Creates a X509Certificate from the ground up. Used by tests that simulate
+ // SSL connections.
+ X509Certificate(std::string subject, std::string issuer,
+ Time start_date, Time expiration_date);
+
+ // Appends a representation of this object to the given pickle.
+ void Persist(Pickle* pickle);
+
+ // 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 Principal& subject() const { return subject_; }
+
+ // The issuer of the certificate.
+ const Principal& 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 Time& valid_start() const { return valid_start_; }
+ const Time& valid_expiry() const { return valid_expiry_; }
+
+ // The fingerprint of this certificate.
+ const Fingerprint& fingerprint() const { return 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;
+
+ // Convenience method that returns whether this certificate has expired as of
+ // now.
+ bool HasExpired() const;
+
+ // Returns true if the certificate is an extended-validation (EV)
+ // certificate.
+ bool IsEV(int cert_status) const;
+
+ OSCertHandle os_cert_handle() const { return cert_handle_; }
+
+ private:
+ // A cache of X509Certificate objects.
+ class Cache;
+
+ // Construct an X509Certificate from a handle to the certificate object
+ // in the underlying crypto library.
+ explicit X509Certificate(OSCertHandle cert_handle);
+
+ friend RefCountedThreadSafe<X509Certificate>;
+ ~X509Certificate();
+
+ // Common object initialization code. Called by the constructors only.
+ void Initialize();
+
+ // Helper function to parse a principal from a WinInet description of that
+ // principal.
+ static void ParsePrincipal(const std::string& description,
+ Principal* principal);
+
+ // The subject of the certificate.
+ Principal subject_;
+
+ // The issuer of the certificate.
+ Principal issuer_;
+
+ // This certificate is not valid before |valid_start_|
+ Time valid_start_;
+
+ // This certificate is not valid after |valid_expiry_|
+ Time valid_expiry_;
+
+ // The fingerprint of this certificate.
+ Fingerprint fingerprint_;
+
+ // A handle to the certificate object in the underlying crypto library.
+ OSCertHandle cert_handle_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(X509Certificate);
+};
+
+#endif // CHROME_COMMON_NET_X509_CERTIFICATE_H__
diff --git a/net/build/convert_tld_data.rules b/net/build/convert_tld_data.rules
new file mode 100644
index 0000000..0331c13
--- /dev/null
+++ b/net/build/convert_tld_data.rules
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<VisualStudioToolFile
+ Name="Convert TLD Data File"
+ Version="8.00"
+ >
+ <Rules>
+ <CustomBuildRule
+ Name="Convert TLD Data File"
+ DisplayName="Convert TLD Data File"
+ CommandLine="&quot;$(OutDir)\tld_cleanup.exe&quot; &quot;$(InputPath)&quot; &quot;$(IntDir)\$(InputName)_clean.dat&quot;"
+ Outputs="&quot;$(IntDir)\$(InputName)_clean.dat&quot;"
+ FileExtensions="*.dat"
+ ExecutionDescription="Converting TLD data file..."
+ >
+ <Properties>
+ </Properties>
+ </CustomBuildRule>
+ </Rules>
+</VisualStudioToolFile>
diff --git a/net/build/crash_cache.vcproj b/net/build/crash_cache.vcproj
new file mode 100644
index 0000000..0a37acf
--- /dev/null
+++ b/net/build/crash_cache.vcproj
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="crash_cache"
+ ProjectGUID="{B0EE0599-2913-46A0-A847-A3EC813658D3}"
+ RootNamespace="crash_cache"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ SubSystem="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ SubSystem="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <File
+ RelativePath="..\tools\crash_cache\crash_cache.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache_test_util.cc"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/net/build/net.vcproj b/net/build/net.vcproj
new file mode 100644
index 0000000..0d670a4
--- /dev/null
+++ b/net/build/net.vcproj
@@ -0,0 +1,895 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="net"
+ ProjectGUID="{326E9795-E760-410A-B69A-3F79DB3F5243}"
+ RootNamespace="net"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ <ToolFile
+ RelativePath=".\convert_tld_data.rules"
+ />
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="4"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="Convert TLD Data File"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="precompiled_net.h"
+ ForcedIncludeFiles="precompiled_net.h"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="4"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="Convert TLD Data File"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="base"
+ >
+ <File
+ RelativePath="..\base\address_list.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\address_list.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\auth.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\auth_cache.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\auth_cache.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\base64.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\base64.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\bzip2_filter.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\bzip2_filter.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\cert_status_flags.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\client_socket.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\client_socket_factory.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\client_socket_factory.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\completion_callback.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\cookie_monster.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\cookie_monster.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\cookie_policy.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\cookie_policy.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\data_url.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\data_url.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\directory_lister.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\directory_lister.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\dns_resolution_observer.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\dns_resolution_observer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\effective_tld_names.dat"
+ >
+ </File>
+ <File
+ RelativePath="..\base\escape.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\escape.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ev_root_ca_metadata.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ev_root_ca_metadata.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\filter.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\filter.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\gzip_filter.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\gzip_filter.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\gzip_header.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\gzip_header.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\host_resolver.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\host_resolver.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\listen_socket.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\listen_socket.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\load_flags.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\mime_sniffer.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\mime_sniffer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\mime_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\mime_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_error_list.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_errors.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_errors.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_module.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_module.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_resources.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_util.h"
+ >
+ </File>
+ <File
+ RelativePath=".\precompiled_net.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\precompiled_net.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\registry_controlled_domain.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\registry_controlled_domain.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\socket.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ssl_client_socket.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ssl_client_socket.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ssl_config_service.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ssl_config_service.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ssl_info.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\tcp_client_socket.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\tcp_client_socket.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\telnet_server.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\telnet_server.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\upload_data.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\upload_data.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\upload_data_stream.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\upload_data_stream.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\wininet_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\wininet_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\winsock_init.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\winsock_init.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\x509_certificate.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\x509_certificate.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="url_request"
+ >
+ <File
+ RelativePath="..\url_request\mime_sniffer_proxy.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\mime_sniffer_proxy.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_about_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_about_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_context.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_error_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_error_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_file_dir_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_file_dir_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_file_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_file_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_filter.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_filter.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_ftp_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_ftp_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_http_cache_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_http_cache_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_inet_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_inet_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job_manager.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job_manager.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job_metrics.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job_metrics.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job_tracker.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_job_tracker.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_simple_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_simple_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_status.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_test_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_test_job.h"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_view_cache_job.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_view_cache_job.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="http"
+ >
+ <File
+ RelativePath="..\http\cert_status_cache.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\cert_status_cache.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_atom_list.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_cache.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_cache.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_chunked_decoder.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_chunked_decoder.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_connection.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_connection.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_connection_manager.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_connection_manager.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_network_layer.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_network_layer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_network_session.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_network_transaction.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_network_transaction.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_proxy_resolver_fixed.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_proxy_resolver_fixed.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_proxy_resolver_winhttp.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_proxy_resolver_winhttp.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_proxy_service.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_proxy_service.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_request_info.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_response_headers.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_response_headers.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_response_info.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_transaction.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_transaction_factory.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_transaction_winhttp.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_transaction_winhttp.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_vary_data.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_vary_data.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\winhttp_request_throttle.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\winhttp_request_throttle.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="disk_cache"
+ >
+ <File
+ RelativePath="..\disk_cache\addr.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\backend_impl.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\backend_impl.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\block_files.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\block_files.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_format.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\entry_impl.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\entry_impl.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\errors.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\file.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\file.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\file_block.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\file_lock.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\file_lock.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\hash.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\hash.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mapped_file.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mapped_file.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mem_backend_impl.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mem_backend_impl.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mem_entry_impl.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mem_entry_impl.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mem_rankings.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mem_rankings.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\rankings.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\rankings.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\stats.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\stats.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\storage_block-inl.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\storage_block.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\trace.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\trace.h"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/net/build/net_perftests.vcproj b/net/build/net_perftests.vcproj
new file mode 100644
index 0000000..04cf0c9
--- /dev/null
+++ b/net/build/net_perftests.vcproj
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="net_perftests"
+ ProjectGUID="{AAC78796-B9A2-4CD9-BF89-09B03E92BF73}"
+ RootNamespace="net_perftests"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="PERF_TEST"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="PERF_TEST"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="support"
+ >
+ <File
+ RelativePath="..\..\base\perftimer.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\run_all_perftests.cc"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tests"
+ >
+ <File
+ RelativePath="..\base\cookie_monster_perftest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache_perftest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache_test_util.cc"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/net/build/net_unittests.vcproj b/net/build/net_unittests.vcproj
new file mode 100644
index 0000000..d31226e
--- /dev/null
+++ b/net/build/net_unittests.vcproj
@@ -0,0 +1,382 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="net_unittests"
+ ProjectGUID="{E99DA267-BE90-4F45-88A1-6919DB2C7567}"
+ RootNamespace="net_unittests"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="UNIT_TEST"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="precompiled_net.h"
+ ForcedIncludeFiles="precompiled_net.h"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="UNIT_TEST"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="support"
+ >
+ <File
+ RelativePath=".\precompiled_net.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\precompiled_net.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\run_all_unittests.cc"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tests"
+ >
+ <Filter
+ Name="disk_cache"
+ >
+ <File
+ RelativePath="..\disk_cache\addr_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\backend_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\block_files_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache_test_base.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache_test_base.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache_test_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\disk_cache_test_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\entry_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\mapped_file_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\storage_block_unittest.cc"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="http"
+ >
+ <File
+ RelativePath="..\http\http_cache_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_chunked_decoder_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_connection_manager_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_network_layer_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_network_transaction_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_proxy_service_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_response_headers_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_transaction_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_transaction_unittest.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_transaction_winhttp_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_util_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_vary_data_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\winhttp_request_throttle_unittest.cc"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="base"
+ >
+ <File
+ RelativePath="..\base\auth_cache_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\base64_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\bzip2_filter_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\cookie_monster_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\cookie_policy_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\data_url_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\directory_lister_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\escape_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\gzip_filter_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\listen_socket_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\listen_socket_unittest.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\mime_sniffer_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\mime_util_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\net_util_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\registry_controlled_domain_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ssl_client_socket_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\ssl_config_service_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\tcp_client_socket_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\telnet_server_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\wininet_util_unittest.cc"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="url_request"
+ >
+ <File
+ RelativePath="..\url_request\url_request_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\url_request\url_request_unittest.h"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/net/build/precompiled_net.cc b/net/build/precompiled_net.cc
new file mode 100644
index 0000000..733702c
--- /dev/null
+++ b/net/build/precompiled_net.cc
@@ -0,0 +1,33 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Generates the precompiled file.
+
+#include "precompiled_net.h"
+
diff --git a/net/build/precompiled_net.h b/net/build/precompiled_net.h
new file mode 100644
index 0000000..fa64b8a
--- /dev/null
+++ b/net/build/precompiled_net.h
@@ -0,0 +1,50 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Header used to generate the precompiled file.
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <wincrypt.h>
+#include <winsock2.h>
+
+#include <algorithm>
+#include <hash_map>
+#include <hash_set>
+#include <list>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <string.h>
diff --git a/net/build/stress_cache.vcproj b/net/build/stress_cache.vcproj
new file mode 100644
index 0000000..590ec5e
--- /dev/null
+++ b/net/build/stress_cache.vcproj
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="stress_cache"
+ ProjectGUID="{B491C3A1-DE5F-4843-A1BB-AB8C4337187B}"
+ RootNamespace="stress_cache"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ SubSystem="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ SubSystem="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <File
+ RelativePath="..\disk_cache\disk_cache_test_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\disk_cache\stress_cache.cc"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/net/build/tld_cleanup.vcproj b/net/build/tld_cleanup.vcproj
new file mode 100644
index 0000000..c628d9a
--- /dev/null
+++ b/net/build/tld_cleanup.vcproj
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="tld_cleanup"
+ ProjectGUID="{E13045CD-7E1F-4A41-9B18-8D288B2E7B41}"
+ RootNamespace="tld_cleanup"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ SubSystem="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ SubSystem="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <File
+ RelativePath="..\tools\tld_cleanup\tld_cleanup.cc"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/net/data/cache_tests/bad_entry/data_0 b/net/data/cache_tests/bad_entry/data_0
new file mode 100644
index 0000000..3e21df8
--- /dev/null
+++ b/net/data/cache_tests/bad_entry/data_0
Binary files differ
diff --git a/net/data/cache_tests/bad_entry/data_1 b/net/data/cache_tests/bad_entry/data_1
new file mode 100644
index 0000000..79c87af
--- /dev/null
+++ b/net/data/cache_tests/bad_entry/data_1
Binary files differ
diff --git a/net/data/cache_tests/bad_entry/data_2 b/net/data/cache_tests/bad_entry/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/bad_entry/data_2
Binary files differ
diff --git a/net/data/cache_tests/bad_entry/data_3 b/net/data/cache_tests/bad_entry/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/bad_entry/data_3
Binary files differ
diff --git a/net/data/cache_tests/bad_entry/index b/net/data/cache_tests/bad_entry/index
new file mode 100644
index 0000000..ad42f278
--- /dev/null
+++ b/net/data/cache_tests/bad_entry/index
Binary files differ
diff --git a/net/data/cache_tests/bad_rankings/data_0 b/net/data/cache_tests/bad_rankings/data_0
new file mode 100644
index 0000000..e7bf298
--- /dev/null
+++ b/net/data/cache_tests/bad_rankings/data_0
Binary files differ
diff --git a/net/data/cache_tests/bad_rankings/data_1 b/net/data/cache_tests/bad_rankings/data_1
new file mode 100644
index 0000000..1e000f9
--- /dev/null
+++ b/net/data/cache_tests/bad_rankings/data_1
Binary files differ
diff --git a/net/data/cache_tests/bad_rankings/data_2 b/net/data/cache_tests/bad_rankings/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/bad_rankings/data_2
Binary files differ
diff --git a/net/data/cache_tests/bad_rankings/data_3 b/net/data/cache_tests/bad_rankings/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/bad_rankings/data_3
Binary files differ
diff --git a/net/data/cache_tests/bad_rankings/index b/net/data/cache_tests/bad_rankings/index
new file mode 100644
index 0000000..ad42f278
--- /dev/null
+++ b/net/data/cache_tests/bad_rankings/index
Binary files differ
diff --git a/net/data/cache_tests/insert_empty1/data_0 b/net/data/cache_tests/insert_empty1/data_0
new file mode 100644
index 0000000..3b52b37
--- /dev/null
+++ b/net/data/cache_tests/insert_empty1/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_empty1/data_1 b/net/data/cache_tests/insert_empty1/data_1
new file mode 100644
index 0000000..c66ca74
--- /dev/null
+++ b/net/data/cache_tests/insert_empty1/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_empty1/data_2 b/net/data/cache_tests/insert_empty1/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_empty1/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_empty1/data_3 b/net/data/cache_tests/insert_empty1/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_empty1/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_empty1/index b/net/data/cache_tests/insert_empty1/index
new file mode 100644
index 0000000..7401238
--- /dev/null
+++ b/net/data/cache_tests/insert_empty1/index
Binary files differ
diff --git a/net/data/cache_tests/insert_empty2/data_0 b/net/data/cache_tests/insert_empty2/data_0
new file mode 100644
index 0000000..78e67d6
--- /dev/null
+++ b/net/data/cache_tests/insert_empty2/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_empty2/data_1 b/net/data/cache_tests/insert_empty2/data_1
new file mode 100644
index 0000000..c66ca74
--- /dev/null
+++ b/net/data/cache_tests/insert_empty2/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_empty2/data_2 b/net/data/cache_tests/insert_empty2/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_empty2/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_empty2/data_3 b/net/data/cache_tests/insert_empty2/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_empty2/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_empty2/index b/net/data/cache_tests/insert_empty2/index
new file mode 100644
index 0000000..7401238
--- /dev/null
+++ b/net/data/cache_tests/insert_empty2/index
Binary files differ
diff --git a/net/data/cache_tests/insert_empty3/data_0 b/net/data/cache_tests/insert_empty3/data_0
new file mode 100644
index 0000000..fa32996
--- /dev/null
+++ b/net/data/cache_tests/insert_empty3/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_empty3/data_1 b/net/data/cache_tests/insert_empty3/data_1
new file mode 100644
index 0000000..c66ca74
--- /dev/null
+++ b/net/data/cache_tests/insert_empty3/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_empty3/data_2 b/net/data/cache_tests/insert_empty3/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_empty3/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_empty3/data_3 b/net/data/cache_tests/insert_empty3/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_empty3/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_empty3/index b/net/data/cache_tests/insert_empty3/index
new file mode 100644
index 0000000..7401238
--- /dev/null
+++ b/net/data/cache_tests/insert_empty3/index
Binary files differ
diff --git a/net/data/cache_tests/insert_load1/data_0 b/net/data/cache_tests/insert_load1/data_0
new file mode 100644
index 0000000..4a53982
--- /dev/null
+++ b/net/data/cache_tests/insert_load1/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_load1/data_1 b/net/data/cache_tests/insert_load1/data_1
new file mode 100644
index 0000000..3689e0d
--- /dev/null
+++ b/net/data/cache_tests/insert_load1/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_load1/data_2 b/net/data/cache_tests/insert_load1/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_load1/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_load1/data_3 b/net/data/cache_tests/insert_load1/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_load1/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_load1/index b/net/data/cache_tests/insert_load1/index
new file mode 100644
index 0000000..4c0f2a0
--- /dev/null
+++ b/net/data/cache_tests/insert_load1/index
Binary files differ
diff --git a/net/data/cache_tests/insert_load2/data_0 b/net/data/cache_tests/insert_load2/data_0
new file mode 100644
index 0000000..26bcef6
--- /dev/null
+++ b/net/data/cache_tests/insert_load2/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_load2/data_1 b/net/data/cache_tests/insert_load2/data_1
new file mode 100644
index 0000000..b13d284
--- /dev/null
+++ b/net/data/cache_tests/insert_load2/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_load2/data_2 b/net/data/cache_tests/insert_load2/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_load2/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_load2/data_3 b/net/data/cache_tests/insert_load2/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_load2/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_load2/index b/net/data/cache_tests/insert_load2/index
new file mode 100644
index 0000000..c86fc19
--- /dev/null
+++ b/net/data/cache_tests/insert_load2/index
Binary files differ
diff --git a/net/data/cache_tests/insert_one1/data_0 b/net/data/cache_tests/insert_one1/data_0
new file mode 100644
index 0000000..b11dc24
--- /dev/null
+++ b/net/data/cache_tests/insert_one1/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_one1/data_1 b/net/data/cache_tests/insert_one1/data_1
new file mode 100644
index 0000000..370dabb
--- /dev/null
+++ b/net/data/cache_tests/insert_one1/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_one1/data_2 b/net/data/cache_tests/insert_one1/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_one1/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_one1/data_3 b/net/data/cache_tests/insert_one1/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_one1/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_one1/index b/net/data/cache_tests/insert_one1/index
new file mode 100644
index 0000000..3e4e7e3
--- /dev/null
+++ b/net/data/cache_tests/insert_one1/index
Binary files differ
diff --git a/net/data/cache_tests/insert_one2/data_0 b/net/data/cache_tests/insert_one2/data_0
new file mode 100644
index 0000000..36e58cb
--- /dev/null
+++ b/net/data/cache_tests/insert_one2/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_one2/data_1 b/net/data/cache_tests/insert_one2/data_1
new file mode 100644
index 0000000..370dabb
--- /dev/null
+++ b/net/data/cache_tests/insert_one2/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_one2/data_2 b/net/data/cache_tests/insert_one2/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_one2/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_one2/data_3 b/net/data/cache_tests/insert_one2/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_one2/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_one2/index b/net/data/cache_tests/insert_one2/index
new file mode 100644
index 0000000..3e4e7e3
--- /dev/null
+++ b/net/data/cache_tests/insert_one2/index
Binary files differ
diff --git a/net/data/cache_tests/insert_one3/data_0 b/net/data/cache_tests/insert_one3/data_0
new file mode 100644
index 0000000..fd23de4
--- /dev/null
+++ b/net/data/cache_tests/insert_one3/data_0
Binary files differ
diff --git a/net/data/cache_tests/insert_one3/data_1 b/net/data/cache_tests/insert_one3/data_1
new file mode 100644
index 0000000..370dabb
--- /dev/null
+++ b/net/data/cache_tests/insert_one3/data_1
Binary files differ
diff --git a/net/data/cache_tests/insert_one3/data_2 b/net/data/cache_tests/insert_one3/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/insert_one3/data_2
Binary files differ
diff --git a/net/data/cache_tests/insert_one3/data_3 b/net/data/cache_tests/insert_one3/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/insert_one3/data_3
Binary files differ
diff --git a/net/data/cache_tests/insert_one3/index b/net/data/cache_tests/insert_one3/index
new file mode 100644
index 0000000..3e4e7e3
--- /dev/null
+++ b/net/data/cache_tests/insert_one3/index
Binary files differ
diff --git a/net/data/cache_tests/list_loop/data_0 b/net/data/cache_tests/list_loop/data_0
new file mode 100644
index 0000000..7c3eb02
--- /dev/null
+++ b/net/data/cache_tests/list_loop/data_0
Binary files differ
diff --git a/net/data/cache_tests/list_loop/data_1 b/net/data/cache_tests/list_loop/data_1
new file mode 100644
index 0000000..f3e6a70
--- /dev/null
+++ b/net/data/cache_tests/list_loop/data_1
Binary files differ
diff --git a/net/data/cache_tests/list_loop/data_2 b/net/data/cache_tests/list_loop/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/list_loop/data_2
Binary files differ
diff --git a/net/data/cache_tests/list_loop/data_3 b/net/data/cache_tests/list_loop/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/list_loop/data_3
Binary files differ
diff --git a/net/data/cache_tests/list_loop/index b/net/data/cache_tests/list_loop/index
new file mode 100644
index 0000000..c159dab
--- /dev/null
+++ b/net/data/cache_tests/list_loop/index
Binary files differ
diff --git a/net/data/cache_tests/remove_head1/data_0 b/net/data/cache_tests/remove_head1/data_0
new file mode 100644
index 0000000..35fb307
--- /dev/null
+++ b/net/data/cache_tests/remove_head1/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_head1/data_1 b/net/data/cache_tests/remove_head1/data_1
new file mode 100644
index 0000000..370dabb
--- /dev/null
+++ b/net/data/cache_tests/remove_head1/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_head1/data_2 b/net/data/cache_tests/remove_head1/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_head1/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_head1/data_3 b/net/data/cache_tests/remove_head1/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_head1/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_head1/index b/net/data/cache_tests/remove_head1/index
new file mode 100644
index 0000000..3844cbb2
--- /dev/null
+++ b/net/data/cache_tests/remove_head1/index
Binary files differ
diff --git a/net/data/cache_tests/remove_head2/data_0 b/net/data/cache_tests/remove_head2/data_0
new file mode 100644
index 0000000..0ee56b7
--- /dev/null
+++ b/net/data/cache_tests/remove_head2/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_head2/data_1 b/net/data/cache_tests/remove_head2/data_1
new file mode 100644
index 0000000..370dabb
--- /dev/null
+++ b/net/data/cache_tests/remove_head2/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_head2/data_2 b/net/data/cache_tests/remove_head2/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_head2/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_head2/data_3 b/net/data/cache_tests/remove_head2/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_head2/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_head2/index b/net/data/cache_tests/remove_head2/index
new file mode 100644
index 0000000..3844cbb2
--- /dev/null
+++ b/net/data/cache_tests/remove_head2/index
Binary files differ
diff --git a/net/data/cache_tests/remove_head3/data_0 b/net/data/cache_tests/remove_head3/data_0
new file mode 100644
index 0000000..e7252f0
--- /dev/null
+++ b/net/data/cache_tests/remove_head3/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_head3/data_1 b/net/data/cache_tests/remove_head3/data_1
new file mode 100644
index 0000000..370dabb
--- /dev/null
+++ b/net/data/cache_tests/remove_head3/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_head3/data_2 b/net/data/cache_tests/remove_head3/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_head3/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_head3/data_3 b/net/data/cache_tests/remove_head3/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_head3/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_head3/index b/net/data/cache_tests/remove_head3/index
new file mode 100644
index 0000000..3844cbb2
--- /dev/null
+++ b/net/data/cache_tests/remove_head3/index
Binary files differ
diff --git a/net/data/cache_tests/remove_head4/data_0 b/net/data/cache_tests/remove_head4/data_0
new file mode 100644
index 0000000..b61d5b1
--- /dev/null
+++ b/net/data/cache_tests/remove_head4/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_head4/data_1 b/net/data/cache_tests/remove_head4/data_1
new file mode 100644
index 0000000..370dabb
--- /dev/null
+++ b/net/data/cache_tests/remove_head4/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_head4/data_2 b/net/data/cache_tests/remove_head4/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_head4/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_head4/data_3 b/net/data/cache_tests/remove_head4/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_head4/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_head4/index b/net/data/cache_tests/remove_head4/index
new file mode 100644
index 0000000..3844cbb2
--- /dev/null
+++ b/net/data/cache_tests/remove_head4/index
Binary files differ
diff --git a/net/data/cache_tests/remove_load1/data_0 b/net/data/cache_tests/remove_load1/data_0
new file mode 100644
index 0000000..cba61af
--- /dev/null
+++ b/net/data/cache_tests/remove_load1/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_load1/data_1 b/net/data/cache_tests/remove_load1/data_1
new file mode 100644
index 0000000..ba43d7f
--- /dev/null
+++ b/net/data/cache_tests/remove_load1/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_load1/data_2 b/net/data/cache_tests/remove_load1/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_load1/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_load1/data_3 b/net/data/cache_tests/remove_load1/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_load1/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_load1/index b/net/data/cache_tests/remove_load1/index
new file mode 100644
index 0000000..f9cedbf
--- /dev/null
+++ b/net/data/cache_tests/remove_load1/index
Binary files differ
diff --git a/net/data/cache_tests/remove_load2/data_0 b/net/data/cache_tests/remove_load2/data_0
new file mode 100644
index 0000000..fd677c0
--- /dev/null
+++ b/net/data/cache_tests/remove_load2/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_load2/data_1 b/net/data/cache_tests/remove_load2/data_1
new file mode 100644
index 0000000..45b1b5e
--- /dev/null
+++ b/net/data/cache_tests/remove_load2/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_load2/data_2 b/net/data/cache_tests/remove_load2/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_load2/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_load2/data_3 b/net/data/cache_tests/remove_load2/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_load2/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_load2/index b/net/data/cache_tests/remove_load2/index
new file mode 100644
index 0000000..aadd59f
--- /dev/null
+++ b/net/data/cache_tests/remove_load2/index
Binary files differ
diff --git a/net/data/cache_tests/remove_load3/data_0 b/net/data/cache_tests/remove_load3/data_0
new file mode 100644
index 0000000..785d355
--- /dev/null
+++ b/net/data/cache_tests/remove_load3/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_load3/data_1 b/net/data/cache_tests/remove_load3/data_1
new file mode 100644
index 0000000..4bc4671
--- /dev/null
+++ b/net/data/cache_tests/remove_load3/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_load3/data_2 b/net/data/cache_tests/remove_load3/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_load3/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_load3/data_3 b/net/data/cache_tests/remove_load3/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_load3/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_load3/index b/net/data/cache_tests/remove_load3/index
new file mode 100644
index 0000000..2543b60
--- /dev/null
+++ b/net/data/cache_tests/remove_load3/index
Binary files differ
diff --git a/net/data/cache_tests/remove_one1/data_0 b/net/data/cache_tests/remove_one1/data_0
new file mode 100644
index 0000000..da79243
--- /dev/null
+++ b/net/data/cache_tests/remove_one1/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_one1/data_1 b/net/data/cache_tests/remove_one1/data_1
new file mode 100644
index 0000000..c66ca74
--- /dev/null
+++ b/net/data/cache_tests/remove_one1/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_one1/data_2 b/net/data/cache_tests/remove_one1/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_one1/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_one1/data_3 b/net/data/cache_tests/remove_one1/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_one1/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_one1/index b/net/data/cache_tests/remove_one1/index
new file mode 100644
index 0000000..b5ccc85
--- /dev/null
+++ b/net/data/cache_tests/remove_one1/index
Binary files differ
diff --git a/net/data/cache_tests/remove_one2/data_0 b/net/data/cache_tests/remove_one2/data_0
new file mode 100644
index 0000000..120d158
--- /dev/null
+++ b/net/data/cache_tests/remove_one2/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_one2/data_1 b/net/data/cache_tests/remove_one2/data_1
new file mode 100644
index 0000000..c66ca74
--- /dev/null
+++ b/net/data/cache_tests/remove_one2/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_one2/data_2 b/net/data/cache_tests/remove_one2/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_one2/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_one2/data_3 b/net/data/cache_tests/remove_one2/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_one2/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_one2/index b/net/data/cache_tests/remove_one2/index
new file mode 100644
index 0000000..b5ccc85
--- /dev/null
+++ b/net/data/cache_tests/remove_one2/index
Binary files differ
diff --git a/net/data/cache_tests/remove_one3/data_0 b/net/data/cache_tests/remove_one3/data_0
new file mode 100644
index 0000000..1ffac7f
--- /dev/null
+++ b/net/data/cache_tests/remove_one3/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_one3/data_1 b/net/data/cache_tests/remove_one3/data_1
new file mode 100644
index 0000000..c66ca74
--- /dev/null
+++ b/net/data/cache_tests/remove_one3/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_one3/data_2 b/net/data/cache_tests/remove_one3/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_one3/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_one3/data_3 b/net/data/cache_tests/remove_one3/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_one3/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_one3/index b/net/data/cache_tests/remove_one3/index
new file mode 100644
index 0000000..b5ccc85
--- /dev/null
+++ b/net/data/cache_tests/remove_one3/index
Binary files differ
diff --git a/net/data/cache_tests/remove_one4/data_0 b/net/data/cache_tests/remove_one4/data_0
new file mode 100644
index 0000000..39247d9
--- /dev/null
+++ b/net/data/cache_tests/remove_one4/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_one4/data_1 b/net/data/cache_tests/remove_one4/data_1
new file mode 100644
index 0000000..c66ca74
--- /dev/null
+++ b/net/data/cache_tests/remove_one4/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_one4/data_2 b/net/data/cache_tests/remove_one4/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_one4/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_one4/data_3 b/net/data/cache_tests/remove_one4/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_one4/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_one4/index b/net/data/cache_tests/remove_one4/index
new file mode 100644
index 0000000..b5ccc85
--- /dev/null
+++ b/net/data/cache_tests/remove_one4/index
Binary files differ
diff --git a/net/data/cache_tests/remove_tail1/data_0 b/net/data/cache_tests/remove_tail1/data_0
new file mode 100644
index 0000000..828c850
--- /dev/null
+++ b/net/data/cache_tests/remove_tail1/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_tail1/data_1 b/net/data/cache_tests/remove_tail1/data_1
new file mode 100644
index 0000000..a22705c
--- /dev/null
+++ b/net/data/cache_tests/remove_tail1/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_tail1/data_2 b/net/data/cache_tests/remove_tail1/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_tail1/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_tail1/data_3 b/net/data/cache_tests/remove_tail1/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_tail1/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_tail1/index b/net/data/cache_tests/remove_tail1/index
new file mode 100644
index 0000000..e8c1460
--- /dev/null
+++ b/net/data/cache_tests/remove_tail1/index
Binary files differ
diff --git a/net/data/cache_tests/remove_tail2/data_0 b/net/data/cache_tests/remove_tail2/data_0
new file mode 100644
index 0000000..527457e0
--- /dev/null
+++ b/net/data/cache_tests/remove_tail2/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_tail2/data_1 b/net/data/cache_tests/remove_tail2/data_1
new file mode 100644
index 0000000..a22705c
--- /dev/null
+++ b/net/data/cache_tests/remove_tail2/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_tail2/data_2 b/net/data/cache_tests/remove_tail2/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_tail2/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_tail2/data_3 b/net/data/cache_tests/remove_tail2/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_tail2/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_tail2/index b/net/data/cache_tests/remove_tail2/index
new file mode 100644
index 0000000..e8c1460
--- /dev/null
+++ b/net/data/cache_tests/remove_tail2/index
Binary files differ
diff --git a/net/data/cache_tests/remove_tail3/data_0 b/net/data/cache_tests/remove_tail3/data_0
new file mode 100644
index 0000000..00c1bec
--- /dev/null
+++ b/net/data/cache_tests/remove_tail3/data_0
Binary files differ
diff --git a/net/data/cache_tests/remove_tail3/data_1 b/net/data/cache_tests/remove_tail3/data_1
new file mode 100644
index 0000000..a22705c
--- /dev/null
+++ b/net/data/cache_tests/remove_tail3/data_1
Binary files differ
diff --git a/net/data/cache_tests/remove_tail3/data_2 b/net/data/cache_tests/remove_tail3/data_2
new file mode 100644
index 0000000..625dd24
--- /dev/null
+++ b/net/data/cache_tests/remove_tail3/data_2
Binary files differ
diff --git a/net/data/cache_tests/remove_tail3/data_3 b/net/data/cache_tests/remove_tail3/data_3
new file mode 100644
index 0000000..fd1fac4
--- /dev/null
+++ b/net/data/cache_tests/remove_tail3/data_3
Binary files differ
diff --git a/net/data/cache_tests/remove_tail3/index b/net/data/cache_tests/remove_tail3/index
new file mode 100644
index 0000000..e8c1460
--- /dev/null
+++ b/net/data/cache_tests/remove_tail3/index
Binary files differ
diff --git a/net/data/cache_tests/wrong_version/index b/net/data/cache_tests/wrong_version/index
new file mode 100644
index 0000000..64c1c3c
--- /dev/null
+++ b/net/data/cache_tests/wrong_version/index
Binary files differ
diff --git a/net/data/filter_unittests/google.txt b/net/data/filter_unittests/google.txt
new file mode 100644
index 0000000..b06e0fe
--- /dev/null
+++ b/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/net/data/filter_unittests/google.txt.bz2 b/net/data/filter_unittests/google.txt.bz2
new file mode 100644
index 0000000..bb08dff
--- /dev/null
+++ b/net/data/filter_unittests/google.txt.bz2
Binary files differ
diff --git a/net/data/purify/net_unittests.exe_FIM.txt b/net/data/purify/net_unittests.exe_FIM.txt
new file mode 100644
index 0000000..e1835d9
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_FIM.txt
@@ -0,0 +1,11 @@
+Freeing invalid memory in RtlFreeHeap
+Free Location
+ ...
+ base/file_util.cc file_util::ShellCopy(class std::basic_string const &,class std::basic_string const &,bool)
+ base/file_util.cc file_util::CopyDirectory(class std::basic_string const &,class std::basic_string const &,bool)
+ net/disk_cache/backend_unittest.cc ?CopyTestCache@?A0xcda35beb@@YA_NABV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z
+ net/disk_cache/backend_unittest.cc ?TestTransaction@?A0xcda35beb@@YAHABV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@H_N@Z
+ net/disk_cache/backend_unittest.cc DiskCacheTest_Backend_RecoverInsert_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
diff --git a/net/data/purify/net_unittests.exe_FIM_flakey.txt b/net/data/purify/net_unittests.exe_FIM_flakey.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_FIM_flakey.txt
diff --git a/net/data/purify/net_unittests.exe_MLK.txt b/net/data/purify/net_unittests.exe_MLK.txt
new file mode 100644
index 0000000..920135c
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_MLK.txt
@@ -0,0 +1,159 @@
+disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::MatchEntry(basic_string::std const&,UINT,bool)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::OpenEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryEnumeration_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<RankingsNode::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Load(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::LoadNodeAddress(void)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::MatchEntry(basic_string::std const&,UINT,bool)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::OpenEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryEnumeration_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<EntryStore::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Load(void)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::MatchEntry(basic_string::std const&,UINT,bool)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::OpenEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryEnumeration_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::MatchEntry(basic_string::std const&,UINT,bool)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::OpenEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryRead_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<RankingsNode::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Load(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::LoadNodeAddress(void)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::MatchEntry(basic_string::std const&,UINT,bool)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::OpenEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryRead_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<EntryStore::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Load(void)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::MatchEntry(basic_string::std const&,UINT,bool)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::OpenEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryRead_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::BackendImpl::CreateEntry(basic_string<char,char_traits<char>::std,allocator<char>::std>::std const&,Entry::disk_cache * *) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryWithLoad_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<EntryStore::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Data(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::CreateEntry(Addr::disk_cache,basic_string::std const&,UINT)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryWithLoad_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<RankingsNode::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Data(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::CreateEntry(Addr::disk_cache,basic_string::std const&,UINT)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryWithLoad_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::BackendImpl::CreateEntry(basic_string<char,char_traits<char>::std,allocator<char>::std>::std const&,Entry::disk_cache * *) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<EntryStore::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Data(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::CreateEntry(Addr::disk_cache,basic_string::std const&,UINT)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<RankingsNode::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Data(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::CreateEntry(Addr::disk_cache,basic_string::std const&,UINT)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::BackendImpl::CreateEntry(basic_string<char,char_traits<char>::std,allocator<char>::std>::std const&,Entry::disk_cache * *) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_TrimInvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<EntryStore::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Data(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::CreateEntry(Addr::disk_cache,basic_string::std const&,UINT)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_TrimInvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<RankingsNode::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Data(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::CreateEntry(Addr::disk_cache,basic_string::std const&,UINT)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::CreateEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_TrimInvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
diff --git a/net/data/purify/net_unittests.exe_MLK_flakey.txt b/net/data/purify/net_unittests.exe_MLK_flakey.txt
new file mode 100644
index 0000000..686ec56
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_MLK_flakey.txt
@@ -0,0 +1,51 @@
+DirectoryLister::ThreadFunc(void *) [net_unittests.exe]
+Alloc Location
+ ...
+ net/base/directory_lister.cc DirectoryLister::ThreadFunc(void *)
+ ...
+
+DirectoryListerTest_BigDirTest_Test::TestBody(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/base/directory_lister_unittest.cc DirectoryListerTest_BigDirTest_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+std::_W::_Allocate(unsigned int,wchar_t *) [net_unittests.exe]
+Alloc Location
+ ...
+ net/base/directory_lister.cc DirectoryLister::DirectoryLister(class std::basic_string const &,class DirectoryLister::Delegate *)
+ net/base/directory_lister_unittest.cc DirectoryListerTest_BigDirTest_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::EntryImpl::PrepareTarget(int,int,int,bool) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::PrepareTarget(int,int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::WriteData(int,int,char const*,int,CallbackRunner *,bool)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_TrimInvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::EntryImpl::PrepareTarget(int,int,int,bool) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::PrepareTarget(int,int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::WriteData(int,int,char const*,int,CallbackRunner *,bool)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntry_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+disk_cache::StorageBlock<EntryStore::disk_cache>::AllocateData(void) [net_unittests.exe]
+Alloc Location
+ ...
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::AllocateData(void)
+ net/disk_cache/storage_block-inl.h disk_cache::StorageBlock::Load(void)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::NewEntry(Addr::disk_cache,EntryImpl::disk_cache * *,bool *)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::MatchEntry(basic_string::std const&,UINT,bool)
+ net/disk_cache/backend_impl.cc disk_cache::BackendImpl::OpenEntry(basic_string::std const&,Entry::disk_cache * *)
+ net/disk_cache/backend_unittest.cc DiskCacheBackendTest_InvalidEntryRead_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
diff --git a/net/data/purify/net_unittests.exe_PAR.txt b/net/data/purify/net_unittests.exe_PAR.txt
new file mode 100644
index 0000000..eebf934
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_PAR.txt
@@ -0,0 +1,7 @@
+WideCharToMultiByte: Invalid size (0x27) for destination buffer.
+Error Location
+ ...
+ net/url_request/url_request_unittest.cc URLRequestTest_ResolveShortcutTest_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
diff --git a/net/data/purify/net_unittests.exe_PAR_flakey.txt b/net/data/purify/net_unittests.exe_PAR_flakey.txt
new file mode 100644
index 0000000..22a2300
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_PAR_flakey.txt
@@ -0,0 +1,8 @@
+UnmapViewOfFile(0x47d0000) arg #1 (lpBaseAddress) not within a mapped view.
+Error Location
+ ...
+ base/file_util.cc file_util::Delete(class std::basic_string const &,bool)
+ net/disk_cache/backend_impl.cc CleanupTask::Run(void)
+ base/message_loop.cc MessageLoop::RunTask(Task *)
+ ^^^
+
diff --git a/net/data/purify/net_unittests.exe_UMR.txt b/net/data/purify/net_unittests.exe_UMR.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_UMR.txt
diff --git a/net/data/purify/net_unittests.exe_UMR_flakey.txt b/net/data/purify/net_unittests.exe_UMR_flakey.txt
new file mode 100644
index 0000000..728e204
--- /dev/null
+++ b/net/data/purify/net_unittests.exe_UMR_flakey.txt
@@ -0,0 +1,50 @@
+Uninitialized memory read in WriteFileEx
+Error Location
+ ...
+ net/disk_cache/file.cc disk_cache::File::AsyncWrite(void const*,UINT,UINT,bool,FileIOCallback::disk_cache *,bool *)
+ net/disk_cache/file.cc disk_cache::File::Write(void const*,UINT,UINT,FileIOCallback::disk_cache *,bool *)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::Flush(int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::~EntryImpl(void)
+ chrome/release/net_unittests.exe disk_cache::EntryImpl::`vector deleting destructor'(UINT)
+ base/ref_counted.h base::RefCounted::Release(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::Close(void)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest::GrowData(void)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest_GrowData_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+Alloc Location
+ ...
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::MoveToLocalBuffer(int)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::GrowUserBuffer(int,int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::PrepareTarget(int,int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::WriteData(int,int,char const*,int,CallbackRunner *,bool)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest::GrowData(void)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest_GrowData_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
+Uninitialized memory read in WriteFileEx
+Error Location
+ ...
+ net/disk_cache/file.cc disk_cache::File::AsyncWrite(void const*,UINT,UINT,bool,FileIOCallback::disk_cache *,bool *)
+ net/disk_cache/file.cc disk_cache::File::Write(void const*,UINT,UINT,FileIOCallback::disk_cache *,bool *)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::Flush(int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::~EntryImpl(void)
+ chrome/release/net_unittests.exe disk_cache::EntryImpl::`scalar deleting destructor'(UINT)
+ base/ref_counted.h base::RefCounted::Release(void)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::Close(void)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest::GrowData(void)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest_GrowData_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+Alloc Location
+ ...
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::MoveToLocalBuffer(int)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::GrowUserBuffer(int,int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::PrepareTarget(int,int,int,bool)
+ net/disk_cache/entry_impl.cc disk_cache::EntryImpl::WriteData(int,int,char const*,int,CallbackRunner *,bool)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest::GrowData(void)
+ net/disk_cache/entry_unittest.cc DiskCacheEntryTest_GrowData_Test::TestBody(void)
+ testing/gtest/src/gtest.cc testing::Test::Run(void)
+ ^^^
+
diff --git a/net/data/url_request_unittest/content-type-normalization.html b/net/data/url_request_unittest/content-type-normalization.html
new file mode 100644
index 0000000..4e54a78
--- /dev/null
+++ b/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/net/data/url_request_unittest/content-type-normalization.html.mock-http-headers b/net/data/url_request_unittest/content-type-normalization.html.mock-http-headers
new file mode 100644
index 0000000..8004f45
--- /dev/null
+++ b/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/net/data/url_request_unittest/redirect-test.html b/net/data/url_request_unittest/redirect-test.html
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/net/data/url_request_unittest/redirect-test.html
@@ -0,0 +1 @@
+hello
diff --git a/net/data/url_request_unittest/redirect-test.html.mock-http-headers b/net/data/url_request_unittest/redirect-test.html.mock-http-headers
new file mode 100644
index 0000000..9fdd1c0
--- /dev/null
+++ b/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/net/data/url_request_unittest/redirect-to-file.html b/net/data/url_request_unittest/redirect-to-file.html
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/net/data/url_request_unittest/redirect-to-file.html
@@ -0,0 +1 @@
+hello
diff --git a/net/data/url_request_unittest/redirect-to-file.html.mock-http-headers b/net/data/url_request_unittest/redirect-to-file.html.mock-http-headers
new file mode 100644
index 0000000..10994b7
--- /dev/null
+++ b/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/net/data/url_request_unittest/with-headers.html b/net/data/url_request_unittest/with-headers.html
new file mode 100644
index 0000000..364322d
--- /dev/null
+++ b/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/net/data/url_request_unittest/with-headers.html.mock-http-headers b/net/data/url_request_unittest/with-headers.html.mock-http-headers
new file mode 100644
index 0000000..ba8c15f
--- /dev/null
+++ b/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/net/disk_cache/addr.h b/net/disk_cache/addr.h
new file mode 100644
index 0000000..3c6923a
--- /dev/null
+++ b/net/disk_cache/addr.h
@@ -0,0 +1,178 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 "base/basictypes.h"
+#include "base/logging.h"
+#include "net/disk_cache/disk_format.h"
+
+namespace disk_cache {
+
+enum FileType {
+ EXTERNAL = 0,
+ RANKINGS = 1,
+ BLOCK_256,
+ BLOCK_1K,
+ BLOCK_4K,
+};
+
+const int kMaxBlockSize = 4096 * 4;
+const int kMaxBlockFile = 255;
+const int kMaxNumBlocks = 4;
+const int kFirstAdditionlBlockFile = 4;
+
+// 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
+//
+// 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 Addr {
+ public:
+ 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 {
+ DCHECK(is_block_file());
+ return value_ & kStartBlockMask;
+ }
+
+ int num_blocks() const {
+ DCHECK(is_block_file() || !value_);
+ return ((value_ & kNumBlocksMask) >> kNumBlocksOffset) + 1;
+ }
+
+ bool SetFileNumber(int file_number) {
+ DCHECK(is_separate_file());
+ if (file_number & ~kFileNameMask)
+ return false;
+ value_ = kInitializedMask | file_number;
+ return true;
+ }
+
+ int BlockSize() const {
+ return BlockSizeForFileType(file_type());
+ }
+
+ 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;
+ 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;
+ }
+
+ private:
+ static const uint32 kInitializedMask = 0x80000000;
+ static const uint32 kFileTypeMask = 0x70000000;
+ static const uint32 kFileTypeOffset = 28;
+ 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/net/disk_cache/addr_unittest.cc b/net/disk_cache/addr_unittest.cc
new file mode 100644
index 0000000..a06cf35
--- /dev/null
+++ b/net/disk_cache/addr_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/addr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace disk_cache {
+
+TEST(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(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(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());
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/backend_impl.cc b/net/disk_cache/backend_impl.cc
new file mode 100644
index 0000000..59dd8e1
--- /dev/null
+++ b/net/disk_cache/backend_impl.cc
@@ -0,0 +1,1169 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/backend_impl.h"
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/scoped_handle.h"
+#include "base/string_util.h"
+#include "base/timer.h"
+#include "base/worker_pool.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/errors.h"
+#include "net/disk_cache/hash.h"
+
+namespace {
+
+const wchar_t* kIndexName = L"index";
+const int kCleanUpMargin = 1024 * 1024;
+const int kMaxOldFolders = 100;
+
+// Seems like ~160 MB correspond to ~50k entries.
+const int k64kEntriesStore = 160 * 1000 * 1000;
+const int kBaseTableLen = 64 * 1024;
+const int kDefaultCacheSize = 80 * 1024 * 1024;
+
+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;
+}
+
+// Deletes all the files on path that match search_name pattern.
+// Do not call this function with "*" as search_name.
+bool DeleteFiles(const wchar_t* path, const wchar_t* search_name) {
+ std::wstring name(path);
+ name += search_name;
+ DCHECK(search_name[0] == L'\\');
+
+ WIN32_FIND_DATA data;
+ ScopedFindFileHandle handle(FindFirstFile(name.c_str(), &data));
+ if (!handle.IsValid()) {
+ DWORD error = GetLastError();
+ return ERROR_FILE_NOT_FOUND == error;
+ }
+ std::wstring adjusted_path(path);
+ adjusted_path += L'\\';
+ do {
+ std::wstring current(adjusted_path);
+ current += data.cFileName;
+ if (!DeleteFile(current.c_str()))
+ return false;
+ } while (FindNextFile(handle, &data));
+ return true;
+}
+
+int LowWaterAdjust(int high_water) {
+ if (high_water < kCleanUpMargin)
+ return 0;
+
+ return high_water - kCleanUpMargin;
+}
+
+// ------------------------------------------------------------------------
+
+// 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".
+std::wstring GetPrefixedName(const std::wstring& path, const std::wstring& name,
+ int index) {
+ std::wstring prefixed(path);
+ std::wstring tmp = StringPrintf(L"%s%s_%03d", L"old_", name.c_str(), index);
+ file_util::AppendToPath(&prefixed, tmp);
+ return prefixed;
+}
+
+// This is a simple Task to cleanup old caches.
+class CleanupTask : public Task {
+ public:
+ CleanupTask(const std::wstring& path, const std::wstring& name)
+ : path_(path), name_(name) {}
+
+ virtual void Run();
+
+ private:
+ std::wstring path_;
+ std::wstring name_;
+ DISALLOW_EVIL_CONSTRUCTORS(CleanupTask);
+};
+
+void CleanupTask::Run() {
+ for (int i = 0; i < kMaxOldFolders; i++) {
+ std::wstring to_delete = GetPrefixedName(path_, name_, i);
+
+ // We do not create subfolders on the cache. If there is any subfolder, it
+ // was created by someone else so we don't want to delete it.
+ file_util::Delete(to_delete, false);
+ }
+}
+
+// Returns a full path to reneme the current cache, in order to delete it. path
+// is the current folder location, and name is the current folder name.
+std::wstring GetTempCacheName(const std::wstring& path,
+ const std::wstring& name) {
+ // We'll attempt to have up to kMaxOldFolders folders for deletion.
+ for (int i = 0; i < kMaxOldFolders; i++) {
+ std::wstring to_delete = GetPrefixedName(path, name, i);
+ if (!file_util::PathExists(to_delete))
+ return to_delete;
+ }
+ return std::wstring();
+}
+
+// Moves the cache files to a new folder and creates a task to delete them.
+bool DelayedCacheCleanup(const std::wstring& full_path) {
+ std::wstring path(full_path);
+ file_util::TrimTrailingSeparator(&path);
+
+ std::wstring name = file_util::GetFilenameFromPath(path);
+ file_util::TrimFilename(&path);
+
+ std::wstring to_delete = GetTempCacheName(path, name);
+ if (to_delete.empty()) {
+ LOG(ERROR) << "Unable to get another cache folder";
+ return false;
+ }
+
+ // 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(full_path.c_str(), to_delete.c_str(), 0)) {
+ DWORD error = GetLastError();
+ LOG(ERROR) << "Unable to rename cache folder";
+ return false;
+ }
+
+ WorkerPool::Run(new CleanupTask(path, name), true);
+ return true;
+}
+
+// ------------------------------------------------------------------------
+
+class TimerTask : public Task {
+ public:
+ explicit TimerTask(disk_cache::BackendImpl* backend) : backend_(backend) {}
+ ~TimerTask() {}
+
+ virtual void Run() {
+ backend_->OnStatsTimer();
+ }
+
+ private:
+ disk_cache::BackendImpl* backend_;
+};
+
+} // namespace
+
+namespace disk_cache {
+
+// If the initialization of the cache fails, and force is true, we will discard
+// the whole cache and create a new one. In order to process a potentially large
+// number of files, we'll rename the cache folder to old_ + original_name +
+// number, (located on the same parent folder), and spawn a worker thread to
+// delete all the files on all the stale cache folders. The whole process can
+// still fail if we are not able to rename the cache folder (for instance due to
+// a sharing violation), and in that case a cache for this profile (on the
+// desired path) cannot be created.
+Backend* CreateCacheBackend(const std::wstring& full_path, bool force,
+ int max_bytes) {
+ BackendImpl* cache = new BackendImpl(full_path);
+ cache->SetMaxSize(max_bytes);
+ if (cache->Init())
+ return cache;
+
+ delete cache;
+ if (!force)
+ return NULL;
+
+ if (!DelayedCacheCleanup(full_path))
+ return NULL;
+
+ // The worker thread will start deleting files soon, but the original folder
+ // is not there anymore... let's create a new set of files.
+ cache = new BackendImpl(full_path);
+ cache->SetMaxSize(max_bytes);
+ if (cache->Init())
+ return cache;
+
+ delete cache;
+ LOG(ERROR) << "Unable to create cache";
+ return NULL;
+}
+
+// ------------------------------------------------------------------------
+
+bool BackendImpl::Init() {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ bool create_files = false;
+ if (!InitBackingStore(&create_files))
+ return false;
+
+ num_refs_ = num_pending_io_ = max_refs_ = 0;
+
+ if (!restarted_) {
+ // Create a recurrent timer of 30 secs.
+ int timer_delay = unit_test_ ? 1000 : 30000;
+ TimerTask* task = new TimerTask(this);
+ timer_task_ = task;
+ timer_ = MessageLoop::current()->timer_manager()->StartTimer(timer_delay,
+ task, true);
+ }
+
+ init_ = true;
+
+ if (!CheckIndex())
+ return false;
+
+ // 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 a we start
+ // having collisions.
+ data_->header.this_id++;
+ if (!data_->header.this_id)
+ data_->header.this_id++;
+
+ if (!block_files_.Init(create_files))
+ return false;
+
+ // stats_ and rankings_ may end up calling back to us so we better be enabled.
+ disabled_ = false;
+ if (!stats_.Init(this, &data_->header.stats))
+ return false;
+
+ disabled_ = !rankings_.Init(this);
+
+ return !disabled_;
+}
+
+BackendImpl::~BackendImpl() {
+ Trace("Backend destructor");
+ if (!init_)
+ return;
+
+ MessageLoop::current()->timer_manager()->StopTimer(timer_);
+ delete timer_;
+ delete timer_task_;
+
+ 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 (we need an alertable wait).
+ // The idea is to let other threads do usefull work and at the same time
+ // allow more than one IO to finish... 20 mS later, we process all queued
+ // APCs and see if we have to repeat the wait.
+ Sleep(20);
+ SleepEx(0, TRUE);
+ }
+ DCHECK(!num_refs_);
+}
+
+bool BackendImpl::InitBackingStore(bool* file_created) {
+ // This call fails if the folder exists.
+ file_util::CreateDirectory(path_);
+
+ std::wstring index_name(path_);
+ file_util::AppendToPath(&index_name, kIndexName);
+
+ HANDLE file = CreateFile(index_name.c_str(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL);
+
+ if (INVALID_HANDLE_VALUE == file)
+ return false;
+
+ bool ret = true;
+ if (ERROR_ALREADY_EXISTS != GetLastError()) {
+ *file_created = true;
+ ret = CreateBackingStore(file);
+ } else {
+ *file_created = false;
+ }
+
+ CloseHandle(file);
+ if (!ret)
+ return false;
+
+ index_ = new MappedFile();
+ data_ = reinterpret_cast<Index*>(index_->Init(index_name, 0));
+ return true;
+}
+
+// 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(HANDLE file) {
+ AdjustMaxCacheSize(0);
+
+ IndexHeader header;
+ header.table_len = DesiredIndexTableLen(max_size_);
+
+ DWORD actual;
+ if (!WriteFile(file, &header, sizeof(header), &actual, NULL) ||
+ sizeof(header) != actual)
+ return false;
+
+ LONG size = static_cast<LONG>(GetIndexSize(header.table_len));
+
+ if (INVALID_SET_FILE_POINTER == SetFilePointer(file, size, NULL, FILE_BEGIN))
+ return false;
+
+ if (!SetEndOfFile(file))
+ return false;
+
+ return true;
+}
+
+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;
+
+ max_size_ = max_bytes;
+ return true;
+}
+
+int32 BackendImpl::GetEntryCount() const {
+ if (!index_)
+ return 0;
+ return data_->header.num_entries;
+}
+
+bool BackendImpl::OpenEntry(const std::string& key, Entry** entry) {
+ if (disabled_)
+ return false;
+
+ uint32 hash = Hash(key);
+
+ EntryImpl* cache_entry = MatchEntry(key, hash, false);
+ if (!cache_entry) {
+ stats_.OnEvent(Stats::OPEN_MISS);
+ return false;
+ }
+
+ DCHECK(entry);
+ *entry = cache_entry;
+
+ stats_.OnEvent(Stats::OPEN_HIT);
+ return true;
+}
+
+bool BackendImpl::CreateEntry(const std::string& key, Entry** entry) {
+ if (disabled_ || key.empty())
+ return false;
+
+ uint32 hash = Hash(key);
+
+ scoped_refptr<EntryImpl> parent;
+ Addr entry_address(data_->table[hash & mask_]);
+ if (entry_address.is_initialized()) {
+ EntryImpl* parent_entry = MatchEntry(key, hash, true);
+ if (!parent_entry) {
+ stats_.OnEvent(Stats::CREATE_MISS);
+ Trace("create entry miss ");
+ return false;
+ }
+ parent.swap(&parent_entry);
+ }
+
+ int num_blocks;
+ size_t key1_len = sizeof(EntryStore) - offsetof(EntryStore, key);
+ if (key.size() < key1_len || key.size() > kMaxInternalKeyLength)
+ num_blocks = 1;
+ else
+ num_blocks = static_cast<int>((key.size() - key1_len) / 256 + 2);
+
+ if (!block_files_.CreateBlock(BLOCK_256, num_blocks, &entry_address)) {
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return false;
+ }
+
+ 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 false;
+ }
+
+ scoped_refptr<EntryImpl> cache_entry(new EntryImpl(this, entry_address));
+ 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 false;
+ }
+
+ if (parent.get())
+ parent->SetNextAddress(entry_address);
+
+ block_files_.GetFile(entry_address)->Store(cache_entry->entry());
+ block_files_.GetFile(node_address)->Store(cache_entry->rankings());
+
+ data_->header.num_entries++;
+ DCHECK(data_->header.num_entries > 0);
+ rankings_.Insert(cache_entry->rankings(), true);
+ if (!parent.get())
+ data_->table[hash & mask_] = entry_address.value();
+
+ DCHECK(entry);
+ *entry = NULL;
+ cache_entry.swap(reinterpret_cast<EntryImpl**>(entry));
+
+ stats_.OnEvent(Stats::CREATE_HIT);
+ Trace("create entry hit ");
+ return true;
+}
+
+EntryImpl* BackendImpl::MatchEntry(const std::string& key, uint32 hash,
+ bool find_parent) {
+ Addr address(data_->table[hash & mask_]);
+ EntryImpl* cache_entry = NULL;
+ EntryImpl* parent_entry = NULL;
+ bool found = false;
+
+ for (;;) {
+ if (disabled_)
+ break;
+
+ if (!address.is_initialized()) {
+ if (find_parent)
+ found = true;
+ break;
+ }
+
+ bool dirty;
+ int error = NewEntry(address, &cache_entry, &dirty);
+
+ if (error || 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) {
+ parent_entry->SetNextAddress(child);
+ parent_entry->Release();
+ parent_entry = NULL;
+ } else {
+ data_->table[hash & mask_] = child.value();
+ }
+
+ if (!error) {
+ // It is important to call DestroyInvalidEntry after removing this
+ // entry from the table.
+ DestroyInvalidEntry(address, cache_entry);
+ cache_entry->Release();
+ cache_entry = NULL;
+ } else {
+ Trace("NewEntry failed on MatchEntry 0x%x", address.value());
+ }
+
+ // Restart the search.
+ address.set_value(data_->table[hash & mask_]);
+ continue;
+ }
+
+ if (cache_entry->IsSameEntry(key, hash)) {
+ cache_entry = EntryImpl::Update(cache_entry);
+ found = true;
+ break;
+ }
+ cache_entry = EntryImpl::Update(cache_entry);
+ if (parent_entry)
+ parent_entry->Release();
+ parent_entry = cache_entry;
+ cache_entry = NULL;
+ if (!parent_entry)
+ break;
+
+ address.set_value(parent_entry->GetNextAddress());
+ }
+
+ if (parent_entry && (!find_parent || !found)) {
+ parent_entry->Release();
+ parent_entry = NULL;
+ }
+
+ if (cache_entry && (find_parent || !found)) {
+ cache_entry->Release();
+ cache_entry = NULL;
+ }
+
+ return find_parent ? parent_entry : cache_entry;
+}
+
+bool BackendImpl::DoomEntry(const std::string& key) {
+ if (disabled_)
+ return false;
+
+ EntryImpl* entry;
+ if (!OpenEntry(key, reinterpret_cast<Entry**>(&entry)))
+ return false;
+
+ entry->Doom();
+ entry->Release();
+ return true;
+}
+
+void BackendImpl::InternalDoomEntry(EntryImpl* entry) {
+ uint32 hash = entry->GetHash();
+ std::string key = entry->GetKey();
+ EntryImpl* parent_entry = MatchEntry(key, hash, true);
+ CacheAddr child(entry->GetNextAddress());
+
+ Trace("Doom entry 0x%p", entry);
+
+ rankings_.Remove(entry->rankings());
+
+ entry->InternalDoom();
+
+ if (parent_entry) {
+ parent_entry->SetNextAddress(Addr(child));
+ parent_entry->Release();
+ } else {
+ data_->table[hash & mask_] = child;
+ }
+
+ data_->header.num_entries--;
+ DCHECK(data_->header.num_entries >= 0);
+ stats_.OnEvent(Stats::DOOM_ENTRY);
+}
+
+bool BackendImpl::DoomAllEntries() {
+ if (!num_refs_) {
+ index_ = NULL;
+ block_files_.CloseFiles();
+ rankings_.Reset();
+ DeleteFiles(path_.c_str(), L"\\f_*");
+ DeleteFiles(path_.c_str(), L"\\data_*");
+
+ std::wstring index(path_);
+ file_util::AppendToPath(&index, kIndexName);
+ DeleteFile(index.c_str());
+ init_ = false;
+ restarted_ = true;
+ return Init();
+ } else {
+ if (disabled_)
+ return false;
+
+ TrimCache(true);
+ stats_.OnEvent(Stats::DOOM_CACHE);
+ return true;
+ }
+}
+
+bool BackendImpl::DoomEntriesBetween(const Time initial_time,
+ const Time end_time) {
+ if (end_time.is_null())
+ return DoomEntriesSince(initial_time);
+
+ DCHECK(end_time >= initial_time);
+
+ if (disabled_)
+ return false;
+
+ Entry* node, *next;
+ void* iter = NULL;
+ if (!OpenNextEntry(&iter, &next))
+ return true;
+
+ while (next) {
+ node = next;
+ if (!OpenNextEntry(&iter, &next))
+ next = NULL;
+
+ if (node->GetLastUsed() >= initial_time &&
+ node->GetLastUsed() < end_time) {
+ node->Doom();
+ } else if (node->GetLastUsed() < initial_time) {
+ if (next)
+ next->Close();
+ next = NULL;
+ EndEnumeration(&iter);
+ }
+
+ node->Close();
+ }
+
+ return true;
+}
+
+// We use OpenNextEntry to retrieve elements from the cache, until we get
+// entries that are too old.
+bool BackendImpl::DoomEntriesSince(const Time initial_time) {
+ if (disabled_)
+ return false;
+
+ for (;;) {
+ Entry* entry;
+ void* iter = NULL;
+ if (!OpenNextEntry(&iter, &entry))
+ return true;
+
+ if (initial_time > entry->GetLastUsed()) {
+ entry->Close();
+ EndEnumeration(&iter);
+ return true;
+ }
+
+ entry->Doom();
+ entry->Close();
+ EndEnumeration(&iter); // Dooming the entry invalidates the iterator.
+ }
+}
+
+bool BackendImpl::OpenNextEntry(void** iter, Entry** next_entry) {
+ if (disabled_)
+ return false;
+
+ Rankings::ScopedRankingsBlock rankings(&rankings_,
+ reinterpret_cast<CacheRankingsBlock*>(*iter));
+ Rankings::ScopedRankingsBlock next(&rankings_,
+ rankings_.GetNext(rankings.get()));
+ *next_entry = NULL;
+ *iter = NULL;
+ if (!next.get())
+ return false;
+
+ scoped_refptr<EntryImpl> entry;
+ if (next->Data()->pointer) {
+ entry = reinterpret_cast<EntryImpl*>(next->Data()->pointer);
+ } else {
+ bool dirty;
+ EntryImpl* temp = NULL;
+ if (NewEntry(Addr(next->Data()->contents), &temp, &dirty))
+ return false;
+ entry.swap(&temp);
+
+ if (dirty) {
+ // We cannot trust this entry. Call MatchEntry to go through the regular
+ // path and take the appropriate action.
+ std::string key = entry->GetKey();
+ uint32 hash = entry->GetHash();
+ entry = NULL; // Release the entry.
+ temp = MatchEntry(key, hash, false);
+ if (temp)
+ temp->Release();
+
+ return false;
+ }
+
+ entry.swap(&temp);
+ temp = EntryImpl::Update(temp); // Update returns an adref'd entry.
+ entry.swap(&temp);
+ if (!entry.get())
+ return false;
+ }
+
+ entry.swap(reinterpret_cast<EntryImpl**>(next_entry));
+ *iter = next.release();
+ return true;
+}
+
+void BackendImpl::EndEnumeration(void** iter) {
+ Rankings::ScopedRankingsBlock rankings(&rankings_,
+ reinterpret_cast<CacheRankingsBlock*>(*iter));
+ *iter = NULL;
+}
+
+void BackendImpl::GetStats(StatsItems* stats) {
+ if (disabled_)
+ return;
+
+ std::pair<std::string, std::string> item;
+
+ item.first = "Entries";
+ item.second = StringPrintf("%d", data_->header.num_entries);
+ stats->push_back(item);
+
+ item.first = "Pending IO";
+ item.second = StringPrintf("%d", num_pending_io_);
+ stats->push_back(item);
+
+ item.first = "Max size";
+ item.second = StringPrintf("%d", max_size_);
+ stats->push_back(item);
+
+ item.first = "Current size";
+ item.second = StringPrintf("%d", data_->header.num_bytes);
+ stats->push_back(item);
+
+ stats_.GetItems(stats);
+}
+
+void BackendImpl::TrimCache(bool empty) {
+ Trace("*** Trim Cache ***");
+ if (disabled_)
+ return;
+
+ Rankings::ScopedRankingsBlock node(&rankings_);
+ Rankings::ScopedRankingsBlock next(&rankings_, rankings_.GetPrev(node.get()));
+ DCHECK(next.get());
+ int target_size = empty ? 0 : LowWaterAdjust(max_size_);
+ while (data_->header.num_bytes > target_size && next.get()) {
+ node.reset(next.release());
+ next.reset(rankings_.GetPrev(node.get()));
+ if (!node->Data()->pointer || empty) {
+ // This entry is not being used by anybody.
+ EntryImpl* entry;
+ bool dirty;
+ if (NewEntry(Addr(node->Data()->contents), &entry, &dirty)) {
+ Trace("NewEntry failed on Trim 0x%x", node->address().value());
+ continue;
+ }
+
+ if (node->Data()->pointer) {
+ entry = EntryImpl::Update(entry);
+ }
+ entry->Doom();
+ entry->Release();
+ if (!empty)
+ stats_.OnEvent(Stats::TRIM_ENTRY);
+ }
+ }
+
+ Trace("*** Trim Cache end ***");
+ return;
+}
+
+void BackendImpl::DestroyInvalidEntry(Addr address, EntryImpl* entry) {
+ LOG(WARNING) << "Destroying invalid entry.";
+ Trace("Destroying invalid entry 0x%p", entry);
+
+ rankings_.Remove(entry->rankings());
+ entry->SetPointerForInvalidEntry(GetCurrentEntryId());
+
+ entry->InternalDoom();
+
+ data_->header.num_entries--;
+ DCHECK(data_->header.num_entries >= 0);
+ stats_.OnEvent(Stats::INVALID_ENTRY);
+}
+
+int BackendImpl::NewEntry(Addr address, EntryImpl** entry, bool* dirty) {
+ scoped_refptr<EntryImpl> cache_entry(new EntryImpl(this, address));
+ IncreaseNumRefs();
+ *entry = NULL;
+
+ if (!address.is_initialized() || address.is_separate_file() ||
+ address.file_type() != BLOCK_256) {
+ LOG(WARNING) << "Wrong entry address.";
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (!cache_entry->entry()->Load())
+ return ERR_READ_FAILURE;
+
+ if (!cache_entry->SanityCheck()) {
+ LOG(WARNING) << "Messed up entry found.";
+ return ERR_INVALID_ENTRY;
+ }
+
+ if (!cache_entry->LoadNodeAddress())
+ return ERR_READ_FAILURE;
+
+ *dirty = cache_entry->IsDirty(GetCurrentEntryId());
+
+ // Prevent overwriting the dirty flag on the destructor.
+ cache_entry->ClearDirtyFlag();
+
+ if (!rankings_.SanityCheck(cache_entry->rankings(), false))
+ return ERR_INVALID_LINKS;
+
+ cache_entry.swap(entry);
+ return 0;
+}
+
+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);
+}
+
+void BackendImpl::CacheEntryDestroyed() {
+ DecreaseNumRefs();
+}
+
+void BackendImpl::AddStorageSize(int32 bytes) {
+ data_->header.num_bytes += bytes;
+ DCHECK(data_->header.num_bytes >= 0);
+
+ if (data_->header.num_bytes > max_size_)
+ TrimCache(false);
+}
+
+void BackendImpl::SubstractStorageSize(int32 bytes) {
+ data_->header.num_bytes -= bytes;
+ DCHECK(data_->header.num_bytes >= 0);
+}
+
+std::wstring BackendImpl::GetFileName(Addr address) const {
+ if (!address.is_separate_file() || !address.is_initialized()) {
+ NOTREACHED();
+ return std::wstring();
+ }
+
+ std::wstring name = StringPrintf(L"%s\\f_%06x", path_.c_str(),
+ address.FileNumber());
+ return name;
+}
+
+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) && !success; i++) {
+ if (!file_address.SetFileNumber(file_number)) {
+ file_number = 1;
+ continue;
+ }
+ std::wstring name = GetFileName(file_address);
+ ScopedHandle file(CreateFile(name.c_str(), GENERIC_WRITE | GENERIC_READ,
+ FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0,
+ NULL));
+ if (!file.IsValid())
+ continue;
+
+ success = true;
+ }
+
+ DCHECK(success);
+ if (!success)
+ return false;
+
+ data_->header.last_file = file_number;
+ address->set_value(file_address.value());
+ return true;
+}
+
+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;
+ return num_entries;
+ }
+
+ if (num_entries != data_->header.num_entries) {
+ LOG(ERROR) << "Number of entries mismatch";
+ return ERR_NUM_ENTRIES_MISMATCH;
+ }
+
+ return CheckAllEntries();
+}
+
+void BackendImpl::CriticalError(int error) {
+ LOG(ERROR) << "Critical error found " << error;
+ if (disabled_)
+ return;
+
+ LogStats();
+
+ // 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_)
+ RestartCache();
+}
+
+bool BackendImpl::CheckIndex() {
+ if (!data_) {
+ LOG(ERROR) << "Unable to map Index file";
+ return false;
+ }
+
+ size_t current_size = index_->GetLength();
+ if (current_size < sizeof(Index)) {
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ if (kIndexMagic != data_->header.magic ||
+ kCurrentVersion != data_->header.version) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+
+ if (data_->header.table_len) {
+ 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);
+ } else {
+ max_size_ = kDefaultCacheSize;
+ }
+
+ if (data_->header.num_bytes < 0 ||
+ data_->header.num_bytes > max_size_ * 11 / 10) {
+ LOG(ERROR) << "Invalid cache (current) size";
+ return false;
+ }
+
+ if (data_->header.num_entries < 0) {
+ LOG(ERROR) << "Invalid number of entries";
+ return false;
+ }
+
+ if (!mask_)
+ mask_ = DesiredIndexTableLen(max_size_) - 1;
+
+ return true;
+}
+
+int BackendImpl::CheckAllEntries() {
+ int num_dirty = 0;
+ int num_entries = 0;
+ DCHECK(mask_ < kuint32max);
+ for (int i = 0; i <= static_cast<int>(mask_); i++) {
+ Addr address(data_->table[i]);
+ if (!address.is_initialized())
+ continue;
+ for (;;) {
+ bool dirty;
+ EntryImpl* tmp;
+ int ret = NewEntry(address, &tmp, &dirty);
+ if (ret)
+ return ret;
+ scoped_refptr<EntryImpl> cache_entry;
+ cache_entry.swap(&tmp);
+
+ if (dirty)
+ num_dirty++;
+ else if (CheckEntry(cache_entry.get()))
+ num_entries++;
+ else
+ return ERR_INVALID_ENTRY;
+
+ address.set_value(cache_entry->GetNextAddress());
+ if (!address.is_initialized())
+ break;
+ }
+ }
+
+ if (num_entries + num_dirty != data_->header.num_entries) {
+ LOG(ERROR) << "Number of entries mismatch";
+ return ERR_NUM_ENTRIES_MISMATCH;
+ }
+
+ return num_dirty;
+}
+
+bool BackendImpl::CheckEntry(EntryImpl* cache_entry) {
+ RankingsNode* rankings = cache_entry->rankings()->Data();
+ return !rankings->pointer;
+}
+
+void BackendImpl::LogStats() {
+ StatsItems stats;
+ GetStats(&stats);
+
+ for (size_t index = 0; index < stats.size(); index++) {
+ LOG(INFO) << stats[index].first << ": " << stats[index].second;
+ }
+}
+
+void BackendImpl::RestartCache() {
+ index_ = NULL;
+ block_files_.CloseFiles();
+ rankings_.Reset();
+
+ DelayedCacheCleanup(path_);
+
+ init_ = false;
+ restarted_ = true;
+ int64 errors = stats_.GetCounter(Stats::FATAL_ERROR);
+ if (Init())
+ stats_.SetCounter(Stats::FATAL_ERROR, errors + 1);
+}
+
+void BackendImpl::RecoveredEntry(CacheRankingsBlock* rankings) {
+ Addr address(rankings->Data()->contents);
+ EntryImpl* cache_entry = NULL;
+ bool dirty;
+ if (NewEntry(address, &cache_entry, &dirty))
+ 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();
+}
+
+void BackendImpl::UpdateRank(CacheRankingsBlock* node, bool modified) {
+ rankings_.UpdateRank(node, modified);
+}
+
+void BackendImpl::IncrementIoCount() {
+ num_pending_io_++;
+}
+
+void BackendImpl::DecrementIoCount() {
+ num_pending_io_--;
+}
+
+int32 BackendImpl::GetCurrentEntryId() {
+ return data_->header.this_id;
+}
+
+void BackendImpl::ClearRefCountForTest() {
+ num_refs_ = 0;
+}
+
+void BackendImpl::ModifyStorageSize(int32 old_size, int32 new_size) {
+ if (disabled_)
+ return;
+ if (old_size > new_size)
+ SubstractStorageSize(old_size - new_size);
+ else
+ AddStorageSize(new_size - old_size);
+
+ // Update the usage statistics.
+ stats_.ModifyStorageStats(old_size, new_size);
+}
+
+void BackendImpl::OnEvent(Stats::Counters an_event) {
+ stats_.OnEvent(an_event);
+}
+
+void BackendImpl::TooMuchStorageRequested(int32 size) {
+ stats_.ModifyStorageStats(0, size);
+}
+
+int BackendImpl::MaxFileSize() const {
+ return max_size_ / 8;
+}
+
+void BackendImpl::OnStatsTimer() {
+ stats_.OnEvent(Stats::TIMER);
+ int64 current = stats_.GetCounter(Stats::OPEN_ENTRIES);
+ int64 time = stats_.GetCounter(Stats::TIMER);
+
+ current = current * (time - 1) + num_refs_;
+ current /= time;
+ stats_.SetCounter(Stats::OPEN_ENTRIES, current);
+ stats_.SetCounter(Stats::MAX_ENTRIES, max_refs_);
+}
+
+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_)
+ RestartCache();
+}
+
+void BackendImpl::SetUnitTestMode() {
+ unit_test_ = true;
+}
+
+void BackendImpl::AdjustMaxCacheSize(int table_len) {
+ if (max_size_)
+ return;
+
+ // The user is not setting the size, let's figure it out.
+ ULARGE_INTEGER available, total, free;
+ if (!GetDiskFreeSpaceExW(path_.c_str(), &available, &total, &free)) {
+ max_size_ = kDefaultCacheSize;
+ return;
+ }
+
+ // Attempt to use 1% of the disk available for this user.
+ available.QuadPart /= 100;
+
+ if (available.QuadPart < static_cast<uint32>(kDefaultCacheSize))
+ max_size_ = kDefaultCacheSize;
+ else if (available.QuadPart > static_cast<uint32>(kint32max))
+ max_size_ = kint32max;
+ else
+ max_size_ = static_cast<int32>(available.LowPart);
+
+ // 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)
+ max_size_ = kDefaultCacheSize;
+
+ 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;
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/backend_impl.h b/net/disk_cache/backend_impl.h
new file mode 100644
index 0000000..b891487
--- /dev/null
+++ b/net/disk_cache/backend_impl.h
@@ -0,0 +1,218 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/rankings.h"
+#include "net/disk_cache/stats.h"
+#include "net/disk_cache/trace.h"
+
+class Timer;
+
+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 BackendImpl : public Backend {
+ public:
+ explicit BackendImpl(const std::wstring& path)
+ : path_(path), init_(false), mask_(0), block_files_(path),
+ unit_test_(false), restarted_(false), max_size_(0) {}
+ // mask can be used to limit the usable size of the hash table, for testing.
+ BackendImpl(const std::wstring& path, uint32 mask)
+ : path_(path), init_(false), mask_(mask), block_files_(path),
+ unit_test_(false), restarted_(false), max_size_(0) {}
+ ~BackendImpl();
+
+ // Performs general initialization for this current instance of the cache.
+ bool Init();
+
+ // Backend interface.
+ virtual int32 GetEntryCount() const;
+ virtual bool OpenEntry(const std::string& key, Entry** entry);
+ virtual bool CreateEntry(const std::string& key, Entry** entry);
+ virtual bool DoomEntry(const std::string& key);
+ virtual bool DoomAllEntries();
+ virtual bool DoomEntriesBetween(const Time initial_time,
+ const Time end_time);
+ virtual bool DoomEntriesSince(const Time initial_time);
+ virtual bool OpenNextEntry(void** iter, Entry** next_entry);
+ virtual void EndEnumeration(void** iter);
+ virtual void GetStats(StatsItems* stats);
+
+ // Sets the maximum size for the total amount of data stored by this instance.
+ bool SetMaxSize(int max_bytes);
+
+ // Returns the actual file used to store a given (non-external) address.
+ MappedFile* File(Addr address) {
+ if (disabled_)
+ return NULL;
+ return block_files_.GetFile(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);
+
+ // Permanently deletes an entry.
+ void InternalDoomEntry(EntryImpl* entry);
+
+ // Returns the full name for an external storage file.
+ std::wstring GetFileName(Addr address) const;
+
+ // Creates an external storage file.
+ bool CreateExternalFile(Addr* address);
+
+ // Updates the ranking information for an entry.
+ void UpdateRank(CacheRankingsBlock* node, bool modified);
+
+ // This method must be called whenever an entry is released for the last time.
+ void CacheEntryDestroyed();
+
+ // Handles the pending asynchronous IO count.
+ void IncrementIoCount();
+ void DecrementIoCount();
+
+ // Returns the id being used on this run of the cache.
+ int32 GetCurrentEntryId();
+
+ // 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);
+
+ // Clears the counter of references to test handling of corruptions.
+ void ClearRefCountForTest();
+
+ // Sets internal parameters to enable unit testing mode.
+ void SetUnitTestMode();
+
+ // 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;
+
+ // Logs requests that are denied due to being too big.
+ void TooMuchStorageRequested(int32 size);
+
+ // Called when an interesting event should be logged (counted).
+ void OnEvent(Stats::Counters an_event);
+
+ // Timer callback to calculate usage statistics.
+ void OnStatsTimer();
+
+ // Peforms a simple self-check, and returns the number of dirty items
+ // or an error code (negative value).
+ int SelfCheck();
+
+ // Reports a critical error (and disables the cache).
+ void CriticalError(int error);
+
+ private:
+ // Creates a new backing file for the cache index.
+ bool CreateBackingStore(HANDLE file);
+ bool InitBackingStore(bool* file_created);
+
+ // 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).
+ EntryImpl* MatchEntry(const std::string& key, uint32 hash,
+ bool find_parent);
+
+ // 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);
+
+ void DestroyInvalidEntry(Addr address, EntryImpl* entry);
+
+ // Creates a new entry object and checks to see if it is dirty. Returns zero
+ // on success, or a disk_cache error on failure.
+ int NewEntry(Addr address, EntryImpl** entry, bool* dirty);
+
+ // Part of the selt 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);
+
+ // Performs basic checks on the index file. Returns false on failure.
+ bool CheckIndex();
+
+ // Dumps current cache statistics to the log.
+ void LogStats();
+
+ // Deletes the cache and starts again.
+ void RestartCache();
+
+ // 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 AdjustMaxCacheSize(int table_len);
+
+ scoped_refptr<MappedFile> index_; // The main cache index.
+ std::wstring 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.
+ int num_refs_; // Number of referenced cache entries.
+ int max_refs_; // Max number of eferenced cache entries.
+ int num_pending_io_; // Number of pending IO operations;
+ bool init_; // controls the initialization of the system.
+ bool restarted_;
+ bool unit_test_;
+ bool disabled_;
+
+ Stats stats_; // Usage statistcs.
+ Task* timer_task_;
+ Timer* timer_; // Usage timer.
+ TraceObject trace_object_; // Inits and destroys internal tracing.
+
+ DISALLOW_EVIL_CONSTRUCTORS(BackendImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BACKEND_IMPL_H__
diff --git a/net/disk_cache/backend_unittest.cc b/net/disk_cache/backend_unittest.cc
new file mode 100644
index 0000000..0514758
--- /dev/null
+++ b/net/disk_cache/backend_unittest.cc
@@ -0,0 +1,944 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "net/base/net_errors.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/mapped_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Copies a set of cache files from the data folder to the test folder.
+bool CopyTestCache(const std::wstring& name) {
+ std::wstring path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ file_util::AppendToPath(&path, L"net");
+ file_util::AppendToPath(&path, L"data");
+ file_util::AppendToPath(&path, L"cache_tests");
+ file_util::AppendToPath(&path, name);
+
+ std::wstring dest = GetCachePath();
+ if (!DeleteCache(dest.c_str()))
+ return false;
+ return file_util::CopyDirectory(path, dest, false);
+}
+
+// Verifies that we can recover a transaction (insert or remove on the rankings
+// list) that is interrupted.
+int TestTransaction(const std::wstring& name, int num_entries, bool load) {
+ if (!CopyTestCache(name))
+ return 1;
+ std::wstring path = GetCachePath();
+ scoped_ptr<disk_cache::Backend> cache;
+
+ if (!load) {
+ cache.reset(disk_cache::CreateCacheBackend(path, false, 0));
+ } else {
+ disk_cache::BackendImpl* cache2 = new disk_cache::BackendImpl(path, 0xf);
+ if (!cache2 || !cache2->SetMaxSize(0x100000) || !cache2->Init())
+ return 2;
+ cache.reset(cache2);
+ }
+ if (!cache.get())
+ return 2;
+
+ if (num_entries + 1 != cache->GetEntryCount())
+ return 3;
+
+ std::string key("the first key");
+ disk_cache::Entry* entry1;
+ if (cache->OpenEntry(key, &entry1))
+ return 4;
+
+ int actual = cache->GetEntryCount();
+ if (num_entries != actual) {
+ if (!load)
+ return 5;
+ // If there is a heavy load, inserting an entry will make another entry
+ // dirty (on the hash bucket) so two entries are removed.
+ if (actual != num_entries - 1)
+ return 5;
+ }
+
+ cache.reset();
+
+ if (!CheckCacheIntegrity(path))
+ return 6;
+
+ return 0;
+}
+
+} // namespace
+
+// Tests that can run with different types of caches.
+class DiskCacheBackendTest : public DiskCacheTestBase {
+ protected:
+ void BackendBasics();
+ void BackendSetSize();
+ void BackendLoad();
+ void BackendKeying();
+ void BackendEnumerations();
+ void BackendDoomRecent();
+ void BackendDoomBetween();
+ void BackendDoomAll();
+};
+
+void DiskCacheBackendTest::BackendBasics() {
+ disk_cache::Entry *entry1 = NULL, *entry2 = NULL;
+ EXPECT_FALSE(cache_->OpenEntry("the first key", &entry1));
+ ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_TRUE(NULL != entry1);
+ entry1->Close();
+ entry1 = NULL;
+
+ ASSERT_TRUE(cache_->OpenEntry("the first key", &entry1));
+ ASSERT_TRUE(NULL != entry1);
+ entry1->Close();
+ entry1 = NULL;
+
+ EXPECT_FALSE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_TRUE(cache_->OpenEntry("the first key", &entry1));
+ EXPECT_FALSE(cache_->OpenEntry("some other key", &entry2));
+ ASSERT_TRUE(cache_->CreateEntry("some other key", &entry2));
+ ASSERT_TRUE(NULL != entry1);
+ ASSERT_TRUE(NULL != entry2);
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ disk_cache::Entry* entry3 = NULL;
+ ASSERT_TRUE(cache_->OpenEntry("some other key", &entry3));
+ ASSERT_TRUE(NULL != entry3);
+ EXPECT_TRUE(entry2 == entry3);
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ EXPECT_TRUE(cache_->DoomEntry("some other key"));
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ entry1->Close();
+ entry2->Close();
+ entry3->Close();
+
+ EXPECT_TRUE(cache_->DoomEntry("the first key"));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+
+ ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_TRUE(cache_->CreateEntry("some other key", &entry2));
+ entry1->Doom();
+ entry1->Close();
+ EXPECT_TRUE(cache_->DoomEntry("some other key"));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ entry2->Close();
+}
+
+TEST_F(DiskCacheBackendTest, Basics) {
+ InitCache();
+ BackendBasics();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyBasics) {
+ SetMemoryOnlyMode();
+ InitCache();
+ BackendBasics();
+}
+
+void DiskCacheBackendTest::BackendKeying() {
+ const char* kName1 = "the first key";
+ const char* kName2 = "the first Key";
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_TRUE(cache_->CreateEntry(kName1, &entry1));
+
+ ASSERT_TRUE(cache_->CreateEntry(kName2, &entry2));
+ EXPECT_TRUE(entry1 != entry2) << "Case sensitive";
+ entry2->Close();
+
+ char buffer[30];
+ EXPECT_EQ(0, strcpy_s(buffer, kName1));
+ ASSERT_TRUE(cache_->OpenEntry(buffer, &entry2));
+ EXPECT_TRUE(entry1 == entry2);
+ entry2->Close();
+
+ EXPECT_EQ(0, strcpy_s(buffer + 1, sizeof(buffer) - 1 , kName1));
+ ASSERT_TRUE(cache_->OpenEntry(buffer + 1, &entry2));
+ EXPECT_TRUE(entry1 == entry2);
+ entry2->Close();
+
+ EXPECT_EQ(0, strcpy_s(buffer + 3, sizeof(buffer) - 3, kName1));
+ ASSERT_TRUE(cache_->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_TRUE(cache_->CreateEntry(buffer2, &entry2)) << "key on block file";
+ entry2->Close();
+
+ buffer2[1023] = 'g';
+ buffer2[19999] = '\0';
+ ASSERT_TRUE(cache_->CreateEntry(buffer2, &entry2)) << "key on external file";
+ entry2->Close();
+ entry1->Close();
+}
+
+TEST_F(DiskCacheBackendTest, Keying) {
+ InitCache();
+ BackendKeying();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyKeying) {
+ SetMemoryOnlyMode();
+ InitCache();
+ BackendKeying();
+}
+
+void DiskCacheBackendTest::BackendSetSize() {
+ SetDirectMode();
+ 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_TRUE(cache_->CreateEntry(first, &entry));
+
+ char buffer[cache_size] = {0};
+ EXPECT_EQ(cache_size / 10, entry->WriteData(0, 0, buffer, cache_size / 10,
+ NULL, false)) << "normal file";
+
+ EXPECT_EQ(net::ERR_FAILED, entry->WriteData(1, 0, buffer, cache_size / 5,
+ NULL, 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, entry->WriteData(1, 0, buffer, cache_size / 5,
+ NULL, false));
+
+ // Let's fill up the cache!.
+ SetMaxSize(cache_size * 10);
+ EXPECT_EQ(cache_size * 3 / 4, entry->WriteData(0, 0, buffer,
+ cache_size * 3 / 4, NULL, false));
+ entry->Close();
+
+ SetMaxSize(cache_size);
+
+ // Verify that the cache is 95% full.
+ ASSERT_TRUE(cache_->OpenEntry(first, &entry));
+ EXPECT_EQ(cache_size * 3 / 4, entry->GetDataSize(0));
+ EXPECT_EQ(cache_size / 5, entry->GetDataSize(1));
+ entry->Close();
+
+ ASSERT_TRUE(cache_->CreateEntry(second, &entry));
+ EXPECT_EQ(cache_size / 10, entry->WriteData(0, 0, buffer, cache_size / 10,
+ NULL, false)) << "trim the cache";
+ entry->Close();
+
+ EXPECT_FALSE(cache_->OpenEntry(first, &entry));
+ ASSERT_TRUE(cache_->OpenEntry(second, &entry));
+ EXPECT_EQ(cache_size / 10, entry->GetDataSize(0));
+ entry->Close();
+}
+
+TEST_F(DiskCacheBackendTest, SetSize) {
+ BackendSetSize();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlySetSize) {
+ SetMemoryOnlyMode();
+ BackendSetSize();
+}
+
+void DiskCacheBackendTest::BackendLoad() {
+ 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_TRUE(cache_->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_TRUE(cache_->OpenEntry(entries[i]->GetKey(), &entry));
+ EXPECT_TRUE(entry == entries[i]);
+ entry->Close();
+ entries[i]->Doom();
+ entries[i]->Close();
+ }
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, Load) {
+ // Work with a tiny index table (16 entries)
+ SetMask(0xf);
+ SetMaxSize(0x100000);
+ InitCache();
+ BackendLoad();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyLoad) {
+ // Work with a tiny index table (16 entries)
+ SetMaxSize(0x100000);
+ SetMemoryOnlyMode();
+ InitCache();
+ BackendLoad();
+}
+
+// Before looking for invalid entries, let's check a valid entry.
+TEST_F(DiskCacheBackendTest, ValidEntry) {
+ SetDirectMode();
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry* entry1;
+ ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+
+ char data[] = "And the data to save";
+ EXPECT_EQ(sizeof(data), entry1->WriteData(0, 0, data, sizeof(data), NULL,
+ false));
+ entry1->Close();
+ SimulateCrash();
+
+ ASSERT_TRUE(cache_->OpenEntry(key, &entry1));
+
+ char buffer[40];
+ memset(buffer, 0, sizeof(buffer));
+ EXPECT_EQ(sizeof(data), entry1->ReadData(0, 0, buffer, sizeof(data), NULL));
+ entry1->Close();
+ EXPECT_STREQ(data, buffer);
+}
+
+// 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.
+TEST_F(DiskCacheBackendTest, InvalidEntry) {
+ // Use the implementation directly... we need to simulate a crash.
+ SetDirectMode();
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry* entry1;
+ ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+
+ char data[] = "And the data to save";
+ EXPECT_EQ(sizeof(data), entry1->WriteData(0, 0, data, sizeof(data), NULL,
+ false));
+ SimulateCrash();
+
+ EXPECT_FALSE(cache_->OpenEntry(key, &entry1));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+// Almost the same test, but this time crash the cache after reading an entry.
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, InvalidEntryRead) {
+ // Use the implementation directly... we need to simulate a crash.
+ SetDirectMode();
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry* entry1;
+ ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+
+ char data[] = "And the data to save";
+ EXPECT_EQ(sizeof(data), entry1->WriteData(0, 0, data, sizeof(data), NULL,
+ false));
+ entry1->Close();
+ ASSERT_TRUE(cache_->OpenEntry(key, &entry1));
+ EXPECT_EQ(sizeof(data), entry1->ReadData(0, 0, data, sizeof(data), NULL));
+
+ SimulateCrash();
+
+ EXPECT_FALSE(cache_->OpenEntry(key, &entry1));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, InvalidEntryWithLoad) {
+ // 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_TRUE(cache_->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_FALSE(cache_->OpenEntry(keys[i], &entry));
+ }
+
+ for (int i = 0; i < kNumEntries / 2; i++) {
+ disk_cache::Entry* entry;
+ EXPECT_TRUE(cache_->OpenEntry(keys[i], &entry));
+ entry->Close();
+ }
+
+ EXPECT_EQ(kNumEntries / 2, cache_->GetEntryCount());
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, TrimInvalidEntry) {
+ // Use the implementation directly... we need to simulate a crash.
+ SetDirectMode();
+
+ const int cache_size = 0x4000; // 16 kB
+ SetMaxSize(cache_size * 10);
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache_->CreateEntry(first, &entry));
+
+ char buffer[cache_size] = {0};
+ EXPECT_EQ(cache_size * 19 / 20, entry->WriteData(0, 0, buffer,
+ cache_size * 19 / 20, NULL, false));
+
+ // Simulate a crash.
+ SimulateCrash();
+
+ ASSERT_TRUE(cache_->CreateEntry(second, &entry));
+ EXPECT_EQ(cache_size / 10, entry->WriteData(0, 0, buffer, cache_size / 10,
+ NULL, false)) << "trim the cache";
+ entry->Close();
+
+ EXPECT_FALSE(cache_->OpenEntry(first, &entry));
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+void DiskCacheBackendTest::BackendEnumerations() {
+ Time initial = Time::Now();
+ int seed = static_cast<int>(initial.ToInternalValue());
+ srand(seed);
+
+ const int kNumEntries = 100;
+ for (int i = 0; i < kNumEntries; i++) {
+ std::string key = GenerateKey(true);
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache_->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 (cache_->OpenNextEntry(&iter, &entry)) {
+ 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 (cache_->OpenNextEntry(&iter, &entry)) {
+ 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) {
+ InitCache();
+ BackendEnumerations();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyEnumerations) {
+ SetMemoryOnlyMode();
+ InitCache();
+ BackendEnumerations();
+}
+
+// Verify handling of invalid entries while doing enumerations.
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, InvalidEntryEnumeration) {
+ // Use the implementation directly... we need to simulate a crash.
+ SetDirectMode();
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry *entry, *entry1, *entry2;
+ ASSERT_TRUE(cache_->CreateEntry(key, &entry1));
+
+ char data[] = "And the data to save";
+ EXPECT_EQ(sizeof(data), entry1->WriteData(0, 0, data, sizeof(data), NULL,
+ false));
+ entry1->Close();
+ ASSERT_TRUE(cache_->OpenEntry(key, &entry1));
+ EXPECT_EQ(sizeof(data), entry1->ReadData(0, 0, data, sizeof(data), NULL));
+
+ std::string key2("Another key");
+ ASSERT_TRUE(cache_->CreateEntry(key2, &entry2));
+ entry2->Close();
+ ASSERT_EQ(2, cache_->GetEntryCount());
+
+ SimulateCrash();
+
+ void* iter = NULL;
+ int count = 0;
+ while (cache_->OpenNextEntry(&iter, &entry)) {
+ ASSERT_TRUE(NULL != entry);
+ EXPECT_EQ(key2, entry->GetKey());
+ entry->Close();
+ count++;
+ };
+ EXPECT_EQ(1, count);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+// Tests that if for some reason entries are modified close to existing cache
+// iterators, we don't generate fatal errors or reset the cache.
+TEST_F(DiskCacheBackendTest, FixEnumerators) {
+ 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_TRUE(cache_->CreateEntry(key, &entry));
+ entry->Close();
+ }
+ EXPECT_EQ(kNumEntries, cache_->GetEntryCount());
+
+ disk_cache::Entry *entry1, *entry2;
+ void* iter1 = NULL;
+ void* iter2 = NULL;
+ ASSERT_TRUE(cache_->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_TRUE(cache_->OpenNextEntry(&iter1, &entry1));
+ ASSERT_TRUE(NULL != entry1);
+
+ ASSERT_TRUE(cache_->OpenNextEntry(&iter2, &entry2));
+ ASSERT_TRUE(NULL != entry2);
+ entry2->Close();
+ }
+
+ // Messing up with entry1 will modify entry2->next.
+ entry1->Doom();
+ ASSERT_TRUE(cache_->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_TRUE(cache_->OpenNextEntry(&iter2, &entry2));
+ ASSERT_TRUE(NULL != entry2);
+ entry2->Close();
+
+ cache_->EndEnumeration(&iter1);
+ cache_->EndEnumeration(&iter2);
+}
+
+void DiskCacheBackendTest::BackendDoomRecent() {
+ Time initial = Time::Now();
+
+ disk_cache::Entry *entry;
+ ASSERT_TRUE(cache_->CreateEntry("first", &entry));
+ entry->Close();
+ ASSERT_TRUE(cache_->CreateEntry("second", &entry));
+ entry->Close();
+
+ Sleep(20);
+ Time middle = Time::Now();
+
+ ASSERT_TRUE(cache_->CreateEntry("third", &entry));
+ entry->Close();
+ ASSERT_TRUE(cache_->CreateEntry("fourth", &entry));
+ entry->Close();
+
+ Sleep(20);
+ Time final = Time::Now();
+
+ ASSERT_EQ(4, cache_->GetEntryCount());
+ EXPECT_TRUE(cache_->DoomEntriesSince(final));
+ ASSERT_EQ(4, cache_->GetEntryCount());
+
+ EXPECT_TRUE(cache_->DoomEntriesSince(middle));
+ ASSERT_EQ(2, cache_->GetEntryCount());
+
+ ASSERT_TRUE(cache_->OpenEntry("second", &entry));
+ entry->Close();
+}
+
+void DiskCacheBackendTest::BackendDoomBetween() {
+ Time initial = Time::Now();
+
+ disk_cache::Entry *entry;
+ ASSERT_TRUE(cache_->CreateEntry("first", &entry));
+ entry->Close();
+
+ Sleep(20);
+ Time middle_start = Time::Now();
+
+ ASSERT_TRUE(cache_->CreateEntry("second", &entry));
+ entry->Close();
+ ASSERT_TRUE(cache_->CreateEntry("third", &entry));
+ entry->Close();
+
+ Sleep(20);
+ Time middle_end = Time::Now();
+
+ ASSERT_TRUE(cache_->CreateEntry("fourth", &entry));
+ entry->Close();
+
+ Sleep(20);
+ Time final = Time::Now();
+
+ ASSERT_EQ(4, cache_->GetEntryCount());
+ EXPECT_TRUE(cache_->DoomEntriesBetween(middle_start, middle_end));
+ ASSERT_EQ(2, cache_->GetEntryCount());
+
+ ASSERT_TRUE(cache_->OpenEntry("fourth", &entry));
+ entry->Close();
+
+ EXPECT_TRUE(cache_->DoomEntriesBetween(middle_start, final));
+ ASSERT_EQ(1, cache_->GetEntryCount());
+
+ ASSERT_TRUE(cache_->OpenEntry("first", &entry));
+ entry->Close();
+}
+
+TEST_F(DiskCacheBackendTest, DoomRecent) {
+ InitCache();
+ BackendDoomRecent();
+}
+
+TEST_F(DiskCacheBackendTest, DoomBetween) {
+ InitCache();
+ BackendDoomBetween();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomRecent) {
+ SetMemoryOnlyMode();
+ InitCache();
+ BackendDoomRecent();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomBetween) {
+ SetMemoryOnlyMode();
+ InitCache();
+ BackendDoomBetween();
+}
+
+TEST(DiskCacheTest, Backend_RecoverInsert) {
+ // Tests with an empty cache.
+ EXPECT_EQ(0, TestTransaction(L"insert_empty1", 0, false));
+ EXPECT_EQ(0, TestTransaction(L"insert_empty2", 0, false));
+ EXPECT_EQ(0, TestTransaction(L"insert_empty3", 0, false));
+
+ // Tests with one entry on the cache.
+ EXPECT_EQ(0, TestTransaction(L"insert_one1", 1, false));
+ EXPECT_EQ(0, TestTransaction(L"insert_one2", 1, false));
+ EXPECT_EQ(0, TestTransaction(L"insert_one3", 1, false));
+
+ // Tests with one hundred entries on the cache, tiny index.
+ EXPECT_EQ(0, TestTransaction(L"insert_load1", 100, true));
+ EXPECT_EQ(0, TestTransaction(L"insert_load2", 100, true));
+}
+
+TEST(DiskCacheTest, Backend_RecoverRemove) {
+ // Removing the only element.
+ EXPECT_EQ(0, TestTransaction(L"remove_one1", 0, false));
+ EXPECT_EQ(0, TestTransaction(L"remove_one2", 0, false));
+ EXPECT_EQ(0, TestTransaction(L"remove_one3", 0, false));
+
+ // Removing the head.
+ EXPECT_EQ(0, TestTransaction(L"remove_head1", 1, false));
+ EXPECT_EQ(0, TestTransaction(L"remove_head2", 1, false));
+ EXPECT_EQ(0, TestTransaction(L"remove_head3", 1, false));
+
+ // Removing the tail.
+ EXPECT_EQ(0, TestTransaction(L"remove_tail1", 1, false));
+ EXPECT_EQ(0, TestTransaction(L"remove_tail2", 1, false));
+ EXPECT_EQ(0, TestTransaction(L"remove_tail3", 1, false));
+
+ // Removing with one hundred entries on the cache, tiny index.
+ EXPECT_EQ(0, TestTransaction(L"remove_load1", 100, true));
+ EXPECT_EQ(0, TestTransaction(L"remove_load2", 100, true));
+ EXPECT_EQ(0, TestTransaction(L"remove_load3", 100, true));
+
+#ifdef NDEBUG
+ // This case cannot be reverted, so it will assert on debug builds.
+ EXPECT_EQ(0, TestTransaction(L"remove_one4", 0, false));
+ EXPECT_EQ(0, TestTransaction(L"remove_head4", 1, false));
+#endif
+}
+
+// Tests dealing with cache files that cannot be recovered.
+TEST(DiskCacheTest, Backend_DeleteOld) {
+ ASSERT_TRUE(CopyTestCache(L"wrong_version"));
+ std::wstring path = GetCachePath();
+ scoped_ptr<disk_cache::Backend> cache;
+ cache.reset(disk_cache::CreateCacheBackend(path, true, 0));
+
+ MessageLoopHelper helper;
+
+ ASSERT_TRUE(NULL != cache.get());
+ ASSERT_EQ(0, cache->GetEntryCount());
+
+ // Wait for a callback that never comes... about 2 secs :). The message loop
+ // has to run to allow destruction of the cleaner thread.
+ helper.WaitUntilCacheIoFinished(1);
+}
+
+// We want to be able to deal with messed up entries on disk.
+TEST(DiskCacheTest, Backend_InvalidEntry) {
+ ASSERT_TRUE(CopyTestCache(L"bad_entry"));
+ std::wstring path = GetCachePath();
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ ASSERT_TRUE(NULL != cache);
+
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_TRUE(cache->OpenEntry("the first key", &entry1));
+ EXPECT_FALSE(cache->OpenEntry("some other key", &entry2));
+ entry1->Close();
+
+ // CheckCacheIntegrity will fail at this point.
+ delete cache;
+}
+
+// We want to be able to deal with messed up entries on disk.
+TEST(DiskCacheTest, Backend_InvalidRankings) {
+ ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ std::wstring path = GetCachePath();
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ ASSERT_TRUE(NULL != cache);
+
+ disk_cache::Entry *entry1, *entry2;
+ EXPECT_FALSE(cache->OpenEntry("the first key", &entry1));
+ ASSERT_TRUE(cache->OpenEntry("some other key", &entry2));
+ entry2->Close();
+
+ // CheckCacheIntegrity will fail at this point.
+ delete cache;
+}
+
+// If the LRU is corrupt, we delete the cache.
+TEST(DiskCacheTest, Backend_InvalidRankings2) {
+ ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ std::wstring path = GetCachePath();
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ ASSERT_TRUE(NULL != cache);
+
+ disk_cache::Entry* entry;
+ void* iter = NULL;
+ ASSERT_TRUE(cache->OpenNextEntry(&iter, &entry));
+ entry->Close();
+ EXPECT_EQ(2, cache->GetEntryCount());
+
+ EXPECT_FALSE(cache->OpenNextEntry(&iter, &entry));
+ EXPECT_EQ(0, cache->GetEntryCount());
+
+ delete cache;
+ EXPECT_TRUE(CheckCacheIntegrity(path));
+}
+
+// If the LRU is corrupt and we have open entries, we disable the cache.
+TEST(DiskCacheTest, Backend_Disable) {
+ ASSERT_TRUE(CopyTestCache(L"bad_rankings"));
+ std::wstring path = GetCachePath();
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ ASSERT_TRUE(NULL != cache);
+
+ disk_cache::Entry *entry1, *entry2;
+ void* iter = NULL;
+ ASSERT_TRUE(cache->OpenNextEntry(&iter, &entry1));
+
+ EXPECT_FALSE(cache->OpenNextEntry(&iter, &entry2));
+ EXPECT_EQ(2, cache->GetEntryCount());
+ EXPECT_FALSE(cache->CreateEntry("Something new", &entry2));
+
+ entry1->Close();
+
+ EXPECT_EQ(0, cache->GetEntryCount());
+
+ delete cache;
+ EXPECT_TRUE(CheckCacheIntegrity(path));
+}
+
+// This is another type of corruption on the LRU; disable the cache.
+TEST(DiskCacheTest, Backend_Disable2) {
+ ASSERT_TRUE(CopyTestCache(L"list_loop"));
+ std::wstring path = GetCachePath();
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ ASSERT_TRUE(NULL != cache);
+
+ EXPECT_EQ(8, cache->GetEntryCount());
+
+ disk_cache::Entry* entry;
+ void* iter = NULL;
+ int count = 0;
+ while (cache->OpenNextEntry(&iter, &entry)) {
+ ASSERT_TRUE(NULL != entry);
+ entry->Close();
+ count++;
+ ASSERT_LT(count, 9);
+ };
+
+ EXPECT_EQ(0, cache->GetEntryCount());
+
+ delete cache;
+ EXPECT_TRUE(CheckCacheIntegrity(path));
+}
+
+TEST(DiskCacheTest, Backend_UsageStats) {
+ MessageLoopHelper helper;
+
+ std::wstring path = GetCachePath();
+ ASSERT_TRUE(DeleteCache(path.c_str()));
+ scoped_ptr<disk_cache::BackendImpl> cache;
+ cache.reset(new disk_cache::BackendImpl(path));
+ ASSERT_TRUE(NULL != cache.get());
+ cache->SetUnitTestMode();
+ ASSERT_TRUE(cache->Init());
+
+ // 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);
+}
+
+void DiskCacheBackendTest::BackendDoomAll() {
+ Time initial = Time::Now();
+
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_TRUE(cache_->CreateEntry("first", &entry1));
+ ASSERT_TRUE(cache_->CreateEntry("second", &entry2));
+ entry1->Close();
+ entry2->Close();
+
+ ASSERT_TRUE(cache_->CreateEntry("third", &entry1));
+ ASSERT_TRUE(cache_->CreateEntry("fourth", &entry2));
+
+ ASSERT_EQ(4, cache_->GetEntryCount());
+ EXPECT_TRUE(cache_->DoomAllEntries());
+ ASSERT_EQ(0, cache_->GetEntryCount());
+
+ disk_cache::Entry *entry3, *entry4;
+ ASSERT_TRUE(cache_->CreateEntry("third", &entry3));
+ ASSERT_TRUE(cache_->CreateEntry("fourth", &entry4));
+
+ EXPECT_TRUE(cache_->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_TRUE(cache_->CreateEntry("third", &entry1));
+ ASSERT_TRUE(cache_->CreateEntry("fourth", &entry2));
+ entry1->Close();
+ entry2->Close();
+
+ ASSERT_EQ(2, cache_->GetEntryCount());
+ EXPECT_TRUE(cache_->DoomAllEntries());
+ ASSERT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DoomAll) {
+ InitCache();
+ BackendDoomAll();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomAll) {
+ SetMemoryOnlyMode();
+ InitCache();
+ BackendDoomAll();
+}
diff --git a/net/disk_cache/block_files.cc b/net/disk_cache/block_files.cc
new file mode 100644
index 0000000..e985765
--- /dev/null
+++ b/net/disk_cache/block_files.cc
@@ -0,0 +1,441 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/block_files.h"
+
+#include "base/scoped_handle.h"
+#include "base/string_util.h"
+#include "net/disk_cache/file_lock.h"
+
+namespace {
+
+const wchar_t* kBlockName = L"\\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];
+}
+
+void FixAllocationCounters(disk_cache::BlockFileHeader* header);
+
+// 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, disk_cache::BlockFileHeader* header,
+ int* index) {
+ if (target <= 0 || target > disk_cache::kMaxNumBlocks ||
+ size <= 0 || size > disk_cache::kMaxNumBlocks) {
+ NOTREACHED();
+ return false;
+ }
+
+ // 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;
+ uint32 to_add = ((1 << size) - 1) << index_offset;
+ header->allocation_map[current] |= to_add;
+
+ header->hints[target - 1] = current;
+ header->empty[target - 1]--;
+ DCHECK(header->empty[target - 1] >= 0);
+ header->num_entries++;
+ if (target != size) {
+ header->empty[target - size - 1]++;
+ }
+ return true;
+ }
+ }
+
+ // It is possible to have an undetected corruption (for example when the OS
+ // crashes), fix it here.
+ LOG(ERROR) << "Failing CreateMapBlock";
+ FixAllocationCounters(header);
+ return false;
+}
+
+// Deletes the block pointed by index from allocation_map, and updates the
+// relevant counters on the header.
+void DeleteMapBlock(int index, int size, disk_cache::BlockFileHeader* header) {
+ if (size < 0 || size > disk_cache::kMaxNumBlocks) {
+ NOTREACHED();
+ return;
+ }
+ 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 availabe 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(header->empty[bits_at_end - 1] >= 0);
+ }
+ header->num_entries--;
+ DCHECK(header->num_entries >= 0);
+}
+
+// Restores the "empty counters" and allocation hints.
+void FixAllocationCounters(disk_cache::BlockFileHeader* header) {
+ for (int i = 0; i < disk_cache::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 NeedToGrowBlockFile(const disk_cache::BlockFileHeader* header,
+ int block_count) {
+ for (int i = block_count; i <= disk_cache::kMaxNumBlocks; i++) {
+ if (header->empty[i - 1])
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+BlockFiles::~BlockFiles() {
+ if (zero_buffer_)
+ delete[] zero_buffer_;
+ CloseFiles();
+}
+
+bool BlockFiles::Init(bool create_files) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ block_files_.resize(kFirstAdditionlBlockFile);
+ for (int i = 0; i < kFirstAdditionlBlockFile; i++) {
+ if (create_files)
+ if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true))
+ return false;
+
+ if (!OpenBlockFile(i))
+ return false;
+ }
+
+ init_ = true;
+ return true;
+}
+
+void BlockFiles::CloseFiles() {
+ 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();
+}
+
+std::wstring BlockFiles::Name(int index) {
+ // The file format allows for 256 files.
+ DCHECK(index < 256 || index >= 0);
+ std::wstring name = StringPrintf(L"%s%s%d", path_.c_str(), kBlockName, index);
+
+ return name;
+}
+
+bool BlockFiles::CreateBlockFile(int index, FileType file_type, bool force) {
+ std::wstring name = Name(index);
+ DWORD disposition = force ? CREATE_ALWAYS : CREATE_NEW;
+
+ ScopedHandle file(CreateFile(name.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, disposition, 0, NULL));
+ if (!file.IsValid())
+ return false;
+
+ BlockFileHeader header;
+ header.entry_size = Addr::BlockSizeForFileType(file_type);
+ header.this_file = static_cast<int16>(index);
+ DCHECK(index <= kint16max && index >= 0);
+
+ DWORD actual;
+ if (!WriteFile(file.Get(), &header, sizeof(header), &actual, NULL) ||
+ sizeof(header) != actual)
+ return false;
+
+ return true;
+}
+
+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);
+ }
+
+ std::wstring name = Name(index);
+ MappedFile* file = new MappedFile();
+ file->AddRef();
+
+ if (!file->Init(name, kBlockHeaderSize)) {
+ NOTREACHED();
+ LOG(ERROR) << "Failed to open " << name;
+ file->Release();
+ return false;
+ }
+
+ block_files_[index] = file;
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ if (kBlockMagic != header->magic || kCurrentVersion != header->version) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+
+ if (header->updating) {
+ // Last instance was not properly shutdown.
+ if (!FixBlockFileHeader(file))
+ return false;
+ }
+ return true;
+}
+
+MappedFile* BlockFiles::GetFile(Addr address) {
+ CHECK(block_files_.size() >= 4);
+
+ 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;
+
+ 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);
+
+ FileLock lock(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);
+ }
+
+ 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_fily_type);
+ MappedFile* file = block_files_[block_type - 1];
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+
+ 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;
+ }
+ return file;
+}
+
+MappedFile* BlockFiles::NextFile(const MappedFile* 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 = kFirstAdditionlBlockFile; i <= kMaxBlockFile; i++) {
+ if (CreateBlockFile(i, block_type, false))
+ return i;
+ }
+ return 0;
+}
+
+bool BlockFiles::CreateBlock(FileType block_type, int block_count,
+ Addr* block_address) {
+ 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;
+
+ 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());
+ return true;
+}
+
+void BlockFiles::DeleteBlock(Addr address, bool deep) {
+ 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;
+
+ 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);
+}
+
+bool BlockFiles::FixBlockFileHeader(MappedFile* 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.
+
+ 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);
+ header->updating = 0;
+ return true;
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/block_files.h b/net/disk_cache/block_files.h
new file mode 100644
index 0000000..f0f27ec
--- /dev/null
+++ b/net/disk_cache/block_files.h
@@ -0,0 +1,105 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 "net/disk_cache/addr.h"
+#include "net/disk_cache/mapped_file.h"
+
+namespace disk_cache {
+
+class EntryImpl;
+
+// This class handles the set of block-files open by the disk cache.
+class BlockFiles {
+ public:
+ explicit BlockFiles(const std::wstring& path)
+ : path_(path), init_(false), zero_buffer_(NULL) {}
+ ~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();
+
+ 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(const MappedFile* file);
+
+ // Creates an empty block file and returns its index.
+ int CreateNextBlockFile(FileType block_type);
+
+ // Restores the header of a potentially inconsistent file.
+ bool FixBlockFileHeader(MappedFile* file);
+
+ // Returns the filename for a given file index.
+ std::wstring Name(int index);
+
+ bool init_;
+ char* zero_buffer_; // Buffer to speed-up cleaning deleted entries.
+ std::wstring path_; // Path to the backing folder.
+ std::vector<MappedFile*> block_files_; // The actual files.
+
+ DISALLOW_EVIL_CONSTRUCTORS(BlockFiles);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BLOCK_FILES_H__
diff --git a/net/disk_cache/block_files_unittest.cc b/net/disk_cache/block_files_unittest.cc
new file mode 100644
index 0000000..804ee3b
--- /dev/null
+++ b/net/disk_cache/block_files_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(DiskCacheTest, BlockFiles_Grow) {
+ std::wstring path = GetCachePath();
+ ASSERT_TRUE(DeleteCache(path.c_str()));
+
+ disk_cache::BlockFiles files(path);
+ ASSERT_TRUE(files.Init(true));
+
+ // Fill up the 32-byte block file (use three files).
+ for (int i = 0; i < 35000; i++) {
+ disk_cache::Addr address(0);
+ EXPECT_TRUE(files.CreateBlock(disk_cache::RANKINGS, 4, &address));
+ }
+}
+
+// Handling of block files not properly closed.
+TEST(DiskCacheTest, BlockFiles_Recover) {
+ std::wstring path = GetCachePath();
+ ASSERT_TRUE(DeleteCache(path.c_str()));
+
+ disk_cache::BlockFiles files(path);
+ ASSERT_TRUE(files.Init(true));
+
+ const int kNumEntries = 2000;
+ disk_cache::CacheAddr entries[kNumEntries];
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ for (int i = 0; i < kNumEntries; i++) {
+ disk_cache::Addr address(0);
+ int size = (rand() % 4) + 1;
+ EXPECT_TRUE(files.CreateBlock(disk_cache::RANKINGS, size, &address));
+ entries[i] = address.value();
+ }
+
+ for (int i = 0; i < kNumEntries; i++) {
+ int source1 = rand() % kNumEntries;
+ int source2 = rand() % kNumEntries;
+ disk_cache::CacheAddr temp = entries[source1];
+ entries[source1] = entries[source2];
+ entries[source2] = temp;
+ }
+
+ for (int i = 0; i < kNumEntries / 2; i++) {
+ disk_cache::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.
+
+ disk_cache::Addr address(entries[kNumEntries / 2]);
+ disk_cache::MappedFile* file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+
+ disk_cache::BlockFileHeader* header =
+ reinterpret_cast<disk_cache::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<disk_cache::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]);
+}
+
diff --git a/net/disk_cache/disk_cache.h b/net/disk_cache/disk_cache.h
new file mode 100644
index 0000000..f2c90b7
--- /dev/null
+++ b/net/disk_cache/disk_cache.h
@@ -0,0 +1,181 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Defines the public interface of the disk cache. For more details see
+// http://wiki/Main/ChromeDiskCacheBackend
+
+#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.h"
+#include "net/base/completion_callback.h"
+
+namespace disk_cache {
+
+class Entry;
+class Backend;
+
+// Returns an instance of the Backend. path points to a folder where
+// the cached data will be stored. 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
+// based on the available disk space. The returned pointer can be NULL if a
+// fatal error is found.
+Backend* CreateCacheBackend(const std::wstring& path, bool force,
+ int max_bytes);
+
+// 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.
+Backend* CreateInMemoryCacheBackend(int max_bytes);
+
+// The root interface for a disk cache instance.
+class Backend {
+ public:
+ virtual ~Backend() {}
+
+ // Returns the number of entries in the cache.
+ virtual int32 GetEntryCount() const = 0;
+
+ // Opens an existing entry. Upon success, the out param holds a pointer
+ // to a Entry object representing the specified disk cache entry.
+ // When the entry pointer is no longer needed, the Close method
+ // should be called.
+ virtual bool OpenEntry(const std::string& key, Entry** entry) = 0;
+
+ // Creates a new entry. Upon success, the out param holds a pointer
+ // to a Entry object representing the newly created disk cache
+ // entry. When the entry pointer is no longer needed, the Close
+ // method should be called.
+ virtual bool CreateEntry(const std::string& key, Entry** entry) = 0;
+
+ // Marks the entry, specified by the given key, for deletion.
+ virtual bool DoomEntry(const std::string& key) = 0;
+
+ // Marks all entries for deletion.
+ virtual bool DoomAllEntries() = 0;
+
+ // Marks a range of entries for deletion. This supports unbounded deletes in
+ // either direction by using null Time values for either argument.
+ virtual bool DoomEntriesBetween(const Time initial_time,
+ const Time end_time) = 0;
+
+ // Marks all entries accessed since initial_time for deletion.
+ virtual bool DoomEntriesSince(const Time initial_time) = 0;
+
+ // Enumerate 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 false when there are no more
+ // entries to enumerate. When the entry pointer is no longer needed, the
+ // Close method should be called.
+ //
+ // 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.
+ virtual bool OpenNextEntry(void** iter, Entry** next_entry) = 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;
+};
+
+// This interface represents an entry in the disk cache.
+class Entry {
+ public:
+ // 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 Time GetLastUsed() const = 0;
+
+ // Returns the time when this cache entry was last modified.
+ virtual Time GetLastModified() const = 0;
+
+ // Returns the size of the cache data with the given index.
+ virtual int32 GetDataSize(int index) const = 0;
+
+ // Copies cache data into the given buffer of length |buf_len|. If
+ // completion_callback is null, then this call blocks until the read
+ // operation is complete. Otherwise, completion_callback will be
+ // called on the current thread once the read completes. Returns the
+ // number of bytes read or a network error code. If a completion callback is
+ // provided then it will be called if this function returns ERR_IO_PENDING.
+ // Note that the callback will be invoked in any case, 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, char* buf, int buf_len,
+ net::CompletionCallback* completion_callback) = 0;
+
+ // Copies cache data from the given buffer of length |buf_len|. If
+ // completion_callback is null, then this call blocks until the write
+ // operation is complete. Otherwise, completion_callback will be
+ // called on the current thread once the write completes. Returns the
+ // number of bytes written or a network error code. If a completion callback
+ // is provided then it will be called if this function returns ERR_IO_PENDING.
+ // Note that the callback will be invoked in any case, 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, const char* buf, int buf_len,
+ net::CompletionCallback* completion_callback,
+ bool truncate) = 0;
+
+ protected:
+ virtual ~Entry() {}
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_DISK_CACHE_H__
diff --git a/net/disk_cache/disk_cache_perftest.cc b/net/disk_cache/disk_cache_perftest.cc
new file mode 100644
index 0000000..6e23471
--- /dev/null
+++ b/net/disk_cache/disk_cache_perftest.cc
@@ -0,0 +1,236 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/perftimer.h"
+#include "base/scoped_handle.h"
+#include "base/timer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/hash.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+extern int g_cache_tests_max_id;
+extern volatile int g_cache_tests_received;
+extern volatile bool g_cache_tests_error;
+
+namespace {
+
+bool EvictFileFromSystemCache(const wchar_t* name) {
+ // Overwrite it with no buffering.
+ ScopedHandle file(CreateFile(name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL));
+ if (!file.IsValid())
+ return false;
+
+ // Execute in chunks. It could be optimized. We want to do few of these since
+ // these opterations will be slow without the cache.
+ char buffer[128 * 1024];
+ int total_bytes = 0;
+ DWORD bytes_read;
+ for (;;) {
+ if (!ReadFile(file, buffer, sizeof(buffer), &bytes_read, NULL))
+ return false;
+ if (bytes_read == 0)
+ break;
+
+ bool final = false;
+ if (bytes_read < sizeof(buffer))
+ final = true;
+
+ DWORD to_write = final ? sizeof(buffer) : bytes_read;
+
+ DWORD actual;
+ SetFilePointer(file, total_bytes, 0, FILE_BEGIN);
+ if (!WriteFile(file, buffer, to_write, &actual, NULL))
+ return false;
+ total_bytes += bytes_read;
+
+ if (final) {
+ SetFilePointer(file, total_bytes, 0, FILE_BEGIN);
+ SetEndOfFile(file);
+ break;
+ }
+ }
+ return true;
+}
+
+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.
+int TimeWrite(int num_entries, disk_cache::Backend* cache,
+ TestEntries* entries) {
+ char buffer1[200];
+ char buffer2[kMaxSize];
+
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ CacheTestFillBuffer(buffer2, sizeof(buffer2), false);
+
+ CallbackTest callback(1);
+ g_cache_tests_error = false;
+ g_cache_tests_max_id = 1;
+ g_cache_tests_received = 0;
+ int expected = 0;
+
+ MessageLoopHelper helper;
+
+ PerfTimeLogger timer("Write disk cache entries");
+
+ for (int i = 0; i < num_entries; i++) {
+ TestEntry entry;
+ entry.key = GenerateKey(true);
+ entry.data_len = rand() % sizeof(buffer2);
+ entries->push_back(entry);
+
+ disk_cache::Entry* cache_entry;
+ if (!cache->CreateEntry(entry.key, &cache_entry))
+ break;
+ int ret = cache_entry->WriteData(0, 0, buffer1, sizeof(buffer1), &callback,
+ false);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ else if (sizeof(buffer1) != ret)
+ break;
+
+ ret = cache_entry->WriteData(1, 0, buffer2, entry.data_len, &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;
+}
+
+// Reads the data and metadata from each entry listed on |entries|.
+int TimeRead(int num_entries, disk_cache::Backend* cache,
+ const TestEntries& entries, bool cold) {
+ char buffer1[200];
+ char buffer2[kMaxSize];
+
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ CacheTestFillBuffer(buffer2, sizeof(buffer2), false);
+
+ CallbackTest callback(1);
+ g_cache_tests_error = false;
+ g_cache_tests_max_id = 1;
+ g_cache_tests_received = 0;
+ int expected = 0;
+
+ MessageLoopHelper helper;
+
+ 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;
+ if (!cache->OpenEntry(entries[i].key, &cache_entry))
+ break;
+ int ret = cache_entry->ReadData(0, 0, buffer1, sizeof(buffer1), &callback);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ else if (sizeof(buffer1) != ret)
+ break;
+
+ ret = cache_entry->ReadData(1, 0, buffer2, entries[i].data_len, &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;
+}
+
+} // namespace
+
+TEST(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);
+ uint32 hash = disk_cache::Hash(key);
+ }
+ timer.Done();
+}
+
+TEST(DiskCacheTest, CacheBackendPerformance) {
+ std::wstring path = GetCachePath();
+ ASSERT_TRUE(DeleteCache(path.c_str()));
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ ASSERT_TRUE(NULL != cache);
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ TestEntries entries;
+ int num_entries = 1000;
+
+ int ret = TimeWrite(num_entries, cache, &entries);
+ EXPECT_EQ(ret, g_cache_tests_received);
+
+ delete cache;
+
+ ASSERT_TRUE(EvictFileFromSystemCache((path + L"\\index").c_str()));
+ ASSERT_TRUE(EvictFileFromSystemCache((path + L"\\data_0").c_str()));
+ ASSERT_TRUE(EvictFileFromSystemCache((path + L"\\data_1").c_str()));
+ ASSERT_TRUE(EvictFileFromSystemCache((path + L"\\data_2").c_str()));
+ ASSERT_TRUE(EvictFileFromSystemCache((path + L"\\data_3").c_str()));
+
+ cache = disk_cache::CreateCacheBackend(path, false, 0);
+ ASSERT_TRUE(NULL != cache);
+
+ ret = TimeRead(num_entries, cache, entries, true);
+ EXPECT_EQ(ret, g_cache_tests_received);
+
+ ret = TimeRead(num_entries, cache, entries, false);
+ EXPECT_EQ(ret, g_cache_tests_received);
+
+ delete cache;
+}
diff --git a/net/disk_cache/disk_cache_test_base.cc b/net/disk_cache/disk_cache_test_base.cc
new file mode 100644
index 0000000..57ab31f
--- /dev/null
+++ b/net/disk_cache/disk_cache_test_base.cc
@@ -0,0 +1,126 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/disk_cache_test_base.h"
+
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/mem_backend_impl.h"
+
+void DiskCacheTestBase::SetMaxSize(int size) {
+ size_ = size;
+ if (cache_impl_)
+ EXPECT_TRUE(cache_impl_->SetMaxSize(size));
+
+ if (mem_cache_)
+ EXPECT_TRUE(mem_cache_->SetMaxSize(size));
+}
+
+void DiskCacheTestBase::InitCache() {
+ if (mask_)
+ implementation_ = true;
+
+ if (memory_only_)
+ InitMemoryCache();
+ else
+ InitDiskCache();
+
+ ASSERT_TRUE(NULL != cache_);
+ ASSERT_EQ(0, cache_->GetEntryCount());
+}
+
+void DiskCacheTestBase::InitMemoryCache() {
+ if (!implementation_) {
+ cache_ = disk_cache::CreateInMemoryCacheBackend(size_);
+ return;
+ }
+
+ mem_cache_ = new disk_cache::MemBackendImpl();
+ cache_ = mem_cache_;
+ ASSERT_TRUE(NULL != cache_);
+
+ if (size_)
+ EXPECT_TRUE(mem_cache_->SetMaxSize(size_));
+
+ ASSERT_TRUE(mem_cache_->Init());
+}
+
+void DiskCacheTestBase::InitDiskCache() {
+ std::wstring path = GetCachePath();
+ ASSERT_TRUE(DeleteCache(path.c_str()));
+
+ if (!implementation_) {
+ cache_ = disk_cache::CreateCacheBackend(path, force_creation_, size_);
+ return;
+ }
+
+ if (mask_)
+ cache_impl_ = new disk_cache::BackendImpl(path, mask_);
+ else
+ cache_impl_ = new disk_cache::BackendImpl(path);
+
+ cache_ = cache_impl_;
+ ASSERT_TRUE(NULL != cache_);
+
+ if (size_)
+ EXPECT_TRUE(cache_impl_->SetMaxSize(size_));
+
+ ASSERT_TRUE(cache_impl_->Init());
+}
+
+
+void DiskCacheTestBase::TearDown() {
+ delete cache_;
+
+ if (!memory_only_) {
+ std::wstring path = GetCachePath();
+ EXPECT_TRUE(CheckCacheIntegrity(path));
+ }
+}
+
+// We are expected to leak memory when simulating crashes.
+void DiskCacheTestBase::SimulateCrash() {
+ ASSERT_TRUE(implementation_ && !memory_only_);
+ cache_impl_->ClearRefCountForTest();
+
+ delete cache_impl_;
+ std::wstring path = GetCachePath();
+ EXPECT_TRUE(CheckCacheIntegrity(path));
+
+ if (mask_)
+ cache_impl_ = new disk_cache::BackendImpl(path, mask_);
+ else
+ cache_impl_ = new disk_cache::BackendImpl(path);
+ cache_ = cache_impl_;
+ ASSERT_TRUE(NULL != cache_);
+
+ if (size_)
+ cache_impl_->SetMaxSize(size_);
+ ASSERT_TRUE(cache_impl_->Init());
+}
diff --git a/net/disk_cache/disk_cache_test_base.h b/net/disk_cache/disk_cache_test_base.h
new file mode 100644
index 0000000..feb22b2
--- /dev/null
+++ b/net/disk_cache/disk_cache_test_base.h
@@ -0,0 +1,92 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H__
+#define NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H__
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace disk_cache {
+
+class Backend;
+class BackendImpl;
+class MemBackendImpl;
+
+}
+
+// Provides basic support for cache related tests.
+class DiskCacheTestBase : public testing::Test {
+ protected:
+ DiskCacheTestBase()
+ : cache_(NULL), cache_impl_(NULL), mem_cache_(NULL), mask_(0), size_(0),
+ memory_only_(false), implementation_(false), force_creation_(false) {}
+
+ void InitCache();
+ virtual void TearDown();
+ void SimulateCrash();
+
+ void SetMemoryOnlyMode() {
+ memory_only_ = true;
+ }
+
+ // Use the implementation directly instead of the factory provided object.
+ void SetDirectMode() {
+ implementation_ = 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;
+ }
+
+ // cache_ will always have a valid object, regardless of how the cache was
+ // initialized. The implementation pointers can be NULL.
+ disk_cache::Backend* cache_;
+ disk_cache::BackendImpl* cache_impl_;
+ disk_cache::MemBackendImpl* mem_cache_;
+
+ uint32 mask_;
+ int size_;
+ bool memory_only_;
+ bool implementation_;
+ bool force_creation_;
+
+ private:
+ void InitMemoryCache();
+ void InitDiskCache();
+};
+
+#endif // NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H__
diff --git a/net/disk_cache/disk_cache_test_util.cc b/net/disk_cache/disk_cache_test_util.cc
new file mode 100644
index 0000000..5ad5d9e
--- /dev/null
+++ b/net/disk_cache/disk_cache_test_util.cc
@@ -0,0 +1,163 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/disk_cache_test_util.h"
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_handle.h"
+#include "net/disk_cache/backend_impl.h"
+
+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';
+}
+
+std::wstring GetCachePath() {
+ std::wstring path;
+ PathService::Get(base::DIR_TEMP, &path);
+ file_util::AppendToPath(&path, L"cache_test");
+ if (!file_util::PathExists(path))
+ file_util::CreateDirectory(path);
+
+ return path;
+}
+
+bool CreateCacheTestFile(const wchar_t* name) {
+ ScopedHandle file(CreateFile(name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ CREATE_ALWAYS, 0, NULL));
+ if (!file.IsValid())
+ return false;
+
+ SetFilePointer(file, 4 * 1024 * 1024, 0, FILE_BEGIN);
+ SetEndOfFile(file);
+ return true;
+}
+
+bool DeleteCache(const wchar_t* path) {
+ std::wstring my_path(path);
+ file_util::AppendToPath(&my_path, L"*.*");
+ return file_util::Delete(my_path, false);
+}
+
+bool CheckCacheIntegrity(const std::wstring& path) {
+ scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(path));
+ if (!cache.get())
+ return false;
+ if (!cache->Init())
+ return false;
+ return cache->SelfCheck() >= 0;
+}
+
+// -----------------------------------------------------------------------
+
+int g_cache_tests_max_id = 0;
+volatile int g_cache_tests_received = 0;
+volatile bool g_cache_tests_error = 0;
+
+// On the actual callback, increase the number of tests received and check for
+// errors (an unexpected test received)
+void CallbackTest::RunWithParams(const Tuple1<int>& params) {
+ if (id_ > g_cache_tests_max_id) {
+ NOTREACHED();
+ g_cache_tests_error = true;
+ } else if (reuse_) {
+ DCHECK(1 == reuse_);
+ if (2 == reuse_)
+ g_cache_tests_error = true;
+ reuse_++;
+ }
+
+ g_cache_tests_received++;
+}
+
+// -----------------------------------------------------------------------
+
+// Quits the message loop when all callbacks are called or we've been waiting
+// too long for them (2 secs without a callback).
+void TimerTask::Run() {
+ if (g_cache_tests_received > num_callbacks_) {
+ NOTREACHED();
+ } else if (g_cache_tests_received == num_callbacks_) {
+ completed_ = true;
+ MessageLoop::current()->Quit();
+ } else {
+ // Not finished yet. See if we have to abort.
+ if (last_ == g_cache_tests_received)
+ num_iterations_++;
+ else
+ last_ = g_cache_tests_received;
+ if (40 == num_iterations_)
+ MessageLoop::current()->Quit();
+ }
+}
+
+// -----------------------------------------------------------------------
+
+MessageLoopHelper::MessageLoopHelper() {
+ message_loop_ = MessageLoop::current();
+ // Create a recurrent timer of 50 mS.
+ timer_ = message_loop_->timer_manager()->StartTimer(50, &timer_task_, true);
+}
+
+MessageLoopHelper::~MessageLoopHelper() {
+ message_loop_->timer_manager()->StopTimer(timer_);
+ delete timer_;
+}
+
+bool MessageLoopHelper::WaitUntilCacheIoFinished(int num_callbacks) {
+ if (num_callbacks == g_cache_tests_received)
+ return true;
+
+ timer_task_.ExpectCallbacks(num_callbacks);
+ message_loop_->Run();
+ return timer_task_.GetSate();
+}
+
diff --git a/net/disk_cache/disk_cache_test_util.h b/net/disk_cache/disk_cache_test_util.h
new file mode 100644
index 0000000..ee41059
--- /dev/null
+++ b/net/disk_cache/disk_cache_test_util.h
@@ -0,0 +1,127 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H__
+#define NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H__
+
+#include <string>
+
+#include "base/message_loop.h"
+#include "base/task.h"
+
+// Re-creates a given test file inside the cache test folder.
+bool CreateCacheTestFile(const wchar_t* name);
+
+// Deletes all file son the cache.
+bool DeleteCache(const wchar_t* path);
+
+// Gets the path to the cache test folder.
+std::wstring GetCachePath();
+
+// Fills buffer with random values (may contain nulls unless no_nulls is true).
+void CacheTestFillBuffer(char* buffer, size_t len, bool no_nulls);
+
+// Deletes all files matching a pattern.
+// Do not call this function with "*" as search_name.
+bool DeleteFiles(const wchar_t* path, const wchar_t* search_name);
+
+// 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 std::wstring& path);
+
+// -----------------------------------------------------------------------
+
+// Simple callback to process IO completions from the cache.
+class CallbackTest : public CallbackRunner< Tuple1<int> > {
+ public:
+ explicit CallbackTest(int id) : id_(id), reuse_(0) {}
+ explicit CallbackTest(int id, bool reuse) : id_(id), reuse_(reuse ? 0 : 1) {}
+ ~CallbackTest() {}
+
+ virtual void RunWithParams(const Tuple1<int>& params);
+
+ private:
+ int id_;
+ int reuse_;
+ DISALLOW_EVIL_CONSTRUCTORS(CallbackTest);
+};
+
+// -----------------------------------------------------------------------
+
+// We'll use a timer to fire from time to time to check the number of IO
+// operations finished so far.
+class TimerTask : public Task {
+ public:
+ TimerTask() : num_callbacks_(0), num_iterations_(0) {}
+ ~TimerTask() {}
+
+ virtual void Run();
+
+ // 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;
+ }
+
+ // Returns true if all callbacks were invoked.
+ bool GetSate() {
+ return completed_;
+ }
+
+ private:
+ int num_callbacks_;
+ int num_iterations_;
+ int last_;
+ bool completed_;
+ DISALLOW_EVIL_CONSTRUCTORS(TimerTask);
+};
+
+// -----------------------------------------------------------------------
+
+// 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.
+ bool WaitUntilCacheIoFinished(int num_callbacks);
+
+ private:
+ MessageLoop* message_loop_;
+ Timer* timer_;
+ TimerTask timer_task_;
+ DISALLOW_EVIL_CONSTRUCTORS(MessageLoopHelper);
+};
+
+#endif // NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H__
diff --git a/net/disk_cache/disk_format.h b/net/disk_cache/disk_format.h
new file mode 100644
index 0000000..4546c88
--- /dev/null
+++ b/net/disk_cache/disk_format.h
@@ -0,0 +1,192 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 (net/addr.h) will be stored on 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. 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.
+//
+// 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 list, 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"
+
+namespace disk_cache {
+
+typedef uint32 CacheAddr;
+
+const int kIndexTablesize = 0x10000;
+const uint32 kIndexMagic = 0xC103CAC3;
+const uint32 kCurrentVersion = 0x10002; // Version 1.2.
+
+// Header for the master index file.
+struct 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 pad[8];
+ IndexHeader() {
+ memset(this, 0, sizeof(*this));
+ magic = kIndexMagic;
+ version = kCurrentVersion;
+ };
+};
+
+// 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 key_len;
+ CacheAddr long_key; // Optional address of a long key.
+ int32 data_size[2]; // We can store up to 2 data chunks for each
+ CacheAddr data_addr[2]; // entry.
+ char key[256 - 9 * 4]; // null terminated
+};
+
+COMPILE_ASSERT(sizeof(EntryStore) == 256, bad_EntyStore);
+const int kMaxInternalKeyLength = 4 * sizeof(EntryStore) -
+ offsetof(EntryStore, key) - 1;
+
+#pragma pack(push, old, 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.
+ void* pointer; // Pointer to the in-memory entry.
+};
+#pragma pack(pop, old)
+
+COMPILE_ASSERT(sizeof(RankingsNode) == 36, bad_RankingsNode);
+
+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;
+ BlockFileHeader() {
+ memset(this, 0, sizeof(BlockFileHeader));
+ magic = kBlockMagic;
+ version = kCurrentVersion;
+ };
+};
+
+COMPILE_ASSERT(sizeof(BlockFileHeader) == kBlockHeaderSize, bad_header);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_DISK_FORMAT_H__
diff --git a/net/disk_cache/entry_impl.cc b/net/disk_cache/entry_impl.cc
new file mode 100644
index 0000000..d2a4b0f
--- /dev/null
+++ b/net/disk_cache/entry_impl.cc
@@ -0,0 +1,779 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/entry_impl.h"
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+
+namespace {
+
+// This is a simple Task to execute the callback (from the message loop instead
+// of the APC).
+class InvokeCallback : public Task {
+ public:
+ InvokeCallback(net::CompletionCallback* callback, int argument)
+ : callback_(callback), argument_(argument) {}
+
+ virtual void Run() {
+ callback_->Run(argument_);
+ }
+
+ private:
+ net::CompletionCallback* callback_;
+ int argument_;
+ DISALLOW_EVIL_CONSTRUCTORS(InvokeCallback);
+};
+
+// This class implements FileIOCallback to buffer the callback from an IO
+// operation from the actual IO class.
+class SyncCallback: public disk_cache::FileIOCallback {
+ public:
+ SyncCallback(disk_cache::EntryImpl* entry,
+ net::CompletionCallback* callback )
+ : entry_(entry), callback_(callback) {
+ entry->AddRef();
+ entry->IncrementIoCount();
+ }
+ ~SyncCallback() {}
+
+ virtual void OnFileIOComplete(int bytes_copied);
+ void Discard();
+ private:
+ disk_cache::EntryImpl* entry_;
+ net::CompletionCallback* callback_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SyncCallback);
+};
+
+void SyncCallback::OnFileIOComplete(int bytes_copied) {
+ entry_->DecrementIoCount();
+ entry_->Release();
+ if (callback_) {
+ InvokeCallback* task = new InvokeCallback(callback_, bytes_copied);
+ MessageLoop::current()->PostTask(FROM_HERE, task);
+ }
+ delete this;
+}
+
+void SyncCallback::Discard() {
+ callback_ = NULL;
+ OnFileIOComplete(0);
+}
+
+// Clears buffer before offset and after valid_len, knowing that the size of
+// buffer is kMaxBlockSize.
+void ClearInvalidData(char* buffer, int offset, int valid_len) {
+ DCHECK(offset >= 0);
+ DCHECK(valid_len >= 0);
+ DCHECK(disk_cache::kMaxBlockSize >= offset + valid_len);
+ if (offset)
+ memset(buffer, 0, offset);
+ int end = disk_cache::kMaxBlockSize - offset - valid_len;
+ if (end)
+ memset(buffer + offset + valid_len, 0, end);
+}
+
+} // namespace
+
+namespace disk_cache {
+
+EntryImpl::EntryImpl(BackendImpl* backend, Addr address)
+ : entry_(NULL, Addr(0)), node_(NULL, Addr(0)) {
+ entry_.LazyInit(backend->File(address), address);
+ doomed_ = false;
+ backend_ = backend;
+ unreported_size_[0] = unreported_size_[1] = 0;
+}
+
+// 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 (doomed_) {
+ for (int index = 0; index < kKeyFileIndex; index++) {
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized()) {
+ DeleteData(address, index);
+ backend_->ModifyStorageSize(entry_.Data()->data_size[index] -
+ unreported_size_[index], 0);
+ }
+ }
+ Addr address(entry_.Data()->long_key);
+ DeleteData(address, kKeyFileIndex);
+ backend_->ModifyStorageSize(entry_.Data()->key_len, 0);
+
+ memset(node_.buffer(), 0, node_.size());
+ memset(entry_.buffer(), 0, entry_.size());
+ node_.Store();
+ entry_.Store();
+
+ backend_->DeleteBlock(node_.address(), false);
+ backend_->DeleteBlock(entry_.address(), false);
+ } else {
+ bool ret = true;
+ for (int index = 0; index < kKeyFileIndex; index++) {
+ if (user_buffers_[index].get()) {
+ if (!(ret = Flush(index, entry_.Data()->data_size[index], false)))
+ LOG(ERROR) << "Failed to save user data";
+ } else if (unreported_size_[index]) {
+ backend_->ModifyStorageSize(
+ entry_.Data()->data_size[index] - unreported_size_[index],
+ entry_.Data()->data_size[index]);
+ }
+ }
+ if (node_.HasData() && this == node_.Data()->pointer) {
+ // We have to do this after Flush because we may trigger a cache trim from
+ // there, and technically this entry should be "in use".
+ node_.Data()->pointer = NULL;
+ node_.set_modified();
+ }
+
+ 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() && node_.Data()->dirty) {
+ node_.Data()->dirty = 0;
+ node_.Store();
+ }
+ }
+
+ backend_->CacheEntryDestroyed();
+}
+
+void EntryImpl::DeleteData(Addr address, int index) {
+ if (!address.is_initialized())
+ return;
+ if (address.is_separate_file()) {
+ if (files_[index])
+ files_[index] = NULL; // Releases the object.
+
+ if (!DeleteFile(backend_->GetFileName(address).c_str()))
+ LOG(ERROR) << "Failed to delete " << backend_->GetFileName(address) <<
+ " from the cache.";
+ } else {
+ backend_->DeleteBlock(address, true);
+ }
+}
+
+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();
+ node->pointer = this;
+
+ entry_store->hash = hash;
+ 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* file = GetBackingFile(address, kKeyFileIndex);
+
+ size_t offset = 0;
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ if (!file || !file->Write(key.data(), key.size(), offset)) {
+ DeleteData(address, kKeyFileIndex);
+ return false;
+ }
+
+ if (address.is_separate_file())
+ 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()));
+ node->dirty = backend_->GetCurrentEntryId();
+ Log("Create Entry ");
+ return true;
+}
+
+void EntryImpl::Close() {
+ Release();
+}
+
+void EntryImpl::Doom() {
+ if (doomed_)
+ return;
+
+ SetPointerForInvalidEntry(backend_->GetCurrentEntryId());
+ backend_->InternalDoomEntry(this);
+}
+
+void EntryImpl::InternalDoom() {
+ DCHECK(node_.HasData());
+ if (!node_.Data()->dirty) {
+ node_.Data()->dirty = backend_->GetCurrentEntryId();
+ node_.Store();
+ }
+ doomed_ = true;
+}
+
+std::string EntryImpl::GetKey() const {
+ CacheEntryBlock* entry = const_cast<CacheEntryBlock*>(&entry_);
+ if (entry->Data()->key_len > kMaxInternalKeyLength) {
+ Addr address(entry->Data()->long_key);
+ DCHECK(address.is_initialized());
+ File* file = const_cast<EntryImpl*>(this)->GetBackingFile(address,
+ kKeyFileIndex);
+
+ size_t offset = 0;
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ std::string key;
+ if (!file || !file->Read(WriteInto(&key, entry->Data()->key_len + 1),
+ entry->Data()->key_len + 1, offset))
+ key.clear();
+ return key;
+ } else {
+ return std::string(entry->Data()->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 > 1)
+ return 0;
+
+ CacheEntryBlock* entry = const_cast<CacheEntryBlock*>(&entry_);
+ return entry->Data()->data_size[index];
+}
+
+int EntryImpl::ReadData(int index, int offset, char* buf, int buf_len,
+ net::CompletionCallback* completion_callback) {
+ DCHECK(node_.Data()->dirty);
+ if (index < 0 || index > 1)
+ 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 (offset + buf_len > entry_size)
+ buf_len = entry_size - offset;
+
+ UpdateRank(false);
+
+ backend_->OnEvent(Stats::READ_DATA);
+
+ if (user_buffers_[index].get()) {
+ // Complete the operation locally.
+ DCHECK(kMaxBlockSize >= offset + buf_len);
+ memcpy(buf , user_buffers_[index].get() + offset, buf_len);
+ return buf_len;
+ }
+
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(address.is_initialized());
+ if (!address.is_initialized())
+ return net::ERR_FAILED;
+
+ File* file = GetBackingFile(address, index);
+ if (!file)
+ return net::ERR_FAILED;
+
+ size_t file_offset = offset;
+ if (address.is_block_file())
+ file_offset += address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+
+ SyncCallback* io_callback = NULL;
+ if (completion_callback)
+ io_callback = new SyncCallback(this, completion_callback);
+
+ bool completed;
+ if (!file->Read(buf, buf_len, file_offset, io_callback, &completed)) {
+ if (io_callback)
+ io_callback->Discard();
+ return net::ERR_FAILED;
+ }
+
+ if (io_callback && completed)
+ io_callback->Discard();
+
+ return (completed || !completion_callback) ? buf_len : net::ERR_IO_PENDING;
+}
+
+int EntryImpl::WriteData(int index, int offset, const char* buf, int buf_len,
+ net::CompletionCallback* completion_callback,
+ bool truncate) {
+ DCHECK(node_.Data()->dirty);
+ if (index < 0 || index > 1)
+ 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) {
+ int size = offset + buf_len;
+ if (size <= max_file_size)
+ size = kint32max;
+ backend_->TooMuchStorageRequested(size);
+ return net::ERR_FAILED;
+ }
+
+ // Read the size at this point (it may change inside prepare).
+ int entry_size = entry_.Data()->data_size[index];
+ if (!PrepareTarget(index, offset, buf_len, truncate))
+ return net::ERR_FAILED;
+
+ if (entry_size < offset + buf_len) {
+ unreported_size_[index] += offset + buf_len - entry_size;
+ entry_.Data()->data_size[index] = offset + buf_len;
+ entry_.set_modified();
+ } else if (truncate) {
+ // If the size was modified inside PrepareTarget, we should not do
+ // anything here.
+ if ((entry_size > offset + buf_len) &&
+ (entry_size == entry_.Data()->data_size[index])) {
+ unreported_size_[index] += offset + buf_len - entry_size;
+ entry_.Data()->data_size[index] = offset + buf_len;
+ entry_.set_modified();
+ } else {
+ // Nothing to truncate.
+ truncate = false;
+ }
+ }
+
+ UpdateRank(true);
+
+ backend_->OnEvent(Stats::WRITE_DATA);
+
+ if (user_buffers_[index].get()) {
+ // Complete the operation locally.
+ DCHECK(kMaxBlockSize >= offset + buf_len);
+ memcpy(user_buffers_[index].get() + offset, buf, buf_len);
+ return buf_len;
+ }
+
+ Addr address(entry_.Data()->data_addr[index]);
+ File* file = GetBackingFile(address, index);
+ if (!file)
+ return net::ERR_FAILED;
+
+ size_t file_offset = offset;
+ if (address.is_block_file()) {
+ file_offset += address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ } else if (truncate) {
+ if (!file->SetLength(offset + buf_len))
+ return net::ERR_FAILED;
+ }
+
+ if (!buf_len)
+ return 0;
+
+ SyncCallback* io_callback = NULL;
+ if (completion_callback)
+ io_callback = new SyncCallback(this, completion_callback);
+
+ bool completed;
+ if (!file->Write(buf, buf_len, file_offset, io_callback, &completed)) {
+ if (io_callback)
+ io_callback->Discard();
+ return net::ERR_FAILED;
+ }
+
+ if (io_callback && completed)
+ io_callback->Discard();
+
+ return (completed || !completion_callback) ? buf_len : net::ERR_IO_PENDING;
+}
+
+bool EntryImpl::PrepareTarget(int index, int offset, int buf_len,
+ bool truncate) {
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized() || user_buffers_[index].get())
+ return GrowUserBuffer(index, offset, buf_len, truncate);
+
+ if (offset + buf_len > kMaxBlockSize)
+ return CreateDataBlock(index, offset + buf_len);
+
+ user_buffers_[index].reset(new char[kMaxBlockSize]);
+
+ // Overwrite the parts of the buffer that are not going to be written
+ // by the current operation (and yes, let's assume that nothing is going
+ // to fail, and we'll actually write over the part that we are not cleaning
+ // here). The point is to avoid writing random stuff to disk later on.
+ ClearInvalidData(user_buffers_[index].get(), offset, buf_len);
+
+ return true;
+}
+
+// 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::GrowUserBuffer(int index, int offset, int buf_len,
+ bool truncate) {
+ Addr address(entry_.Data()->data_addr[index]);
+
+ if (offset + buf_len > kMaxBlockSize) {
+ // The data has to be stored externally.
+ if (address.is_initialized()) {
+ if (address.is_separate_file())
+ return true;
+ if (!MoveToLocalBuffer(index))
+ return false;
+ }
+ return Flush(index, offset + buf_len, true);
+ }
+
+ if (!address.is_initialized()) {
+ DCHECK(user_buffers_[index].get());
+ if (truncate)
+ ClearInvalidData(user_buffers_[index].get(), 0, offset + buf_len);
+ return true;
+ }
+ if (address.is_separate_file()) {
+ if (!truncate)
+ return true;
+ return ImportSeparateFile(index, offset, buf_len);
+ }
+
+ // At this point we are dealing with data stored on disk, inside a block file.
+ if (offset + buf_len <= address.BlockSize() * address.num_blocks())
+ return true;
+
+ // ... and the allocated block has to change.
+ if (!MoveToLocalBuffer(index))
+ return false;
+
+ int clear_start = entry_.Data()->data_size[index];
+ if (truncate)
+ clear_start = std::min(clear_start, offset + buf_len);
+ else if (offset < clear_start)
+ clear_start = std::max(offset + buf_len, clear_start);
+
+ // Clear the end of the buffer.
+ ClearInvalidData(user_buffers_[index].get(), 0, clear_start);
+ return true;
+}
+
+bool EntryImpl::ImportSeparateFile(int index, int offset, int buf_len) {
+ if (entry_.Data()->data_size[index] > offset + buf_len) {
+ entry_.Data()->data_size[index] = offset + buf_len;
+ unreported_size_[index] += offset + buf_len -
+ entry_.Data()->data_size[index];
+ }
+
+ if (!MoveToLocalBuffer(index))
+ return false;
+
+ // Clear the end of the buffer.
+ ClearInvalidData(user_buffers_[index].get(), 0, offset + buf_len);
+ return true;
+}
+
+bool EntryImpl::MoveToLocalBuffer(int index) {
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(!user_buffers_[index].get());
+ DCHECK(address.is_initialized());
+ scoped_ptr<char> buffer(new char[kMaxBlockSize]);
+
+ File* file = GetBackingFile(address, index);
+ size_t len = entry_.Data()->data_size[index];
+ size_t offset = 0;
+
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ if (!file || !file->Read(buffer.get(), len, offset, NULL, NULL))
+ return false;
+
+ DeleteData(address, index);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Store();
+
+ // If we lose this entry we'll see it as zero sized.
+ backend_->ModifyStorageSize(static_cast<int>(len) - unreported_size_[index],
+ 0);
+ unreported_size_[index] = static_cast<int>(len);
+
+ user_buffers_[index].swap(buffer);
+ return true;
+}
+
+// The common scenario is that this is called from the destructor of the entry,
+// to write to disk what we have buffered. We don't want to hold the destructor
+// until the actual IO finishes, so we'll send an asynchronous write that will
+// free up the memory containing the data. To be consistent, this method always
+// returns with the buffer freed up (on success).
+bool EntryImpl::Flush(int index, int size, bool async) {
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(user_buffers_[index].get());
+ DCHECK(!address.is_initialized());
+
+ if (!size)
+ return true;
+
+ if (!CreateDataBlock(index, size))
+ return false;
+
+ address.set_value(entry_.Data()->data_addr[index]);
+
+ File* file = GetBackingFile(address, index);
+ size_t len = entry_.Data()->data_size[index];
+ size_t offset = 0;
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ // We just told the backend to store len bytes for real.
+ DCHECK(len == unreported_size_[index]);
+ backend_->ModifyStorageSize(0, static_cast<int>(len));
+ unreported_size_[index] = 0;
+
+ if (!file)
+ return false;
+
+ if (async) {
+ if (!file->PostWrite(user_buffers_[index].get(), len, offset))
+ return false;
+ } else {
+ if (!file->Write(user_buffers_[index].get(), len, offset, NULL, NULL))
+ return false;
+ user_buffers_[index].reset(NULL);
+ }
+
+ // The buffer is deleted from the PostWrite operation.
+ user_buffers_[index].release();
+
+ return true;
+}
+
+bool EntryImpl::LoadNodeAddress() {
+ Addr address(entry_.Data()->rankings_node);
+ if (!node_.LazyInit(backend_->File(address), address))
+ return false;
+ return node_.Load();
+}
+
+EntryImpl* EntryImpl::Update(EntryImpl* entry) {
+ DCHECK(entry->rankings()->HasData());
+
+ RankingsNode* rankings = entry->rankings()->Data();
+ if (rankings->pointer) {
+ // Already in memory. Prevent clearing the dirty flag on the destructor.
+ rankings->dirty = 0;
+ EntryImpl* real_node = reinterpret_cast<EntryImpl*>(rankings->pointer);
+ real_node->AddRef();
+ entry->Release();
+ return real_node;
+ } else {
+ rankings->dirty = entry->backend_->GetCurrentEntryId();
+ rankings->pointer = entry;
+ if (!entry->rankings()->Store()) {
+ entry->Release();
+ return NULL;
+ }
+ return entry;
+ }
+}
+
+bool EntryImpl::CreateDataBlock(int index, int size) {
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(0 == index || 1 == 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());
+
+ 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 = (size + Addr::BlockSizeForFileType(file_type) - 1) /
+ Addr::BlockSizeForFileType(file_type);
+
+ if (!backend_->CreateBlock(file_type, num_blocks, address))
+ return false;
+ }
+ return true;
+}
+
+bool EntryImpl::IsSameEntry(const std::string& key, uint32 hash) {
+ if (entry_.Data()->hash != hash || entry_.Data()->key_len != key.size())
+ return false;
+
+ std::string my_key = GetKey();
+ return key.compare(my_key) ? false : true;
+}
+
+CacheAddr EntryImpl::GetNextAddress() {
+ return entry_.Data()->next;
+}
+
+void EntryImpl::SetNextAddress(Addr address) {
+ entry_.Data()->next = address.value();
+ bool success = entry_.Store();
+ DCHECK(success);
+}
+
+void EntryImpl::UpdateRank(bool modified) {
+ if (!doomed_) {
+ // Everything is handled by the backend.
+ backend_->UpdateRank(&node_, true);
+ 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) {
+ 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 <= 2);
+ if (!files_[index].get()) {
+ // For a key file, use mixed mode IO.
+ scoped_refptr<File> file(new File(2 == index));
+ if (file->Init(backend_->GetFileName(address)))
+ files_[index].swap(file);
+ }
+ return files_[index].get();
+}
+
+uint32 EntryImpl::GetHash() {
+ return entry_.Data()->hash;
+}
+
+bool EntryImpl::IsDirty(int32 current_id) {
+ DCHECK(node_.HasData());
+ return node_.Data()->dirty && current_id != node_.Data()->dirty;
+}
+
+void EntryImpl::ClearDirtyFlag() {
+ node_.Data()->dirty = 0;
+}
+
+void EntryImpl::SetPointerForInvalidEntry(int32 new_id) {
+ node_.Data()->dirty = new_id;
+ node_.Data()->pointer = this;
+ node_.Store();
+}
+
+bool EntryImpl::SanityCheck() {
+ if (!entry_.Data()->rankings_node || !entry_.Data()->key_len)
+ return false;
+
+ Addr rankings_addr(entry_.Data()->rankings_node);
+ if (!rankings_addr.is_initialized() || rankings_addr.is_separate_file() ||
+ rankings_addr.file_type() != RANKINGS)
+ return false;
+
+ Addr next_addr(entry_.Data()->next);
+ if (next_addr.is_initialized() &&
+ (next_addr.is_separate_file() || next_addr.file_type() != BLOCK_256))
+ return false;
+
+ return true;
+}
+
+void EntryImpl::IncrementIoCount() {
+ backend_->IncrementIoCount();
+}
+
+void EntryImpl::DecrementIoCount() {
+ backend_->DecrementIoCount();
+}
+
+void EntryImpl::Log(const char* msg) {
+ void* pointer = NULL;
+ int dirty = 0;
+ if (node_.HasData()) {
+ pointer = node_.Data()->pointer;
+ 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%p 0x%x", doomed_, pointer, dirty);
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/entry_impl.h b/net/disk_cache/entry_impl.h
new file mode 100644
index 0000000..8000f37
--- /dev/null
+++ b/net/disk_cache/entry_impl.h
@@ -0,0 +1,168 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_DISK_CACHE_ENTRY_IMPL_H__
+#define NET_DISK_CACHE_ENTRY_IMPL_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;
+
+// This class implements the Entry interface. An object of this
+// class represents a single entry on the cache.
+class EntryImpl : public Entry, public base::RefCounted<EntryImpl> {
+ friend class base::RefCounted<EntryImpl>;
+ public:
+ EntryImpl(BackendImpl* backend, Addr address);
+
+ // Entry interface.
+ virtual void Doom();
+ virtual void Close();
+ virtual std::string GetKey() const;
+ virtual Time GetLastUsed() const;
+ virtual Time GetLastModified() const;
+ virtual int32 GetDataSize(int index) const;
+ virtual int ReadData(int index, int offset, char* buf, int buf_len,
+ net::CompletionCallback* completion_callback);
+ virtual int WriteData(int index, int offset, const char* buf, int buf_len,
+ net::CompletionCallback* completion_callback,
+ bool truncate);
+
+ 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();
+
+ // 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();
+
+ // Reloads the data for this entry. If there is already an object in memory
+ // for the entry, the returned value is a pointer to that entry, otherwise
+ // it is the passed in entry. On failure returns NULL.
+ static EntryImpl* Update(EntryImpl* entry);
+
+ // Returns true if this entry is marked as dirty on disk.
+ bool IsDirty(int32 current_id);
+ void ClearDirtyFlag();
+
+ // Fixes this entry so it can be treated as valid (to delete it).
+ void SetPointerForInvalidEntry(int32 new_id);
+
+ // Returns false if the entry is clearly invalid.
+ bool SanityCheck();
+
+ // Handle the pending asynchronous IO count.
+ void IncrementIoCount();
+ void DecrementIoCount();
+
+ private:
+ ~EntryImpl();
+
+ // Index for the file used to store the key, if any (files_[kKeyFileIndex]).
+ static const int kKeyFileIndex = 2;
+
+ // 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].
+ 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);
+
+ // Grows the size of the storage used to store user data, if needed.
+ bool GrowUserBuffer(int index, int offset, int buf_len, bool truncate);
+
+ // 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 offset, int buf_len);
+
+ // Flush the in-memory data to the backing storage.
+ bool Flush(int index, int size, bool async);
+
+ // 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.
+ BackendImpl* backend_; // Back pointer to the cache.
+ scoped_ptr<char> user_buffers_[2]; // Store user data.
+ scoped_refptr<File> files_[3]; // Files to store external user data and key.
+ int unreported_size_[2]; // Bytes not reported yet to the backend.
+ bool doomed_; // True if this entry was removed from the cache.
+
+ DISALLOW_EVIL_CONSTRUCTORS(EntryImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_ENTRY_IMPL_H__
diff --git a/net/disk_cache/entry_unittest.cc b/net/disk_cache/entry_unittest.cc
new file mode 100644
index 0000000..d2eea79
--- /dev/null
+++ b/net/disk_cache/entry_unittest.cc
@@ -0,0 +1,713 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/timer.h"
+#include "net/base/net_errors.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 "testing/gtest/include/gtest/gtest.h"
+
+extern int g_cache_tests_max_id;
+extern volatile int g_cache_tests_received;
+extern volatile bool g_cache_tests_error;
+
+// Tests that can run with different types of caches.
+class DiskCacheEntryTest : public DiskCacheTestBase {
+ protected:
+ void InternalSyncIO();
+ void InternalAsyncIO();
+ void ExternalSyncIO();
+ void ExternalAsyncIO();
+ void GetKey();
+ void GrowData();
+ void TruncateData();
+ void InvalidData();
+ void DoomEntry();
+ void DoomedEntry();
+};
+
+void DiskCacheEntryTest::InternalSyncIO() {
+ disk_cache::Entry *entry1 = NULL;
+ ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_TRUE(NULL != entry1);
+
+ char buffer1[10];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ EXPECT_EQ(0, entry1->ReadData(0, 0, buffer1, sizeof(buffer1), NULL));
+ strcpy_s(buffer1, "the data");
+ EXPECT_EQ(10, entry1->WriteData(0, 0, buffer1, sizeof(buffer1), NULL, false));
+ memset(buffer1, 0, sizeof(buffer1));
+ EXPECT_EQ(10, entry1->ReadData(0, 0, buffer1, sizeof(buffer1), NULL));
+ EXPECT_STREQ("the data", buffer1);
+
+ char buffer2[5000];
+ char buffer3[10000] = {0};
+ CacheTestFillBuffer(buffer2, sizeof(buffer2), false);
+ strcpy_s(buffer2, "The really big data goes here");
+ EXPECT_EQ(5000, entry1->WriteData(1, 1500, buffer2, sizeof(buffer2), NULL,
+ false));
+ memset(buffer2, 0, sizeof(buffer2));
+ EXPECT_EQ(4989, entry1->ReadData(1, 1511, buffer2, sizeof(buffer2), NULL));
+ EXPECT_STREQ("big data goes here", buffer2);
+ EXPECT_EQ(5000, entry1->ReadData(1, 0, buffer2, sizeof(buffer2), NULL));
+ EXPECT_EQ(0, memcmp(buffer2, buffer3, 1500));
+ EXPECT_EQ(1500, entry1->ReadData(1, 5000, buffer2, sizeof(buffer2), NULL));
+
+ EXPECT_EQ(0, entry1->ReadData(1, 6500, buffer2, sizeof(buffer2), NULL));
+ EXPECT_EQ(6500, entry1->ReadData(1, 0, buffer3, sizeof(buffer3), NULL));
+ EXPECT_EQ(8192, entry1->WriteData(1, 0, buffer3, 8192, NULL, false));
+ EXPECT_EQ(8192, entry1->ReadData(1, 0, buffer3, sizeof(buffer3), NULL));
+ EXPECT_EQ(8192, entry1->GetDataSize(1));
+
+ entry1->Doom();
+ entry1->Close();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, InternalSyncIO) {
+ InitCache();
+ InternalSyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyInternalSyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ InternalSyncIO();
+}
+
+void DiskCacheEntryTest::InternalAsyncIO() {
+ disk_cache::Entry *entry1 = NULL;
+ ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+ ASSERT_TRUE(NULL != entry1);
+
+ // Let's verify that each IO goes to the right callback object.
+ CallbackTest callback1(1, false);
+ CallbackTest callback2(2, false);
+ CallbackTest callback3(3, false);
+ CallbackTest callback4(4, false);
+ CallbackTest callback5(5, false);
+ CallbackTest callback6(6, false);
+ CallbackTest callback7(7, false);
+ CallbackTest callback8(8, false);
+ CallbackTest callback9(9, false);
+ CallbackTest callback10(10, false);
+ CallbackTest callback11(11, false);
+ CallbackTest callback12(12, false);
+ CallbackTest callback13(13, false);
+
+ g_cache_tests_error = false;
+ g_cache_tests_max_id = 0;
+ g_cache_tests_received = 0;
+
+ MessageLoopHelper helper;
+
+ char buffer1[10];
+ char buffer2[5000];
+ char buffer3[10000];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ CacheTestFillBuffer(buffer2, sizeof(buffer2), false);
+ CacheTestFillBuffer(buffer3, sizeof(buffer3), false);
+
+ EXPECT_EQ(0, entry1->ReadData(0, 0, buffer1, sizeof(buffer1), &callback1));
+ strcpy_s(buffer1, "the data");
+ int expected = 0;
+ int ret = entry1->WriteData(0, 0, buffer1, sizeof(buffer1), &callback2,
+ false);
+ EXPECT_TRUE(10 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ memset(buffer2, 0, sizeof(buffer1));
+ ret = entry1->ReadData(0, 0, buffer2, sizeof(buffer1), &callback3);
+ EXPECT_TRUE(10 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 3;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("the data", buffer2);
+
+ strcpy_s(buffer2, sizeof(buffer2), "The really big data goes here");
+ ret = entry1->WriteData(1, 1500, buffer2, sizeof(buffer2), &callback4, false);
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ memset(buffer3, 0, sizeof(buffer2));
+ ret = entry1->ReadData(1, 1511, buffer3, sizeof(buffer2), &callback5);
+ EXPECT_TRUE(4989 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 5;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("big data goes here", buffer3);
+ ret = entry1->ReadData(1, 0, buffer2, sizeof(buffer2), &callback6);
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ memset(buffer3, 0, sizeof(buffer3));
+
+ g_cache_tests_max_id = 6;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(0, memcmp(buffer2, buffer3, 1500));
+ ret = entry1->ReadData(1, 5000, buffer2, sizeof(buffer2), &callback7);
+ EXPECT_TRUE(1500 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_EQ(0, entry1->ReadData(1, 6500, buffer2, sizeof(buffer2), &callback8));
+ ret = entry1->ReadData(1, 0, buffer3, sizeof(buffer3), &callback9);
+ EXPECT_TRUE(6500 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ ret = entry1->WriteData(1, 0, buffer3, 8192, &callback10, false);
+ EXPECT_TRUE(8192 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ ret = entry1->ReadData(1, 0, buffer3, sizeof(buffer3), &callback11);
+ EXPECT_TRUE(8192 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_EQ(8192, entry1->GetDataSize(1));
+
+ ret = entry1->ReadData(0, 0, buffer1, sizeof(buffer1), &callback12);
+ EXPECT_TRUE(10 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ ret = entry1->ReadData(1, 0, buffer2, sizeof(buffer2), &callback13);
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 13;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+
+ EXPECT_FALSE(g_cache_tests_error);
+ EXPECT_EQ(expected, g_cache_tests_received);
+
+ entry1->Doom();
+ entry1->Close();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, InternalAsyncIO) {
+ InitCache();
+ InternalAsyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyInternalAsyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ InternalAsyncIO();
+}
+
+void DiskCacheEntryTest::ExternalSyncIO() {
+ disk_cache::Entry *entry1;
+ ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+
+ char buffer1[17000], buffer2[25000];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ CacheTestFillBuffer(buffer2, sizeof(buffer2), false);
+ strcpy_s(buffer1, "the data");
+ EXPECT_EQ(17000, entry1->WriteData(0, 0, buffer1, sizeof(buffer1), NULL,
+ false));
+ memset(buffer1, 0, sizeof(buffer1));
+ EXPECT_EQ(17000, entry1->ReadData(0, 0, buffer1, sizeof(buffer1), NULL));
+ EXPECT_STREQ("the data", buffer1);
+
+ strcpy_s(buffer2, "The really big data goes here");
+ EXPECT_EQ(25000, entry1->WriteData(1, 10000, buffer2, sizeof(buffer2), NULL,
+ false));
+ memset(buffer2, 0, sizeof(buffer2));
+ EXPECT_EQ(24989, entry1->ReadData(1, 10011, buffer2, sizeof(buffer2), NULL));
+ EXPECT_STREQ("big data goes here", buffer2);
+ EXPECT_EQ(25000, entry1->ReadData(1, 0, buffer2, sizeof(buffer2), NULL));
+ EXPECT_EQ(0, memcmp(buffer2, buffer2, 10000));
+ EXPECT_EQ(5000, entry1->ReadData(1, 30000, buffer2, sizeof(buffer2), NULL));
+
+ EXPECT_EQ(0, entry1->ReadData(1, 35000, buffer2, sizeof(buffer2), NULL));
+ EXPECT_EQ(17000, entry1->ReadData(1, 0, buffer1, sizeof(buffer1), NULL));
+ EXPECT_EQ(17000, entry1->WriteData(1, 20000, buffer1, sizeof(buffer1), NULL,
+ false));
+ EXPECT_EQ(37000, entry1->GetDataSize(1));
+
+ entry1->Doom();
+ entry1->Close();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, ExternalSyncIO) {
+ InitCache();
+ ExternalSyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyExternalSyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ ExternalSyncIO();
+}
+
+void DiskCacheEntryTest::ExternalAsyncIO() {
+ disk_cache::Entry *entry1;
+ ASSERT_TRUE(cache_->CreateEntry("the first key", &entry1));
+
+ // Let's verify that each IO goes to the right callback object.
+ CallbackTest callback1(1, false);
+ CallbackTest callback2(2, false);
+ CallbackTest callback3(3, false);
+ CallbackTest callback4(4, false);
+ CallbackTest callback5(5, false);
+ CallbackTest callback6(6, false);
+ CallbackTest callback7(7, false);
+ CallbackTest callback8(8, false);
+ CallbackTest callback9(9, false);
+
+ g_cache_tests_error = false;
+ g_cache_tests_max_id = 0;
+ g_cache_tests_received = 0;
+ int expected = 0;
+
+ MessageLoopHelper helper;
+
+ char buffer1[17000], buffer2[25000], buffer3[25000];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ CacheTestFillBuffer(buffer2, sizeof(buffer2), false);
+ CacheTestFillBuffer(buffer3, sizeof(buffer3), false);
+ strcpy_s(buffer1, "the data");
+ int ret = entry1->WriteData(0, 0, buffer1, sizeof(buffer1), &callback1,
+ false);
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 1;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+
+ memset(buffer2, 0, sizeof(buffer1));
+ ret = entry1->ReadData(0, 0, buffer2, sizeof(buffer1), &callback2);
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 2;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("the data", buffer1);
+
+ strcpy_s(buffer2, "The really big data goes here");
+ ret = entry1->WriteData(1, 10000, buffer2, sizeof(buffer2), &callback3,
+ false);
+ EXPECT_TRUE(25000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 3;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+
+ memset(buffer3, 0, sizeof(buffer3));
+ ret = entry1->ReadData(1, 10011, buffer3, sizeof(buffer3), &callback4);
+ EXPECT_TRUE(24989 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 4;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("big data goes here", buffer3);
+ ret = entry1->ReadData(1, 0, buffer2, sizeof(buffer2), &callback5);
+ EXPECT_TRUE(25000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ g_cache_tests_max_id = 5;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(0, memcmp(buffer2, buffer2, 10000));
+ ret = entry1->ReadData(1, 30000, buffer2, sizeof(buffer2), &callback6);
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_EQ(0, entry1->ReadData(1, 35000, buffer2, sizeof(buffer2),
+ &callback7));
+ ret = entry1->ReadData(1, 0, buffer1, sizeof(buffer1), &callback8);
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ ret = entry1->WriteData(1, 20000, buffer1, sizeof(buffer1), &callback9,
+ false);
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ EXPECT_EQ(37000, entry1->GetDataSize(1));
+
+ g_cache_tests_max_id = 9;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+
+ EXPECT_FALSE(g_cache_tests_error);
+ EXPECT_EQ(expected, g_cache_tests_received);
+
+ entry1->Doom();
+ entry1->Close();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, ExternalAsyncIO) {
+ InitCache();
+ ExternalAsyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyExternalAsyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ ExternalAsyncIO();
+}
+
+void DiskCacheEntryTest::GetKey() {
+ std::string key1("the first key");
+ disk_cache::Entry *entry1;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ EXPECT_EQ(key1, entry1->GetKey()) << "short key";
+ entry1->Close();
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ char key_buffer[20000];
+
+ CacheTestFillBuffer(key_buffer, 3000, true);
+ key_buffer[1000] = '\0';
+
+ key1 = key_buffer;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ EXPECT_TRUE(key1 == entry1->GetKey()) << "1000 bytes key";
+ entry1->Close();
+
+ key_buffer[1000] = 'p';
+ key_buffer[3000] = '\0';
+ key1 = key_buffer;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ EXPECT_TRUE(key1 == entry1->GetKey()) << "medium size key";
+ entry1->Close();
+
+ CacheTestFillBuffer(key_buffer, sizeof(key_buffer), true);
+ key_buffer[19999] = '\0';
+
+ key1 = key_buffer;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ EXPECT_TRUE(key1 == entry1->GetKey()) << "long key";
+ entry1->Close();
+}
+
+TEST_F(DiskCacheEntryTest, GetKey) {
+ InitCache();
+ GetKey();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyGetKey) {
+ SetMemoryOnlyMode();
+ InitCache();
+ GetKey();
+}
+
+void DiskCacheEntryTest::GrowData() {
+ std::string key1("the first key");
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+
+ char buffer1[20000];
+ char buffer2[20000];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ memset(buffer2, 0, sizeof(buffer2));
+
+ strcpy_s(buffer1, "the data");
+ EXPECT_EQ(10, entry1->WriteData(0, 0, buffer1, 10, NULL, false));
+ EXPECT_EQ(10, entry1->ReadData(0, 0, buffer2, 10, NULL));
+ EXPECT_STREQ("the data", buffer2);
+ EXPECT_EQ(10, entry1->GetDataSize(0));
+
+ EXPECT_EQ(2000, entry1->WriteData(0, 0, buffer1, 2000, NULL, false));
+ EXPECT_EQ(2000, entry1->GetDataSize(0));
+ EXPECT_EQ(2000, entry1->ReadData(0, 0, buffer2, 2000, NULL));
+ EXPECT_TRUE(!memcmp(buffer1, buffer2, 2000));
+
+ EXPECT_EQ(20000, entry1->WriteData(0, 0, buffer1, sizeof(buffer1), NULL,
+ false));
+ EXPECT_EQ(20000, entry1->GetDataSize(0));
+ EXPECT_EQ(20000, entry1->ReadData(0, 0, buffer2, sizeof(buffer2), NULL));
+ EXPECT_TRUE(!memcmp(buffer1, buffer2, sizeof(buffer1)));
+ entry1->Close();
+
+ memset(buffer2, 0, sizeof(buffer2));
+ ASSERT_TRUE(cache_->CreateEntry("Second key", &entry2));
+ EXPECT_EQ(10, entry2->WriteData(0, 0, buffer1, 10, NULL, false));
+ EXPECT_EQ(10, entry2->GetDataSize(0));
+ entry2->Close();
+
+ // Go from an internal address to a bigger block size.
+ ASSERT_TRUE(cache_->OpenEntry("Second key", &entry2));
+ EXPECT_EQ(2000, entry2->WriteData(0, 0, buffer1, 2000, NULL, false));
+ EXPECT_EQ(2000, entry2->GetDataSize(0));
+ EXPECT_EQ(2000, entry2->ReadData(0, 0, buffer2, 2000, NULL));
+ EXPECT_TRUE(!memcmp(buffer1, buffer2, 2000));
+ entry2->Close();
+ memset(buffer2, 0, sizeof(buffer2));
+
+ // Go from an internal address to an external one.
+ ASSERT_TRUE(cache_->OpenEntry("Second key", &entry2));
+ EXPECT_EQ(20000, entry2->WriteData(0, 0, buffer1, sizeof(buffer1), NULL,
+ false));
+ EXPECT_EQ(20000, entry2->GetDataSize(0));
+ EXPECT_EQ(20000, entry2->ReadData(0, 0, buffer2, sizeof(buffer2), NULL));
+ EXPECT_TRUE(!memcmp(buffer1, buffer2, sizeof(buffer1)));
+ entry2->Close();
+}
+
+TEST_F(DiskCacheEntryTest, GrowData) {
+ InitCache();
+ GrowData();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyGrowData) {
+ SetMemoryOnlyMode();
+ InitCache();
+ GrowData();
+}
+
+void DiskCacheEntryTest::TruncateData() {
+ std::string key1("the first key");
+ disk_cache::Entry *entry1;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+
+ char buffer1[20000];
+ char buffer2[20000];
+
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ memset(buffer2, 0, sizeof(buffer2));
+
+ // Simple truncation:
+ EXPECT_EQ(200, entry1->WriteData(0, 0, buffer1, 200, NULL, false));
+ EXPECT_EQ(200, entry1->GetDataSize(0));
+ EXPECT_EQ(100, entry1->WriteData(0, 0, buffer1, 100, NULL, false));
+ EXPECT_EQ(200, entry1->GetDataSize(0));
+ EXPECT_EQ(100, entry1->WriteData(0, 0, buffer1, 100, NULL, true));
+ EXPECT_EQ(100, entry1->GetDataSize(0));
+ EXPECT_EQ(0, entry1->WriteData(0, 50, buffer1, 0, NULL, true));
+ EXPECT_EQ(50, entry1->GetDataSize(0));
+ EXPECT_EQ(0, entry1->WriteData(0, 0, buffer1, 0, NULL, true));
+ EXPECT_EQ(0, entry1->GetDataSize(0));
+ entry1->Close();
+ ASSERT_TRUE(cache_->OpenEntry(key1, &entry1));
+
+ // Go to an external file.
+ EXPECT_EQ(20000, entry1->WriteData(0, 0, buffer1, 20000, NULL, true));
+ EXPECT_EQ(20000, entry1->GetDataSize(0));
+ EXPECT_EQ(20000, entry1->ReadData(0, 0, buffer2, 20000, NULL));
+ EXPECT_TRUE(!memcmp(buffer1, buffer2, 20000));
+ memset(buffer2, 0, sizeof(buffer2));
+
+ // External file truncation
+ EXPECT_EQ(18000, entry1->WriteData(0, 0, buffer1, 18000, NULL, false));
+ EXPECT_EQ(20000, entry1->GetDataSize(0));
+ EXPECT_EQ(18000, entry1->WriteData(0, 0, buffer1, 18000, NULL, true));
+ EXPECT_EQ(18000, entry1->GetDataSize(0));
+ EXPECT_EQ(0, entry1->WriteData(0, 17500, buffer1, 0, NULL, true));
+ EXPECT_EQ(17500, entry1->GetDataSize(0));
+
+ // And back to an internal block.
+ EXPECT_EQ(600, entry1->WriteData(0, 1000, buffer1, 600, NULL, true));
+ EXPECT_EQ(1600, entry1->GetDataSize(0));
+ EXPECT_EQ(600, entry1->ReadData(0, 1000, buffer2, 600, NULL));
+ EXPECT_TRUE(!memcmp(buffer1, buffer2, 600));
+ EXPECT_EQ(1000, entry1->ReadData(0, 0, buffer2, 1000, NULL));
+ EXPECT_TRUE(!memcmp(buffer1, buffer2, 1000)) << "Preserves previous data";
+
+ // Go from external file to zero length.
+ EXPECT_EQ(20000, entry1->WriteData(0, 0, buffer1, 20000, NULL, true));
+ EXPECT_EQ(20000, entry1->GetDataSize(0));
+ EXPECT_EQ(0, entry1->WriteData(0, 0, buffer1, 0, NULL, true));
+ EXPECT_EQ(0, entry1->GetDataSize(0));
+
+ entry1->Close();
+}
+
+TEST_F(DiskCacheEntryTest, TruncateData) {
+ InitCache();
+ TruncateData();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyTruncateData) {
+ SetMemoryOnlyMode();
+ InitCache();
+ TruncateData();
+}
+
+// Reading somewhere that was not written should return zeros.
+void DiskCacheEntryTest::InvalidData() {
+ std::string key1("the first key");
+ disk_cache::Entry *entry1;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+
+ char buffer1[20000];
+ char buffer2[20000];
+ char buffer3[20000];
+
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ memset(buffer2, 0, sizeof(buffer2));
+
+ // Simple data grow:
+ EXPECT_EQ(200, entry1->WriteData(0, 400, buffer1, 200, NULL, false));
+ EXPECT_EQ(600, entry1->GetDataSize(0));
+ EXPECT_EQ(100, entry1->ReadData(0, 300, buffer3, 100, NULL));
+ EXPECT_TRUE(!memcmp(buffer3, buffer2, 100));
+ entry1->Close();
+ ASSERT_TRUE(cache_->OpenEntry(key1, &entry1));
+
+ // The entry is now on disk. Load it and extend it.
+ EXPECT_EQ(200, entry1->WriteData(0, 800, buffer1, 200, NULL, false));
+ EXPECT_EQ(1000, entry1->GetDataSize(0));
+ EXPECT_EQ(100, entry1->ReadData(0, 700, buffer3, 100, NULL));
+ EXPECT_TRUE(!memcmp(buffer3, buffer2, 100));
+ entry1->Close();
+ ASSERT_TRUE(cache_->OpenEntry(key1, &entry1));
+
+ // This time using truncate.
+ EXPECT_EQ(200, entry1->WriteData(0, 1800, buffer1, 200, NULL, true));
+ EXPECT_EQ(2000, entry1->GetDataSize(0));
+ EXPECT_EQ(100, entry1->ReadData(0, 1500, buffer3, 100, NULL));
+ EXPECT_TRUE(!memcmp(buffer3, buffer2, 100));
+
+ // Go to an external file.
+ EXPECT_EQ(200, entry1->WriteData(0, 19800, buffer1, 200, NULL, false));
+ EXPECT_EQ(20000, entry1->GetDataSize(0));
+ EXPECT_EQ(4000, entry1->ReadData(0, 14000, buffer3, 4000, NULL));
+ EXPECT_TRUE(!memcmp(buffer3, buffer2, 4000));
+
+ // And back to an internal block.
+ EXPECT_EQ(600, entry1->WriteData(0, 1000, buffer1, 600, NULL, true));
+ EXPECT_EQ(1600, entry1->GetDataSize(0));
+ EXPECT_EQ(600, entry1->ReadData(0, 1000, buffer3, 600, NULL));
+ EXPECT_TRUE(!memcmp(buffer3, buffer1, 600));
+
+ // Extend it again.
+ EXPECT_EQ(600, entry1->WriteData(0, 2000, buffer1, 600, NULL, false));
+ EXPECT_EQ(2600, entry1->GetDataSize(0));
+ EXPECT_EQ(200, entry1->ReadData(0, 1800, buffer3, 200, NULL));
+ EXPECT_TRUE(!memcmp(buffer3, buffer2, 200));
+
+ // And again (with truncation flag).
+ EXPECT_EQ(600, entry1->WriteData(0, 3000, buffer1, 600, NULL, true));
+ EXPECT_EQ(3600, entry1->GetDataSize(0));
+ EXPECT_EQ(200, entry1->ReadData(0, 2800, buffer3, 200, NULL));
+ EXPECT_TRUE(!memcmp(buffer3, buffer2, 200));
+
+ entry1->Close();
+}
+
+TEST_F(DiskCacheEntryTest, InvalidData) {
+ InitCache();
+ InvalidData();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyInvalidData) {
+ SetMemoryOnlyMode();
+ InitCache();
+ InvalidData();
+}
+
+void DiskCacheEntryTest::DoomEntry() {
+ std::string key1("the first key");
+ disk_cache::Entry *entry1;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ entry1->Doom();
+ entry1->Close();
+
+ char key_buffer[20000];
+ CacheTestFillBuffer(key_buffer, sizeof(key_buffer), true);
+ key_buffer[19999] = '\0';
+
+ key1 = key_buffer;
+ ASSERT_TRUE(cache_->CreateEntry(key1, &entry1));
+ EXPECT_EQ(20000, entry1->WriteData(0, 0, key_buffer, 20000, NULL, false));
+ EXPECT_EQ(20000, entry1->WriteData(1, 0, key_buffer, 20000, NULL, false));
+ entry1->Doom();
+ entry1->Close();
+
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, DoomEntry) {
+ InitCache();
+ DoomEntry();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyDoomEntry) {
+ SetMemoryOnlyMode();
+ InitCache();
+ DoomEntry();
+}
+
+// Verify that basic operations work as expected with doomed entries.
+void DiskCacheEntryTest::DoomedEntry() {
+ std::string key("the first key");
+ disk_cache::Entry *entry;
+ ASSERT_TRUE(cache_->CreateEntry(key, &entry));
+ entry->Doom();
+
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ Time initial = Time::Now();
+ Sleep(20);
+
+ char buffer1[2000];
+ char buffer2[2000];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ memset(buffer2, 0, sizeof(buffer2));
+
+ EXPECT_EQ(2000, entry->WriteData(0, 0, buffer1, 2000, NULL, false));
+ EXPECT_EQ(2000, entry->ReadData(0, 0, buffer2, 2000, NULL));
+ EXPECT_EQ(0, memcmp(buffer1, buffer2, sizeof(buffer1)));
+ EXPECT_TRUE(initial < entry->GetLastModified());
+ EXPECT_TRUE(initial < entry->GetLastUsed());
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, DoomedEntry) {
+ InitCache();
+ DoomEntry();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyDoomedEntry) {
+ SetMemoryOnlyMode();
+ InitCache();
+ DoomEntry();
+}
diff --git a/net/disk_cache/errors.h b/net/disk_cache/errors.h
new file mode 100644
index 0000000..553e6d14
--- /dev/null
+++ b/net/disk_cache/errors.h
@@ -0,0 +1,52 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Error codes reported by self tests.
+
+#ifndef NET_DISK_CACHE_ERRORS_H__
+#define NET_DISK_CACHE_ERRORS_H__
+
+namespace disk_cache {
+
+enum {
+ 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
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_ERRORS_H__
diff --git a/net/disk_cache/file.cc b/net/disk_cache/file.cc
new file mode 100644
index 0000000..36b023b
--- /dev/null
+++ b/net/disk_cache/file.cc
@@ -0,0 +1,313 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/file.h"
+
+#include "net/disk_cache/disk_cache.h"
+
+namespace {
+
+// This class implements FileIOCallback to perform IO operations
+// when the callback parameter of the operation is NULL.
+class SyncCallback: public disk_cache::FileIOCallback {
+ public:
+ SyncCallback() : called_(false) {}
+ ~SyncCallback() {}
+
+ virtual void OnFileIOComplete(int bytes_copied);
+ void WaitForResult(int* bytes_copied);
+ private:
+ bool called_;
+ int actual_;
+};
+
+void SyncCallback::OnFileIOComplete(int bytes_copied) {
+ actual_ = bytes_copied;
+ called_ = true;
+}
+
+// Waits for the IO operation to complete.
+void SyncCallback::WaitForResult(int* bytes_copied) {
+ for (;;) {
+ SleepEx(INFINITE, TRUE);
+ if (called_)
+ break;
+ }
+ *bytes_copied = actual_;
+}
+
+// Structure used for asynchronous operations.
+struct MyOverlapped {
+ OVERLAPPED overlapped;
+ disk_cache::File* file;
+ disk_cache::FileIOCallback* callback;
+ const void* buffer;
+ DWORD actual_bytes;
+ bool async; // Invoke the callback form the completion.
+ bool called; // Completion received.
+ bool delete_buffer; // Delete the user buffer at completion.
+};
+
+COMPILE_ASSERT(!offsetof(MyOverlapped, overlapped), starts_with_overlapped);
+
+} // namespace
+
+namespace disk_cache {
+
+// SyncCallback to be invoked as an APC when the asynchronous operation
+// completes.
+void CALLBACK IoCompletion(DWORD error, DWORD actual_bytes,
+ OVERLAPPED* overlapped) {
+ MyOverlapped* data = reinterpret_cast<MyOverlapped*>(overlapped);
+
+ if (error) {
+ DCHECK(!actual_bytes);
+ actual_bytes = static_cast<DWORD>(-1);
+ NOTREACHED();
+ }
+
+ if (data->delete_buffer) {
+ DCHECK(!data->callback);
+ data->file->Release();
+ delete data->buffer;
+ delete data;
+ return;
+ }
+
+ if (data->async) {
+ data->callback->OnFileIOComplete(static_cast<int>(actual_bytes));
+ data->file->Release();
+ delete data;
+ } else {
+ // Somebody is waiting for this so don't delete data and instead notify
+ // that we were called.
+ data->actual_bytes = actual_bytes;
+ data->file->Release();
+ data->called = true;
+ }
+}
+
+bool File::Init(const std::wstring name) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ handle_ = CreateFile(name.c_str(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL);
+
+ if (INVALID_HANDLE_VALUE == handle_)
+ return false;
+
+ init_ = true;
+ if (mixed_) {
+ sync_handle_ = CreateFile(name.c_str(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, NULL);
+
+ if (INVALID_HANDLE_VALUE == sync_handle_)
+ return false;
+ }
+
+ return true;
+}
+
+File::~File() {
+ if (!init_)
+ return;
+
+ CloseHandle(handle_);
+ if (mixed_ && INVALID_HANDLE_VALUE != sync_handle_)
+ CloseHandle(sync_handle_);
+}
+
+bool File::Read(void* buffer, size_t buffer_len, size_t offset) {
+ if (!mixed_ || buffer_len > ULONG_MAX || offset > LONG_MAX)
+ return false;
+
+ DWORD ret = SetFilePointer(sync_handle_, 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_handle_, buffer, size, &actual, NULL))
+ return false;
+ return actual == size;
+}
+
+bool File::Write(const void* buffer, size_t buffer_len, size_t offset) {
+ if (!mixed_ || buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ DWORD ret = SetFilePointer(sync_handle_, 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_handle_, 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) {
+ if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ MyOverlapped* data = new MyOverlapped;
+ memset(data, 0, sizeof(*data));
+
+ SyncCallback local_callback;
+ data->overlapped.Offset = static_cast<DWORD>(offset);
+ data->callback = callback ? callback : &local_callback;
+ data->file = this;
+
+ DWORD size = static_cast<DWORD>(buffer_len);
+ AddRef();
+
+ if (!ReadFileEx(handle_, buffer, size, &data->overlapped, &IoCompletion)) {
+ Release();
+ delete data;
+ return false;
+ }
+
+ if (callback) {
+ *completed = false;
+ // Let's check if the operation is already finished.
+ SleepEx(0, TRUE);
+ if (data->called) {
+ *completed = (data->actual_bytes == size);
+ DCHECK(data->actual_bytes == size);
+ delete data;
+ return *completed;
+ }
+ data->async = true;
+ } else {
+ // Invoke the callback and perform cleanup on the APC.
+ data->async = true;
+ int bytes_copied;
+ local_callback.WaitForResult(&bytes_copied);
+ if (static_cast<int>(buffer_len) != bytes_copied) {
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool File::Write(const void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed) {
+ return AsyncWrite(buffer, buffer_len, offset, true, callback, completed);
+}
+
+bool File::PostWrite(const void* buffer, size_t buffer_len, size_t offset) {
+ return AsyncWrite(buffer, buffer_len, offset, false, NULL, NULL);
+}
+
+bool File::AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
+ bool notify, FileIOCallback* callback, bool* completed) {
+ if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ MyOverlapped* data = new MyOverlapped;
+ memset(data, 0, sizeof(*data));
+
+ SyncCallback local_callback;
+ data->overlapped.Offset = static_cast<DWORD>(offset);
+ data->callback = callback ? callback : &local_callback;
+ data->file = this;
+ if (!callback && !notify) {
+ data->delete_buffer = true;
+ data->callback = NULL;
+ data->buffer = buffer;
+ }
+
+ DWORD size = static_cast<DWORD>(buffer_len);
+ AddRef();
+
+ if (!WriteFileEx(handle_, buffer, size, &data->overlapped, &IoCompletion)) {
+ Release();
+ delete data;
+ return false;
+ }
+
+ if (callback) {
+ *completed = false;
+ SleepEx(0, TRUE);
+ if (data->called) {
+ *completed = (data->actual_bytes == size);
+ DCHECK(data->actual_bytes == size);
+ delete data;
+ return *completed;
+ }
+ data->async = true;
+ } else if (notify) {
+ data->async = true;
+ int bytes_copied;
+ local_callback.WaitForResult(&bytes_copied);
+ if (static_cast<int>(buffer_len) != bytes_copied) {
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool File::SetLength(size_t length) {
+ if (length > ULONG_MAX)
+ return false;
+
+ DWORD size = static_cast<DWORD>(length);
+ if (INVALID_SET_FILE_POINTER == SetFilePointer(handle_, size, NULL,
+ FILE_BEGIN))
+ return false;
+
+ return TRUE == SetEndOfFile(handle_);
+}
+
+size_t File::GetLength() {
+ LARGE_INTEGER size;
+ if (!GetFileSizeEx(handle_, &size))
+ return 0;
+ if (size.HighPart)
+ return ULONG_MAX;
+
+ return static_cast<size_t>(size.LowPart);
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/file.h b/net/disk_cache/file.h
new file mode 100644
index 0000000..db42026
--- /dev/null
+++ b/net/disk_cache/file.h
@@ -0,0 +1,112 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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/ref_counted.h"
+
+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;
+};
+
+// Simple wrapper around a file that allows asynchronous operations.
+class File : public base::RefCounted<File> {
+ friend class base::RefCounted<File>;
+ public:
+ File() : init_(false), mixed_(false) {}
+ // mixed_mode set to true enables regular synchronous operations for the file.
+ explicit File(bool mixed_mode) : init_(false), mixed_(mixed_mode) {}
+
+ // 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 std::wstring name);
+
+#ifdef WIN32
+ HANDLE handle() const {
+ return handle_;
+ }
+#else
+ int file_descriptor() const {
+ return file_descriptor_;
+ }
+#endif
+
+ // 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);
+
+ // Performs asynchronous writes, but doesn't notify when done. Automatically
+ // deletes buffer when done.
+ bool PostWrite(const void* buffer, size_t buffer_len, size_t offset);
+
+ // 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();
+
+ 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,
+ bool notify, FileIOCallback* callback, bool* completed);
+
+ private:
+ bool init_;
+ bool mixed_;
+#ifdef WIN32
+ HANDLE handle_; // Regular, asynchronous IO handle.
+ HANDLE sync_handle_; // Synchronous IO hanlde.
+#else
+ int file_descriptor_;
+#endif
+
+ DISALLOW_EVIL_CONSTRUCTORS(File);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FILE_H__
diff --git a/net/disk_cache/file_block.h b/net/disk_cache/file_block.h
new file mode 100644
index 0000000..50574b3
--- /dev/null
+++ b/net/disk_cache/file_block.h
@@ -0,0 +1,56 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 DWORD offset() const = 0;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FILE_BLOCK_H__
diff --git a/net/disk_cache/file_lock.cc b/net/disk_cache/file_lock.cc
new file mode 100644
index 0000000..e78d567
--- /dev/null
+++ b/net/disk_cache/file_lock.cc
@@ -0,0 +1,52 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/file_lock.h"
+
+namespace disk_cache {
+
+FileLock::FileLock(BlockFileHeader* header) {
+ updating_ = &header->updating;
+ (*updating_)++;
+ acquired_ = true;
+}
+
+void FileLock::Lock() {
+ if (acquired_)
+ return;
+ (*updating_)++;
+}
+
+void FileLock::Unlock() {
+ if (!acquired_)
+ return;
+ (*updating_)--;
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/file_lock.h b/net/disk_cache/file_lock.h
new file mode 100644
index 0000000..4134ad9
--- /dev/null
+++ b/net/disk_cache/file_lock.h
@@ -0,0 +1,70 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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/disk_cache/disk_format.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 FileLock {
+ public:
+ explicit FileLock(BlockFileHeader* header);
+ ~FileLock() {
+ Unlock();
+ }
+ // 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/net/disk_cache/hash.cc b/net/disk_cache/hash.cc
new file mode 100644
index 0000000..1e83913
--- /dev/null
+++ b/net/disk_cache/hash.cc
@@ -0,0 +1,67 @@
+// From http://www.azillionmonkeys.com/qed/hash.html
+
+#include "net/disk_cache/hash.h"
+
+typedef uint32 uint32_t;
+typedef uint16 uint16_t;
+
+namespace disk_cache {
+
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
+ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+ +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+
+uint32 SuperFastHash(const char * data, int len) {
+ uint32_t hash = len, tmp;
+ int rem;
+
+ if (len <= 0 || data == NULL)
+ return 0;
+
+ rem = len & 3;
+ len >>= 2;
+
+ /* Main loop */
+ for (;len > 0; len--) {
+ hash += get16bits(data);
+ tmp = (get16bits(data + 2) << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ data += 2 * sizeof(uint16_t);
+ hash += hash >> 11;
+ }
+
+ /* Handle end cases */
+ switch (rem) {
+ case 3: hash += get16bits(data);
+ hash ^= hash << 16;
+ hash ^= data[sizeof(uint16_t)] << 18;
+ hash += hash >> 11;
+ break;
+ case 2: hash += get16bits(data);
+ hash ^= hash << 11;
+ hash += hash >> 17;
+ break;
+ case 1: hash += *data;
+ hash ^= hash << 10;
+ hash += hash >> 1;
+ }
+
+ /* Force "avalanching" of final 127 bits */
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+
+ return hash;
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/hash.h b/net/disk_cache/hash.h
new file mode 100644
index 0000000..e71a678
--- /dev/null
+++ b/net/disk_cache/hash.h
@@ -0,0 +1,55 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_DISK_CACHE_HASH_H__
+#define NET_DISK_CACHE_HASH_H__
+
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace disk_cache {
+
+// From http://www.azillionmonkeys.com/qed/hash.html
+// This is the hash used on WebCore/platform/stringhash
+uint32 SuperFastHash(const char * data, int len);
+
+inline uint32 Hash(const char* key, size_t length) {
+ return SuperFastHash(key, static_cast<int>(length));
+}
+
+inline uint32 Hash(const std::string& key) {
+ if (key.empty())
+ return 0;
+ return SuperFastHash(key.data(), static_cast<int>(key.size()));
+}
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_HASH_H__
diff --git a/net/disk_cache/mapped_file.cc b/net/disk_cache/mapped_file.cc
new file mode 100644
index 0000000..ee459da
--- /dev/null
+++ b/net/disk_cache/mapped_file.cc
@@ -0,0 +1,78 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/mapped_file.h"
+
+#include "net/disk_cache/disk_cache.h"
+
+namespace disk_cache {
+
+void* MappedFile::Init(const std::wstring name, size_t size) {
+ DCHECK(!init_);
+ if (init_ || !File::Init(name))
+ return NULL;
+
+ buffer_ = NULL;
+ init_ = true;
+ section_ = CreateFileMapping(handle(), 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;
+
+ 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);
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/mapped_file.h b/net/disk_cache/mapped_file.h
new file mode 100644
index 0000000..cb2da0c
--- /dev/null
+++ b/net/disk_cache/mapped_file.h
@@ -0,0 +1,77 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 "base/ref_counted.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/file.h"
+#include "net/disk_cache/file_block.h"
+
+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 MappedFile : public File {
+ public:
+ MappedFile() : init_(false), File(true) {}
+
+ // Performs object initialization. name is the file to use, and size is the
+ // ammount of data to memory map from th efile. If size is 0, the whole file
+ // will be mapped in memory.
+ void* Init(const std::wstring 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);
+
+ protected:
+ virtual ~MappedFile();
+
+ private:
+ bool init_;
+ HANDLE section_;
+ void* buffer_; // Address of the memory mapped buffer.
+ size_t view_size_; // Size of the memory pointed by buffer_.
+
+ DISALLOW_EVIL_CONSTRUCTORS(MappedFile);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MAPPED_FILE_H__
diff --git a/net/disk_cache/mapped_file_unittest.cc b/net/disk_cache/mapped_file_unittest.cc
new file mode 100644
index 0000000..84d7fec
--- /dev/null
+++ b/net/disk_cache/mapped_file_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/file_util.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 {
+
+int g_cache_tests_max_id;
+volatile int g_cache_tests_received;
+volatile bool g_cache_tests_error;
+
+// Implementation of FileIOCallback for the tests.
+class FileCallbackTest: public disk_cache::FileIOCallback {
+ public:
+ explicit FileCallbackTest(int id) : id_(id), reuse_(0) {}
+ explicit FileCallbackTest(int id, bool reuse)
+ : id_(id), reuse_(reuse_ ? 0 : 1) {}
+ ~FileCallbackTest() {}
+
+ virtual void OnFileIOComplete(int bytes_copied);
+ private:
+ int id_;
+ int reuse_;
+};
+
+void FileCallbackTest::OnFileIOComplete(int bytes_copied) {
+ if (id_ > g_cache_tests_max_id) {
+ NOTREACHED();
+ g_cache_tests_error = true;
+ } else if (reuse_) {
+ DCHECK(1 == reuse_);
+ if (2 == reuse_)
+ g_cache_tests_error = true;
+ reuse_++;
+ }
+
+ g_cache_tests_received++;
+}
+
+// Wait up to 2 secs without callbacks, or until we receive expected callbacks.
+void WaitForCallbacks(int expected) {
+ if (!expected)
+ return;
+
+ int iterations = 0;
+ int last = 0;
+ while (iterations < 40) {
+ SleepEx(50, TRUE);
+ if (expected == g_cache_tests_received)
+ return;
+ if (last == g_cache_tests_received)
+ iterations++;
+ else
+ iterations = 0;
+ }
+}
+
+} // namespace
+
+TEST(DiskCacheTest, MappedFile_SyncIO) {
+ std::wstring filename = GetCachePath();
+ file_util::AppendToPath(&filename, L"a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename.c_str()));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ char buffer1[20];
+ char buffer2[20];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ strcpy_s(buffer1, "the data");
+ EXPECT_TRUE(file->Write(buffer1, sizeof(buffer1), 8192));
+ EXPECT_TRUE(file->Read(buffer2, sizeof(buffer2), 8192));
+ EXPECT_STREQ(buffer1, buffer2);
+}
+
+TEST(DiskCacheTest, MappedFile_AsyncIO) {
+ std::wstring filename = GetCachePath();
+ file_util::AppendToPath(&filename, L"a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename.c_str()));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ FileCallbackTest callback(1);
+ g_cache_tests_error = false;
+ g_cache_tests_max_id = 0;
+ g_cache_tests_received = 0;
+
+ char buffer1[20];
+ char buffer2[20];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ strcpy_s(buffer1, "the data");
+ bool completed;
+ EXPECT_TRUE(file->Write(buffer1, sizeof(buffer1), 1024 * 1024, &callback,
+ &completed));
+ int expected = completed ? 0 : 1;
+
+ g_cache_tests_max_id = 1;
+ WaitForCallbacks(expected);
+
+ EXPECT_TRUE(file->Read(buffer2, sizeof(buffer2), 1024 * 1024, &callback,
+ &completed));
+ if (!completed)
+ expected++;
+
+ WaitForCallbacks(expected);
+
+ EXPECT_EQ(expected, g_cache_tests_received);
+ EXPECT_FALSE(g_cache_tests_error);
+ EXPECT_STREQ(buffer1, buffer2);
+}
diff --git a/net/disk_cache/mem_backend_impl.cc b/net/disk_cache/mem_backend_impl.cc
new file mode 100644
index 0000000..10aadb8
--- /dev/null
+++ b/net/disk_cache/mem_backend_impl.cc
@@ -0,0 +1,276 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/mem_backend_impl.h"
+
+#include "net/disk_cache/mem_entry_impl.h"
+
+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 {
+
+Backend* CreateInMemoryCacheBackend(int max_bytes) {
+ MemBackendImpl* cache = new MemBackendImpl();
+ cache->SetMaxSize(max_bytes);
+ if (cache->Init())
+ return cache;
+
+ delete cache;
+ LOG(ERROR) << "Unable to create cache";
+ return NULL;
+}
+
+// ------------------------------------------------------------------------
+
+bool MemBackendImpl::Init() {
+ if (max_size_)
+ return true;
+
+ MEMORYSTATUSEX memory_info;
+ memory_info.dwLength = sizeof(memory_info);
+ if (!GlobalMemoryStatusEx(&memory_info)) {
+ 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.
+ memory_info.ullTotalPhys = memory_info.ullTotalPhys * 2 / 100;
+ if (memory_info.ullTotalPhys >
+ static_cast<unsigned int>(kDefaultCacheSize * 5))
+ max_size_ = kDefaultCacheSize * 5;
+ else
+ max_size_ = static_cast<int>(memory_info.ullTotalPhys);
+
+ return true;
+}
+
+MemBackendImpl::~MemBackendImpl() {
+ EntryMap::iterator it = entries_.begin();
+ while (it != entries_.end()) {
+ it->second->Doom();
+ it = entries_.begin();
+ }
+ DCHECK(!current_size_);
+}
+
+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;
+}
+
+int32 MemBackendImpl::GetEntryCount() const {
+ return static_cast<int32>(entries_.size());
+}
+
+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)) {
+ 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;
+}
+
+void MemBackendImpl::InternalDoomEntry(MemEntryImpl* entry) {
+ rankings_.Remove(entry);
+ EntryMap::iterator it = entries_.find(entry->GetKey());
+ if (it != entries_.end())
+ entries_.erase(it);
+ else
+ NOTREACHED();
+
+ entry->InternalDoom();
+}
+
+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* next = rankings_.GetNext(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 (next) {
+ MemEntryImpl* node = next;
+ next = rankings_.GetNext(next);
+
+ if (node->GetLastUsed() < initial_time)
+ break;
+
+ if (node->GetLastUsed() < end_time) {
+ node->Doom();
+ }
+ }
+
+ return true;
+}
+
+// We use OpenNextEntry to retrieve elements from the cache, until we get
+// entries that are too old.
+bool MemBackendImpl::DoomEntriesSince(const Time initial_time) {
+ for (;;) {
+ Entry* entry;
+ void* iter = NULL;
+ if (!OpenNextEntry(&iter, &entry))
+ return true;
+
+ if (initial_time > entry->GetLastUsed()) {
+ entry->Close();
+ EndEnumeration(&iter);
+ return true;
+ }
+
+ entry->Doom();
+ entry->Close();
+ EndEnumeration(&iter); // Dooming the entry invalidates the iterator.
+ }
+}
+
+bool MemBackendImpl::OpenNextEntry(void** iter, Entry** next_entry) {
+ MemEntryImpl* current = reinterpret_cast<MemEntryImpl*>(*iter);
+ MemEntryImpl* node = rankings_.GetNext(current);
+ *next_entry = node;
+ *iter = node;
+
+ if (node)
+ node->Open();
+
+ return NULL != node;
+}
+
+void MemBackendImpl::EndEnumeration(void** iter) {
+ *iter = NULL;
+}
+
+void MemBackendImpl::TrimCache(bool empty) {
+ MemEntryImpl* next = rankings_.GetPrev(NULL);
+
+ DCHECK(next);
+
+ 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(current_size_ >= 0);
+
+ if (current_size_ > max_size_)
+ TrimCache(false);
+}
+
+void MemBackendImpl::SubstractStorageSize(int32 bytes) {
+ current_size_ -= bytes;
+ DCHECK(current_size_ >= 0);
+}
+
+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);
+}
+
+void MemBackendImpl::UpdateRank(MemEntryImpl* node) {
+ rankings_.UpdateRank(node);
+}
+
+int MemBackendImpl::MaxFileSize() const {
+ return max_size_ / 8;
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/mem_backend_impl.h b/net/disk_cache/mem_backend_impl.h
new file mode 100644
index 0000000..0804ad0
--- /dev/null
+++ b/net/disk_cache/mem_backend_impl.h
@@ -0,0 +1,104 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 <hash_map>
+
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/mem_rankings.h"
+
+namespace disk_cache {
+
+class MemEntryImpl;
+
+// This class implements the Backend interface. An object of this class handles
+// the operations of the cache without writting to disk.
+class MemBackendImpl : public Backend {
+ public:
+ MemBackendImpl() : max_size_(0), current_size_(0) {}
+ ~MemBackendImpl();
+
+ // Performs general initialization for this current instance of the cache.
+ bool Init();
+
+ // Backend interface.
+ virtual int32 GetEntryCount() const;
+ virtual bool OpenEntry(const std::string& key, Entry** entry);
+ virtual bool CreateEntry(const std::string& key, Entry** entry);
+ virtual bool DoomEntry(const std::string& key);
+ virtual bool DoomAllEntries();
+ virtual bool DoomEntriesBetween(const Time initial_time, const Time end_time);
+ virtual bool DoomEntriesSince(const Time initial_time);
+ virtual bool OpenNextEntry(void** iter, Entry** next_entry);
+ virtual void EndEnumeration(void** iter);
+ virtual void GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) {}
+
+ // 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;
+
+ private:
+ // 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);
+
+ typedef stdext::hash_map<std::string, MemEntryImpl*> EntryMap;
+
+ EntryMap entries_;
+ MemRankings rankings_; // Rankings to be able to trim the cache.
+ int32 max_size_; // Maximum data size for this instance.
+ int32 current_size_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MemBackendImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MEM_BACKEND_IMPL_H__
diff --git a/net/disk_cache/mem_entry_impl.cc b/net/disk_cache/mem_entry_impl.cc
new file mode 100644
index 0000000..130b4da
--- /dev/null
+++ b/net/disk_cache/mem_entry_impl.cc
@@ -0,0 +1,200 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/mem_entry_impl.h"
+
+#include "net/base/net_errors.h"
+#include "net/disk_cache/mem_backend_impl.h"
+
+namespace disk_cache {
+
+MemEntryImpl::MemEntryImpl(MemBackendImpl* backend) {
+ doomed_ = false;
+ backend_ = backend;
+ ref_count_ = 0;
+ data_size_[0] = data_size_[1] = 0;
+}
+
+MemEntryImpl::~MemEntryImpl() {
+ backend_->ModifyStorageSize(data_size_[0], 0);
+ backend_->ModifyStorageSize(data_size_[1], 0);
+ backend_->ModifyStorageSize(static_cast<int32>(key_.size()), 0);
+}
+
+bool MemEntryImpl::CreateEntry(const std::string& key) {
+ key_ = key;
+ last_modified_ = Time::Now();
+ last_used_ = Time::Now();
+ Open();
+ backend_->ModifyStorageSize(0, static_cast<int32>(key.size()));
+ return true;
+}
+
+void MemEntryImpl::Close() {
+ ref_count_--;
+ DCHECK(ref_count_ >= 0);
+ if (!ref_count_ && doomed_)
+ delete this;
+}
+
+void MemEntryImpl::Open() {
+ ref_count_++;
+ DCHECK(ref_count_ >= 0);
+ DCHECK(!doomed_);
+}
+
+bool MemEntryImpl::InUse() {
+ return ref_count_ > 0;
+}
+
+void MemEntryImpl::Doom() {
+ if (doomed_)
+ return;
+ backend_->InternalDoomEntry(this);
+}
+
+void MemEntryImpl::InternalDoom() {
+ doomed_ = true;
+ if (!ref_count_)
+ delete this;
+}
+
+std::string MemEntryImpl::GetKey() const {
+ 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 > 1)
+ return 0;
+
+ return data_size_[index];
+}
+
+int MemEntryImpl::ReadData(int index, int offset, char* buf, int buf_len,
+ net::CompletionCallback* completion_callback) {
+ if (index < 0 || index > 1)
+ 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_[index])[offset], buf_len);
+ return buf_len;
+}
+
+int MemEntryImpl::WriteData(int index, int offset, const char* buf, int buf_len,
+ net::CompletionCallback* completion_callback,
+ bool truncate) {
+ if (index < 0 || index > 1)
+ 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) {
+ int size = offset + buf_len;
+ if (size <= max_file_size)
+ size = kint32max;
+ 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, buf_len);
+ return buf_len;
+}
+
+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);
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/mem_entry_impl.h b/net/disk_cache/mem_entry_impl.h
new file mode 100644
index 0000000..08f2983
--- /dev/null
+++ b/net/disk_cache/mem_entry_impl.h
@@ -0,0 +1,110 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_DISK_CACHE_MEM_ENTRY_IMPL_H__
+#define NET_DISK_CACHE_MEM_ENTRY_IMPL_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.
+class MemEntryImpl : public Entry {
+ public:
+ explicit MemEntryImpl(MemBackendImpl* backend);
+
+ // Entry interface.
+ virtual void Doom();
+ virtual void Close();
+ virtual std::string GetKey() const;
+ virtual Time GetLastUsed() const;
+ virtual Time GetLastModified() const;
+ virtual int32 GetDataSize(int index) const;
+ virtual int ReadData(int index, int offset, char* buf, int buf_len,
+ net::CompletionCallback* completion_callback);
+ virtual int WriteData(int index, int offset, const char* buf, int buf_len,
+ net::CompletionCallback* completion_callback,
+ bool truncate);
+
+ // Performs the initialization of a EntryImpl that will be added to the
+ // cache.
+ bool CreateEntry(const std::string& key);
+
+ // Permamently destroys this entry
+ void InternalDoom();
+
+ MemEntryImpl* next() const {
+ return next_;
+ }
+
+ MemEntryImpl* prev() const {
+ return prev_;
+ }
+
+ void set_next(MemEntryImpl* next) {
+ next_ = next;
+ }
+
+ void set_prev(MemEntryImpl* prev) {
+ prev_ = prev;
+ }
+
+ void Open();
+ bool InUse();
+
+ private:
+ ~MemEntryImpl();
+
+ // Grows and cleans up the data buffer.
+ void PrepareTarget(int index, int offset, int buf_len);
+
+ // Updates ranking information.
+ void UpdateRank(bool modified);
+
+ std::string key_;
+ std::vector<char> data_[2]; // User data.
+ int32 data_size_[2];
+ int ref_count_;
+
+ MemEntryImpl* next_; // Pointers for the LRU list.
+ MemEntryImpl* prev_;
+ Time last_modified_; // LRU information.
+ Time last_used_;
+ MemBackendImpl* backend_; // Back pointer to the cache.
+ bool doomed_; // True if this entry was removed from the cache.
+
+ DISALLOW_EVIL_CONSTRUCTORS(MemEntryImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MEM_ENTRY_IMPL_H__
diff --git a/net/disk_cache/mem_rankings.cc b/net/disk_cache/mem_rankings.cc
new file mode 100644
index 0000000..13d1ccc
--- /dev/null
+++ b/net/disk_cache/mem_rankings.cc
@@ -0,0 +1,87 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/mem_rankings.h"
+
+#include "net/disk_cache/mem_entry_impl.h"
+
+namespace disk_cache {
+
+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/net/disk_cache/mem_rankings.h b/net/disk_cache/mem_rankings.h
new file mode 100644
index 0000000..b262f5e
--- /dev/null
+++ b/net/disk_cache/mem_rankings.h
@@ -0,0 +1,69 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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_EVIL_CONSTRUCTORS(MemRankings);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MEM_RANKINGS_H__
diff --git a/net/disk_cache/rankings.cc b/net/disk_cache/rankings.cc
new file mode 100644
index 0000000..81b5767
--- /dev/null
+++ b/net/disk_cache/rankings.cc
@@ -0,0 +1,697 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/rankings.h"
+
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/errors.h"
+
+// This is used by crash_cache.exe to generate unit test files.
+extern disk_cache::RankCrashes g_rankings_crash = disk_cache::NO_CRASH;
+
+namespace {
+
+const wchar_t* kBlockName = L"\\data_";
+const int kHeadIndex = 0;
+const int kTailIndex = 1;
+const int kTransactionIndex = 2;
+const int kOperationIndex = 3;
+
+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 int32* user_data, disk_cache::Addr addr, Operation op);
+ ~Transaction();
+ private:
+ volatile int32* user_data_;
+ DISALLOW_EVIL_CONSTRUCTORS(Transaction);
+};
+
+Transaction::Transaction(volatile int32* user_data, disk_cache::Addr addr,
+ Operation op) : user_data_(user_data) {
+ DCHECK(!user_data_[kTransactionIndex]);
+ DCHECK(addr.is_initialized());
+ user_data_[kOperationIndex] = op;
+ user_data_[kTransactionIndex] = static_cast<int32>(addr.value());
+}
+
+Transaction::~Transaction() {
+ DCHECK(user_data_[kTransactionIndex]);
+ user_data_[kTransactionIndex] = 0;
+ user_data_[kOperationIndex] = 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
+};
+
+// 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 == g_rankings_crash)
+ return;
+ switch (location) {
+ case ON_INSERT_1:
+ switch (g_rankings_crash) {
+ case disk_cache::INSERT_ONE_1:
+ case disk_cache::INSERT_LOAD_1:
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+ break;
+ case ON_INSERT_2:
+ if (disk_cache::INSERT_EMPTY_1 == g_rankings_crash)
+ TerminateProcess(GetCurrentProcess(), 0);
+ break;
+ case ON_INSERT_3:
+ switch (g_rankings_crash) {
+ case disk_cache::INSERT_EMPTY_2:
+ case disk_cache::INSERT_ONE_2:
+ case disk_cache::INSERT_LOAD_2:
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+ break;
+ case ON_INSERT_4:
+ switch (g_rankings_crash) {
+ case disk_cache::INSERT_EMPTY_3:
+ case disk_cache::INSERT_ONE_3:
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+ break;
+ case ON_REMOVE_1:
+ switch (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:
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+ break;
+ case ON_REMOVE_2:
+ if (disk_cache::REMOVE_ONE_2 == g_rankings_crash)
+ TerminateProcess(GetCurrentProcess(), 0);
+ break;
+ case ON_REMOVE_3:
+ if (disk_cache::REMOVE_ONE_3 == g_rankings_crash)
+ TerminateProcess(GetCurrentProcess(), 0);
+ break;
+ case ON_REMOVE_4:
+ if (disk_cache::REMOVE_HEAD_2 == g_rankings_crash)
+ TerminateProcess(GetCurrentProcess(), 0);
+ break;
+ case ON_REMOVE_5:
+ if (disk_cache::REMOVE_TAIL_2 == g_rankings_crash)
+ TerminateProcess(GetCurrentProcess(), 0);
+ break;
+ case ON_REMOVE_6:
+ if (disk_cache::REMOVE_TAIL_3 == g_rankings_crash)
+ TerminateProcess(GetCurrentProcess(), 0);
+ break;
+ case ON_REMOVE_7:
+ switch (g_rankings_crash) {
+ case disk_cache::REMOVE_ONE_4:
+ case disk_cache::REMOVE_LOAD_2:
+ case disk_cache::REMOVE_HEAD_3:
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+ break;
+ case ON_REMOVE_8:
+ switch (g_rankings_crash) {
+ case disk_cache::REMOVE_HEAD_4:
+ case disk_cache::REMOVE_LOAD_3:
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+ break;
+ default:
+ NOTREACHED();
+ return;
+ }
+#endif
+}
+
+} // namespace
+
+namespace disk_cache {
+
+bool Rankings::Init(BackendImpl* backend) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ backend_ = backend;
+ MappedFile* file = backend_->File(Addr(RANKINGS, 0, 0, 0));
+
+ header_ = reinterpret_cast<BlockFileHeader*>(file->buffer());
+
+ head_ = ReadHead();
+ tail_ = ReadTail();
+
+ if (header_->user[kTransactionIndex])
+ CompleteTransaction();
+
+ init_ = true;
+ return true;
+}
+
+void Rankings::Reset() {
+ init_ = false;
+ head_.set_value(0);
+ tail_.set_value(0);
+ header_ = NULL;
+}
+
+bool Rankings::GetRanking(CacheRankingsBlock* rankings) {
+ if (!rankings->address().is_initialized())
+ return false;
+
+ if (!rankings->Load())
+ return false;
+
+ if (!SanityCheck(rankings, true)) {
+ backend_->CriticalError(ERR_INVALID_LINKS);
+ return false;
+ }
+
+ if (!rankings->Data()->pointer) {
+ backend_->OnEvent(Stats::GET_RANKINGS);
+ return true;
+ }
+
+ backend_->OnEvent(Stats::OPEN_RANKINGS);
+
+ if (backend_->GetCurrentEntryId() != rankings->Data()->dirty) {
+ // We cannot trust this entry, but we cannot initiate a cleanup from this
+ // point (we may be in the middle of a cleanup already). Just get rid of
+ // the invalid pointer and continue; the entry will be deleted when detected
+ // from a regular open/create path.
+ rankings->Data()->pointer = NULL;
+ return true;
+ }
+
+ EntryImpl* cache_entry =
+ reinterpret_cast<EntryImpl*>(rankings->Data()->pointer);
+ rankings->SetData(cache_entry->rankings()->Data());
+ return true;
+}
+
+void Rankings::Insert(CacheRankingsBlock* node, bool modified) {
+ Trace("Insert 0x%x", node->address().value());
+ DCHECK(node->HasData());
+ Transaction lock(header_->user, node->address(), INSERT);
+ CacheRankingsBlock head(backend_->File(head_), head_);
+ if (head_.is_initialized()) {
+ if (!GetRanking(&head))
+ return;
+
+ if (head.Data()->prev != 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 = head_.value();
+ node->Data()->prev = node->address().value();
+ head_.set_value(node->address().value());
+
+ if (!tail_.is_initialized() || tail_.value() == node->address().value()) {
+ tail_.set_value(node->address().value());
+ node->Data()->next = tail_.value();
+ WriteTail();
+ GenerateCrash(ON_INSERT_2);
+ }
+
+ Time now = Time::Now();
+ node->Data()->last_used = now.ToInternalValue();
+ if (modified)
+ node->Data()->last_modified = now.ToInternalValue();
+ node->Store();
+ GenerateCrash(ON_INSERT_3);
+
+ // The last thing to do is move our head to point to a node already stored.
+ WriteHead();
+ GenerateCrash(ON_INSERT_4);
+}
+
+// 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) {
+ Trace("Remove 0x%x (0x%x 0x%x)", node->address().value(), node->Data()->next,
+ node->Data()->prev);
+ DCHECK(node->HasData());
+ 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()) {
+ LOG(WARNING) << "Invalid rankings info.";
+ return;
+ }
+
+ CacheRankingsBlock next(backend_->File(next_addr), next_addr);
+ CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr);
+ if (!GetRanking(&next) || !GetRanking(&prev))
+ return;
+
+ if (!CheckLinks(node, &prev, &next))
+ return;
+
+ Transaction lock(header_->user, node->address(), REMOVE);
+ prev.Data()->next = next.address().value();
+ next.Data()->prev = prev.address().value();
+ GenerateCrash(ON_REMOVE_1);
+
+ CacheAddr node_value = node->address().value();
+ if (node_value == head_.value() || node_value == tail_.value()) {
+ if (head_.value() == tail_.value()) {
+ head_.set_value(0);
+ tail_.set_value(0);
+
+ WriteHead();
+ GenerateCrash(ON_REMOVE_2);
+ WriteTail();
+ GenerateCrash(ON_REMOVE_3);
+ } else if (node_value == head_.value()) {
+ head_.set_value(next.address().value());
+ next.Data()->prev = next.address().value();
+
+ WriteHead();
+ GenerateCrash(ON_REMOVE_4);
+ } else if (node_value == tail_.value()) {
+ tail_.set_value(prev.address().value());
+ prev.Data()->next = prev.address().value();
+
+ WriteTail();
+ 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();
+ UpdateIterators(&next);
+ UpdateIterators(&prev);
+}
+
+// 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) {
+ Remove(node);
+ Insert(node, modified);
+}
+
+void Rankings::CompleteTransaction() {
+ Addr node_addr(static_cast<CacheAddr>(header_->user[kTransactionIndex]));
+ 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.Data()->pointer = NULL;
+ node.Store();
+
+ // 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 == header_->user[kOperationIndex]) {
+ Trace("FinishInsert h:0x%x t:0x%x", head_.value(), tail_.value());
+ FinishInsert(&node);
+ } else if (REMOVE == header_->user[kOperationIndex]) {
+ Trace("RevertRemove h:0x%x t:0x%x", head_.value(), tail_.value());
+ RevertRemove(&node);
+ } else {
+ NOTREACHED();
+ LOG(ERROR) << "Invalid operation to recover.";
+ }
+}
+
+void Rankings::FinishInsert(CacheRankingsBlock* node) {
+ header_->user[kTransactionIndex] = 0;
+ header_->user[kOperationIndex] = 0;
+ if (head_.value() != node->address().value()) {
+ if (tail_.value() == node->address().value()) {
+ // This part will be skipped by the logic of Insert.
+ node->Data()->next = tail_.value();
+ }
+
+ Insert(node, true);
+ }
+
+ // 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.
+ header_->user[kTransactionIndex] = 0;
+ return;
+ }
+ if (next_addr.is_separate_file() || prev_addr.is_separate_file()) {
+ NOTREACHED();
+ LOG(WARNING) << "Invalid rankings info.";
+ header_->user[kTransactionIndex] = 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;
+
+ if (!head_.is_initialized() || !tail_.is_initialized()) {
+ head_.set_value(node_value);
+ tail_.set_value(node_value);
+ WriteHead();
+ WriteTail();
+ } else if (head_.value() == next.address().value()) {
+ head_.set_value(node_value);
+ prev.Data()->next = next.address().value();
+ WriteHead();
+ } else if (tail_.value() == prev.address().value()) {
+ tail_.set_value(node_value);
+ next.Data()->prev = prev.address().value();
+ WriteTail();
+ }
+
+ next.Store();
+ prev.Store();
+ header_->user[kTransactionIndex] = 0;
+ header_->user[kOperationIndex] = 0;
+}
+
+CacheRankingsBlock* Rankings::GetNext(CacheRankingsBlock* node) {
+ ScopedRankingsBlock next(this);
+ if (!node) {
+ if (!head_.is_initialized())
+ return NULL;
+ next.reset(new CacheRankingsBlock(backend_->File(head_), head_));
+ } else {
+ if (!tail_.is_initialized())
+ return NULL;
+ if (tail_.value() == node->address().value())
+ return NULL;
+ Addr address(node->Data()->next);
+ next.reset(new CacheRankingsBlock(backend_->File(address), address));
+ }
+
+ TrackRankingsBlock(next.get(), true);
+
+ if (!GetRanking(next.get()))
+ return NULL;
+
+ if (node && !CheckSingleLink(node, next.get()))
+ return NULL;
+
+ return next.release();
+}
+
+CacheRankingsBlock* Rankings::GetPrev(CacheRankingsBlock* node) {
+ ScopedRankingsBlock prev(this);
+ if (!node) {
+ if (!tail_.is_initialized())
+ return NULL;
+ prev.reset(new CacheRankingsBlock(backend_->File(tail_), tail_));
+ } else {
+ if (!head_.is_initialized())
+ return NULL;
+ if (head_.value() == node->address().value())
+ return NULL;
+ Addr address(node->Data()->prev);
+ prev.reset(new CacheRankingsBlock(backend_->File(address), address));
+ }
+
+ TrackRankingsBlock(prev.get(), true);
+
+ if (!GetRanking(prev.get()))
+ return NULL;
+
+ if (node && !CheckSingleLink(prev.get(), node))
+ return NULL;
+
+ return prev.release();
+}
+
+void Rankings::FreeRankingsBlock(CacheRankingsBlock* node) {
+ TrackRankingsBlock(node, false);
+}
+
+int Rankings::SelfCheck() {
+ if (!head_.is_initialized()) {
+ if (!tail_.is_initialized())
+ return 0;
+ return ERR_INVALID_TAIL;
+ }
+ if (!tail_.is_initialized())
+ return ERR_INVALID_HEAD;
+
+ if (tail_.is_separate_file())
+ return ERR_INVALID_TAIL;
+
+ if (head_.is_separate_file())
+ return ERR_INVALID_HEAD;
+
+ int num_items = 0;
+ Addr address(head_.value());
+ Addr prev(head_.value());
+ scoped_ptr<CacheRankingsBlock> node;
+ do {
+ node.reset(new CacheRankingsBlock(backend_->File(address), address));
+ node->Load();
+ if (node->Data()->prev != prev.value())
+ return ERR_INVALID_PREV;
+ if (!CheckEntry(node.get()))
+ return ERR_INVALID_ENTRY;
+
+ prev.set_value(address.value());
+ address.set_value(node->Data()->next);
+ if (!address.is_initialized() || address.is_separate_file())
+ return ERR_INVALID_NEXT;
+
+ num_items++;
+ } while (node->address().value() != address.value());
+ return num_items;
+}
+
+bool Rankings::SanityCheck(CacheRankingsBlock* node, bool from_list) {
+ 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;
+
+ 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;
+
+ if ((node->address().value() == data->prev) && (head_.value() != data->prev))
+ return false;
+
+ if ((node->address().value() == data->next) && (tail_.value() != data->next))
+ return false;
+
+ return true;
+}
+
+Addr Rankings::ReadHead() {
+ CacheAddr head = static_cast<CacheAddr>(header_->user[kHeadIndex]);
+ return Addr(head);
+}
+
+Addr Rankings::ReadTail() {
+ CacheAddr tail = static_cast<CacheAddr>(header_->user[kTailIndex]);
+ return Addr(tail);
+}
+
+void Rankings::WriteHead() {
+ header_->user[kHeadIndex] = static_cast<int32>(head_.value());
+}
+
+void Rankings::WriteTail() {
+ header_->user[kTailIndex] = static_cast<int32>(tail_.value());
+}
+
+bool Rankings::CheckEntry(CacheRankingsBlock* rankings) {
+ if (!rankings->Data()->pointer)
+ return true;
+
+ // If this entry is not dirty, it is a serious problem.
+ return backend_->GetCurrentEntryId() != rankings->Data()->dirty;
+}
+
+bool Rankings::CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev,
+ CacheRankingsBlock* next) {
+ if ((prev->Data()->next != node->address().value() &&
+ head_.value() != node->address().value()) ||
+ (next->Data()->prev != node->address().value() &&
+ tail_.value() != node->address().value())) {
+ LOG(ERROR) << "Inconsistent LRU.";
+
+ if (prev->Data()->next == next->address().value() &&
+ next->Data()->prev == prev->address().value()) {
+ // The list is actually ok, node is wrong.
+ node->Data()->next = 0;
+ node->Data()->prev = 0;
+ node->Store();
+ return false;
+ }
+ backend_->CriticalError(ERR_INVALID_LINKS);
+ return false;
+ }
+
+ return true;
+}
+
+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;
+}
+
+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);
+}
+
+// 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) {
+ CacheRankingsBlock* other = it->second;
+ other->Data()->next = node->Data()->next;
+ other->Data()->prev = node->Data()->prev;
+ }
+ }
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/rankings.h b/net/disk_cache/rankings.h
new file mode 100644
index 0000000..898092d
--- /dev/null
+++ b/net/disk_cache/rankings.h
@@ -0,0 +1,178 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 "net/disk_cache/addr.h"
+#include "net/disk_cache/mapped_file.h"
+#include "net/disk_cache/storage_block.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+
+// 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:
+ // 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:
+ explicit ScopedRankingsBlock(Rankings* rankings) : rankings_(rankings) {}
+ ScopedRankingsBlock(Rankings* rankings, CacheRankingsBlock* node)
+ : rankings_(rankings), scoped_ptr<CacheRankingsBlock>(node) {}
+
+ ~ScopedRankingsBlock() {
+ rankings_->FreeRankingsBlock(get());
+ }
+
+ // scoped_ptr::reset will delete the object.
+ void reset(CacheRankingsBlock* p = NULL) {
+ if (p != get())
+ rankings_->FreeRankingsBlock(get());
+ scoped_ptr::reset(p);
+ }
+
+ private:
+ Rankings* rankings_;
+ DISALLOW_EVIL_CONSTRUCTORS(ScopedRankingsBlock);
+ };
+
+ Rankings()
+ : init_(false), head_(0), tail_(0) {}
+ ~Rankings() {}
+
+ bool Init(BackendImpl* backend);
+
+ // 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);
+
+ // Removes a given entry from the LRU list.
+ void Remove(CacheRankingsBlock* node);
+
+ // Moves a given entry to the head.
+ void UpdateRank(CacheRankingsBlock* node, bool modified);
+
+ // Iterates through the list.
+ CacheRankingsBlock* GetNext(CacheRankingsBlock* node);
+ CacheRankingsBlock* GetPrev(CacheRankingsBlock* node);
+ void FreeRankingsBlock(CacheRankingsBlock* node);
+
+ // Peforms a simple self-check of the list, 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);
+
+ private:
+ typedef std::pair<CacheAddr, CacheRankingsBlock*> IteratorPair;
+ typedef std::list<IteratorPair> IteratorList;
+
+ Addr ReadHead();
+ Addr ReadTail();
+ void WriteHead();
+ void WriteTail();
+
+ // Gets the rankings information for a given rankings node.
+ bool GetRanking(CacheRankingsBlock* rankings);
+
+ // Finishes a list modification after a crash.
+ void CompleteTransaction();
+ void FinishInsert(CacheRankingsBlock* rankings);
+ void RevertRemove(CacheRankingsBlock* rankings);
+
+ // Returns false if this entry will not be recognized as dirty (called during
+ // selfcheck).
+ bool CheckEntry(CacheRankingsBlock* rankings);
+
+ // Returns false if node is not properly linked.
+ bool CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev,
+ CacheRankingsBlock* next);
+
+ // Checks the links between two consecutive nodes.
+ bool CheckSingleLink(CacheRankingsBlock* prev, CacheRankingsBlock* next);
+
+ // Controls tracking of nodes used for enumerations.
+ void TrackRankingsBlock(CacheRankingsBlock* node, bool start_tracking);
+
+ // Updates the iterators whenever node is being changed.
+ void UpdateIterators(CacheRankingsBlock* node);
+
+ bool init_;
+ Addr head_;
+ Addr tail_;
+ BlockFileHeader* header_; // Header of the block-file used to store rankings.
+ BackendImpl* backend_;
+ IteratorList iterators_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Rankings);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_RANKINGS_H__
diff --git a/net/disk_cache/stats.cc b/net/disk_cache/stats.cc
new file mode 100644
index 0000000..5a5df7c
--- /dev/null
+++ b/net/disk_cache/stats.cc
@@ -0,0 +1,258 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/stats.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/disk_cache/backend_impl.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];
+};
+
+// 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);
+}
+
+static const char* kCounterNames[] = {
+ "Open miss",
+ "Open hit",
+ "Create miss",
+ "Create 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",
+};
+COMPILE_ASSERT(arraysize(kCounterNames) == disk_cache::Stats::MAX_COUNTER,
+ update_the_names);
+
+} // namespace
+
+namespace disk_cache {
+
+bool LoadStats(BackendImpl* backend, Addr address, OnDiskStats* stats) {
+ MappedFile* file = backend->File(address);
+ if (!file)
+ return false;
+
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ if (!file->Read(stats, sizeof(*stats), offset))
+ return false;
+
+ if (stats->signature != kDiskSignature)
+ return false;
+
+ // We don't want to discard the whole cache everytime we have one extra
+ // counter; just reset them to zero.
+ if (stats->size != sizeof(*stats))
+ memset(stats, 0, sizeof(*stats));
+
+ return true;
+}
+
+bool StoreStats(BackendImpl* backend, Addr address, OnDiskStats* stats) {
+ MappedFile* file = backend->File(address);
+ if (!file)
+ return false;
+
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ return file->Write(stats, sizeof(*stats), offset);
+}
+
+bool CreateStats(BackendImpl* backend, Addr* address, OnDiskStats* stats) {
+ if (!backend->CreateBlock(BLOCK_256, 2, address))
+ return false;
+
+ // If we have more than 512 bytes of counters, change kDiskSignature so we
+ // don't overwrite something else (LoadStats must fail).
+ COMPILE_ASSERT(sizeof(*stats) <= 256 * 2, use_more_blocks);
+ memset(stats, 0, sizeof(*stats));
+ stats->signature = kDiskSignature;
+ stats->size = sizeof(*stats);
+
+ return StoreStats(backend, *address, stats);
+}
+
+bool Stats::Init(BackendImpl* backend, uint32* storage_addr) {
+ backend_ = backend;
+
+ OnDiskStats stats;
+ Addr address(*storage_addr);
+ if (address.is_initialized()) {
+ if (!LoadStats(backend, address, &stats))
+ return false;
+ } else {
+ if (!CreateStats(backend, &address, &stats))
+ return false;
+ *storage_addr = address.value();
+ }
+
+ storage_addr_ = address.value();
+
+ memcpy(data_sizes_, stats.data_sizes, sizeof(data_sizes_));
+ memcpy(counters_, stats.counters, sizeof(counters_));
+
+ return true;
+}
+
+Stats::~Stats() {
+ if (!backend_)
+ return;
+
+ OnDiskStats stats;
+ stats.signature = kDiskSignature;
+ stats.size = sizeof(stats);
+ memcpy(stats.data_sizes, data_sizes_, sizeof(data_sizes_));
+ memcpy(stats.counters, counters_, sizeof(counters_));
+
+ Addr address(storage_addr_);
+ StoreStats(backend_, address, &stats);
+}
+
+// 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;
+}
+
+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 = StringPrintf("Size%02d", i);
+ item.second = StringPrintf("0x%08x", data_sizes_[i]);
+ items->push_back(item);
+ }
+
+ for (int i = MIN_COUNTER + 1; i < MAX_COUNTER; i++) {
+ item.first = kCounterNames[i];
+ item.second = StringPrintf("0x%I64x", counters_[i]);
+ items->push_back(item);
+ }
+}
+
+} // namespace disk_cache
diff --git a/net/disk_cache/stats.h b/net/disk_cache/stats.h
new file mode 100644
index 0000000..f862116
--- /dev/null
+++ b/net/disk_cache/stats.h
@@ -0,0 +1,98 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_DISK_CACHE_STATS_H__
+#define NET_DISK_CACHE_STATS_H__
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+
+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,
+ 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,
+ MAX_COUNTER
+ };
+
+ Stats() : backend_(NULL) {}
+ ~Stats();
+
+ bool Init(BackendImpl* backend, uint32* storage_addr);
+
+ // 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);
+
+ private:
+ int GetStatsBucket(int32 size);
+
+ BackendImpl* backend_;
+ uint32 storage_addr_;
+ int data_sizes_[kDataSizesLength];
+ int64 counters_[MAX_COUNTER];
+
+ DISALLOW_EVIL_CONSTRUCTORS(Stats);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_STATS_H__
diff --git a/net/disk_cache/storage_block-inl.h b/net/disk_cache/storage_block-inl.h
new file mode 100644
index 0000000..35ba95b
--- /dev/null
+++ b/net/disk_cache/storage_block-inl.h
@@ -0,0 +1,152 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_DISK_CACHE_CACHE_INTERNAL_INL_H__
+#define NET_DISK_CACHE_CACHE_INTERNAL_INL_H__
+
+#include "net/disk_cache/storage_block.h"
+
+#include "net/disk_cache/trace.h"
+
+namespace disk_cache {
+
+template<typename T> StorageBlock<T>::StorageBlock(MappedFile* file,
+ Addr address)
+ : file_(file), address_(address), data_(NULL), 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();
+ if (own_data_)
+ delete data_;
+}
+
+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> DWORD 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_);
+ if (own_data_) {
+ delete data_;
+ own_data_ = false;
+ }
+ data_ = other;
+}
+
+template<typename T> void StorageBlock<T>::set_modified() {
+ DCHECK(data_);
+ modified_ = true;
+}
+
+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> 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_) {
+ 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;
+}
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_CACHE_INTERNAL_INL_H__
diff --git a/net/disk_cache/storage_block.h b/net/disk_cache/storage_block.h
new file mode 100644
index 0000000..7c100fd
--- /dev/null
+++ b/net/disk_cache/storage_block.h
@@ -0,0 +1,107 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 {
+
+class EntryImpl;
+
+// 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 DWORD 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 momory provided by other instance.
+ void SetData(T* other);
+
+ // Sets the object to lazily save the in-memory data on destruction.
+ void set_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;
+
+ const Addr address() const;
+
+ // Loads and store the data.
+ bool Load();
+ bool Store();
+
+ private:
+ void AllocateData();
+
+ 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_EVIL_CONSTRUCTORS(StorageBlock);
+};
+
+typedef StorageBlock<EntryStore> CacheEntryBlock;
+typedef StorageBlock<RankingsNode> CacheRankingsBlock;
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_STORAGE_BLOCK_H__
diff --git a/net/disk_cache/storage_block_unittest.cc b/net/disk_cache/storage_block_unittest.cc
new file mode 100644
index 0000000..9f6e8c1
--- /dev/null
+++ b/net/disk_cache/storage_block_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/file_util.h"
+#include "net/disk_cache/storage_block.h"
+#include "net/disk_cache/storage_block-inl.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(DiskCacheTest, StorageBlock_LoadStore) {
+ std::wstring filename = GetCachePath();
+ file_util::AppendToPath(&filename, L"a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename.c_str()));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ disk_cache::CacheEntryBlock entry1(file, 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(DiskCacheTest, StorageBlock_SetData) {
+ std::wstring filename = GetCachePath();
+ file_util::AppendToPath(&filename, L"a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename.c_str()));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ disk_cache::CacheEntryBlock entry1(file, disk_cache::Addr(0xa0010001));
+ entry1.Data()->hash = 0xaa5555aa;
+
+ disk_cache::CacheEntryBlock entry2(file, disk_cache::Addr(0xa0010002));
+ EXPECT_TRUE(entry2.Load());
+ EXPECT_TRUE(entry2.Data() != NULL);
+ EXPECT_EQ(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(DiskCacheTest, StorageBlock_SetModified) {
+ std::wstring filename = GetCachePath();
+ file_util::AppendToPath(&filename, L"a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename.c_str()));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ disk_cache::CacheEntryBlock* entry1 =
+ new disk_cache::CacheEntryBlock(file, disk_cache::Addr(0xa0010003));
+ EXPECT_TRUE(entry1->Load());
+ EXPECT_EQ(0, entry1->Data()->hash);
+ entry1->Data()->hash = 0x45687912;
+ entry1->set_modified();
+ delete entry1;
+
+ disk_cache::CacheEntryBlock entry2(file, disk_cache::Addr(0xa0010003));
+ EXPECT_TRUE(entry2.Load());
+ EXPECT_EQ(0x45687912, entry2.Data()->hash);
+}
diff --git a/net/disk_cache/stress_cache.cc b/net/disk_cache/stress_cache.cc
new file mode 100644
index 0000000..74f257a
--- /dev/null
+++ b/net/disk_cache/stress_cache.cc
@@ -0,0 +1,221 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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.
+
+#include <windows.h>
+#include <string>
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+
+const int kError = -1;
+const int kExpectedCrash = 1000000;
+
+// Starts a new process.
+int RunSlave(int iteration) {
+ std::wstring exe;
+ PathService::Get(base::FILE_EXE, &exe);
+
+ std::wstring command = StringPrintf(L"%s %d", exe.c_str(), iteration);
+
+ STARTUPINFO startup_info = {0};
+ startup_info.cb = sizeof(startup_info);
+ PROCESS_INFORMATION process_info;
+
+ // I really don't care about this call modifying the string.
+ if (!::CreateProcess(exe.c_str(), const_cast<wchar_t*>(command.c_str()), NULL,
+ NULL, FALSE, 0, NULL, NULL, &startup_info,
+ &process_info)) {
+ printf("Unable to run test\n");
+ return kError;
+ }
+
+ DWORD reason = ::WaitForSingleObject(process_info.hProcess, INFINITE);
+
+ int code;
+ bool ok = ::GetExitCodeProcess(process_info.hProcess,
+ reinterpret_cast<PDWORD>(&code)) ? true :
+ false;
+
+ ::CloseHandle(process_info.hProcess);
+ ::CloseHandle(process_info.hThread);
+
+ if (!ok) {
+ printf("Unable to get return code\n");
+ return kError;
+ }
+
+ return 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;
+}
+
+// -----------------------------------------------------------------------
+
+// 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 = 0x800000; // 8MB
+ std::wstring path = GetCachePath();
+ path.append(L"_stress");
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false,
+ cache_size);
+ if (NULL == cache) {
+ 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);
+
+ const int kNumKeys = 5000;
+ const int kNumEntries = 30;
+ std::string keys[kNumKeys];
+ disk_cache::Entry* entries[kNumEntries] = {0};
+
+ for (int i = 0; i < kNumKeys; i++) {
+ keys[i] = GenerateKey(true);
+ }
+
+ const int kDataLen = 4000;
+ char data[kDataLen];
+ memset(data, 'k', kDataLen);
+
+ for (int i = 0;; i++) {
+ int slot = rand() % kNumEntries;
+ int key = rand() % kNumKeys;
+
+ if (entries[slot])
+ entries[slot]->Close();
+
+ if (!cache->OpenEntry(keys[key], &entries[slot]))
+ CHECK(cache->CreateEntry(keys[key], &entries[slot]));
+
+ sprintf_s(data, "%d %d", iteration, i);
+ CHECK(kDataLen == entries[slot]->WriteData(0, 0, data, kDataLen, NULL,
+ false));
+
+ if (rand() % 100 > 80) {
+ key = rand() % kNumKeys;
+ cache->DoomEntry(keys[key]);
+ }
+
+ 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;
+
+class CrashTask : public Task {
+ public:
+ CrashTask() {}
+ ~CrashTask() {}
+
+ virtual void Run() {
+ if (g_crashing)
+ return;
+
+ if (rand() % 100 > 1) {
+ printf("sweet death...\n");
+ TerminateProcess(GetCurrentProcess(), kExpectedCrash);
+ }
+ }
+};
+
+// We leak everything here :)
+bool StartCrashThread() {
+ Thread* thread = new Thread("party_crasher");
+ if (!thread->Start())
+ return false;
+
+ // Create a recurrent timer of 10 secs.
+ int timer_delay = 10000;
+ CrashTask* task = new CrashTask();
+ thread->message_loop()->timer_manager()->StartTimer(timer_delay, task, true);
+
+ return true;
+}
+
+void CrashHandler(const std::string& str) {
+ g_crashing = true;
+ __debugbreak();
+}
+
+// -----------------------------------------------------------------------
+
+int main(int argc, const char* argv[]) {
+ if (argc < 2)
+ return MasterCode();
+
+ logging::SetLogAssertHandler(CrashHandler);
+
+ // Some time for the memory manager to flush stuff.
+ Sleep(3000);
+ MessageLoop message_loop;
+
+ 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/net/disk_cache/trace.cc b/net/disk_cache/trace.cc
new file mode 100644
index 0000000..5f97b7d
--- /dev/null
+++ b/net/disk_cache/trace.cc
@@ -0,0 +1,146 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <windows.h>
+
+#include "net/disk_cache/trace.h"
+
+#include "base/logging.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
+
+#if _DEBUG
+#undef ENABLE_TRACING
+#define ENABLE_TRACING 1
+#endif
+
+namespace {
+
+const int kEntrySize = 48;
+const int kNumberOfEntries = 5000; // 240 KB.
+
+struct TraceBuffer {
+ int num_traces;
+ int current;
+ char buffer[kNumberOfEntries][kEntrySize];
+};
+
+TraceBuffer* s_trace_buffer = NULL;
+
+void DebugOutput(char* msg) {
+ OutputDebugStringA(msg);
+}
+
+} // namespace
+
+namespace disk_cache {
+
+#if ENABLE_TRACING
+
+bool InitTrace(void) {
+ DCHECK(!s_trace_buffer);
+ if (s_trace_buffer)
+ return false;
+
+ s_trace_buffer = new TraceBuffer;
+ memset(s_trace_buffer, 0, sizeof(*s_trace_buffer));
+ return true;
+}
+
+void DestroyTrace(void) {
+ DCHECK(s_trace_buffer);
+ delete s_trace_buffer;
+ s_trace_buffer = NULL;
+}
+
+void Trace(const char* format, ...) {
+ DCHECK(s_trace_buffer);
+ va_list ap;
+ va_start(ap, format);
+
+ vsprintf_s(s_trace_buffer->buffer[s_trace_buffer->current], format, ap);
+ s_trace_buffer->num_traces++;
+ s_trace_buffer->current++;
+ if (s_trace_buffer->current == kNumberOfEntries)
+ s_trace_buffer->current = 0;
+
+ va_end(ap);
+}
+
+// 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
+
+bool InitTrace(void) {
+ return true;
+}
+
+void DestroyTrace(void) {
+}
+
+void Trace(const char* format, ...) {
+}
+
+#endif // ENABLE_TRACING
+
+} // namespace disk_cache
diff --git a/net/disk_cache/trace.h b/net/disk_cache/trace.h
new file mode 100644
index 0000000..25a05ae
--- /dev/null
+++ b/net/disk_cache/trace.h
@@ -0,0 +1,67 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace disk_cache {
+
+// Create and destroy the tracing buffer.
+bool InitTrace(void);
+void DestroyTrace(void);
+
+// Simple class to handle the trace buffer lifetime.
+class TraceObject {
+ public:
+ TraceObject() {
+ InitTrace();
+ }
+ ~TraceObject() {
+ DestroyTrace();
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(TraceObject);
+};
+
+// Traces to the internal buffer.
+void Trace(const char* format, ...);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_TRACE_H__
diff --git a/net/http/cert_status_cache.cc b/net/http/cert_status_cache.cc
new file mode 100644
index 0000000..82741f7
--- /dev/null
+++ b/net/http/cert_status_cache.cc
@@ -0,0 +1,89 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/cert_status_flags.h"
+#include "net/http/cert_status_cache.h"
+
+namespace net {
+
+CertStatusCache::CertStatusCache() {
+}
+
+CertStatusCache::~CertStatusCache() {
+ for (HostMap::iterator iter = fingerprint_to_bad_hosts_.begin();
+ iter != fingerprint_to_bad_hosts_.end(); ++iter) {
+ delete iter->second;
+ }
+}
+
+int CertStatusCache::GetCertStatus(const X509Certificate& cert,
+ const std::string& host) const {
+ StatusMap::const_iterator iter =
+ fingerprint_to_cert_status_.find(cert.fingerprint());
+ if (iter != fingerprint_to_cert_status_.end()) {
+ int cert_status = iter->second;
+
+ // We get the CERT_STATUS_COMMON_NAME_INVALID error based on the host.
+ HostMap::const_iterator fp_iter =
+ fingerprint_to_bad_hosts_.find(cert.fingerprint());
+ if (fp_iter != fingerprint_to_bad_hosts_.end()) {
+ StringSet* bad_hosts = fp_iter->second;
+ StringSet::const_iterator host_iter = bad_hosts->find(host);
+ if (host_iter != bad_hosts->end())
+ cert_status |= net::CERT_STATUS_COMMON_NAME_INVALID;
+ }
+
+ return cert_status;
+ }
+ return 0; // The cert has never had errors.
+}
+
+void CertStatusCache::SetCertStatus(const X509Certificate& cert,
+ const std::string& host,
+ int status) {
+ // We store the CERT_STATUS_COMMON_NAME_INVALID status separately as it is
+ // host related.
+ fingerprint_to_cert_status_[cert.fingerprint()] =
+ status & ~net::CERT_STATUS_COMMON_NAME_INVALID;
+
+ if ((status & net::CERT_STATUS_COMMON_NAME_INVALID) != 0) {
+ StringSet* bad_hosts;
+ HostMap::const_iterator iter =
+ fingerprint_to_bad_hosts_.find(cert.fingerprint());
+ if (iter == fingerprint_to_bad_hosts_.end()) {
+ bad_hosts = new StringSet;
+ fingerprint_to_bad_hosts_[cert.fingerprint()] = bad_hosts;
+ } else {
+ bad_hosts = iter->second;
+ }
+ bad_hosts->insert(host);
+ }
+}
+
+} \ No newline at end of file
diff --git a/net/http/cert_status_cache.h b/net/http/cert_status_cache.h
new file mode 100644
index 0000000..4f5f7b9
--- /dev/null
+++ b/net/http/cert_status_cache.h
@@ -0,0 +1,74 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_CERT_STATUS_CACHE_H
+#define NET_HTTP_CERT_STATUS_CACHE_H
+
+#include <vector>
+#include <map>
+
+#include "net/base/x509_certificate.h"
+
+// This class is used to remember the status of certificates, as WinHTTP
+// does not report errors once it has been told to ignore them.
+// It only exists because of the WinHTTP bug.
+// IMPORTANT: this class is not thread-safe.
+
+namespace net {
+
+class CertStatusCache {
+ public:
+ CertStatusCache();
+ ~CertStatusCache();
+
+ int GetCertStatus(const X509Certificate& cert,
+ const std::string& host_name) const;
+ void SetCertStatus(const X509Certificate& cert,
+ const std::string& host_name,
+ int status);
+
+ private:
+ typedef std::map<X509Certificate::Fingerprint, int,
+ X509Certificate::FingerprintLessThan> StatusMap;
+ typedef std::set<std::string> StringSet;
+ typedef std::map<X509Certificate::Fingerprint, StringSet*,
+ X509Certificate::FingerprintLessThan> HostMap;
+
+ StatusMap fingerprint_to_cert_status_;
+
+ // We keep a map for each cert to the list of host names that have been marked
+ // with the CN invalid error, as that error is host name specific.
+ HostMap fingerprint_to_bad_hosts_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CertStatusCache);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_CERT_STATUS_CACHE_H
diff --git a/net/http/http_atom_list.h b/net/http/http_atom_list.h
new file mode 100644
index 0000000..7ac67c5
--- /dev/null
+++ b/net/http/http_atom_list.h
@@ -0,0 +1,86 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+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/net/http/http_cache.cc b/net/http/http_cache.cc
new file mode 100644
index 0000000..50e6de0
--- /dev/null
+++ b/net/http/http_cache.cc
@@ -0,0 +1,1359 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_cache.h"
+
+#include <algorithm>
+
+#include "base/message_loop.h"
+#include "base/pickle.h"
+#include "base/ref_counted.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_proxy_service.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_util.h"
+
+#pragma warning(disable: 4355)
+
+namespace net {
+
+// disk cache entry data indices.
+enum {
+ kResponseInfoIndex,
+ kResponseContentIndex
+};
+
+// 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 = 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.
+ 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,
+
+ // TODO(darin): Add other bits to indicate alternate request methods and
+ // whether or not we are storing a partial document. For now, we don't
+ // support storing those.
+};
+
+//-----------------------------------------------------------------------------
+
+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[] = {
+ { "range", NULL }, // causes unexpected 206s
+ { "if-modified-since", NULL }, // causes unexpected 304s
+ { "if-none-match", NULL }, // causes unexpected 304s
+ { "if-unmodified-since", NULL }, // causes unexpected 412s
+ { "if-match", NULL }, // causes unexpected 412s
+ { NULL, NULL }
+};
+
+// 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 HttpUtil::HeadersIterator& h,
+ const HeaderNameAndValue* search) {
+ for (; search->name; ++search) {
+ if (!LowerCaseEqualsASCII(h.name_begin(), h.name_end(), search->name))
+ continue;
+
+ if (!search->value)
+ return true;
+
+ HttpUtil::ValuesIterator v(h.values_begin(), h.values_end(), ',');
+ while (v.GetNext()) {
+ if (LowerCaseEqualsASCII(v.value_begin(), v.value_end(), search->value))
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) {
+ std::string url = request->url.spec();
+ if (request->url.has_ref())
+ url.erase(url.find_last_of('#'));
+
+ if (mode_ == NORMAL) {
+ 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 = IntToString(generation);
+ result.append(request->method);
+ result.append(url);
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry *e)
+ : disk_entry(e),
+ will_process_pending_queue(false),
+ doomed(false) {
+}
+
+HttpCache::ActiveEntry::~ActiveEntry() {
+ disk_entry->Close();
+}
+
+//-----------------------------------------------------------------------------
+
+class HttpCache::Transaction : public HttpTransaction,
+ public base::RefCounted<HttpCache::Transaction> {
+ public:
+ explicit Transaction(HttpCache* cache)
+ : request_(NULL),
+ cache_(cache),
+ entry_(NULL),
+ network_trans_(NULL),
+ callback_(NULL),
+ mode_(NONE),
+ read_buf_(NULL),
+ read_offset_(0),
+ effective_load_flags_(0),
+ final_upload_progress_(0),
+ network_info_callback_(this, &Transaction::OnNetworkInfoAvailable),
+ network_read_callback_(this, &Transaction::OnNetworkReadCompleted),
+ cache_read_callback_(this, &Transaction::OnCacheReadCompleted) {
+ AddRef(); // Balanced in Destroy
+ }
+
+ // HttpTransaction methods:
+ virtual void Destroy();
+ virtual int Start(const HttpRequestInfo*, CompletionCallback*);
+ virtual int RestartIgnoringLastError(CompletionCallback*);
+ virtual int RestartWithAuth(const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback);
+ virtual int Read(char* buf, int buf_len, CompletionCallback*);
+ virtual const HttpResponseInfo* GetResponseInfo() const;
+ virtual LoadState GetLoadState() const;
+ virtual uint64 GetUploadProgress(void) const;
+
+ // 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).
+ //
+ enum Mode {
+ NONE = 0x0,
+ READ = 0x1,
+ WRITE = 0x2,
+ READ_WRITE = READ | WRITE
+ };
+
+ Mode mode() const { return mode_; }
+
+ const std::string& key() const { return cache_key_; }
+
+ // Associates this transaction with a cache entry.
+ int AddToEntry();
+
+ // Called by the HttpCache when the given disk cache entry becomes accessible
+ // to the transaction. Returns network error code.
+ int EntryAvailable(ActiveEntry* entry);
+
+ private:
+ // 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);
+
+ // Set request_ and fields derived from it.
+ void SetRequest(const HttpRequestInfo* request);
+
+ // Returns true if the request should be handled exclusively by the network
+ // layer (skipping the cache entirely).
+ bool ShouldPassThrough();
+
+ // Returns true if we should force an end-to-end fetch.
+ bool ShouldBypassCache();
+
+ // 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 a network transaction. Returns network error code.
+ int BeginNetworkRequest();
+
+ // Called to restart a network transaction after an error. Returns network
+ // error code.
+ int RestartNetworkRequest();
+
+ // Called to restart a network transaction with authentication credentials.
+ // Returns network error code.
+ int RestartNetworkRequestWithAuth(const std::wstring& username,
+ const std::wstring& password);
+
+ // 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();
+
+ // Called to populate response_ from the cache entry.
+ int ReadResponseInfoFromEntry();
+
+ // 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.
+ void WriteToEntry(int index, int offset, const char* data, int data_len);
+
+ // Called to write response_ to the cache entry.
+ void WriteResponseInfoToEntry();
+
+ // Called to append response data to the cache entry.
+ void AppendResponseDataToEntry(const char* data, int data_len);
+
+ // Called when we are done writing to the cache entry.
+ void DoneWritingToEntry(bool success);
+
+ // Called to signal completion of the network transaction's Start method:
+ void OnNetworkInfoAvailable(int result);
+
+ // Called to signal completion of the network transaction's Read method:
+ void OnNetworkReadCompleted(int result);
+
+ // Called to signal completion of the cache's ReadData method:
+ void OnCacheReadCompleted(int result);
+
+ const HttpRequestInfo* request_;
+ scoped_ptr<HttpRequestInfo> custom_request_;
+ HttpCache* cache_;
+ HttpCache::ActiveEntry* entry_;
+ HttpTransaction* network_trans_;
+ CompletionCallback* callback_; // consumer's callback
+ HttpResponseInfo response_;
+ HttpResponseInfo auth_response_;
+ std::string cache_key_;
+ Mode mode_;
+ char* read_buf_;
+ int read_offset_;
+ int effective_load_flags_;
+ uint64 final_upload_progress_;
+ CompletionCallbackImpl<Transaction> network_info_callback_;
+ CompletionCallbackImpl<Transaction> network_read_callback_;
+ CompletionCallbackImpl<Transaction> cache_read_callback_;
+};
+
+void HttpCache::Transaction::Destroy() {
+ if (entry_) {
+ if (mode_ & WRITE) {
+ // Assume that this is not a successful write.
+ cache_->DoneWritingToEntry(entry_, false);
+ } else {
+ cache_->DoneReadingFromEntry(entry_, this);
+ }
+ } else {
+ cache_->RemovePendingTransaction(this);
+ }
+
+ if (network_trans_)
+ network_trans_->Destroy();
+
+ // We could still have a cache read in progress, so we just null the cache_
+ // pointer to signal that we are dead. See OnCacheReadCompleted.
+ cache_ = NULL;
+
+ Release();
+}
+
+int HttpCache::Transaction::Start(const HttpRequestInfo* request,
+ CompletionCallback* callback) {
+ DCHECK(request);
+ DCHECK(callback);
+
+ // ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ SetRequest(request);
+
+ int rv;
+
+ if (ShouldPassThrough()) {
+ // if must use cache, then we must fail. this can happen for back/forward
+ // navigations to a page generated via a form post.
+ if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE)
+ return ERR_CACHE_MISS;
+
+ rv = BeginNetworkRequest();
+ } else {
+ 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;
+ }
+
+ rv = AddToEntry();
+ }
+
+ // setting this here allows us to check for the existance of a callback_ to
+ // determine if we are still inside Start.
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+int HttpCache::Transaction::RestartIgnoringLastError(
+ CompletionCallback* callback) {
+ DCHECK(callback);
+
+ // ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ int rv = RestartNetworkRequest();
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+int HttpCache::Transaction::RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback) {
+ DCHECK(auth_response_.headers);
+ DCHECK(callback);
+
+ // Ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ // Clear the intermediate response since we are going to start over.
+ auth_response_ = HttpResponseInfo();
+
+ int rv = RestartNetworkRequestWithAuth(username, password);
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+int HttpCache::Transaction::Read(char* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK(buf_len > 0);
+ DCHECK(callback);
+
+ DCHECK(!callback_);
+
+ // 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 && mode_ != NONE) {
+ DCHECK(mode_ & WRITE);
+ DoneWritingToEntry(mode_ == READ_WRITE);
+ mode_ = NONE;
+ }
+
+ int rv;
+
+ switch (mode_) {
+ case NONE:
+ case WRITE:
+ DCHECK(network_trans_);
+ rv = network_trans_->Read(buf, buf_len, &network_read_callback_);
+ read_buf_ = buf;
+ if (rv >= 0)
+ OnNetworkReadCompleted(rv);
+ break;
+ case READ:
+ DCHECK(entry_);
+ AddRef(); // Balanced in OnCacheReadCompleted
+ rv = entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_,
+ buf, buf_len, &cache_read_callback_);
+ read_buf_ = buf;
+ if (rv >= 0) {
+ OnCacheReadCompleted(rv);
+ } else if (rv != ERR_IO_PENDING) {
+ Release();
+ }
+ break;
+ default:
+ NOTREACHED();
+ rv = ERR_FAILED;
+ }
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ return rv;
+}
+
+const HttpResponseInfo* HttpCache::Transaction::GetResponseInfo() const {
+ // Null headers means we encountered an error or haven't a response yet
+ if (auth_response_.headers)
+ return &auth_response_;
+ return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL;
+}
+
+LoadState HttpCache::Transaction::GetLoadState() const {
+ if (network_trans_)
+ return network_trans_->GetLoadState();
+ if (entry_ || !request_)
+ return LOAD_STATE_IDLE;
+ return LOAD_STATE_WAITING_FOR_CACHE;
+}
+
+uint64 HttpCache::Transaction::GetUploadProgress() const {
+ if (network_trans_)
+ return network_trans_->GetUploadProgress();
+ return final_upload_progress_;
+}
+
+int HttpCache::Transaction::AddToEntry() {
+ ActiveEntry* entry = NULL;
+
+ if (mode_ == WRITE) {
+ cache_->DoomEntry(cache_key_);
+ } else {
+ entry = cache_->FindActiveEntry(cache_key_);
+ if (!entry) {
+ entry = cache_->OpenEntry(cache_key_);
+ if (!entry) {
+ if (mode_ & WRITE) {
+ mode_ = WRITE;
+ } else {
+ if (cache_->mode() == PLAYBACK)
+ DLOG(INFO) << "Playback Cache Miss: " << request_->url;
+
+ // entry does not exist, and not permitted to create a new entry, so
+ // we must fail.
+ return HandleResult(ERR_CACHE_MISS);
+ }
+ }
+ }
+ }
+
+ if (mode_ == WRITE) {
+ DCHECK(!entry);
+ entry = cache_->CreateEntry(cache_key_);
+ if (!entry) {
+ DLOG(WARNING) << "unable to create cache entry";
+ mode_ = NONE;
+ return BeginNetworkRequest();
+ }
+ }
+
+ return cache_->AddTransactionToEntry(entry, this);
+}
+
+int HttpCache::Transaction::EntryAvailable(ActiveEntry* entry) {
+ // We now have access to the cache entry.
+ //
+ // o if we are the writer for the transaction, then we can start the network
+ // transaction.
+ //
+ // 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.
+ //
+ int rv;
+ entry_ = entry;
+ switch (mode_) {
+ case READ:
+ rv = BeginCacheRead();
+ break;
+ case WRITE:
+ rv = BeginNetworkRequest();
+ break;
+ case READ_WRITE:
+ rv = BeginCacheValidation();
+ break;
+ default:
+ NOTREACHED();
+ rv = ERR_FAILED;
+ }
+ return rv;
+}
+
+void HttpCache::Transaction::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(callback_);
+
+ // since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback* c = callback_;
+ callback_ = NULL;
+ c->Run(rv);
+}
+
+int HttpCache::Transaction::HandleResult(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ if (callback_)
+ DoCallback(rv);
+ return rv;
+}
+
+void HttpCache::Transaction::SetRequest(const HttpRequestInfo* request) {
+ request_ = request;
+ effective_load_flags_ = request_->load_flags;
+
+ // When in playback mode, we want to load exclusively from the cache.
+ if (cache_->mode() == PLAYBACK)
+ effective_load_flags_ |= LOAD_ONLY_FROM_CACHE;
+
+ // 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.
+ if (cache_->mode() == RECORD)
+ effective_load_flags_ |= LOAD_BYPASS_CACHE;
+
+ // 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 },
+ };
+
+ // scan request headers to see if we have any that would impact our load flags
+ HttpUtil::HeadersIterator it(request_->extra_headers.begin(),
+ request_->extra_headers.end(),
+ "\r\n");
+ while (it.GetNext()) {
+ for (size_t i = 0; i < arraysize(kSpecialHeaders); ++i) {
+ if (HeaderMatches(it, kSpecialHeaders[i].search)) {
+ effective_load_flags_ |= kSpecialHeaders[i].load_flag;
+ break;
+ }
+ }
+ }
+}
+
+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())
+ 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;
+
+ // TODO(darin): add support for caching HEAD and POST responses
+ if (request_->method != "GET")
+ return true;
+
+ return false;
+}
+
+int HttpCache::Transaction::BeginCacheRead() {
+ DCHECK(mode_ == READ);
+
+ // read response headers
+ return HandleResult(ReadResponseInfoFromEntry());
+}
+
+int HttpCache::Transaction::BeginCacheValidation() {
+ DCHECK(mode_ == READ_WRITE);
+
+ int rv = ReadResponseInfoFromEntry();
+ if (rv != OK) {
+ DCHECK(rv != ERR_IO_PENDING);
+ } else if (effective_load_flags_ & LOAD_PREFERRING_CACHE ||
+ !RequiresValidation()) {
+ cache_->ConvertWriterToReader(entry_);
+ mode_ = READ;
+ } 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. We'll switch to
+ // either READ or WRITE mode once we hear back from the server.
+ if (!ConditionalizeRequest())
+ mode_ = WRITE;
+ return BeginNetworkRequest();
+ }
+ return HandleResult(rv);
+}
+
+int HttpCache::Transaction::BeginNetworkRequest() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(!network_trans_);
+
+ network_trans_ = cache_->network_layer_->CreateTransaction();
+ if (!network_trans_)
+ return net::ERR_FAILED;
+
+ int rv = network_trans_->Start(request_, &network_info_callback_);
+ if (rv != ERR_IO_PENDING)
+ OnNetworkInfoAvailable(rv);
+ return rv;
+}
+
+int HttpCache::Transaction::RestartNetworkRequest() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_);
+
+ int rv = network_trans_->RestartIgnoringLastError(&network_info_callback_);
+ if (rv != ERR_IO_PENDING)
+ OnNetworkInfoAvailable(rv);
+ return rv;
+}
+
+int HttpCache::Transaction::RestartNetworkRequestWithAuth(
+ const std::wstring& username,
+ const std::wstring& password) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_);
+
+ int rv = network_trans_->RestartWithAuth(username, password,
+ &network_info_callback_);
+ if (rv != ERR_IO_PENDING)
+ OnNetworkInfoAvailable(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 (mode_ == PLAYBACK)
+ return false;
+
+ if (effective_load_flags_ & LOAD_VALIDATE_CACHE)
+ return true;
+
+ if (response_.headers->RequiresValidation(
+ response_.request_time, response_.response_time, Time::Now()))
+ return true;
+
+ // Since Vary header computation is fairly expensive, we save it for last.
+ if (response_.vary_data.is_valid() &&
+ !response_.vary_data.MatchesRequest(*request_, *response_.headers))
+ return true;
+
+ return false;
+}
+
+bool HttpCache::Transaction::ConditionalizeRequest() {
+ DCHECK(response_.headers);
+
+ // This only makes sense for cached 200 responses.
+ if (response_.headers->response_code() != 200)
+ return false;
+
+ // Just use the first available ETag and/or Last-Modified header value.
+ // TODO(darin): Or should we use the last?
+
+ std::string etag_value;
+ response_.headers->EnumerateHeader(NULL, "etag", &etag_value);
+
+ std::string last_modified_value;
+ response_.headers->EnumerateHeader(NULL, "last-modified",
+ &last_modified_value);
+
+ if (etag_value.empty() && last_modified_value.empty())
+ return false;
+
+ // Need to customize the request, so this forces us to allocate :(
+ custom_request_.reset(new HttpRequestInfo(*request_));
+ request_ = custom_request_.get();
+
+ if (!etag_value.empty()) {
+ custom_request_->extra_headers.append("If-None-Match: ");
+ custom_request_->extra_headers.append(etag_value);
+ custom_request_->extra_headers.append("\r\n");
+ }
+
+ if (!last_modified_value.empty()) {
+ custom_request_->extra_headers.append("If-Modified-Since: ");
+ custom_request_->extra_headers.append(last_modified_value);
+ custom_request_->extra_headers.append("\r\n");
+ }
+
+ return true;
+}
+
+int HttpCache::Transaction::ReadResponseInfoFromEntry() {
+ DCHECK(entry_);
+
+ if (!HttpCache::ReadResponseInfo(entry_->disk_entry, &response_))
+ return ERR_FAILED;
+
+ return OK;
+}
+
+void HttpCache::Transaction::WriteToEntry(int index, int offset,
+ const char* data, int data_len) {
+ if (!entry_)
+ return;
+
+ int rv = entry_->disk_entry->WriteData(index, offset, data, data_len, NULL,
+ true);
+ if (rv != data_len) {
+ DLOG(ERROR) << "failed to write response data to cache";
+ DoneWritingToEntry(false);
+ }
+}
+
+void HttpCache::Transaction::WriteResponseInfoToEntry() {
+ if (!entry_)
+ return;
+
+ // 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);
+ return;
+ }
+
+ // 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 (!HttpCache::WriteResponseInfo(entry_->disk_entry, &response_,
+ skip_transient_headers)) {
+ DLOG(ERROR) << "failed to write response info to cache";
+ DoneWritingToEntry(false);
+ }
+}
+
+void HttpCache::Transaction::AppendResponseDataToEntry(const char* data,
+ int data_len) {
+ if (!entry_)
+ return;
+
+ int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex);
+ WriteToEntry(kResponseContentIndex, current_size, data, data_len);
+}
+
+void HttpCache::Transaction::DoneWritingToEntry(bool success) {
+ if (!entry_)
+ return;
+
+ if (cache_->mode() == RECORD)
+ DLOG(INFO) << "Recorded: " << request_->method << request_->url
+ << " status: " << response_.headers->response_code();
+
+ cache_->DoneWritingToEntry(entry_, success);
+ entry_ = NULL;
+ mode_ = NONE; // switch to 'pass through' mode
+}
+
+void HttpCache::Transaction::OnNetworkInfoAvailable(int result) {
+ DCHECK(result != ERR_IO_PENDING);
+
+ if (result == OK) {
+ const HttpResponseInfo* new_response = network_trans_->GetResponseInfo();
+ if (new_response->headers->response_code() == 401 ||
+ new_response->headers->response_code() == 407) {
+ auth_response_ = *new_response;
+ } else {
+ // Are we expecting a response to a conditional query?
+ if (mode_ == READ_WRITE) {
+ if (new_response->headers->response_code() == 304) {
+ // Update cached response based on headers in new_response
+ response_.headers->Update(*new_response->headers);
+ WriteResponseInfoToEntry();
+
+ if (entry_) {
+ cache_->ConvertWriterToReader(entry_);
+ // We no longer need the network transaction, so destroy it.
+ final_upload_progress_ = network_trans_->GetUploadProgress();
+ network_trans_->Destroy();
+ network_trans_ = NULL;
+ mode_ = READ;
+ }
+ } else {
+ mode_ = WRITE;
+ }
+ }
+
+ if (!(mode_ & READ)) {
+ response_ = *new_response;
+ WriteResponseInfoToEntry();
+
+ // Truncate the response data
+ WriteToEntry(kResponseContentIndex, 0, NULL, 0);
+
+ // 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);
+ }
+ }
+ } else if (IsCertificateError(result)) {
+ response_.ssl_info = network_trans_->GetResponseInfo()->ssl_info;
+ }
+ HandleResult(result);
+}
+
+void HttpCache::Transaction::OnNetworkReadCompleted(int result) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+
+ if (result > 0) {
+ AppendResponseDataToEntry(read_buf_, result);
+ } else if (result == 0) { // end of file
+ DoneWritingToEntry(true);
+ }
+ HandleResult(result);
+}
+
+void HttpCache::Transaction::OnCacheReadCompleted(int result) {
+ // If Destroy was called while waiting for this callback, then cache_ will be
+ // NULL. In that case, we don't want to do anything but cleanup.
+ if (cache_) {
+ if (result > 0) {
+ read_offset_ += result;
+ } else if (result == 0) { // end of file
+ cache_->DoneReadingFromEntry(entry_, this);
+ entry_ = NULL;
+ }
+ HandleResult(result);
+ }
+ Release();
+}
+
+//-----------------------------------------------------------------------------
+
+HttpCache::HttpCache(const HttpProxyInfo* proxy_info,
+ const std::wstring& cache_dir,
+ int cache_size)
+ : disk_cache_dir_(cache_dir),
+ mode_(NORMAL),
+ network_layer_(HttpNetworkLayer::CreateFactory(proxy_info)),
+ task_factory_(this),
+ in_memory_cache_(false),
+ cache_size_(cache_size) {
+}
+
+HttpCache::HttpCache(const HttpProxyInfo* proxy_info, int cache_size)
+ : mode_(NORMAL),
+ network_layer_(HttpNetworkLayer::CreateFactory(proxy_info)),
+ task_factory_(this),
+ in_memory_cache_(true),
+ cache_size_(cache_size) {
+}
+
+HttpCache::HttpCache(HttpTransactionFactory* network_layer,
+ disk_cache::Backend* disk_cache)
+ : mode_(NORMAL),
+ network_layer_(network_layer),
+ disk_cache_(disk_cache),
+ task_factory_(this),
+ in_memory_cache_(false),
+ cache_size_(0) {
+}
+
+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;
+ DeactivateEntry(entry);
+ }
+
+ ActiveEntriesSet::iterator it = doomed_entries_.begin();
+ for (; it != doomed_entries_.end(); ++it)
+ delete *it;
+}
+
+HttpTransaction* HttpCache::CreateTransaction() {
+ // Do lazy initialization of disk cache if needed.
+ if (!disk_cache_.get()) {
+ DCHECK(cache_size_ >= 0);
+ if (in_memory_cache_) {
+ // We may end up with no folder name and no cache if the initialization
+ // of the disk cache fails. We want to be sure that what we wanted to have
+ // was an in-memory cache.
+ disk_cache_.reset(disk_cache::CreateInMemoryCacheBackend(cache_size_));
+ } else if (!disk_cache_dir_.empty()) {
+ disk_cache_.reset(disk_cache::CreateCacheBackend(disk_cache_dir_, true,
+ cache_size_));
+ disk_cache_dir_.clear(); // Reclaim memory.
+ }
+ }
+ return new HttpCache::Transaction(this);
+}
+
+HttpCache* HttpCache::GetCache() {
+ return this;
+}
+
+AuthCache* HttpCache::GetAuthCache() {
+ return network_layer_->GetAuthCache();
+}
+
+void HttpCache::Suspend(bool suspend) {
+ network_layer_->Suspend(suspend);
+}
+
+// static
+bool HttpCache::ReadResponseInfo(disk_cache::Entry* disk_entry,
+ HttpResponseInfo* response_info) {
+ int size = disk_entry->GetDataSize(kResponseInfoIndex);
+
+ std::string data;
+ int rv = disk_entry->ReadData(kResponseInfoIndex, 0,
+ WriteInto(&data, size + 1),
+ size, NULL);
+ if (rv != size) {
+ DLOG(ERROR) << "ReadData failed: " << rv;
+ return false;
+ }
+
+ Pickle pickle(data.data(), static_cast<int>(data.size()));
+ void* iter = NULL;
+
+ // 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_VERSION) {
+ DLOG(ERROR) << "unexpected response info version: " << version;
+ return false;
+ }
+
+ // read request-time
+ int64 time_val;
+ if (!pickle.ReadInt64(&iter, &time_val))
+ return false;
+ response_info->request_time = Time::FromInternalValue(time_val);
+
+ // read response-time
+ if (!pickle.ReadInt64(&iter, &time_val))
+ return false;
+ response_info->response_time = Time::FromInternalValue(time_val);
+
+ // read response-headers
+ response_info->headers = new HttpResponseHeaders(pickle, &iter);
+ DCHECK(response_info->headers->response_code() != -1);
+
+ // read ssl-info
+ if (flags & RESPONSE_INFO_HAS_CERT) {
+ response_info->ssl_info.cert =
+ X509Certificate::CreateFromPickle(pickle, &iter);
+ }
+ if (flags & RESPONSE_INFO_HAS_CERT_STATUS) {
+ int cert_status;
+ if (!pickle.ReadInt(&iter, &cert_status))
+ return false;
+ response_info->ssl_info.cert_status = cert_status;
+ }
+ if (flags & RESPONSE_INFO_HAS_SECURITY_BITS) {
+ int security_bits;
+ if (!pickle.ReadInt(&iter, &security_bits))
+ return false;
+ response_info->ssl_info.security_bits = security_bits;
+ }
+
+ // read vary-data
+ if (flags & RESPONSE_INFO_HAS_VARY_DATA) {
+ if (!response_info->vary_data.InitFromPickle(pickle, &iter))
+ return false;
+ }
+
+ return true;
+}
+
+// static
+bool HttpCache::WriteResponseInfo(disk_cache::Entry* disk_entry,
+ const HttpResponseInfo* response_info,
+ bool skip_transient_headers) {
+ int flags = RESPONSE_INFO_VERSION;
+ if (response_info->ssl_info.cert) {
+ flags |= RESPONSE_INFO_HAS_CERT;
+ flags |= RESPONSE_INFO_HAS_CERT_STATUS;
+ }
+ if (response_info->ssl_info.security_bits != -1)
+ flags |= RESPONSE_INFO_HAS_SECURITY_BITS;
+ if (response_info->vary_data.is_valid())
+ flags |= RESPONSE_INFO_HAS_VARY_DATA;
+
+ Pickle pickle;
+ pickle.WriteInt(flags);
+ pickle.WriteInt64(response_info->request_time.ToInternalValue());
+ pickle.WriteInt64(response_info->response_time.ToInternalValue());
+
+ response_info->headers->Persist(&pickle, skip_transient_headers);
+
+ if (response_info->ssl_info.cert) {
+ response_info->ssl_info.cert->Persist(&pickle);
+ pickle.WriteInt(response_info->ssl_info.cert_status);
+ }
+ if (response_info->ssl_info.security_bits != -1)
+ pickle.WriteInt(response_info->ssl_info.security_bits);
+
+ if (response_info->vary_data.is_valid())
+ response_info->vary_data.Persist(&pickle);
+
+ const char* data = static_cast<const char*>(pickle.data());
+ int len = pickle.size();
+
+ return disk_entry->WriteData(kResponseInfoIndex, 0, data, len, NULL,
+ true) == len;
+}
+
+void HttpCache::DoomEntry(const std::string& key) {
+ // 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()) {
+ disk_cache_->DoomEntry(key);
+ } else {
+ 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());
+ }
+}
+
+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::OpenEntry(const std::string& key) {
+ DCHECK(!FindActiveEntry(key));
+
+ disk_cache::Entry* disk_entry;
+ if (!disk_cache_->OpenEntry(key, &disk_entry))
+ return NULL;
+
+ return ActivateEntry(key, disk_entry);
+}
+
+HttpCache::ActiveEntry* HttpCache::CreateEntry(const std::string& key) {
+ DCHECK(!FindActiveEntry(key));
+
+ disk_cache::Entry* disk_entry;
+ if (!disk_cache_->CreateEntry(key, &disk_entry))
+ return NULL;
+
+ return ActivateEntry(key, disk_entry);
+}
+
+void HttpCache::DestroyEntry(ActiveEntry* entry) {
+ if (entry->doomed) {
+ FinalizeDoomedEntry(entry);
+ } else {
+ DeactivateEntry(entry);
+ }
+}
+
+HttpCache::ActiveEntry* HttpCache::ActivateEntry(
+ const std::string& key,
+ disk_cache::Entry* disk_entry) {
+ ActiveEntry* entry = new ActiveEntry(disk_entry);
+ entry->writer = NULL;
+ active_entries_[key] = entry;
+ return entry;
+}
+
+void HttpCache::DeactivateEntry(ActiveEntry* entry) {
+ DCHECK(!entry->will_process_pending_queue);
+ DCHECK(!entry->doomed);
+ DCHECK(!entry->writer);
+ DCHECK(entry->readers.empty());
+ DCHECK(entry->pending_queue.empty());
+
+ ActiveEntriesMap::iterator it =
+ active_entries_.find(entry->disk_entry->GetKey());
+ DCHECK(it != active_entries_.end());
+ DCHECK(it->second == entry);
+
+ active_entries_.erase(it);
+ delete entry;
+}
+
+int HttpCache::AddTransactionToEntry(ActiveEntry* entry, Transaction* trans) {
+ DCHECK(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->pending_queue.empty())
+ ProcessPendingQueue(entry);
+
+ return trans->EntryAvailable(entry);
+}
+
+void HttpCache::DoneWritingToEntry(ActiveEntry* entry, bool success) {
+ DCHECK(entry->readers.empty());
+
+ entry->writer = NULL;
+
+ if (success) {
+ ProcessPendingQueue(entry);
+ } else {
+ // 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()) {
+ pending_queue.front()->AddToEntry();
+ 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);
+}
+
+void HttpCache::RemovePendingTransaction(Transaction* trans) {
+ ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key());
+ if (i == active_entries_.end())
+ return;
+
+ TransactionList& pending_queue = i->second->pending_queue;
+
+ TransactionList::iterator j =
+ find(pending_queue.begin(), pending_queue.end(), trans);
+ if (j == pending_queue.end())
+ return;
+
+ pending_queue.erase(j);
+}
+
+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;
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ task_factory_.NewRunnableMethod(&HttpCache::OnProcessPendingQueue,
+ entry));
+}
+
+void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) {
+ entry->will_process_pending_queue = false;
+
+ if (entry->writer)
+ return;
+
+ // If no one is interested in this entry, then we can de-activate 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());
+
+ AddTransactionToEntry(entry, next);
+}
+
+//-----------------------------------------------------------------------------
+
+} // namespace net
diff --git a/net/http/http_cache.h b/net/http/http_cache.h
new file mode 100644
index 0000000..42fbfe0
--- /dev/null
+++ b/net/http/http_cache.h
@@ -0,0 +1,200 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 <hash_map>
+#include <hash_set>
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/task.h"
+#include "net/http/http_transaction_factory.h"
+
+namespace disk_cache {
+class Backend;
+class Entry;
+}
+
+namespace net {
+
+class HttpProxyInfo;
+class HttpRequestInfo;
+class HttpResponseInfo;
+
+class HttpCache : public HttpTransactionFactory {
+ public:
+ ~HttpCache();
+
+ // 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
+ };
+
+ // Initialize the cache from the directory where its data is stored. The
+ // disk cache is initialized lazily (by CreateTransaction) in this case. If
+ // |cache_size| is zero, a default value will be calculated automatically.
+ // If the proxy information is null, then the system settings will be used.
+ HttpCache(const HttpProxyInfo* proxy_info,
+ const std::wstring& cache_dir,
+ int cache_size);
+
+ // Initialize using an in-memory cache. The cache is initialized lazily
+ // (by CreateTransaction) in this case. If |cache_size| is zero, a default
+ // value will be calculated automatically. If the proxy information is null,
+ // then the system settings will be used.
+ HttpCache(const HttpProxyInfo* proxy_info, int cache_size);
+
+ // Initialize the cache from its component parts, which is useful for
+ // testing. The lifetime of the network_layer and disk_cache are managed by
+ // the HttpCache and will be destroyed using |delete| when the HttpCache is
+ // destroyed.
+ HttpCache(HttpTransactionFactory* network_layer,
+ disk_cache::Backend* disk_cache);
+
+ HttpTransactionFactory* network_layer() { return network_layer_.get(); }
+ disk_cache::Backend* disk_cache() { return disk_cache_.get(); }
+
+ // HttpTransactionFactory implementation:
+ virtual HttpTransaction* CreateTransaction();
+ virtual HttpCache* GetCache();
+ virtual AuthCache* GetAuthCache();
+ virtual void Suspend(bool suspend);
+
+ // Helper function for reading response info from the disk cache.
+ static bool ReadResponseInfo(disk_cache::Entry* disk_entry,
+ HttpResponseInfo* response_info);
+
+ // Helper function for writing response info into the disk cache.
+ static bool WriteResponseInfo(disk_cache::Entry* disk_entry,
+ const HttpResponseInfo* response_info,
+ bool skip_transient_headers);
+
+ // Generate a key that can be used inside the cache.
+ std::string GenerateCacheKey(const HttpRequestInfo* request);
+
+ // Get/Set the cache's mode.
+ void set_mode(Mode value) { mode_ = value; }
+ Mode mode() { return mode_; }
+
+ private:
+
+ // Types --------------------------------------------------------------------
+
+ class Transaction;
+ friend Transaction;
+
+ typedef std::list<Transaction*> TransactionList;
+
+ struct ActiveEntry {
+ disk_cache::Entry* disk_entry;
+ Transaction* writer;
+ TransactionList readers;
+ TransactionList pending_queue;
+ bool will_process_pending_queue;
+ bool doomed;
+
+ explicit ActiveEntry(disk_cache::Entry*);
+ ~ActiveEntry();
+ };
+
+ typedef stdext::hash_map<std::string, ActiveEntry*> ActiveEntriesMap;
+ typedef stdext::hash_set<ActiveEntry*> ActiveEntriesSet;
+
+
+ // Methods ------------------------------------------------------------------
+
+ void DoomEntry(const std::string& key);
+ void FinalizeDoomedEntry(ActiveEntry* entry);
+ ActiveEntry* FindActiveEntry(const std::string& key);
+ ActiveEntry* ActivateEntry(const std::string& key, disk_cache::Entry*);
+ void DeactivateEntry(ActiveEntry* entry);
+ ActiveEntry* OpenEntry(const std::string& key);
+ ActiveEntry* CreateEntry(const std::string& cache_key);
+ void DestroyEntry(ActiveEntry* entry);
+ int AddTransactionToEntry(ActiveEntry* entry, Transaction* trans);
+ void DoneWritingToEntry(ActiveEntry* entry, bool success);
+ void DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans);
+ void ConvertWriterToReader(ActiveEntry* entry);
+ void RemovePendingTransaction(Transaction* trans);
+ void ProcessPendingQueue(ActiveEntry* entry);
+
+
+ // Events (called via PostTask) ---------------------------------------------
+
+ void OnProcessPendingQueue(ActiveEntry* entry);
+
+
+ // Variables ----------------------------------------------------------------
+
+ // used when lazily constructing the disk_cache_
+ std::wstring disk_cache_dir_;
+
+ Mode mode_;
+
+ 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_;
+
+ ScopedRunnableMethodFactory<HttpCache> task_factory_;
+
+ bool in_memory_cache_;
+ int cache_size_;
+
+ typedef stdext::hash_map<std::string, int> PlaybackCacheMap;
+ scoped_ptr<PlaybackCacheMap> playback_cache_map_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HttpCache);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CACHE_H__
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
new file mode 100644
index 0000000..8f5db81
--- /dev/null
+++ b/net/http/http_cache_unittest.cc
@@ -0,0 +1,1012 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_cache.h"
+
+#include <windows.h>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/load_flags.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#pragma warning(disable: 4355)
+
+namespace {
+
+//-----------------------------------------------------------------------------
+// mock disk cache (a very basic memory cache implementation)
+
+class MockDiskEntry : public disk_cache::Entry,
+ public base::RefCounted<MockDiskEntry> {
+ public:
+ MockDiskEntry() : test_mode_(0), doomed_(false) {
+ }
+
+ MockDiskEntry(const std::string& key) : key_(key), doomed_(false) {
+ const MockTransaction* t = FindMockTransaction(GURL(key));
+ DCHECK(t);
+ test_mode_ = t->test_mode;
+ }
+
+ ~MockDiskEntry() {
+ }
+
+ bool is_doomed() const { return doomed_; }
+
+ virtual void Doom() {
+ doomed_ = true;
+ }
+
+ virtual void Close() {
+ Release();
+ }
+
+ virtual std::string GetKey() const {
+ return key_;
+ }
+
+ virtual Time GetLastUsed() const {
+ return Time::FromInternalValue(0);
+ }
+
+ virtual Time GetLastModified() const {
+ return Time::FromInternalValue(0);
+ }
+
+ virtual int32 GetDataSize(int index) const {
+ DCHECK(index >= 0 && index < 2);
+ return static_cast<int32>(data_[index].size());
+ }
+
+ virtual int ReadData(int index, int offset, char* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ DCHECK(index >= 0 && index < 2);
+
+ if (offset < 0 || offset > static_cast<int>(data_[index].size()))
+ return net::ERR_FAILED;
+ if (offset == data_[index].size())
+ return 0;
+
+ int num = std::min(buf_len, static_cast<int>(data_[index].size()) - offset);
+ memcpy(buf, &data_[index][offset], num);
+
+ if (!callback || (test_mode_ & TEST_MODE_SYNC_CACHE_READ))
+ return num;
+
+ CallbackLater(callback, num);
+ return net::ERR_IO_PENDING;
+ }
+
+ virtual int WriteData(int index, int offset, const char* buf, int buf_len,
+ net::CompletionCallback* callback, bool truncate) {
+ DCHECK(index >= 0 && index < 2);
+ DCHECK(truncate);
+
+ 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, buf_len);
+ return buf_len;
+ }
+
+ private:
+ // 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(net::CompletionCallback* callback, int result) {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &MockDiskEntry::RunCallback, callback, result));
+ }
+ void RunCallback(net::CompletionCallback* callback, int result) {
+ callback->Run(result);
+ }
+
+ std::string key_;
+ std::vector<char> data_[2];
+ int test_mode_;
+ bool doomed_;
+};
+
+class MockDiskCache : public disk_cache::Backend {
+ public:
+ MockDiskCache() : open_count_(0), create_count_(0), fail_requests_(0) {
+ }
+
+ ~MockDiskCache() {
+ EntryMap::iterator it = entries_.begin();
+ for (; it != entries_.end(); ++it)
+ it->second->Release();
+ }
+
+ virtual int32 GetEntryCount() const {
+ return static_cast<int32>(entries_.size());
+ }
+
+ virtual bool OpenEntry(const std::string& key, disk_cache::Entry** entry) {
+ if (fail_requests_)
+ return false;
+
+ EntryMap::iterator it = entries_.find(key);
+ if (it == entries_.end())
+ return false;
+
+ if (it->second->is_doomed()) {
+ it->second->Release();
+ entries_.erase(it);
+ return false;
+ }
+
+ open_count_++;
+
+ it->second->AddRef();
+ *entry = it->second;
+
+ return true;
+ }
+
+ virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry) {
+ if (fail_requests_)
+ return false;
+
+ EntryMap::iterator it = entries_.find(key);
+ DCHECK(it == entries_.end());
+
+ create_count_++;
+
+ MockDiskEntry* new_entry = new MockDiskEntry(key);
+
+ new_entry->AddRef();
+ entries_[key] = new_entry;
+
+ new_entry->AddRef();
+ *entry = new_entry;
+
+ return true;
+ }
+
+ virtual bool DoomEntry(const std::string& key) {
+ EntryMap::iterator it = entries_.find(key);
+ if (it != entries_.end()) {
+ it->second->Release();
+ entries_.erase(it);
+ }
+ return true;
+ }
+
+ virtual bool DoomAllEntries() {
+ return false;
+ }
+
+ virtual bool DoomEntriesBetween(const Time initial_time,
+ const Time end_time) {
+ return true;
+ }
+
+ virtual bool DoomEntriesSince(const Time initial_time) {
+ return true;
+ }
+
+ virtual bool OpenNextEntry(void** iter, disk_cache::Entry** next_entry) {
+ return false;
+ }
+
+ virtual void EndEnumeration(void** iter) {}
+
+ virtual void GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) {
+ }
+
+ // 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; }
+
+ private:
+ typedef stdext::hash_map<std::string, MockDiskEntry*> EntryMap;
+ EntryMap entries_;
+ int open_count_;
+ int create_count_;
+ bool fail_requests_;
+};
+
+class MockHttpCache {
+ public:
+ MockHttpCache() : http_cache_(new MockNetworkLayer(), new MockDiskCache()) {
+ }
+
+ net::HttpCache* http_cache() { return &http_cache_; }
+
+ MockNetworkLayer* network_layer() {
+ return static_cast<MockNetworkLayer*>(http_cache_.network_layer());
+ }
+ MockDiskCache* disk_cache() {
+ return static_cast<MockDiskCache*>(http_cache_.disk_cache());
+ }
+
+ private:
+ net::HttpCache http_cache_;
+};
+
+
+//-----------------------------------------------------------------------------
+// helpers
+
+void ReadAndVerifyTransaction(net::HttpTransaction* trans,
+ const MockTransaction& trans_info) {
+ std::string content;
+ int rv = ReadTransaction(trans, &content);
+
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_EQ(strlen(trans_info.data), content.size());
+ EXPECT_EQ(0, memcmp(trans_info.data, content.data(), content.size()));
+}
+
+void RunTransactionTest(net::HttpCache* cache,
+ const MockTransaction& trans_info) {
+ MockHttpRequest request(trans_info);
+ TestCompletionCallback callback;
+
+ // write to the cache
+
+ net::HttpTransaction* trans = cache->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response);
+
+ ReadAndVerifyTransaction(trans, trans_info);
+
+ trans->Destroy();
+}
+
+} // namespace
+
+
+//-----------------------------------------------------------------------------
+// tests
+
+
+TEST(HttpCache, CreateThenDestroy) {
+ MockHttpCache cache;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ trans->Destroy();
+}
+
+TEST(HttpCache, SimpleGET) {
+ MockHttpCache cache;
+
+ // 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());
+}
+
+TEST(HttpCache, SimpleGETNoDiskCache) {
+ MockHttpCache cache;
+
+ cache.disk_cache()->set_fail_requests();
+
+ // 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_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to read from the cache
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_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_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);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans->Destroy();
+
+ 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());
+}
+
+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;
+
+ 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_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";
+
+ 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";
+
+ 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;
+
+ 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, 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";
+
+ 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, SimpleGET_ManyReaders) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ struct Context {
+ int result;
+ TestCompletionCallback callback;
+ net::HttpTransaction* trans;
+
+ Context(net::HttpTransaction* t) : result(net::ERR_IO_PENDING), trans(t) {
+ }
+ };
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(
+ new Context(cache.http_cache()->CreateTransaction()));
+
+ Context* c = context_list[i];
+ int rv = c->trans->Start(&request, &c->callback);
+ if (rv != net::ERR_IO_PENDING)
+ c->result = rv;
+ }
+
+ // 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();
+ ReadAndVerifyTransaction(c->trans, 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];
+ c->trans->Destroy();
+ delete c;
+ }
+}
+
+TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ struct Context {
+ int result;
+ TestCompletionCallback callback;
+ net::HttpTransaction* trans;
+
+ Context(net::HttpTransaction* t) : result(net::ERR_IO_PENDING), trans(t) {
+ }
+ };
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 2;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(
+ new Context(cache.http_cache()->CreateTransaction()));
+
+ Context* c = context_list[i];
+ int rv = c->trans->Start(&request, &c->callback);
+ if (rv != net::ERR_IO_PENDING)
+ c->result = rv;
+ }
+
+ // 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) {
+ c->trans->Destroy();
+ 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, 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];
+ c->trans->Destroy();
+ delete c;
+ }
+}
+
+TEST(HttpCache, SimpleGET_AbandonedCacheRead) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ char buf[256];
+ rv = trans->Read(buf, sizeof(buf), &callback);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Test that destroying the transaction while it is reading from the cache
+ // works properly.
+ trans->Destroy();
+
+ // Make sure we pump any pending events, which should include a call to
+ // HttpCache::Transaction::OnCacheReadCompleted.
+ MessageLoop::current()->Quit();
+ MessageLoop::current()->Run();
+}
+
+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.
+ RunTransactionTest(cache.http_cache(), kTypicalGET_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 ETagGet_ConditionalRequest_Handler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_TRUE(request->extra_headers.find("If-None-Match") != std::string::npos);
+ 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;
+ 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, SimplePOST_SkipsCache) {
+ MockHttpCache cache;
+
+ // Test that we skip the cache for POST 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(), 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;
+
+ // Test that we skip the cache for POST 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.
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans->Destroy();
+
+ 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, RangeGET_SkipsCache) {
+ MockHttpCache cache;
+
+ // Test that we skip the cache for POST 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";
+ 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";
+ 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(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);
+
+ MockHttpRequest r1(transaction),
+ r2(transaction),
+ r3(transaction);
+
+ TestTransactionConsumer c1(cache.http_cache()),
+ c2(cache.http_cache()),
+ c3(cache.http_cache());
+
+ c1.Start(&r1);
+
+ r2.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ c2.Start(&r2);
+
+ r3.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ c3.Start(&r3);
+
+ 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);
+ TestCompletionCallback callback;
+
+ // write to the cache
+ {
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ 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/");
+
+ // now, destroy the transaction without actually reading the response body.
+ // we want to test that it is still getting cached.
+ trans->Destroy();
+ }
+ 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
+ {
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ 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/");
+
+ // now, destroy the transaction without actually reading the response body.
+ // we want to test that it is still getting cached.
+ trans->Destroy();
+ }
+ 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;
+ bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
+ EXPECT_FALSE(exists);
+}
+
+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;
+ bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
+ EXPECT_FALSE(exists);
+}
+
+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;
+ bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
+ EXPECT_FALSE(exists);
+}
+
+// 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);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans->Destroy();
+}
diff --git a/net/http/http_chunked_decoder.cc b/net/http/http_chunked_decoder.cc
new file mode 100644
index 0000000..059d792
--- /dev/null
+++ b/net/http/http_chunked_decoder.cc
@@ -0,0 +1,136 @@
+/* ***** 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 ***** */
+
+// Derived from:
+// mozilla/netwerk/protocol/http/src/nsHttpChunkedDecoder.cpp
+
+#include "net/http/http_chunked_decoder.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+HttpChunkedDecoder::HttpChunkedDecoder()
+ : chunk_remaining_(0),
+ reached_last_chunk_(false),
+ reached_eof_(false) {
+}
+
+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;
+ continue;
+ } else if (reached_eof_) {
+ break; // Done!
+ }
+
+ // Returns bytes consumed or an error code.
+ int rv = ScanForChunkRemaining(buf, buf_len);
+ if (rv < 0)
+ return rv;
+
+ buf_len -= rv;
+ if (buf_len)
+ memmove(buf, buf + rv, buf_len);
+ }
+
+ return result;
+}
+
+int HttpChunkedDecoder::ScanForChunkRemaining(char* buf, int buf_len) {
+ DCHECK(chunk_remaining_ == 0);
+ DCHECK(buf_len > 0);
+
+ int result = 0;
+
+ char *p = static_cast<char*>(memchr(buf, '\n', buf_len));
+ if (p) {
+ *p = 0;
+ if ((p > buf) && (*(p - 1) == '\r')) // Eliminate a preceding CR.
+ *(p - 1) = 0;
+ result = static_cast<int>(p - buf) + 1;
+
+ // Make buf point to the full line buffer to parse.
+ if (!line_buf_.empty()) {
+ line_buf_.append(buf);
+ buf = const_cast<char*>(line_buf_.data());
+ }
+
+ if (reached_last_chunk_) {
+ if (*buf) {
+ DLOG(INFO) << "ignoring http trailer";
+ } else {
+ reached_eof_ = true;
+ }
+ } else if (*buf) {
+ // Ignore any chunk-extensions.
+ if ((p = strchr(buf, ';')) != NULL)
+ *p = 0;
+
+ if (!sscanf_s(buf, "%x", &chunk_remaining_)) {
+ DLOG(ERROR) << "sscanf failed parsing HEX from: " << buf;
+ return ERR_FAILED;
+ }
+
+ if (chunk_remaining_ == 0)
+ reached_last_chunk_ = true;
+ }
+ line_buf_.clear();
+ } else {
+ // Save the partial line; wait for more data.
+ result = buf_len;
+
+ // Ignore a trailing CR
+ if (buf[buf_len - 1] == '\r')
+ buf_len--;
+
+ line_buf_.append(buf, buf_len);
+ }
+ return result;
+}
+
+} // namespace net
diff --git a/net/http/http_chunked_decoder.h b/net/http/http_chunked_decoder.h
new file mode 100644
index 0000000..799ebfd
--- /dev/null
+++ b/net/http/http_chunked_decoder.h
@@ -0,0 +1,107 @@
+/* ***** 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 ***** */
+
+// Derived from:
+// mozilla/netwerk/protocol/http/src/nsHttpChunkedDecoder.h
+
+#ifndef NET_HTTP_HTTP_CHUNKED_DECODER_H_
+#define NET_HTTP_HTTP_CHUNKED_DECODER_H_
+
+#include <string>
+
+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 HttpChunkedDecoder {
+ public:
+ HttpChunkedDecoder();
+
+ // Indicates that a previous call to FilterBuf encountered the final CRLF.
+ bool reached_eof() const { return reached_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:
+ // Scan |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(char* buf, int buf_len);
+
+ // 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_;
+
+ // 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_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CHUNKED_DECODER_H_
diff --git a/net/http/http_chunked_decoder_unittest.cc b/net/http/http_chunked_decoder_unittest.cc
new file mode 100644
index 0000000..602ba6a
--- /dev/null
+++ b/net/http/http_chunked_decoder_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "net/http/http_chunked_decoder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+typedef testing::Test HttpChunkedDecoderTest;
+
+void RunTest(const char* inputs[], size_t num_inputs,
+ const char* expected_output,
+ bool expected_eof) {
+ net::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_TRUE(n >= 0);
+ if (n > 0)
+ result.append(input.data(), n);
+ }
+
+ EXPECT_TRUE(result == expected_output);
+ EXPECT_TRUE(decoder.reached_eof() == expected_eof);
+}
+
+} // namespace
+
+TEST(HttpChunkedDecoderTest, Basic) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n0\r\n\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true);
+}
+
+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);
+}
+
+TEST(HttpChunkedDecoderTest, Incremental) {
+ const char* inputs[] = {
+ "5",
+ "\r",
+ "\n",
+ "hello",
+ "\r",
+ "\n",
+ "0",
+ "\r",
+ "\n",
+ "\r",
+ "\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true);
+}
+
+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);
+}
+
+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);
+}
diff --git a/net/http/http_connection.cc b/net/http/http_connection.cc
new file mode 100644
index 0000000..0ca8a3d
--- /dev/null
+++ b/net/http/http_connection.cc
@@ -0,0 +1,64 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_connection.h"
+
+#include "net/base/client_socket.h"
+#include "net/http/http_connection_manager.h"
+
+namespace net {
+
+HttpConnection::HttpConnection(HttpConnectionManager* mgr)
+ : mgr_(mgr), socket_(NULL) {
+}
+
+HttpConnection::~HttpConnection() {
+ Reset();
+}
+
+int HttpConnection::Init(const std::string& group_name,
+ CompletionCallback* callback) {
+ Reset();
+ group_name_ = group_name;
+ return mgr_->RequestSocket(group_name_, &socket_, callback);
+}
+
+void HttpConnection::Reset() {
+ if (group_name_.empty()) // Was Init called?
+ return;
+ if (socket_) {
+ mgr_->ReleaseSocket(group_name_, socket_);
+ socket_ = NULL;
+ } else {
+ mgr_->CancelRequest(group_name_, &socket_);
+ }
+ group_name_.clear();
+}
+
+} // namespace net
diff --git a/net/http/http_connection.h b/net/http/http_connection.h
new file mode 100644
index 0000000..ada8468
--- /dev/null
+++ b/net/http/http_connection.h
@@ -0,0 +1,94 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_CONNECTION_H_
+#define NET_HTTP_HTTP_CONNECTION_H_
+
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "net/base/client_socket.h"
+#include "net/base/completion_callback.h"
+
+namespace net {
+
+class HttpConnectionManager;
+
+// A container for a ClientSocket, representing a HTTP connection.
+//
+// The connection's |group_name| uniquely identifies the origin and type of the
+// connection. It is used by the HttpConnectionManager to group similar http
+// connection objects.
+//
+// A connection object is initialized with a null socket. It is the consumer's
+// job to initialize a ClientSocket object and set it on the connection.
+//
+class HttpConnection {
+ public:
+ HttpConnection(HttpConnectionManager* mgr);
+ ~HttpConnection();
+
+ // Initializes a HttpConnection object, which involves talking to the
+ // HttpConnectionManager to locate a socket to possibly reuse.
+ //
+ // If this method succeeds, then the socket member will be set to an existing
+ // socket if an existing socket was available to reuse. Otherwise, the
+ // consumer should set the socket member of this connection object.
+ //
+ // This method returns ERR_IO_PENDING if it cannot complete synchronously, in
+ // which case the consumer should wait for the completion callback to run.
+ //
+ // Init may be called multiple times.
+ //
+ int Init(const std::string& group_name, CompletionCallback* callback);
+
+ // An initialized connection 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 is not closed, indicates that the socket may be kept
+ // alive for use by a subsequent HttpConnection. NOTE: To prevent the socket
+ // from being kept alive, be sure to call its Close method.
+ void Reset();
+
+ // Returns true when Init has completed successfully.
+ bool is_initialized() const { return socket_ != NULL; }
+
+ // These may only be used if is_initialized() is true.
+ ClientSocket* socket() { return socket_->get(); }
+ void set_socket(ClientSocket* s) { socket_->reset(s); }
+
+ private:
+ scoped_refptr<HttpConnectionManager> mgr_;
+ scoped_ptr<ClientSocket>* socket_;
+ std::string group_name_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HttpConnection);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CONNECTION_H_
diff --git a/net/http/http_connection_manager.cc b/net/http/http_connection_manager.cc
new file mode 100644
index 0000000..a7edd92
--- /dev/null
+++ b/net/http/http_connection_manager.cc
@@ -0,0 +1,199 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_connection_manager.h"
+
+#include "base/message_loop.h"
+#include "net/base/client_socket.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+HttpConnectionManager::HttpConnectionManager()
+ : timer_(TimeDelta::FromSeconds(5)),
+ idle_count_(0) {
+ timer_.set_task(this);
+}
+
+HttpConnectionManager::~HttpConnectionManager() {
+ timer_.set_task(NULL);
+
+ // Cleanup any idle sockets. Assert that we have no remaining active sockets
+ // or pending requests. They should have all been cleaned up prior to the
+ // manager being destroyed.
+
+ CloseIdleSockets();
+ DCHECK(group_map_.empty());
+}
+
+int HttpConnectionManager::RequestSocket(const std::string& group_name,
+ SocketHandle** handle,
+ CompletionCallback* callback) {
+ Group& group =
+ group_map_.insert(std::make_pair(group_name, Group())).first->second;
+
+ // Can we make another active socket now?
+ if (group.active_socket_count == kMaxSocketsPerGroup) {
+ Request r;
+ r.result = handle;
+ r.callback = callback;
+ group.pending_requests.push_back(r);
+ return ERR_IO_PENDING;
+ }
+
+ // OK, we are going to activate one.
+ group.active_socket_count++;
+
+ // Use idle sockets in LIFO order.
+ while (!group.idle_sockets.empty()) {
+ SocketHandle* h = group.idle_sockets.back();
+ group.idle_sockets.pop_back();
+ DecrementIdleCount();
+ if (!h->get()->IsConnected()) {
+ delete h;
+ } else {
+ // We found one we can reuse!
+ *handle = h;
+ return OK;
+ }
+ }
+
+ *handle = new SocketHandle();
+ return OK;
+}
+
+void HttpConnectionManager::CancelRequest(const std::string& group_name,
+ SocketHandle** handle) {
+ Group& group = group_map_[group_name];
+
+ // In order for us to be canceling a pending request, we must have active
+ // sockets equaling the limit.
+ DCHECK(group.active_socket_count == kMaxSocketsPerGroup);
+
+ // Search pending_requests for matching handle.
+ std::deque<Request>::iterator it = group.pending_requests.begin();
+ for (; it != group.pending_requests.end(); ++it) {
+ if (it->result == handle) {
+ group.pending_requests.erase(it);
+ break;
+ }
+ }
+}
+
+void HttpConnectionManager::ReleaseSocket(const std::string& group_name,
+ SocketHandle* handle) {
+ // Run this asynchronously to allow the caller to finish before we let
+ // another to begin doing work. This also avoids nasty recursion issues.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &HttpConnectionManager::DoReleaseSocket, group_name, handle));
+}
+
+void HttpConnectionManager::CloseIdleSockets() {
+ MaybeCloseIdleSockets(false);
+}
+
+void HttpConnectionManager::MaybeCloseIdleSockets(
+ bool only_if_disconnected) {
+ if (idle_count_ == 0)
+ return;
+
+ GroupMap::iterator i = group_map_.begin();
+ while (i != group_map_.end()) {
+ Group& group = i->second;
+
+ std::deque<SocketHandle*>::iterator j = group.idle_sockets.begin();
+ while (j != group.idle_sockets.end()) {
+ if (!only_if_disconnected || !(*j)->get()->IsConnected()) {
+ delete *j;
+ j = group.idle_sockets.erase(j);
+ DecrementIdleCount();
+ } else {
+ ++j;
+ }
+ }
+
+ // Delete group if no longer needed.
+ if (group.active_socket_count == 0 && group.idle_sockets.empty()) {
+ DCHECK(group.pending_requests.empty());
+ group_map_.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void HttpConnectionManager::IncrementIdleCount() {
+ if (++idle_count_ == 1)
+ timer_.Start();
+}
+
+void HttpConnectionManager::DecrementIdleCount() {
+ if (--idle_count_ == 0)
+ timer_.Stop();
+}
+
+void HttpConnectionManager::DoReleaseSocket(const std::string& group_name,
+ SocketHandle* handle) {
+ GroupMap::iterator i = group_map_.find(group_name);
+ DCHECK(i != group_map_.end());
+
+ Group& group = i->second;
+
+ DCHECK(group.active_socket_count > 0);
+ group.active_socket_count--;
+
+ bool can_reuse = handle->get() && handle->get()->IsConnected();
+ if (can_reuse) {
+ group.idle_sockets.push_back(handle);
+ IncrementIdleCount();
+ } else {
+ delete handle;
+ }
+
+ // Process one pending request.
+ if (!group.pending_requests.empty()) {
+ Request r = group.pending_requests.front();
+ group.pending_requests.pop_front();
+ RequestSocket(i->first, r.result, NULL);
+ r.callback->Run(OK);
+ return;
+ }
+
+ // Delete group if no longer needed.
+ if (group.active_socket_count == 0 && group.idle_sockets.empty()) {
+ DCHECK(group.pending_requests.empty());
+ group_map_.erase(i);
+ }
+}
+
+void HttpConnectionManager::Run() {
+ MaybeCloseIdleSockets(true);
+}
+
+} // namespace net
diff --git a/net/http/http_connection_manager.h b/net/http/http_connection_manager.h
new file mode 100644
index 0000000..22080a3
--- /dev/null
+++ b/net/http/http_connection_manager.h
@@ -0,0 +1,136 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_CONNECTION_MANAGER_H_
+#define NET_HTTP_HTTP_CONNECTION_MANAGER_H_
+
+#include <deque>
+#include <map>
+
+#include "base/ref_counted.h"
+#include "base/timer.h"
+#include "net/base/completion_callback.h"
+
+namespace net {
+
+class ClientSocket;
+
+// A HttpConnectionManager is used to restrict the number of HTTP sockets
+// open at a time. It also maintains a list of idle persistent sockets.
+//
+// The HttpConnectionManager allocates SocketHandle objects, but it is not
+// responsible for allocating the associated ClientSocket object. The
+// consumer must do so if it gets a SocketHandle with a null ClientSocket.
+//
+class HttpConnectionManager : public base::RefCounted<HttpConnectionManager>,
+ public Task {
+ public:
+ HttpConnectionManager();
+
+ // The maximum number of simultaneous sockets per group.
+ enum { kMaxSocketsPerGroup = 6 };
+
+ typedef scoped_ptr<ClientSocket> SocketHandle;
+
+ // Called to get access to a SocketHandle object for the given group name.
+ //
+ // If this function returns OK, then |*handle| will reference a SocketHandle
+ // object. If ERR_IO_PENDING is returned, then the completion callback will
+ // be called when |*handle| has been populated. Otherwise, an error code is
+ // returned.
+ //
+ // If the resultant SocketHandle object has a null member, then it is the
+ // callers job to create a ClientSocket and associate it with the handle.
+ //
+ int RequestSocket(const std::string& group_name, SocketHandle** handle,
+ CompletionCallback* callback);
+
+ // Called to cancel a RequestSocket call that returned ERR_IO_PENDING. The
+ // same group_name and handle parameters must be passed to this method as
+ // were passed to the RequestSocket call being cancelled. The associated
+ // CompletionCallback is not run.
+ void CancelRequest(const std::string& group_name, SocketHandle** handle);
+
+ // Called to release a SocketHandle object that is no longer in use. If the
+ // handle has a ClientSocket that is still connected, then this handle may be
+ // added to the keep-alive set of sockets.
+ void ReleaseSocket(const std::string& group_name, SocketHandle* handle);
+
+ // Called to close any idle connections held by the connection manager.
+ void CloseIdleSockets();
+
+ private:
+ friend class base::RefCounted<HttpConnectionManager>;
+
+ ~HttpConnectionManager();
+
+ // Closes all idle sockets if |only_if_disconnected| is false. Else, only
+ // idle sockets that are disconnected get closed.
+ void MaybeCloseIdleSockets(bool only_if_disconnected);
+
+ // Called when the number of idle sockets changes.
+ void IncrementIdleCount();
+ void DecrementIdleCount();
+
+ // Called via PostTask by ReleaseSocket.
+ void DoReleaseSocket(const std::string& group_name, SocketHandle* handle);
+
+ // Task implementation. This method scans the idle sockets checking to see
+ // if any have been disconnected.
+ virtual void Run();
+
+ // A Request is allocated per call to RequestSocket that results in
+ // ERR_IO_PENDING.
+ struct Request {
+ SocketHandle** result;
+ CompletionCallback* callback;
+ };
+
+ // A Group is allocated per group_name when there are idle sockets or pending
+ // requests. Otherwise, the Group object is removed from the map.
+ struct Group {
+ std::deque<SocketHandle*> idle_sockets;
+ std::deque<Request> pending_requests;
+ int active_socket_count;
+ Group() : active_socket_count(0) {}
+ };
+
+ typedef std::map<std::string, Group> GroupMap;
+ GroupMap group_map_;
+
+ // Timer used to periodically prune sockets that have been disconnected.
+ RepeatingTimer timer_;
+
+ // The total number of idle sockets in the system.
+ int idle_count_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CONNECTION_MANAGER_H_
diff --git a/net/http/http_connection_manager_unittest.cc b/net/http/http_connection_manager_unittest.cc
new file mode 100644
index 0000000..9eade18
--- /dev/null
+++ b/net/http/http_connection_manager_unittest.cc
@@ -0,0 +1,277 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/message_loop.h"
+#include "net/base/client_socket.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_connection_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+typedef testing::Test HttpConnectionManagerTest;
+
+class MockClientSocket : public net::ClientSocket {
+ public:
+ MockClientSocket() : connected_(false) {
+ allocation_count++;
+ }
+
+ // ClientSocket methods:
+ virtual int Connect(net::CompletionCallback* callback) {
+ connected_ = true;
+ return net::OK;
+ }
+ virtual int ReconnectIgnoringLastError(net::CompletionCallback* callback) {
+ return net::ERR_FAILED;
+ }
+ virtual void Disconnect() {
+ connected_ = false;
+ }
+ virtual bool IsConnected() const {
+ return connected_;
+ }
+
+ // Socket methods:
+ virtual int Read(char* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ return net::ERR_FAILED;
+ }
+ virtual int Write(const char* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ return net::ERR_FAILED;
+ }
+ virtual int GetProperty(int property, void* buf, int buf_len) const {
+ return net::ERR_FAILED;
+ }
+ virtual int SetProperty(int property, const void* buf, int buf_len) {
+ return net::ERR_FAILED;
+ }
+
+ static int allocation_count;
+
+ private:
+ bool connected_;
+};
+
+int MockClientSocket::allocation_count = 0;
+
+class TestSocketRequest : public CallbackRunner< Tuple1<int> > {
+ public:
+ TestSocketRequest() : handle(NULL) {}
+
+ net::HttpConnectionManager::SocketHandle* handle;
+
+ void InitHandle() {
+ DCHECK(handle);
+ if (!handle->get()) {
+ handle->reset(new MockClientSocket());
+ handle->get()->Connect(NULL);
+ }
+ }
+
+ virtual void RunWithParams(const Tuple1<int>& params) {
+ DCHECK(params.a == net::OK);
+ completion_count++;
+ InitHandle();
+ }
+
+ static int completion_count;
+};
+
+int TestSocketRequest::completion_count = 0;
+
+// Call ReleaseSocket and wait for it to complete. It runs via PostTask, so we
+// can just empty the MessageLoop to ensure that ReleaseSocket finished.
+void CallReleaseSocket(net::HttpConnectionManager* connection_mgr,
+ const std::string& group_name,
+ net::HttpConnectionManager::SocketHandle* handle) {
+ connection_mgr->ReleaseSocket(group_name, handle);
+ MessageLoop::current()->Quit();
+ MessageLoop::current()->Run();
+}
+
+} // namespace
+
+TEST(HttpConnectionManagerTest, Basic) {
+ scoped_refptr<net::HttpConnectionManager> mgr =
+ new net::HttpConnectionManager();
+
+ TestSocketRequest r;
+ int rv;
+
+ rv = mgr->RequestSocket("a", &r.handle, &r);
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(r.handle != NULL);
+
+ CallReleaseSocket(mgr, "a", r.handle);
+}
+
+TEST(HttpConnectionManagerTest, WithIdleConnection) {
+ scoped_refptr<net::HttpConnectionManager> mgr =
+ new net::HttpConnectionManager();
+
+ TestSocketRequest r;
+ int rv;
+
+ rv = mgr->RequestSocket("a", &r.handle, &r);
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(r.handle != NULL);
+
+ r.InitHandle();
+
+ CallReleaseSocket(mgr, "a", r.handle);
+}
+
+TEST(HttpConnectionManagerTest, PendingRequests) {
+ scoped_refptr<net::HttpConnectionManager> mgr =
+ new net::HttpConnectionManager();
+
+ int rv;
+
+ TestSocketRequest reqs[
+ net::HttpConnectionManager::kMaxSocketsPerGroup + 10];
+
+ // Reset
+ MockClientSocket::allocation_count = 0;
+ TestSocketRequest::completion_count = 0;
+
+ // Create connections or queue up requests.
+ for (size_t i = 0; i < arraysize(reqs); ++i) {
+ rv = mgr->RequestSocket("a", &reqs[i].handle, &reqs[i]);
+ if (rv != net::ERR_IO_PENDING) {
+ EXPECT_EQ(net::OK, rv);
+ reqs[i].InitHandle();
+ }
+ }
+
+ // Release any connections until we have no connections.
+ bool released_one;
+ do {
+ released_one = false;
+ for (size_t i = 0; i < arraysize(reqs); ++i) {
+ if (reqs[i].handle) {
+ CallReleaseSocket(mgr, "a", reqs[i].handle);
+ reqs[i].handle = NULL;
+ released_one = true;
+ }
+ }
+ } while (released_one);
+
+ EXPECT_EQ(net::HttpConnectionManager::kMaxSocketsPerGroup,
+ MockClientSocket::allocation_count);
+ EXPECT_EQ(10, TestSocketRequest::completion_count);
+}
+
+TEST(HttpConnectionManagerTest, PendingRequests_NoKeepAlive) {
+ scoped_refptr<net::HttpConnectionManager> mgr =
+ new net::HttpConnectionManager();
+
+ int rv;
+
+ TestSocketRequest reqs[
+ net::HttpConnectionManager::kMaxSocketsPerGroup + 10];
+
+ // Reset
+ MockClientSocket::allocation_count = 0;
+ TestSocketRequest::completion_count = 0;
+
+ // Create connections or queue up requests.
+ for (size_t i = 0; i < arraysize(reqs); ++i) {
+ rv = mgr->RequestSocket("a", &reqs[i].handle, &reqs[i]);
+ if (rv != net::ERR_IO_PENDING) {
+ EXPECT_EQ(net::OK, rv);
+ reqs[i].InitHandle();
+ }
+ }
+
+ // Release any connections until we have no connections.
+ bool released_one;
+ do {
+ released_one = false;
+ for (size_t i = 0; i < arraysize(reqs); ++i) {
+ if (reqs[i].handle) {
+ reqs[i].handle->get()->Disconnect();
+ CallReleaseSocket(mgr, "a", reqs[i].handle);
+ reqs[i].handle = NULL;
+ released_one = true;
+ }
+ }
+ } while (released_one);
+
+ EXPECT_EQ(net::HttpConnectionManager::kMaxSocketsPerGroup + 10,
+ MockClientSocket::allocation_count);
+ EXPECT_EQ(10, TestSocketRequest::completion_count);
+}
+
+TEST(HttpConnectionManagerTest, CancelRequest) {
+ scoped_refptr<net::HttpConnectionManager> mgr =
+ new net::HttpConnectionManager();
+
+ int rv;
+
+ TestSocketRequest reqs[
+ net::HttpConnectionManager::kMaxSocketsPerGroup + 10];
+
+ // Reset
+ MockClientSocket::allocation_count = 0;
+ TestSocketRequest::completion_count = 0;
+
+ // Create connections or queue up requests.
+ for (size_t i = 0; i < arraysize(reqs); ++i) {
+ rv = mgr->RequestSocket("a", &reqs[i].handle, &reqs[i]);
+ if (rv != net::ERR_IO_PENDING) {
+ EXPECT_EQ(net::OK, rv);
+ reqs[i].InitHandle();
+ }
+ }
+
+ // Cancel a request.
+ size_t index_to_cancel =
+ net::HttpConnectionManager::kMaxSocketsPerGroup + 2;
+ EXPECT_TRUE(reqs[index_to_cancel].handle == NULL);
+ mgr->CancelRequest("a", &reqs[index_to_cancel].handle);
+
+ // Release any connections until we have no connections.
+ bool released_one;
+ do {
+ released_one = false;
+ for (size_t i = 0; i < arraysize(reqs); ++i) {
+ if (reqs[i].handle) {
+ CallReleaseSocket(mgr, "a", reqs[i].handle);
+ reqs[i].handle = NULL;
+ released_one = true;
+ }
+ }
+ } while (released_one);
+
+ EXPECT_EQ(net::HttpConnectionManager::kMaxSocketsPerGroup,
+ MockClientSocket::allocation_count);
+ EXPECT_EQ(9, TestSocketRequest::completion_count);
+}
diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc
new file mode 100644
index 0000000..2fd846c
--- /dev/null
+++ b/net/http/http_network_layer.cc
@@ -0,0 +1,99 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_network_layer.h"
+
+#include "net/base/client_socket_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_proxy_resolver_fixed.h"
+#include "net/http/http_proxy_resolver_winhttp.h"
+#include "net/http/http_transaction_winhttp.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+// static
+bool HttpNetworkLayer::use_winhttp_ = true;
+
+// static
+HttpTransactionFactory* HttpNetworkLayer::CreateFactory(
+ const HttpProxyInfo* pi) {
+ if (use_winhttp_)
+ return new HttpTransactionWinHttp::Factory(pi);
+
+ return new HttpNetworkLayer(pi);
+}
+
+// static
+void HttpNetworkLayer::UseWinHttp(bool value) {
+ use_winhttp_ = value;
+}
+
+//-----------------------------------------------------------------------------
+
+HttpNetworkLayer::HttpNetworkLayer(const HttpProxyInfo* pi)
+ : suspended_(false) {
+ HttpProxyResolver* proxy_resolver;
+ if (pi) {
+ proxy_resolver = new HttpProxyResolverFixed(*pi);
+ } else {
+ proxy_resolver = new HttpProxyResolverWinHttp();
+ }
+ session_ = new HttpNetworkSession(proxy_resolver);
+}
+
+HttpNetworkLayer::~HttpNetworkLayer() {
+}
+
+HttpTransaction* HttpNetworkLayer::CreateTransaction() {
+ if (suspended_)
+ return NULL;
+
+ return new HttpNetworkTransaction(
+ session_, ClientSocketFactory::GetDefaultFactory());
+}
+
+HttpCache* HttpNetworkLayer::GetCache() {
+ return NULL;
+}
+
+AuthCache* HttpNetworkLayer::GetAuthCache() {
+ return session_->auth_cache();
+}
+
+void HttpNetworkLayer::Suspend(bool suspend) {
+ suspended_ = suspend;
+
+ if (suspend)
+ session_->connection_manager()->CloseIdleSockets();
+}
+
+} // namespace net
diff --git a/net/http/http_network_layer.h b/net/http/http_network_layer.h
new file mode 100644
index 0000000..72fbca3
--- /dev/null
+++ b/net/http/http_network_layer.h
@@ -0,0 +1,68 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_NETWORK_LAYER_H_
+#define NET_HTTP_HTTP_NETWORK_LAYER_H_
+
+#include "base/ref_counted.h"
+#include "net/http/http_transaction_factory.h"
+
+namespace net {
+
+class HttpNetworkSession;
+class HttpProxyInfo;
+
+class HttpNetworkLayer : public HttpTransactionFactory {
+ public:
+ // This function hides the details of how a network layer gets instantiated
+ // and allows other implementations to be substituted.
+ static HttpTransactionFactory* CreateFactory(const HttpProxyInfo* pi);
+
+ // If value is true, then WinHTTP will be used.
+ static void UseWinHttp(bool value);
+
+ HttpNetworkLayer(const HttpProxyInfo* pi);
+ ~HttpNetworkLayer();
+
+ // HttpTransactionFactory methods:
+ virtual HttpTransaction* CreateTransaction();
+ virtual HttpCache* GetCache();
+ virtual AuthCache* GetAuthCache();
+ virtual void Suspend(bool suspend);
+
+ private:
+ static bool use_winhttp_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+ bool suspended_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_LAYER_H_
diff --git a/net/http/http_network_layer_unittest.cc b/net/http/http_network_layer_unittest.cc
new file mode 100644
index 0000000..dae47ae
--- /dev/null
+++ b/net/http/http_network_layer_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_network_layer.h"
+#include "net/http/http_transaction_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class HttpNetworkLayerTest : public testing::Test {
+};
+
+} // namespace
+
+TEST_F(HttpNetworkLayerTest, CreateAndDestroy) {
+ net::HttpNetworkLayer factory(NULL);
+
+ net::HttpTransaction* trans = factory.CreateTransaction();
+ trans->Destroy();
+}
+
+TEST_F(HttpNetworkLayerTest, Suspend) {
+ net::HttpNetworkLayer factory(NULL);
+
+ net::HttpTransaction* trans = factory.CreateTransaction();
+ trans->Destroy();
+
+ factory.Suspend(true);
+
+ trans = factory.CreateTransaction();
+ ASSERT_TRUE(trans == NULL);
+
+ factory.Suspend(false);
+
+ trans = factory.CreateTransaction();
+ trans->Destroy();
+}
+
+TEST_F(HttpNetworkLayerTest, GoogleGET) {
+ net::HttpNetworkLayer factory(NULL);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = factory.CreateTransaction();
+
+ net::HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.user_agent = "Foo/1.0";
+ request_info.load_flags = net::LOAD_NORMAL;
+
+ int rv = trans->Start(&request_info, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ std::string contents;
+ rv = ReadTransaction(trans, &contents);
+ EXPECT_EQ(net::OK, rv);
+
+ trans->Destroy();
+}
diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h
new file mode 100644
index 0000000..dc31ed1
--- /dev/null
+++ b/net/http/http_network_session.h
@@ -0,0 +1,62 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_NETWORK_SESSION_H_
+#define NET_HTTP_HTTP_NETWORK_SESSION_H_
+
+#include "base/ref_counted.h"
+#include "net/base/auth_cache.h"
+#include "net/http/http_connection_manager.h"
+#include "net/http/http_proxy_service.h"
+
+namespace net {
+
+// This class holds session objects used by HttpNetworkTransaction objects.
+class HttpNetworkSession : public base::RefCounted<HttpNetworkSession> {
+ public:
+ HttpNetworkSession(HttpProxyResolver* proxy_resolver)
+ : connection_manager_(new HttpConnectionManager()),
+ proxy_resolver_(proxy_resolver),
+ proxy_service_(proxy_resolver) {
+ }
+
+ AuthCache* auth_cache() { return &auth_cache_; }
+ HttpConnectionManager* connection_manager() { return connection_manager_; }
+ HttpProxyService* proxy_service() { return &proxy_service_; }
+
+ private:
+ AuthCache auth_cache_;
+ scoped_refptr<HttpConnectionManager> connection_manager_;
+ scoped_ptr<HttpProxyResolver> proxy_resolver_;
+ HttpProxyService proxy_service_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_SESSION_H_
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
new file mode 100644
index 0000000..5ae927f
--- /dev/null
+++ b/net/http/http_network_transaction.cc
@@ -0,0 +1,673 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_network_transaction.h"
+
+#include "base/string_util.h"
+#include "net/base/client_socket_factory.h"
+#include "net/base/host_resolver.h"
+#include "net/base/load_flags.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_chunked_decoder.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_util.h"
+
+// TODO(darin):
+// - authentication
+// - proxies (need to call ReconsiderProxyAfterError and handle SSL tunnel)
+// - ssl
+// - http/0.9
+// - header line continuations (i.e., lines that start with LWS)
+// - tolerate some junk (up to 4 bytes) in front of the HTTP/1.x status line
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+// TODO(darin): Move this onto HttpProxyInfo
+static std::string GetProxyHostPort(const HttpProxyInfo& pi) {
+ return WideToASCII(pi.proxy_server());
+}
+
+//-----------------------------------------------------------------------------
+
+HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session,
+ ClientSocketFactory* csf)
+#pragma warning(suppress: 4355)
+ : io_callback_(this, &HttpNetworkTransaction::OnIOComplete),
+ user_callback_(NULL),
+ session_(session),
+ request_(NULL),
+ pac_request_(NULL),
+ socket_factory_(csf),
+ connection_(session->connection_manager()),
+ reused_socket_(false),
+ using_ssl_(false),
+ using_proxy_(false),
+ using_tunnel_(false),
+ bytes_sent_(0),
+ header_buf_capacity_(0),
+ header_buf_len_(0),
+ header_buf_body_offset_(-1),
+ content_length_(-1), // -1 means unspecified.
+ content_read_(0),
+ read_buf_(NULL),
+ read_buf_len_(0),
+ next_state_(STATE_NONE) {
+}
+
+HttpNetworkTransaction::~HttpNetworkTransaction() {
+ // If we still have an open socket, then make sure to close it so we don't
+ // try to reuse it later on.
+ if (connection_.is_initialized())
+ connection_.set_socket(NULL);
+
+ if (pac_request_)
+ session_->proxy_service()->CancelPacRequest(pac_request_);
+}
+
+void HttpNetworkTransaction::BuildRequestHeaders() {
+ std::string path;
+ if (using_proxy_) {
+ // TODO(darin): GURL should have a method for this.
+ path = request_->url.spec();
+ size_t ref_pos = path.rfind('#');
+ if (ref_pos != std::string::npos)
+ path.erase(ref_pos);
+ } else {
+ path = request_->url.PathForRequest();
+ }
+
+ request_headers_ = request_->method + " " + path + " HTTP/1.1\r\n" +
+ "Host: " + request_->url.host();
+ if (request_->url.IntPort() != -1)
+ request_headers_ += ":" + request_->url.port();
+ request_headers_ += "\r\n";
+
+ // For compat with HTTP/1.0 servers and proxies:
+ if (using_proxy_)
+ request_headers_ += "Proxy-";
+ request_headers_ += "Connection: keep-alive\r\n";
+
+ if (!request_->user_agent.empty())
+ request_headers_ += "User-Agent: " + request_->user_agent + "\r\n";
+
+ // Our consumer should have made sure that this is a safe referrer. See for
+ // instance WebCore::FrameLoader::HideReferrer.
+ if (request_->referrer.is_valid())
+ request_headers_ += "Referer: " + request_->referrer.spec() + "\r\n";
+
+ // Add a content length header?
+ if (request_->upload_data) {
+ request_body_stream_.reset(new UploadDataStream(request_->upload_data));
+ request_headers_ +=
+ "Content-Length: " + Uint64ToString(request_body_stream_->size()) +
+ "\r\n";
+ } 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_ += "Content-Length: 0\r\n";
+ }
+
+ // Honor load flags that impact proxy caches.
+ if (request_->load_flags & LOAD_BYPASS_CACHE) {
+ request_headers_ += "Pragma: no-cache\r\nCache-Control: no-cache\r\n";
+ } else if (request_->load_flags & LOAD_VALIDATE_CACHE) {
+ request_headers_ += "Cache-Control: max-age=0\r\n";
+ }
+
+ // TODO(darin): Need to prune out duplicate headers.
+
+ request_headers_ += request_->extra_headers;
+ request_headers_ += "\r\n";
+}
+
+void HttpNetworkTransaction::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(user_callback_);
+
+ // Since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ 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_RESOLVE_PROXY:
+ rv = DoResolveProxy();
+ break;
+ case STATE_RESOLVE_PROXY_COMPLETE:
+ rv = DoResolveProxyComplete(rv);
+ break;
+ case STATE_INIT_CONNECTION:
+ rv = DoInitConnection();
+ break;
+ case STATE_INIT_CONNECTION_COMPLETE:
+ rv = DoInitConnectionComplete(rv);
+ break;
+ case STATE_RESOLVE_HOST:
+ rv = DoResolveHost();
+ break;
+ case STATE_RESOLVE_HOST_COMPLETE:
+ rv = DoResolveHostComplete(rv);
+ break;
+ case STATE_CONNECT:
+ rv = DoConnect();
+ break;
+ case STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ case STATE_WRITE_HEADERS:
+ rv = DoWriteHeaders();
+ break;
+ case STATE_WRITE_HEADERS_COMPLETE:
+ rv = DoWriteHeadersComplete(rv);
+ break;
+ case STATE_WRITE_BODY:
+ rv = DoWriteBody();
+ break;
+ case STATE_WRITE_BODY_COMPLETE:
+ rv = DoWriteBodyComplete(rv);
+ break;
+ case STATE_READ_HEADERS:
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ rv = DoReadHeadersComplete(rv);
+ break;
+ case STATE_READ_BODY:
+ rv = DoReadBody();
+ break;
+ case STATE_READ_BODY_COMPLETE:
+ rv = DoReadBodyComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int HttpNetworkTransaction::DoResolveProxy() {
+ DCHECK(!pac_request_);
+
+ next_state_ = STATE_RESOLVE_PROXY_COMPLETE;
+
+ return session_->proxy_service()->ResolveProxy(
+ request_->url, &proxy_info_, &io_callback_, &pac_request_);
+}
+
+int HttpNetworkTransaction::DoResolveProxyComplete(int result) {
+ next_state_ = STATE_INIT_CONNECTION;
+
+ pac_request_ = NULL;
+
+ if (result != OK) {
+ DLOG(ERROR) << "Failed to resolve proxy: " << result;
+ proxy_info_.UseDirect();
+ }
+ return OK;
+}
+
+int HttpNetworkTransaction::DoInitConnection() {
+ DCHECK(!connection_.is_initialized());
+
+ next_state_ = STATE_INIT_CONNECTION_COMPLETE;
+
+ using_ssl_ = request_->url.SchemeIs("https");
+ using_proxy_ = !proxy_info_.is_direct() && !using_ssl_;
+ using_tunnel_ = !proxy_info_.is_direct() && using_ssl_;
+
+ // Build the string used to uniquely identify connections of this type.
+ std::string connection_group;
+ if (using_proxy_ || using_tunnel_)
+ connection_group = "proxy/" + GetProxyHostPort(proxy_info_) + "/";
+ if (!using_proxy_)
+ connection_group.append(request_->url.GetOrigin().spec());
+
+ return connection_.Init(connection_group, &io_callback_);
+}
+
+int HttpNetworkTransaction::DoInitConnectionComplete(int result) {
+ if (result < 0)
+ return result;
+
+ DCHECK(connection_.is_initialized());
+
+ // Set the reused_socket_ flag to indicate that we are using a keep-alive
+ // connection. This flag is used to handle errors that occur while we are
+ // trying to reuse a keep-alive connection.
+ if (reused_socket_ = (connection_.socket() != NULL)) {
+ next_state_ = STATE_WRITE_HEADERS;
+ } else {
+ next_state_ = STATE_RESOLVE_HOST;
+ }
+ return OK;
+}
+
+int HttpNetworkTransaction::DoResolveHost() {
+ next_state_ = STATE_RESOLVE_HOST_COMPLETE;
+
+ DCHECK(!resolver_.get());
+
+ std::string host;
+ int port;
+
+ // Determine the host and port to connect to.
+ if (using_proxy_ || using_tunnel_) {
+ const std::string& proxy = GetProxyHostPort(proxy_info_);
+ StringTokenizer t(proxy, ":");
+ // TODO(darin): Handle errors here. Perhaps HttpProxyInfo should do this
+ // before claiming a proxy server configuration.
+ t.GetNext();
+ host = t.token();
+ t.GetNext();
+ port = static_cast<int>(StringToInt64(t.token()));
+ } else {
+ host = request_->url.host();
+ port = request_->url.IntPort();
+ if (port == -1) {
+ if (using_ssl_) {
+ port = 443; // Default HTTPS port
+ } else {
+ port = 80; // Default HTTP port
+ }
+ }
+ }
+
+ resolver_.reset(new HostResolver());
+ return resolver_->Resolve(host, port, &addresses_, &io_callback_);
+}
+
+int HttpNetworkTransaction::DoResolveHostComplete(int result) {
+ resolver_.reset();
+ if (result == OK)
+ next_state_ = STATE_CONNECT;
+ return result;
+}
+
+int HttpNetworkTransaction::DoConnect() {
+ next_state_ = STATE_CONNECT_COMPLETE;
+
+ DCHECK(!connection_.socket());
+
+ ClientSocket* s = socket_factory_->CreateTCPClientSocket(addresses_);
+
+ // If we are using a direct SSL connection, then go ahead and create the SSL
+ // wrapper socket now. Otherwise, we need to first issue a CONNECT request.
+ if (using_ssl_ && !using_tunnel_)
+ s = socket_factory_->CreateSSLClientSocket(s, request_->url.host());
+
+ connection_.set_socket(s);
+ return connection_.socket()->Connect(&io_callback_);
+}
+
+int HttpNetworkTransaction::DoConnectComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_WRITE_HEADERS;
+ return result;
+}
+
+int HttpNetworkTransaction::DoWriteHeaders() {
+ next_state_ = STATE_WRITE_HEADERS_COMPLETE;
+
+ // This is constructed lazily (instead of within our Start method), so that
+ // we have proxy info available.
+ if (request_headers_.empty())
+ BuildRequestHeaders();
+
+ // Record our best estimate of the 'request time' as the time when we send
+ // out the first bytes of the request headers.
+ if (bytes_sent_ == 0)
+ response_.request_time = Time::Now();
+
+ const char* buf = request_headers_.data() + bytes_sent_;
+ int buf_len = static_cast<int>(request_headers_.size() - bytes_sent_);
+ DCHECK(buf_len > 0);
+
+ return connection_.socket()->Write(buf, buf_len, &io_callback_);
+}
+
+int HttpNetworkTransaction::DoWriteHeadersComplete(int result) {
+ if (result < 0)
+ return HandleIOError(result);
+
+ bytes_sent_ += result;
+ if (bytes_sent_ < request_headers_.size()) {
+ next_state_ = STATE_WRITE_HEADERS;
+ } else if (request_->upload_data) {
+ next_state_ = STATE_WRITE_BODY;
+ } else {
+ next_state_ = STATE_READ_HEADERS;
+ }
+ return OK;
+}
+
+int HttpNetworkTransaction::DoWriteBody() {
+ next_state_ = STATE_WRITE_BODY_COMPLETE;
+
+ DCHECK(request_->upload_data);
+ DCHECK(request_body_stream_.get());
+
+ const char* buf = request_body_stream_->buf();
+ int buf_len = static_cast<int>(request_body_stream_->buf_len());
+
+ return connection_.socket()->Write(buf, buf_len, &io_callback_);
+}
+
+int HttpNetworkTransaction::DoWriteBodyComplete(int result) {
+ if (result < 0)
+ return HandleIOError(result);
+
+ request_body_stream_->DidConsume(result);
+
+ if (request_body_stream_->position() < request_body_stream_->size()) {
+ next_state_ = STATE_WRITE_BODY;
+ } else {
+ next_state_ = STATE_READ_HEADERS;
+ }
+ return OK;
+}
+
+int HttpNetworkTransaction::DoReadHeaders() {
+ next_state_ = STATE_READ_HEADERS_COMPLETE;
+
+ // Grow the read buffer if necessary.
+ if (header_buf_len_ == header_buf_capacity_) {
+ header_buf_capacity_ += kHeaderBufInitialSize;
+ header_buf_.reset(static_cast<char*>(
+ realloc(header_buf_.release(), header_buf_capacity_)));
+ }
+
+ char* buf = header_buf_.get() + header_buf_len_;
+ int buf_len = header_buf_capacity_ - header_buf_len_;
+
+ return connection_.socket()->Read(buf, buf_len, &io_callback_);
+}
+
+int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
+ if (result < 0)
+ return HandleIOError(result);
+
+ // Record our best estimate of the 'response time' as the time when we read
+ // the first bytes of the response headers.
+ if (header_buf_len_ == 0)
+ response_.response_time = Time::Now();
+
+ if (result == 0) {
+ // The socket was closed before we found end-of-headers. Assume that EOF
+ // is end-of-headers.
+ header_buf_body_offset_ = header_buf_len_;
+ } else {
+ header_buf_len_ += result;
+ DCHECK(header_buf_len_ <= header_buf_capacity_);
+
+ // TODO(darin): Check for a HTTP/0.9 response.
+
+ int eoh = HttpUtil::LocateEndOfHeaders(header_buf_.get(), header_buf_len_);
+ if (eoh != -1) {
+ header_buf_body_offset_ = eoh;
+ } else {
+ next_state_ = STATE_READ_HEADERS; // Read more.
+ return OK;
+ }
+ }
+
+ // And, we are done with the Start sequence.
+ next_state_ = STATE_NONE;
+ return DidReadResponseHeaders();
+}
+
+int HttpNetworkTransaction::DoReadBody() {
+ DCHECK(read_buf_);
+ DCHECK(read_buf_len_ > 0);
+ DCHECK(connection_.is_initialized());
+
+ next_state_ = STATE_READ_BODY_COMPLETE;
+
+ // We may have some data remaining in the read buffer.
+ if (header_buf_.get() && header_buf_body_offset_ < header_buf_len_) {
+ int n = std::min(read_buf_len_, header_buf_len_ - header_buf_body_offset_);
+ memcpy(read_buf_, header_buf_.get() + header_buf_body_offset_, n);
+ header_buf_body_offset_ += n;
+ if (header_buf_body_offset_ == header_buf_len_)
+ header_buf_.reset();
+ return n;
+ }
+
+ return connection_.socket()->Read(read_buf_, read_buf_len_, &io_callback_);
+}
+
+int HttpNetworkTransaction::DoReadBodyComplete(int result) {
+ // We are done with the Read call.
+
+ // Filter incoming data if appropriate. FilterBuf may return an error.
+ if (result > 0 && chunked_decoder_.get()) {
+ result = chunked_decoder_->FilterBuf(read_buf_, result);
+ if (result == 0) {
+ // Don't signal completion of the Read call yet or else it'll look like
+ // we received end-of-file. Wait for more data.
+ next_state_ = STATE_READ_BODY;
+ return OK;
+ }
+ }
+
+ bool done = false, keep_alive = false;
+ if (result < 0) {
+ // Error while reading the socket.
+ done = true;
+ } else {
+ content_read_ += result;
+ if ((content_length_ != -1 && content_read_ >= content_length_) ||
+ (chunked_decoder_.get() && chunked_decoder_->reached_eof())) {
+ done = true;
+ keep_alive = response_.headers->IsKeepAlive();
+ }
+ }
+
+ // Cleanup the HttpConnection if we are done.
+ if (done) {
+ if (!keep_alive)
+ connection_.set_socket(NULL);
+ connection_.Reset();
+ }
+
+ // Clear these to avoid leaving around old state.
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+
+ return result;
+}
+
+int HttpNetworkTransaction::DidReadResponseHeaders() {
+ scoped_refptr<HttpResponseHeaders> headers = new HttpResponseHeaders(
+ HttpUtil::AssembleRawHeaders(header_buf_.get(), header_buf_body_offset_));
+
+ // 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.
+ if (headers->response_code() == 100) {
+ header_buf_len_ = 0;
+ header_buf_body_offset_ = -1;
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+ }
+
+ response_.headers = headers;
+ response_.vary_data.Init(*request_, *response_.headers);
+
+ // Figure how to determine EOF:
+
+ // For certain responses, we know the content length is always 0.
+ switch (response_.headers->response_code()) {
+ case 204:
+ case 205:
+ case 304:
+ content_length_ = 0;
+ break;
+ }
+
+ if (content_length_ == -1) {
+ // Ignore spurious chunked responses from HTTP/1.0 servers and proxies.
+ // Otherwise "Transfer-Encoding: chunked" trumps "Content-Length: N"
+ const std::string& status_line = response_.headers->GetStatusLine();
+ if (!StartsWithASCII(status_line, "HTTP/1.0 ", true) &&
+ response_.headers->HasHeaderValue("Transfer-Encoding", "chunked")) {
+ chunked_decoder_.reset(new HttpChunkedDecoder());
+ } else {
+ content_length_ = response_.headers->GetContentLength();
+ // If content_length_ is still -1, then we have to wait for the server to
+ // close the connection.
+ }
+ }
+
+ return OK;
+}
+
+int HttpNetworkTransaction::HandleIOError(int 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:
+ if (reused_socket_ && // We reused a keep-alive connection.
+ !header_buf_len_) { // We have not received any response data yet.
+ connection_.set_socket(NULL);
+ connection_.Reset();
+ bytes_sent_ = 0;
+ if (request_body_stream_.get())
+ request_body_stream_->Reset();
+ next_state_ = STATE_INIT_CONNECTION;
+ error = OK;
+ }
+ break;
+ }
+ return error;
+}
+
+void HttpNetworkTransaction::Destroy() {
+ delete this;
+}
+
+int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback) {
+ request_ = request_info;
+
+ next_state_ = STATE_RESOLVE_PROXY;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int HttpNetworkTransaction::RestartIgnoringLastError(
+ CompletionCallback* callback) {
+ return ERR_FAILED; // TODO(darin): implement me!
+}
+
+int HttpNetworkTransaction::RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback) {
+ return ERR_FAILED; // TODO(darin): implement me!
+}
+
+int HttpNetworkTransaction::Read(char* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(response_.headers);
+ DCHECK(buf);
+ DCHECK(buf_len > 0);
+
+ if (!connection_.is_initialized())
+ return 0; // Treat like EOF.
+
+ read_buf_ = buf;
+ read_buf_len_ = buf_len;
+
+ next_state_ = STATE_READ_BODY;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+const HttpResponseInfo* HttpNetworkTransaction::GetResponseInfo() const {
+ return response_.headers ? &response_ : NULL;
+}
+
+LoadState HttpNetworkTransaction::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_RESOLVE_PROXY_COMPLETE:
+ return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
+ case STATE_RESOLVE_HOST_COMPLETE:
+ return LOAD_STATE_RESOLVING_HOST;
+ case STATE_CONNECT_COMPLETE:
+ return LOAD_STATE_CONNECTING;
+ case STATE_WRITE_HEADERS_COMPLETE:
+ case STATE_WRITE_BODY_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;
+ }
+}
+
+uint64 HttpNetworkTransaction::GetUploadProgress() const {
+ if (!request_body_stream_.get())
+ return 0;
+
+ return request_body_stream_->position();
+}
+
+} // namespace net
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
new file mode 100644
index 0000000..f53a5e7
--- /dev/null
+++ b/net/http/http_network_transaction.h
@@ -0,0 +1,179 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_NETWORK_TRANSACTION_H_
+#define NET_HTTP_HTTP_NETWORK_TRANSACTION_H_
+
+#include "base/ref_counted.h"
+#include "net/base/address_list.h"
+#include "net/http/http_connection.h"
+#include "net/http/http_proxy_service.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class HostResolver;
+class HttpChunkedDecoder;
+class HttpNetworkSession;
+class UploadDataStream;
+
+class HttpNetworkTransaction : public HttpTransaction {
+ public:
+ HttpNetworkTransaction(HttpNetworkSession* session,
+ ClientSocketFactory* socket_factory);
+
+ // HttpTransaction methods:
+ virtual void Destroy();
+ virtual int Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback);
+ virtual int RestartIgnoringLastError(CompletionCallback* callback);
+ virtual int RestartWithAuth(const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback);
+ virtual int Read(char* buf, int buf_len, CompletionCallback* callback);
+ virtual const HttpResponseInfo* GetResponseInfo() const;
+ virtual LoadState GetLoadState() const;
+ virtual uint64 GetUploadProgress() const;
+
+ private:
+ ~HttpNetworkTransaction();
+ void BuildRequestHeaders();
+ 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 DoResolveProxy();
+ int DoResolveProxyComplete(int result);
+ int DoInitConnection();
+ int DoInitConnectionComplete(int result);
+ int DoResolveHost();
+ int DoResolveHostComplete(int result);
+ int DoConnect();
+ int DoConnectComplete(int result);
+ int DoWriteHeaders();
+ int DoWriteHeadersComplete(int result);
+ int DoWriteBody();
+ int DoWriteBodyComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoReadBody();
+ int DoReadBodyComplete(int result);
+
+ // Called when read_buf_ contains the complete response headers.
+ int DidReadResponseHeaders();
+
+ // 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);
+
+ CompletionCallbackImpl<HttpNetworkTransaction> io_callback_;
+ CompletionCallback* user_callback_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+
+ const HttpRequestInfo* request_;
+ HttpResponseInfo response_;
+
+ HttpProxyService::PacRequest* pac_request_;
+ HttpProxyInfo proxy_info_;
+
+ scoped_ptr<HostResolver> resolver_;
+ AddressList addresses_;
+
+ ClientSocketFactory* socket_factory_;
+ HttpConnection connection_;
+ bool reused_socket_;
+
+ bool using_ssl_; // True if handling a HTTPS request
+ bool using_proxy_; // True if using a HTTP proxy
+ bool using_tunnel_; // True if using a tunnel for HTTPS
+
+ std::string request_headers_;
+ scoped_ptr<UploadDataStream> request_body_stream_;
+ uint64 bytes_sent_;
+
+ // The read buffer may be larger than it is full. The 'capacity' indicates
+ // the allocation size of the buffer, and the 'len' indicates how much data
+ // is in the buffer already. The 'body offset' indicates the offset of the
+ // start of the response body within the read buffer.
+ scoped_ptr_malloc<char> header_buf_;
+ int header_buf_capacity_;
+ int header_buf_len_;
+ int header_buf_body_offset_;
+ enum { kHeaderBufInitialSize = 4096 };
+
+ // Indicates the content length remaining to read. If this value is less
+ // than zero (and chunked_decoder_ is null), then we read until the server
+ // closes the connection.
+ int64 content_length_;
+
+ // Keeps track of the number of response body bytes read so far.
+ int64 content_read_;
+
+ scoped_ptr<HttpChunkedDecoder> chunked_decoder_;
+
+ // User buffer and length passed to the Read method.
+ char* read_buf_;
+ int read_buf_len_;
+
+ // The different states for the 'Start' routine.
+ enum State {
+ STATE_RESOLVE_PROXY,
+ STATE_RESOLVE_PROXY_COMPLETE,
+ STATE_INIT_CONNECTION,
+ STATE_INIT_CONNECTION_COMPLETE,
+ STATE_RESOLVE_HOST,
+ STATE_RESOLVE_HOST_COMPLETE,
+ STATE_CONNECT,
+ STATE_CONNECT_COMPLETE,
+ STATE_WRITE_HEADERS,
+ STATE_WRITE_HEADERS_COMPLETE,
+ STATE_WRITE_BODY,
+ STATE_WRITE_BODY_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_READ_BODY,
+ STATE_READ_BODY_COMPLETE,
+ STATE_NONE
+ };
+ State next_state_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_TRANSACTION_H_
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
new file mode 100644
index 0000000..266c30e
--- /dev/null
+++ b/net/http/http_network_transaction_unittest.cc
@@ -0,0 +1,425 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/base/client_socket_factory.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/upload_data.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_transaction_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+struct MockConnect {
+ bool async;
+ int result;
+};
+
+struct MockRead {
+ bool async;
+ int result; // Ignored if data is non-null.
+ const char* data;
+ int data_len; // -1 if strlen(data) should be used.
+};
+
+struct MockSocket {
+ MockConnect connect;
+ MockRead* reads; // Terminated by a MockRead element with data == NULL.
+};
+
+// Holds an array of MockSocket elements. As MockTCPClientSocket objects get
+// instantiated, they take their data from the i'th element of this array.
+//
+// Tests should assign the first N entries of mock_sockets to point to valid
+// MockSocket objects. The first unused entry should be NULL'd.
+//
+MockSocket* mock_sockets[10];
+
+// Index of the next mock_sockets element to use.
+int mock_sockets_index;
+
+class MockTCPClientSocket : public net::ClientSocket {
+ public:
+ MockTCPClientSocket(const net::AddressList& addresses)
+#pragma warning(suppress:4355)
+ : data_(mock_sockets[mock_sockets_index++]),
+ method_factory_(this),
+ callback_(NULL),
+ read_index_(0),
+ read_offset_(0),
+ connected_(false) {
+ DCHECK(data_) << "overran mock_sockets array";
+ }
+ // ClientSocket methods:
+ virtual int Connect(net::CompletionCallback* callback) {
+ DCHECK(!callback_);
+ if (connected_)
+ return net::OK;
+ connected_ = true;
+ if (data_->connect.async) {
+ RunCallbackAsync(callback, data_->connect.result);
+ return net::ERR_IO_PENDING;
+ }
+ return data_->connect.result;
+ }
+ virtual int ReconnectIgnoringLastError(net::CompletionCallback* callback) {
+ NOTREACHED();
+ return net::ERR_FAILED;
+ }
+ virtual void Disconnect() {
+ connected_ = false;
+ callback_ = NULL;
+ }
+ virtual bool IsConnected() const {
+ return connected_;
+ }
+ // Socket methods:
+ virtual int Read(char* buf, int buf_len, net::CompletionCallback* callback) {
+ DCHECK(!callback_);
+ MockRead& r = data_->reads[read_index_];
+ int result;
+ if (r.data) {
+ if (r.data_len == -1)
+ r.data_len = static_cast<int>(strlen(r.data));
+ if (r.data_len - read_offset_ > 0) {
+ result = std::min(buf_len, r.data_len - read_offset_);
+ memcpy(buf, r.data + read_offset_, result);
+ read_offset_ += result;
+ if (read_offset_ == r.data_len) {
+ read_index_++;
+ read_offset_ = 0;
+ }
+ } else {
+ result = 0; // EOF
+ }
+ } else {
+ result = r.result;
+ }
+ if (r.async) {
+ RunCallbackAsync(callback, result);
+ return net::ERR_IO_PENDING;
+ }
+ return result;
+ }
+ virtual int Write(const char* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ DCHECK(!callback_);
+ return buf_len; // OK, we wrote it.
+ }
+ private:
+ void RunCallbackAsync(net::CompletionCallback* callback, int result) {
+ callback_ = callback;
+ MessageLoop::current()->PostTask(FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &MockTCPClientSocket::RunCallback, result));
+ }
+ void RunCallback(int result) {
+ net::CompletionCallback* c = callback_;
+ callback_ = NULL;
+ if (c)
+ c->Run(result);
+ }
+ MockSocket* data_;
+ ScopedRunnableMethodFactory<MockTCPClientSocket> method_factory_;
+ net::CompletionCallback* callback_;
+ int read_index_;
+ int read_offset_;
+ bool connected_;
+};
+
+class MockClientSocketFactory : public net::ClientSocketFactory {
+ public:
+ virtual net::ClientSocket* CreateTCPClientSocket(
+ const net::AddressList& addresses) {
+ return new MockTCPClientSocket(addresses);
+ }
+ virtual net::ClientSocket* CreateSSLClientSocket(
+ net::ClientSocket* transport_socket,
+ const std::string& hostname) {
+ return NULL;
+ }
+};
+
+MockClientSocketFactory mock_socket_factory;
+
+class NullProxyResolver : public net::HttpProxyResolver {
+ public:
+ virtual int GetProxyConfig(net::HttpProxyConfig* config) {
+ return net::ERR_FAILED;
+ }
+ virtual int GetProxyForURL(const std::wstring& query_url,
+ const std::wstring& pac_url,
+ net::HttpProxyInfo* results) {
+ return net::ERR_FAILED;
+ }
+};
+
+net::HttpNetworkSession* CreateSession() {
+ return new net::HttpNetworkSession(new NullProxyResolver());
+}
+
+class HttpNetworkTransactionTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ mock_sockets[0] = NULL;
+ mock_sockets_index = 0;
+ }
+};
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+TEST_F(HttpNetworkTransactionTest, Basic) {
+ net::HttpTransaction* trans = new net::HttpNetworkTransaction(
+ CreateSession(), &mock_socket_factory);
+ trans->Destroy();
+}
+
+TEST_F(HttpNetworkTransactionTest, SimpleGET) {
+ net::HttpTransaction* trans = new net::HttpNetworkTransaction(
+ CreateSession(), &mock_socket_factory);
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockRead data_reads[] = {
+ { true, 0, "HTTP/1.0 200 OK\r\n\r\n", -1 },
+ { true, 0, "hello world", -1 },
+ { false, net::OK, NULL, 0 },
+ };
+ MockSocket data;
+ data.connect.async = true;
+ data.connect.result = net::OK;
+ data.reads = data_reads;
+ mock_sockets[0] = &data;
+ mock_sockets[1] = NULL;
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.0 200 OK");
+
+ std::string response_data;
+ rv = ReadTransaction(trans, &response_data);
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(response_data == "hello world");
+
+ trans->Destroy();
+
+ // Empty the current queue.
+ MessageLoop::current()->Quit();
+ MessageLoop::current()->Run();
+}
+
+TEST_F(HttpNetworkTransactionTest, ReuseConnection) {
+ scoped_refptr<net::HttpNetworkSession> session = CreateSession();
+
+ MockRead data_reads[] = {
+ { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 },
+ { true, 0, "hello", -1 },
+ { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 },
+ { true, 0, "world", -1 },
+ { false, net::OK, NULL, 0 },
+ };
+ MockSocket data;
+ data.connect.async = true;
+ data.connect.result = net::OK;
+ data.reads = data_reads;
+ mock_sockets[0] = &data;
+ mock_sockets[1] = NULL;
+
+ const char* kExpectedResponseData[] = {
+ "hello", "world"
+ };
+
+ for (int i = 0; i < 2; ++i) {
+ net::HttpTransaction* trans =
+ new net::HttpNetworkTransaction(session, &mock_socket_factory);
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.1 200 OK");
+
+ std::string response_data;
+ rv = ReadTransaction(trans, &response_data);
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(response_data == kExpectedResponseData[i]);
+
+ trans->Destroy();
+
+ // Empty the current queue.
+ MessageLoop::current()->Quit();
+ MessageLoop::current()->Run();
+ }
+}
+
+TEST_F(HttpNetworkTransactionTest, Ignores100) {
+ net::HttpTransaction* trans = new net::HttpNetworkTransaction(
+ CreateSession(), &mock_socket_factory);
+
+ net::HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.foo.com/");
+ request.upload_data = new net::UploadData;
+ request.upload_data->AppendBytes("foo", 3);
+ request.load_flags = 0;
+
+ MockRead data_reads[] = {
+ { true, 0, "HTTP/1.0 100 Continue\r\n\r\n", -1 },
+ { true, 0, "HTTP/1.0 200 OK\r\n\r\n", -1 },
+ { true, 0, "hello world", -1 },
+ { false, net::OK, NULL, 0 },
+ };
+ MockSocket data;
+ data.connect.async = true;
+ data.connect.result = net::OK;
+ data.reads = data_reads;
+ mock_sockets[0] = &data;
+ mock_sockets[1] = NULL;
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.0 200 OK");
+
+ std::string response_data;
+ rv = ReadTransaction(trans, &response_data);
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(response_data == "hello world");
+
+ trans->Destroy();
+
+ // Empty the current queue.
+ MessageLoop::current()->Quit();
+ MessageLoop::current()->Run();
+}
+
+TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionReset) {
+ scoped_refptr<net::HttpNetworkSession> session = CreateSession();
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ MockRead data1_reads[] = {
+ { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 },
+ { true, 0, "hello", -1 },
+ { true, net::ERR_CONNECTION_RESET, NULL, 0 },
+ };
+ MockSocket data1;
+ data1.connect.async = true;
+ data1.connect.result = net::OK;
+ data1.reads = data1_reads;
+ mock_sockets[0] = &data1;
+
+ MockRead data2_reads[] = {
+ { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 },
+ { true, 0, "world", -1 },
+ { true, net::OK, NULL, 0 },
+ };
+ MockSocket data2;
+ data2.connect.async = true;
+ data2.connect.result = net::OK;
+ data2.reads = data2_reads;
+ mock_sockets[1] = &data2;
+
+ const char* kExpectedResponseData[] = {
+ "hello", "world"
+ };
+
+ for (int i = 0; i < 2; ++i) {
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans =
+ new net::HttpNetworkTransaction(session, &mock_socket_factory);
+
+ int rv = trans->Start(&request, &callback);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.1 200 OK");
+
+ std::string response_data;
+ rv = ReadTransaction(trans, &response_data);
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(response_data == kExpectedResponseData[i]);
+
+ trans->Destroy();
+
+ // Empty the current queue.
+ MessageLoop::current()->Quit();
+ MessageLoop::current()->Run();
+ }
+}
diff --git a/net/http/http_proxy_resolver_fixed.cc b/net/http/http_proxy_resolver_fixed.cc
new file mode 100644
index 0000000..dd2ac8d
--- /dev/null
+++ b/net/http/http_proxy_resolver_fixed.cc
@@ -0,0 +1,48 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_proxy_resolver_fixed.h"
+
+#include "net/base/net_errors.h"
+
+namespace net {
+
+int HttpProxyResolverFixed::GetProxyConfig(HttpProxyConfig* config) {
+ config->proxy_server = pi_.proxy_server();
+ return OK;
+}
+
+int HttpProxyResolverFixed::GetProxyForURL(const std::wstring& query_url,
+ const std::wstring& pac_url,
+ HttpProxyInfo* results) {
+ NOTREACHED() << "Should not be asked to do proxy auto config";
+ return ERR_FAILED;
+}
+
+} // namespace net
diff --git a/net/http/http_proxy_resolver_fixed.h b/net/http/http_proxy_resolver_fixed.h
new file mode 100644
index 0000000..93beb33
--- /dev/null
+++ b/net/http/http_proxy_resolver_fixed.h
@@ -0,0 +1,54 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_PROXY_RESOLVER_FIXED_H_
+#define NET_HTTP_HTTP_PROXY_RESOLVER_FIXED_H_
+
+#include "net/http/http_proxy_service.h"
+
+namespace net {
+
+// Implementation of HttpProxyResolver that returns a fixed result.
+class HttpProxyResolverFixed : public HttpProxyResolver {
+ public:
+ HttpProxyResolverFixed(const HttpProxyInfo& pi) { pi_.Use(pi); }
+
+ // HttpProxyResolver methods:
+ virtual int GetProxyConfig(HttpProxyConfig* config);
+ virtual int GetProxyForURL(const std::wstring& query_url,
+ const std::wstring& pac_url,
+ HttpProxyInfo* results);
+
+ private:
+ HttpProxyInfo pi_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PROXY_RESOLVER_FIXED_H_
diff --git a/net/http/http_proxy_resolver_winhttp.cc b/net/http/http_proxy_resolver_winhttp.cc
new file mode 100644
index 0000000..2393245
--- /dev/null
+++ b/net/http/http_proxy_resolver_winhttp.cc
@@ -0,0 +1,185 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_proxy_resolver_winhttp.h"
+
+#include <windows.h>
+#include <winhttp.h>
+
+#include "base/histogram.h"
+#include "net/base/net_errors.h"
+
+#pragma comment(lib, "winhttp.lib")
+
+namespace net {
+
+// A small wrapper for histogramming purposes ;-)
+static BOOL CallWinHttpGetProxyForUrl(HINTERNET session, LPCWSTR url,
+ WINHTTP_AUTOPROXY_OPTIONS* options,
+ WINHTTP_PROXY_INFO* results) {
+ TimeTicks time_start = TimeTicks::Now();
+ BOOL rv = WinHttpGetProxyForUrl(session, url, options, results);
+ TimeDelta time_delta = TimeTicks::Now() - time_start;
+ // Record separately success and failure times since they will have very
+ // different characteristics.
+ if (rv) {
+ UMA_HISTOGRAM_LONG_TIMES(L"Net.GetProxyForUrl_OK", time_delta);
+ } else {
+ UMA_HISTOGRAM_LONG_TIMES(L"Net.GetProxyForUrl_FAIL", time_delta);
+ }
+ return rv;
+}
+
+static void FreeConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* config) {
+ if (config->lpszAutoConfigUrl)
+ GlobalFree(config->lpszAutoConfigUrl);
+ if (config->lpszProxy)
+ GlobalFree(config->lpszProxy);
+ if (config->lpszProxyBypass)
+ GlobalFree(config->lpszProxyBypass);
+}
+
+static void FreeInfo(WINHTTP_PROXY_INFO* info) {
+ if (info->lpszProxy)
+ GlobalFree(info->lpszProxy);
+ if (info->lpszProxyBypass)
+ GlobalFree(info->lpszProxyBypass);
+}
+
+HttpProxyResolverWinHttp::HttpProxyResolverWinHttp()
+ : session_handle_(NULL) {
+}
+
+HttpProxyResolverWinHttp::~HttpProxyResolverWinHttp() {
+ CloseWinHttpSession();
+}
+
+int HttpProxyResolverWinHttp::GetProxyConfig(HttpProxyConfig* config) {
+ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = {0};
+ if (!WinHttpGetIEProxyConfigForCurrentUser(&ie_config)) {
+ LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser failed: " <<
+ GetLastError();
+ return ERR_FAILED; // TODO(darin): Bug 1189288: translate error code.
+ }
+
+ if (ie_config.fAutoDetect)
+ config->auto_detect = true;
+ if (ie_config.lpszProxy)
+ config->proxy_server = ie_config.lpszProxy;
+ if (ie_config.lpszProxyBypass)
+ config->proxy_bypass = ie_config.lpszProxyBypass;
+ if (ie_config.lpszAutoConfigUrl)
+ config->pac_url = ie_config.lpszAutoConfigUrl;
+
+ FreeConfig(&ie_config);
+ return OK;
+}
+
+int HttpProxyResolverWinHttp::GetProxyForURL(const std::wstring& query_url,
+ const std::wstring& pac_url,
+ HttpProxyInfo* results) {
+ // 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 = TRUE;
+ options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
+ options.lpszAutoConfigUrl =
+ pac_url.empty() ? L"http://wpad/wpad.dat" : pac_url.c_str();
+
+ WINHTTP_PROXY_INFO info = {0};
+ DCHECK(session_handle_);
+ if (!CallWinHttpGetProxyForUrl(session_handle_, query_url.c_str(), &options,
+ &info)) {
+ DWORD error = GetLastError();
+ LOG(ERROR) << "WinHttpGetProxyForUrl failed: " << error;
+
+ // 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:
+ results->UseNamedProxy(info.lpszProxy);
+ break;
+ default:
+ NOTREACHED();
+ rv = ERR_FAILED;
+ }
+
+ FreeInfo(&info);
+ return rv;
+}
+
+bool HttpProxyResolverWinHttp::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 HttpProxyResolverWinHttp::CloseWinHttpSession() {
+ if (session_handle_) {
+ WinHttpCloseHandle(session_handle_);
+ session_handle_ = NULL;
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_proxy_resolver_winhttp.h b/net/http/http_proxy_resolver_winhttp.h
new file mode 100644
index 0000000..bea2bda
--- /dev/null
+++ b/net/http/http_proxy_resolver_winhttp.h
@@ -0,0 +1,64 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_PROXY_RESOLVER_WINHTTP_H__
+#define NET_HTTP_HTTP_PROXY_RESOLVER_WINHTTP_H__
+
+#include "net/http/http_proxy_service.h"
+
+typedef LPVOID HINTERNET; // From winhttp.h
+
+namespace net {
+
+// An implementation of HttpProxyResolver that uses WinHTTP and the system
+// proxy settings.
+class HttpProxyResolverWinHttp : public HttpProxyResolver {
+ public:
+ HttpProxyResolverWinHttp();
+ ~HttpProxyResolverWinHttp();
+
+ // HttpProxyResolver implementation:
+ virtual int GetProxyConfig(HttpProxyConfig* config);
+ virtual int GetProxyForURL(const std::wstring& query_url,
+ const std::wstring& pac_url,
+ HttpProxyInfo* results);
+
+ private:
+ bool OpenWinHttpSession();
+ void CloseWinHttpSession();
+
+ // Proxy configuration is cached on the session handle.
+ HINTERNET session_handle_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HttpProxyResolverWinHttp);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PROXY_RESOLVER_WINHTTP_H__
diff --git a/net/http/http_proxy_service.cc b/net/http/http_proxy_service.cc
new file mode 100644
index 0000000..d851a6d
--- /dev/null
+++ b/net/http/http_proxy_service.cc
@@ -0,0 +1,496 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_proxy_service.h"
+
+#include <windows.h>
+#include <winhttp.h>
+
+#include <algorithm>
+
+#include "base/message_loop.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// HttpProxyConfig ------------------------------------------------------------
+
+// static
+HttpProxyConfig::ID HttpProxyConfig::last_id_ = HttpProxyConfig::INVALID_ID;
+
+HttpProxyConfig::HttpProxyConfig()
+ : auto_detect(false),
+ id_(++last_id_) {
+}
+
+bool HttpProxyConfig::Equals(const HttpProxyConfig& other) const {
+ // The two configs can have different IDs. We are just interested in if they
+ // have the same settings.
+ return auto_detect == other.auto_detect &&
+ pac_url == other.pac_url &&
+ proxy_server == other.proxy_server &&
+ proxy_bypass == other.proxy_bypass;
+}
+
+// HttpProxyList --------------------------------------------------------------
+void HttpProxyList::SetVector(const std::vector<std::wstring>& proxies) {
+ proxies_.clear();
+ std::vector<std::wstring>::const_iterator iter = proxies.begin();
+ for (; iter != proxies.end(); ++iter) {
+ std::wstring proxy_sever;
+ TrimWhitespace(*iter, TRIM_ALL, &proxy_sever);
+ proxies_.push_back(proxy_sever);
+ }
+}
+
+void HttpProxyList::Set(const std::wstring& proxy_list) {
+ // Extract the different proxies from the list.
+ std::vector<std::wstring> proxies;
+ SplitString(proxy_list, L';', &proxies);
+ SetVector(proxies);
+}
+
+void HttpProxyList::RemoveBadProxies(const HttpProxyRetryInfoMap&
+ http_proxy_retry_info) {
+ std::vector<std::wstring> new_proxy_list;
+ std::vector<std::wstring>::const_iterator iter = proxies_.begin();
+ for (; iter != proxies_.end(); ++iter) {
+ HttpProxyRetryInfoMap::const_iterator bad_proxy =
+ http_proxy_retry_info.find(*iter);
+ if (bad_proxy != http_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.
+ continue;
+ }
+ }
+ new_proxy_list.push_back(*iter);
+ }
+
+ proxies_ = new_proxy_list;
+}
+
+std::wstring HttpProxyList::Get() const {
+ if (!proxies_.empty())
+ return proxies_[0];
+
+ return std::wstring();
+}
+
+const std::vector<std::wstring>& HttpProxyList::GetVector() const {
+ return proxies_;
+}
+
+std::wstring HttpProxyList::GetList() const {
+ std::wstring proxy_list;
+ std::vector<std::wstring>::const_iterator iter = proxies_.begin();
+ for (; iter != proxies_.end(); ++iter) {
+ if (!proxy_list.empty())
+ proxy_list += L';';
+
+ proxy_list += *iter;
+ }
+
+ return proxy_list;
+}
+
+bool HttpProxyList::Fallback(HttpProxyRetryInfoMap* http_proxy_retry_info) {
+ // Number of minutes to wait before retrying a bad proxy server.
+ const TimeDelta kProxyRetryDelay = TimeDelta::FromMinutes(5);
+
+ if (proxies_.empty()) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Mark this proxy as bad.
+ HttpProxyRetryInfoMap::iterator iter =
+ http_proxy_retry_info->find(proxies_[0]);
+ if (iter != http_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 {
+ HttpProxyRetryInfo retry_info;
+ retry_info.current_delay = kProxyRetryDelay;
+ retry_info.bad_until = TimeTicks().Now() + retry_info.current_delay;
+ (*http_proxy_retry_info)[proxies_[0]] = retry_info;
+ }
+
+ // Remove this proxy from our list.
+ proxies_.erase(proxies_.begin());
+
+ return !proxies_.empty();
+}
+
+// HttpProxyInfo --------------------------------------------------------------
+
+HttpProxyInfo::HttpProxyInfo()
+ : config_id_(HttpProxyConfig::INVALID_ID),
+ config_was_tried_(false) {
+}
+
+void HttpProxyInfo::Use(const HttpProxyInfo& other) {
+ proxy_list_.SetVector(other.proxy_list_.GetVector());
+}
+
+void HttpProxyInfo::UseDirect() {
+ proxy_list_.Set(std::wstring());
+}
+
+void HttpProxyInfo::UseNamedProxy(const std::wstring& proxy_server) {
+ proxy_list_.Set(proxy_server);
+}
+
+void HttpProxyInfo::Apply(HINTERNET request_handle) {
+ WINHTTP_PROXY_INFO pi;
+ std::wstring proxy; // We need to declare this variable here because
+ // lpszProxy needs to be valid in WinHttpSetOption.
+ if (is_direct()) {
+ pi.dwAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY;
+ pi.lpszProxy = WINHTTP_NO_PROXY_NAME;
+ pi.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS;
+ } else {
+ proxy = proxy_list_.Get();
+ pi.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ pi.lpszProxy = const_cast<LPWSTR>(proxy.c_str());
+ // NOTE: Specifying a bypass list here would serve no purpose.
+ pi.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS;
+ }
+ WinHttpSetOption(request_handle, WINHTTP_OPTION_PROXY, &pi, sizeof(pi));
+}
+
+// HttpProxyService::PacRequest -----------------------------------------------
+
+// We rely on the fact that the origin thread (and its message loop) will not
+// be destroyed until after the PAC thread is destroyed.
+
+class HttpProxyService::PacRequest :
+ public base::RefCountedThreadSafe<HttpProxyService::PacRequest> {
+ public:
+ PacRequest(HttpProxyService* service,
+ const std::wstring& pac_url,
+ CompletionCallback* callback)
+ : service_(service),
+ callback_(callback),
+ results_(NULL),
+ config_id_(service->config_id()),
+ pac_url_(pac_url),
+ origin_loop_(NULL) {
+ // We need to remember original loop if only in case of asynchronous call
+ if (callback_)
+ origin_loop_ = MessageLoop::current();
+ }
+
+ void Query(const std::wstring& url, HttpProxyInfo* results) {
+ results_ = results;
+ // If we have a valid callback then execute Query asynchronously
+ if (callback_) {
+ AddRef(); // balanced in QueryComplete
+ service_->pac_thread()->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &HttpProxyService::PacRequest::DoQuery,
+ service_->resolver(),
+ url,
+ pac_url_));
+ } else {
+ DoQuery(service_->resolver(), url, pac_url_);
+ }
+ }
+
+ void Cancel() {
+ // Clear these to inform QueryComplete that it should not try to
+ // access them.
+ service_ = NULL;
+ callback_ = NULL;
+ results_ = NULL;
+ }
+
+ private:
+ // Runs on the PAC thread if a valid callback is provided.
+ void DoQuery(HttpProxyResolver* resolver,
+ const std::wstring& query_url,
+ const std::wstring& pac_url) {
+ int rv = resolver->GetProxyForURL(query_url, pac_url, &results_buf_);
+ if (origin_loop_) {
+ origin_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &PacRequest::QueryComplete, rv));
+ } else {
+ QueryComplete(rv);
+ }
+ }
+
+ // If a valid callback is provided, this runs on the origin thread to
+ // indicate that the completion callback should be run.
+ void QueryComplete(int result_code) {
+ if (service_)
+ service_->DidCompletePacRequest(config_id_, result_code);
+
+ if (result_code == OK && results_) {
+ results_->Use(results_buf_);
+ results_->RemoveBadProxies(service_->http_proxy_retry_info_);
+ }
+
+ if (callback_)
+ callback_->Run(result_code);
+
+ if (origin_loop_) {
+ Release(); // balances the AddRef in Query. we may get deleted after
+ // we return.
+ }
+ }
+
+ // Must only be used on the "origin" thread.
+ HttpProxyService* service_;
+ CompletionCallback* callback_;
+ HttpProxyInfo* results_;
+ HttpProxyConfig::ID config_id_;
+
+ // Usable from within DoQuery on the PAC thread.
+ HttpProxyInfo results_buf_;
+ std::wstring pac_url_;
+ MessageLoop* origin_loop_;
+};
+
+// HttpProxyService -----------------------------------------------------------
+
+HttpProxyService::HttpProxyService(HttpProxyResolver* resolver)
+ : resolver_(resolver),
+ config_is_bad_(false) {
+ UpdateConfig();
+}
+
+int HttpProxyService::ResolveProxy(const GURL& url, HttpProxyInfo* result,
+ CompletionCallback* callback,
+ PacRequest** pac_request) {
+ // The overhead of calling WinHttpGetIEProxyConfigForCurrentUser is very low.
+ const TimeDelta kProxyConfigMaxAge = TimeDelta::FromSeconds(5);
+
+ // Periodically check for a new config.
+ if ((TimeTicks::Now() - config_last_update_time_) > kProxyConfigMaxAge)
+ UpdateConfig();
+ result->config_id_ = config_.id();
+
+ // Fallback to a "direct" (no proxy) connection if the current configuration
+ // is known to be bad.
+ if (config_is_bad_) {
+ // Reset this flag to false in case the HttpProxyInfo object is being
+ // re-used by the caller.
+ result->config_was_tried_ = false;
+ } else {
+ // Remember that we are trying to use the current proxy configuration.
+ result->config_was_tried_ = true;
+
+ if (!config_.proxy_server.empty()) {
+ if (ShouldBypassProxyForURL(url)) {
+ result->UseDirect();
+ } else {
+ // If proxies are specified on a per protocol basis, the proxy server
+ // field contains a list the format of which is as below:-
+ // "scheme1=url:port;scheme2=url:port", etc.
+ std::wstring url_scheme = ASCIIToWide(url.scheme());
+
+ WStringTokenizer proxy_server_list(config_.proxy_server, L";");
+ while (proxy_server_list.GetNext()) {
+ WStringTokenizer proxy_server_for_scheme(
+ proxy_server_list.token_begin(), proxy_server_list.token_end(),
+ L"=");
+
+ while (proxy_server_for_scheme.GetNext()) {
+ const std::wstring& proxy_server_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()) {
+ result->UseNamedProxy(proxy_server_scheme);
+ return OK;
+ }
+
+ if (proxy_server_scheme == url_scheme) {
+ result->UseNamedProxy(proxy_server_for_scheme.token());
+ return OK;
+ }
+ }
+ }
+ // We failed to find a matching proxy server for the current URL
+ // scheme. Default to direct.
+ result->UseDirect();
+ }
+ return OK;
+ }
+
+ if (!config_.pac_url.empty() || config_.auto_detect) {
+ if (callback) {
+ // Create PAC thread for asynchronous mode.
+ if (!pac_thread_.get()) {
+ pac_thread_.reset(new Thread("pac-thread"));
+ pac_thread_->Start();
+ }
+ } else {
+ // If this request is synchronous, then there's no point
+ // in returning PacRequest instance
+ DCHECK(!pac_request);
+ }
+
+ scoped_refptr<PacRequest> req =
+ new PacRequest(this, config_.pac_url, callback);
+ req->Query(UTF8ToWide(url.spec()), result);
+
+ if (callback) {
+ if (pac_request)
+ *pac_request = req;
+ return ERR_IO_PENDING; // Wait for callback.
+ }
+ return OK;
+ }
+ }
+
+ // otherwise, we have no proxy config
+ result->UseDirect();
+ return OK;
+}
+
+int HttpProxyService::ReconsiderProxyAfterError(const GURL& url,
+ HttpProxyInfo* result,
+ CompletionCallback* callback,
+ PacRequest** pac_request) {
+ bool was_direct = result->is_direct();
+ if (!was_direct && result->Fallback(&http_proxy_retry_info_))
+ return OK;
+
+ // 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) {
+ UpdateConfig();
+ if (result->config_id_ != config_.id()) {
+ // A new configuration!
+ re_resolve = true;
+ } else if (!result->config_was_tried_) {
+ // We never tried the proxy configuration since we thought it was bad,
+ // but because we failed to establish a connection, let's try the proxy
+ // configuration again to see if it will work now.
+ config_is_bad_ = false;
+ re_resolve = true;
+ }
+ }
+ if (re_resolve)
+ return ResolveProxy(url, result, callback, pac_request);
+
+ if (!config_.auto_detect && !config_.proxy_server.empty()) {
+ // If auto detect is on, then we should try a DIRECT connection
+ // as the attempt to reach the proxy failed.
+ return ERR_FAILED;
+ }
+
+ // If we already tried a direct connection, then just give up.
+ if (was_direct)
+ return ERR_FAILED;
+
+ // Try going direct.
+ result->UseDirect();
+ return OK;
+}
+
+void HttpProxyService::CancelPacRequest(PacRequest* pac_request) {
+ pac_request->Cancel();
+}
+
+void HttpProxyService::DidCompletePacRequest(int config_id, int result_code) {
+ // If we get an error that indicates a bad PAC config, then we should
+ // remember that, and not try the PAC config again for a while.
+
+ // Our config may have already changed.
+ if (result_code == OK || config_id != config_.id())
+ return;
+
+ // Remember that this configuration doesn't work.
+ config_is_bad_ = true;
+}
+
+void HttpProxyService::UpdateConfig() {
+ HttpProxyConfig latest;
+ if (resolver_->GetProxyConfig(&latest) != OK)
+ return;
+ config_last_update_time_ = TimeTicks::Now();
+
+ if (latest.Equals(config_))
+ return;
+
+ config_ = latest;
+ config_is_bad_ = false;
+}
+
+bool HttpProxyService::ShouldBypassProxyForURL(const GURL& url) {
+ std::wstring url_domain = ASCIIToWide(url.scheme());
+ if (!url_domain.empty())
+ url_domain += L"://";
+
+ url_domain += ASCIIToWide(url.host());
+ StringToLowerASCII(url_domain);
+
+ WStringTokenizer proxy_server_bypass_list(config_.proxy_bypass, L";");
+ while (proxy_server_bypass_list.GetNext()) {
+ std::wstring bypass_url_domain = proxy_server_bypass_list.token();
+ if (bypass_url_domain == L"<local>") {
+ // Any name without a DOT (.) is considered to be local.
+ if (url.host().find(L'.') == std::wstring::npos)
+ return true;
+ continue;
+ }
+
+ // The proxy server bypass list can contain entities with http/https
+ // If no scheme is specified then it indicates that all schemes are
+ // allowed for the current entry. For matching this we just use
+ // the protocol scheme of the url passed in.
+ if (bypass_url_domain.find(L"://") == std::wstring::npos) {
+ std::wstring bypass_url_domain_with_scheme = ASCIIToWide(url.scheme());
+ bypass_url_domain_with_scheme += L"://";
+ bypass_url_domain_with_scheme += bypass_url_domain;
+
+ bypass_url_domain = bypass_url_domain_with_scheme;
+ }
+
+ StringToLowerASCII(bypass_url_domain);
+
+ if (MatchPattern(url_domain, bypass_url_domain))
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace net
diff --git a/net/http/http_proxy_service.h b/net/http/http_proxy_service.h
new file mode 100644
index 0000000..d1ca955
--- /dev/null
+++ b/net/http/http_proxy_service.h
@@ -0,0 +1,303 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_PROXY_SERVICE_H__
+#define NET_HTTP_HTTP_PROXY_SERVICE_H__
+
+#include <map>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/thread.h"
+#include "base/time.h"
+#include "net/base/completion_callback.h"
+
+typedef LPVOID HINTERNET; // From winhttp.h
+
+class GURL;
+
+namespace net {
+
+class HttpProxyInfo;
+class HttpProxyResolver;
+
+// Proxy configuration used to by the HttpProxyService.
+class HttpProxyConfig {
+ public:
+ typedef int ID;
+
+ // Indicates an invalid proxy config.
+ enum { INVALID_ID = 0 };
+
+ HttpProxyConfig();
+ // Default copy-constructor an assignment operator are OK!
+
+ // Used to numerically identify this configuration.
+ ID id() const { return id_; }
+
+ // 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.
+ std::wstring pac_url;
+
+ // If non-empty, indicates the proxy server to use (of the form host:port).
+ std::wstring proxy_server;
+
+ // If non-empty, indicates a comma-delimited list of hosts that should bypass
+ // any proxy configuration. For these hosts, a direct connection should
+ // always be used.
+ std::wstring proxy_bypass;
+
+ // Returns true if the given config is equivalent to this config.
+ bool Equals(const HttpProxyConfig& other) const;
+
+ private:
+ static int last_id_;
+ int id_;
+};
+
+// Contains the information about when to retry a proxy server.
+struct HttpProxyRetryInfo {
+ // We should not retry until this time.
+ TimeTicks bad_until;
+
+ // This is the current delay. If the proxy is still bad, we need to increase
+ // this delay.
+ TimeDelta current_delay;
+};
+
+// Map of proxy servers with the associated RetryInfo structures.
+typedef std::map<std::wstring, HttpProxyRetryInfo> HttpProxyRetryInfoMap;
+
+// This class can be used to resolve the proxy server to use when loading a
+// HTTP(S) URL. It uses to the given HttpProxyResolver to handle the actual
+// proxy resolution. See HttpProxyResolverWinHttp for example. The consumer
+// of this class is responsible for ensuring that the HttpProxyResolver
+// instance remains valid for the lifetime of the HttpProxyService.
+class HttpProxyService {
+ public:
+ explicit HttpProxyService(HttpProxyResolver* resolver);
+
+ // Used internally to handle PAC queries.
+ class PacRequest;
+
+ // Returns OK if proxy information could be provided synchronously. Else,
+ // ERR_IO_PENDING is returned 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.
+ //
+ // We use the three possible proxy access types in the following order, and
+ // we only use one of them (no falling back to other access types if the
+ // chosen one doesn't work).
+ // 1. named proxy
+ // 2. PAC URL
+ // 3. WPAD auto-detection
+ //
+ int ResolveProxy(const GURL& url,
+ HttpProxyInfo* results,
+ CompletionCallback* callback,
+ PacRequest** pac_request);
+
+ // 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.
+ //
+ // Returns ERR_FAILED if there is not another proxy config to try.
+ //
+ int ReconsiderProxyAfterError(const GURL& url,
+ HttpProxyInfo* results,
+ CompletionCallback* callback,
+ PacRequest** pac_request);
+
+ // Call this method with a non-null |pac_request| to cancel the PAC request.
+ void CancelPacRequest(PacRequest* pac_request);
+
+ private:
+ friend class PacRequest;
+
+ HttpProxyResolver* resolver() { return resolver_; }
+ Thread* pac_thread() { return pac_thread_.get(); }
+
+ // Identifies the proxy configuration.
+ HttpProxyConfig::ID config_id() const { return config_.id(); }
+
+ // Checks to see if the proxy configuration changed, and then updates config_
+ // to reference the new configuration.
+ void UpdateConfig();
+
+ // Called to indicate that a PacRequest completed. The |config_id| parameter
+ // indicates the proxy configuration that was queried. |result_code| is OK
+ // if the PAC file could be downloaded and executed. Otherwise, it is an
+ // error code, indicating a bad proxy configuration.
+ void DidCompletePacRequest(int config_id, int result_code);
+
+ // Returns true if the URL passed in should not go through the proxy server.
+ // 1. If the bypass proxy list contains the string <local> and the URL
+ // passed in is a local URL, i.e. a URL without a DOT (.)
+ // 2. The URL matches one of the entities in the proxy bypass list.
+ bool ShouldBypassProxyForURL(const GURL& url);
+
+ HttpProxyResolver* resolver_;
+ scoped_ptr<Thread> pac_thread_;
+
+ // We store the IE proxy config and a counter that is incremented each time
+ // the config changes.
+ HttpProxyConfig config_;
+
+ // Indicates that the configuration is bad and should be ignored.
+ bool config_is_bad_;
+
+ // The time when the proxy configuration was last read from the system.
+ TimeTicks config_last_update_time_;
+
+ // Map of the known bad proxies and the information about the retry time.
+ HttpProxyRetryInfoMap http_proxy_retry_info_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HttpProxyService);
+};
+
+// 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 HttpProxyList {
+ public:
+ // Initializes the proxy list to a string containing one or more proxy servers
+ // delimited by a semicolon.
+ void Set(const std::wstring& proxy_list);
+
+ // Initializes the proxy list to a vector containing one or more proxy
+ // servers.
+ void SetVector(const std::vector<std::wstring>& proxy_list);
+
+ // Remove all proxies known to be bad from the proxy list.
+ void RemoveBadProxies(const HttpProxyRetryInfoMap& http_proxy_retry_info);
+
+ // Returns the first valid proxy server in the list.
+ std::wstring Get() const;
+
+ // Returns all the valid proxies, delimited by a semicolon.
+ std::wstring GetList() const;
+
+ // Returns all the valid proxies in a vector.
+ const std::vector<std::wstring>& GetVector() const;
+
+ // Marks the current proxy server as bad and deletes it from the list.
+ // The list of known bad proxies is given by http_proxy_retry_info.
+ // Returns true if there is another server available in the list.
+ bool Fallback(HttpProxyRetryInfoMap* http_proxy_retry_info);
+
+ private:
+ // List of proxies.
+ std::vector<std::wstring> proxies_;
+};
+
+// This object holds proxy information returned by ResolveProxy.
+class HttpProxyInfo {
+ public:
+ HttpProxyInfo();
+
+ // Use the same proxy server as the given |proxy_info|.
+ void Use(const HttpProxyInfo& proxy_info);
+
+ // Use a direct connection.
+ void UseDirect();
+
+ // Use a specific proxy server, of the form: <hostname> [":" <port>]
+ // This may optionally be a semi-colon delimited list of proxy servers.
+ void UseNamedProxy(const std::wstring& proxy_server);
+
+ // Apply this proxy information to the given WinHTTP request handle.
+ void Apply(HINTERNET request_handle);
+
+ // Returns true if this proxy info specifies a direct connection.
+ bool is_direct() const { return proxy_list_.Get().empty(); }
+
+ // Returns the first valid proxy server.
+ const std::wstring proxy_server() const { return proxy_list_.Get(); }
+
+ // Marks the current proxy as bad. Returns true if there is another proxy
+ // available to try in proxy list_.
+ bool Fallback(HttpProxyRetryInfoMap* http_proxy_retry_info) {
+ return proxy_list_.Fallback(http_proxy_retry_info);
+ }
+
+ // Remove all proxies known to be bad from the proxy list.
+ void RemoveBadProxies(const HttpProxyRetryInfoMap& http_proxy_retry_info) {
+ proxy_list_.RemoveBadProxies(http_proxy_retry_info);
+ }
+
+ private:
+ friend class HttpProxyService;
+
+ // If proxy_list_ is set to empty, then a "direct" connection is indicated.
+ HttpProxyList proxy_list_;
+
+ // This value identifies the proxy config used to initialize this object.
+ HttpProxyConfig::ID config_id_;
+
+ // This flag is false when the proxy configuration was known to be bad when
+ // this proxy info was initialized. In such cases, we know that if this
+ // proxy info does not yield a connection that we might want to reconsider
+ // the proxy config given by config_id_.
+ bool config_was_tried_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HttpProxyInfo);
+};
+
+// This interface provides the low-level functions to access the proxy
+// configuration and resolve proxies for given URLs synchronously.
+class HttpProxyResolver {
+ public:
+ virtual ~HttpProxyResolver() {}
+
+ // Get the proxy configuration. Returns OK if successful or an error code if
+ // otherwise. |config| should be in its initial state when this method is
+ // called.
+ virtual int GetProxyConfig(HttpProxyConfig* config) = 0;
+
+ // Query the proxy auto-config file (specified by |pac_url|) for the proxy to
+ // use to load the given |query_url|. Returns OK if successful or an error
+ // code if otherwise.
+ virtual int GetProxyForURL(const std::wstring& query_url,
+ const std::wstring& pac_url,
+ HttpProxyInfo* results) = 0;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PROXY_SERVICE_H__
diff --git a/net/http/http_proxy_service_unittest.cc b/net/http/http_proxy_service_unittest.cc
new file mode 100644
index 0000000..c047d9c
--- /dev/null
+++ b/net/http/http_proxy_service_unittest.cc
@@ -0,0 +1,299 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_proxy_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class MockProxyResolver : public net::HttpProxyResolver {
+ public:
+ MockProxyResolver() : fail_get_proxy_for_url(false) {
+ }
+ virtual int GetProxyConfig(net::HttpProxyConfig* results) {
+ *results = config;
+ return net::OK;
+ }
+ virtual int GetProxyForURL(const std::wstring& query_url,
+ const std::wstring& pac_url,
+ net::HttpProxyInfo* results) {
+ if (pac_url != config.pac_url)
+ return net::ERR_INVALID_ARGUMENT;
+ if (fail_get_proxy_for_url)
+ return net::ERR_FAILED;
+ if (GURL(query_url).host() == info_predicate_query_host) {
+ results->Use(info);
+ } else {
+ results->UseDirect();
+ }
+ return net::OK;
+ }
+ net::HttpProxyConfig config;
+ net::HttpProxyInfo info;
+
+ // info is only returned if query_url in GetProxyForURL matches this:
+ std::string info_predicate_query_host;
+
+ // If true, then GetProxyForURL will fail, which simulates failure to
+ // download or execute the PAC file.
+ bool fail_get_proxy_for_url;
+};
+
+} // namespace
+
+TEST(HttpProxyServiceTest, Direct) {
+ MockProxyResolver resolver;
+ net::HttpProxyService service(&resolver);
+
+ GURL url("http://www.google.com/");
+
+ net::HttpProxyInfo info;
+ int rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info.is_direct());
+}
+
+TEST(HttpProxyServiceTest, PAC) {
+ MockProxyResolver resolver;
+ resolver.config.pac_url = L"http://foopy/proxy.pac";
+ resolver.info.UseNamedProxy(L"foopy");
+ resolver.info_predicate_query_host = "www.google.com";
+
+ net::HttpProxyService service(&resolver);
+
+ GURL url("http://www.google.com/");
+
+ net::HttpProxyInfo info;
+ int rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ(info.proxy_server(), L"foopy");
+}
+
+TEST(HttpProxyServiceTest, PAC_FailoverToDirect) {
+ MockProxyResolver resolver;
+ resolver.config.pac_url = L"http://foopy/proxy.pac";
+ resolver.info.UseNamedProxy(L"foopy:8080");
+ resolver.info_predicate_query_host = "www.google.com";
+
+ net::HttpProxyService service(&resolver);
+
+ GURL url("http://www.google.com/");
+
+ net::HttpProxyInfo info;
+ int rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ(info.proxy_server(), L"foopy:8080");
+
+ // Now, imagine that connecting to foopy:8080 fails.
+ rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info.is_direct());
+}
+
+TEST(HttpProxyServiceTest, PAC_FailsToDownload) {
+ // Test what happens when we fail to download the PAC URL.
+
+ MockProxyResolver resolver;
+ resolver.config.pac_url = L"http://foopy/proxy.pac";
+ resolver.info.UseNamedProxy(L"foopy:8080");
+ resolver.info_predicate_query_host = "www.google.com";
+ resolver.fail_get_proxy_for_url = true;
+
+ net::HttpProxyService service(&resolver);
+
+ GURL url("http://www.google.com/");
+ net::HttpProxyInfo info;
+ int rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info.is_direct());
+
+ rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info.is_direct());
+
+ resolver.fail_get_proxy_for_url = false;
+ resolver.info.UseNamedProxy(L"foopy_valid:8080");
+
+ // But, if that fails, then we should give the proxy config another shot
+ // since we have never tried it with this URL before.
+ rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ(info.proxy_server(), L"foopy_valid:8080");
+}
+
+TEST(HttpProxyServiceTest, ProxyFallback) {
+ // Test what happens when we specify multiple proxy servers and some of them
+ // are bad.
+
+ MockProxyResolver resolver;
+ resolver.config.pac_url = L"http://foopy/proxy.pac";
+ resolver.info.UseNamedProxy(L"foopy1:8080;foopy2:9090");
+ resolver.info_predicate_query_host = "www.google.com";
+ resolver.fail_get_proxy_for_url = false;
+
+ net::HttpProxyService service(&resolver);
+
+ GURL url("http://www.google.com/");
+
+ // Get the proxy information.
+ net::HttpProxyInfo info;
+ int rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info.is_direct());
+
+ // The first item is valid.
+ EXPECT_EQ(info.proxy_server(), L"foopy1:8080");
+
+ // Fake an error on the proxy.
+ rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+
+ // The second proxy should be specified.
+ EXPECT_EQ(info.proxy_server(), L"foopy2:9090");
+
+ // Create a new resolver that returns 3 proxies. The second one is already
+ // known to be bad.
+ resolver.config.pac_url = L"http://foopy/proxy.pac";
+ resolver.info.UseNamedProxy(L"foopy3:7070;foopy1:8080;foopy2:9090");
+ resolver.info_predicate_query_host = "www.google.com";
+ resolver.fail_get_proxy_for_url = false;
+
+ rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ(info.proxy_server(), L"foopy3:7070");
+
+ // We fake another error. It should now try the third one.
+ rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_EQ(info.proxy_server(), L"foopy2:9090");
+
+ // Fake another error, the last proxy is gone, the list should now be empty.
+ rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK); // We try direct.
+ EXPECT_TRUE(info.is_direct());
+
+ // If it fails again, we don't have anything else to try.
+ rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::ERR_FAILED); // We try direct.
+
+ // TODO(nsylvain): Test that the proxy can be retried after the delay.
+}
+
+TEST(HttpProxyServiceTest, ProxyBypassList) {
+ // Test what happens when a proxy bypass list is specified.
+
+ MockProxyResolver resolver;
+ resolver.config.proxy_server = L"foopy1:8080;foopy2:9090";
+ resolver.config.auto_detect = false;
+ resolver.config.proxy_bypass = L"<local>";
+
+ net::HttpProxyService service(&resolver);
+ GURL url("http://www.google.com/");
+ // Get the proxy information.
+ net::HttpProxyInfo info;
+ int rv = service.ResolveProxy(url, &info, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info.is_direct());
+
+ net::HttpProxyService service1(&resolver);
+ GURL test_url1("local");
+ net::HttpProxyInfo info1;
+ rv = service1.ResolveProxy(test_url1, &info1, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info1.is_direct());
+
+ resolver.config.proxy_bypass = L"<local>;*.org";
+ net::HttpProxyService service2(&resolver);
+ GURL test_url2("http://www.webkit.org");
+ net::HttpProxyInfo info2;
+ rv = service2.ResolveProxy(test_url2, &info2, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info2.is_direct());
+
+ resolver.config.proxy_bypass = L"<local>;*.org;7*";
+ net::HttpProxyService service3(&resolver);
+ GURL test_url3("http://74.125.19.147");
+ net::HttpProxyInfo info3;
+ rv = service3.ResolveProxy(test_url3, &info3, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info3.is_direct());
+
+ resolver.config.proxy_bypass = L"<local>;*.org;";
+ net::HttpProxyService service4(&resolver);
+ GURL test_url4("http://www.msn.com");
+ net::HttpProxyInfo info4;
+ rv = service4.ResolveProxy(test_url4, &info4, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info4.is_direct());
+}
+
+TEST(HttpProxyServiceTest, PerProtocolProxyTests) {
+ MockProxyResolver resolver;
+ resolver.config.proxy_server = L"http=foopy1:8080;https=foopy2:8080";
+ resolver.config.auto_detect = false;
+
+ net::HttpProxyService service1(&resolver);
+ GURL test_url1("http://www.msn.com");
+ net::HttpProxyInfo info1;
+ int rv = service1.ResolveProxy(test_url1, &info1, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info1.is_direct());
+ EXPECT_TRUE(info1.proxy_server() == L"foopy1:8080");
+
+ net::HttpProxyService service2(&resolver);
+ GURL test_url2("ftp://ftp.google.com");
+ net::HttpProxyInfo info2;
+ rv = service2.ResolveProxy(test_url2, &info2, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_TRUE(info2.is_direct());
+ EXPECT_TRUE(info2.proxy_server() == L"");
+
+ net::HttpProxyService service3(&resolver);
+ GURL test_url3("https://webbranch.techcu.com");
+ net::HttpProxyInfo info3;
+ rv = service3.ResolveProxy(test_url3, &info3, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info3.is_direct());
+ EXPECT_TRUE(info3.proxy_server() == L"foopy2:8080");
+
+ resolver.config.proxy_server = L"foopy1:8080";
+ net::HttpProxyService service4(&resolver);
+ GURL test_url4("www.microsoft.com");
+ net::HttpProxyInfo info4;
+ rv = service4.ResolveProxy(test_url4, &info4, NULL, NULL);
+ EXPECT_EQ(rv, net::OK);
+ EXPECT_FALSE(info4.is_direct());
+ EXPECT_TRUE(info4.proxy_server() == L"foopy1:8080");
+} \ No newline at end of file
diff --git a/net/http/http_request_info.h b/net/http/http_request_info.h
new file mode 100644
index 0000000..c0099c6
--- /dev/null
+++ b/net/http/http_request_info.h
@@ -0,0 +1,66 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_REQUEST_INFO_H__
+#define NET_HTTP_HTTP_REQUEST_INFO_H__
+
+#include "base/ref_counted.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/upload_data.h"
+
+namespace net {
+
+class HttpRequestInfo {
+ public:
+ // The requested URL.
+ GURL url;
+
+ // The referring URL (if any).
+ GURL referrer;
+
+ // The method to use (GET, POST, etc.).
+ std::string method;
+
+ // The user agent string to use. TODO(darin): we should just add this to
+ // extra_headers
+ std::string user_agent;
+
+ // Any extra request headers (\r\n-delimited).
+ std::string extra_headers;
+
+ // Any upload data.
+ scoped_refptr<UploadData> upload_data;
+
+ // Any load flags (see load_flags.h).
+ int load_flags;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_REQUEST_INFO_H__
diff --git a/net/http/http_response_headers.cc b/net/http/http_response_headers.cc
new file mode 100644
index 0000000..38e2fe2
--- /dev/null
+++ b/net/http/http_response_headers.cc
@@ -0,0 +1,936 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 <hash_map>
+
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "net/base/escape.h"
+#include "net/http/http_util.h"
+
+using std::string;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+// These response headers are not persisted in a cached representation of the
+// response headers. (This list comes from Mozilla's nsHttpResponseHead.cpp)
+const char* kTransientHeaders[] = {
+ "connection",
+ "proxy-connection",
+ "keep-alive",
+ "www-authenticate",
+ "proxy-authenticate",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "set-cookie",
+ "set-cookie2"
+};
+
+// These respones 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* kNonUpdatedHeaders[] = {
+ "connection",
+ "proxy-connection",
+ "keep-alive",
+ "www-authenticate",
+ "proxy-authenticate",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ // these should never change:
+ "content-location",
+ "content-md5",
+ "etag",
+ // assume cache-control: no-transform
+ "content-encoding",
+ "content-range",
+ "content-type",
+ // some broken microsoft servers send 'content-length: 0' with 304s
+ "content-length"
+};
+
+bool ShouldUpdateHeader(const string::const_iterator& name_begin,
+ const string::const_iterator& name_end) {
+ for (size_t i = 0; i < arraysize(kNonUpdatedHeaders); ++i) {
+ if (LowerCaseEqualsASCII(name_begin, name_end, kNonUpdatedHeaders[i]))
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+HttpResponseHeaders::HttpResponseHeaders(const string& raw_input)
+ : response_code_(-1) {
+ Parse(raw_input);
+}
+
+HttpResponseHeaders::HttpResponseHeaders(const Pickle& pickle, void** iter)
+ : response_code_(-1) {
+ string raw_input;
+ if (pickle.ReadString(iter, &raw_input))
+ Parse(raw_input);
+}
+
+void HttpResponseHeaders::Persist(Pickle* pickle, bool for_cache) {
+ if (for_cache) {
+ HeaderSet transient_headers;
+ GetTransientHeaders(&transient_headers);
+
+ std::string blob;
+ blob.reserve(raw_headers_.size());
+
+ // this just copies the status line w/ terminator
+ 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;
+
+ string header_name(parsed_[i].name_begin, parsed_[i].name_end);
+ StringToLowerASCII(&header_name);
+
+ if (transient_headers.find(header_name) == transient_headers.end()) {
+ // includes terminator
+ blob.append(parsed_[i].name_begin, parsed_[k].value_end + 1);
+ }
+
+ i = k;
+ }
+ blob.push_back('\0');
+
+ pickle->WriteString(blob);
+ } else {
+ pickle->WriteString(raw_headers_);
+ }
+}
+
+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.
+ 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 string::const_iterator& name_begin = new_parsed[i].name_begin;
+ const string::const_iterator& name_end = new_parsed[i].name_end;
+ if (ShouldUpdateHeader(name_begin, name_end)) {
+ string name(name_begin, name_end);
+ StringToLowerASCII(&name);
+ updated_headers.insert(name);
+
+ // preserve this header line in the merged result (including trailing '\0')
+ new_raw_headers.append(name_begin, new_parsed[k].value_end + 1);
+ }
+
+ i = k;
+ }
+
+ // now, build the new 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;
+
+ string name(parsed_[i].name_begin, parsed_[i].name_end);
+ StringToLowerASCII(&name);
+ if (updated_headers.find(name) == updated_headers.end()) {
+ // ok to preserve this header in the final result
+ new_raw_headers.append(parsed_[i].name_begin, parsed_[k].value_end + 1);
+ }
+
+ i = k;
+ }
+ new_raw_headers.push_back('\0');
+
+ // ok, make this object hold the new data
+ raw_headers_.clear();
+ parsed_.clear();
+ Parse(new_raw_headers);
+}
+
+void HttpResponseHeaders::Parse(const string& raw_input) {
+ raw_headers_.reserve(raw_input.size());
+
+ // ParseStatusLine adds a normalized status line to raw_headers_
+ string::const_iterator line_begin = raw_input.begin();
+ string::const_iterator line_end = find(line_begin, raw_input.end(), '\0');
+ ParseStatusLine(line_begin, line_end);
+
+ if (line_end == raw_input.end()) {
+ raw_headers_.push_back('\0');
+ 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());
+
+ // 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(),
+ string(1, '\0'));
+ while (headers.GetNext()) {
+ AddHeader(headers.name_begin(),
+ headers.name_end(),
+ headers.values_begin(),
+ headers.values_end());
+ }
+}
+
+// Append all of our headers to the final output string.
+void HttpResponseHeaders::GetNormalizedHeaders(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 stdext::hash_map<string, size_t> HeadersMap;
+ HeadersMap headers_map;
+ HeadersMap::iterator iter = headers_map.end();
+
+ std::vector<string> headers;
+
+ for (size_t i = 0; i < parsed_.size(); ++i) {
+ DCHECK(!parsed_[i].is_continuation());
+
+ string name(parsed_[i].name_begin, parsed_[i].name_end);
+ 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(", ");
+ }
+
+ string::const_iterator value_begin = parsed_[i].value_begin;
+ 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 string& name,
+ 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 == string::npos)
+ break;
+
+ found = true;
+
+ if (!value->empty())
+ value->append(", ");
+
+ string::const_iterator value_begin = parsed_[i].value_begin;
+ 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;
+}
+
+string HttpResponseHeaders::GetStatusLine() const {
+ // copy up to the null byte.
+ return string(raw_headers_.c_str());
+}
+
+bool HttpResponseHeaders::EnumerateHeaderLines(void** iter,
+ string* name,
+ 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);
+
+ string::const_iterator value_begin = parsed_[i].value_begin;
+ 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 string& name,
+ 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 = string::npos;
+ } else if (!parsed_[i].is_continuation()) {
+ i = FindHeader(i, name);
+ }
+ }
+
+ if (i == 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 std::string& name,
+ const std::string& 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(),
+ CaseInsensitiveCompare<char>()))
+ return true;
+ }
+ return false;
+}
+
+// Note: this implementation implicitly assumes that line_end points at a valid
+// sentinel character (such as '\0').
+void HttpResponseHeaders::ParseVersion(string::const_iterator line_begin,
+ string::const_iterator line_end) {
+ string::const_iterator p = line_begin;
+
+ // RFC2616 sec 3.1: HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
+ // (1*DIGIT apparently means one or more digits, but we only handle 1).
+
+ if ((line_end - p < 4) || !LowerCaseEqualsASCII(p, p + 4, "http")) {
+ DLOG(INFO) << "missing status line; assuming HTTP/0.9";
+ // Morph this into HTTP/1.0 since HTTP/0.9 has no status line.
+ raw_headers_ = "HTTP/1.0";
+ return;
+ }
+
+ p += 4;
+
+ if (p >= line_end || *p != '/') {
+ DLOG(INFO) << "missing version; assuming HTTP/1.0";
+ raw_headers_ = "HTTP/1.0";
+ return;
+ }
+
+ string::const_iterator dot = find(p, line_end, '.');
+ if (dot == line_end) {
+ DLOG(INFO) << "malformed version; assuming HTTP/1.0";
+ raw_headers_ = "HTTP/1.0";
+ return;
+ }
+
+ ++p; // from / to first digit.
+ ++dot; // from . to second digit.
+
+ if (!(*p >= '0' && *p <= '9' && *dot >= '0' && *dot <= '9')) {
+ DLOG(INFO) << "malformed version number; assuming HTTP/1.0";
+ raw_headers_ = "HTTP/1.0";
+ return;
+ }
+
+ int major = *p - '0';
+ int minor = *dot - '0';
+
+ if ((major > 1) || ((major == 1) && (minor >= 1))) {
+ // at least HTTP/1.1
+ raw_headers_ = "HTTP/1.1";
+ } else {
+ // treat anything else as version 1.0
+ raw_headers_ = "HTTP/1.0";
+ }
+}
+
+// Note: this implementation implicitly assumes that line_end points at a valid
+// sentinel character (such as '\0').
+void HttpResponseHeaders::ParseStatusLine(string::const_iterator line_begin,
+ string::const_iterator line_end) {
+ ParseVersion(line_begin, line_end);
+
+ string::const_iterator p = find(line_begin, line_end, ' ');
+
+ if (p == line_end) {
+ DLOG(INFO) << "missing response status; assuming 200 OK";
+ raw_headers_.append(" 200 OK");
+ raw_headers_.push_back('\0');
+ response_code_ = 200;
+ return;
+ }
+
+ // Skip whitespace.
+ while (*p == ' ')
+ ++p;
+
+ string::const_iterator code = p;
+ while (*p >= '0' && *p <= '9')
+ ++p;
+
+ if (p == code) {
+ DLOG(INFO) << "missing response status number; assuming 200";
+ raw_headers_.append(" 200 ");
+ response_code_ = 200;
+ } else {
+ raw_headers_.push_back(' ');
+ raw_headers_.append(code, p);
+ raw_headers_.push_back(' ');
+ response_code_ = static_cast<int>(StringToInt64(string(code, p)));
+ }
+
+ // Skip whitespace.
+ while (*p == ' ')
+ ++p;
+
+ // Trim trailing whitespace.
+ while (line_end > p && line_end[-1] == ' ')
+ --line_end;
+
+ if (p == line_end) {
+ DLOG(INFO) << "missing response status text; assuming OK";
+ raw_headers_.append("OK");
+ } else {
+ raw_headers_.append(p, line_end);
+ }
+
+ raw_headers_.push_back('\0');
+}
+
+size_t HttpResponseHeaders::FindHeader(size_t from,
+ const string& search) const {
+ for (size_t i = from; i < parsed_.size(); ++i) {
+ if (parsed_[i].is_continuation())
+ continue;
+ const string::const_iterator& name_begin = parsed_[i].name_begin;
+ const string::const_iterator& name_end = parsed_[i].name_end;
+ if ((name_end - name_begin) == search.size() &&
+ std::equal(name_begin, name_end, search.begin(),
+ CaseInsensitiveCompare<char>()))
+ return i;
+ }
+
+ return string::npos;
+}
+
+void HttpResponseHeaders::AddHeader(string::const_iterator name_begin,
+ string::const_iterator name_end,
+ string::const_iterator values_begin,
+ 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(string::const_iterator name_begin,
+ string::const_iterator name_end,
+ string::const_iterator value_begin,
+ 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::GetTransientHeaders(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 string kCacheControl = "cache-control";
+ const string kPrefix = "no-cache=\"";
+ string value;
+ void* iter = NULL;
+ while (EnumerateHeader(&iter, kCacheControl, &value)) {
+ if (value.size() > kPrefix.size() &&
+ value.compare(0, kPrefix.size(), kPrefix) == 0) {
+ // if it doesn't end with a quote, then treat as malformed
+ if (value[value.size()-1] != '\"')
+ continue;
+
+ // trim off leading and trailing bits
+ size_t len = value.size() - kPrefix.size() - 1;
+ TrimString(value.substr(kPrefix.size(), len), HTTP_LWS, &value);
+
+ size_t begin_pos = 0;
+ for (;;) {
+ // find the end of this header name
+ size_t comma_pos = value.find(',', begin_pos);
+ if (comma_pos == string::npos)
+ comma_pos = value.size();
+ size_t end = comma_pos;
+ while (end > begin_pos && strchr(HTTP_LWS, value[end - 1]))
+ end--;
+
+ // assuming the header is not emtpy, lowercase and insert into set
+ if (end > begin_pos) {
+ string name = value.substr(begin_pos, end - begin_pos);
+ StringToLowerASCII(&name);
+ result->insert(name);
+ }
+
+ // repeat
+ begin_pos = comma_pos + 1;
+ while (begin_pos < value.size() && strchr(HTTP_LWS, value[begin_pos]))
+ begin_pos++;
+ if (begin_pos >= value.size())
+ break;
+ }
+ }
+ }
+
+ // Add standard transient headers. Perhaps we should move this to a
+ // statically cached hash_set to avoid duplicated work?
+ for (size_t i = 0; i < arraysize(kTransientHeaders); ++i)
+ result->insert(string(kTransientHeaders[i]));
+}
+
+void HttpResponseHeaders::GetMimeTypeAndCharset(string* mime_type,
+ string* charset) const {
+ mime_type->clear();
+ charset->clear();
+
+ string name = "content-type";
+ string value;
+
+ bool had_charset = false;
+
+ void* iter = NULL;
+ while (EnumerateHeader(&iter, name, &value))
+ HttpUtil::ParseContentType(value, mime_type, charset, &had_charset);
+}
+
+bool HttpResponseHeaders::GetMimeType(string* mime_type) const {
+ string unused;
+ GetMimeTypeAndCharset(mime_type, &unused);
+ return !mime_type->empty();
+}
+
+bool HttpResponseHeaders::GetCharset(string* charset) const {
+ string unused;
+ GetMimeTypeAndCharset(&unused, charset);
+ return !charset->empty();
+}
+
+bool HttpResponseHeaders::IsRedirect(string* location) const {
+ // Users probably want to see 300 (multiple choice) pages, so we don't count
+ // them as redirects that need to be followed.
+ if (!(response_code_ == 301 ||
+ response_code_ == 302 ||
+ response_code_ == 303 ||
+ response_code_ == 307))
+ 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 = -1;
+ do {
+ i = FindHeader(++i, "location");
+ if (i == 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;
+}
+
+// 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.
+ //
+ // Since we do not support byte range requests yet, we exclude 206. See
+ // HttpCache::Transaction::ShouldPassThrough.
+ //
+ // 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) &&
+ !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 {
+ string name = "cache-control";
+ string value;
+
+ const char kMaxAgePrefix[] = "max-age=";
+ const int kMaxAgePrefixLen = arraysize(kMaxAgePrefix) - 1;
+
+ void* iter = NULL;
+ while (EnumerateHeader(&iter, name, &value)) {
+ if (value.size() > kMaxAgePrefixLen) {
+ if (LowerCaseEqualsASCII(value.begin(),
+ value.begin() + kMaxAgePrefixLen,
+ kMaxAgePrefix)) {
+ *result = TimeDelta::FromSeconds(
+ StringToInt64(value.substr(kMaxAgePrefixLen)));
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool HttpResponseHeaders::GetAgeValue(TimeDelta* result) const {
+ string value;
+ if (!EnumerateHeader(NULL, "Age", &value))
+ return false;
+
+ *result = TimeDelta::FromSeconds(StringToInt64(value));
+ 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 {
+ string value;
+ if (!EnumerateHeader(NULL, name, &value))
+ return false;
+
+ std::wstring value_wide(value.begin(), value.end()); // inflate ascii
+ return Time::FromString(value_wide.c_str(), result);
+}
+
+bool HttpResponseHeaders::IsKeepAlive() const {
+ const char kPrefix[] = "HTTP/1.0";
+ const int kPrefixLen = arraysize(kPrefix) - 1;
+ if (raw_headers_.size() < kPrefixLen) // Lacking a status line?
+ 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.
+ string connection_val;
+ void* iter = NULL;
+ if (!EnumerateHeader(&iter, "connection", &connection_val))
+ EnumerateHeader(&iter, "proxy-connection", &connection_val);
+
+ bool keep_alive;
+
+ if (std::equal(raw_headers_.begin(),
+ raw_headers_.begin() + kPrefixLen, kPrefix)) {
+ // 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;
+}
+
+int64 HttpResponseHeaders::GetContentLength() const {
+ void* iter = NULL;
+ string content_length_val;
+ if (!EnumerateHeader(&iter, "content-length", &content_length_val))
+ return -1;
+
+ if (content_length_val.empty())
+ return -1;
+
+ // NOTE: We do not use StringToInt64 here since we want to know if
+ // parsing failed.
+
+ char* end;
+ int64 result = _strtoi64(content_length_val.c_str(), &end, 10);
+
+ if (result < 0)
+ return -1;
+
+ if (end != content_length_val.c_str() + content_length_val.length())
+ return -1;
+
+ return result;
+}
+
+} // namespace net
diff --git a/net/http/http_response_headers.h b/net/http/http_response_headers.h
new file mode 100644
index 0000000..91a5281
--- /dev/null
+++ b/net/http/http_response_headers.h
@@ -0,0 +1,295 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_RESPONSE_HEADERS_H__
+#define NET_HTTP_RESPONSE_HEADERS_H__
+
+#include <hash_set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+
+class Pickle;
+class Time;
+class TimeDelta;
+
+namespace net {
+
+// HttpResponseHeaders: parses and holds HTTP response headers.
+class HttpResponseHeaders :
+ public base::RefCountedThreadSafe<HttpResponseHeaders> {
+ public:
+ // 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: For now, raw_headers is not really 'raw' in that this constructor is
+ // called with a 'NativeMB' string on Windows because WinHTTP does not allow
+ // us to access the raw byte sequence as sent by a web server. In any case,
+ // 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, void** pickle_iter);
+
+ // Appends a representation of this object to the given pickle. If the
+ // for_cache argument is true, then non-cacheable headers will be pruned from
+ // the persisted version of the response headers.
+ void Persist(Pickle* pickle, bool for_cache);
+
+ // Performs header merging as described in 13.5.3 of RFC 2616.
+ void Update(const HttpResponseHeaders& new_headers);
+
+ // 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;
+
+ // 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). 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. Call EnumerateHeader repeatedly until it returns false.
+ bool EnumerateHeader(void** iter,
+ const std::string& 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 std::string& name, const std::string& value) 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 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 Time& request_time,
+ const Time& response_time,
+ const 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.
+ TimeDelta GetFreshnessLifetime(const 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.
+ TimeDelta GetCurrentAge(const Time& request_time,
+ const Time& response_time,
+ const 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(TimeDelta* value) const;
+ bool GetAgeValue(TimeDelta* value) const;
+ bool GetDateValue(Time* value) const;
+ bool GetLastModifiedValue(Time* value) const;
+ bool GetExpiresValue(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, Time* result) const;
+
+ // Determines if this response indicates a keep-alive connection.
+ bool IsKeepAlive() const;
+
+ // Extracts the value of the Content-Length header or returns -1 if there is
+ // no such header in the response.
+ int64 GetContentLength() const;
+
+ // 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 RefCountedThreadSafe<HttpResponseHeaders>;
+
+ 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, we'll guess a version number.
+ // Output will be a normalized version of this, with a trailing \n.
+ void 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, with a trailing \n.
+ void ParseStatusLine(std::string::const_iterator line_begin,
+ std::string::const_iterator line_end);
+
+ // Tries to extract the header line from a header block, given a single
+ // line of said header block. If the header is malformed, we skip it.
+ // Example input:
+ // Content-Length : text/html; charset=utf-8
+ void ParseHeaderLine(std::string::const_iterator line_begin,
+ std::string::const_iterator line_end);
+
+ // 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 std::string& 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);
+
+ typedef stdext::hash_set<std::string> HeaderSet;
+
+ // Returns the values from any 'cache-control: no-cache="foo,bar"' headers as
+ // well as other known-to-be-transient header names. The header names are
+ // all lowercase to support fast lookup.
+ void GetTransientHeaders(HeaderSet* header_names) const;
+
+ // The members of this structure point into raw_headers_.
+ struct ParsedHeader {
+ std::string::const_iterator name_begin;
+ std::string::const_iterator name_end;
+ std::string::const_iterator value_begin;
+ std::string::const_iterator value_end;
+
+ // 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; }
+ };
+ typedef std::vector<ParsedHeader> HeaderList;
+
+ // 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).
+ std::string raw_headers_;
+
+ // This is the parsed HTTP response code.
+ int response_code_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HttpResponseHeaders);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_RESPONSE_HEADERS_H__
diff --git a/net/http/http_response_headers_unittest.cc b/net/http/http_response_headers_unittest.cc
new file mode 100644
index 0000000..41b7c563
--- /dev/null
+++ b/net/http/http_response_headers_unittest.cc
@@ -0,0 +1,965 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/pickle.h"
+#include "base/time.h"
+#include "net/base/net_util.h"
+#include "net/http/http_response_headers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace std;
+using net::HttpResponseHeaders;
+
+namespace {
+
+struct TestData {
+ const char* raw_headers;
+ const char* expected_headers;
+ int expected_response_code;
+};
+
+struct ContentTypeTestData {
+ const string raw_headers;
+ const string mime_type;
+ const bool has_mimetype;
+ const string charset;
+ const bool has_charset;
+ const 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) {
+ replace(headers->begin(), headers->end(), '\n', '\0');
+ if (!headers->empty())
+ *headers += '\0';
+}
+
+void TestCommon(const TestData& test) {
+ string raw_headers(test.raw_headers);
+ HeadersToRaw(&raw_headers);
+ string expected_headers(test.expected_headers);
+
+ string headers;
+ scoped_refptr<HttpResponseHeaders> parsed =
+ new HttpResponseHeaders(raw_headers);
+ parsed->GetNormalizedHeaders(&headers);
+
+ // Transform to readable output format (so it's easier to see diffs).
+ replace(headers.begin(), headers.end(), ' ', '_');
+ replace(headers.begin(), headers.end(), '\n', '\\');
+ replace(expected_headers.begin(), expected_headers.end(), ' ', '_');
+ replace(expected_headers.begin(), expected_headers.end(), '\n', '\\');
+
+ EXPECT_EQ(expected_headers, headers);
+
+ EXPECT_EQ(test.expected_response_code, parsed->response_code());
+}
+
+} // 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
+ };
+ 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
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersVersion) {
+ 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
+ };
+ 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
+ };
+ 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
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersEmpty) {
+ TestData test = {
+ "",
+
+ "HTTP/1.0 200 OK\n",
+
+ 200
+ };
+ 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
+ };
+ 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
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersOfWhitepace) {
+ TestData test = {
+ "\n \n",
+
+ "HTTP/1.0 200 OK\n",
+
+ 200
+ };
+ 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
+ };
+ 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<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
+
+ std::string value;
+ EXPECT_TRUE(parsed->GetNormalizedHeader("cache-control", &value));
+ EXPECT_EQ("private, no-store", value);
+}
+
+TEST(HttpResponseHeadersTest, Persist) {
+ const struct {
+ const char* raw_headers;
+ const char* expected_headers;
+ } tests[] = {
+ { "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"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "server: blah\n",
+
+ "HTTP/1.1 200 OK\n"
+ "server: blah\n"
+ },
+ { "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"
+ },
+ { "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
+ { "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
+ { "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
+ { "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
+ { "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
+ { "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
+ { "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
+ { "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)
+ { "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"
+ },
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ std::string headers = tests[i].raw_headers;
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed1 =
+ new HttpResponseHeaders(headers);
+
+ Pickle pickle;
+ parsed1->Persist(&pickle, true);
+
+ void* iter = NULL;
+ scoped_refptr<HttpResponseHeaders> parsed2 =
+ new HttpResponseHeaders(pickle, &iter);
+
+ std::string h2;
+ parsed2->GetNormalizedHeaders(&h2);
+ EXPECT_EQ(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<HttpResponseHeaders> parsed = new 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);
+}
+
+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<HttpResponseHeaders> parsed = new 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, 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) {
+ string headers(tests[i].raw_headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed = new 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
+ },
+ // 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
+ },
+ // 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
+ };
+ Time request_time, response_time, current_time;
+ Time::FromString(L"Wed, 28 Nov 2007 00:40:09 GMT", &request_time);
+ Time::FromString(L"Wed, 28 Nov 2007 00:40:12 GMT", &response_time);
+ Time::FromString(L"Wed, 28 Nov 2007 00:45:20 GMT", &current_time);
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed = new 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"
+ },
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ string orig_headers(tests[i].orig_headers);
+ HeadersToRaw(&orig_headers);
+ scoped_refptr<HttpResponseHeaders> parsed =
+ new HttpResponseHeaders(orig_headers);
+
+ string new_headers(tests[i].new_headers);
+ HeadersToRaw(&new_headers);
+ scoped_refptr<HttpResponseHeaders> new_parsed =
+ new HttpResponseHeaders(new_headers);
+
+ parsed->Update(*new_parsed);
+
+ string resulting_headers;
+ parsed->GetNormalizedHeaders(&resulting_headers);
+ EXPECT_EQ(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(tests); ++i) {
+ string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed =
+ new HttpResponseHeaders(headers);
+
+ 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(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(tests); ++i) {
+ string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed =
+ new 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: 23xb5\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 0xA\n",
+ -1
+ },
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed =
+ new HttpResponseHeaders(headers);
+
+ EXPECT_EQ(tests[i].expected_len, parsed->GetContentLength());
+ }
+}
+
+TEST(HttpResponseHeadersTest, IsKeepAlive) {
+ const struct {
+ const char* headers;
+ bool expected_keep_alive;
+ } tests[] = {
+ { "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.1 200 OK\n"
+ "proxy-connection: close\n",
+ false
+ },
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed =
+ new HttpResponseHeaders(headers);
+
+ EXPECT_EQ(tests[i].expected_keep_alive, parsed->IsKeepAlive());
+ }
+}
diff --git a/net/http/http_response_info.h b/net/http/http_response_info.h
new file mode 100644
index 0000000..7199cbd
--- /dev/null
+++ b/net/http/http_response_info.h
@@ -0,0 +1,67 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_RESPONSE_INFO_H__
+#define NET_HTTP_HTTP_RESPONSE_INFO_H__
+
+#include "base/time.h"
+#include "net/base/auth.h"
+#include "net/base/ssl_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_vary_data.h"
+
+namespace net {
+
+class HttpResponseInfo {
+ public:
+ // The time at which the request was made that resulted in this response.
+ // For cached responses, this time could be "far" in the past.
+ Time request_time;
+
+ // The time at which the response headers were received. For cached
+ // responses, this time could be "far" in the past.
+ 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 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;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_RESPONSE_INFO_H__
diff --git a/net/http/http_transaction.h b/net/http/http_transaction.h
new file mode 100644
index 0000000..4a13f7c
--- /dev/null
+++ b/net/http/http_transaction.h
@@ -0,0 +1,111 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_TRANSACTION_H_
+#define NET_HTTP_HTTP_TRANSACTION_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+
+namespace net {
+
+class HttpRequestInfo;
+class HttpResponseInfo;
+
+// 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 HttpTransaction {
+ public:
+ // Stops any pending IO and destroys the transaction object.
+ virtual void Destroy() = 0;
+
+ // 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.
+ //
+ virtual int Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback) = 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(CompletionCallback* callback) = 0;
+
+ // Restarts the HTTP transaction with authentication credentials.
+ virtual int RestartWithAuth(const std::wstring& username,
+ const std::wstring& password,
+ 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(char* buf, int buf_len, CompletionCallback* callback) = 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 uint64 GetUploadProgress() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_TRANSACTION_H_
diff --git a/net/http/http_transaction_factory.h b/net/http/http_transaction_factory.h
new file mode 100644
index 0000000..013a48d
--- /dev/null
+++ b/net/http/http_transaction_factory.h
@@ -0,0 +1,61 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_TRANSACTION_FACTORY_H__
+#define NET_HTTP_HTTP_TRANSACTION_FACTORY_H__
+
+class AuthCache;
+
+namespace net {
+
+class HttpCache;
+class HttpTransaction;
+
+// An interface to a class that can create HttpTransaction objects.
+class HttpTransactionFactory {
+ public:
+ virtual ~HttpTransactionFactory() {}
+
+ // Creates a HttpTransaction object.
+ virtual HttpTransaction* CreateTransaction() = 0;
+
+ // Returns the associated cache if any (may be NULL).
+ virtual HttpCache* GetCache() = 0;
+
+ // Returns the associated HTTP auth cache if any (may be NULL).
+ virtual AuthCache* GetAuthCache() = 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_HTTP_HTTP_TRANSACTION_FACTORY_H__
diff --git a/net/http/http_transaction_unittest.cc b/net/http/http_transaction_unittest.cc
new file mode 100644
index 0000000..bfa2a79
--- /dev/null
+++ b/net/http/http_transaction_unittest.cc
@@ -0,0 +1,183 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_transaction_unittest.h"
+
+#include <windows.h>
+
+#include <hash_map>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/load_flags.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"
+
+#pragma warning(disable: 4355)
+
+//-----------------------------------------------------------------------------
+// mock transaction data
+
+const MockTransaction kSimpleGET_Transaction = {
+ "http://www.google.com/",
+ "GET",
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n",
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0
+};
+
+const MockTransaction kSimplePOST_Transaction = {
+ "http://bugdatabase.com/edit",
+ "POST",
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "",
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0
+};
+
+const MockTransaction kTypicalGET_Transaction = {
+ "http://www.example.com/~foo/bar.html",
+ "GET",
+ "",
+ 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",
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0
+};
+
+const MockTransaction kETagGET_Transaction = {
+ "http://www.google.com/foopy",
+ "GET",
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n"
+ "Etag: foopy\n",
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0
+};
+
+const MockTransaction kRangeGET_Transaction = {
+ "http://www.google.com/",
+ "GET",
+ "Range: 0-100\r\n",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n",
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0
+};
+
+static const MockTransaction* const kBuiltinMockTransactions[] = {
+ &kSimpleGET_Transaction,
+ &kSimplePOST_Transaction,
+ &kTypicalGET_Transaction,
+ &kETagGET_Transaction,
+ &kRangeGET_Transaction
+};
+
+typedef stdext::hash_map<std::string, const MockTransaction*>
+ MockTransactionMap;
+static MockTransactionMap mock_transactions;
+
+void AddMockTransaction(const MockTransaction* trans) {
+ mock_transactions[GURL(trans->url).spec()] = trans;
+}
+
+void RemoveMockTransaction(const MockTransaction* trans) {
+ mock_transactions.erase(GURL(trans->url).spec());
+}
+
+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 (int i = 0; i < arraysize(kBuiltinMockTransactions); ++i) {
+ if (url == GURL(kBuiltinMockTransactions[i]->url))
+ return kBuiltinMockTransactions[i];
+ }
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+
+// static
+int TestTransactionConsumer::quit_counter_ = 0;
+
+
+//-----------------------------------------------------------------------------
+// helpers
+
+int ReadTransaction(net::HttpTransaction* trans, std::string* result) {
+ int rv;
+
+ TestCompletionCallback callback;
+
+ std::string content;
+ do {
+ char buf[256];
+ rv = trans->Read(buf, sizeof(buf), &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv > 0) {
+ content.append(buf, rv);
+ } else if (rv < 0) {
+ return rv;
+ }
+ } while (rv > 0);
+
+ result->swap(content);
+ return net::OK;
+}
diff --git a/net/http/http_transaction_unittest.h b/net/http/http_transaction_unittest.h
new file mode 100644
index 0000000..32f8a5a
--- /dev/null
+++ b/net/http/http_transaction_unittest.h
@@ -0,0 +1,346 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_
+#define NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_
+
+#include "net/http/http_transaction.h"
+
+#include <windows.h>
+
+#include <string>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/load_flags.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_info.h"
+
+#pragma warning(disable: 4355)
+
+//-----------------------------------------------------------------------------
+// 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,
+};
+
+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;
+ const char* request_headers;
+ int load_flags;
+ const char* status;
+ const char* response_headers;
+ const char* data;
+ int test_mode;
+ MockTransactionHandler handler;
+ int cert_status;
+};
+
+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) {
+ url = GURL(t.url);
+ method = t.method;
+ extra_headers = t.request_headers;
+ load_flags = t.load_flags;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// use this class to test completely consuming a transaction
+
+class TestTransactionConsumer : public CallbackRunner< Tuple1<int> > {
+ public:
+ explicit TestTransactionConsumer(net::HttpTransactionFactory* factory)
+ : trans_(factory->CreateTransaction()),
+ state_(IDLE),
+ error_(net::OK) {
+ ++quit_counter_;
+ }
+
+ ~TestTransactionConsumer() {
+ trans_->Destroy();
+ }
+
+ void Start(const net::HttpRequestInfo* request) {
+ state_ = STARTING;
+ int result = trans_->Start(request, this);
+ if (result != net::ERR_IO_PENDING)
+ DidStart(result);
+ }
+
+ 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:
+ // Callback implementation:
+ virtual void RunWithParams(const Tuple1<int>& params) {
+ int result = params.a;
+ switch (state_) {
+ case STARTING:
+ DidStart(result);
+ break;
+ case READING:
+ DidRead(result);
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ void DidStart(int result) {
+ if (result != net::OK) {
+ DidFinish(result);
+ } else {
+ Read();
+ }
+ }
+
+ void DidRead(int result) {
+ if (result <= 0) {
+ DidFinish(result);
+ } else {
+ content_.append(read_buf_, result);
+ Read();
+ }
+ }
+
+ void DidFinish(int result) {
+ state_ = DONE;
+ error_ = result;
+ if (--quit_counter_ == 0)
+ MessageLoop::current()->Quit();
+ }
+
+ void Read() {
+ state_ = READING;
+ int result = trans_->Read(read_buf_, sizeof(read_buf_), this);
+ if (result != net::ERR_IO_PENDING)
+ DidRead(result);
+ }
+
+ enum State {
+ IDLE,
+ STARTING,
+ READING,
+ DONE
+ } state_;
+
+ net::HttpTransaction* trans_;
+ std::string content_;
+ char read_buf_[1024];
+ int error_;
+
+ static int quit_counter_;
+};
+
+//-----------------------------------------------------------------------------
+// mock network layer
+
+// 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:
+ MockNetworkTransaction() : task_factory_(this), data_cursor_(0) {
+ }
+
+ virtual void Destroy() {
+ delete this;
+ }
+
+ virtual int Start(const net::HttpRequestInfo* request,
+ net::CompletionCallback* callback) {
+ const MockTransaction* t = FindMockTransaction(request->url);
+ if (!t)
+ return net::ERR_FAILED;
+
+ 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 =
+ 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 = Time::Now();
+ response_.response_time = Time::Now();
+ response_.headers = new net::HttpResponseHeaders(header_data);
+ response_.ssl_info.cert_status = t->cert_status;
+ data_ = resp_data;
+ test_mode_ = t->test_mode;
+
+ if (test_mode_ & TEST_MODE_SYNC_NET_START)
+ return net::OK;
+
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
+ }
+
+ virtual int RestartIgnoringLastError(net::CompletionCallback* callback) {
+ return net::ERR_FAILED;
+ }
+
+ virtual int RestartWithAuth(const std::wstring& username,
+ const std::wstring& password,
+ net::CompletionCallback* callback) {
+ return net::ERR_FAILED;
+ }
+
+ virtual int Read(char* buf, int buf_len, 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_cursor_, num);
+ data_cursor_ += num;
+ }
+ if (test_mode_ & TEST_MODE_SYNC_NET_READ)
+ return num;
+
+ CallbackLater(callback, num);
+ return net::ERR_IO_PENDING;
+ }
+
+ virtual const net::HttpResponseInfo* GetResponseInfo() const {
+ return &response_;
+ }
+
+ virtual net::LoadState GetLoadState() const {
+ NOTREACHED() << "define some mock state transitions";
+ return net::LOAD_STATE_IDLE;
+ }
+
+ virtual uint64 GetUploadProgress() const {
+ return 0;
+ }
+
+ private:
+ void CallbackLater(net::CompletionCallback* callback, int result) {
+ MessageLoop::current()->PostTask(FROM_HERE, task_factory_.NewRunnableMethod(
+ &MockNetworkTransaction::RunCallback, callback, result));
+ }
+ void RunCallback(net::CompletionCallback* callback, int result) {
+ callback->Run(result);
+ }
+
+ ScopedRunnableMethodFactory<MockNetworkTransaction> task_factory_;
+ net::HttpResponseInfo response_;
+ std::string data_;
+ int data_cursor_;
+ int test_mode_;
+};
+
+class MockNetworkLayer : public net::HttpTransactionFactory {
+ public:
+ MockNetworkLayer() : transaction_count_(0) {
+ }
+
+ virtual net::HttpTransaction* CreateTransaction() {
+ transaction_count_++;
+ return new MockNetworkTransaction();
+ }
+
+ virtual net::HttpCache* GetCache() {
+ return NULL;
+ }
+
+ virtual AuthCache* GetAuthCache() {
+ return NULL;
+ }
+
+ virtual void Suspend(bool suspend) {}
+
+ int transaction_count() const { return transaction_count_; }
+
+ private:
+ int transaction_count_;
+};
+
+
+//-----------------------------------------------------------------------------
+// helpers
+
+// read the transaction completely
+int ReadTransaction(net::HttpTransaction* trans, std::string* result);
+
+#endif // NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_
diff --git a/net/http/http_transaction_winhttp.cc b/net/http/http_transaction_winhttp.cc
new file mode 100644
index 0000000..6570b0d
--- /dev/null
+++ b/net/http/http_transaction_winhttp.cc
@@ -0,0 +1,1807 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_transaction_winhttp.h"
+
+#include <winhttp.h>
+
+#include "base/lock.h"
+#include "base/memory_debug.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/auth_cache.h"
+#include "net/base/cert_status_flags.h"
+#include "net/base/dns_resolution_observer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/ssl_config_service.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/cert_status_cache.h"
+#include "net/http/http_proxy_resolver_winhttp.h"
+#include "net/http/http_request_info.h"
+#include "net/http/winhttp_request_throttle.h"
+
+#pragma comment(lib, "winhttp.lib")
+#pragma warning(disable: 4355)
+
+namespace net {
+
+static int TranslateOSError(DWORD error) {
+ switch (error) {
+ case ERROR_SUCCESS:
+ return OK;
+ case ERROR_FILE_NOT_FOUND:
+ return ERR_FILE_NOT_FOUND;
+ case ERROR_HANDLE_EOF: // TODO(wtc): return OK?
+ return ERR_CONNECTION_CLOSED;
+ case ERROR_INVALID_HANDLE:
+ return ERR_INVALID_HANDLE;
+ case ERROR_INVALID_PARAMETER:
+ return ERR_INVALID_ARGUMENT;
+
+ case ERROR_WINHTTP_CANNOT_CONNECT:
+ return ERR_CONNECTION_FAILED;
+ case ERROR_WINHTTP_TIMEOUT:
+ return ERR_TIMED_OUT;
+ case ERROR_WINHTTP_INVALID_URL:
+ return ERR_INVALID_URL;
+ case ERROR_WINHTTP_NAME_NOT_RESOLVED:
+ return ERR_NAME_NOT_RESOLVED;
+ case ERROR_WINHTTP_OPERATION_CANCELLED:
+ return ERR_ABORTED;
+ case ERROR_WINHTTP_SECURE_CHANNEL_ERROR:
+ case ERROR_WINHTTP_SECURE_FAILURE:
+ return ERR_SSL_PROTOCOL_ERROR;
+ case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ case ERROR_WINHTTP_UNRECOGNIZED_SCHEME:
+ return ERR_UNKNOWN_URL_SCHEME;
+ case ERROR_WINHTTP_INVALID_SERVER_RESPONSE:
+ return ERR_INVALID_RESPONSE;
+
+ // SSL certificate errors
+ case ERROR_WINHTTP_SECURE_CERT_CN_INVALID:
+ return ERR_CERT_COMMON_NAME_INVALID;
+ case ERROR_WINHTTP_SECURE_CERT_DATE_INVALID:
+ return ERR_CERT_DATE_INVALID;
+ case ERROR_WINHTTP_SECURE_INVALID_CA:
+ return ERR_CERT_AUTHORITY_INVALID;
+ case ERROR_WINHTTP_SECURE_CERT_REV_FAILED:
+ return ERR_CERT_UNABLE_TO_CHECK_REVOCATION;
+ case ERROR_WINHTTP_SECURE_CERT_REVOKED:
+ return ERR_CERT_REVOKED;
+ case ERROR_WINHTTP_SECURE_INVALID_CERT:
+ return ERR_CERT_INVALID;
+
+ default:
+ DCHECK(error != ERROR_IO_PENDING); // WinHTTP doesn't use this error.
+ return ERR_FAILED;
+ }
+}
+
+static int TranslateLastOSError() {
+ return TranslateOSError(GetLastError());
+}
+
+// Clear certificate errors that we want to ignore.
+static DWORD FilterSecureFailure(DWORD status, int load_flags) {
+ if (load_flags & LOAD_IGNORE_CERT_COMMON_NAME_INVALID)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID;
+ if (load_flags & LOAD_IGNORE_CERT_DATE_INVALID)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID;
+ if (load_flags & LOAD_IGNORE_CERT_AUTHORITY_INVALID)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA;
+ if (load_flags & LOAD_IGNORE_CERT_WRONG_USAGE)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE;
+ return status;
+}
+
+static DWORD MapSecureFailureToError(DWORD status) {
+ // A certificate may have multiple errors. We report the most
+ // serious error.
+
+ // Unrecoverable errors
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)
+ return ERROR_WINHTTP_SECURE_CHANNEL_ERROR;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)
+ return ERROR_WINHTTP_SECURE_INVALID_CERT;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)
+ return ERROR_WINHTTP_SECURE_CERT_REVOKED;
+
+ // Recoverable errors
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)
+ return ERROR_WINHTTP_SECURE_INVALID_CA;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)
+ return ERROR_WINHTTP_SECURE_CERT_CN_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)
+ return ERROR_WINHTTP_SECURE_CERT_DATE_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE)
+ return ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE;
+
+ // Unknown status. Give it the benefit of the doubt.
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)
+ return ERROR_WINHTTP_SECURE_CERT_REV_FAILED;
+
+ // Map a status of 0 to the generic secure failure error. We have seen a
+ // case where WinHttp doesn't notify us of a secure failure (so status is 0)
+ // before notifying us of a request error with ERROR_WINHTTP_SECURE_FAILURE.
+ // (WinInet fails with ERROR_INTERNET_SECURITY_CHANNEL_ERROR in that case.)
+ return ERROR_WINHTTP_SECURE_FAILURE;
+}
+
+static int MapSecureFailureToCertStatus(DWORD status) {
+ int cert_status = 0;
+
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)
+ cert_status |= CERT_STATUS_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)
+ cert_status |= CERT_STATUS_REVOKED;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)
+ cert_status |= CERT_STATUS_AUTHORITY_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)
+ cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)
+ cert_status |= CERT_STATUS_DATE_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)
+ cert_status |= CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+
+ return cert_status;
+ // TODO(jcampan): what about ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE?
+}
+
+// Session --------------------------------------------------------------------
+
+class HttpTransactionWinHttp::Session
+ : public base::RefCounted<HttpTransactionWinHttp::Session> {
+ public:
+ enum {
+ // By default WinHTTP enables only SSL3 and TLS1.
+ SECURE_PROTOCOLS_SSL3_TLS1 = WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 |
+ WINHTTP_FLAG_SECURE_PROTOCOL_TLS1
+ };
+
+ Session()
+ : internet_(NULL),
+ internet_no_tls_(NULL),
+ message_loop_(NULL),
+ handle_closing_event_(NULL),
+ quit_event_(NULL),
+ session_callback_ref_count_(0),
+ quitting_(false),
+ rev_checking_enabled_(false),
+ secure_protocols_(SECURE_PROTOCOLS_SSL3_TLS1) {
+ }
+
+ bool Init();
+
+ // Opens the alternative WinHttp session handle for TLS-intolerant servers.
+ bool InitNoTLS();
+
+ void AddRefBySessionCallback();
+
+ void ReleaseBySessionCallback();
+
+ // The primary WinHttp session handle.
+ HINTERNET internet() { return internet_; }
+
+ // An alternative WinHttp session handle. It is not opened until we have
+ // encountered a TLS-intolerant server and used for those servers only.
+ // TLS is disabled in this session.
+ HINTERNET internet_no_tls() { return internet_no_tls_; }
+
+ // The message loop of the thread where the session was created.
+ MessageLoop* message_loop() { return message_loop_; }
+
+ HttpProxyService* proxy_service() { return proxy_service_.get(); }
+
+ // Gets the HTTP authentication cache for the session.
+ AuthCache* auth_cache() { return &auth_cache_; }
+
+ HANDLE handle_closing_event() const { return handle_closing_event_; }
+
+ CertStatusCache* cert_status_cache() { return &cert_status_cache_; }
+
+ bool rev_checking_enabled() const { return rev_checking_enabled_; }
+
+ bool tls_enabled() const {
+ return (secure_protocols_ & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1) != 0;
+ }
+
+ bool ShouldIgnoreCertRev(const std::string& origin) const {
+ OriginSet::const_iterator pos = ignore_cert_rev_servers_.find(origin);
+ return pos != ignore_cert_rev_servers_.end();
+ }
+
+ void IgnoreCertRev(const std::string& origin) {
+ ignore_cert_rev_servers_.insert(origin);
+ }
+
+ WinHttpRequestThrottle* request_throttle() {
+ return &request_throttle_;
+ }
+
+ private:
+ friend class base::RefCounted<HttpTransactionWinHttp::Session>;
+
+ ~Session();
+
+ // Called by the destructor only.
+ void WaitUntilCallbacksAllDone();
+
+ HINTERNET OpenWinHttpSession();
+
+ // Get the SSL configuration settings and apply them to the session handle.
+ void ConfigureSSL();
+
+ HINTERNET internet_;
+ HINTERNET internet_no_tls_;
+ MessageLoop* message_loop_;
+ scoped_ptr<HttpProxyService> proxy_service_;
+ scoped_ptr<HttpProxyResolver> proxy_resolver_;
+ AuthCache auth_cache_;
+
+ // This event object is used when destroying a transaction. It is given
+ // to the transaction's session callback if WinHTTP still has the caller's
+ // data (request info or read buffer) and we need to wait until WinHTTP is
+ // done with the data.
+ HANDLE handle_closing_event_;
+
+ // The following members ensure a clean destruction of the Session object.
+ // The Session destructor waits until all the request handles have been
+ // terminated by WinHTTP, at which point no more status callbacks will
+ // reference the MessageLoop of the Session.
+ //
+ // quit_event_ is the event object used for this wait.
+ //
+ // lock_ protects session_callback_ref_count_ and quitting_.
+ //
+ // session_callback_ref_count_ is the number of SessionCallback objects
+ // that may reference the MessageLoop of the Session.
+ //
+ // The boolean quitting_ is true when the Session object is being
+ // destructed.
+ HANDLE quit_event_;
+ Lock lock_;
+ int session_callback_ref_count_;
+ bool quitting_;
+
+ // We use a cache to store the certificate error as we cannot always rely on
+ // WinHTTP to provide us the SSL error once we restarted a connection asking
+ // to ignored errors.
+ CertStatusCache cert_status_cache_;
+
+ // SSL settings
+ bool rev_checking_enabled_;
+ DWORD secure_protocols_;
+
+ // The servers for which certificate revocation should be ignored.
+ //
+ // WinHTTP verifies each certificate only once and caches the certificate
+ // verification results, so if we ever ignore certificate revocation for a
+ // server, we cannot enable revocation checking again for that server for
+ // the rest of the session.
+ //
+ // If we honor changes to the rev_checking_enabled system setting during
+ // the session, we will have to remember all the servers we have visited
+ // while the rev_checking_enabled setting is false. This will consume a
+ // lot of memory. So we now require the users to restart Chrome for a
+ // rev_checking_enabled change to take effect, just like IE does.
+ typedef std::set<std::string> OriginSet;
+ OriginSet ignore_cert_rev_servers_;
+
+ WinHttpRequestThrottle request_throttle_;
+};
+
+HttpTransactionWinHttp::Session::~Session() {
+ // It is important to shutdown the proxy service before closing the WinHTTP
+ // session handle since the proxy service uses the WinHTTP session handle.
+ proxy_service_.reset();
+
+ // Next, the resolver which also references our session handle.
+ proxy_resolver_.reset();
+
+ if (internet_) {
+ WinHttpCloseHandle(internet_);
+ if (internet_no_tls_)
+ WinHttpCloseHandle(internet_no_tls_);
+
+ // Ensure that all status callbacks that may reference the MessageLoop
+ // of this thread are done before we can allow the current thread to exit.
+ WaitUntilCallbacksAllDone();
+ }
+
+ if (handle_closing_event_)
+ CloseHandle(handle_closing_event_);
+ if (quit_event_)
+ CloseHandle(quit_event_);
+}
+
+bool HttpTransactionWinHttp::Session::Init() {
+ DCHECK(!internet_);
+
+ internet_ = OpenWinHttpSession();
+
+ if (!internet_)
+ return false;
+
+ proxy_resolver_.reset(new HttpProxyResolverWinHttp());
+ proxy_service_.reset(new HttpProxyService(proxy_resolver_.get()));
+
+ ConfigureSSL();
+
+ // Save the current message loop for callback notifications.
+ message_loop_ = MessageLoop::current();
+
+ handle_closing_event_ = CreateEvent(NULL,
+ FALSE, // auto-reset
+ FALSE, // initially nonsignaled
+ NULL); // unnamed
+
+ quit_event_ = CreateEvent(NULL,
+ FALSE, // auto-reset
+ FALSE, // initially nonsignaled
+ NULL); // unnamed
+
+ return true;
+}
+
+bool HttpTransactionWinHttp::Session::InitNoTLS() {
+ DCHECK(tls_enabled());
+ DCHECK(internet_);
+ DCHECK(!internet_no_tls_);
+
+ internet_no_tls_ = OpenWinHttpSession();
+
+ if (!internet_no_tls_)
+ return false;
+
+ DWORD protocols = secure_protocols_ & ~WINHTTP_FLAG_SECURE_PROTOCOL_TLS1;
+ BOOL rv = WinHttpSetOption(internet_no_tls_,
+ WINHTTP_OPTION_SECURE_PROTOCOLS,
+ &protocols, sizeof(protocols));
+ DCHECK(rv);
+
+ return true;
+}
+
+void HttpTransactionWinHttp::Session::AddRefBySessionCallback() {
+ AutoLock lock(lock_);
+ session_callback_ref_count_++;
+}
+
+void HttpTransactionWinHttp::Session::ReleaseBySessionCallback() {
+ bool need_to_signal;
+ {
+ AutoLock lock(lock_);
+ session_callback_ref_count_--;
+ need_to_signal = (quitting_ && session_callback_ref_count_ == 0);
+ }
+ if (need_to_signal)
+ SetEvent(quit_event_);
+}
+
+// This is called by the Session destructor only. By now the transaction
+// factory and all the transactions have been destructed. This means that
+// new transactions can't be created, and existing transactions can't be
+// started, which in turn implies that session_callback_ref_count_ cannot
+// increase. We wait until session_callback_ref_count_ drops to 0.
+void HttpTransactionWinHttp::Session::WaitUntilCallbacksAllDone() {
+ bool need_to_wait;
+ {
+ AutoLock lock(lock_);
+ quitting_ = true;
+ need_to_wait = (session_callback_ref_count_ != 0);
+ }
+ if (need_to_wait)
+ WaitForSingleObject(quit_event_, INFINITE);
+ DCHECK(session_callback_ref_count_ == 0);
+}
+
+HINTERNET HttpTransactionWinHttp::Session::OpenWinHttpSession() {
+ // UA string and proxy config will be set explicitly for each request
+ HINTERNET internet = WinHttpOpen(NULL,
+ WINHTTP_ACCESS_TYPE_NO_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ WINHTTP_FLAG_ASYNC);
+ if (!internet)
+ return internet;
+
+ // Use a 90-second timeout (1.5 times the default) for connect. Disable
+ // name resolution, send, and receive timeouts. We expect our consumer to
+ // apply timeouts or provide controls for users to stop requests that are
+ // taking too long.
+ BOOL rv = WinHttpSetTimeouts(internet, 0, 90000, 0, 0);
+ DCHECK(rv);
+
+ return internet;
+}
+
+void HttpTransactionWinHttp::Session::ConfigureSSL() {
+ DCHECK(internet_);
+
+ SSLConfig ssl_config;
+ SSLConfigService::GetSSLConfigNow(&ssl_config);
+ rev_checking_enabled_ = ssl_config.rev_checking_enabled;
+ DWORD protocols = 0;
+ if (ssl_config.ssl2_enabled)
+ protocols |= WINHTTP_FLAG_SECURE_PROTOCOL_SSL2;
+ if (ssl_config.ssl3_enabled)
+ protocols |= WINHTTP_FLAG_SECURE_PROTOCOL_SSL3;
+ if (ssl_config.tls1_enabled)
+ protocols |= WINHTTP_FLAG_SECURE_PROTOCOL_TLS1;
+ if (protocols != secure_protocols_) {
+ BOOL rv = WinHttpSetOption(internet_, WINHTTP_OPTION_SECURE_PROTOCOLS,
+ &protocols, sizeof(protocols));
+ DCHECK(rv);
+ secure_protocols_ = protocols;
+ }
+}
+
+// SessionCallback ------------------------------------------------------------
+
+class HttpTransactionWinHttp::SessionCallback
+ : public base::RefCountedThreadSafe<HttpTransactionWinHttp::SessionCallback> {
+ public:
+ SessionCallback(HttpTransactionWinHttp* trans, Session* session)
+ : trans_(trans),
+ session_(session),
+ load_state_(LOAD_STATE_IDLE),
+ handle_closing_event_(NULL),
+ bytes_available_(0),
+ read_buf_(NULL),
+ read_buf_len_(0),
+ secure_failure_(0),
+ connection_was_opened_(false),
+ request_was_probably_sent_(false),
+ response_was_received_(false),
+ response_is_empty_(true) {
+ }
+
+ // Called when the associated trans_ has to reopen its connection and
+ // request handles to recover from certain SSL errors. Resets the members
+ // that may have been modified at that point.
+ void ResetForNewRequest() {
+ secure_failure_ = 0;
+ connection_was_opened_ = false;
+ }
+
+ void DropTransaction() {
+ trans_ = NULL;
+ }
+
+ void Notify(DWORD status, DWORD_PTR result, DWORD error) {
+ DWORD secure_failure = 0;
+ if (status == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) {
+ switch (error) {
+ // WinHttp sends this error code in two interesting cases: 1) when a
+ // response header is malformed, and 2) when a response is empty. In
+ // the latter case, we want to actually resend the request if the
+ // request was sent over a reused "keep-alive" connection. This is a
+ // risky thing to do since it is possible that the server did receive
+ // our request, but it is unfortunately required to support HTTP keep-
+ // alive connections properly, and other browsers all do this too.
+ case ERROR_WINHTTP_INVALID_SERVER_RESPONSE:
+ if (empty_response_was_received() && !connection_was_opened_)
+ error = ERROR_WINHTTP_RESEND_REQUEST;
+ break;
+ case ERROR_WINHTTP_SECURE_FAILURE:
+ secure_failure = secure_failure_;
+ break;
+ }
+ } else if (status == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) {
+ secure_failure = secure_failure_;
+ }
+ session_->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &HttpTransactionWinHttp::SessionCallback::OnNotify,
+ status, result, error, secure_failure));
+ }
+
+ // Calls WinHttpReadData and returns its return value.
+ BOOL ReadData(HINTERNET request_handle);
+
+ void OnHandleClosing() {
+ if (handle_closing_event_)
+ SetEvent(handle_closing_event_);
+ session_->ReleaseBySessionCallback();
+ Release();
+ }
+
+ // Modified from any thread.
+ void set_load_state(LoadState state) {
+ load_state_ = state;
+ }
+ LoadState load_state() const {
+ return load_state_;
+ }
+
+ int bytes_available() const { return bytes_available_; }
+ void set_bytes_available(int n) { bytes_available_ = n; }
+ void reduce_bytes_available(int n) { bytes_available_ -= n; }
+
+ char* read_buf() const { return read_buf_; }
+ void set_read_buf(char* buf) { read_buf_ = buf; }
+
+ int read_buf_len() const { return read_buf_len_; }
+ void set_read_buf_len(int buf_len) { read_buf_len_ = buf_len; }
+
+ // Tells this SessionCallback to signal this event when receiving the
+ // handle closing status callback.
+ void set_handle_closing_event(HANDLE event) {
+ handle_closing_event_ = event;
+ }
+
+ void set_secure_failure(DWORD flags) { secure_failure_ = flags; }
+
+ void did_open_connection() {
+ connection_was_opened_ = true;
+ }
+
+ void did_start_sending_request() {
+ request_was_probably_sent_ = true;
+ }
+ bool request_was_probably_sent() const {
+ return request_was_probably_sent_;
+ }
+
+ void did_receive_bytes(DWORD count) {
+ response_was_received_ = true;
+ if (count)
+ response_is_empty_ = false;
+ }
+
+ private:
+ friend base::RefCountedThreadSafe<HttpTransactionWinHttp::SessionCallback>;
+ ~SessionCallback() {}
+
+ void OnNotify(DWORD status,
+ DWORD_PTR result,
+ DWORD error,
+ DWORD secure_failure) {
+ if (trans_)
+ trans_->HandleStatusCallback(status, result, error, secure_failure);
+
+ // Balances the AddRefs made by the transaction object after an async
+ // WinHTTP call.
+ Release();
+ }
+
+ bool empty_response_was_received() const {
+ return response_was_received_ && response_is_empty_;
+ }
+
+ HttpTransactionWinHttp* trans_;
+
+ // Session is reference-counted, but this is a plain pointer. The
+ // reference on the Session owned by SessionCallback is managed using
+ // Session::AddRefBySessionCallback and Session::ReleaseBySessionCallback.
+ Session* session_;
+
+ // Modified from any thread.
+ volatile LoadState load_state_;
+
+ // Amount of data available reported by WinHttpQueryDataAvailable that
+ // haven't been consumed by WinHttpReadData.
+ int bytes_available_;
+
+ // Caller's read buffer and buffer size, to be passed to WinHttpReadData.
+ // These are used by the IO thread and the thread WinHTTP uses to make
+ // status callbacks, but not at the same time.
+ char* read_buf_;
+ int read_buf_len_;
+
+ // If not null, we set this event on receiving the handle closing callback.
+ HANDLE handle_closing_event_;
+
+ // The secure connection failure flags reported by the
+ // WINHTTP_CALLBACK_STATUS_SECURE_FAILURE status callback.
+ DWORD secure_failure_;
+
+ // True if a connection was opened for this request.
+ bool connection_was_opened_;
+
+ // True if the request may have been sent to the server (and therefore we
+ // should not restart the request).
+ bool request_was_probably_sent_;
+
+ // True if any response was received.
+ bool response_was_received_;
+
+ // True if we have an empty response (no headers, no status line, nothing).
+ bool response_is_empty_;
+};
+
+BOOL HttpTransactionWinHttp::SessionCallback::ReadData(
+ HINTERNET request_handle) {
+ DCHECK(bytes_available_ >= 0);
+ char* buf = read_buf_;
+ read_buf_ = NULL;
+ int bytes_to_read = std::min(bytes_available_, read_buf_len_);
+ read_buf_len_ = 0;
+ if (!bytes_to_read)
+ bytes_to_read = 1;
+
+ // Because of how WinHTTP fills memory when used asynchronously, Purify isn't
+ // able to detect that it's been initialized, so it scans for 0xcd in the
+ // buffer and reports UMRs (uninitialized memory reads) for those individual
+ // bytes. We override that to avoid the false error reports.
+ // See http://b/issue?id=1173916.
+ base::MemoryDebug::MarkAsInitialized(buf, bytes_to_read);
+ return WinHttpReadData(request_handle, buf, bytes_to_read, NULL);
+}
+
+// static
+void HttpTransactionWinHttp::StatusCallback(HINTERNET handle,
+ DWORD_PTR context,
+ DWORD status,
+ LPVOID status_info,
+ DWORD status_info_len) {
+ SessionCallback* callback = reinterpret_cast<SessionCallback*>(context);
+
+ switch (status) {
+ case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
+ if (callback)
+ callback->OnHandleClosing();
+ break;
+ case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
+ callback->set_load_state(LOAD_STATE_CONNECTING);
+ break;
+ case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
+ callback->did_open_connection();
+ break;
+ case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
+ callback->set_load_state(LOAD_STATE_SENDING_REQUEST);
+ callback->did_start_sending_request();
+ break;
+ case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
+ callback->set_load_state(LOAD_STATE_WAITING_FOR_RESPONSE);
+ break;
+ case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
+ callback->did_receive_bytes(*static_cast<DWORD*>(status_info));
+ break;
+ case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
+ DCHECK(callback->bytes_available() == 0);
+ DCHECK(status_info_len == sizeof(DWORD));
+ callback->set_bytes_available(static_cast<DWORD*>(status_info)[0]);
+ if (!callback->ReadData(handle))
+ callback->Notify(WINHTTP_CALLBACK_STATUS_REQUEST_ERROR,
+ API_READ_DATA, GetLastError());
+ break;
+ case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
+ callback->Notify(status, status_info_len, 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
+ DCHECK(status_info_len == sizeof(DWORD));
+ callback->Notify(status, static_cast<DWORD*>(status_info)[0], 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
+ callback->Notify(status, TRUE, 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
+ callback->Notify(status, TRUE, 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: {
+ WINHTTP_ASYNC_RESULT* result =
+ static_cast<WINHTTP_ASYNC_RESULT*>(status_info);
+ callback->Notify(status, result->dwResult, result->dwError);
+ if (API_SEND_REQUEST == result->dwResult &&
+ ERROR_WINHTTP_NAME_NOT_RESOLVED == result->dwError)
+ DidFinishDnsResolutionWithStatus(false,
+ reinterpret_cast<void*>(context));
+ break;
+ }
+ // This status callback provides the detailed reason for a secure
+ // failure. We map that to an error code and save it for later use.
+ case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: {
+ DCHECK(status_info_len == sizeof(DWORD));
+ DWORD* status_ptr = static_cast<DWORD*>(status_info);
+ callback->set_secure_failure(*status_ptr);
+ break;
+ }
+ // Looking up the IP address of a server name. The status_info
+ // parameter contains a pointer to the server name being resolved.
+ case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME: {
+ callback->set_load_state(LOAD_STATE_RESOLVING_HOST);
+ std::wstring wname(static_cast<wchar_t*>(status_info),
+ status_info_len - 1);
+ DidStartDnsResolution(WideToASCII(wname),
+ reinterpret_cast<void*>(context));
+ break;
+ }
+ // Successfully found the IP address of the server.
+ case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
+ DidFinishDnsResolutionWithStatus(true, reinterpret_cast<void*>(context));
+ break;
+ }
+}
+
+// Factory --------------------------------------------------------------------
+
+HttpTransactionWinHttp::Factory::~Factory() {
+ if (session_)
+ session_->Release();
+}
+
+HttpTransaction* HttpTransactionWinHttp::Factory::CreateTransaction() {
+ if (is_suspended_)
+ return NULL;
+
+ if (!session_) {
+ session_ = new Session();
+ session_->AddRef();
+ if (!session_->Init()) {
+ DLOG(ERROR) << "unable to create the internet";
+ session_->Release();
+ session_ = NULL;
+ return NULL;
+ }
+ }
+ return new HttpTransactionWinHttp(session_, proxy_info_.get());
+}
+
+HttpCache* HttpTransactionWinHttp::Factory::GetCache() {
+ return NULL;
+}
+
+AuthCache* HttpTransactionWinHttp::Factory::GetAuthCache() {
+ return session_->auth_cache();
+}
+
+void HttpTransactionWinHttp::Factory::Suspend(bool suspend) {
+ is_suspended_ = suspend;
+
+ if (is_suspended_ && session_) {
+ session_->Release();
+ session_ = NULL;
+ }
+}
+
+// Transaction ----------------------------------------------------------------
+
+HttpTransactionWinHttp::HttpTransactionWinHttp(Session* session,
+ const HttpProxyInfo* info)
+ : session_(session),
+ request_(NULL),
+ load_flags_(0),
+ last_error_(ERROR_SUCCESS),
+ content_length_remaining_(-1),
+ pac_request_(NULL),
+ proxy_callback_(this, &HttpTransactionWinHttp::OnProxyInfoAvailable),
+ callback_(NULL),
+ upload_progress_(0),
+ connect_handle_(NULL),
+ request_handle_(NULL),
+ is_https_(false),
+ is_tls_intolerant_(false),
+ rev_checking_enabled_(false),
+ request_submitted_(false),
+ have_proxy_info_(false),
+ need_to_wait_for_handle_closing_(false) {
+ session->AddRef();
+ session_callback_ = new SessionCallback(this, session);
+ if (info) {
+ proxy_info_.Use(*info);
+ have_proxy_info_ = true;
+ }
+}
+
+HttpTransactionWinHttp::~HttpTransactionWinHttp() {
+ if (pac_request_)
+ session_->proxy_service()->CancelPacRequest(pac_request_);
+
+ if (request_handle_) {
+ if (need_to_wait_for_handle_closing_) {
+ session_callback_->set_handle_closing_event(
+ session_->handle_closing_event());
+ }
+ WinHttpCloseHandle(request_handle_);
+ if (need_to_wait_for_handle_closing_)
+ WaitForSingleObject(session_->handle_closing_event(), INFINITE);
+ }
+ if (connect_handle_)
+ WinHttpCloseHandle(connect_handle_);
+
+ if (request_submitted_) {
+ session_->request_throttle()->RemoveRequest(connect_peer_,
+ request_handle_);
+ }
+
+ if (session_callback_) {
+ session_callback_->DropTransaction();
+ session_callback_ = NULL; // Release() reference as side effect.
+ }
+ if (session_)
+ session_->Release();
+}
+
+void HttpTransactionWinHttp::Destroy() {
+ delete this;
+}
+
+int HttpTransactionWinHttp::Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback) {
+ DCHECK(request_info);
+ DCHECK(callback);
+
+ // ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ LOG(INFO) << request_info->method << ": " << request_info->url;
+
+ request_ = request_info;
+ load_flags_ = request_info->load_flags;
+
+ int rv = OK;
+ if (!have_proxy_info_) {
+ // Resolve proxy info.
+ rv = session_->proxy_service()->ResolveProxy(request_->url,
+ &proxy_info_,
+ &proxy_callback_,
+ &pac_request_);
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->set_load_state(
+ LOAD_STATE_RESOLVING_PROXY_FOR_URL);
+ }
+ }
+
+ if (rv == OK)
+ rv = DidResolveProxy(); // calls OpenRequest and SendRequest
+
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->AddRef(); // balanced when callback runs or from
+ // OnProxyInfoAvailable.
+ callback_ = callback;
+ }
+
+ return rv;
+}
+
+int HttpTransactionWinHttp::RestartIgnoringLastError(
+ CompletionCallback* callback) {
+ int flags = load_flags_;
+
+ // Depending on the error, we make different adjustments to our load flags.
+ // We DCHECK that we shouldn't already have ignored this error.
+ switch (last_error_) {
+ case ERROR_WINHTTP_SECURE_CERT_CN_INVALID:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_COMMON_NAME_INVALID));
+ flags |= LOAD_IGNORE_CERT_COMMON_NAME_INVALID;
+ break;
+ case ERROR_WINHTTP_SECURE_CERT_DATE_INVALID:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_DATE_INVALID));
+ flags |= LOAD_IGNORE_CERT_DATE_INVALID;
+ break;
+ case ERROR_WINHTTP_SECURE_INVALID_CA:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_AUTHORITY_INVALID));
+ flags |= LOAD_IGNORE_CERT_AUTHORITY_INVALID;
+ break;
+ case ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_WRONG_USAGE));
+ flags |= LOAD_IGNORE_CERT_WRONG_USAGE;
+ break;
+ case ERROR_WINHTTP_SECURE_CERT_REV_FAILED: {
+ DCHECK(!(flags & LOAD_IGNORE_CERT_REVOCATION));
+ flags |= LOAD_IGNORE_CERT_REVOCATION;
+ // WinHTTP doesn't have a SECURITY_FLAG_IGNORE_CERT_REV_FAILED flag
+ // and doesn't let us undo WINHTTP_ENABLE_SSL_REVOCATION. The only
+ // way to ignore this error is to open a new request without enabling
+ // WINHTTP_ENABLE_SSL_REVOCATION.
+ if (!ReopenRequest())
+ return TranslateLastOSError();
+ break;
+ }
+ // We can't instruct WinHttp to recover from these errors. No choice
+ // but to cancel the request.
+ case ERROR_WINHTTP_SECURE_CHANNEL_ERROR:
+ case ERROR_WINHTTP_SECURE_INVALID_CERT:
+ case ERROR_WINHTTP_SECURE_CERT_REVOKED:
+ // We don't knows how to continue from here.
+ default:
+ LOG(ERROR) << "Unable to restart the HTTP transaction ignoring "
+ "the error " << last_error_;
+ return ERR_ABORTED;
+ }
+
+ // Update the load flags to ignore the specified error.
+ load_flags_ = flags;
+
+ return Restart(callback);
+}
+
+int HttpTransactionWinHttp::RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback) {
+ DCHECK(proxy_auth_ && proxy_auth_->state == AUTH_STATE_NEED_AUTH ||
+ server_auth_ && server_auth_->state == AUTH_STATE_NEED_AUTH);
+
+ // Proxy gets set first, then WWW.
+ AuthData* auth =
+ proxy_auth_ && proxy_auth_->state == AUTH_STATE_NEED_AUTH ?
+ proxy_auth_ : server_auth_;
+
+ if (auth) {
+ auth->state = AUTH_STATE_HAVE_AUTH;
+ auth->username = username;
+ auth->password = password;
+ }
+
+ return Restart(callback);
+}
+
+// The code common to RestartIgnoringLastError and RestartWithAuth.
+int HttpTransactionWinHttp::Restart(CompletionCallback* callback) {
+ DCHECK(callback);
+
+ // ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ int rv = SendRequest();
+ if (rv != ERR_IO_PENDING)
+ return rv;
+
+ session_callback_->AddRef(); // balanced when callback runs.
+
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+// We use WinHttpQueryDataAvailable rather than pure async read to trade
+// a better latency for a decreased throughput. We'll make more IO calls,
+// and thus use more CPU for a given transaction by using
+// WinHttpQueryDataAvailable, but it allows us to get a faster response
+// time to the app for data, which is more important.
+int HttpTransactionWinHttp::Read(char* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK(buf_len > 0);
+ DCHECK(callback);
+
+ DCHECK(!callback_);
+ DCHECK(request_handle_);
+
+ // If we have already received the full response, then we know we are done.
+ if (content_length_remaining_ == 0)
+ return 0;
+
+ session_callback_->set_read_buf(buf);
+ session_callback_->set_read_buf_len(buf_len);
+
+ // We must consume all the available data reported by the previous
+ // WinHttpQueryDataAvailable call before we can call
+ // WinHttpQueryDataAvailable again.
+ BOOL ok;
+ if (session_callback_->bytes_available()) {
+ ok = session_callback_->ReadData(request_handle_);
+ } else {
+ ok = WinHttpQueryDataAvailable(request_handle_, NULL);
+ }
+ if (!ok)
+ return TranslateLastOSError();
+
+ session_callback_->set_load_state(LOAD_STATE_READING_RESPONSE);
+ session_callback_->AddRef(); // balanced when callback runs.
+ need_to_wait_for_handle_closing_ = true;
+
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+const HttpResponseInfo* HttpTransactionWinHttp::GetResponseInfo() const {
+ return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL;
+}
+
+LoadState HttpTransactionWinHttp::GetLoadState() const {
+ return session_callback_->load_state();
+}
+
+uint64 HttpTransactionWinHttp::GetUploadProgress() const {
+ return upload_progress_;
+}
+
+void HttpTransactionWinHttp::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(callback_);
+
+ // since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback* c = callback_;
+ callback_ = NULL;
+ c->Run(rv);
+}
+
+bool HttpTransactionWinHttp::OpenRequest() {
+ DCHECK(!connect_handle_);
+ DCHECK(!request_handle_);
+
+ const GURL& url = request_->url;
+ const std::string& scheme = url.scheme();
+
+ // Flags passed to WinHttpOpenRequest. Disable any conversion WinHttp
+ // might perform on our URL string. We handle the escaping ourselves.
+ DWORD open_flags = WINHTTP_FLAG_ESCAPE_DISABLE |
+ WINHTTP_FLAG_ESCAPE_DISABLE_QUERY |
+ WINHTTP_FLAG_NULL_CODEPAGE;
+
+ // We should only be dealing with HTTP at this point:
+ DCHECK(LowerCaseEqualsASCII(scheme, "http") ||
+ LowerCaseEqualsASCII(scheme, "https"));
+
+ int in_port = url.IntPort();
+ DCHECK(in_port != url_parse::PORT_INVALID) <<
+ "Valid URLs should have valid ports";
+
+ // Map to port numbers that Windows expects.
+ INTERNET_PORT port = in_port;
+ if (LowerCaseEqualsASCII(scheme, "https")) {
+ is_https_ = true;
+ open_flags |= WINHTTP_FLAG_SECURE;
+ if (in_port == url_parse::PORT_UNSPECIFIED)
+ port = INTERNET_DEFAULT_HTTPS_PORT;
+ } else {
+ if (in_port == url_parse::PORT_UNSPECIFIED)
+ port = INTERNET_DEFAULT_HTTP_PORT;
+ }
+
+ const std::string& host = url.host();
+
+ // Use the primary session handle unless we are talking to a TLS-intolerant
+ // server.
+ //
+ // Since the SSL protocol versions enabled are an option of a session
+ // handle, supporting TLS-intolerant servers unfortunately requires opening
+ // an alternative session in which TLS 1.0 is disabled.
+ HINTERNET internet = session_->internet();
+ if (is_tls_intolerant_) {
+ if (!session_->internet_no_tls() && !session_->InitNoTLS()) {
+ DLOG(ERROR) << "unable to create the no-TLS alternative internet";
+ return false;
+ }
+ internet = session_->internet_no_tls();
+ }
+
+ // This function operates synchronously.
+ connect_handle_ =
+ WinHttpConnect(internet, ASCIIToWide(host).c_str(), port, 0);
+ if (!connect_handle_) {
+ DLOG(ERROR) << "WinHttpConnect failed: " << GetLastError();
+ return false;
+ }
+
+ std::string request_path = url.PathForRequest();
+
+ // This function operates synchronously.
+ request_handle_ =
+ WinHttpOpenRequest(connect_handle_,
+ ASCIIToWide(request_->method).c_str(),
+ ASCIIToWide(request_path).c_str(),
+ NULL, // use HTTP/1.1
+ WINHTTP_NO_REFERER, // none
+ WINHTTP_DEFAULT_ACCEPT_TYPES, // none
+ open_flags);
+ if (!request_handle_) {
+ DLOG(ERROR) << "WinHttpOpenRequest failed: " << GetLastError();
+ return false;
+ }
+
+ // TODO(darin): we may wish to prune-back the set of notifications we receive
+ WINHTTP_STATUS_CALLBACK old_callback = WinHttpSetStatusCallback(
+ request_handle_, StatusCallback,
+ WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, NULL);
+ DCHECK(old_callback == NULL);
+ if (old_callback == WINHTTP_INVALID_STATUS_CALLBACK) {
+ DLOG(ERROR) << "WinHttpSetStatusCallback failed:" << GetLastError();
+ return false;
+ }
+
+ DWORD_PTR ctx = reinterpret_cast<DWORD_PTR>(session_callback_.get());
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_CONTEXT_VALUE,
+ &ctx, sizeof(ctx))) {
+ DLOG(ERROR) << "WinHttpSetOption context value failed:" << GetLastError();
+ return false;
+ }
+
+ // We just associated a status callback context value with the request
+ // handle.
+ session_callback_->AddRef(); // balanced in OnHandleClosing
+ session_->AddRefBySessionCallback();
+
+ // We have our own cookie and redirect management.
+ DWORD options = WINHTTP_DISABLE_COOKIES |
+ WINHTTP_DISABLE_REDIRECTS;
+
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_DISABLE_FEATURE,
+ &options, sizeof(options))) {
+ DLOG(ERROR) << "WinHttpSetOption disable feature failed:" << GetLastError();
+ return false;
+ }
+
+ // Disable auto-login for Negotiate and NTLM auth methods.
+ DWORD security_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_AUTOLOGON_POLICY,
+ &security_level, sizeof(security_level))) {
+ DLOG(ERROR) << "WinHttpSetOption autologon failed: " << GetLastError();
+ return false;
+ }
+
+ // Add request headers. WinHttp is known to convert the headers to bytes
+ // using the system charset converter, so we use the same converter to map
+ // our request headers to UTF-16 before handing the data to WinHttp.
+ std::wstring request_headers = NativeMBToWide(GetRequestHeaders());
+
+ DWORD len = static_cast<DWORD>(request_headers.size());
+ if (!WinHttpAddRequestHeaders(request_handle_,
+ request_headers.c_str(),
+ len,
+ WINHTTP_ADDREQ_FLAG_ADD |
+ WINHTTP_ADDREQ_FLAG_REPLACE)) {
+ DLOG(ERROR) << "WinHttpAddRequestHeaders failed: " << GetLastError();
+ return false;
+ }
+
+ return true;
+}
+
+int HttpTransactionWinHttp::SendRequest() {
+ DCHECK(request_handle_);
+
+ // Apply any authentication (username/password) we might have.
+ ApplyAuth();
+
+ // Apply any proxy info.
+ proxy_info_.Apply(request_handle_);
+
+ // Check SSL server certificate revocation.
+ if (is_https_) {
+ bool ignore_cert_rev = (load_flags_ & LOAD_IGNORE_CERT_REVOCATION) != 0;
+ GURL origin = request_->url.GetOrigin();
+ const std::string& origin_spec = origin.spec();
+ if (ignore_cert_rev)
+ session_->IgnoreCertRev(origin_spec);
+ else if (session_->ShouldIgnoreCertRev(origin_spec))
+ ignore_cert_rev = true;
+
+ if (session_->rev_checking_enabled() && !ignore_cert_rev) {
+ DWORD options = WINHTTP_ENABLE_SSL_REVOCATION;
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_ENABLE_FEATURE,
+ &options, sizeof(options))) {
+ DLOG(ERROR) << "WinHttpSetOption failed: " << GetLastError();
+ return TranslateLastOSError();
+ }
+ rev_checking_enabled_ = true;
+ }
+ }
+
+ const int kCertFlags = LOAD_IGNORE_CERT_COMMON_NAME_INVALID |
+ LOAD_IGNORE_CERT_DATE_INVALID |
+ LOAD_IGNORE_CERT_AUTHORITY_INVALID |
+ LOAD_IGNORE_CERT_WRONG_USAGE;
+
+ if (load_flags_ & kCertFlags) {
+ DWORD security_flags;
+ DWORD length = sizeof(security_flags);
+
+ if (!WinHttpQueryOption(request_handle_,
+ WINHTTP_OPTION_SECURITY_FLAGS,
+ &security_flags,
+ &length)) {
+ NOTREACHED() << "WinHttpQueryOption failed.";
+ return TranslateLastOSError();
+ }
+
+ // On Vista, WinHttpSetOption() fails with an incorrect parameter error.
+ // WinHttpQueryOption() sets an undocumented flag (0x01000000, which seems
+ // to be a query-only flag) in security_flags that causes this error. To
+ // work-around it, we only keep the documented error flags.
+ security_flags &= (SECURITY_FLAG_IGNORE_UNKNOWN_CA |
+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE);
+
+ if (load_flags_ & LOAD_IGNORE_CERT_COMMON_NAME_INVALID)
+ security_flags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
+
+ if (load_flags_ & LOAD_IGNORE_CERT_DATE_INVALID)
+ security_flags |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
+
+ if (load_flags_ & LOAD_IGNORE_CERT_AUTHORITY_INVALID)
+ security_flags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+
+ if (load_flags_ & LOAD_IGNORE_CERT_WRONG_USAGE)
+ security_flags |= SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
+
+ if (!WinHttpSetOption(request_handle_,
+ WINHTTP_OPTION_SECURITY_FLAGS,
+ &security_flags,
+ sizeof(security_flags))) {
+ NOTREACHED() << "WinHttpSetOption failed.";
+ return TranslateLastOSError();
+ }
+ }
+
+ response_.request_time = Time::Now();
+
+ DWORD total_size = 0;
+ if (request_->upload_data) {
+ upload_stream_.reset(new UploadDataStream(request_->upload_data));
+ uint64 upload_len = upload_stream_->size();
+ if (upload_len == 0) {
+ upload_stream_.reset();
+ } else {
+ // TODO(darin): no way to support >4GB uploads w/ WinHttp?
+ if (upload_len > static_cast<uint64>(DWORD(-1))) {
+ NOTREACHED() << "upload length is too large";
+ return ERR_FAILED;
+ }
+
+ total_size = static_cast<DWORD>(upload_len);
+ }
+ }
+
+ if (request_submitted_) {
+ request_submitted_ = false;
+ session_->request_throttle()->NotifyRequestDone(connect_peer_);
+ }
+ if (proxy_info_.is_direct())
+ connect_peer_ = request_->url.GetOrigin().spec();
+ else
+ connect_peer_ = WideToASCII(proxy_info_.proxy_server());
+ DWORD_PTR ctx = reinterpret_cast<DWORD_PTR>(session_callback_.get());
+ if (!session_->request_throttle()->SubmitRequest(connect_peer_,
+ request_handle_,
+ total_size, ctx)) {
+ last_error_ = GetLastError();
+ DLOG(ERROR) << "WinHttpSendRequest failed: " << last_error_;
+ return TranslateOSError(last_error_);
+ }
+
+ request_submitted_ = true;
+ return ERR_IO_PENDING;
+}
+
+// Called after certain failures of SendRequest to reset the members opened
+// or modified in OpenRequest and SendRequest and call OpenRequest again.
+bool HttpTransactionWinHttp::ReopenRequest() {
+ DCHECK(connect_handle_);
+ DCHECK(request_handle_);
+
+ session_callback_->set_handle_closing_event(
+ session_->handle_closing_event());
+ WinHttpCloseHandle(request_handle_);
+ WaitForSingleObject(session_->handle_closing_event(), INFINITE);
+ request_handle_ = NULL;
+ WinHttpCloseHandle(connect_handle_);
+ connect_handle_ = NULL;
+ session_callback_->ResetForNewRequest();
+
+ // Don't need to reset is_https_, rev_checking_enabled_, and
+ // response_.request_time.
+
+ return OpenRequest();
+}
+
+int HttpTransactionWinHttp::DidResolveProxy() {
+ // We may already have a request handle if we are changing proxy config.
+ if (!(request_handle_ ? ReopenRequest() : OpenRequest()))
+ return TranslateLastOSError();
+
+ return SendRequest();
+}
+
+int HttpTransactionWinHttp::DidReceiveError(DWORD error,
+ DWORD secure_failure) {
+ DCHECK(error != ERROR_SUCCESS);
+
+ session_callback_->set_load_state(LOAD_STATE_IDLE);
+ need_to_wait_for_handle_closing_ = false;
+
+ int rv;
+
+ if (error == ERROR_WINHTTP_RESEND_REQUEST) {
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ return Restart(callback);
+ }
+
+ if (error == ERROR_WINHTTP_NAME_NOT_RESOLVED ||
+ error == ERROR_WINHTTP_CANNOT_CONNECT ||
+ error == ERROR_WINHTTP_TIMEOUT) {
+ // These errors may have been caused by a proxy configuration error, or
+ // rather they may go away by trying a different proxy config! If we have
+ // an explicit proxy config, then we just have to report an error.
+ if (!have_proxy_info_) {
+ rv = session_->proxy_service()->ReconsiderProxyAfterError(
+ request_->url, &proxy_info_, &proxy_callback_, &pac_request_);
+ if (rv == OK) // got new proxy info to try
+ return DidResolveProxy();
+ if (rv == ERR_IO_PENDING) // waiting to resolve proxy info
+ return rv;
+ // else, fall through and just report an error.
+ }
+ }
+
+ if (error == ERROR_WINHTTP_SECURE_FAILURE) {
+ DWORD filtered_secure_failure = FilterSecureFailure(secure_failure,
+ load_flags_);
+ // If load_flags_ ignores all the errors in secure_failure, we shouldn't
+ // get the ERROR_WINHTTP_SECURE_FAILURE error.
+ DCHECK(filtered_secure_failure || !secure_failure);
+ error = MapSecureFailureToError(filtered_secure_failure);
+ }
+
+ last_error_ = error;
+ rv = TranslateOSError(error);
+
+ if (rv == ERR_SSL_PROTOCOL_ERROR &&
+ !session_callback_->request_was_probably_sent() &&
+ session_->tls_enabled() && !is_tls_intolerant_) {
+ // The server might be TLS intolerant. Downgrade to SSL 3.0 and retry.
+ is_tls_intolerant_ = true;
+ if (!ReopenRequest())
+ return TranslateLastOSError();
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ return Restart(callback);
+ }
+ if (rv == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ // TODO(wtc): Bug 1230409: We don't support SSL client authentication yet.
+ // For now we set a null client certificate, which works on Vista and
+ // later. On XP, this fails with ERROR_INVALID_PARAMETER (87). This
+ // allows us to access servers that request but do not require client
+ // certificates.
+ if (WinHttpSetOption(request_handle_,
+ WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
+ WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) {
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ return Restart(callback);
+ }
+ }
+ if (IsCertificateError(rv)) {
+ response_.ssl_info.cert = GetServerCertificate();
+ response_.ssl_info.cert_status =
+ MapSecureFailureToCertStatus(secure_failure);
+ CertStatusCache* cert_status_cache = session_->cert_status_cache();
+ cert_status_cache->SetCertStatus(*response_.ssl_info.cert,
+ request_->url.host(),
+ response_.ssl_info.cert_status);
+ }
+
+ return rv;
+}
+
+int HttpTransactionWinHttp::DidSendRequest() {
+ BOOL ok;
+ if (upload_stream_.get() && upload_stream_->buf_len() > 0) {
+ // write upload data
+ DWORD buf_len = static_cast<DWORD>(upload_stream_->buf_len());
+ ok = WinHttpWriteData(request_handle_,
+ upload_stream_->buf(),
+ buf_len,
+ NULL);
+ if (ok)
+ need_to_wait_for_handle_closing_ = true;
+ } else {
+ upload_stream_.reset();
+ need_to_wait_for_handle_closing_ = false;
+
+ // begin receiving the response
+ ok = WinHttpReceiveResponse(request_handle_, NULL);
+ }
+ return ok ? ERR_IO_PENDING : TranslateLastOSError();
+}
+
+int HttpTransactionWinHttp::DidWriteData(DWORD num_bytes) {
+ DCHECK(upload_stream_.get());
+ DCHECK(num_bytes > 0);
+
+ upload_stream_->DidConsume(num_bytes);
+ upload_progress_ = upload_stream_->position();
+
+ // OK, we are ready to start receiving the response. The code in
+ // DidSendRequest does exactly what we want!
+ return DidSendRequest();
+}
+
+int HttpTransactionWinHttp::DidReadData(DWORD num_bytes) {
+ int rv = static_cast<int>(num_bytes);
+ DCHECK(rv >= 0);
+
+ session_callback_->set_load_state(LOAD_STATE_IDLE);
+ session_callback_->reduce_bytes_available(rv);
+ need_to_wait_for_handle_closing_ = false;
+
+ if (content_length_remaining_ > 0) {
+ content_length_remaining_ -= rv;
+
+ // HTTP/1.0 servers are known to send more data than they report in their
+ // Content-Length header (in the non-keepalive case). IE and Moz both
+ // tolerate this situation, and therefore so must we.
+ if (content_length_remaining_ < 0)
+ content_length_remaining_ = 0;
+ }
+
+ return rv;
+}
+
+int HttpTransactionWinHttp::DidReceiveHeaders() {
+ session_callback_->set_load_state(LOAD_STATE_IDLE);
+
+ DWORD size = 0;
+ if (!WinHttpQueryHeaders(request_handle_,
+ WINHTTP_QUERY_RAW_HEADERS,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ NULL,
+ &size,
+ WINHTTP_NO_HEADER_INDEX)) {
+ DWORD error = GetLastError();
+ if (error != ERROR_INSUFFICIENT_BUFFER) {
+ DLOG(ERROR) << "WinHttpQueryHeaders failed: " << GetLastError();
+ return TranslateLastOSError();
+ }
+ // OK, size should tell us how much to allocate...
+ DCHECK(size > 0);
+ }
+
+ std::wstring raw_headers;
+
+ // 'size' is the number of bytes rather than the number of characters.
+ DCHECK(size % 2 == 0);
+ if (!WinHttpQueryHeaders(request_handle_,
+ WINHTTP_QUERY_RAW_HEADERS,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ WriteInto(&raw_headers, size/2 + 1),
+ &size,
+ WINHTTP_NO_HEADER_INDEX)) {
+ DLOG(ERROR) << "WinHttpQueryHeaders failed: " << GetLastError();
+ return TranslateLastOSError();
+ }
+
+ response_.response_time = Time::Now();
+
+ // From experimentation, it appears that WinHttp translates non-ASCII bytes
+ // found in the response headers to UTF-16 assuming that they are encoded
+ // using the default system charset. We attempt to undo that here.
+ response_.headers = new HttpResponseHeaders(WideToNativeMB(raw_headers));
+
+ // WinHTTP truncates a response longer than 2GB. Perhaps it stores the
+ // response's content length in a signed 32-bit integer. We fail rather
+ // than reading a truncated response.
+ if (response_.headers->GetContentLength() > 0x80000000)
+ return ERR_FILE_TOO_BIG;
+
+ response_.vary_data.Init(*request_, *response_.headers);
+ PopulateAuthChallenge();
+
+ // Unfortunately, WinHttp does not close the connection when a non-keepalive
+ // response is _not_ followed by the server closing the connection. So, we
+ // attempt to hack around this bug.
+ if (!response_.headers->IsKeepAlive())
+ content_length_remaining_ = response_.headers->GetContentLength();
+
+ return OK;
+}
+
+// Populates response_.auth_challenge with the authentication challenge info.
+void HttpTransactionWinHttp::PopulateAuthChallenge() {
+ DCHECK(response_.headers);
+
+ int status = response_.headers->response_code();
+ if (status != 401 && status != 407)
+ return;
+
+ scoped_refptr<AuthChallengeInfo> auth_info = new AuthChallengeInfo;
+
+ auth_info->is_proxy = (status == 407);
+
+ if (auth_info->is_proxy) {
+ // TODO(wtc): get the proxy server host from proxy_info_.
+ // TODO(wtc): internationalize?
+ auth_info->host = L"proxy";
+ } else {
+ auth_info->host = ASCIIToWide(request_->url.host());
+ }
+
+ // Here we're checking only the first *-Authenticate header. When a server
+ // responds with multiple methods, we use the first.
+ // TODO(wtc): Bug 1124614: look at all the authentication methods and pick
+ // the best one that we support. failover to other authentication methods.
+ std::string header_value;
+ std::string header_name = auth_info->is_proxy ?
+ "Proxy-Authenticate" : "WWW-Authenticate";
+ if (!response_.headers->EnumerateHeader(NULL, header_name, &header_value))
+ return;
+
+ // TODO(darin): Need to support RFC 2047 encoded realm strings. For now, we
+ // just match Mozilla and limit our support to ASCII realm strings.
+ std::wstring auth_header = ASCIIToWide(header_value);
+
+ // auth_header is a string which looks like:
+ // Digest realm="The Awesome Site", domain="/page.html", ...
+ std::wstring::const_iterator space = find(auth_header.begin(),
+ auth_header.end(), ' ');
+ auth_info->scheme.assign(auth_header.begin(), space);
+ auth_info->realm = net_util::GetHeaderParamValue(auth_header, L"realm");
+
+ // Now auth_info has been fully populated. Before we swap it with
+ // response_.auth_challenge, update the auth cache key and remove any
+ // presumably incorrect auth data in the auth cache.
+ std::string* auth_cache_key;
+ AuthData* auth;
+ if (auth_info->is_proxy) {
+ if (!proxy_auth_)
+ proxy_auth_ = new AuthData;
+ auth = proxy_auth_;
+ auth_cache_key = &proxy_auth_cache_key_;
+ } else {
+ if (!server_auth_)
+ server_auth_ = new AuthData;
+ auth = server_auth_;
+ auth_cache_key = &server_auth_cache_key_;
+ }
+ *auth_cache_key = AuthCache::HttpKey(request_->url, *auth_info);
+ DCHECK(!auth_cache_key->empty());
+ auth->scheme = auth_info->scheme;
+ if (auth->state == AUTH_STATE_HAVE_AUTH) {
+ session_->auth_cache()->Remove(*auth_cache_key);
+ auth->state = AUTH_STATE_NEED_AUTH;
+ }
+
+ response_.auth_challenge.swap(auth_info);
+}
+
+static DWORD StringToAuthScheme(const std::wstring& scheme) {
+ if (LowerCaseEqualsASCII(scheme, "basic"))
+ return WINHTTP_AUTH_SCHEME_BASIC;
+ if (LowerCaseEqualsASCII(scheme, "digest"))
+ return WINHTTP_AUTH_SCHEME_DIGEST;
+ if (LowerCaseEqualsASCII(scheme, "ntlm"))
+ return WINHTTP_AUTH_SCHEME_NTLM;
+ if (LowerCaseEqualsASCII(scheme, "negotiate"))
+ return WINHTTP_AUTH_SCHEME_NEGOTIATE;
+ if (LowerCaseEqualsASCII(scheme, "passport1.4"))
+ return WINHTTP_AUTH_SCHEME_PASSPORT;
+ return 0;
+}
+
+// Applies authentication credentials to request_handle_.
+void HttpTransactionWinHttp::ApplyAuth() {
+ DWORD auth_scheme;
+ BOOL rv;
+ if (proxy_auth_ && proxy_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ // Add auth data to cache.
+ DCHECK(!proxy_auth_cache_key_.empty());
+ session_->auth_cache()->Add(proxy_auth_cache_key_, proxy_auth_);
+ auth_scheme = StringToAuthScheme(proxy_auth_->scheme);
+ if (auth_scheme == 0)
+ return;
+
+ rv = WinHttpSetCredentials(request_handle_,
+ WINHTTP_AUTH_TARGET_PROXY,
+ auth_scheme,
+ proxy_auth_->username.c_str(),
+ proxy_auth_->password.c_str(),
+ NULL);
+ }
+
+ // First, check if we have auth, then check URL.
+ // That way a user can re-enter credentials, and we'll try with their
+ // latest input rather than always trying what they specified
+ // in the URL (if anything).
+ if (server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ // Add auth data to cache.
+ DCHECK(!server_auth_cache_key_.empty());
+ session_->auth_cache()->Add(server_auth_cache_key_, server_auth_);
+ auth_scheme = StringToAuthScheme(server_auth_->scheme);
+ if (auth_scheme == 0)
+ return;
+
+ rv = WinHttpSetCredentials(request_handle_,
+ WINHTTP_AUTH_TARGET_SERVER,
+ auth_scheme,
+ server_auth_->username.c_str(),
+ server_auth_->password.c_str(),
+ NULL);
+ } else if (request_->url.has_username()) {
+ // TODO(wtc) It may be necessary to unescape the username and password
+ // after extracting them from the URL. We should be careful about
+ // embedded nulls in that case.
+ rv = WinHttpSetCredentials(request_handle_,
+ WINHTTP_AUTH_TARGET_SERVER,
+ WINHTTP_AUTH_SCHEME_BASIC, // TODO(wtc)
+ ASCIIToWide(request_->url.username()).c_str(),
+ ASCIIToWide(request_->url.password()).c_str(),
+ NULL);
+ }
+}
+
+void HttpTransactionWinHttp::OnProxyInfoAvailable(int result) {
+ if (result != OK) {
+ DLOG(WARNING) << "failed to get proxy info: " << result;
+ proxy_info_.UseDirect();
+ }
+
+ // Balances extra reference taken when proxy resolution was initiated.
+ session_callback_->Release();
+
+ pac_request_ = NULL;
+
+ // Since OnProxyInfoAvailable is always called asynchronously (via the
+ // message loop), we need to trap any errors and pass them to the consumer
+ // via their completion callback.
+
+ int rv = DidResolveProxy();
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->AddRef(); // balanced when callback runs.
+ } else {
+ DoCallback(rv);
+ }
+}
+
+std::string HttpTransactionWinHttp::GetRequestHeaders() const {
+ std::string headers;
+
+ if (!request_->user_agent.empty())
+ headers += "User-Agent: " + request_->user_agent + "\r\n";
+
+ // Our consumer should have made sure that this is a safe referrer. See for
+ // instance WebCore::FrameLoader::HideReferrer.
+ if (request_->referrer.is_valid())
+ headers += "Referer: " + request_->referrer.spec() + "\r\n";
+
+ // IE and Safari do this. 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.
+ if (request_->method == "HEAD")
+ headers += "Content-Length: 0\r\n";
+
+ // Honor load flags that impact proxy caches.
+ if (request_->load_flags & LOAD_BYPASS_CACHE) {
+ headers += "Pragma: no-cache\r\nCache-Control: no-cache\r\n";
+ } else if (request_->load_flags & LOAD_VALIDATE_CACHE) {
+ headers += "Cache-Control: max-age=0\r\n";
+ }
+
+ // TODO(darin): Prune out duplicate headers?
+ headers += request_->extra_headers;
+
+ return headers;
+}
+
+// Retrieves the SSL server certificate associated with the transaction.
+// The caller is responsible for freeing the certificate.
+X509Certificate* HttpTransactionWinHttp::GetServerCertificate() const {
+ DCHECK(is_https_);
+ PCCERT_CONTEXT cert_context = NULL;
+ DWORD length = sizeof(cert_context);
+ if (!WinHttpQueryOption(request_handle_,
+ WINHTTP_OPTION_SERVER_CERT_CONTEXT,
+ &cert_context,
+ &length)) {
+ return NULL;
+ }
+ // cert_context may be NULL here even though WinHttpQueryOption succeeded.
+ // For example, a proxy server may return a 404 error page to report the
+ // DNS resolution failure of the server's hostname.
+ if (!cert_context)
+ return NULL;
+ return X509Certificate::CreateFromHandle(cert_context);
+}
+
+// Retrieves the security strength, in bits, of the SSL cipher suite
+// associated with the transaction.
+int HttpTransactionWinHttp::GetSecurityBits() const {
+ DCHECK(is_https_);
+ DWORD key_bits = 0;
+ DWORD length = sizeof(key_bits);
+ if (!WinHttpQueryOption(request_handle_,
+ WINHTTP_OPTION_SECURITY_KEY_BITNESS,
+ &key_bits,
+ &length)) {
+ return -1;
+ }
+ return key_bits;
+}
+
+void HttpTransactionWinHttp::PopulateSSLInfo(DWORD secure_failure) {
+ if (is_https_) {
+ response_.ssl_info.cert = GetServerCertificate();
+ response_.ssl_info.security_bits = GetSecurityBits();
+ // If there is no cert (such as when the proxy server makes up a
+ // 404 response to report a server name resolution error), don't set
+ // the cert status.
+ if (!response_.ssl_info.cert)
+ return;
+ response_.ssl_info.cert_status =
+ MapSecureFailureToCertStatus(secure_failure);
+ // WinHTTP does not always return a cert status once we ignored errors
+ // for a cert. (Our experiments showed that WinHTTP reliably returns a
+ // cert status only when there are unignored errors or when we resend a
+ // request with the errors ignored.) So we have to remember what the
+ // last status was for a cert. Note that if the cert status changes
+ // from error to OK, we won't know that. If we have never stored our
+ // status in the CertStatusCache (meaning no errors so far), then it is
+ // OK (0).
+ CertStatusCache* cert_status_cache = session_->cert_status_cache();
+ if (net::IsCertStatusError(response_.ssl_info.cert_status)) {
+ cert_status_cache->SetCertStatus(*response_.ssl_info.cert,
+ request_->url.host(),
+ response_.ssl_info.cert_status);
+ } else {
+ response_.ssl_info.cert_status |=
+ cert_status_cache->GetCertStatus(*response_.ssl_info.cert,
+ request_->url.host()) &
+ net::CERT_STATUS_ALL_ERRORS;
+ }
+
+ if (rev_checking_enabled_)
+ response_.ssl_info.cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+ } else {
+ // If this is not https, we should not get a cert status.
+ DCHECK(!secure_failure);
+ }
+}
+
+void HttpTransactionWinHttp::HandleStatusCallback(DWORD status,
+ DWORD_PTR result,
+ DWORD error,
+ DWORD secure_failure) {
+ int rv = ERR_FAILED;
+
+ switch (status) {
+ case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
+ rv = DidReceiveError(error, secure_failure);
+ break;
+ case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
+ PopulateSSLInfo(secure_failure);
+ rv = DidSendRequest();
+ break;
+ case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
+ rv = DidWriteData(static_cast<DWORD>(result));
+ break;
+ case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
+ rv = DidReceiveHeaders();
+ break;
+ case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
+ rv = DidReadData(static_cast<DWORD>(result));
+ break;
+ default:
+ NOTREACHED() << "unexpected status code";
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->AddRef(); // balanced when callback runs.
+ } else if (callback_) {
+ DoCallback(rv);
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_transaction_winhttp.h b/net/http/http_transaction_winhttp.h
new file mode 100644
index 0000000..bb8ec1f
--- /dev/null
+++ b/net/http/http_transaction_winhttp.h
@@ -0,0 +1,222 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_TRANSACTION_WINHTTP_H__
+#define NET_HTTP_HTTP_TRANSACTION_WINHTTP_H__
+
+#include <windows.h>
+#include <winhttp.h>
+
+#include <string>
+
+#include "base/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/http/http_proxy_service.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_factory.h"
+
+namespace net {
+
+class UploadDataStream;
+
+class HttpTransactionWinHttp : public HttpTransaction {
+ class Session; // Represents a WinHttp session handle.
+ class SessionCallback;
+ public:
+ // Instantiate this class, and use it to create HttpTransaction objects.
+ class Factory : public HttpTransactionFactory {
+ public:
+ Factory() : session_(NULL), proxy_info_(NULL), is_suspended_(false) {}
+ explicit Factory(const HttpProxyInfo* info)
+ : session_(NULL), proxy_info_(NULL), is_suspended_(false) {
+ if (info) {
+ proxy_info_.reset(new HttpProxyInfo());
+ proxy_info_->Use(*info);
+ }
+ }
+ ~Factory();
+
+ virtual HttpTransaction* CreateTransaction();
+ virtual HttpCache* GetCache();
+ virtual AuthCache* GetAuthCache();
+ virtual void Suspend(bool suspend);
+
+ private:
+ Session* session_;
+ scoped_ptr<HttpProxyInfo> proxy_info_;
+ bool is_suspended_;
+ DISALLOW_EVIL_CONSTRUCTORS(Factory);
+ };
+
+ // HttpTransaction methods:
+ virtual void Destroy();
+ virtual int Start(const HttpRequestInfo*, CompletionCallback*);
+ virtual int RestartIgnoringLastError(CompletionCallback*);
+ virtual int RestartWithAuth(const std::wstring&,
+ const std::wstring&,
+ CompletionCallback*);
+ virtual int Read(char*, int, CompletionCallback*);
+ virtual const HttpResponseInfo* GetResponseInfo() const;
+ virtual LoadState GetLoadState() const;
+ virtual uint64 GetUploadProgress() const;
+
+ static void CALLBACK StatusCallback(HINTERNET handle,
+ DWORD_PTR context,
+ DWORD status,
+ LPVOID status_info,
+ DWORD status_info_len);
+
+ // Called via the message loop in response to a WinHttp status callback.
+ void HandleStatusCallback(DWORD status,
+ DWORD_PTR result,
+ DWORD error,
+ DWORD secure_failure);
+
+ private:
+ friend class Factory;
+
+ // Methods ------------------------------------------------------------------
+
+ HttpTransactionWinHttp(Session* session, const HttpProxyInfo* info);
+ ~HttpTransactionWinHttp();
+
+ void DoCallback(int rv);
+ int ResolveProxy();
+ bool OpenRequest();
+ int SendRequest();
+ bool ReopenRequest();
+ int Restart(CompletionCallback* callback);
+ int DidResolveProxy();
+ int DidReceiveError(DWORD error, DWORD secure_failure);
+ int DidSendRequest();
+ int DidWriteData(DWORD num_bytes);
+ int DidReadData(DWORD num_bytes);
+ int DidReceiveHeaders();
+
+ void PopulateAuthChallenge();
+ void ApplyAuth();
+
+ std::string GetRequestHeaders() const;
+ X509Certificate* GetServerCertificate() const;
+ int GetSecurityBits() const;
+ void PopulateSSLInfo(DWORD secure_failure);
+
+ void OnProxyInfoAvailable(int result);
+
+ // Variables ----------------------------------------------------------------
+
+ Session* session_;
+ const HttpRequestInfo* request_;
+
+ // A copy of request_->load_flags that we can modify in
+ // RestartIgnoringLastError.
+ int load_flags_;
+
+ // Optional auth data for proxy and origin server.
+ scoped_refptr<AuthData> proxy_auth_;
+ scoped_refptr<AuthData> server_auth_;
+
+ // The key for looking up the auth data in the auth cache, consisting
+ // of the scheme, host, and port of the request URL and the realm in
+ // the auth challenge.
+ std::string proxy_auth_cache_key_;
+ std::string server_auth_cache_key_;
+
+ // The peer of the connection. For a direct connection, this is the
+ // destination server. If we use a proxy, this is the proxy.
+ std::string connect_peer_;
+
+ // The last error from SendRequest that occurred. Used by
+ // RestartIgnoringLastError to adjust load_flags_ to ignore this error.
+ DWORD last_error_;
+
+ // This value is non-negative when we are streaming a response over a
+ // non-keepalive connection. We decrement this value as we receive data to
+ // allow us to discover end-of-file. This is used to workaround a bug in
+ // WinHttp (see bug 1063336).
+ int64 content_length_remaining_;
+
+ HttpProxyInfo proxy_info_;
+ HttpProxyService::PacRequest* pac_request_;
+ CompletionCallbackImpl<HttpTransactionWinHttp> proxy_callback_;
+
+ HttpResponseInfo response_;
+ CompletionCallback* callback_;
+ HINTERNET connect_handle_;
+ HINTERNET request_handle_;
+ scoped_refptr<SessionCallback> session_callback_;
+ scoped_ptr<UploadDataStream> upload_stream_;
+ uint64 upload_progress_;
+
+ // True if the URL's scheme is https.
+ bool is_https_;
+
+ // True if the SSL server doesn't support TLS but also cannot correctly
+ // negotiate with a TLS-enabled client to use SSL 3.0. The workaround is
+ // for the client to downgrade to SSL 3.0 and retry the SSL handshake.
+ bool is_tls_intolerant_;
+
+ // True if revocation checking of the SSL server certificate is enabled.
+ bool rev_checking_enabled_;
+
+ // A flag to indicate whether or not we already have proxy information.
+ // If false, we will attempt to resolve proxy information from the proxy
+ // service. This flag is set to true if proxy information is supplied by
+ // a client.
+ bool have_proxy_info_;
+
+ // If WinHTTP is still using our caller's data (upload data or read buffer),
+ // we need to wait for the HANDLE_CLOSING status notification after we close
+ // the request handle.
+ //
+ // There are only five WinHTTP functions that work asynchronously (listed in
+ // the order in which they're called):
+ // WinHttpSendRequest, WinHttpWriteData, WinHttpReceiveResponse,
+ // WinHttpQueryDataAvailable, WinHttpReadData.
+ // WinHTTP is using our caller's data during the two time intervals:
+ // - From the first WinHttpWriteData call to the completion of the last
+ // WinHttpWriteData call. (We may call WinHttpWriteData multiple times.)
+ // - From the WinHttpReadData call to its completion.
+ // We set need_to_wait_for_handle_closing_ to true at the beginning of these
+ // time intervals and set it to false at the end. We're not sandwiching the
+ // intervals as tightly as possible. (To do that, we'd need to give WinHTTP
+ // worker threads access to the need_to_wait_for_handle_closing_ flag and
+ // worry about thread synchronization issues.)
+ bool need_to_wait_for_handle_closing_;
+
+ // True if we have called WinHttpRequestThrottle::SubmitRequest.
+ bool request_submitted_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HttpTransactionWinHttp);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_TRANSACTION_WINHTTP_H__
diff --git a/net/http/http_transaction_winhttp_unittest.cc b/net/http/http_transaction_winhttp_unittest.cc
new file mode 100644
index 0000000..cbb07f9
--- /dev/null
+++ b/net/http/http_transaction_winhttp_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_transaction_winhttp.h"
+#include "net/http/http_transaction_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(HttpTransactionWinHttp, CreateAndDestroy) {
+ net::HttpTransactionWinHttp::Factory factory;
+
+ net::HttpTransaction* trans = factory.CreateTransaction();
+ trans->Destroy();
+}
+
+TEST(HttpTransactionWinHttp, Suspend) {
+ net::HttpTransactionWinHttp::Factory factory;
+
+ net::HttpTransaction* trans = factory.CreateTransaction();
+ trans->Destroy();
+
+ factory.Suspend(true);
+
+ trans = factory.CreateTransaction();
+ ASSERT_TRUE(trans == NULL);
+
+ factory.Suspend(false);
+
+ trans = factory.CreateTransaction();
+ trans->Destroy();
+}
+
+TEST(HttpTransactionWinHttp, GoogleGET) {
+ net::HttpTransactionWinHttp::Factory factory;
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = factory.CreateTransaction();
+
+ net::HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.user_agent = "Foo/1.0";
+ request_info.load_flags = net::LOAD_NORMAL;
+
+ int rv = trans->Start(&request_info, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ std::string contents;
+ rv = ReadTransaction(trans, &contents);
+ EXPECT_EQ(net::OK, rv);
+
+ trans->Destroy();
+}
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
new file mode 100644
index 0000000..a56a4db
--- /dev/null
+++ b/net/http/http_util.cc
@@ -0,0 +1,358 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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/logging.h"
+#include "base/string_util.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) {
+ // 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;
+
+ // Iterate over parameters
+ bool type_has_charset = false;
+ size_t param_start = content_type_str.find_first_of(';', type_end);
+ if (param_start != string::npos) {
+ // We have parameters. Iterate over them.
+ size_t cur_param_start = param_start + 1;
+ do {
+ size_t cur_param_end =
+ FindDelimiter(content_type_str, cur_param_start, ';');
+
+ size_t param_name_start = content_type_str.find_first_not_of(HTTP_LWS,
+ cur_param_start);
+ param_name_start = std::min(param_name_start, cur_param_end);
+
+ static const char charset_str[] = "charset=";
+ size_t charset_end_offset = std::min(param_name_start +
+ sizeof(charset_str) - 1, cur_param_end);
+ if (LowerCaseEqualsASCII(content_type_str.begin() + param_name_start,
+ content_type_str.begin() + charset_end_offset, charset_str)) {
+ charset_val = param_name_start + sizeof(charset_str) - 1;
+ charset_end = cur_param_end;
+ type_has_charset = true;
+ }
+
+ cur_param_start = cur_param_end + 1;
+ } while (cur_param_start < content_type_str.length());
+ }
+
+ 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(content_type_str.begin() + type_val,
+ content_type_str.begin() + type_end,
+ mime_type->data());
+ if (!eq) {
+ mime_type->assign(content_type_str.begin() + type_val,
+ content_type_str.begin() + type_end);
+ StringToLowerASCII(mime_type);
+ }
+ if ((!eq && *had_charset) || type_has_charset) {
+ *had_charset = true;
+ charset->assign(content_type_str.begin() + charset_val,
+ content_type_str.begin() + charset_end);
+ StringToLowerASCII(charset);
+ }
+ }
+}
+
+// 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,
+ 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;
+}
+
+// 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"
+ };
+ for (size_t i = 0; i < arraysize(kNonCoalescingHeaders); ++i) {
+ if (LowerCaseEqualsASCII(name_begin, name_end, kNonCoalescingHeaders[i]))
+ return true;
+ }
+ return false;
+}
+
+void HttpUtil::TrimLWS(string::const_iterator* begin,
+ string::const_iterator* end) {
+ // leading whitespace
+ while (*begin < *end && strchr(HTTP_LWS, (*begin)[0]))
+ ++(*begin);
+
+ // trailing whitespace
+ while (*begin < *end && strchr(HTTP_LWS, (*end)[-1]))
+ --(*end);
+}
+
+int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len) {
+ bool was_lf = false;
+ char last_c = '\0';
+ for (int i = 0; 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;
+}
+
+std::string HttpUtil::AssembleRawHeaders(const char* buf, int buf_len) {
+ std::string raw_headers;
+
+ // TODO(darin):
+ // - Handle header line continuations.
+ // - Be careful about CRs that appear spuriously mid header line.
+
+ int line_start = 0;
+ for (int i = 0; i < buf_len; ++i) {
+ char c = buf[i];
+ if (c == '\r' || c == '\n') {
+ if (line_start != i) {
+ // (line_start,i) is a header line.
+ raw_headers.append(buf + line_start, buf + i);
+ raw_headers.push_back('\0');
+ }
+ line_start = i + 1;
+ }
+ }
+ raw_headers.push_back('\0');
+
+ return raw_headers;
+}
+
+// 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) {
+}
+
+bool HttpUtil::HeadersIterator::GetNext() {
+ while (lines_.GetNext()) {
+ name_begin_ = lines_.token_begin();
+ values_end_ = lines_.token_end();
+
+ string::const_iterator colon = find(name_begin_, values_end_, ':');
+ if (colon == values_end_)
+ continue; // skip malformed header
+
+ name_end_ = colon;
+ 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;
+}
+
+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("\'\"");
+}
+
+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;
+}
+
+} // namespace net
diff --git a/net/http/http_util.h b/net/http/http_util.h
new file mode 100644
index 0000000..f7802a7
--- /dev/null
+++ b/net/http/http_util.h
@@ -0,0 +1,173 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_UTIL_H_
+#define NET_HTTP_HTTP_UTIL_H_
+
+#include "base/string_tokenizer.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 HttpUtil {
+ public:
+ // 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.
+ static void ParseContentType(const std::string& content_type_str,
+ std::string* mime_type,
+ std::string* charset,
+ bool *had_charset);
+
+ // 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);
+
+ // 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());
+ }
+
+ // 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);
+
+ // 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).
+ static int LocateEndOfHeaders(const char* buf, int buf_len);
+
+ // 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.
+ static std::string AssembleRawHeaders(const char* buf, int buf_len);
+
+ // Used to iterate over the name/value pairs of HTTP headers. To iterate
+ // over the values in a multi-value header, use ValuesIterator.
+ class HeadersIterator {
+ public:
+ HeadersIterator(std::string::const_iterator headers_begin,
+ std::string::const_iterator headers_end,
+ const std::string& line_delimiter);
+
+ // 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();
+
+ 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:
+ 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_;
+ };
+
+ // Used to iterate over deliminated values in a HTTP header. HTTP LWS is
+ // automatically trimmed from the resulting values.
+ //
+ // When using this class to iterate over response header values, beware 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 ValuesIterator {
+ public:
+ ValuesIterator(std::string::const_iterator values_begin,
+ std::string::const_iterator values_end,
+ char delimiter);
+
+ // 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:
+ StringTokenizer values_;
+ std::string::const_iterator value_begin_;
+ std::string::const_iterator value_end_;
+ };
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_UTIL_H_
diff --git a/net/http/http_util_unittest.cc b/net/http/http_util_unittest.cc
new file mode 100644
index 0000000..bfadb30
--- /dev/null
+++ b/net/http/http_util_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+
+#include "base/basictypes.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, 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(tests); ++i) {
+ bool result = HttpUtil::HasHeader(tests[i].headers, tests[i].name);
+ EXPECT_EQ(tests[i].expected_result, result);
+ }
+}
+
+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, 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, 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(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;
+ 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||" },
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ int input_len = static_cast<int>(strlen(tests[i].input));
+ std::string raw = HttpUtil::AssembleRawHeaders(tests[i].input, input_len);
+ std::replace(raw.begin(), raw.end(), '\0', '|');
+ EXPECT_TRUE(raw == tests[i].expected_result);
+ }
+}
diff --git a/net/http/http_vary_data.cc b/net/http/http_vary_data.cc
new file mode 100644
index 0000000..b10f279
--- /dev/null
+++ b/net/http/http_vary_data.cc
@@ -0,0 +1,167 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_vary_data.h"
+
+#include <stdlib.h>
+
+#include "base/pickle.h"
+#include "base/string_util.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) {
+ memset(&request_digest_, 0, sizeof(request_digest_));
+}
+
+bool HttpVaryData::Init(const HttpRequestInfo& request_info,
+ const HttpResponseHeaders& response_headers) {
+ MD5Context ctx;
+ MD5Init(&ctx);
+
+ 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;
+
+ MD5Final(&request_digest_, &ctx);
+ return is_valid_ = true;
+}
+
+bool HttpVaryData::InitFromPickle(const Pickle& pickle, void** iter) {
+ 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 {
+ 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) {
+ // Some special cases:
+ if (LowerCaseEqualsASCII(request_header, "referer"))
+ return request_info.referrer.spec();
+ if (LowerCaseEqualsASCII(request_header, "user-agent"))
+ return request_info.user_agent;
+
+ std::string result;
+
+ // Check extra headers:
+ HttpUtil::HeadersIterator it(request_info.extra_headers.begin(),
+ request_info.extra_headers.end(),
+ "\r\n");
+ while (it.GetNext()) {
+ size_t name_len = it.name_end() - it.name_begin();
+ if (request_header.size() == name_len &&
+ std::equal(it.name_begin(), it.name_end(), request_header.begin(),
+ CaseInsensitiveCompare<char>())) {
+ if (!result.empty())
+ result.append(1, ',');
+ result.append(it.values());
+ }
+ }
+
+ // 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.
+
+ return result;
+}
+
+// static
+void HttpVaryData::AddField(const HttpRequestInfo& request_info,
+ const std::string& request_header,
+ 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');
+
+ MD5Update(ctx, request_value.data(), request_value.size());
+}
+
+} // namespace net
diff --git a/net/http/http_vary_data.h b/net/http/http_vary_data.h
new file mode 100644
index 0000000..ff4f753
--- /dev/null
+++ b/net/http/http_vary_data.h
@@ -0,0 +1,109 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_HTTP_VARY_DATA_H__
+#define NET_HTTP_HTTP_VARY_DATA_H__
+
+#include "base/md5.h"
+#include "base/ref_counted.h"
+
+class Pickle;
+
+namespace net {
+
+class 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 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
+ // was not modified.
+ //
+ 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 was not modified.
+ //
+ bool InitFromPickle(const Pickle& pickle, void** pickle_iter);
+
+ // Call this method to persist the vary data.
+ 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,
+ MD5Context* context);
+
+ // A digested version of the request headers corresponding to the Vary header.
+ 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/net/http/http_vary_data_unittest.cc b/net/http/http_vary_data_unittest.cc
new file mode 100644
index 0000000..553a023
--- /dev/null
+++ b/net/http/http_vary_data_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#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 = request_headers;
+ }
+};
+
+} // namespace
+
+TEST(HttpVaryDataTest, IsValid) {
+ // 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("", kTestResponses[i]);
+
+ net::HttpVaryData v;
+ EXPECT_FALSE(v.Init(t.request, *t.response));
+ 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));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response));
+}
+
+TEST(HttpVaryDataTest, DoesVary2) {
+ TestTransaction a;
+ a.Init("Foo: 1\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+
+ TestTransaction b;
+ b.Init("Foo: 12\nbar: 3", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response));
+}
+
+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));
+
+ EXPECT_TRUE(v.MatchesRequest(b.request, *b.response));
+}
+
+TEST(HttpVaryDataTest, DoesntVary2) {
+ TestTransaction a;
+ a.Init("Foo: 1\nbAr: 2", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+
+ TestTransaction b;
+ b.Init("Foo: 1\nbaR: 2", "HTTP/1.1 200 OK\nVary: foo\nVary: bar\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response));
+
+ EXPECT_TRUE(v.MatchesRequest(b.request, *b.response));
+}
+
+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));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response));
+}
+
+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));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response));
+}
diff --git a/net/http/winhttp_request_throttle.cc b/net/http/winhttp_request_throttle.cc
new file mode 100644
index 0000000..b8eeaf2
--- /dev/null
+++ b/net/http/winhttp_request_throttle.cc
@@ -0,0 +1,200 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/winhttp_request_throttle.h"
+
+#include "base/logging.h"
+#include "net/http/http_transaction_winhttp.h"
+
+namespace {
+
+// The arguments to a WinHttpSendRequest call.
+struct SendRequestArgs {
+ SendRequestArgs() : request_handle(NULL), total_size(0), context(0) {}
+
+ SendRequestArgs(HINTERNET handle, DWORD size, DWORD_PTR context_value)
+ : request_handle(handle), total_size(size), context(context_value) {}
+
+ HINTERNET request_handle;
+ DWORD total_size;
+ DWORD_PTR context;
+};
+
+} // namespace
+
+namespace net {
+
+// Per-server queue for WinHttpSendRequest calls.
+class WinHttpRequestThrottle::RequestQueue {
+ public:
+ RequestQueue() {}
+
+ // Adds |args| to the end of the queue.
+ void PushBack(const SendRequestArgs& args) { queue_.push_back(args); }
+
+ // If the queue is not empty, pops the first entry off the queue, saves it
+ // in |*args|, and returns true. If the queue is empty, returns false.
+ bool GetFront(SendRequestArgs* args);
+
+ // If the queue has an entry containing |request_handle|, removes it and
+ // returns true. Otherwise, returns false.
+ bool Remove(HINTERNET request_handle);
+
+ bool empty() const { return queue_.empty(); }
+
+ private:
+ std::list<SendRequestArgs> queue_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(RequestQueue);
+};
+
+bool WinHttpRequestThrottle::RequestQueue::GetFront(SendRequestArgs* args) {
+ if (queue_.empty())
+ return false;
+ *args = queue_.front();
+ queue_.pop_front();
+ return true;
+}
+
+bool WinHttpRequestThrottle::RequestQueue::Remove(HINTERNET request_handle) {
+ std::list<SendRequestArgs>::iterator it;
+ for (it = queue_.begin(); it != queue_.end(); ++it) {
+ if (it->request_handle == request_handle) {
+ queue_.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+WinHttpRequestThrottle::~WinHttpRequestThrottle() {
+#ifndef NDEBUG
+ ThrottleMap::const_iterator throttle_iter = throttles_.begin();
+ for (; throttle_iter != throttles_.end(); ++throttle_iter) {
+ const PerServerThrottle& throttle = throttle_iter->second;
+ DCHECK(throttle.num_requests == 0);
+ DCHECK(!throttle.request_queue.get() || throttle.request_queue->empty());
+ }
+#endif
+}
+
+BOOL WinHttpRequestThrottle::SubmitRequest(const std::string &server,
+ HINTERNET request_handle,
+ DWORD total_size,
+ DWORD_PTR context) {
+ PerServerThrottle& throttle = throttles_[server];
+ DCHECK(throttle.num_requests >= 0 &&
+ throttle.num_requests <= kMaxConnectionsPerServer);
+ if (throttle.num_requests >= kMaxConnectionsPerServer) {
+ if (!throttle.request_queue.get())
+ throttle.request_queue.reset(new RequestQueue);
+ SendRequestArgs args(request_handle, total_size, context);
+ throttle.request_queue->PushBack(args);
+ return TRUE;
+ }
+
+ BOOL ok = SendRequest(request_handle, total_size, context, false);
+ if (ok)
+ throttle.num_requests += 1;
+ return ok;
+}
+
+void WinHttpRequestThrottle::NotifyRequestDone(const std::string& server) {
+ PerServerThrottle& throttle = throttles_[server];
+ DCHECK(throttle.num_requests > 0 &&
+ throttle.num_requests <= kMaxConnectionsPerServer);
+ throttle.num_requests -= 1;
+ SendRequestArgs args;
+ if (throttle.request_queue.get() &&
+ throttle.request_queue->GetFront(&args)) {
+ throttle.num_requests += 1;
+ SendRequest(args.request_handle, args.total_size, args.context, true);
+ }
+ if (throttles_.size() > static_cast<size_t>(kGarbageCollectionThreshold))
+ GarbageCollect();
+}
+
+void WinHttpRequestThrottle::RemoveRequest(const std::string& server,
+ HINTERNET request_handle) {
+ PerServerThrottle& throttle = throttles_[server];
+ if (throttle.request_queue.get() &&
+ throttle.request_queue->Remove(request_handle))
+ return;
+ NotifyRequestDone(server);
+}
+
+BOOL WinHttpRequestThrottle::SendRequest(HINTERNET request_handle,
+ DWORD total_size,
+ DWORD_PTR context,
+ bool report_async_error) {
+ BOOL ok = WinHttpSendRequest(request_handle,
+ WINHTTP_NO_ADDITIONAL_HEADERS,
+ 0,
+ WINHTTP_NO_REQUEST_DATA,
+ 0,
+ total_size,
+ context);
+ if (!ok && report_async_error) {
+ WINHTTP_ASYNC_RESULT async_result = { API_SEND_REQUEST, GetLastError() };
+ HttpTransactionWinHttp::StatusCallback(
+ request_handle, context,
+ WINHTTP_CALLBACK_STATUS_REQUEST_ERROR,
+ &async_result, sizeof(async_result));
+ }
+ return ok;
+}
+
+WinHttpRequestThrottle::PerServerThrottle::PerServerThrottle()
+ : num_requests(0) {
+}
+
+WinHttpRequestThrottle::PerServerThrottle::~PerServerThrottle() {
+}
+
+// static
+const int WinHttpRequestThrottle::kMaxConnectionsPerServer = 6;
+
+// static
+const int WinHttpRequestThrottle::kGarbageCollectionThreshold = 64;
+
+void WinHttpRequestThrottle::GarbageCollect() {
+ ThrottleMap::iterator throttle_iter = throttles_.begin();
+ while (throttle_iter != throttles_.end()) {
+ PerServerThrottle& throttle = throttle_iter->second;
+ if (throttle.num_requests == 0 &&
+ (!throttle.request_queue.get() || throttle.request_queue->empty())) {
+ // Erase the current item but keep the iterator valid.
+ throttles_.erase(throttle_iter++);
+ } else {
+ ++throttle_iter;
+ }
+ }
+}
+
+} // namespace net
diff --git a/net/http/winhttp_request_throttle.h b/net/http/winhttp_request_throttle.h
new file mode 100644
index 0000000..0bca7e5
--- /dev/null
+++ b/net/http/winhttp_request_throttle.h
@@ -0,0 +1,128 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_HTTP_WINHTTP_REQUEST_THROTTLE_H_
+#define NET_HTTP_WINHTTP_REQUEST_THROTTLE_H_
+
+#include <windows.h>
+#include <winhttp.h>
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/linked_ptr.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace net {
+
+// The WinHttpRequestThrottle class regulates the rate at which we call
+// WinHttpSendRequest, ensuring that at any time there are at most 6 WinHTTP
+// requests in progress for each server or proxy.
+//
+// The throttling is intended to cause WinHTTP to maintain at most 6
+// persistent HTTP connections with each server or proxy. This works well in
+// most cases, except when making HTTPS requests via a proxy, in which case
+// WinHTTP may open much more than 6 connections to the proxy in spite of our
+// rate limiting.
+//
+// Because we identify a server by its hostname rather than its IP address,
+// we also can't distinguish between two different hostnames that resolve to
+// the same IP address.
+//
+// Although WinHTTP has the WINHTTP_OPTION_MAX_CONNS_PER_SERVER option to
+// limit the number of connections allowed per server, we can't use it
+// because it has two serious bugs:
+// 1. It causes WinHTTP to not close idle persistent connections, leaving
+// many connections in the CLOSE_WAIT state. This may cause system
+// crashes (Blue Screen of Death) when VPN is used.
+// 2. It causes WinHTTP to crash intermittently in
+// HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel_Fsm() if a proxy is used.
+// Therefore, we have to resort to throttling our WinHTTP requests to achieve
+// the same effect.
+//
+// Note on thread safety: The WinHttpRequestThrottle class is only used by
+// the IO thread, so it doesn't need to be protected with a lock. The
+// drawback is that the time we mark a request done is only approximate.
+// We do that in the HttpTransactionWinHttp destructor, rather than in the
+// WinHTTP status callback upon receiving HANDLE_CLOSING.
+class WinHttpRequestThrottle {
+ public:
+ WinHttpRequestThrottle() {}
+
+ virtual ~WinHttpRequestThrottle();
+
+ // Intended to be a near drop-in replacement of WinHttpSendRequest.
+ BOOL SubmitRequest(const std::string& server,
+ HINTERNET request_handle,
+ DWORD total_size,
+ DWORD_PTR context);
+
+ // Called when a request failed or completed successfully.
+ void NotifyRequestDone(const std::string& server);
+
+ // Called from the HttpTransactionWinHttp destructor.
+ void RemoveRequest(const std::string& server,
+ HINTERNET request_handle);
+
+ protected:
+ // Unit tests can stub out this method in a derived class.
+ virtual BOOL SendRequest(HINTERNET request_handle,
+ DWORD total_size,
+ DWORD_PTR context,
+ bool report_async_error);
+
+ private:
+ FRIEND_TEST(WinHttpRequestThrottleTest, GarbageCollect);
+
+ class RequestQueue;
+
+ struct PerServerThrottle {
+ PerServerThrottle();
+ ~PerServerThrottle();
+
+ int num_requests; // Number of requests in progress
+ linked_ptr<RequestQueue> request_queue; // Requests waiting to be sent
+ };
+
+ typedef std::map<std::string, PerServerThrottle> ThrottleMap;
+
+ static const int kMaxConnectionsPerServer;
+ static const int kGarbageCollectionThreshold;
+
+ void GarbageCollect();
+
+ ThrottleMap throttles_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(WinHttpRequestThrottle);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_WINHTTP_REQUEST_THROTTLE_H_
diff --git a/net/http/winhttp_request_throttle_unittest.cc b/net/http/winhttp_request_throttle_unittest.cc
new file mode 100644
index 0000000..44643e3
--- /dev/null
+++ b/net/http/winhttp_request_throttle_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/string_util.h"
+#include "net/http/winhttp_request_throttle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Converts an int i to an HINTERNET (void *) request handle.
+HINTERNET RequestHandle(int i) {
+ return reinterpret_cast<HINTERNET>(static_cast<intptr_t>(i));
+}
+
+class MockRequestThrottle : public net::WinHttpRequestThrottle {
+ public:
+ MockRequestThrottle() : last_sent_request_(NULL) { }
+
+ // The request handle of the last sent request. This allows us to determine
+ // whether a submitted request was sent or queued.
+ HINTERNET last_sent_request() const { return last_sent_request_; }
+
+ protected:
+ virtual BOOL SendRequest(HINTERNET request_handle,
+ DWORD total_size,
+ DWORD_PTR context,
+ bool report_async_error) {
+ last_sent_request_ = request_handle;
+ return TRUE;
+ }
+
+ private:
+ HINTERNET last_sent_request_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MockRequestThrottle);
+};
+
+} // namespace
+
+namespace net {
+
+TEST(WinHttpRequestThrottleTest, OneServer) {
+ MockRequestThrottle throttle;
+ std::string server("http://www.foo.com");
+ HINTERNET request_handle;
+
+ // Submit 20 requests to the request throttle.
+ // Expected outcome: 6 requests should be in progress, and requests 7-20
+ // should be queued.
+ for (int i = 1; i <= 20; i++) {
+ request_handle = RequestHandle(i);
+ EXPECT_TRUE(throttle.SubmitRequest(server, request_handle, 0, 0));
+ if (i <= 6)
+ EXPECT_EQ(request_handle, throttle.last_sent_request());
+ else
+ EXPECT_EQ(RequestHandle(6), throttle.last_sent_request());
+ }
+
+ // Notify the request throttle of the completion of 10 requests.
+ // Expected outcome: 6 requests should be in progress, and requests 17-20
+ // should be queued.
+ for (int j = 0; j < 10; j++) {
+ throttle.NotifyRequestDone(server);
+ EXPECT_EQ(RequestHandle(7 + j), throttle.last_sent_request());
+ }
+
+ // Remove request 17, which is queued.
+ // Expected outcome: Requests 18-20 should remain queued.
+ request_handle = RequestHandle(17);
+ throttle.RemoveRequest(server, request_handle);
+ EXPECT_EQ(RequestHandle(16), throttle.last_sent_request());
+
+ // Remove request 16, which is in progress.
+ // Expected outcome: The request throttle should send request 18.
+ // Requests 19-20 should remained queued.
+ request_handle = RequestHandle(16);
+ throttle.RemoveRequest(server, request_handle);
+ EXPECT_EQ(RequestHandle(18), throttle.last_sent_request());
+
+ // Notify the request throttle of the completion of the remaining
+ // 8 requests.
+ for (int j = 0; j < 8; j++) {
+ throttle.NotifyRequestDone(server);
+ if (j < 2)
+ EXPECT_EQ(RequestHandle(19 + j), throttle.last_sent_request());
+ else
+ EXPECT_EQ(RequestHandle(20), throttle.last_sent_request());
+ }
+}
+
+// Submit requests to a large number (> 64) of servers to force the garbage
+// collection of idle PerServerThrottles.
+TEST(WinHttpRequestThrottleTest, GarbageCollect) {
+ MockRequestThrottle throttle;
+ for (int i = 0; i < 150; i++) {
+ std::string server("http://www.foo");
+ server.append(IntToString(i));
+ server.append(".com");
+ throttle.SubmitRequest(server, RequestHandle(1), 0, 0);
+ throttle.NotifyRequestDone(server);
+ if (i < 64)
+ EXPECT_EQ(i + 1, throttle.throttles_.size());
+ else if (i < 129)
+ EXPECT_EQ(i - 64, throttle.throttles_.size());
+ else
+ EXPECT_EQ(i - 129, throttle.throttles_.size());
+ }
+}
+
+} // namespace net
diff --git a/net/net.sln b/net/net.sln
new file mode 100644
index 0000000..29a39d1
--- /dev/null
+++ b/net/net.sln
@@ -0,0 +1,162 @@
+Microsoft Visual Studio Solution File, Format Version 9.00
+# Visual Studio 2005
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencies", "dependencies", "{A04F65DF-D422-4E8F-B918-29EBA839363E}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "googleurl", "..\googleurl\build\googleurl.vcproj", "{EF5E94AB-B646-4E5B-A058-52EF07B8351C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "base", "..\base\build\base.vcproj", "{1832A374-8A74-4F9E-B536-69A699B3E165}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0E5474AC-5996-4B13-87C0-4AE931EE0815} = {0E5474AC-5996-4B13-87C0-4AE931EE0815}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debug_message", "..\base\build\debug_message.vcproj", "{0E5474AC-5996-4B13-87C0-4AE931EE0815}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "icuuc", "..\third_party\icu38\build\icuuc.vcproj", "{8C27D792-2648-4F5E-9ED0-374276327308}"
+ ProjectSection(ProjectDependencies) = postProject
+ {A0D94973-D355-47A5-A1E2-3456F321F010} = {A0D94973-D355-47A5-A1E2-3456F321F010}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "icudt", "..\third_party\icu38\build\icudt.vcproj", "{A0D94973-D355-47A5-A1E2-3456F321F010}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "net", "build\net.vcproj", "{326E9795-E760-410A-B69A-3F79DB3F5243}"
+ ProjectSection(ProjectDependencies) = postProject
+ {E13045CD-7E1F-4A41-9B18-8D288B2E7B41} = {E13045CD-7E1F-4A41-9B18-8D288B2E7B41}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "net_unittests", "build\net_unittests.vcproj", "{E99DA267-BE90-4F45-88A1-6919DB2C7567}"
+ ProjectSection(ProjectDependencies) = postProject
+ {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165}
+ {2A70CBF0-847E-4E3A-B926-542A656DC7FE} = {2A70CBF0-847E-4E3A-B926-542A656DC7FE}
+ {326E9795-E760-410A-B69A-3F79DB3F5243} = {326E9795-E760-410A-B69A-3F79DB3F5243}
+ {7100F41F-868D-4E99-80A2-AF8E6574749D} = {7100F41F-868D-4E99-80A2-AF8E6574749D}
+ {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C} = {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}
+ {8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308}
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF5E94AB-B646-4E5B-A058-52EF07B8351C}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tld_cleanup", "build\tld_cleanup.vcproj", "{E13045CD-7E1F-4A41-9B18-8D288B2E7B41}"
+ ProjectSection(ProjectDependencies) = postProject
+ {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165}
+ {8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308}
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF5E94AB-B646-4E5B-A058-52EF07B8351C}
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{E7D78B1F-F7D3-47CB-BF51-3957C646B406}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\third_party\zlib\zlib.vcproj", "{8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bzip2", "..\third_party\bzip2\bzip2.vcproj", "{2A70CBF0-847E-4E3A-B926-542A656DC7FE}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "modp_b64", "..\third_party\modp_b64\modp_b64.vcproj", "{7100F41F-868D-4E99-80A2-AF8E6574749D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "net_perftests", "build\net_perftests.vcproj", "{AAC78796-B9A2-4CD9-BF89-09B03E92BF73}"
+ ProjectSection(ProjectDependencies) = postProject
+ {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165}
+ {326E9795-E760-410A-B69A-3F79DB3F5243} = {326E9795-E760-410A-B69A-3F79DB3F5243}
+ {7100F41F-868D-4E99-80A2-AF8E6574749D} = {7100F41F-868D-4E99-80A2-AF8E6574749D}
+ {8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308}
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF5E94AB-B646-4E5B-A058-52EF07B8351C}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_cache", "build\crash_cache.vcproj", "{B0EE0599-2913-46A0-A847-A3EC813658D3}"
+ ProjectSection(ProjectDependencies) = postProject
+ {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165}
+ {326E9795-E760-410A-B69A-3F79DB3F5243} = {326E9795-E760-410A-B69A-3F79DB3F5243}
+ {8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stress_cache", "build\stress_cache.vcproj", "{B491C3A1-DE5F-4843-A1BB-AB8C4337187B}"
+ ProjectSection(ProjectDependencies) = postProject
+ {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165}
+ {326E9795-E760-410A-B69A-3F79DB3F5243} = {326E9795-E760-410A-B69A-3F79DB3F5243}
+ {8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308}
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtest", "..\testing\gtest.vcproj", "{BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Debug|Win32.Build.0 = Debug|Win32
+ {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Release|Win32.ActiveCfg = Release|Win32
+ {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Release|Win32.Build.0 = Release|Win32
+ {1832A374-8A74-4F9E-B536-69A699B3E165}.Debug|Win32.ActiveCfg = Debug|Win32
+ {1832A374-8A74-4F9E-B536-69A699B3E165}.Debug|Win32.Build.0 = Debug|Win32
+ {1832A374-8A74-4F9E-B536-69A699B3E165}.Release|Win32.ActiveCfg = Release|Win32
+ {1832A374-8A74-4F9E-B536-69A699B3E165}.Release|Win32.Build.0 = Release|Win32
+ {2A70CBF0-847E-4E3A-B926-542A656DC7FE}.Debug|Win32.ActiveCfg = Debug|Win32
+ {2A70CBF0-847E-4E3A-B926-542A656DC7FE}.Debug|Win32.Build.0 = Debug|Win32
+ {2A70CBF0-847E-4E3A-B926-542A656DC7FE}.Release|Win32.ActiveCfg = Release|Win32
+ {2A70CBF0-847E-4E3A-B926-542A656DC7FE}.Release|Win32.Build.0 = Release|Win32
+ {326E9795-E760-410A-B69A-3F79DB3F5243}.Debug|Win32.ActiveCfg = Debug|Win32
+ {326E9795-E760-410A-B69A-3F79DB3F5243}.Debug|Win32.Build.0 = Debug|Win32
+ {326E9795-E760-410A-B69A-3F79DB3F5243}.Release|Win32.ActiveCfg = Release|Win32
+ {326E9795-E760-410A-B69A-3F79DB3F5243}.Release|Win32.Build.0 = Release|Win32
+ {7100F41F-868D-4E99-80A2-AF8E6574749D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {7100F41F-868D-4E99-80A2-AF8E6574749D}.Debug|Win32.Build.0 = Debug|Win32
+ {7100F41F-868D-4E99-80A2-AF8E6574749D}.Release|Win32.ActiveCfg = Release|Win32
+ {7100F41F-868D-4E99-80A2-AF8E6574749D}.Release|Win32.Build.0 = Release|Win32
+ {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Debug|Win32.ActiveCfg = Debug|Win32
+ {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Debug|Win32.Build.0 = Debug|Win32
+ {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Release|Win32.ActiveCfg = Release|Win32
+ {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Release|Win32.Build.0 = Release|Win32
+ {8C27D792-2648-4F5E-9ED0-374276327308}.Debug|Win32.ActiveCfg = Debug|Win32
+ {8C27D792-2648-4F5E-9ED0-374276327308}.Debug|Win32.Build.0 = Debug|Win32
+ {8C27D792-2648-4F5E-9ED0-374276327308}.Release|Win32.ActiveCfg = Release|Win32
+ {8C27D792-2648-4F5E-9ED0-374276327308}.Release|Win32.Build.0 = Release|Win32
+ {A0D94973-D355-47A5-A1E2-3456F321F010}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A0D94973-D355-47A5-A1E2-3456F321F010}.Debug|Win32.Build.0 = Debug|Win32
+ {A0D94973-D355-47A5-A1E2-3456F321F010}.Release|Win32.ActiveCfg = Release|Win32
+ {A0D94973-D355-47A5-A1E2-3456F321F010}.Release|Win32.Build.0 = Release|Win32
+ {AAC78796-B9A2-4CD9-BF89-09B03E92BF73}.Debug|Win32.ActiveCfg = Debug|Win32
+ {AAC78796-B9A2-4CD9-BF89-09B03E92BF73}.Debug|Win32.Build.0 = Debug|Win32
+ {AAC78796-B9A2-4CD9-BF89-09B03E92BF73}.Release|Win32.ActiveCfg = Release|Win32
+ {AAC78796-B9A2-4CD9-BF89-09B03E92BF73}.Release|Win32.Build.0 = Release|Win32
+ {B0EE0599-2913-46A0-A847-A3EC813658D3}.Debug|Win32.ActiveCfg = Debug|Win32
+ {B0EE0599-2913-46A0-A847-A3EC813658D3}.Debug|Win32.Build.0 = Debug|Win32
+ {B0EE0599-2913-46A0-A847-A3EC813658D3}.Release|Win32.ActiveCfg = Release|Win32
+ {B0EE0599-2913-46A0-A847-A3EC813658D3}.Release|Win32.Build.0 = Release|Win32
+ {B491C3A1-DE5F-4843-A1BB-AB8C4337187B}.Debug|Win32.ActiveCfg = Debug|Win32
+ {B491C3A1-DE5F-4843-A1BB-AB8C4337187B}.Debug|Win32.Build.0 = Debug|Win32
+ {B491C3A1-DE5F-4843-A1BB-AB8C4337187B}.Release|Win32.ActiveCfg = Release|Win32
+ {B491C3A1-DE5F-4843-A1BB-AB8C4337187B}.Release|Win32.Build.0 = Release|Win32
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Debug|Win32.ActiveCfg = Debug|Win32
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Debug|Win32.Build.0 = Debug|Win32
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Release|Win32.ActiveCfg = Release|Win32
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Release|Win32.Build.0 = Release|Win32
+ {E13045CD-7E1F-4A41-9B18-8D288B2E7B41}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E13045CD-7E1F-4A41-9B18-8D288B2E7B41}.Debug|Win32.Build.0 = Debug|Win32
+ {E13045CD-7E1F-4A41-9B18-8D288B2E7B41}.Release|Win32.ActiveCfg = Release|Win32
+ {E13045CD-7E1F-4A41-9B18-8D288B2E7B41}.Release|Win32.Build.0 = Release|Win32
+ {E99DA267-BE90-4F45-88A1-6919DB2C7567}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E99DA267-BE90-4F45-88A1-6919DB2C7567}.Debug|Win32.Build.0 = Debug|Win32
+ {E99DA267-BE90-4F45-88A1-6919DB2C7567}.Release|Win32.ActiveCfg = Release|Win32
+ {E99DA267-BE90-4F45-88A1-6919DB2C7567}.Release|Win32.Build.0 = Release|Win32
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C}.Debug|Win32.ActiveCfg = Debug|Win32
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C}.Debug|Win32.Build.0 = Debug|Win32
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C}.Release|Win32.ActiveCfg = Release|Win32
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {0E5474AC-5996-4B13-87C0-4AE931EE0815} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {1832A374-8A74-4F9E-B536-69A699B3E165} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {2A70CBF0-847E-4E3A-B926-542A656DC7FE} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {7100F41F-868D-4E99-80A2-AF8E6574749D} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {8C27D792-2648-4F5E-9ED0-374276327308} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {A0D94973-D355-47A5-A1E2-3456F321F010} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ {E13045CD-7E1F-4A41-9B18-8D288B2E7B41} = {E7D78B1F-F7D3-47CB-BF51-3957C646B406}
+ {EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {A04F65DF-D422-4E8F-B918-29EBA839363E}
+ EndGlobalSection
+EndGlobal
diff --git a/net/tools/crash_cache/crash_cache.cc b/net/tools/crash_cache/crash_cache.cc
new file mode 100644
index 0000000..859cf94
--- /dev/null
+++ b/net/tools/crash_cache/crash_cache.cc
@@ -0,0 +1,337 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 <windows.h>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/string_util.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"
+
+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) {
+ std::wstring exe;
+ PathService::Get(base::FILE_EXE, &exe);
+
+ std::wstring command = StringPrintf(L"%s %d", exe.c_str(), action);
+
+ STARTUPINFO startup_info = {0};
+ startup_info.cb = sizeof(startup_info);
+ PROCESS_INFORMATION process_info;
+
+ // I really don't care about this call modifying the string.
+ if (!::CreateProcess(exe.c_str(), const_cast<wchar_t*>(command.c_str()), NULL,
+ NULL, FALSE, 0, NULL, NULL, &startup_info,
+ &process_info)) {
+ printf("Unable to run test %d\n", action);
+ return GENERIC;
+ }
+
+ DWORD reason = ::WaitForSingleObject(process_info.hProcess, INFINITE);
+
+ int code;
+ bool ok = ::GetExitCodeProcess(process_info.hProcess,
+ reinterpret_cast<PDWORD>(&code)) ? true :
+ false;
+
+ ::CloseHandle(process_info.hProcess);
+ ::CloseHandle(process_info.hThread);
+
+ if (!ok) {
+ printf("Unable to get return code, test %d\n", action);
+ return GENERIC;
+ }
+
+ if (ALL_GOOD != code)
+ printf("Test %d failed, code %d\n", action, code);
+
+ return 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;
+}
+
+// -----------------------------------------------------------------------
+
+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 std::wstring& path, RankCrashes action,
+ std::wstring* full_path) {
+ const wchar_t* folders[] = {
+ L"",
+ L"insert_empty1",
+ L"insert_empty2",
+ L"insert_empty3",
+ L"insert_one1",
+ L"insert_one2",
+ L"insert_one3",
+ L"insert_load1",
+ L"insert_load2",
+ L"remove_one1",
+ L"remove_one2",
+ L"remove_one3",
+ L"remove_one4",
+ L"remove_head1",
+ L"remove_head2",
+ L"remove_head3",
+ L"remove_head4",
+ L"remove_tail1",
+ L"remove_tail2",
+ L"remove_tail3",
+ L"remove_load1",
+ L"remove_load2",
+ L"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;
+ file_util::AppendToPath(full_path, folders[action]);
+
+ return file_util::CreateDirectory(*full_path);
+}
+
+// Generates the files for an empty and one item cache.
+int SimpleInsert(const std::wstring& path, RankCrashes action) {
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ if (!cache || cache->GetEntryCount())
+ return GENERIC;
+
+ const char* test_name = "some other key";
+
+ if (action <= disk_cache::INSERT_EMPTY_3) {
+ test_name = kCrashEntryName;
+ g_rankings_crash = action;
+ }
+
+ disk_cache::Entry* entry;
+ if (!cache->CreateEntry(test_name, &entry))
+ return GENERIC;
+
+ entry->Close();
+
+ DCHECK(action <= disk_cache::INSERT_ONE_3);
+ g_rankings_crash = action;
+ test_name = kCrashEntryName;
+
+ if (!cache->CreateEntry(test_name, &entry))
+ return GENERIC;
+
+ return NOT_REACHED;
+}
+
+// Generates the files for a one item cache, and removing the head.
+int SimpleRemove(const std::wstring& path, RankCrashes action) {
+ DCHECK(action >= disk_cache::REMOVE_ONE_1);
+ DCHECK(action <= disk_cache::REMOVE_TAIL_3);
+
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ if (!cache || cache->GetEntryCount())
+ return GENERIC;
+
+ disk_cache::Entry* entry;
+ if (!cache->CreateEntry(kCrashEntryName, &entry))
+ return GENERIC;
+
+ entry->Close();
+
+ if (action >= disk_cache::REMOVE_TAIL_1) {
+ if (!cache->CreateEntry("some other key", &entry))
+ return GENERIC;
+
+ entry->Close();
+ }
+
+ if (!cache->OpenEntry(kCrashEntryName, &entry))
+ return GENERIC;
+
+ g_rankings_crash = action;
+ entry->Doom();
+ entry->Close();
+
+ return NOT_REACHED;
+}
+
+int HeadRemove(const std::wstring& path, RankCrashes action) {
+ DCHECK(action >= disk_cache::REMOVE_HEAD_1);
+ DCHECK(action <= disk_cache::REMOVE_HEAD_4);
+
+ disk_cache::Backend* cache = disk_cache::CreateCacheBackend(path, false, 0);
+ if (!cache || cache->GetEntryCount())
+ return GENERIC;
+
+ disk_cache::Entry* entry;
+ if (!cache->CreateEntry("some other key", &entry))
+ return GENERIC;
+
+ entry->Close();
+ if (!cache->CreateEntry(kCrashEntryName, &entry))
+ return GENERIC;
+
+ entry->Close();
+
+ if (!cache->OpenEntry(kCrashEntryName, &entry))
+ return GENERIC;
+
+ g_rankings_crash = action;
+ entry->Doom();
+ entry->Close();
+
+ return NOT_REACHED;
+}
+
+// Generates the files for insertion and removals on heavy loaded caches.
+int LoadOperations(const std::wstring& path, RankCrashes action) {
+ 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);
+ if (!cache || !cache->SetMaxSize(0x100000) || !cache->Init() ||
+ 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);
+ if (!cache->CreateEntry(key, &entry))
+ return GENERIC;
+ entry->Close();
+ if (50 == i && action >= disk_cache::REMOVE_LOAD_1) {
+ if (!cache->CreateEntry(kCrashEntryName, &entry))
+ return GENERIC;
+ entry->Close();
+ }
+ }
+
+ if (action <= disk_cache::INSERT_LOAD_2) {
+ g_rankings_crash = action;
+
+ if (!cache->CreateEntry(kCrashEntryName, &entry))
+ return GENERIC;
+ }
+
+ if (!cache->OpenEntry(kCrashEntryName, &entry))
+ return GENERIC;
+
+ g_rankings_crash = action;
+
+ entry->Doom();
+ entry->Close();
+
+ return NOT_REACHED;
+}
+
+// Main function on the child process.
+int SlaveCode(const std::wstring& path, RankCrashes action) {
+ MessageLoop message_loop;
+
+ std::wstring full_path;
+ if (!CreateTargetFolder(path, action, &full_path)) {
+ printf("Destination folder found, please remove it.\n");
+ return CRASH_OVERWRITE;
+ }
+
+ if (action <= disk_cache::INSERT_ONE_3)
+ return SimpleInsert(full_path, action);
+
+ if (action <= disk_cache::INSERT_LOAD_2)
+ return LoadOperations(full_path, action);
+
+ if (action <= disk_cache::REMOVE_ONE_4)
+ return SimpleRemove(full_path, action);
+
+ if (action <= disk_cache::REMOVE_HEAD_4)
+ return HeadRemove(full_path, action);
+
+ if (action <= disk_cache::REMOVE_TAIL_3)
+ return SimpleRemove(full_path, action);
+
+ if (action <= disk_cache::REMOVE_LOAD_3)
+ return LoadOperations(full_path, action);
+
+ return NOT_REACHED;
+}
+
+// -----------------------------------------------------------------------
+
+int main(int argc, const char* argv[]) {
+ 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;
+ }
+
+ std::wstring path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ file_util::AppendToPath(&path, L"net");
+ file_util::AppendToPath(&path, L"data");
+ file_util::AppendToPath(&path, L"cache_tests");
+ file_util::AppendToPath(&path, L"new_crashes");
+
+ return SlaveCode(path, action);
+}
diff --git a/net/tools/testserver/dist/_socket.pyd b/net/tools/testserver/dist/_socket.pyd
new file mode 100644
index 0000000..5ae91b7
--- /dev/null
+++ b/net/tools/testserver/dist/_socket.pyd
Binary files differ
diff --git a/net/tools/testserver/dist/_ssl.pyd b/net/tools/testserver/dist/_ssl.pyd
new file mode 100644
index 0000000..6a9b73c
--- /dev/null
+++ b/net/tools/testserver/dist/_ssl.pyd
Binary files differ
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
new file mode 100644
index 0000000..381b6a8
--- /dev/null
+++ b/net/tools/testserver/testserver.py
@@ -0,0 +1,943 @@
+#!/usr/bin/python2.4
+# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""This is a simple HTTP server used for testing Chrome.
+
+It supports several test URLs, as specified by the handlers in TestPageHandler.
+It defaults to living on localhost:8888.
+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.
+To shut it down properly, visit localhost:8888/kill.
+"""
+
+import base64
+import BaseHTTPServer
+import cgi
+import md5
+import optparse
+import os
+import re
+import SocketServer
+import sys
+import time
+import tlslite
+import tlslite.api
+
+debug_output = sys.stderr
+def debug(str):
+ debug_output.write(str + "\n")
+ debug_output.flush()
+
+class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
+ """This is a specialization of 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 = None
+ while not self.stop:
+ self.handle_request()
+ self.socket.close()
+
+class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
+ """This is a specialization of StoppableHTTPerver that add https support."""
+
+ def __init__(self, server_address, request_hander_class, cert_path):
+ s = open(cert_path).read()
+ x509 = tlslite.api.X509()
+ x509.parse(s)
+ self.cert_chain = tlslite.api.X509CertChain([x509])
+ s = open(cert_path).read()
+ self.private_key = tlslite.api.parsePEMKey(s, private=True)
+
+ self.session_cache = tlslite.api.SessionCache()
+ StoppableHTTPServer.__init__(self, server_address, request_hander_class)
+
+ def handshake(self, tlsConnection):
+ """Creates the SSL connection."""
+ try:
+ tlsConnection.handshakeServer(certChain=self.cert_chain,
+ privateKey=self.private_key,
+ sessionCache=self.session_cache)
+ tlsConnection.ignoreAbruptClose = True
+ return True
+ except tlslite.api.TLSError, error:
+ print "Handshake failure:", str(error)
+ return False
+
+class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+
+ def __init__(self, request, client_address, socket_server):
+ self._get_handlers = [
+ self.KillHandler,
+ 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.FileHandler,
+ self.RealFileWithCommonHeaderHandler,
+ self.RealBZ2FileWithCommonHeaderHandler,
+ self.AuthBasicHandler,
+ self.AuthDigestHandler,
+ self.SlowServerHandler,
+ self.ContentTypeHandler,
+ self.ServerRedirectHandler,
+ self.ClientRedirectHandler,
+ self.DefaultResponseHandler]
+ self._post_handlers = [
+ self.EchoTitleHandler,
+ self.EchoAllHandler,
+ self.EchoHandler] + self._get_handlers
+
+ self._mime_types = { 'gif': 'image/gif', 'jpeg' : 'image/jpeg', 'jpg' : 'image/jpeg' }
+ self._default_mime_type = 'text/html'
+
+ BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, client_address, socket_server)
+
+ 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)
+ if len(extension) == 0:
+ # no extension.
+ return self._default_mime_type
+
+ return self._mime_types.get(extension, self._default_mime_type)
+
+ def KillHandler(self):
+ """This request handler kills the server, for use when we're done"
+ with the a particular test."""
+
+ if (self.path.find("kill") < 0):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=0')
+ self.end_headers()
+ self.wfile.write("Time to die")
+ self.server.stop = True
+
+ return True
+
+ def NoCacheMaxAgeTimeHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and no caching requested."""
+
+ if (self.path.find("/nocachetime/maxage") != 0):
+ 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 (self.path.find("/nocachetime") != 0):
+ 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 self.path.find("/cachetime") != 0:
+ 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 (self.path.find("/cache/expires") != 0):
+ 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 (self.path.find("/cache/proxy-revalidate") != 0):
+ 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 (self.path.find("/cache/private") != 0):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=5, 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 (self.path.find("/cache/public") != 0):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=5, 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 (self.path.find("/cache/s-maxage") != 0):
+ 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 (self.path.find("/cache/must-revalidate") != 0):
+ 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 (self.path.find("/cache/must-revalidate/max-age") != 0):
+ 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 (self.path.find("/cache/no-store") != 0):
+ 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 (self.path.find("/cache/no-store/max-age") != 0):
+ 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 (self.path.find("/cache/no-transform") != 0):
+ 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."""
+
+ if self.path.find("/echoheader") != 0:
+ 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')
+ self.send_header('Cache-control', 'max-age=60000')
+ # 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 EchoHandler(self):
+ """This handler just echoes back the payload of the request, for testing
+ form submission."""
+
+ if self.path.find("/echo") != 0:
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.end_headers()
+ length = int(self.headers.getheader('content-length'))
+ request = self.rfile.read(length)
+ self.wfile.write(request)
+ return True
+
+ def EchoTitleHandler(self):
+ """This handler is like Echo, but sets the page title to the request."""
+
+ if self.path.find("/echotitle") != 0:
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.end_headers()
+ length = int(self.headers.getheader('content-length'))
+ request = self.rfile.read(length)
+ 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 self.path.find("/echoall") != 0:
+ 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="http://localhost:8888/echo">back to referring page</a></div>'
+ '<h1>Request Body:</h1><pre>')
+ length = int(self.headers.getheader('content-length'))
+ qs = self.rfile.read(length)
+ 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.waitForDownload = True
+ while self.server.waitForDownload:
+ 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.path.startswith("/download-finish"):
+ return False
+
+ self.server.waitForDownload = 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 FileHandler(self):
+ """This handler sends the contents of the requested file. Wow, it's like
+ a real webserver!"""
+
+ prefix='/files/'
+ if not self.path.startswith(prefix):
+ return False
+
+ file = self.path[len(prefix):]
+ entries = file.split('/');
+ path = os.path.join(self.server.data_dir, *entries)
+
+ if not os.path.isfile(path):
+ print "File not found " + file + " full path:" + path
+ self.send_error(404)
+ return True
+
+ f = open(path, "rb")
+ data = f.read()
+ f.close()
+
+ # If file.mock-http-headers exists, it contains the headers we
+ # should send. Read them in and parse them.
+ headers_path = path + '.mock-http-headers'
+ if os.path.isfile(headers_path):
+ f = open(headers_path, "r")
+
+ # "HTTP/1.1 200 OK"
+ response = f.readline()
+ status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
+ self.send_response(int(status_code))
+
+ for line in f:
+ # "name: value"
+ name, value = re.findall('(\S+):\s*(.*)', line)[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.
+ self.send_response(200)
+ self.send_header('Content-type', self.GetMIMETypeFromName(file))
+ self.send_header('Content-Length', len(data))
+ self.end_headers()
+
+ self.wfile.write(data)
+
+ return True
+
+ def RealFileWithCommonHeaderHandler(self):
+ """This handler sends the contents of the requested file without the pseudo
+ http head!"""
+
+ prefix='/realfiles/'
+ if not self.path.startswith(prefix):
+ return False
+
+ file = self.path[len(prefix):]
+ path = os.path.join(self.server.data_dir, file)
+
+ try:
+ f = open(path, "rb")
+ data = f.read()
+ f.close()
+
+ # just simply set the MIME as octal stream
+ self.send_response(200)
+ self.send_header('Content-type', 'application/octet-stream')
+ self.end_headers()
+
+ self.wfile.write(data)
+ except:
+ self.send_error(404)
+
+ return True
+
+ def RealBZ2FileWithCommonHeaderHandler(self):
+ """This handler sends the bzip2 contents of the requested file with
+ corresponding Content-Encoding field in http head!"""
+
+ prefix='/realbz2files/'
+ if not self.path.startswith(prefix):
+ return False
+
+ parts = self.path.split('?')
+ file = parts[0][len(prefix):]
+ path = os.path.join(self.server.data_dir, file) + '.bz2'
+
+ if len(parts) > 1:
+ options = parts[1]
+ else:
+ options = ''
+
+ try:
+ self.send_response(200)
+ accept_encoding = self.headers.get("Accept-Encoding")
+ if accept_encoding.find("bzip2") != -1:
+ f = open(path, "rb")
+ data = f.read()
+ f.close()
+ self.send_header('Content-Encoding', 'bzip2')
+ self.send_header('Content-type', 'application/x-bzip2')
+ self.end_headers()
+ if options == 'incremental-header':
+ self.wfile.write(data[:1])
+ self.wfile.flush()
+ time.sleep(1.0)
+ self.wfile.write(data[1:])
+ else:
+ self.wfile.write(data)
+ else:
+ """client do not support bzip2 format, send pseudo content
+ """
+ self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
+ self.end_headers()
+ self.wfile.write("you do not support bzip2 encoding")
+ except:
+ self.send_error(404)
+
+ 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.path.startswith("/auth-basic"):
+ return False
+
+ username = userpass = password = b64str = ""
+
+ 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 != 'secret':
+ raise Exception('wrong password')
+ except Exception, e:
+ # Authentication failed.
+ self.send_response(401)
+ self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
+ 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('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.)
+ if_none_match = self.headers.getheader('if-none-match')
+ if if_none_match == "abc":
+ self.send_response(304)
+ self.end_headers()
+ 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('</body></html>')
+
+ return True
+
+ def AuthDigestHandler(self):
+ """This handler tests 'Digest' authentication. It just sends a page with
+ title 'user/pass' if you succeed."""
+
+ if not self.path.startswith("/auth-digest"):
+ return False
+
+ # Periodically generate a new nonce. Technically we should incorporate
+ # the request URL into this, but we don't care for testing.
+ nonce_life = 10
+ stale = False
+ if not self.server.nonce or (time.time() - self.server.nonce_time > nonce_life):
+ if self.server.nonce:
+ stale = True
+ self.server.nonce_time = time.time()
+ self.server.nonce = \
+ md5.new(time.ctime(self.server.nonce_time) + 'privatekey').hexdigest()
+
+ nonce = self.server.nonce
+ opaque = md5.new('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 = md5.new(':'.join([pairs['username'], realm, password])).hexdigest()
+ hash_a2 = md5.new(':'.join([self.command, pairs['uri']])).hexdigest()
+ if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
+ response = md5.new(':'.join([hash_a1, nonce, pairs['nc'],
+ pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
+ else:
+ response = md5.new(':'.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.path.startswith("/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 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.path.startswith('/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 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.path.startswith(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.path.startswith(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 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()
+ self.wfile.write(contents)
+ return True
+
+ def do_GET(self):
+ for handler in self._get_handlers:
+ if (handler()):
+ return
+
+ def do_POST(self):
+ for handler in self._post_handlers:
+ if(handler()):
+ return
+
+ # 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>')
+
+def main(options, args):
+ # redirect output to a log file so it doesn't spam the unit test output
+ logfile = open('testserver.log', 'w')
+ sys.stderr = sys.stdout = logfile
+
+ port = options.port
+
+ if options.cert:
+ # let's make sure the cert file exists.
+ if not os.path.isfile(options.cert):
+ print 'specified cert file not found: ' + options.cert + ' exiting...'
+ return
+ server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert)
+ print 'HTTPS server started on port %d...' % port
+ else:
+ server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
+ print 'HTTP server started on port %d...' % port
+
+ if options.data_dir:
+ if not os.path.isdir(options.data_dir):
+ print 'specified data dir not found: ' + options.data_dir + ' exiting...'
+ return
+ server.data_dir = options.data_dir
+ else:
+ # Create the default path to our data dir, relative to the exe dir.
+ server.data_dir = os.path.dirname(sys.argv[0])
+ server.data_dir = os.path.join(server.data_dir, "..", "..", "..", "..",
+ "test", "data")
+
+ try:
+ server.serve_forever()
+ except KeyboardInterrupt:
+ print 'shutting down server'
+ server.stop = True
+
+if __name__ == '__main__':
+ option_parser = optparse.OptionParser()
+ option_parser.add_option('', '--port', default='8888', type='int',
+ help='Port used by the server')
+ option_parser.add_option('', '--data-dir', dest='data_dir',
+ help='Directory from which to read the files')
+ option_parser.add_option('', '--https', dest='cert',
+ help='Specify that https should be used, specify '
+ 'the path to the cert containing the private key '
+ 'the server should use')
+ options, args = option_parser.parse_args()
+
+ sys.exit(main(options, args))
diff --git a/net/tools/tld_cleanup/SConscript b/net/tools/tld_cleanup/SConscript
new file mode 100644
index 0000000..f4ef035
--- /dev/null
+++ b/net/tools/tld_cleanup/SConscript
@@ -0,0 +1,121 @@
+# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('env')
+
+env = env.Clone()
+
+env.Prepend(
+ CPPPATH = [
+ '../../..',
+ ],
+)
+
+env.Append(
+ CCFLAGS = [
+ '/TP',
+
+ '/wd4503',
+ '/wd4819',
+ ],
+
+ LINKFLAGS = [
+ '/INCREMENTAL',
+ '/MANIFEST',
+ '/DELAYLOAD:"dwmapi.dll"',
+ '/DELAYLOAD:"uxtheme.dll"',
+ '/DEBUG',
+ '/SUBSYSTEM:CONSOLE',
+ '/MACHINE:X86',
+ '/FIXED:No',
+ '/safeseh',
+ '/dynamicbase',
+ '/ignore:4199',
+ '/nxcompat',
+ ],
+
+ LIBS = [
+ 'wininet.lib',
+ 'version.lib',
+ 'msimg32.lib',
+ 'ws2_32.lib',
+ 'usp10.lib',
+ 'psapi.lib',
+ 'kernel32.lib',
+ 'user32.lib',
+ 'gdi32.lib',
+ 'winspool.lib',
+ 'comdlg32.lib',
+ 'advapi32.lib',
+ 'shell32.lib',
+ 'ole32.lib',
+ 'oleaut32.lib',
+ 'uuid.lib',
+ 'odbc32.lib',
+ 'odbccp32.lib',
+ 'DelayImp.lib',
+ ],
+)
+
+input_files = [
+ 'tld_cleanup.cc',
+]
+
+libs = [
+ '$GOOGLEURL_DIR/googleurl.lib',
+ '$ICU38_DIR/icuuc.lib',
+ '$BASE_DIR/base.lib',
+ # We only need to link with net.lib due to use precompiled_net.pch.
+ '$NET_DIR/net.lib',
+]
+
+exe_targets = env.Program(['tld_cleanup',
+ 'tld_cleanup.ilk',
+ 'tld_cleanup.pdb'],
+ input_files + libs)
+i = env.Install('$TARGET_ROOT', exe_targets)
+env.Alias('net', i)
+
+#env.Program('tld_cleanup', input_files + libs,
+# #WINDOWS_INSERT_MANIFEST=1,
+# PDB='tld_cleanup.pdb')
+
+#env.Command('tld_cleanup.exe.embed.manifest',
+# 'tld_cleanup.exe.intermediate.manifest',
+# '-mt.exe /out:${TARGET} /notify_update /manifest $SOURCE')
+
+#env.RES('tld_cleanup.exe.embed.manifest.res',
+# 'tld_cleanup.exe.embed.manifest',
+# CFLAGS=None,
+# CCFLAGS=None,
+# CXXFLAGS=None,
+# CPPDEFINES=[],
+# CPPPATH=[])
+
+env.Install('$TARGET_ROOT', exe_targets)
diff --git a/net/tools/tld_cleanup/tld_cleanup.cc b/net/tools/tld_cleanup/tld_cleanup.cc
new file mode 100644
index 0000000..2efac1b7
--- /dev/null
+++ b/net/tools/tld_cleanup/tld_cleanup.cc
@@ -0,0 +1,266 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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. Any errors
+// or warnings are recorded in tld_cleanup.log.
+//
+// In particular, it
+// * Strips blank lines and comments, as well as notes for individual rules.
+// * Changes all line endings to LF.
+// * 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.
+
+#include <windows.h>
+#include <set>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/icu_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_parse.h"
+
+static const wchar_t* const kLogFileName = L"tld_cleanup.log";
+typedef std::set<std::string> StringSet;
+
+// 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 StringSet& rules, HANDLE outfile) {
+ std::string data;
+ for (StringSet::const_iterator iter = rules.begin();
+ iter != rules.end();
+ ++iter) {
+ data.append(*iter);
+ data.append(1, '\n');
+ }
+
+ unsigned long written = 0;
+ BOOL success = WriteFile(outfile,
+ data.data(),
+ static_cast<long>(data.size()),
+ &written,
+ NULL);
+ return (success && written == static_cast<long>(data.size()));
+}
+
+// These result codes should be in increasing order of severity.
+typedef enum {
+ kSuccess,
+ kWarning,
+ kError,
+} NormalizeResult;
+
+// 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* rule) {
+ NormalizeResult result = kSuccess;
+
+ // Strip single leading and trailing dots.
+ if (rule->at(0) == '.')
+ rule->erase(0, 1);
+ if (rule->size() == 0) {
+ LOG(WARNING) << "Ignoring empty rule";
+ return kWarning;
+ }
+ if (rule->at(rule->size() - 1) == '.')
+ rule->erase(rule->size() - 1, 1);
+ if (rule->size() == 0) {
+ LOG(WARNING) << "Ignoring empty rule";
+ return kWarning;
+ }
+
+ // Allow single leading '*.' or '!', saved here so it's not canonicalized.
+ bool wildcard = false;
+ bool exception = false;
+ size_t start_offset = 0;
+ if (rule->at(0) == '!') {
+ rule->erase(0, 1);
+ exception = true;
+ } else if (rule->find("*.") == 0) {
+ rule->erase(0, 2);
+ wildcard = true;
+ }
+ if (rule->size() == 0) {
+ LOG(WARNING) << "Ignoring empty rule";
+ return kWarning;
+ }
+
+ // Warn about additional '*.' or '!'.
+ if (rule->find("*.", start_offset) != std::string::npos ||
+ rule->find('!', start_offset) != std::string::npos) {
+ LOG(WARNING) << "Keeping probably invalid rule: " << *rule;
+ result = kWarning;
+ }
+
+ // Make a GURL and normalize it, then get the host back out.
+ std::string url = "http://";
+ url.append(*rule);
+ 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: " << *rule;
+ return kError;
+ }
+ if (!gurl.is_valid()) {
+ LOG(WARNING) << "Keeping rule that GURL says is invalid: " << *rule;
+ result = kWarning;
+ }
+ rule->assign(spec.substr(host.begin, host.len));
+
+ // Restore wildcard or exception marker.
+ if (exception)
+ rule->insert(0, 1, '!');
+ else if (wildcard)
+ rule->insert(0, "*.");
+
+ return result;
+}
+
+// Loads the file described by 'in_filename', converts it to the desired format
+// (see the file comments above), and saves it into 'out_filename'. Returns
+// the most severe of the result codes encountered when normalizing the rules.
+NormalizeResult NormalizeFile(const std::wstring& in_filename,
+ const std::wstring& out_filename) {
+ std::string data;
+ if (!file_util::ReadFileToString(in_filename, &data)) {
+ fwprintf(stderr, L"Unable to read file %s\n", in_filename.c_str());
+ // We return success since we've already reported the error.
+ return kSuccess;
+ }
+
+ HANDLE outfile(CreateFile(out_filename.c_str(),
+ GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL));
+ if (outfile == INVALID_HANDLE_VALUE) {
+ fwprintf(stderr, L"Unable to write file %s\n", out_filename.c_str());
+ // We return success since we've already reported the error.
+ return kSuccess;
+ }
+
+ // We do a lot of string assignment during parsing, but simplicity is more
+ // important than performance here.
+ std::string rule;
+ NormalizeResult result = kSuccess;
+ size_t line_start = 0;
+ size_t line_end = 0;
+ StringSet rules;
+ while (line_start < data.size()) {
+ // Skip comments.
+ if (line_start + 1 < data.size() &&
+ data[line_start] == '/' &&
+ data[line_start + 1] == '/') {
+ 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();
+ rule.assign(data.data(), line_start, line_end - line_start);
+
+ NormalizeResult new_result = NormalizeRule(&rule);
+ if (new_result != kError) {
+ rules.insert(rule);
+ // Add true TLD for multi-level rules.
+ size_t tld_start = rule.find_last_of('.');
+ if (tld_start != std::string::npos && tld_start + 1 < rule.size())
+ rules.insert(rule.substr(tld_start + 1));
+ }
+ 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();
+ }
+
+ if (!WriteRules(rules, outfile)) {
+ LOG(ERROR) << "Error(s) writing " << out_filename;
+ result = kError;
+ }
+
+ return result;
+}
+
+int main(int argc, const char* argv[]) {
+ if (argc != 3) {
+ fprintf(stderr, "Normalizes and verifies UTF-8 TLD data files\n");
+ fprintf(stderr, "Usage: %s <input> <output>\n", argv[0]);
+ return 1;
+ }
+
+ // Only use OutputDebugString in debug mode.
+#ifdef NDEBUG
+ logging::LoggingDestination destination = logging::LOG_ONLY_TO_FILE;
+#else
+ logging::LoggingDestination destination =
+ logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG;
+#endif
+
+ std::wstring log_filename;
+ PathService::Get(base::DIR_EXE, &log_filename);
+ file_util::AppendToPath(&log_filename, kLogFileName);
+ logging::InitLogging(log_filename.c_str(),
+ destination,
+ logging::LOCK_LOG_FILE,
+ logging::DELETE_OLD_LOG_FILE);
+
+ icu_util::Initialize();
+
+ NormalizeResult result = NormalizeFile(UTF8ToWide(argv[1]),
+ UTF8ToWide(argv[2]));
+ if (result != kSuccess) {
+ fwprintf(stderr, L"Errors or warnings processing file. See log in %s.",
+ kLogFileName);
+ }
+
+ if (result == kError)
+ return 1;
+ return 0;
+}
diff --git a/net/url_request/mime_sniffer_proxy.cc b/net/url_request/mime_sniffer_proxy.cc
new file mode 100644
index 0000000..2a0dec6
--- /dev/null
+++ b/net/url_request/mime_sniffer_proxy.cc
@@ -0,0 +1,94 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/mime_sniffer_proxy.h"
+
+#include "net/base/mime_sniffer.h"
+
+MimeSnifferProxy::MimeSnifferProxy(URLRequest* request,
+ URLRequest::Delegate* delegate)
+ : request_(request), delegate_(delegate),
+ sniff_content_(false), error_(false) {
+ request->set_delegate(this);
+}
+
+void MimeSnifferProxy::OnResponseStarted(URLRequest* request) {
+ if (request->status().is_success()) {
+ request->GetMimeType(&mime_type_);
+ if (mime_util::ShouldSniffMimeType(request->url(), mime_type_)) {
+ // We need to read content before we know the mime type,
+ // so we don't call OnResponseStarted.
+ sniff_content_ = true;
+ if (request_->Read(buf_, sizeof(buf_), &bytes_read_) && bytes_read_) {
+ OnReadCompleted(request, bytes_read_);
+ } else if (!request_->status().is_io_pending()) {
+ error_ = true;
+ delegate_->OnResponseStarted(request);
+ } // Otherwise, IO pending. Wait for OnReadCompleted.
+ return;
+ }
+ }
+ delegate_->OnResponseStarted(request);
+}
+
+bool MimeSnifferProxy::Read(char* buf, int max_bytes, int *bytes_read) {
+ if (sniff_content_) {
+ // This is the first call to Read() after we've sniffed content.
+ // Return our local buffer or the error we ran into.
+ sniff_content_ = false; // We're done with sniffing for this request.
+
+ if (error_) {
+ *bytes_read = 0;
+ return false;
+ }
+
+ memcpy(buf, buf_, bytes_read_);
+ *bytes_read = bytes_read_;
+ return true;
+ }
+ return request_->Read(buf, max_bytes, bytes_read);
+}
+
+void MimeSnifferProxy::OnReadCompleted(URLRequest* request, int bytes_read) {
+ if (sniff_content_) {
+ // Our initial content-sniffing Read() has completed.
+ if (request->status().is_success() && bytes_read) {
+ std::string type_hint;
+ request_->GetMimeType(&type_hint);
+ bytes_read_ = bytes_read;
+ mime_util::SniffMimeType(buf_, bytes_read_,
+ request_->url(), type_hint, &mime_type_);
+ } else {
+ error_ = true;
+ }
+ delegate_->OnResponseStarted(request_);
+ return;
+ }
+ delegate_->OnReadCompleted(request, bytes_read);
+}
diff --git a/net/url_request/mime_sniffer_proxy.h b/net/url_request/mime_sniffer_proxy.h
new file mode 100644
index 0000000..7246e46
--- /dev/null
+++ b/net/url_request/mime_sniffer_proxy.h
@@ -0,0 +1,100 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// MimeSnifferProxy wraps an URLRequest to use mime_util's MIME
+// sniffer to better report the content's MIME type.
+// It only supports a subset of the URLRequest API, and must be used together
+// with an URLRequest. Their lifetimes should be the same.
+//
+// To use it, create a normal URLRequest and initialize it appropriately,
+// then insert a MimeSnifferProxy between your object and the URLRequest:
+// ms_.reset(new MimeSnifferProxy(url_request, this));
+// It then proxies URLRequest delegate callbacks (from URLRequest back into
+// your object) appropriately.
+//
+// For the other direction of calls (from your object to URLRequest), be sure
+// to use two MimeSniffed functions in place of the URLRequest functions:
+// 1) ms_->Read() -- just like URLRequest::Read()
+// 2) ms_->mime_type() -- returns the sniffed mime type of the data;
+// valid after OnResponseStarted() is called.
+
+#include "net/url_request/url_request.h"
+
+class MimeSnifferProxy : public URLRequest::Delegate {
+ public:
+ // The constructor inserts this MimeSnifferProxy in between the URLRequest
+ // and the URLRequest::Delegate, so that the URLRequest's delegate callbacks
+ // first go through the MimeSnifferProxy.
+ MimeSnifferProxy(URLRequest* request, URLRequest::Delegate* delegate);
+
+ // URLRequest::Delegate implementation.
+ // These first two functions are handled specially:
+ virtual void OnResponseStarted(URLRequest* request);
+ virtual void OnReadCompleted(URLRequest* request, int bytes_read);
+ // The remaining three just proxy directly to the delegate:
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url) {
+ delegate_->OnReceivedRedirect(request, new_url);
+ }
+ virtual void OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) {
+ delegate_->OnAuthRequired(request, auth_info);
+ }
+ virtual void OnSSLCertificateError(URLRequest* request,
+ int cert_error,
+ X509Certificate* cert) {
+ delegate_->OnSSLCertificateError(request, cert_error, cert);
+ }
+
+ // Wrapper around URLRequest::Read.
+ bool Read(char* buf, int max_bytes, int *bytes_read);
+
+ // Return the sniffed mime type of the request. Valid after
+ // OnResponseStarted() has been called on the delegate.
+ const std::string& mime_type() const { return mime_type_; }
+
+ private:
+ // The request underneath us.
+ URLRequest* request_;
+ // The delegate above us, that we're proxying the request to.
+ URLRequest::Delegate* delegate_;
+
+ // The (sniffed, if necessary) request mime type.
+ std::string mime_type_;
+
+ // Whether we're sniffing this request.
+ bool sniff_content_;
+ // Whether we've encountered an error on our initial Read().
+ bool error_;
+
+ // A buffer for the first bit of the request.
+ char buf_[1024];
+ // The number of bytes we've read into the buffer.
+ int bytes_read_;
+};
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
new file mode 100644
index 0000000..74619eb
--- /dev/null
+++ b/net/url_request/url_request.cc
@@ -0,0 +1,360 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request.h"
+
+#include "base/basictypes.h"
+#include "base/process_util.h"
+#include "base/singleton.h"
+#include "base/stats_counters.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_data.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_manager.h"
+
+#ifndef NDEBUG
+URLRequestMetrics url_request_metrics;
+#endif
+
+using net::UploadData;
+using std::string;
+using std::wstring;
+
+// Max number of http redirects to follow. Same number as gecko.
+const static int kMaxRedirects = 20;
+
+// The id of the current process. Lazily initialized.
+static int32 current_proc_id = -1;
+
+static URLRequestJobManager* GetJobManager() {
+ return Singleton<URLRequestJobManager>::get();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// URLRequest
+
+URLRequest::URLRequest(const GURL& url, Delegate* delegate)
+ : url_(url),
+ original_url_(url),
+ method_("GET"),
+ load_flags_(net::LOAD_NORMAL),
+ delegate_(delegate),
+ is_pending_(false),
+ user_data_(NULL),
+ enable_profiling_(false),
+ redirect_limit_(kMaxRedirects),
+ final_upload_progress_(0) {
+ URLREQUEST_COUNT_CTOR();
+ SIMPLE_STATS_COUNTER(L"URLRequestCount");
+ if (current_proc_id == -1)
+ base::AtomicSwap(&current_proc_id, process_util::GetCurrentProcId());
+ origin_pid_ = current_proc_id;
+}
+
+URLRequest::~URLRequest() {
+ URLREQUEST_COUNT_DTOR();
+
+ Cancel();
+
+ if (job_)
+ OrphanJob();
+
+ delete user_data_; // NULL check unnecessary for delete
+}
+
+// static
+URLRequest::ProtocolFactory* URLRequest::RegisterProtocolFactory(
+ const string& scheme, ProtocolFactory* factory) {
+ return GetJobManager()->RegisterProtocolFactory(scheme, factory);
+}
+
+// static
+void URLRequest::RegisterRequestInterceptor(Interceptor* interceptor) {
+ GetJobManager()->RegisterRequestInterceptor(interceptor);
+}
+
+// static
+void URLRequest::UnregisterRequestInterceptor(Interceptor* interceptor) {
+ GetJobManager()->UnregisterRequestInterceptor(interceptor);
+}
+
+void URLRequest::AppendBytesToUpload(const char* bytes, int bytes_len) {
+ DCHECK(bytes_len > 0 && bytes);
+ if (!upload_)
+ upload_ = new UploadData();
+ upload_->AppendBytes(bytes, bytes_len);
+}
+
+void URLRequest::AppendFileRangeToUpload(const wstring& file_path,
+ uint64 offset, uint64 length) {
+ DCHECK(file_path.length() > 0 && length > 0);
+ if (!upload_)
+ upload_ = new UploadData();
+ upload_->AppendFileRange(file_path, offset, length);
+}
+
+void URLRequest::SetExtraRequestHeaderById(int id, const string& value,
+ bool overwrite) {
+ DCHECK(!is_pending_);
+ NOTREACHED() << "implement me!";
+}
+
+void URLRequest::SetExtraRequestHeaderByName(const string& name,
+ const string& value,
+ bool overwrite) {
+ DCHECK(!is_pending_);
+ NOTREACHED() << "implement me!";
+}
+
+void URLRequest::SetExtraRequestHeaders(const string& headers) {
+ DCHECK(!is_pending_);
+ if (headers.empty()) {
+ extra_request_headers_.clear();
+ } else {
+#ifndef NDEBUG
+ size_t crlf = headers.rfind("\r\n", headers.size() - 1);
+ DCHECK(crlf != headers.size() - 2) << "headers must not end with CRLF";
+#endif
+ extra_request_headers_ = headers + "\r\n";
+ }
+
+ // NOTE: This method will likely become non-trivial once the other setters
+ // for request headers are implemented.
+}
+
+net::LoadState URLRequest::GetLoadState() const {
+ return job_ ? job_->GetLoadState() : net::LOAD_STATE_IDLE;
+}
+
+uint64 URLRequest::GetUploadProgress() const {
+ if (!job_) {
+ // We haven't started or the request was cancelled
+ return 0;
+ }
+ if (final_upload_progress_) {
+ // 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_);
+ NOTREACHED() << "implement me!";
+}
+
+void URLRequest::GetResponseHeaderByName(const string& name, string* value) {
+ DCHECK(value);
+ if (response_info_.headers) {
+ response_info_.headers->GetNormalizedHeader(name, value);
+ } else {
+ value->clear();
+ }
+}
+
+void URLRequest::GetAllResponseHeaders(string* headers) {
+ DCHECK(headers);
+ if (response_info_.headers) {
+ response_info_.headers->GetNormalizedHeaders(headers);
+ } else {
+ headers->clear();
+ }
+}
+
+bool URLRequest::GetResponseCookies(ResponseCookies* cookies) {
+ DCHECK(job_);
+ return job_->GetResponseCookies(cookies);
+}
+
+void URLRequest::GetMimeType(string* mime_type) {
+ DCHECK(job_);
+ job_->GetMimeType(mime_type);
+}
+
+void URLRequest::GetCharset(string* charset) {
+ DCHECK(job_);
+ job_->GetCharset(charset);
+}
+
+int URLRequest::GetResponseCode() {
+ DCHECK(job_);
+ return job_->GetResponseCode();
+}
+
+// static
+bool URLRequest::IsHandledProtocol(const std::string& scheme) {
+ return GetJobManager()->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::Start() {
+ DCHECK(!is_pending_);
+ DCHECK(!job_);
+
+ job_ = GetJobManager()->CreateJob(this);
+ job_->SetExtraRequestHeaders(extra_request_headers_);
+
+ if (upload_.get())
+ job_->SetUpload(upload_.get());
+
+ is_pending_ = true;
+ response_info_.request_time = Time::Now();
+
+ // 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::Cancel() {
+ CancelWithError(net::ERR_ABORTED);
+}
+
+void URLRequest::CancelWithError(int os_error) {
+ DCHECK(os_error < 0);
+
+ // There's nothing to do if we are not waiting on a Job.
+ if (!is_pending_ || !job_)
+ return;
+
+ // 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_os_error(os_error);
+ }
+
+ job_->Kill();
+
+ // 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(char* dest, int dest_size, int *bytes_read) {
+ DCHECK(job_);
+ DCHECK(bytes_read);
+ DCHECK(!job_->is_done());
+ *bytes_read = 0;
+
+ 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;
+ }
+
+ return job_->Read(dest, dest_size, bytes_read);
+}
+
+void URLRequest::SetAuth(const wstring& username, const wstring& password) {
+ DCHECK(job_);
+ DCHECK(job_->NeedsAuth());
+
+ job_->SetAuth(username, password);
+}
+
+void URLRequest::CancelAuth() {
+ DCHECK(job_);
+ DCHECK(job_->NeedsAuth());
+
+ job_->CancelAuth();
+}
+
+void URLRequest::ContinueDespiteLastError() {
+ DCHECK(job_);
+
+ job_->ContinueDespiteLastError();
+}
+
+void URLRequest::OrphanJob() {
+ job_->DetachRequest(); // ensures that the job will not call us again
+ job_ = NULL;
+}
+
+int URLRequest::Redirect(const GURL& location, int http_status_code) {
+ // TODO(darin): treat 307 redirects of POST requests very carefully. we
+ // should prompt the user before re-submitting the POST body.
+ DCHECK(!(method_ == "POST" && http_status_code == 307)) << "implement me!";
+
+ if (redirect_limit_ <= 0) {
+ DLOG(INFO) << "disallowing redirect: exceeds limit";
+ return net::ERR_TOO_MANY_REDIRECTS;
+ }
+
+ if (!job_->IsSafeRedirect(location)) {
+ DLOG(INFO) << "disallowing redirect: unsafe protocol";
+ return net::ERR_UNSAFE_REDIRECT;
+ }
+
+ // NOTE: even though RFC 2616 says to preserve the request method when
+ // following a 302 redirect, normal browsers don't do that. instead, they
+ // all convert a POST into a GET in response to a 302, and so shall we.
+ url_ = location;
+ method_ = "GET";
+ upload_ = 0;
+ status_ = URLRequestStatus();
+ --redirect_limit_;
+
+ if (!final_upload_progress_) {
+ final_upload_progress_ = job_->GetUploadProgress();
+ }
+
+ OrphanJob();
+
+ is_pending_ = false;
+ Start();
+ return net::OK;
+}
+
+int64 URLRequest::GetExpectedContentSize() const {
+ int64 expected_content_size = -1;
+ if (job_)
+ expected_content_size = job_->expected_content_size();
+
+ return expected_content_size;
+}
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
new file mode 100644
index 0000000..89b36b0
--- /dev/null
+++ b/net/url_request/url_request.h
@@ -0,0 +1,542 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_H__
+#define BASE_URL_REQUEST_URL_REQUEST_H__
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/ref_counted.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/load_states.h"
+#include "net/base/ssl_info.h"
+#include "net/base/upload_data.h"
+#include "net/base/x509_certificate.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_status.h"
+
+class URLRequestJob;
+
+// 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 URLRequest {
+ public:
+ // Derive from this class and add your own data members to associate extra
+ // information with a URLRequest. Use user_data() and set_user_data()
+ class UserData {
+ public:
+ UserData() {};
+ virtual ~UserData() {};
+ };
+
+ // 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,
+ const std::string& scheme);
+
+ // This class handles network interception. Use with
+ // (Un)RegisterRequestInterceptor.
+ class 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) = 0;
+ };
+
+ // 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()
+ // - 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 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 request is not canceled the redirect will be followed and the
+ // request's URL will be changed to the new URL.
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url) = 0;
+
+ // 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) {
+ request->CancelAuth();
+ }
+
+ // 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 net::ERR_* error code
+ // indicating what's wrong with the certificate.
+ virtual void OnSSLCertificateError(URLRequest* request,
+ int cert_error,
+ X509Certificate* cert) {
+ request->Cancel();
+ }
+
+ // 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;
+ };
+
+ // Initialize an URL request.
+ URLRequest(const GURL& url, Delegate* 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.
+ ~URLRequest();
+
+ // The user data allows the owner to associate data with this request.
+ // This request will TAKE OWNERSHIP of the given pointer, and will delete
+ // the object if it is changed or the request is destroyed.
+ UserData* user_data() const {
+ return user_data_;
+ }
+ void set_user_data(UserData* user_data) {
+ if (user_data_)
+ delete user_data_;
+ user_data_ = user_data;
+ }
+
+ // 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);
+
+ // 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.
+ 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 original_url_; }
+ const GURL& url() const { return url_; }
+
+ // The URL that should be consulted for the third-party cookie blocking
+ // policy.
+ const GURL& policy_url() const { return policy_url_; }
+ void set_policy_url(const GURL& policy_url) {
+ DCHECK(!is_pending_);
+ policy_url_ = policy_url;
+ }
+
+ // 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) {
+ DCHECK(!is_pending_);
+ method_ = method;
+ }
+
+ // 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_; }
+ void set_referrer(const std::string& referrer) {
+ DCHECK(!is_pending_);
+ referrer_ = referrer;
+ }
+
+ // The delegate of the request. This value may be changed at any time,
+ // and it is permissible for it to be null.
+ Delegate* delegate() const { return delegate_; }
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // The data comprising the request message body is specified as a sequence of
+ // data segments and/or files containing data to upload. These methods may
+ // be called to construct the data sequence to upload, and they may only be
+ // called before Start() is called. For POST requests, the user must call
+ // SetRequestHeaderBy{Id,Name} to set the Content-Type of the request to the
+ // appropriate value before calling Start().
+ //
+ // When uploading data, bytes_len must be non-zero.
+ // When uploading a file range, length must be non-zero. If length
+ // exceeds the end-of-file, the upload is clipped at end-of-file.
+ void AppendBytesToUpload(const char* bytes, int bytes_len);
+ void AppendFileRangeToUpload(const std::wstring& file_path,
+ uint64 offset, uint64 length);
+ void AppendFileToUpload(const std::wstring& file_path) {
+ AppendFileRangeToUpload(file_path, 0, kuint64max);
+ }
+
+ // Set the upload data directly.
+ void set_upload(net::UploadData* upload) { upload_ = upload; }
+
+ // Returns true if the request has a non-empty message body to upload.
+ bool has_upload() const { return upload_ != NULL; }
+
+ // Set an extra request header by ID or name. These methods may only be
+ // called before Start() is called. It is an error to call it later.
+ void SetExtraRequestHeaderById(int header_id, const std::string& value,
+ bool overwrite);
+ void SetExtraRequestHeaderByName(const std::string& name,
+ const std::string& value, bool overwrite);
+
+ // Sets all extra request headers, from a \r\n-delimited string. 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 std::string& headers);
+
+ // Returns the current load state for the request.
+ net::LoadState GetLoadState() const;
+
+ // Returns the current upload progress in bytes.
+ uint64 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 at which the returned response was requested. For cached
+ // responses, this may be a time well in the past.
+ const Time& request_time() const {
+ return response_info_.request_time;
+ }
+
+ // The time at which the returned response was generated. For cached
+ // responses, this may be a time well in the past.
+ const Time& response_time() const {
+ return response_info_.response_time;
+ }
+
+ // Get all response headers, as a HttpResponseHeaders object. See comments
+ // in HttpResponseHeaders class as to the format of the data.
+ net::HttpResponseHeaders* response_headers() const {
+ return response_info_.headers.get();
+ }
+
+ // Get the SSL connection info.
+ const net::SSLInfo& ssl_info() const {
+ return response_info_.ssl_info;
+ }
+
+ // 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();
+
+ // Access the net::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; }
+
+ // Accessors to the pid of the process this request originated from.
+ int origin_pid() const { return origin_pid_; }
+ void set_origin_pid(int proc_id) {
+ origin_pid_ = proc_id;
+ }
+
+ // 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 the error status of the request. This value is 0 if there is no
+ // error. Otherwise, it is a value defined by the operating system (e.g., an
+ // error code returned by GetLastError() on windows).
+ const URLRequestStatus& status() const { return status_; }
+
+ // 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.
+ void Cancel();
+
+ // Similar to Cancel but sets the error to |os_error| (see net_error_list.h
+ // for values) instead of net::ERR_ABORTED.
+ // Used to attach a reason for canceling a request.
+ void CancelWithError(int os_error);
+
+ // 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 caller guarantees the
+ // buffer provided will be available until the Read is finished. The
+ // Read is finished when the caller receives the OnReadComplete
+ // callback. OnReadComplete will be always be called, even if there
+ // was a failure.
+ //
+ // The buf parameter is a buffer to receive the data. Once the read is
+ // initiated, the caller guarantees availability of this buffer until
+ // the OnReadComplete is received. 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(char* buf, int max_bytes, int *bytes_read);
+
+ // 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 std::wstring& username, const std::wstring& password);
+ void CancelAuth();
+
+ // 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();
+
+ // 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
+ };
+
+ // Returns true if performance profiling should be enabled on the
+ // URLRequestJob serving this request.
+ bool enable_profiling() const { return enable_profiling_; }
+
+ void set_enable_profiling(bool profiling) { enable_profiling_ = profiling; }
+
+ // Used to specify the context (cookie store, cache) for this request.
+ URLRequestContext* context() { return context_.get(); }
+ void set_context(URLRequestContext* context) { context_ = context; }
+
+ // Returns the expected content size if available
+ int64 GetExpectedContentSize() const;
+
+ 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 net::OK if
+ // successful, otherwise an error code is returned.
+ int Redirect(const GURL& location, int http_status_code);
+
+ private:
+ friend class URLRequestJob;
+
+ // 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();
+
+ scoped_refptr<URLRequestJob> job_;
+ scoped_refptr<net::UploadData> upload_;
+ GURL url_;
+ GURL original_url_;
+ GURL policy_url_;
+ std::string method_; // "GET", "POST", etc. Should be all uppercase.
+ std::string referrer_;
+ std::string extra_request_headers_;
+ int load_flags_; // Flags indicating the request type for the load;
+ // expected values are LOAD_* enums above.
+
+ // The pid of the process that initiated this request. Initialized to the id
+ // of the current process.
+ int origin_pid_;
+
+ 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.
+ net::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_;
+
+ // Externally-defined data associated with this request
+ UserData* user_data_;
+
+ // Whether to enable performance profiling on the job serving this request.
+ bool enable_profiling_;
+
+ // Number of times we're willing to redirect. Used to guard against
+ // infinite redirects.
+ int redirect_limit_;
+
+ // Contextual information used for this request (can be NULL).
+ scoped_refptr<URLRequestContext> context_;
+
+ // Cached value for use after we've orphaned the job handling the
+ // first transaction in a request involving redirects.
+ uint64 final_upload_progress_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequest);
+};
+
+//-----------------------------------------------------------------------------
+// To help ensure that all requests are cleaned up properly, we keep static
+// counters of live objects. TODO(darin): Move this leak checking stuff into
+// a common place and generalize it so it can be used everywhere (Bug 566229).
+
+#ifndef NDEBUG
+
+struct URLRequestMetrics {
+ int object_count;
+ URLRequestMetrics() : object_count(0) {}
+ ~URLRequestMetrics() {
+ DLOG_IF(WARNING, object_count != 0) <<
+ "Leaking " << object_count << " URLRequest object(s)";
+ }
+};
+
+extern URLRequestMetrics url_request_metrics;
+
+#define URLREQUEST_COUNT_CTOR() url_request_metrics.object_count++
+#define URLREQUEST_COUNT_DTOR() url_request_metrics.object_count--
+
+#else // disable leak checking in release builds...
+
+#define URLREQUEST_COUNT_CTOR()
+#define URLREQUEST_COUNT_DTOR()
+
+#endif
+
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_H__
diff --git a/net/url_request/url_request_about_job.cc b/net/url_request/url_request_about_job.cc
new file mode 100644
index 0000000..b102ad5
--- /dev/null
+++ b/net/url_request/url_request_about_job.cc
@@ -0,0 +1,62 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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/message_loop.h"
+
+// static
+URLRequestJob* URLRequestAboutJob::Factory(URLRequest* request,
+ const std::string& scheme) {
+ return new URLRequestAboutJob(request);
+}
+
+URLRequestAboutJob::URLRequestAboutJob(URLRequest* request)
+ : URLRequestJob(request) {
+}
+
+void URLRequestAboutJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestAboutJob::StartAsync));
+}
+
+bool URLRequestAboutJob::GetMimeType(std::string* mime_type) {
+ *mime_type = "text/html";
+ return true;
+}
+
+void URLRequestAboutJob::StartAsync() {
+ NotifyHeadersComplete();
+}
diff --git a/net/url_request/url_request_about_job.h b/net/url_request/url_request_about_job.h
new file mode 100644
index 0000000..f94f9a7
--- /dev/null
+++ b/net/url_request/url_request_about_job.h
@@ -0,0 +1,49 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H__
+#define BASE_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H__
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+class URLRequestAboutJob : public URLRequestJob {
+ public:
+ URLRequestAboutJob(URLRequest* request);
+
+ virtual void Start();
+ virtual bool GetMimeType(std::string* mime_type);
+
+ static URLRequest::ProtocolFactory Factory;
+
+ private:
+ void StartAsync();
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H__
diff --git a/net/url_request/url_request_context.h b/net/url_request/url_request_context.h
new file mode 100644
index 0000000..059df6c
--- /dev/null
+++ b/net/url_request/url_request_context.h
@@ -0,0 +1,105 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 BASE_URL_REQUEST_URL_REQUEST_CONTEXT_H__
+#define BASE_URL_REQUEST_URL_REQUEST_CONTEXT_H__
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "net/base/auth_cache.h"
+#include "net/base/cookie_policy.h"
+#include "net/http/http_transaction_factory.h"
+
+class CookieMonster;
+
+// Subclass to provide application-specific context for URLRequest instances.
+class URLRequestContext :
+ public base::RefCountedThreadSafe<URLRequestContext> {
+ public:
+ URLRequestContext()
+ : http_transaction_factory_(NULL),
+ cookie_store_(NULL),
+ is_off_the_record_(false) {
+ }
+
+ // Gets the http transaction factory for this context.
+ net::HttpTransactionFactory* http_transaction_factory() {
+ return http_transaction_factory_;
+ }
+
+ // Gets the cookie store for this context.
+ CookieMonster* cookie_store() { return cookie_store_; }
+
+ // Gets the cookie policy for this context.
+ CookiePolicy* cookie_policy() { return &cookie_policy_; }
+
+ // Gets the FTP realm authentication cache for this context.
+ AuthCache* ftp_auth_cache() { return &ftp_auth_cache_; }
+
+ // Gets the UA string to use for this context.
+ const std::string& user_agent() const { return user_agent_; }
+
+ // Gets the value of 'Accept-Charset' header field.
+ const std::string& accept_charset() const { return accept_charset_; }
+
+ // Gets the value of 'Accept-Language' header field.
+ const std::string& accept_language() const { return accept_language_; }
+
+ // Returns true if this context is off the record.
+ bool is_off_the_record() { return is_off_the_record_; }
+
+ // Do not call this directly. TODO(darin): extending from RefCounted* should
+ // not require a public destructor!
+ virtual ~URLRequestContext() {}
+
+ protected:
+ // The following members are expected to be initialized and owned by
+ // subclasses.
+ net::HttpTransactionFactory* http_transaction_factory_;
+ CookieMonster* cookie_store_;
+ CookiePolicy cookie_policy_;
+ AuthCache ftp_auth_cache_;
+ std::string user_agent_;
+ bool is_off_the_record_;
+ std::string accept_language_;
+ std::string accept_charset_;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestContext);
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_CONTEXT_H__
diff --git a/net/url_request/url_request_error_job.cc b/net/url_request/url_request_error_job.cc
new file mode 100644
index 0000000..f9c7d38
--- /dev/null
+++ b/net/url_request/url_request_error_job.cc
@@ -0,0 +1,46 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_error_job.h"
+
+#include "base/message_loop.h"
+#include "net/base/net_errors.h"
+
+URLRequestErrorJob::URLRequestErrorJob(URLRequest* request, int error)
+ : URLRequestJob(request), error_(error) {
+}
+
+void URLRequestErrorJob::Start() {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestErrorJob::StartAsync));
+}
+
+void URLRequestErrorJob::StartAsync() {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, error_));
+}
diff --git a/net/url_request/url_request_error_job.h b/net/url_request/url_request_error_job.h
new file mode 100644
index 0000000..d93ca7f
--- /dev/null
+++ b/net/url_request/url_request_error_job.h
@@ -0,0 +1,49 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Invalid URLs go through this URLRequestJob class rather than being passed
+// to the default job handler.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_ERROR_JOB_H__
+#define BASE_URL_REQUEST_URL_REQUEST_ERROR_JOB_H__
+
+#include "net/url_request/url_request_job.h"
+
+class URLRequestErrorJob : public URLRequestJob {
+ public:
+ URLRequestErrorJob(URLRequest* request, int error);
+
+ virtual void Start();
+
+ private:
+ int error_;
+ void StartAsync();
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_ERROR_JOB_H__
diff --git a/net/url_request/url_request_file_dir_job.cc b/net/url_request/url_request_file_dir_job.cc
new file mode 100644
index 0000000..5ad44a6
--- /dev/null
+++ b/net/url_request/url_request_file_dir_job.cc
@@ -0,0 +1,207 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_file_dir_job.h"
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_util.h"
+#include "net/base/wininet_util.h"
+#include "net/url_request/url_request.h"
+
+using std::string;
+using std::wstring;
+
+using net::WinInetUtil;
+
+URLRequestFileDirJob::URLRequestFileDirJob(URLRequest* request,
+ const wstring& dir_path)
+ : URLRequestJob(request),
+ dir_path_(dir_path),
+ canceled_(false),
+ list_complete_(false),
+ wrote_header_(false),
+ read_pending_(false),
+ read_buffer_(NULL),
+ read_buffer_length_(0) {
+}
+
+URLRequestFileDirJob::~URLRequestFileDirJob() {
+ DCHECK(read_pending_ == false);
+ DCHECK(lister_ == NULL);
+}
+
+void URLRequestFileDirJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFileDirJob::StartAsync));
+}
+
+void URLRequestFileDirJob::StartAsync() {
+ DCHECK(!lister_);
+
+ // AddRef so that *this* cannot be destroyed while the lister_
+ // is trying to feed us data.
+
+ AddRef();
+ lister_ = new DirectoryLister(dir_path_, this);
+ lister_->Start();
+
+ NotifyHeadersComplete();
+}
+
+void URLRequestFileDirJob::Kill() {
+ if (canceled_)
+ return;
+
+ canceled_ = true;
+
+ // Don't call CloseLister or dispatch an error to the URLRequest because we
+ // want OnListDone to be called to also write the error to the output stream.
+ // OnListDone will notify the URLRequest at this time.
+ if (lister_)
+ lister_->Cancel();
+}
+
+bool URLRequestFileDirJob::ReadRawData(char* buf, int buf_size,
+ int *bytes_read) {
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+
+ if (is_done())
+ return true;
+
+ if (FillReadBuffer(buf, 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(string* mime_type) {
+ *mime_type = "text/html";
+ return true;
+}
+
+bool URLRequestFileDirJob::GetCharset(string* charset) {
+ // All the filenames are converted to UTF-8 before being added.
+ *charset = "utf-8";
+ return true;
+}
+
+void URLRequestFileDirJob::OnListFile(const WIN32_FIND_DATA& data) {
+ FILETIME local_time;
+ FileTimeToLocalFileTime(&data.ftLastWriteTime, &local_time);
+ int64 size = (static_cast<unsigned __int64>(data.nFileSizeHigh) << 32) |
+ data.nFileSizeLow;
+
+ // 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_) {
+ data_.append(net_util::GetDirectoryListingHeader(WideToUTF8(dir_path_)));
+ wrote_header_ = true;
+ }
+
+ data_.append(net_util::GetDirectoryListingEntry(
+ WideToUTF8(data.cFileName), data.dwFileAttributes, size, &local_time));
+
+ // TODO(darin): coalesce more?
+
+ CompleteRead();
+}
+
+void URLRequestFileDirJob::OnListDone(int error) {
+ CloseLister();
+
+ if (error) {
+ read_pending_ = false;
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(error)));
+ } else if (canceled_) {
+ read_pending_ = false;
+ NotifyCanceled();
+ } else {
+ list_complete_ = true;
+ CompleteRead();
+ }
+
+ Release(); // The Lister is finished; may delete *this*
+}
+
+void URLRequestFileDirJob::CloseLister() {
+ if (lister_) {
+ lister_->Cancel();
+ lister_->set_delegate(NULL);
+ lister_ = NULL;
+ }
+}
+
+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;
+}
+
+void URLRequestFileDirJob::CompleteRead() {
+ if (read_pending_) {
+ int bytes_read;
+ if (FillReadBuffer(read_buffer_, 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();
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, 0)); // TODO: Better error code.
+ }
+ }
+}
diff --git a/net/url_request/url_request_file_dir_job.h b/net/url_request/url_request_file_dir_job.h
new file mode 100644
index 0000000..78d6863
--- /dev/null
+++ b/net/url_request/url_request_file_dir_job.h
@@ -0,0 +1,85 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H__
+#define NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H__
+
+#include "net/base/directory_lister.h"
+#include "net/url_request/url_request_job.h"
+
+class URLRequestFileDirJob : public URLRequestJob,
+ public DirectoryLister::Delegate {
+ public:
+ URLRequestFileDirJob(URLRequest* request, const std::wstring& dir_path);
+ virtual ~URLRequestFileDirJob();
+
+ // URLRequestJob methods:
+ virtual void Start();
+ virtual void StartAsync();
+ virtual void Kill();
+ virtual bool ReadRawData(char* buf, int buf_size, int *bytes_read);
+ virtual bool GetMimeType(std::string* mime_type);
+ virtual bool GetCharset(std::string* charset);
+
+ // DirectoryLister::Delegate methods:
+ virtual void OnListFile(const WIN32_FIND_DATA& data);
+ virtual void OnListDone(int error);
+
+ private:
+ 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);
+
+ scoped_refptr<DirectoryLister> lister_;
+ std::wstring 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_;
+ char *read_buffer_;
+ int read_buffer_length_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestFileDirJob);
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H__
diff --git a/net/url_request/url_request_file_job.cc b/net/url_request/url_request_file_job.cc
new file mode 100644
index 0000000..17e5f5c
--- /dev/null
+++ b/net/url_request/url_request_file_job.cc
@@ -0,0 +1,349 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// 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 <process.h>
+#include <windows.h>
+
+#include "net/url_request/url_request_file_job.h"
+
+#include "base/file_util.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/wininet_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_file_dir_job.h"
+
+using net::WinInetUtil;
+
+namespace {
+
+// Thread used to run ResolveFile. The parameter is a pointer to the
+// URLRequestFileJob object.
+DWORD WINAPI NetworkFileThread(LPVOID param) {
+ URLRequestFileJob* job = reinterpret_cast<URLRequestFileJob*>(param);
+ job->ResolveFile();
+ return 0;
+}
+
+} // namespace
+
+// static
+URLRequestJob* URLRequestFileJob::Factory(URLRequest* request,
+ const std::string& scheme) {
+ std::wstring file_path;
+ if (net_util::FileURLToFilePath(request->url(), &file_path)) {
+ if (file_path[file_path.size() - 1] == file_util::kPathSeparator) {
+ // Only directories have trailing slashes.
+ return new URLRequestFileDirJob(request, file_path);
+ }
+ }
+
+ // Use a regular file request job for all non-directories (including invalid
+ // file names).
+ URLRequestFileJob* job = new URLRequestFileJob(request);
+ job->file_path_ = file_path;
+ return job;
+}
+
+URLRequestFileJob::URLRequestFileJob(URLRequest* request)
+ : URLRequestJob(request),
+ handle_(INVALID_HANDLE_VALUE),
+ is_waiting_(false),
+ is_directory_(false),
+ is_not_found_(false),
+ network_file_thread_(NULL),
+ loop_(NULL) {
+ memset(&overlapped_, 0, sizeof(overlapped_));
+}
+
+URLRequestFileJob::~URLRequestFileJob() {
+ CloseHandles();
+
+ // The thread might still be running. We need to kill it because it holds
+ // a reference to this object.
+ if (network_file_thread_) {
+ TerminateThread(network_file_thread_, 0);
+ CloseHandle(network_file_thread_);
+ }
+}
+
+// This function can be called on the main thread or on the network file thread.
+// When the request is done, we call StartAsync on the main thread.
+void URLRequestFileJob::ResolveFile() {
+ WIN32_FILE_ATTRIBUTE_DATA file_attributes = {0};
+ if (!GetFileAttributesEx(file_path_.c_str(),
+ GetFileExInfoStandard,
+ &file_attributes)) {
+ is_not_found_ = true;
+ } else {
+ if (file_attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ is_directory_ = true;
+ } else {
+ // Set the file size as expected size to be read.
+ ULARGE_INTEGER file_size;
+ file_size.HighPart = file_attributes.nFileSizeHigh;
+ file_size.LowPart = file_attributes.nFileSizeLow;
+
+ set_expected_content_size(file_size.QuadPart);
+ }
+ }
+
+
+ // We need to protect the loop_ pointer with a lock because if we are running
+ // on the network file thread, it is possible that the main thread is
+ // executing Kill() at this moment. Kill() sets the loop_ to NULL because it
+ // might be going away.
+ AutoLock lock(loop_lock_);
+ if (loop_) {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFileJob::StartAsync));
+ }
+}
+
+void URLRequestFileJob::Start() {
+ // This is the loop on which we should execute StartAsync. This is used in
+ // ResolveFile().
+ loop_ = MessageLoop::current();
+
+ if (!file_path_.compare(0, 2, L"\\\\")) {
+ // This is on a network share. It might be slow to check if it's a directory
+ // or a file. We need to do it in another thread.
+ network_file_thread_ = CreateThread(NULL, 0, &NetworkFileThread, this, 0,
+ NULL);
+ } else {
+ // We can call the function directly because it's going to be fast.
+ ResolveFile();
+ }
+}
+
+void URLRequestFileJob::Kill() {
+ // If we are killed while waiting for an overlapped result...
+ if (is_waiting_) {
+ MessageLoop::current()->WatchObject(overlapped_.hEvent, NULL);
+ is_waiting_ = false;
+ Release();
+ }
+ CloseHandles();
+ URLRequestJob::Kill();
+
+ // It is possible that the network file thread is running and will invoke
+ // StartAsync() on loop_. We set loop_ to NULL here because the message loop
+ // might be going away and we don't want the other thread to call StartAsync()
+ // on this loop anymore. We protect loop_ with a lock in case the other thread
+ // is currenly invoking the call.
+ AutoLock lock(loop_lock_);
+ loop_ = NULL;
+}
+
+bool URLRequestFileJob::ReadRawData(char* dest, int dest_size,
+ int *bytes_read) {
+ DCHECK_NE(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK(!is_waiting_);
+
+ *bytes_read = 0;
+
+ DWORD bytes;
+ if (ReadFile(handle_, dest, dest_size, &bytes, &overlapped_)) {
+ // data is immediately available
+ overlapped_.Offset += bytes;
+ *bytes_read = static_cast<int>(bytes);
+ DCHECK(!is_waiting_);
+ DCHECK(!is_done());
+ return true;
+ }
+
+ // otherwise, a read error occured.
+ DWORD err = GetLastError();
+ if (err == ERROR_IO_PENDING) {
+ // OK, wait for the object to become signaled
+ MessageLoop::current()->WatchObject(overlapped_.hEvent, this);
+ is_waiting_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ AddRef();
+ return false;
+ }
+ if (err == ERROR_HANDLE_EOF) {
+ // OK, nothing more to read
+ return true;
+ }
+
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(err)));
+ return false;
+}
+
+bool URLRequestFileJob::GetMimeType(std::string* mime_type) {
+ DCHECK(request_);
+ return mime_util::GetMimeTypeFromFile(file_path_, mime_type);
+}
+
+void URLRequestFileJob::CloseHandles() {
+ DCHECK(!is_waiting_);
+ if (handle_ != INVALID_HANDLE_VALUE) {
+ CloseHandle(handle_);
+ handle_ = INVALID_HANDLE_VALUE;
+ }
+ if (overlapped_.hEvent) {
+ CloseHandle(overlapped_.hEvent);
+ overlapped_.hEvent = NULL;
+ }
+}
+
+void URLRequestFileJob::StartAsync() {
+ if (network_file_thread_) {
+ CloseHandle(network_file_thread_);
+ network_file_thread_ = NULL;
+ }
+
+ // The request got killed, we need to stop.
+ if (!loop_)
+ return;
+
+ // We may have been orphaned...
+ if (!request_)
+ return;
+
+ // This is not a file, this is a directory.
+ if (is_directory_) {
+ NotifyHeadersComplete();
+ return;
+ }
+
+ if (is_not_found_) {
+ // some kind of invalid file URI
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ net::ERR_FILE_NOT_FOUND));
+ } else {
+ handle_ = CreateFile(file_path_.c_str(),
+ GENERIC_READ|SYNCHRONIZE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN,
+ NULL);
+ if (handle_ == INVALID_HANDLE_VALUE) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(GetLastError())));
+ } else {
+ // Get setup for overlapped reads (w/ a manual reset event)
+ overlapped_.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ }
+ }
+
+ NotifyHeadersComplete();
+}
+
+void URLRequestFileJob::OnObjectSignaled(HANDLE object) {
+ DCHECK(overlapped_.hEvent == object);
+ DCHECK(is_waiting_);
+
+ // We'll resume watching this handle if need be when we do
+ // another IO.
+ MessageLoop::current()->WatchObject(object, NULL);
+ is_waiting_ = false;
+
+ DWORD bytes_read = 0;
+ if (!GetOverlappedResult(handle_, &overlapped_, &bytes_read, FALSE)) {
+ DWORD err = GetLastError();
+ if (err == ERROR_HANDLE_EOF) {
+ // successfully read all data
+ NotifyDone(URLRequestStatus());
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(err)));
+ }
+ } else if (bytes_read) {
+ overlapped_.Offset += bytes_read;
+ // clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+ } else {
+ // there was no more data so we're done
+ NotifyDone(URLRequestStatus());
+ }
+ NotifyReadComplete(bytes_read);
+
+ Release(); // Balance with AddRef from FillBuf; may destroy |this|
+}
+
+bool URLRequestFileJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (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;
+ }
+
+ size_t found;
+ found = file_path_.find_last_of('.');
+
+ // We just resolve .lnk file, ignor others.
+ if (found == std::string::npos ||
+ !LowerCaseEqualsASCII(file_path_.substr(found), ".lnk"))
+ return false;
+
+ std::wstring new_path = file_path_;
+ bool resolved;
+ resolved = file_util::ResolveShortcut(&new_path);
+
+ // If shortcut is not resolved succesfully, do not redirect.
+ if (!resolved)
+ return false;
+
+ *location = net_util::FilePathToFileURL(new_path);
+ *http_status_code = 301;
+ return true;
+}
diff --git a/net/url_request/url_request_file_job.h b/net/url_request/url_request_file_job.h
new file mode 100644
index 0000000..1851444
--- /dev/null
+++ b/net/url_request/url_request_file_job.h
@@ -0,0 +1,98 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_FILE_JOB_H__
+#define BASE_URL_REQUEST_URL_REQUEST_FILE_JOB_H__
+
+#include "base/lock.h"
+#include "base/message_loop.h"
+#include "base/thread.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+// A request job that handles reading file URLs
+class URLRequestFileJob : public URLRequestJob,
+ protected MessageLoop::Watcher {
+ public:
+ URLRequestFileJob(URLRequest* request);
+ virtual ~URLRequestFileJob();
+
+ virtual void Start();
+ virtual void Kill();
+ virtual bool ReadRawData(char* buf, int buf_size, int *bytes_read);
+ virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
+
+ virtual bool GetMimeType(std::string* mime_type);
+
+ // Checks the status of the file. Set is_directory_ and is_not_found_
+ // accordingly. Call StartAsync on the main message loop when it's done.
+ void ResolveFile();
+
+ static URLRequest::ProtocolFactory Factory;
+
+ protected:
+ // The OS-specific full path name of the file
+ std::wstring file_path_;
+
+ private:
+ // The net util test wants to run our FileURLToFilePath function.
+ FRIEND_TEST(NetUtilTest, FileURLConversion);
+
+ void CloseHandles();
+ void StartAsync();
+
+ // MessageLoop::Watcher callback
+ virtual void OnObjectSignaled(HANDLE object);
+
+ // We use overlapped reads to ensure that reads from network file systems do
+ // not hang the application thread.
+ HANDLE handle_;
+ OVERLAPPED overlapped_;
+ bool is_waiting_; // true when waiting for overlapped result
+ bool is_directory_; // true when the file request is for a direcotry.
+ bool is_not_found_; // true when the file requested does not exist.
+
+ // This lock ensure that the network_file_thread is not using the loop_ after
+ // is has been set to NULL in Kill().
+ Lock loop_lock_;
+
+ // Main message loop where StartAsync has to be called.
+ MessageLoop* loop_;
+
+ // Thread used to query the attributes of files on the network.
+ // We need to do it on a separate thread because it can be really
+ // slow.
+ HANDLE network_file_thread_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestFileJob);
+};
+
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_FILE_JOB_H__
diff --git a/net/url_request/url_request_filter.cc b/net/url_request/url_request_filter.cc
new file mode 100644
index 0000000..57e76ba
--- /dev/null
+++ b/net/url_request/url_request_filter.cc
@@ -0,0 +1,156 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_filter.h"
+
+#include <set>
+
+#include "net/url_request/url_request_inet_job.h"
+
+URLRequestFilter* URLRequestFilter::shared_instance_ = NULL;
+
+/* static */
+URLRequestFilter* URLRequestFilter::GetInstance() {
+ if (!shared_instance_)
+ shared_instance_ = new URLRequestFilter;
+ return shared_instance_;
+}
+
+/* static */
+URLRequestJob* URLRequestFilter::Factory(URLRequest* request,
+ const std::string& scheme) {
+ // Returning null here just means that the built-in handler will be used.
+ return GetInstance()->FindRequestHandler(request, scheme);
+}
+
+void URLRequestFilter::AddHostnameHandler(const std::string& scheme,
+ const std::string& hostname, URLRequest::ProtocolFactory* factory) {
+ hostname_handler_map_[make_pair(scheme, hostname)] = factory;
+
+ // Register with the ProtocolFactory.
+ URLRequest::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());
+
+ 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) {
+ if (!url.is_valid())
+ return false;
+ url_handler_map_[url.spec()] = factory;
+
+ // Register with the ProtocolFactory.
+ URLRequest::RegisterProtocolFactory(url.scheme(),
+ &URLRequestFilter::Factory);
+#ifndef NDEBUG
+ // Check to see if this URL is masked by a hostname handler.
+ HostnameHandlerMap::iterator host_it =
+ hostname_handler_map_.find(make_pair(url.scheme(), url.host()));
+ if (host_it != hostname_handler_map_.end())
+ NOTREACHED();
+#endif // !NDEBUG
+
+ return true;
+}
+
+void URLRequestFilter::RemoveUrlHandler(const GURL& url) {
+ UrlHandlerMap::iterator iter = url_handler_map_.find(url.spec());
+ DCHECK(iter != url_handler_map_.end());
+
+ 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::RegisterProtocolFactory(*scheme, NULL);
+ }
+
+ url_handler_map_.clear();
+ hostname_handler_map_.clear();
+}
+
+URLRequestJob* URLRequestFilter::FindRequestHandler(URLRequest* request,
+ 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(request, scheme);
+
+ 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(request, scheme);
+ }
+ }
+ return job;
+}
diff --git a/net/url_request/url_request_filter.h b/net/url_request/url_request_filter.h
new file mode 100644
index 0000000..6bdb2bd
--- /dev/null
+++ b/net/url_request/url_request_filter.h
@@ -0,0 +1,105 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// 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 BASE_URL_REQUEST_URL_REQUEST_FILTER_H__
+#define BASE_URL_REQUEST_URL_REQUEST_FILTER_H__
+
+#include <hash_map>
+#include <map>
+#include <string>
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+class GURL;
+
+class URLRequestFilter {
+ public:
+ // scheme,hostname -> ProtocolFactory
+ typedef std::map<std::pair<std::string, std::string>,
+ URLRequest::ProtocolFactory*> HostnameHandlerMap;
+ typedef stdext::hash_map<std::string, URLRequest::ProtocolFactory*>
+ UrlHandlerMap;
+
+ // Singleton instance for use.
+ static URLRequestFilter* GetInstance();
+
+ static URLRequest::ProtocolFactory Factory;
+
+ void AddHostnameHandler(const std::string& scheme,
+ const std::string& hostname,
+ URLRequest::ProtocolFactory* factory);
+ 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);
+
+ void RemoveUrlHandler(const GURL& url);
+
+ // Clear all the existing URL handlers and unregister with the
+ // ProtocolFactory.
+ void ClearHandlers();
+
+ protected:
+ URLRequestFilter() { }
+
+ // Helper method that looks up the request in the url_handler_map_.
+ URLRequestJob* FindRequestHandler(URLRequest* request,
+ 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_;
+
+ private:
+ // Singleton instance.
+ static URLRequestFilter* shared_instance_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestFilter);
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_FILTER_H__
diff --git a/net/url_request/url_request_ftp_job.cc b/net/url_request/url_request_ftp_job.cc
new file mode 100644
index 0000000..f619609
--- /dev/null
+++ b/net/url_request/url_request_ftp_job.cc
@@ -0,0 +1,547 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_ftp_job.h"
+
+#include <windows.h>
+#include <wininet.h>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/auth.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_util.h"
+#include "net/base/wininet_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/base/escape.h"
+
+using std::string;
+
+using net::WinInetUtil;
+
+// When building the directory listing, the period to wait before notifying
+// the parent class that we wrote the data.
+#define kFtpBufferTimeMs 50
+
+static bool UnescapeAndValidatePath(const URLRequest* request,
+ std::string* unescaped_path) {
+ // Path in GURL is %-encoded UTF-8. FTP servers do not
+ // understand %-escaped path so that we have to unescape leading to an
+ // unescaped UTF-8 path. Then, the presence of NULL, CR and LF is checked
+ // because they're not allowed in FTP.
+ // TODO(jungshik) Even though RFC 2640 specifies that UTF-8 be used.
+ // There are many FTP servers that use legacy encodings. For them,
+ // we need to identify the encoding and convert to that encoding.
+ static const std::string kInvalidChars("\x00\x0d\x0a", 3);
+ *unescaped_path = UnescapeURLComponent(request->url().path(),
+ UnescapeRule::SPACES | UnescapeRule::PERCENTS);
+ if (unescaped_path->find_first_of(kInvalidChars) != std::string::npos) {
+ SetLastError(ERROR_INTERNET_INVALID_URL);
+ // GURL path should not contain '%00' which is NULL(0x00) when unescaped.
+ // URLRequestFtpJob should not have been invoked for an invalid GURL.
+ DCHECK(unescaped_path->find(std::string("\x00", 1)) == std::string::npos) <<
+ "Path should not contain %00.";
+ return false;
+ }
+ return true;
+}
+
+// static
+URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request,
+ const std::string &scheme) {
+ DCHECK(scheme == "ftp");
+
+ if (request->url().has_port() &&
+ !net_util::IsPortAllowedByFtp(request->url().IntPort()))
+ return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT);
+
+ return new URLRequestFtpJob(request);
+}
+
+URLRequestFtpJob::URLRequestFtpJob(URLRequest* request)
+ : URLRequestInetJob(request), state_(START), is_directory_(false),
+ dest_(NULL), dest_size_(0) {
+}
+
+URLRequestFtpJob::~URLRequestFtpJob() {
+}
+
+void URLRequestFtpJob::Start() {
+ GURL parts(request_->url());
+ const std::string& scheme = parts.scheme();
+
+ // We should only be dealing with FTP at this point:
+ DCHECK(LowerCaseEqualsASCII(scheme, "ftp"));
+
+ SendRequest();
+}
+
+bool URLRequestFtpJob::GetMimeType(std::string* mime_type) {
+ if (!is_directory_)
+ return false;
+
+ mime_type->assign("text/html");
+ return true;
+}
+
+void URLRequestFtpJob::OnCancelAuth() {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::ContinueNotifyHeadersComplete));
+}
+
+void URLRequestFtpJob::OnSetAuth() {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::SendRequest));
+}
+
+void URLRequestFtpJob::SendRequest() {
+ state_ = CONNECTING;
+
+ DWORD flags =
+ INTERNET_FLAG_KEEP_CONNECTION |
+ INTERNET_FLAG_EXISTING_CONNECT |
+ INTERNET_FLAG_PASSIVE |
+ INTERNET_FLAG_RAW_DATA;
+
+ // It doesn't make sense to ask for both a cache validation and a
+ // reload at the same time.
+ DCHECK(!((request_->load_flags() & net::LOAD_VALIDATE_CACHE) &&
+ (request_->load_flags() & net::LOAD_BYPASS_CACHE)));
+
+ if (request_->load_flags() & net::LOAD_BYPASS_CACHE)
+ flags |= INTERNET_FLAG_RELOAD;
+
+ // Apply authentication if we have any, otherwise authenticate
+ // according to FTP defaults. (See InternetConnect documentation.)
+ // First, check if we have auth in cache, then check URL.
+ // That way a user can re-enter credentials, and we'll try with their
+ // latest input rather than always trying what they specified
+ // in the url (if anything).
+ string username, password;
+ bool have_auth = false;
+ if (server_auth_ != NULL && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ // Add auth info to cache
+ have_auth = true;
+ username = WideToUTF8(server_auth_->username);
+ password = WideToUTF8(server_auth_->password);
+ request_->context()->ftp_auth_cache()->Add(request_->url().host(),
+ server_auth_.get());
+ } else {
+ if (request_->url().has_username()) {
+ username = request_->url().username();
+ password = request_->url().has_password() ? request_->url().password() :
+ "";
+ have_auth = true;
+ }
+ }
+
+ int port = request_->url().has_port() ?
+ request_->url().IntPort() : INTERNET_DEFAULT_FTP_PORT;
+
+ connection_handle_ = InternetConnectA(GetTheInternet(),
+ request_->url().host().c_str(),
+ port,
+ have_auth ? username.c_str() : NULL,
+ have_auth ? password.c_str() : NULL,
+ INTERNET_SERVICE_FTP, flags,
+ reinterpret_cast<DWORD_PTR>(this));
+
+ if (connection_handle_) {
+ OnConnect();
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+}
+
+void URLRequestFtpJob::OnIOComplete(const AsyncResult& result) {
+ if (state_ == CONNECTING) {
+ switch (result.dwError) {
+ case ERROR_NO_MORE_FILES:
+ // url is an empty directory
+ OnStartDirectoryTraversal();
+ OnFinishDirectoryTraversal();
+ return;
+ case ERROR_INTERNET_LOGIN_FAILURE:
+ // fall through
+ case ERROR_INTERNET_INCORRECT_USER_NAME:
+ // fall through
+ case ERROR_INTERNET_INCORRECT_PASSWORD:
+ if (server_auth_ != NULL &&
+ server_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ request_->context()->ftp_auth_cache()->Remove(request_->url().host());
+ } else {
+ server_auth_ = new AuthData();
+ }
+ // Try again, prompting for authentication.
+ server_auth_->state = AUTH_STATE_NEED_AUTH;
+ // The io completed fine, the error was due to invalid auth.
+ SetStatus(URLRequestStatus());
+ NotifyHeadersComplete();
+ return;
+ case ERROR_SUCCESS:
+ connection_handle_ = (HINTERNET)result.dwResult;
+ OnConnect();
+ return;
+ case ERROR_INTERNET_EXTENDED_ERROR: {
+ DWORD extended_err(ERROR_SUCCESS);
+ DWORD size = 1;
+ char buffer[1];
+ if (!InternetGetLastResponseInfoA(&extended_err, buffer, &size))
+ // We don't care about the error text here, so the only acceptable
+ // error is one regarding insufficient buffer length.
+ DCHECK(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ if (extended_err != ERROR_SUCCESS) {
+ CleanupConnection();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(extended_err)));
+ return;
+ }
+ // Fall through in the case we saw ERROR_INTERNET_EXTENDED_ERROR but
+ // InternetGetLastResponseInfo gave us no additional information.
+ }
+ default:
+ CleanupConnection();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(result.dwError)));
+ return;
+ }
+ } else if (state_ == SETTING_CUR_DIRECTORY) {
+ OnSetCurrentDirectory(result.dwError);
+ } else if (state_ == FINDING_FIRST_FILE) {
+ if (result.dwError != ERROR_SUCCESS) {
+ DWORD result_error = result.dwError;
+ CleanupConnection();
+ // Fixup the error message from our directory/file guessing.
+ if (!is_directory_ && result_error == ERROR_NO_MORE_FILES)
+ result_error = ERROR_PATH_NOT_FOUND;
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(result_error)));
+ return;
+ }
+ request_handle_ = (HINTERNET)result.dwResult;
+ OnFindFirstFile(result.dwError);
+ } else if (state_ == GETTING_DIRECTORY) {
+ OnFindFile(result.dwError);
+ } else if (state_ == GETTING_FILE_HANDLE) {
+ if (result.dwError != ERROR_SUCCESS) {
+ CleanupConnection();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(result.dwError)));
+ return;
+ }
+ // start reading file contents
+ state_ = GETTING_FILE;
+ request_handle_ = (HINTERNET)result.dwResult;
+ NotifyHeadersComplete();
+ } else {
+ // we don't have IO outstanding. Pass to our base class.
+ URLRequestInetJob::OnIOComplete(result);
+ }
+}
+
+bool URLRequestFtpJob::NeedsAuth() {
+ // Note that we only have to worry about cases where an actual FTP server
+ // requires auth (and not a proxy), because connecting to FTP via proxy
+ // effectively means the browser communicates via HTTP, and uses HTTP's
+ // Proxy-Authenticate protocol when proxy servers require auth.
+ return ((server_auth_ != NULL) &&
+ server_auth_->state == AUTH_STATE_NEED_AUTH);
+}
+
+void URLRequestFtpJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* result) {
+ DCHECK((server_auth_ != NULL) &&
+ (server_auth_->state == AUTH_STATE_NEED_AUTH));
+ scoped_refptr<AuthChallengeInfo> auth_info = new AuthChallengeInfo;
+ auth_info->is_proxy = false;
+ auth_info->host = UTF8ToWide(request_->url().host());
+ auth_info->scheme = L"";
+ auth_info->realm = L"";
+ result->swap(auth_info);
+}
+
+void URLRequestFtpJob::GetCachedAuthData(
+ const AuthChallengeInfo& auth_info,
+ scoped_refptr<AuthData>* auth_data) {
+ *auth_data = request_->context()->ftp_auth_cache()->
+ Lookup(WideToUTF8(auth_info.host));
+}
+
+void URLRequestFtpJob::OnConnect() {
+ DCHECK_EQ(state_, CONNECTING);
+
+ state_ = SETTING_CUR_DIRECTORY;
+ // Setting the directory lets us determine if the URL is a file,
+ // and also keeps the working directory for the FTP session in sync
+ // with what is being displayed in the browser.
+ if (request_->url().has_path()) {
+ std::string unescaped_path;
+ if (UnescapeAndValidatePath(request_, &unescaped_path) &&
+ FtpSetCurrentDirectoryA(connection_handle_,
+ unescaped_path.c_str())) {
+ OnSetCurrentDirectory(ERROR_SUCCESS);
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+ }
+}
+
+void URLRequestFtpJob::OnSetCurrentDirectory(DWORD last_error) {
+ DCHECK_EQ(state_, SETTING_CUR_DIRECTORY);
+
+ is_directory_ = (last_error == ERROR_SUCCESS);
+ // if last_error is not ERROR_SUCCESS, the requested url is either
+ // a file or an invalid path. We optimistically try to read as a file,
+ // and if it fails, we fail.
+ state_ = FINDING_FIRST_FILE;
+
+ std::string unescaped_path;
+ bool is_path_valid = true;
+ if (request_->url().has_path()) {
+ is_path_valid = UnescapeAndValidatePath(request_, &unescaped_path);
+ }
+ if (is_path_valid &&
+ (request_handle_ = FtpFindFirstFileA(connection_handle_,
+ unescaped_path.c_str(),
+ &find_data_, 0,
+ reinterpret_cast<DWORD_PTR>(this)))) {
+ OnFindFirstFile(GetLastError());
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+}
+
+void URLRequestFtpJob::FindNextFile() {
+ DWORD last_error;
+ if (InternetFindNextFileA(request_handle_, &find_data_)) {
+ last_error = ERROR_SUCCESS;
+ } else {
+ last_error = GetLastError();
+ // We'll get ERROR_NO_MORE_FILES if the directory is empty.
+ if (last_error != ERROR_NO_MORE_FILES) {
+ ProcessRequestError(last_error);
+ return;
+ }
+ }
+ // Use InvokeLater to call OnFindFile as it ends up calling us, so we don't
+ // to blow the stack.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::OnFindFile, last_error));
+}
+
+void URLRequestFtpJob::OnFindFirstFile(DWORD last_error) {
+ DCHECK_EQ(state_, FINDING_FIRST_FILE);
+ if (!is_directory_) {
+ // Note that it is not enough to just check !is_directory_ and assume
+ // the URL is a file, because is_directory_ is true iff we successfully
+ // set current directory to the URL path. Therefore, the URL could just
+ // be an invalid path. We proceed optimistically and fail in that case.
+ state_ = GETTING_FILE_HANDLE;
+ std::string unescaped_path;
+ if (UnescapeAndValidatePath(request_, &unescaped_path) &&
+ (request_handle_ = FtpOpenFileA(connection_handle_,
+ unescaped_path.c_str(),
+ GENERIC_READ,
+ INTERNET_FLAG_TRANSFER_BINARY,
+ reinterpret_cast<DWORD_PTR>(this)))) {
+ // Start reading file contents
+ state_ = GETTING_FILE;
+ NotifyHeadersComplete();
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+ } else {
+ OnStartDirectoryTraversal();
+ // If we redirect in OnStartDirectoryTraversal() then this request job
+ // is cancelled.
+ if (request_handle_)
+ OnFindFile(last_error);
+ }
+}
+
+void URLRequestFtpJob::OnFindFile(DWORD last_error) {
+ DCHECK_EQ(state_, GETTING_DIRECTORY);
+
+ if (last_error == ERROR_SUCCESS) {
+ // TODO(jabdelmalek): need to add icons for files/folders.
+ int64 size =
+ (static_cast<unsigned __int64>(find_data_.nFileSizeHigh) << 32) |
+ find_data_.nFileSizeLow;
+
+ // We don't know the encoding, and can't assume utf8, so pass the 8bit
+ // directly to the browser for it to decide.
+ string file_entry = net_util::GetDirectoryListingEntry(
+ find_data_.cFileName, find_data_.dwFileAttributes, size,
+ &find_data_.ftLastWriteTime);
+ WriteData(&file_entry, true);
+
+ FindNextFile();
+ return;
+ }
+
+ DCHECK(last_error == ERROR_NO_MORE_FILES);
+ OnFinishDirectoryTraversal();
+}
+
+void URLRequestFtpJob::OnStartDirectoryTraversal() {
+ state_ = GETTING_DIRECTORY;
+
+ // Unescape the URL path and pass the raw 8bit directly to the browser.
+ string html = net_util::GetDirectoryListingHeader(
+ UnescapeURLComponent(request_->url().path(),
+ UnescapeRule::SPACES | UnescapeRule::PERCENTS));
+
+ // If this isn't top level directory (i.e. the path isn't "/",) add a link to
+ // the parent directory.
+ if (request_->url().path().length() > 1)
+ html.append(net_util::GetDirectoryListingEntry("..", 0, 0, NULL));
+
+ WriteData(&html, true);
+
+ NotifyHeadersComplete();
+}
+
+void URLRequestFtpJob::OnFinishDirectoryTraversal() {
+ state_ = DONE;
+}
+
+int URLRequestFtpJob::WriteData(const std::string* data,
+ bool call_io_complete) {
+ int written = 0;
+
+ if (data && data->length())
+ directory_html_.append(*data);
+
+ if (dest_) {
+ size_t bytes_to_copy = std::min(static_cast<size_t>(dest_size_),
+ directory_html_.length());
+ if (bytes_to_copy) {
+ memcpy(dest_, directory_html_.c_str(), bytes_to_copy);
+ directory_html_.erase(0, bytes_to_copy);
+ dest_ = NULL;
+ dest_size_ = NULL;
+ written = static_cast<int>(bytes_to_copy);
+
+ if (call_io_complete) {
+ // Wait a little bit before telling the parent class that we wrote
+ // data. This avoids excessive cycles of us getting one file entry and
+ // telling the parent class to Read().
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::ContinueIOComplete, written),
+ kFtpBufferTimeMs);
+ }
+ }
+ }
+
+ return written;
+}
+
+void URLRequestFtpJob::ContinueIOComplete(int bytes_written) {
+ AsyncResult result;
+ result.dwResult = bytes_written;
+ result.dwError = ERROR_SUCCESS;
+ URLRequestInetJob::OnIOComplete(result);
+}
+
+void URLRequestFtpJob::ContinueNotifyHeadersComplete() {
+ NotifyHeadersComplete();
+}
+
+int URLRequestFtpJob::CallInternetRead(char* dest, int dest_size,
+ int *bytes_read) {
+ int result;
+
+ if (is_directory_) {
+ // Copy the html that we created from the directory listing that we got
+ // from InternetFindNextFile.
+ DCHECK(dest_ == NULL);
+ dest_ = dest;
+ dest_size_ = dest_size;
+
+ DCHECK(state_ == GETTING_DIRECTORY || state_ == DONE);
+ int written = WriteData(NULL, false);
+ if (written) {
+ *bytes_read = written;
+ result = ERROR_SUCCESS;
+ } else {
+ result = state_ == GETTING_DIRECTORY ? ERROR_IO_PENDING : ERROR_SUCCESS;
+ }
+ } else {
+ DWORD bytes_to_read = dest_size;
+ bytes_read_ = 0;
+ // InternetReadFileEx doesn't work for asynchronous FTP, InternetReadFile
+ // must be used instead.
+ if (!InternetReadFile(request_handle_, dest, bytes_to_read, &bytes_read_))
+ return GetLastError();
+
+ *bytes_read = static_cast<int>(bytes_read_);
+ result = ERROR_SUCCESS;
+ }
+
+ return result;
+}
+
+bool URLRequestFtpJob::GetReadBytes(const AsyncResult& result,
+ int* bytes_read) {
+ if (is_directory_) {
+ *bytes_read = static_cast<int>(result.dwResult);
+ } else {
+ if (!result.dwResult)
+ return false;
+
+ // IE5 and later return the number of read bytes in the
+ // INTERNET_ASYNC_RESULT structure. IE4 holds on to the pointer passed in
+ // to InternetReadFile and store it there.
+ *bytes_read = bytes_read_;
+
+ if (!*bytes_read)
+ *bytes_read = result.dwError;
+ }
+
+ return true;
+}
+
+bool URLRequestFtpJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (is_directory_) {
+ std::string ftp_path = request_->url().path();
+ if (!ftp_path.empty() && ('/' != ftp_path[ftp_path.length() - 1])) {
+ ftp_path.push_back('/');
+ GURL::Replacements replacements;
+ replacements.SetPathStr(ftp_path);
+
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 301; // simulate a permanent redirect
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/net/url_request/url_request_ftp_job.h b/net/url_request/url_request_ftp_job.h
new file mode 100644
index 0000000..ae47dbf
--- /dev/null
+++ b/net/url_request/url_request_ftp_job.h
@@ -0,0 +1,133 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_FTP_JOB_H__
+#define BASE_URL_REQUEST_URL_REQUEST_FTP_JOB_H__
+
+#include "net/url_request/url_request_inet_job.h"
+
+// A basic FTP job that handles download files and showing directory listings.
+class URLRequestFtpJob : public URLRequestInetJob {
+ public:
+ static URLRequestJob* Factory(URLRequest* request, const std::string& scheme);
+
+ virtual ~URLRequestFtpJob();
+
+ // URLRequestJob methods:
+ virtual void Start();
+ virtual bool GetMimeType(std::string* mime_type);
+
+ // URLRequestInetJob methods:
+ virtual void OnIOComplete(const AsyncResult& result);
+
+ protected:
+ URLRequestFtpJob(URLRequest* request);
+
+ // Starts the WinInet request.
+ virtual void SendRequest();
+
+ virtual int CallInternetRead(char* dest, int dest_size, int *bytes_read);
+ virtual bool GetReadBytes(const AsyncResult& result, int* bytes_read);
+ virtual void OnCancelAuth();
+ virtual void OnSetAuth();
+ virtual bool NeedsAuth();
+ virtual void GetAuthChallengeInfo(scoped_refptr<AuthChallengeInfo>*);
+ virtual void GetCachedAuthData(const AuthChallengeInfo& auth_info,
+ scoped_refptr<AuthData>* auth_data);
+ virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
+
+ private:
+ // Called after InternetConnect successfully connects to server.
+ void OnConnect();
+
+ // Called after FtpSetCurrentDirectory attempts to change current dir.
+ void OnSetCurrentDirectory(DWORD last_error);
+
+ // Requests the next file in the directory listing from WinInet.
+ void FindNextFile();
+
+ // Called when the first file in a directory listing is available.
+ void OnFindFirstFile(DWORD last_error);
+
+ // Called when a file in a directory listing is available.
+ void OnFindFile(DWORD last_error);
+
+ // Call this when starting a directory listing to setup the html.
+ void OnStartDirectoryTraversal();
+
+ // Call this at the end of a directory listing to complete the html.
+ void OnFinishDirectoryTraversal();
+
+ // If given data, writes it to the directory listing html. If
+ // call_io_complete is true, will also notify the parent class that we wrote
+ // data in the given buffer.
+ int WriteData(const std::string* data, bool call_io_complete);
+
+ // Continuation function for calling OnIOComplete through the message loop.
+ virtual void ContinueIOComplete(int bytes_written);
+
+ // Continuation function for calling NotifyHeadersComplete through
+ //the message loop
+ virtual void ContinueNotifyHeadersComplete();
+
+ typedef enum {
+ START = 0x200, // initial state of the ftp job
+ CONNECTING, // opening the url
+ SETTING_CUR_DIRECTORY, // attempting to change current dir to match request
+ FINDING_FIRST_FILE, // retrieving first file information in cur dir (by FtpFindFirstFile)
+ GETTING_DIRECTORY, // retrieving the directory listing (if directory)
+ GETTING_FILE_HANDLE, // initiate access to file by call to FtpOpenFile (if file)
+ GETTING_FILE, // retrieving the file (if file)
+ DONE // URLRequestInetJob is reading the response now
+ } FtpJobState;
+
+ // The FtpJob has several asynchronous operations which happen
+ // in sequence. The state keeps track of which asynchronous IO
+ // is pending at any given point in time.
+ FtpJobState state_;
+
+ // In IE 4 and before, this pointer passed to asynchronous InternetReadFile
+ // calls is where the number of read bytes is written to.
+ DWORD bytes_read_;
+
+ bool is_directory_; // does the url point to a file or directory
+ WIN32_FIND_DATAA find_data_;
+ std::string directory_html_; // if url is directory holds html
+
+ // When building a directory listing, we need to temporarily hold on to the
+ // buffer in between the time a Read() call comes in and we get the file
+ // entry from WinInet.
+ char* dest_;
+ int dest_size_;
+
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestFtpJob);
+};
+
+#endif // #define BASE_URL_REQUEST_URL_REQUEST_FTP_JOB_H__
diff --git a/net/url_request/url_request_http_cache_job.cc b/net/url_request/url_request_http_cache_job.cc
new file mode 100644
index 0000000..9537638
--- /dev/null
+++ b/net/url_request/url_request_http_cache_job.cc
@@ -0,0 +1,539 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_http_cache_job.h"
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+
+// TODO(darin): make sure the port blocking code is not lost
+
+#pragma warning(disable: 4355)
+
+// static
+URLRequestJob* URLRequestHttpCacheJob::Factory(URLRequest* request,
+ const std::string& scheme) {
+ DCHECK(scheme == "http" || scheme == "https");
+
+ if (!net_util::IsPortAllowedByDefault(request->url().IntPort()))
+ return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT);
+
+ if (!request->context() ||
+ !request->context()->http_transaction_factory()) {
+ NOTREACHED() << "requires a valid context";
+ return new URLRequestErrorJob(request, net::ERR_INVALID_ARGUMENT);
+ }
+
+ return new URLRequestHttpCacheJob(request);
+}
+
+URLRequestHttpCacheJob::URLRequestHttpCacheJob(URLRequest* request)
+ : URLRequestJob(request),
+ context_(request->context()),
+ transaction_(NULL),
+ response_info_(NULL),
+ proxy_auth_state_(AUTH_STATE_DONT_NEED_AUTH),
+ server_auth_state_(AUTH_STATE_DONT_NEED_AUTH),
+ start_callback_(this, &URLRequestHttpCacheJob::OnStartCompleted),
+ read_callback_(this, &URLRequestHttpCacheJob::OnReadCompleted),
+ read_in_progress_(false) {
+}
+
+URLRequestHttpCacheJob::~URLRequestHttpCacheJob() {
+ if (transaction_)
+ DestroyTransaction();
+}
+
+void URLRequestHttpCacheJob::SetUpload(net::UploadData* upload) {
+ DCHECK(!transaction_) << "cannot change once started";
+ request_info_.upload_data = upload;
+}
+
+void URLRequestHttpCacheJob::SetExtraRequestHeaders(
+ const std::string& headers) {
+ DCHECK(!transaction_) << "cannot change once started";
+ request_info_.extra_headers = headers;
+}
+
+void URLRequestHttpCacheJob::Start() {
+ DCHECK(!transaction_);
+
+ // TODO(darin): URLRequest::referrer() should return a GURL
+ GURL referrer(request_->referrer());
+
+ // Ensure that we do not send username and password fields in the referrer.
+ if (referrer.has_username() || referrer.has_password()) {
+ GURL::Replacements referrer_mods;
+ referrer_mods.ClearUsername();
+ referrer_mods.ClearPassword();
+ referrer = referrer.ReplaceComponents(referrer_mods);
+ }
+
+ request_info_.url = request_->url();
+ request_info_.referrer = referrer;
+ request_info_.method = request_->method();
+ request_info_.load_flags = request_->load_flags();
+
+ if (request_->context())
+ request_info_.user_agent = request_->context()->user_agent();
+
+ AddExtraHeaders();
+
+ StartTransaction();
+}
+
+void URLRequestHttpCacheJob::Kill() {
+ if (!transaction_)
+ return;
+
+ DestroyTransaction();
+ URLRequestJob::Kill();
+}
+
+net::LoadState URLRequestHttpCacheJob::GetLoadState() const {
+ return transaction_ ? transaction_->GetLoadState() : net::LOAD_STATE_IDLE;
+}
+
+uint64 URLRequestHttpCacheJob::GetUploadProgress() const {
+ return transaction_ ? transaction_->GetUploadProgress() : 0;
+}
+
+bool URLRequestHttpCacheJob::GetMimeType(std::string* mime_type) {
+ DCHECK(transaction_);
+
+ if (!response_info_)
+ return false;
+
+ return response_info_->headers->GetMimeType(mime_type);
+}
+
+bool URLRequestHttpCacheJob::GetCharset(std::string* charset) {
+ DCHECK(transaction_);
+
+ if (!response_info_)
+ return false;
+
+ return response_info_->headers->GetCharset(charset);
+}
+
+void URLRequestHttpCacheJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ DCHECK(request_);
+ DCHECK(transaction_);
+
+ if (response_info_)
+ *info = *response_info_;
+}
+
+bool URLRequestHttpCacheJob::GetResponseCookies(
+ std::vector<std::string>* cookies) {
+ DCHECK(transaction_);
+
+ if (!response_info_)
+ return false;
+
+ if (response_cookies_.empty())
+ FetchResponseCookies();
+
+ cookies->clear();
+ cookies->swap(response_cookies_);
+ return true;
+}
+
+int URLRequestHttpCacheJob::GetResponseCode() {
+ DCHECK(transaction_);
+
+ if (!response_info_)
+ return -1;
+
+ return response_info_->headers->response_code();
+}
+
+bool URLRequestHttpCacheJob::GetContentEncoding(std::string* encoding_type) {
+ DCHECK(transaction_);
+
+ if (!response_info_)
+ return false;
+
+ // TODO(darin): what if there are multiple content encodings?
+ return response_info_->headers->EnumerateHeader(NULL, "Content-Encoding",
+ encoding_type);
+}
+
+bool URLRequestHttpCacheJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (!response_info_)
+ return false;
+
+ std::string value;
+ if (!response_info_->headers->IsRedirect(&value))
+ return false;
+
+ *location = request_->url().Resolve(value);
+ *http_status_code = response_info_->headers->response_code();
+ return true;
+}
+
+bool URLRequestHttpCacheJob::IsSafeRedirect(const GURL& location) {
+ // We only allow redirects to certain "safe" protocols. This does not
+ // restrict redirects to externally handled protocols. Our consumer would
+ // need to take care of those.
+
+ if (!URLRequest::IsHandledURL(location))
+ return true;
+
+ static const char* kSafeSchemes[] = {
+ "http",
+ "https",
+ "ftp"
+ };
+
+ for (size_t i = 0; i < arraysize(kSafeSchemes); ++i) {
+ if (location.SchemeIs(kSafeSchemes[i]))
+ return true;
+ }
+
+ return false;
+}
+
+bool URLRequestHttpCacheJob::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 URLRequestHttpCacheJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* result) {
+ DCHECK(transaction_);
+ DCHECK(response_info_);
+
+ // sanity checks:
+ DCHECK(proxy_auth_state_ == AUTH_STATE_NEED_AUTH ||
+ server_auth_state_ == AUTH_STATE_NEED_AUTH);
+ DCHECK(response_info_->headers->response_code() == 401 ||
+ response_info_->headers->response_code() == 407);
+
+ *result = response_info_->auth_challenge;
+}
+
+void URLRequestHttpCacheJob::GetCachedAuthData(
+ const AuthChallengeInfo& auth_info,
+ scoped_refptr<AuthData>* auth_data) {
+ AuthCache* auth_cache =
+ request_->context()->http_transaction_factory()->GetAuthCache();
+ if (!auth_cache) {
+ *auth_data = NULL;
+ return;
+ }
+ std::string auth_cache_key = AuthCache::HttpKey(request_->url(),
+ auth_info);
+ *auth_data = auth_cache->Lookup(auth_cache_key);
+}
+
+void URLRequestHttpCacheJob::SetAuth(const std::wstring& username,
+ const std::wstring& password) {
+ DCHECK(transaction_);
+
+ // Proxy gets set first, then WWW.
+ if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
+ proxy_auth_state_ = AUTH_STATE_HAVE_AUTH;
+ } else {
+ DCHECK(server_auth_state_ == AUTH_STATE_NEED_AUTH);
+ server_auth_state_ = AUTH_STATE_HAVE_AUTH;
+ }
+
+ // These will be reset in OnStartCompleted.
+ response_info_ = NULL;
+ response_cookies_.clear();
+
+ // 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_->RestartWithAuth(username, password,
+ &start_callback_);
+ if (rv == net::ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestHttpCacheJob::OnStartCompleted, rv));
+}
+
+void URLRequestHttpCacheJob::CancelAuth() {
+ // Proxy gets set first, then WWW.
+ if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
+ proxy_auth_state_ = AUTH_STATE_CANCELED;
+ } else {
+ DCHECK(server_auth_state_ == AUTH_STATE_NEED_AUTH);
+ server_auth_state_ = AUTH_STATE_CANCELED;
+ }
+
+ // These will be reset in OnStartCompleted.
+ response_info_ = NULL;
+ response_cookies_.clear();
+
+ // 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.
+ //
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestHttpCacheJob::OnStartCompleted, net::OK));
+}
+
+void URLRequestHttpCacheJob::ContinueDespiteLastError() {
+ DCHECK(transaction_);
+ DCHECK(!response_info_) << "should not have a response yet";
+
+ // 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 == net::ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestHttpCacheJob::OnStartCompleted, rv));
+}
+
+bool URLRequestHttpCacheJob::GetMoreData() {
+ return transaction_ && !read_in_progress_;
+}
+
+bool URLRequestHttpCacheJob::ReadRawData(char* 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, &read_callback_);
+ if (rv >= 0) {
+ *bytes_read = rv;
+ return true;
+ }
+
+ if (rv == net::ERR_IO_PENDING) {
+ read_in_progress_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+
+ return false;
+}
+
+void URLRequestHttpCacheJob::OnStartCompleted(int result) {
+ // If the request was destroyed, then there is no more work to do.
+ if (!request_ || !request_->delegate())
+ return;
+
+ // If the transaction was destroyed, then the job was cancelled, and
+ // we can just ignore this notification.
+ if (!transaction_)
+ return;
+
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+
+ if (result == net::OK) {
+ NotifyHeadersComplete();
+ } else if (net::IsCertificateError(result)) {
+ // We encountered an SSL certificate error. Ask our delegate to decide
+ // what we should do.
+ // TODO(wtc): also pass ssl_info.cert_status, or just pass the whole
+ // ssl_info.
+ request_->delegate()->OnSSLCertificateError(
+ request_, result, transaction_->GetResponseInfo()->ssl_info.cert);
+ } else {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestHttpCacheJob::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 URLRequestHttpCacheJob::NotifyHeadersComplete() {
+ DCHECK(!response_info_);
+
+ response_info_ = transaction_->GetResponseInfo();
+
+ // Get the Set-Cookie values, and send them to our cookie database.
+
+ FetchResponseCookies();
+
+ URLRequestContext* ctx = request_->context();
+ if (ctx && ctx->cookie_store() &&
+ ctx->cookie_policy()->CanSetCookie(request_->url(),
+ request_->policy_url()))
+ ctx->cookie_store()->SetCookies(request_->url(), response_cookies_);
+
+ URLRequestJob::NotifyHeadersComplete();
+}
+
+void URLRequestHttpCacheJob::DestroyTransaction() {
+ DCHECK(transaction_);
+
+ transaction_->Destroy();
+ transaction_ = NULL;
+ response_info_ = NULL;
+}
+
+void URLRequestHttpCacheJob::StartTransaction() {
+ // NOTE: This method assumes that request_info_ is already setup properly.
+
+ // Create a transaction.
+ DCHECK(!transaction_);
+
+ DCHECK(request_->context());
+ DCHECK(request_->context()->http_transaction_factory());
+
+ transaction_ =
+ request_->context()->http_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 (transaction_) {
+ rv = transaction_->Start(&request_info_, &start_callback_);
+ if (rv == net::ERR_IO_PENDING)
+ return;
+ } else {
+ rv = net::ERR_FAILED;
+ }
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestHttpCacheJob::OnStartCompleted, rv));
+}
+
+void URLRequestHttpCacheJob::AddExtraHeaders() {
+ URLRequestContext* context = request_->context();
+ if (context) {
+ // Add in the cookie header. TODO might we need more than one header?
+ if (context->cookie_store() &&
+ context->cookie_policy()->CanGetCookies(request_->url(),
+ request_->policy_url())) {
+ std::string cookies = request_->context()->cookie_store()->
+ GetCookiesWithOptions(request_->url(),
+ CookieMonster::INCLUDE_HTTPONLY);
+ if (!cookies.empty())
+ request_info_.extra_headers += "Cookie: " + cookies + "\r\n";
+ }
+ if (!context->accept_language().empty())
+ request_info_.extra_headers += "Accept-Language: " +
+ context->accept_language() + "\r\n";
+ if (!context->accept_charset().empty())
+ request_info_.extra_headers += "Accept-Charset: " +
+ context->accept_charset() + "\r\n";
+ }
+
+#ifdef CHROME_LAST_MINUTE
+ // Tell the server what compression formats we support.
+ request_info_.extra_headers += "Accept-Encoding: gzip,deflate,bzip2\r\n";
+#else
+ // Tell the server that we support gzip/deflate encoding.
+ request_info_.extra_headers += "Accept-Encoding: gzip,deflate";
+
+ // const string point to google domain
+ static const char kGoogleDomain[] = "google.com";
+ static const unsigned int kGoogleDomainLen = arraysize(kGoogleDomain) - 1;
+ static const char kLocalHostName[] = "localhost";
+
+ // At now, only support bzip2 feature for those requests which are
+ // sent to google domain or localhost.
+ // TODO(jnd) : we will remove the "google.com" domain check before launch.
+ // See bug : 861940
+ const std::string &host = request_->url().host();
+
+ if (host == kLocalHostName ||
+ request_->url().DomainIs(kGoogleDomain, kGoogleDomainLen)) {
+ request_info_.extra_headers += ",bzip2\r\n";
+ } else {
+ request_info_.extra_headers += "\r\n";
+ }
+#endif
+}
+
+void URLRequestHttpCacheJob::FetchResponseCookies() {
+ DCHECK(response_info_);
+ DCHECK(response_cookies_.empty());
+
+ std::string name = "Set-Cookie";
+ std::string value;
+
+ void* iter = NULL;
+ while (response_info_->headers->EnumerateHeader(&iter, name, &value))
+ response_cookies_.push_back(value);
+}
diff --git a/net/url_request/url_request_http_cache_job.h b/net/url_request/url_request_http_cache_job.h
new file mode 100644
index 0000000..261d66b
--- /dev/null
+++ b/net/url_request/url_request_http_cache_job.h
@@ -0,0 +1,112 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_HTTP_CACHE_JOB_H__
+#define NET_URL_REQUEST_URL_REQUEST_HTTP_CACHE_JOB_H__
+
+#include "net/base/completion_callback.h"
+#include "net/http/http_request_info.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+class HttpResponseInfo;
+class HttpTransaction;
+}
+class URLRequestContext;
+
+// A URLRequestJob subclass that is built on top of the HttpCache. It provides
+// an implementation for both HTTP and HTTPS.
+class URLRequestHttpCacheJob : public URLRequestJob {
+ public:
+ static URLRequestJob* Factory(URLRequest* request, const std::string& scheme);
+
+ virtual ~URLRequestHttpCacheJob();
+
+ protected:
+ URLRequestHttpCacheJob(URLRequest* request);
+
+ // URLRequestJob methods:
+ virtual void SetUpload(net::UploadData* upload);
+ virtual void SetExtraRequestHeaders(const std::string& headers);
+ virtual void Start();
+ virtual void Kill();
+ virtual net::LoadState GetLoadState() const;
+ virtual uint64 GetUploadProgress() const;
+ virtual bool GetMimeType(std::string* mime_type);
+ virtual bool GetCharset(std::string* charset);
+ virtual void GetResponseInfo(net::HttpResponseInfo* info);
+ virtual bool GetResponseCookies(std::vector<std::string>* cookies);
+ virtual int GetResponseCode();
+ virtual bool GetContentEncoding(std::string* encoding_type);
+ virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
+ virtual bool IsSafeRedirect(const GURL& location);
+ virtual bool NeedsAuth();
+ virtual void GetAuthChallengeInfo(scoped_refptr<AuthChallengeInfo>*);
+ virtual void GetCachedAuthData(const AuthChallengeInfo& auth_info,
+ scoped_refptr<AuthData>* auth_data);
+ virtual void SetAuth(const std::wstring& username,
+ const std::wstring& password);
+ virtual void CancelAuth();
+ virtual void ContinueDespiteLastError();
+ virtual bool GetMoreData();
+ virtual bool ReadRawData(char* buf, int buf_size, int *bytes_read);
+
+ // Shadows URLRequestJob's version of this method so we can grab cookies.
+ void NotifyHeadersComplete();
+
+ void DestroyTransaction();
+ void StartTransaction();
+ void AddExtraHeaders();
+ void FetchResponseCookies();
+
+ void OnStartCompleted(int result);
+ void OnReadCompleted(int result);
+
+ net::HttpRequestInfo request_info_;
+ net::HttpTransaction* transaction_;
+ const net::HttpResponseInfo* response_info_;
+ std::vector<std::string> response_cookies_;
+
+ // Auth states for proxy and origin server.
+ AuthState proxy_auth_state_;
+ AuthState server_auth_state_;
+
+ net::CompletionCallbackImpl<URLRequestHttpCacheJob> start_callback_;
+ net::CompletionCallbackImpl<URLRequestHttpCacheJob> read_callback_;
+
+ bool read_in_progress_;
+
+ // Keep a reference to the url request context to be sure it's not
+ // deleted before us.
+ scoped_refptr<URLRequestContext> context_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestHttpCacheJob);
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_HTTP_CACHE_JOB_H__
diff --git a/net/url_request/url_request_inet_job.cc b/net/url_request/url_request_inet_job.cc
new file mode 100644
index 0000000..ab4f91b
--- /dev/null
+++ b/net/url_request/url_request_inet_job.cc
@@ -0,0 +1,462 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_inet_job.h"
+
+#include <algorithm>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/auth.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/wininet_util.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_ftp_job.h"
+#include "net/url_request/url_request_job_metrics.h"
+#include "net/url_request/url_request_job_tracker.h"
+
+//
+// HOW ASYNC IO WORKS
+//
+// The URLRequestInet* classes are now fully asynchronous. This means that
+// all IO operations pass buffers into WinInet, and as WinInet completes those
+// IO requests, it will fill the buffer, and then callback to the client.
+// Asynchronous IO Operations include:
+// HttpSendRequestEx
+// InternetWriteFile
+// HttpEndRequest
+// InternetOpenUrl
+// InternetReadFile (for FTP)
+// InternetReadFileEx (for HTTP)
+// InternetCloseHandle
+//
+// To understand how this works, you need to understand the basic class
+// hierarchy for the URLRequestJob classes:
+//
+// URLRequestJob
+// |
+// +--------------+-------------------+
+// | |
+// (Other Job Types) URLRequestInetJob
+// e.g. | |
+// URLRequestFileJob URLRequestFtpJob URLRequestHttpJob
+// |
+// URLRequestHttpUploadJob
+//
+//
+// To make this work, each URLRequestInetJob has a virtual method called
+// OnIOComplete(). If a derived URLRequestInetJob class issues
+// an asynchronous IO, it must override the OnIOComplete method
+// to handle the IO completion. Once it has overridden this method,
+// *all* asynchronous IO completions will come to this method, even
+// those asynchronous IOs which may have been issued by a base class.
+// For example, URLRequestInetJob has methods which Read from the
+// connection asynchronously. Once URLRequestHttpJob overrides
+// OnIOComplete (so that it can receive its own async IO callbacks)
+// it will also receive the URLRequestInetJob async IO callbacks. To
+// make this work, the derived class must track its own state, and call
+// the base class' version of OnIOComplete if appropriate.
+//
+
+
+using namespace std;
+
+using net::WinInetUtil;
+
+static const wchar_t kWndClass[] = L"URLRequestMessageWnd";
+
+// Custom message types for use with message_hwnd
+enum {
+ MSG_REQUEST_COMPLETE = WM_USER + 1
+};
+
+HINTERNET URLRequestInetJob::the_internet_ = NULL;
+HWND URLRequestInetJob::message_hwnd_ = NULL;
+#ifndef NDEBUG
+MessageLoop* URLRequestInetJob::my_message_loop_ = NULL;
+#endif
+
+URLRequestInetJob::URLRequestInetJob(URLRequest* request)
+ : URLRequestJob(request),
+ connection_handle_(NULL),
+ request_handle_(NULL),
+ last_error_(ERROR_SUCCESS),
+ is_waiting_(false),
+ read_in_progress_(false) {
+ // TODO(darin): we should re-create the internet if the UA string changes,
+ // but we have to be careful about existing users of this internet.
+ if (!the_internet_) {
+ InitializeTheInternet(
+ request->context() ? request->context()->user_agent() : std::string());
+ }
+#ifndef NDEBUG
+ DCHECK(MessageLoop::current() == my_message_loop_) <<
+ "All URLRequests should happen on the same thread";
+#endif
+}
+
+URLRequestInetJob::~URLRequestInetJob() {
+ DCHECK(!request_) << "request should be detached at this point";
+
+ // The connections may have already been cleaned up. It is ok to call
+ // CleanupConnection again to make sure the resource is properly released.
+ // See bug 684997.
+ CleanupConnection();
+}
+
+void URLRequestInetJob::Kill() {
+ CleanupConnection();
+
+ // Dispatch the NotifyDone message to the URLRequest
+ URLRequestJob::Kill();
+}
+
+void URLRequestInetJob::SetAuth(const wstring& username,
+ const wstring& password) {
+ DCHECK((proxy_auth_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH) ||
+ (server_auth_ != NULL &&
+ (server_auth_->state == AUTH_STATE_NEED_AUTH)));
+
+ // Proxy gets set first, then WWW.
+ AuthData* auth =
+ (proxy_auth_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH ?
+ proxy_auth_.get() : server_auth_.get());
+
+ if (auth) {
+ auth->state = AUTH_STATE_HAVE_AUTH;
+ auth->username = username;
+ auth->password = password;
+ }
+
+ // Resend the request with the new username and password.
+ // Do this asynchronously in case we were called from within a
+ // NotifyDataAvailable callback.
+ // TODO(mpcomplete): hmm... is it possible 'this' gets deleted before the task
+ // is run?
+ OnSetAuth();
+}
+
+void URLRequestInetJob::CancelAuth() {
+ DCHECK((proxy_auth_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH) ||
+ (server_auth_ != NULL &&
+ (server_auth_->state == AUTH_STATE_NEED_AUTH)));
+
+ // Proxy gets set first, then WWW.
+ AuthData* auth =
+ (proxy_auth_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH ?
+ proxy_auth_.get() : server_auth_.get());
+
+ if (auth) {
+ auth->state = AUTH_STATE_CANCELED;
+ }
+
+ // Once the auth is cancelled, we proceed with the request as though
+ // there were no auth. So, send the OnResponseStarted. Schedule this
+ // for later so that we don't cause any recursing into the caller
+ // as a result of this call.
+ OnCancelAuth();
+}
+
+void URLRequestInetJob::OnIOComplete(const AsyncResult& result) {
+ URLRequestStatus status;
+
+ if (read_in_progress_) {
+ read_in_progress_ = false;
+ int bytes_read = 0;
+ if (GetReadBytes(result, &bytes_read)) {
+ SetStatus(status);
+ if (bytes_read == 0) {
+ NotifyDone(status);
+ CleanupConnection();
+ }
+ } else {
+ bytes_read = -1;
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::FAILED);
+ status.set_os_error(WinInetUtil::OSErrorToNetError(result.dwError));
+ NotifyDone(status);
+ CleanupConnection();
+ }
+ NotifyReadComplete(bytes_read);
+ } else {
+ // If we get here, an IO is completing which we didn't
+ // start or we lost track of our state.
+ NOTREACHED();
+ }
+}
+
+bool URLRequestInetJob::ReadRawData(char* dest, int dest_size,
+ int *bytes_read) {
+ if (is_done())
+ return 0;
+
+ DCHECK_NE(dest_size, 0);
+ DCHECK_NE(bytes_read, (int*)NULL);
+ DCHECK(!read_in_progress_);
+
+ *bytes_read = 0;
+
+ int result = CallInternetRead(dest, dest_size, bytes_read);
+ if (result == ERROR_SUCCESS) {
+ DLOG(INFO) << "read " << *bytes_read << " bytes";
+ if (*bytes_read == 0)
+ CleanupConnection(); // finished reading all the data
+ return true;
+ }
+
+ if (ProcessRequestError(result))
+ read_in_progress_ = true;
+
+ // Whether we had an error or the request is pending.
+ // Both of these cases return false.
+ return false;
+}
+
+void URLRequestInetJob::CallOnIOComplete(const AsyncResult& result) {
+ // It's important to clear this flag before calling OnIOComplete
+ is_waiting_ = false;
+
+ // the job could have completed with an error while the message was pending
+ if (is_done()) {
+ Release(); // may destroy self if last reference
+ return;
+ }
+
+ // Verify that our status is currently set to IO_PENDING and
+ // reset it on success.
+ DCHECK(GetStatus().is_io_pending());
+ if (result.dwResult && result.dwError == 0)
+ SetStatus(URLRequestStatus());
+
+ OnIOComplete(result);
+
+ Release(); // may destroy self if last reference
+}
+
+bool URLRequestInetJob::ProcessRequestError(int error) {
+ if (error == ERROR_IO_PENDING) {
+ DLOG(INFO) << "waiting for WinInet call to complete";
+ AddRef(); // balanced in CallOnIOComplete
+ is_waiting_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ return true;
+ }
+ DLOG(ERROR) << "WinInet call failed: " << error;
+ CleanupConnection();
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(error)));
+ return false;
+}
+
+bool URLRequestInetJob::GetMoreData() {
+ if (!is_waiting_ && !is_done()) {
+ // The connection is still in the middle of transmission.
+ // Return true so InternetReadFileExA can be called again.
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void URLRequestInetJob::CleanupConnection() {
+ if (!request_handle_ && !connection_handle_)
+ return; // nothing to clean up
+
+ if (request_handle_) {
+ CleanupHandle(request_handle_);
+ request_handle_ = NULL;
+ }
+ if (connection_handle_) {
+ CleanupHandle(connection_handle_);
+ connection_handle_ = NULL;
+ }
+}
+
+void URLRequestInetJob::CleanupHandle(HINTERNET handle) {
+ // We no longer need notifications from this connection.
+ InternetSetStatusCallback(handle, NULL);
+
+ if (!InternetCloseHandle(handle)) {
+ // InternetCloseHandle is evil. The documentation specifies that it
+ // either succeeds immediately or returns ERROR_IO_PENDING if there is
+ // something outstanding, in which case the close will happen automagically
+ // later. In either of these cases, it will call us back with
+ // INTERNET_STATUS_HANDLE_CLOSING (because we set up the async callbacks)
+ // and we simply do nothing for the message.
+ //
+ // However, sometimes it also seems to fail with ERROR_INVALID_HANDLE.
+ // This seems to happen when we cancel before it has called us back with
+ // data. For example, if we cancel during DNS resolution or while waiting
+ // for a slow server.
+ //
+ // Our speculation is that in these cases WinInet creates a handle for
+ // us with an internal structure, but that the driver has not yet called
+ // it back with a "real" handle (the driver level is probably what
+ // generates IO_PENDING). The driver has not yet specified a handle, which
+ // causes WinInet to barf.
+ //
+ // However, in this case, the cancel seems to work. The TCP connection is
+ // closed and we still get a callback that the handle is being closed. Yay.
+ //
+ // We assert that the error is either of these two because we aren't sure
+ // if any other error values could also indicate this bogus condition, and
+ // we want to notice if we do something wrong that causes a real error.
+ DWORD last_error = GetLastError();
+ DCHECK(last_error == ERROR_INVALID_HANDLE) <<
+ "Unknown error when closing handle, possibly leaking job";
+ if (ERROR_IO_PENDING == last_error) {
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ async_result_.dwError = ERROR_INTERNET_CONNECTION_ABORTED;
+ async_result_.dwResult = reinterpret_cast<DWORD_PTR>(handle);
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestInetJob::CallOnIOComplete, async_result_));
+ }
+ }
+}
+
+// static
+HINTERNET URLRequestInetJob::GetTheInternet() {
+ return the_internet_;
+}
+
+// static
+void URLRequestInetJob::InitializeTheInternet(const std::string& user_agent) {
+ // construct message window for processsing
+ HINSTANCE hinst = GetModuleHandle(NULL);
+
+ WNDCLASSEX wc = {0};
+ wc.cbSize = sizeof(wc);
+ wc.lpfnWndProc = URLRequestWndProc;
+ wc.hInstance = hinst;
+ wc.lpszClassName = kWndClass;
+ RegisterClassEx(&wc);
+
+ message_hwnd_ = CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0,
+ hinst, 0);
+ if (!message_hwnd_) {
+ NOTREACHED() << "error: " << GetLastError();
+ return;
+ }
+
+ // Hack attack. We are hitting a deadlock in wininet deinitialization.
+ // What is happening is that when we deinitialize, FreeLibrary will be
+ // called on wininet. The loader lock is held, and wininet!DllMain is
+ // called. The problem is that wininet tries to do a bunch of cleanup
+ // in their DllMain, including calling ICAsyncThread::~ICASyncThread.
+ // This tries to shutdown the "select thread", and then does a
+ // WaitForSingleObject on the thread with a 5 sec timeout. However the
+ // thread they are waiting for cannot exit because the thread shutdown
+ // routine (LdrShutdownThread) is trying to acquire the loader lock.
+ // This causes chrome.exe to hang for 5 seconds on shutdown before the
+ // process will exit. Making sure we close our wininet handles did not help.
+ //
+ // Since DLLs are reference counted, we inflate the reference count on
+ // wininet so that it will never be deinitialized :)
+ LoadLibraryA("wininet");
+
+ the_internet_ = InternetOpenA(user_agent.c_str(),
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ NULL, // no proxy override
+ NULL, // no proxy bypass list
+ INTERNET_FLAG_ASYNC);
+ InternetSetStatusCallback(the_internet_, URLRequestStatusCallback);
+
+ // Keep track of this message loop so we can catch callers who don't make
+ // requests on the same thread. Only do this in debug mode; in release mode
+ // my_message_loop_ doesn't exist.
+#ifndef NDEBUG
+ DCHECK(!my_message_loop_) << "InitializeTheInternet() called twice";
+ DCHECK(my_message_loop_ = MessageLoop::current());
+#endif
+}
+
+// static
+LRESULT CALLBACK URLRequestInetJob::URLRequestWndProc(HWND hwnd,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ URLRequestInetJob* job = reinterpret_cast<URLRequestInetJob*>(wparam);
+ HINTERNET handle = reinterpret_cast<HINTERNET>(lparam);
+
+ switch (message) {
+ case MSG_REQUEST_COMPLETE: {
+ // The callback will be reset if we have closed the handle and deleted
+ // the job instance. Call CallOnIOComplete only if the handle still
+ // has a valid callback.
+ INTERNET_STATUS_CALLBACK callback = NULL;
+ DWORD option_buffer_size = sizeof(callback);
+ if (InternetQueryOption(handle, INTERNET_OPTION_CALLBACK,
+ &callback, &option_buffer_size)
+ && (NULL != callback)) {
+ const AsyncResult& r = job->async_result_;
+ DLOG(INFO) << "REQUEST_COMPLETE: job=" << job << ", result=" <<
+ (void*) r.dwResult << ", error=" << r.dwError;
+ job->CallOnIOComplete(r);
+ }
+ break;
+ }
+ default:
+ return DefWindowProc(hwnd, message, wparam, lparam);
+ }
+
+ return 0;
+}
+
+// static
+void CALLBACK URLRequestInetJob::URLRequestStatusCallback(
+ HINTERNET handle, DWORD_PTR job_id, DWORD status, LPVOID status_info,
+ DWORD status_info_len) {
+ UINT message = 0;
+ LPARAM message_param = 0;
+ switch (status) {
+ case INTERNET_STATUS_REQUEST_COMPLETE: {
+ message = MSG_REQUEST_COMPLETE;
+ DCHECK(status_info_len == sizeof(INTERNET_ASYNC_RESULT));
+ LPINTERNET_ASYNC_RESULT r =
+ static_cast<LPINTERNET_ASYNC_RESULT>(status_info);
+ URLRequestInetJob* job = reinterpret_cast<URLRequestInetJob*>(job_id);
+ job->async_result_.dwResult = r->dwResult;
+ job->async_result_.dwError = r->dwError;
+ message_param = reinterpret_cast<LPARAM>(handle);
+ break;
+ }
+ case INTERNET_STATUS_USER_INPUT_REQUIRED:
+ case INTERNET_STATUS_STATE_CHANGE:
+ // TODO(darin): This is probably a security problem. Do something better.
+ ResumeSuspendedDownload(handle, 0);
+ break;
+ }
+
+ if (message)
+ PostMessage(URLRequestInetJob::message_hwnd_, message,
+ static_cast<WPARAM>(job_id), message_param);
+}
diff --git a/net/url_request/url_request_inet_job.h b/net/url_request/url_request_inet_job.h
new file mode 100644
index 0000000..a82d754
--- /dev/null
+++ b/net/url_request/url_request_inet_job.h
@@ -0,0 +1,184 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_INET_JOB_H__
+#define NET_URL_REQUEST_URL_REQUEST_INET_JOB_H__
+
+#include <windows.h>
+#include <wininet.h>
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+class AuthData;
+class MessageLoop;
+
+// For all WinInet-based URL requests
+class URLRequestInetJob : public URLRequestJob {
+ public:
+ URLRequestInetJob(URLRequest* request);
+ virtual ~URLRequestInetJob();
+
+ virtual void SetExtraRequestHeaders(const std::string& headers) {
+ extra_request_headers_ = headers;
+ }
+
+ virtual void Kill();
+ virtual bool ReadRawData(char* buf, int buf_size, int *bytes_read);
+
+ // URLRequestJob Authentication methods
+ virtual void SetAuth(const std::wstring& username,
+ const std::wstring& password);
+ virtual void CancelAuth();
+
+ // A structure holding the result and error code of an asynchronous IO.
+ // This is a copy of INTERNET_ASYNC_RESULT.
+ struct AsyncResult {
+ DWORD_PTR dwResult;
+ DWORD dwError;
+ };
+
+ // A virtual method to handle WinInet callbacks. If this class
+ // issues asynchronous IO, it will need to override this method
+ // to receive completions of those asynchronous IOs. The class
+ // must track whether it has an async IO outstanding, and if it
+ // does not it must call the base class' OnIOComplete.
+ virtual void OnIOComplete(const AsyncResult& result) = 0;
+
+ // Used internally to setup the OnIOComplete call. Public because this
+ // is called from the Windows procedure, and we don't want to make it a
+ // friend so we can avoid the Windows headers for this header file.
+ void CallOnIOComplete(const AsyncResult& result);
+
+ HINTERNET request_handle() const { return request_handle_; }
+
+protected:
+ // Called by this class and subclasses to send or resend this request.
+ virtual void SendRequest() = 0;
+
+ // Calls InternetReadFile(Ex) depending on the derived class.
+ // Returns ERROR_SUCCESS on success, or else a standard Windows error code
+ // on failure (from GetLastError()).
+ virtual int CallInternetRead(char* dest, int dest_size, int *bytes_read) = 0;
+
+ // After the base class calls CallInternetRead and the result is available,
+ // it will call this method to get the number of received bytes.
+ virtual bool GetReadBytes(const AsyncResult& result, int* bytes_read) = 0;
+
+ // Called by this class and subclasses whenever a WinInet call fails. This
+ // method returns true if the error just means that we have to wait for
+ // OnIOComplete to be called.
+ bool ProcessRequestError(int error);
+
+ // Called by URLRequestJob to get more data from the data stream of this job.
+ virtual bool GetMoreData();
+
+ // Cleans up the connection, if necessary, and closes the connection and
+ // request handles. May be called multiple times, it will be a NOP if
+ // there is nothing to do.
+ void CleanupConnection();
+
+ // Closes the given handle.
+ void CleanupHandle(HINTERNET handle);
+
+ // Returns the global handle to the internet (NOT the same as the connection
+ // or request handle below)
+ static HINTERNET GetTheInternet();
+
+ // Makes appropriate asynch call to re-send a request based on
+ // dynamic scheme type and user action at authentication prompt
+ //(OK or Cancel)
+ virtual void OnCancelAuth() = 0;
+ virtual void OnSetAuth() = 0;
+
+ // Handle of the connection for this request. This handle is created
+ // by subclasses that create the connection according to their requirements.
+ // It will be automatically destroyed by this class when the connection is
+ // being closed. See also 'request_handle_'
+ HINTERNET connection_handle_;
+
+ // Handle of the specific request created by subclasses to meet their own
+ // requirements. This handle has a more narrow scope than the connection
+ // handle. If non-null, it will be automatically destroyed by this class
+ // when the connection is being closed. It will be destroyed before the
+ // connection handle.
+ HINTERNET request_handle_;
+
+ // The last error that occurred. Used by ContinueDespiteLastError to adjust
+ // the request's load_flags to ignore this error.
+ DWORD last_error_;
+
+ // Any extra request headers (\n-delimited) that should be included in the
+ // request.
+ std::string extra_request_headers_;
+
+ // Authentication information.
+ scoped_refptr<AuthData> proxy_auth_;
+ scoped_refptr<AuthData> server_auth_;
+
+ private:
+
+ // One-time global state setup
+ static void InitializeTheInternet(const std::string& user_agent);
+
+ // Runs on the thread where the first URLRequest was created
+ static LRESULT CALLBACK URLRequestWndProc(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam);
+
+ // Runs on some background thread (called by WinInet)
+ static void CALLBACK URLRequestStatusCallback(HINTERNET handle,
+ DWORD_PTR job_id,
+ DWORD status,
+ LPVOID status_info,
+ DWORD status_info_len);
+
+ static HINTERNET the_internet_;
+ static HWND message_hwnd_;
+#ifndef NDEBUG
+ static MessageLoop* my_message_loop_; // Used to sanity-check that all
+ // requests are made on the same
+ // thread
+#endif
+
+ // true if waiting for OnIOComplete to be called
+ bool is_waiting_;
+
+ // debugging state - is there a read already in progress
+ bool read_in_progress_;
+
+ // The result and error code of asynchronous IO. It is modified by the
+ // status callback functions on asynchronous IO completion and passed to
+ // CallOnIOComplete. Since there is at most one pending IO, the object
+ // can reuse the async_result_ member for all its asynchronous IOs.
+ AsyncResult async_result_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestInetJob);
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_INET_JOB_H__
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
new file mode 100644
index 0000000..6485489
--- /dev/null
+++ b/net/url_request/url_request_job.cc
@@ -0,0 +1,497 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_job.h"
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/auth.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job_metrics.h"
+#include "net/url_request/url_request_job_tracker.h"
+
+// Buffer size allocated when de-compressing data.
+static const int kFilterBufSize = 32 * 1024;
+
+URLRequestJob::URLRequestJob(URLRequest* request)
+ : request_(request),
+ read_buffer_(NULL),
+ read_buffer_len_(0),
+ has_handled_response_(false),
+ done_(false),
+ expected_content_size_(-1) {
+ is_profiling_ = request->enable_profiling();
+ if (is_profiling()) {
+ metrics_.reset(new URLRequestJobMetrics());
+ metrics_->start_time_ = TimeTicks::Now();
+ }
+ g_url_request_job_tracker.AddNewJob(this);
+}
+
+URLRequestJob::~URLRequestJob() {
+ g_url_request_job_tracker.RemoveJob(this);
+}
+
+void URLRequestJob::Kill() {
+ // 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;
+}
+
+void URLRequestJob::SetupFilter() {
+ std::string encoding_type;
+ if (GetContentEncoding(&encoding_type)) {
+ std::string mime_type;
+ GetMimeType(&mime_type);
+ filter_.reset(Filter::Factory(encoding_type, mime_type, kFilterBufSize));
+ }
+}
+
+// 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(char* buf, int buf_size, int *bytes_read) {
+ bool rv = false;
+
+ DCHECK_LT(buf_size, 1000000); // sanity check
+ DCHECK(buf);
+ DCHECK(bytes_read);
+
+ *bytes_read = 0;
+
+ // Skip Filter if not present
+ if (!filter_.get()) {
+ rv = ReadRawData(buf, buf_size, bytes_read);
+ if (rv && *bytes_read > 0)
+ RecordBytesRead(*bytes_read);
+ } else {
+ // Get more pre-filtered data if needed.
+ int filtered_data_read = 0;
+
+ // Save the caller's buffers while we do IO
+ // in the filter's buffers.
+ read_buffer_ = buf;
+ read_buffer_len_ = buf_size;
+
+ if (ReadFilteredData(bytes_read)) {
+ rv = true; // we have data to return
+ } else {
+ rv = false; // error, or a new IO is pending
+ }
+ }
+ if (rv && *bytes_read == 0)
+ NotifyDone(URLRequestStatus());
+ return rv;
+}
+
+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()) {
+ char* stream_buffer = filter_->stream_buffer();
+ int stream_buffer_size = filter_->stream_buffer_size();
+ rv = ReadRawData(stream_buffer, stream_buffer_size, bytes_read);
+ if (rv && *bytes_read > 0)
+ RecordBytesRead(*bytes_read);
+ }
+ return rv;
+}
+
+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(read_buffer_ != NULL); // we need to have a buffer to fill
+ DCHECK(read_buffer_len_ > 0); // sanity check
+ DCHECK(read_buffer_len_ < 1000000); // sanity check
+
+ bool rv = false;
+ *bytes_read = 0;
+
+ if (is_done())
+ return true;
+
+ if (!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);
+ } else {
+ return true; // EOF
+ }
+ } else {
+ return false; // IO Pending (or error)
+ }
+ }
+
+ if (filter_->stream_data_len() && !is_done()) {
+ // Get filtered data
+ int filtered_data_len = read_buffer_len_;
+ Filter::FilterStatus status;
+ status = filter_->ReadFilteredData(read_buffer_, &filtered_data_len);
+ switch (status) {
+ case Filter::FILTER_DONE: {
+ *bytes_read = filtered_data_len;
+ rv = true;
+ break;
+ }
+ case Filter::FILTER_NEED_MORE_DATA: {
+ // 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;
+ 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: {
+ *bytes_read = filtered_data_len;
+ rv = true;
+ break;
+ }
+ case Filter::FILTER_ERROR: {
+ // TODO: Figure out a better error code.
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, net::ERR_FAILED));
+ rv = false;
+ break;
+ }
+ default: {
+ NOTREACHED();
+ rv = false;
+ break;
+ }
+ }
+ } 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. For debugging purposes, we clear
+ // them out.
+ read_buffer_ = NULL;
+ read_buffer_len_ = 0;
+ }
+ return rv;
+}
+
+bool URLRequestJob::ReadRawData(char* buf, int buf_size, int *bytes_read) {
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+ NotifyDone(URLRequestStatus());
+ return false;
+}
+
+URLRequestJobMetrics* URLRequestJob::RetrieveMetrics() {
+ if (is_profiling())
+ return metrics_.release();
+ else
+ return NULL;
+}
+
+void URLRequestJob::NotifyHeadersComplete() {
+ if (!request_ || !request_->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 = 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;
+
+ int http_status_code;
+ GURL new_location;
+ 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);
+ }
+
+ // Toggle this flag to true so the consumer can access response headers.
+ // Then toggle it back if we choose to follow the redirect.
+ has_handled_response_ = true;
+ request_->delegate()->OnReceivedRedirect(request_, new_location);
+
+ // Ensure that the request wasn't destroyed in OnReceivedRedirect
+ if (!request_ || !request_->delegate())
+ return;
+
+ // If we were not cancelled, then follow the redirect.
+ if (request_->status().is_success()) {
+ has_handled_response_ = false;
+ 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) {
+ scoped_refptr<AuthData> auth_data;
+ GetCachedAuthData(*auth_info, &auth_data);
+ if (auth_data) {
+ SetAuth(auth_data->username, auth_data->password);
+ return;
+ }
+ request_->delegate()->OnAuthRequired(request_, auth_info);
+ // Wait for SetAuth or CancelAuth to be called.
+ return;
+ }
+ }
+
+ has_handled_response_ = true;
+ if (request_->status().is_success())
+ SetupFilter();
+
+ if (!filter_.get()) {
+ std::string content_length;
+ request_->GetResponseHeaderByName("content-length", &content_length);
+ if (!content_length.empty())
+ expected_content_size_ = StringToInt64(content_length);
+ }
+
+ request_->delegate()->OnResponseStarted(request_);
+}
+
+void URLRequestJob::NotifyStartError(const URLRequestStatus &status) {
+ DCHECK(!has_handled_response_);
+ has_handled_response_ = true;
+ if (request_) {
+ request_->set_status(status);
+ if (request_->delegate())
+ request_->delegate()->OnResponseStarted(request_);
+ }
+}
+
+void URLRequestJob::NotifyReadComplete(int bytes_read) {
+ if (!request_ || !request_->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_);
+
+ if (bytes_read > 0)
+ RecordBytesRead(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 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 (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))
+ request_->delegate()->OnReadCompleted(request_, filter_bytes_read);
+ } else {
+ request_->delegate()->OnReadCompleted(request_, bytes_read);
+ }
+}
+
+void URLRequestJob::NotifyDone(const URLRequestStatus &status) {
+ DCHECK(!done_) << "Job sending done notification twice";
+ if (done_)
+ return;
+ done_ = true;
+
+ if (is_profiling() && metrics_->total_bytes_read_ > 0) {
+ // There are valid IO statistics. Fill in other fields of metrics for
+ // profiling consumers to retrieve information.
+ metrics_->original_url_.reset(new GURL(request_->original_url()));
+ metrics_->end_time_ = TimeTicks::Now();
+ metrics_->success_ = status.is_success();
+
+ if (!(request_->original_url() == request_->url())) {
+ metrics_->url_.reset(new GURL(request_->url()));
+ }
+ } else {
+ metrics_.reset();
+ }
+
+
+ // 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());
+
+ // In the success case, we cannot send the NotifyDone now. It can only be
+ // sent after the request is completed, because otherwise we can get the
+ // NotifyDone() called while the delegate is still accessing the request.
+ // In the case of an error, we are fre
+ if (status.is_success()) {
+ // If there is data left in the filter, then something is probably wrong.
+ DCHECK(!FilterHasData());
+ }
+
+ // 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())
+ request_->set_status(status);
+ }
+
+ g_url_request_job_tracker.OnJobDone(this, status);
+
+ // Complete this notification later. This prevents us from re-entering the
+ // delegate if we're done because of a synchronous call.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestJob::CompleteNotifyDone));
+}
+
+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_->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_->delegate()->OnReadCompleted(request_, -1);
+ } else {
+ has_handled_response_ = true;
+ request_->delegate()->OnResponseStarted(request_);
+ }
+ }
+}
+
+void URLRequestJob::NotifyCanceled() {
+ if (!done_) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::CANCELED,
+ net::ERR_ABORTED));
+ }
+}
+
+bool URLRequestJob::FilterHasData() {
+ return filter_.get() && filter_->stream_data_len();
+}
+
+void URLRequestJob::FollowRedirect(const GURL& location,
+ int http_status_code) {
+ g_url_request_job_tracker.OnJobRedirect(this, location, http_status_code);
+ Kill();
+ // Kill could have notified the Delegate and destroyed the request.
+ if (!request_)
+ return;
+
+ int rv = request_->Redirect(location, http_status_code);
+ if (rv != net::OK)
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, rv));
+}
+
+void URLRequestJob::RecordBytesRead(int bytes_read) {
+ if (is_profiling()) {
+ ++(metrics_->number_of_read_IO_);
+ metrics_->total_bytes_read_ += bytes_read;
+ }
+ g_url_request_job_tracker.OnBytesRead(this, bytes_read);
+}
+
+const URLRequestStatus URLRequestJob::GetStatus() {
+ if (request_)
+ return request_->status();
+ // If the request is gone, we must be cancelled.
+ return URLRequestStatus(URLRequestStatus::CANCELED,
+ net::ERR_ABORTED);
+}
+
+void URLRequestJob::SetStatus(const URLRequestStatus &status) {
+ if (request_)
+ request_->set_status(status);
+}
diff --git a/net/url_request/url_request_job.h b/net/url_request/url_request_job.h
new file mode 100644
index 0000000..60a870d
--- /dev/null
+++ b/net/url_request/url_request_job.h
@@ -0,0 +1,336 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_JOB_H__
+#define BASE_URL_REQUEST_URL_REQUEST_JOB_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "net/base/auth.h"
+#include "net/base/filter.h"
+#include "net/base/load_states.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+class HttpResponseInfo;
+class UploadData;
+}
+
+class GURL;
+class URLRequest;
+class URLRequestJobMetrics;
+
+class URLRequestJob : public base::RefCounted<URLRequestJob> {
+ public:
+ URLRequestJob(URLRequest* request);
+ virtual ~URLRequestJob();
+
+ // 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(net::UploadData* upload) { }
+
+ // Sets extra request headers for Job types that support request headers.
+ virtual void SetExtraRequestHeaders(const std::string& headers) { }
+
+ // 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 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(char* buf, int buf_size, int *bytes_read);
+
+ // Called to fetch the current load state for the job.
+ virtual net::LoadState GetLoadState() const { return net::LOAD_STATE_IDLE; }
+
+ // Called to get the upload progress in bytes.
+ virtual uint64 GetUploadProgress() const { return 0; }
+
+ // Called to fetch the mime_type 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 mime type will return false.
+ virtual bool GetMimeType(std::string* mime_type) { return false; }
+
+ // 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) { return false; }
+
+ // Called to get response info.
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) {}
+
+ // 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) {
+ return false;
+ }
+
+ // Returns the HTTP response code for the request.
+ virtual int GetResponseCode() { return -1; }
+
+ // Called to fetch the encoding type for this request. Only makes sense for
+ // some types of requests. Returns true on success. Calling this on a request
+ // that doesn't have or specify an encoding type will return false.
+ virtual bool GetContentEncoding(std::string* encoding_type) { return false; }
+
+ // Called to setup stream filter for this request. An example of filter is
+ // content encoding/decoding.
+ void SetupFilter();
+
+ // 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.
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ return false;
+ }
+
+ // 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
+ // net::ERR_UNSAFE_REDIRECT.
+ virtual bool IsSafeRedirect(const GURL& location) {
+ return true;
+ }
+
+ // 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() { return false; }
+
+ // Fills the authentication info with the server's response.
+ virtual void 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();
+ }
+
+ // Returns cached auth data for the auth challenge. Returns NULL if there
+ // is no auth cache or if the auth cache doesn't have the auth data for
+ // the auth challenge.
+ virtual void GetCachedAuthData(const AuthChallengeInfo& auth_info,
+ scoped_refptr<AuthData>* auth_data) {
+ *auth_data = NULL;
+ }
+
+ // Resend the request with authentication credentials.
+ virtual void SetAuth(const std::wstring& username,
+ const std::wstring& password) {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+ }
+
+ // Display the error page without asking for credentials again.
+ virtual void CancelAuth() {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+ }
+
+ // Continue processing the request ignoring the last error.
+ virtual void 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();
+ }
+
+ // Returns true if the Job is done producing response data and has called
+ // NotifyDone on the request.
+ bool is_done() const { return done_; }
+
+ // Returns true if the job is doing performance profiling
+ bool is_profiling() const { return is_profiling_; }
+
+ // Retrieve the performance measurement of the job. The data is encapsulated
+ // with a URLRequestJobMetrics object. The caller owns this object from now
+ // on.
+ URLRequestJobMetrics* RetrieveMetrics();
+
+ // 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;
+ }
+
+ protected:
+ // 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();
+
+ // Called to get more data from the request response. Returns true if there
+ // is data immediately available to read. Return false otherwise.
+ // Internally this function may initiate I/O operations to get more data.
+ virtual bool GetMoreData() { return false; }
+
+ // 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(char* buf, int buf_size, int *bytes_read);
+
+ // 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);
+
+ // The request that initiated this job. This value MAY BE NULL if the
+ // request was released by DetachRequest().
+ URLRequest* request_;
+
+ // The status of the job.
+ const URLRequestStatus GetStatus();
+
+ // Set the status of the job.
+ void SetStatus(const URLRequestStatus &status);
+
+ // Whether the job is doing performance profiling
+ bool is_profiling_;
+
+ // Contains IO performance measurement when profiling is enabled.
+ scoped_ptr<URLRequestJobMetrics> metrics_;
+
+ 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);
+
+ // 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);
+
+ // Updates the profiling info and notifies observers that bytes_read bytes
+ // have been read.
+ void RecordBytesRead(int bytes_read);
+
+ private:
+ // Called to query whether there is data available in the filter to be read
+ // out.
+ bool FilterHasData();
+
+ // 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_;
+
+ // The data stream filter which is enabled on demand.
+ scoped_ptr<Filter> filter_;
+ // 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.
+ char *read_buffer_;
+ int read_buffer_len_;
+
+ // 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_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestJob);
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_JOB_H__
diff --git a/net/url_request/url_request_job_manager.cc b/net/url_request/url_request_job_manager.cc
new file mode 100644
index 0000000..4d03ccf
--- /dev/null
+++ b/net/url_request/url_request_job_manager.cc
@@ -0,0 +1,178 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_job_manager.h"
+
+#include "base/string_util.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_request_about_job.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_job.h"
+#include "net/url_request/url_request_ftp_job.h"
+#include "net/url_request/url_request_http_cache_job.h"
+#include "net/url_request/url_request_view_cache_job.h"
+
+// The built-in set of protocol factories
+static const struct {
+ const char* scheme;
+ URLRequest::ProtocolFactory* factory;
+} kBuiltinFactories[] = {
+ { "http", URLRequestHttpCacheJob::Factory },
+ { "https", URLRequestHttpCacheJob::Factory },
+ { "file", URLRequestFileJob::Factory },
+ { "ftp", URLRequestFtpJob::Factory },
+ { "about", URLRequestAboutJob::Factory },
+ { "view-cache", URLRequestViewCacheJob::Factory },
+};
+
+URLRequestJobManager::URLRequestJobManager() {
+#ifndef NDEBUG
+ allowed_thread_ = NULL;
+#endif
+}
+
+URLRequestJob* URLRequestJobManager::CreateJob(URLRequest* request) const {
+#ifndef NDEBUG
+ DCHECK(IsAllowedThread());
+#endif
+
+ // 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, net::ERR_INVALID_URL);
+
+ const std::string& scheme = request->url().scheme(); // already lowercase
+
+ // We do this here to avoid asking interceptors about unsupported schemes.
+ if (!SupportsScheme(scheme))
+ return new URLRequestErrorJob(request, net::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.
+ if (!(request->load_flags() & net::LOAD_DISABLE_INTERCEPT)) {
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ URLRequestJob* job = (*i)->MaybeIntercept(request);
+ if (job)
+ return job;
+ }
+ }
+
+ // 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, 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, 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 know have a specific error to report here :-(
+ return new URLRequestErrorJob(request, net::ERR_FAILED);
+}
+
+bool URLRequestJobManager::SupportsScheme(const std::string& scheme) const {
+ // The set of registered factories may change on another thread.
+ {
+ 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) {
+#ifndef NDEBUG
+ DCHECK(IsAllowedThread());
+#endif
+
+ 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) {
+#ifndef NDEBUG
+ DCHECK(IsAllowedThread());
+#endif
+
+ AutoLock locked(lock_);
+
+ DCHECK(std::find(interceptors_.begin(), interceptors_.end(), interceptor) ==
+ interceptors_.end());
+ interceptors_.push_back(interceptor);
+}
+
+void URLRequestJobManager::UnregisterRequestInterceptor(
+ URLRequest::Interceptor* interceptor) {
+#ifndef NDEBUG
+ DCHECK(IsAllowedThread());
+#endif
+
+ AutoLock locked(lock_);
+
+ InterceptorList::iterator i =
+ std::find(interceptors_.begin(), interceptors_.end(), interceptor);
+ DCHECK(i != interceptors_.end());
+ interceptors_.erase(i);
+}
diff --git a/net/url_request/url_request_job_manager.h b/net/url_request/url_request_job_manager.h
new file mode 100644
index 0000000..f10d5ee
--- /dev/null
+++ b/net/url_request/url_request_job_manager.h
@@ -0,0 +1,100 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H__
+#define NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H__
+
+#include <map>
+
+#include "base/lock.h"
+#include "net/url_request/url_request.h"
+
+// 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:
+ URLRequestJobManager();
+
+ // 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) 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;
+
+ mutable Lock lock_;
+ FactoryMap factories_;
+ InterceptorList interceptors_;
+
+#ifndef NDEBUG
+ // We use this to assert that CreateJob and the registration functions all
+ // run on the same thread.
+ mutable HANDLE allowed_thread_;
+
+ // 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 (!allowed_thread_)
+ allowed_thread_ = GetCurrentThread();
+ return allowed_thread_ == GetCurrentThread();
+ }
+#endif
+
+ DISALLOW_EVIL_CONSTRUCTORS(URLRequestJobManager);
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H__
diff --git a/net/url_request/url_request_job_metrics.cc b/net/url_request/url_request_job_metrics.cc
new file mode 100644
index 0000000..3610459
--- /dev/null
+++ b/net/url_request/url_request_job_metrics.cc
@@ -0,0 +1,57 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_job_metrics.h"
+
+#include "base/basictypes.h"
+#include "base/string_util.h"
+
+void URLRequestJobMetrics::AppendText(std::wstring* text) {
+ if (!text)
+ return;
+
+ text->append(L"job url = ");
+ text->append(UTF8ToWide(original_url_->spec()));
+
+ if (url_.get()) {
+ text->append(L"; redirected url = ");
+ text->append(UTF8ToWide(url_->spec()));
+ }
+
+ TimeDelta elapsed = end_time_ - start_time_;
+ StringAppendF(text,
+ L"; total bytes read = %d; read calls = %d; time = %lld ms;",
+ total_bytes_read_, number_of_read_IO_, elapsed.InMilliseconds());
+
+ if (success_) {
+ text->append(L" success.");
+ } else {
+ text->append(L" fail.");
+ }
+}
diff --git a/net/url_request/url_request_job_metrics.h b/net/url_request/url_request_job_metrics.h
new file mode 100644
index 0000000..e6a9bc6
--- /dev/null
+++ b/net/url_request/url_request_job_metrics.h
@@ -0,0 +1,74 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Records IO statistics associated with a URLRequestJob.
+// See description in navigation_profiler.h for an overview of perf profiling.
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_JOB_METRICS_H__
+#define BASE_URL_REQUEST_URL_REQUEST_JOB_METRICS_H__
+
+class URLRequestJobMetrics {
+ public:
+ URLRequestJobMetrics() : total_bytes_read_(0), number_of_read_IO_(0) { }
+ ~URLRequestJobMetrics() { }
+
+ // The original url the job has been created for.
+ scoped_ptr<GURL> original_url_;
+
+ // The actual url the job connects to. If the actual url is same as the
+ // original url, url_ is empty.
+ scoped_ptr<GURL> url_;
+
+ // Time when the job starts.
+ TimeTicks start_time_;
+
+ // Time when the job is done.
+ TimeTicks end_time_;
+
+ // Total number of bytes the job reads from underline IO.
+ int total_bytes_read_;
+
+ // Number of IO read operations the job issues.
+ int number_of_read_IO_;
+
+ // Final status of the job.
+ bool success_;
+
+ // Append the text report of the frame loading to the input string.
+ void AppendText(std::wstring* text);
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_JOB_METRICS_H__
diff --git a/net/url_request/url_request_job_tracker.cc b/net/url_request/url_request_job_tracker.cc
new file mode 100644
index 0000000..4a0f0f3
--- /dev/null
+++ b/net/url_request/url_request_job_tracker.cc
@@ -0,0 +1,82 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+
+#include "net/url_request/url_request_job_tracker.h"
+
+#include "base/logging.h"
+#include "net/url_request/url_request_job.h"
+
+URLRequestJobTracker g_url_request_job_tracker;
+
+URLRequestJobTracker::URLRequestJobTracker() {
+}
+
+URLRequestJobTracker::~URLRequestJobTracker() {
+ DLOG_IF(WARNING, active_jobs_.size() != 0) <<
+ "Leaking " << active_jobs_.size() << " URLRequestJob object(s), this could be "
+ "because the URLRequest forgot to free it (bad), or if the program was "
+ "terminated while a request was active (normal).";
+}
+
+void URLRequestJobTracker::AddNewJob(URLRequestJob* job) {
+ active_jobs_.push_back(job);
+ FOR_EACH_OBSERVER(JobObserver, observers_, OnJobAdded(job));
+}
+
+void URLRequestJobTracker::RemoveJob(URLRequestJob* job) {
+ JobList::iterator iter = std::find(active_jobs_.begin(), active_jobs_.end(),
+ job);
+ if (iter == active_jobs_.end()) {
+ NOTREACHED() << "Removing a non-active job";
+ return;
+ }
+ active_jobs_.erase(iter);
+
+ FOR_EACH_OBSERVER(JobObserver, observers_, OnJobRemoved(job));
+}
+
+void URLRequestJobTracker::OnJobDone(URLRequestJob* job,
+ const URLRequestStatus& status) {
+ FOR_EACH_OBSERVER(JobObserver, observers_, OnJobDone(job, status));
+}
+
+void URLRequestJobTracker::OnJobRedirect(URLRequestJob* job,
+ const GURL& location,
+ int status_code) {
+ FOR_EACH_OBSERVER(JobObserver, observers_,
+ OnJobRedirect(job, location, status_code));
+}
+
+void URLRequestJobTracker::OnBytesRead(URLRequestJob* job,
+ int byte_count) {
+ FOR_EACH_OBSERVER(JobObserver, observers_,
+ OnBytesRead(job, byte_count));
+}
diff --git a/net/url_request/url_request_job_tracker.h b/net/url_request/url_request_job_tracker.h
new file mode 100644
index 0000000..ca5a380
--- /dev/null
+++ b/net/url_request/url_request_job_tracker.h
@@ -0,0 +1,117 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_JOB_TRACKER_H__
+#define BASE_URL_REQUEST_URL_REQUEST_JOB_TRACKER_H__
+
+#include <vector>
+
+#include "base/observer_list.h"
+#include "net/url_request/url_request_status.h"
+
+class URLRequestJob;
+class GURL;
+
+// This class maintains a list of active URLRequestJobs for debugging purposes.
+// This allows us to warn on leaked jobs and also allows an observer to track
+// what is happening, for example, for the network status monitor.
+//
+// NOTE: URLRequest is single-threaded, so this class should only be used on
+// the same thread where all of the application's URLRequest calls are made.
+//
+class URLRequestJobTracker {
+ public:
+ typedef std::vector<URLRequestJob*> JobList;
+ typedef JobList::const_iterator JobIterator;
+
+ // The observer's methods are called on the thread that called AddObserver.
+ class JobObserver {
+ public:
+ // Called after the given job has been added to the list
+ virtual void OnJobAdded(URLRequestJob* job) = 0;
+
+ // Called after the given job has been removed from the list
+ virtual void OnJobRemoved(URLRequestJob* job) = 0;
+
+ // Called when the given job has completed, before notifying the request
+ virtual void OnJobDone(URLRequestJob* job,
+ const URLRequestStatus& status) = 0;
+
+ // Called when the given job is about to follow a redirect to the given
+ // new URL. The redirect type is given in status_code
+ virtual void OnJobRedirect(URLRequestJob* job, const GURL& location,
+ int status_code) = 0;
+
+ // Called when a new chunk of bytes has been read for the given job. The
+ // byte count is the number of bytes for that read event only.
+ virtual void OnBytesRead(URLRequestJob* job, int byte_count) = 0;
+ };
+
+ URLRequestJobTracker();
+ ~URLRequestJobTracker();
+
+ // adds or removes an observer from the list. note, these methods should
+ // only be called on the same thread where URLRequest objects are used.
+ void AddObserver(JobObserver* observer) {
+ observers_.AddObserver(observer);
+ }
+ void RemoveObserver(JobObserver* observer) {
+ observers_.RemoveObserver(observer);
+ }
+
+ // adds or removes the job from the active list, should be called by the
+ // job constructor and destructor. Note: don't use "AddJob" since that
+ // is #defined by windows.h :(
+ void AddNewJob(URLRequestJob* job);
+ void RemoveJob(URLRequestJob* job);
+
+ // Job status change notifications
+ void OnJobDone(URLRequestJob* job, const URLRequestStatus& status);
+ void OnJobRedirect(URLRequestJob* job, const GURL& location,
+ int status_code);
+
+ // Bytes read notifications.
+ void OnBytesRead(URLRequestJob* job, int byte_count);
+
+ // allows iteration over all active jobs
+ JobIterator begin() const {
+ return active_jobs_.begin();
+ }
+ JobIterator end() const {
+ return active_jobs_.end();
+ }
+
+ private:
+ ObserverList<JobObserver> observers_;
+ JobList active_jobs_;
+};
+
+extern URLRequestJobTracker g_url_request_job_tracker;
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_JOB_TRACKER_H__
diff --git a/net/url_request/url_request_simple_job.cc b/net/url_request/url_request_simple_job.cc
new file mode 100644
index 0000000..cffdf6c
--- /dev/null
+++ b/net/url_request/url_request_simple_job.cc
@@ -0,0 +1,81 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_simple_job.h"
+
+#include "base/message_loop.h"
+#include "net/base/net_errors.h"
+
+URLRequestSimpleJob::URLRequestSimpleJob(URLRequest* request)
+ : URLRequestJob(request),
+ data_offset_(0) {
+}
+
+void URLRequestSimpleJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestSimpleJob::StartAsync));
+}
+
+bool URLRequestSimpleJob::GetMimeType(std::string* mime_type) {
+ *mime_type = mime_type_;
+ return true;
+}
+
+bool URLRequestSimpleJob::GetCharset(std::string* charset) {
+ *charset = charset_;
+ return true;
+}
+
+bool URLRequestSimpleJob::ReadRawData(char* 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_offset_, buf_size);
+ data_offset_ += buf_size;
+ *bytes_read = buf_size;
+ return true;
+}
+
+void URLRequestSimpleJob::StartAsync() {
+ if (!request_)
+ return;
+
+ if (GetData(&mime_type_, &charset_, &data_)) {
+ // Notify that the headers are complete
+ NotifyHeadersComplete();
+ } else {
+ // what should the error code be?
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ net::ERR_INVALID_URL));
+ }
+}
diff --git a/net/url_request/url_request_simple_job.h b/net/url_request/url_request_simple_job.h
new file mode 100644
index 0000000..65743b2
--- /dev/null
+++ b/net/url_request/url_request_simple_job.h
@@ -0,0 +1,60 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H__
+#define NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H__
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+class URLRequestSimpleJob : public URLRequestJob {
+ public:
+ URLRequestSimpleJob(URLRequest* request);
+
+ virtual void Start();
+ virtual bool ReadRawData(char* buf, int buf_size, int *bytes_read);
+ virtual bool GetMimeType(std::string* mime_type);
+ virtual bool GetCharset(std::string* charset);
+
+ protected:
+ // subclasses must override the way response data is determined.
+ virtual bool GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data) const = 0;
+
+ private:
+ void StartAsync();
+
+ std::string mime_type_;
+ std::string charset_;
+ std::string data_;
+ int data_offset_;
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_DATA_JOB_H__
diff --git a/net/url_request/url_request_status.h b/net/url_request/url_request_status.h
new file mode 100644
index 0000000..d0fa16a
--- /dev/null
+++ b/net/url_request/url_request_status.h
@@ -0,0 +1,91 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// 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 BASE_URL_REQUEST_URL_REQUEST_STATUS_H__
+#define BASE_URL_REQUEST_URL_REQUEST_STATUS_H__
+
+// Respresents the result of a URL request. It encodes errors and various
+// types of success.
+class URLRequestStatus {
+ public:
+ enum Status {
+ // Request succeeded, os_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 successful but was handled by an external program, so there
+ // is no response data. This usually means the current page should not be
+ // navigated, but no error should be displayed. os_error will be 0.
+ HANDLED_EXTERNALLY,
+
+ // Request was cancelled programatically.
+ CANCELED,
+
+ // The request failed for some reason. os_error may have more information.
+ FAILED,
+ };
+
+ URLRequestStatus() : status_(SUCCESS), os_error_(0) {}
+ URLRequestStatus(Status s, int e) : status_(s), os_error_(e) {}
+
+ Status status() const { return status_; }
+ void set_status(Status s) { status_ = s; }
+
+ int os_error() const { return os_error_; }
+ void set_os_error(int e) { os_error_ = e; }
+
+ // Returns true if the status is success, which makes some calling code more
+ // convenient because this is the most common test. Note that we do NOT treat
+ // HANDLED_EXTERNALLY as success. For everything except user notifications,
+ // this value should be handled like an error (processing should stop).
+ 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 operating system network layer if an error was
+ // encountered
+ int os_error_;
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_STATUS_H__
diff --git a/net/url_request/url_request_test_job.cc b/net/url_request/url_request_test_job.cc
new file mode 100644
index 0000000..da9d393
--- /dev/null
+++ b/net/url_request/url_request_test_job.cc
@@ -0,0 +1,204 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <vector>
+
+#include "net/url_request/url_request_test_job.h"
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+
+// This emulates the global message loop for the test URL request class, since
+// this is only test code, it's probably not too dangerous to have this static
+// object.
+static std::vector< scoped_refptr<URLRequestTestJob> > pending_jobs;
+
+// 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
+URLRequestJob* URLRequestTestJob::Factory(URLRequest* request,
+ const std::string& scheme) {
+ return new URLRequestTestJob(request);
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request)
+ : URLRequestJob(request),
+ stage_(WAITING),
+ async_buf_(NULL),
+ async_buf_size_(0),
+ offset_(0) {
+}
+
+// Force the response to set a reasonable MIME type
+bool URLRequestTestJob::GetMimeType(std::string* mime_type) {
+ DCHECK(mime_type);
+ *mime_type = "text/html";
+ return true;
+}
+
+void URLRequestTestJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestTestJob::StartAsync));
+}
+
+void URLRequestTestJob::StartAsync() {
+ if (request_->url().spec() == test_url_1().spec()) {
+ data_ = test_data_1();
+ stage_ = DATA_AVAILABLE; // Simulate a synchronous response for this one.
+ } else if (request_->url().spec() == test_url_2().spec()) {
+ data_ = test_data_2();
+ } else if (request_->url().spec() == test_url_3().spec()) {
+ data_ = test_data_3();
+ } else {
+ // unexpected url, return error
+ // FIXME(brettw) we may want to use WININET errors or have some more types
+ // of errors
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ net::ERR_INVALID_URL));
+ // FIXME(brettw): this should emulate a network error, and not just fail
+ // initiating a connection
+ return;
+ }
+
+ pending_jobs.push_back(scoped_refptr<URLRequestTestJob>(this));
+
+ this->NotifyHeadersComplete();
+}
+
+bool URLRequestTestJob::ReadRawData(char* 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>(data_.length())) {
+ return true; // done reading
+ }
+
+ int to_read = buf_size;
+ if (to_read + offset_ > static_cast<int>(data_.length()))
+ to_read = static_cast<int>(data_.length()) - offset_;
+
+ memcpy(buf, &data_.c_str()[offset_], to_read);
+ offset_ += to_read;
+
+ *bytes_read = to_read;
+ return true;
+}
+
+void URLRequestTestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ const std::string kResponseHeaders = StringPrintf(
+ "HTTP/1.1 200 OK%c"
+ "Content-type: text/html%c"
+ "%c", 0, 0, 0);
+ info->headers = new net::HttpResponseHeaders(kResponseHeaders);
+}
+
+void URLRequestTestJob::Kill() {
+ if (request_) {
+ // Note that this state will still cause a NotifyDone to get called
+ // in ProcessNextOperation, which is required for jobs.
+ stage_ = ALL_DATA;
+ pending_jobs.push_back(scoped_refptr<URLRequestTestJob>(this));
+ }
+}
+
+bool URLRequestTestJob::ProcessNextOperation() {
+ switch (stage_) {
+ case WAITING:
+ 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
+ NotifyReadComplete(bytes_read);
+ }
+ break;
+ case DATA_AVAILABLE:
+ stage_ = ALL_DATA; // done sending data
+ break;
+ case ALL_DATA:
+ stage_ = DONE;
+ return false;
+ case DONE:
+ return false;
+ default:
+ NOTREACHED() << "Invalid stage";
+ return false;
+ }
+ return true;
+}
+
+// static
+bool URLRequestTestJob::ProcessOnePendingMessage() {
+ if (pending_jobs.empty())
+ return false;
+
+ scoped_refptr<URLRequestTestJob> next_job(pending_jobs[0]);
+ pending_jobs.erase(pending_jobs.begin());
+
+ if (next_job->ProcessNextOperation())
+ pending_jobs.push_back(next_job);
+
+ return true;
+}
diff --git a/net/url_request/url_request_test_job.h b/net/url_request/url_request_test_job.h
new file mode 100644
index 0000000..bc7a57b
--- /dev/null
+++ b/net/url_request/url_request_test_job.h
@@ -0,0 +1,110 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_TEST_JOB_H__
+#define BASE_URL_REQUEST_URL_REQUEST_TEST_JOB_H__
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+// 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().
+//
+// 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.
+class URLRequestTestJob : public URLRequestJob {
+ public:
+ URLRequestTestJob(URLRequest* request);
+ virtual ~URLRequestTestJob() {}
+
+ // the three URLs this handler will respond to
+ // 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();
+
+ // 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.
+ static bool ProcessOnePendingMessage();
+
+ // Factory method for protocol factory registration if callers don't subclass
+ static URLRequest::ProtocolFactory Factory;
+
+ // Job functions
+ virtual void Start();
+ virtual bool ReadRawData(char* buf, int buf_size, int *bytes_read);
+ virtual void Kill();
+ virtual bool GetMimeType(std::string* mime_type);
+ virtual void GetResponseInfo(net::HttpResponseInfo* info);
+
+ protected:
+ // 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 };
+
+ // Call to process the next opeation, usually sending a notification, and
+ // advancing the stage if necessary. THIS MAY DELETE THE OBJECT, we will
+ // return false if the operations are complete, true if there are more.
+ bool ProcessNextOperation();
+
+ // Called via InvokeLater to cause callbacks to occur after Start() returns.
+ void StartAsync();
+
+ Stage stage_;
+
+ // The data to send, will be set in Start()
+ std::string data_;
+
+ // current offset within data_
+ int offset_;
+
+ // Holds the buffer for an asynchronous ReadRawData call
+ char* async_buf_;
+ int async_buf_size_;
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_TEST_JOB_H__
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
new file mode 100644
index 0000000..6f842bb
--- /dev/null
+++ b/net/url_request/url_request_unittest.cc
@@ -0,0 +1,792 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <windows.h>
+#include <shlobj.h>
+#include <algorithm>
+#include <string>
+
+#include "net/url_request/url_request_unittest.h"
+
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/string_util.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_module.h"
+#include "net/base/net_util.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/url_request/url_request.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class URLRequestTest : public testing::Test {
+};
+
+class URLRequestHttpCacheContext : public URLRequestContext {
+ public:
+ URLRequestHttpCacheContext() {
+ http_transaction_factory_ =
+ new net::HttpCache(net::HttpNetworkLayer::CreateFactory(NULL),
+ disk_cache::CreateInMemoryCacheBackend(0));
+ }
+
+ virtual ~URLRequestHttpCacheContext() {
+ delete http_transaction_factory_;
+ }
+};
+
+class TestURLRequest : public URLRequest {
+ public:
+ TestURLRequest(const GURL& url, Delegate* delegate)
+ : URLRequest(url, delegate) {
+ set_context(new URLRequestHttpCacheContext());
+ }
+};
+
+std::string TestNetResourceProvider(int key) {
+ return "header";
+}
+
+}
+
+TEST(URLRequestTest, GetTest_NoCache) {
+ TestServer server(L"");
+ TestDelegate d;
+ {
+ TestURLRequest r(server.TestServerPage(""), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, GetTest) {
+ TestServer server(L"");
+ TestDelegate d;
+ {
+ TestURLRequest r(server.TestServerPage(""), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, CancelTest) {
+ TestDelegate d;
+ {
+ TestURLRequest r(GURL("http://www.google.com/"), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ r.Cancel();
+
+ 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());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, CancelTest2) {
+ TestServer server(L"");
+ TestDelegate d;
+ {
+ TestURLRequest r(server.TestServerPage(""), &d);
+
+ d.set_cancel_in_response_started(true);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ 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());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, CancelTest3) {
+ TestServer server(L"");
+ TestDelegate d;
+ {
+ TestURLRequest r(server.TestServerPage(""), &d);
+
+ d.set_cancel_in_received_data(true);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ 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());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, CancelTest4) {
+ TestServer server(L"");
+ TestDelegate d;
+ {
+ TestURLRequest r(server.TestServerPage(""), &d);
+
+ 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(URLRequestTest, CancelTest5) {
+ TestServer server(L"");
+ scoped_refptr<URLRequestContext> context = new URLRequestHttpCacheContext();
+
+ // populate cache
+ {
+ TestDelegate d;
+ URLRequest r(server.TestServerPage("cachetime"), &d);
+ r.set_context(context);
+ r.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ }
+
+ // cancel read from cache (see bug 990242)
+ {
+ TestDelegate d;
+ URLRequest r(server.TestServerPage("cachetime"), &d);
+ r.set_context(context);
+ r.Start();
+ r.Cancel();
+ 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());
+ }
+
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count, 0);
+#endif
+}
+
+TEST(URLRequestTest, PostTest) {
+ TestServer server(L"net/data");
+
+ 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';
+
+ scoped_refptr<URLRequestContext> context =
+ new URLRequestHttpCacheContext();
+
+ for (int i = 0; i < kIterations; ++i) {
+ TestDelegate d;
+ URLRequest r(server.TestServerPage("echo"), &d);
+ r.set_context(context);
+ r.set_method("POST");
+
+ r.AppendBytesToUpload(uploadBytes, kMsgSize);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.response_started_count()) << "request failed: " <<
+ (int) r.status().status() << ", os error: " << r.status().os_error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(uploadBytes, d.data_received());
+ EXPECT_EQ(memcmp(uploadBytes, d.data_received().c_str(), kMsgSize),0);
+ EXPECT_EQ(d.data_received().compare(uploadBytes), 0);
+ }
+ delete[] uploadBytes;
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, PostEmptyTest) {
+ TestServer server(L"net/data");
+ TestDelegate d;
+ {
+ TestURLRequest r(server.TestServerPage("echo"), &d);
+ r.set_method("POST");
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.response_started_count()) << "request failed: " <<
+ (int) r.status().status() << ", os error: " << r.status().os_error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.data_received().empty());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, PostFileTest) {
+ TestServer server(L"net/data");
+ TestDelegate d;
+ {
+ TestURLRequest r(server.TestServerPage("echo"), &d);
+ r.set_method("POST");
+
+ std::wstring dir;
+ PathService::Get(base::DIR_EXE, &dir);
+ _wchdir(dir.c_str());
+
+ std::wstring path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ file_util::AppendToPath(&path, L"net");
+ file_util::AppendToPath(&path, L"data");
+ file_util::AppendToPath(&path, L"url_request_unittest");
+ file_util::AppendToPath(&path, L"with-headers.html");
+ r.AppendFileToUpload(path);
+
+ // This file should just be ignored in the upload stream.
+ r.AppendFileToUpload(L"c:\\path\\to\\non\\existant\\file.randomness.12345");
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ HANDLE file = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ ASSERT_NE(INVALID_HANDLE_VALUE, file);
+
+ DWORD size = GetFileSize(file, NULL);
+ scoped_array<char> buf(new char[size]);
+
+ DWORD size_read;
+ EXPECT_TRUE(ReadFile(file, buf.get(), size, &size_read, NULL));
+
+ CloseHandle(file);
+
+ EXPECT_EQ(size, size_read);
+
+ ASSERT_EQ(1, d.response_started_count()) << "request failed: " <<
+ (int) r.status().status() << ", os error: " << r.status().os_error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+
+ ASSERT_EQ(size, d.bytes_received());
+ EXPECT_EQ(0, memcmp(d.data_received().c_str(), buf.get(), size));
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, AboutBlankTest) {
+ TestDelegate d;
+ {
+ TestURLRequest r(GURL("about:blank"), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 0);
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, FileTest) {
+ std::wstring app_path;
+ PathService::Get(base::FILE_EXE, &app_path);
+
+ std::string app_url = WideToUTF8(app_path);
+ std::replace(app_url.begin(), app_url.end(),
+ file_util::kPathSeparator, L'/');
+ app_url.insert(0, "file:///");
+
+ TestDelegate d;
+ {
+ TestURLRequest r(GURL(app_url), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ WIN32_FILE_ATTRIBUTE_DATA data;
+ GetFileAttributesEx(app_path.c_str(), GetFileExInfoStandard, &data);
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), data.nFileSizeLow);
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, InvalidUrlTest) {
+ TestDelegate d;
+ {
+ TestURLRequest r(GURL("invalid url"), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(d.request_failed());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+/* This test is disabled because it fails on some computers due to proxies
+ returning a page in response to this request rather than reporting failure.
+TEST(URLRequestTest, DnsFailureTest) {
+ TestDelegate d;
+ {
+ URLRequest r(GURL("http://thisisnotavalidurl0123456789foo.com/"), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(d.request_failed());
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+*/
+
+TEST(URLRequestTest, ResponseHeadersTest) {
+ TestServer server(L"net/data/url_request_unittest");
+ TestDelegate d;
+ TestURLRequest req(server.TestServerPage("files/with-headers.html"), &d);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ const net::HttpResponseHeaders* headers = req.response_headers();
+ 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(URLRequestTest, BZip2ContentTest) {
+ TestServer server(L"net/data/filter_unittests");
+
+ // for localhost domain, we also should support bzip2 encoding
+ // first, get the original file
+ TestDelegate d1;
+ TestURLRequest req1(server.TestServerPage("realfiles/google.txt"), &d1);
+ req1.Start();
+ MessageLoop::current()->Run();
+
+ const std::string& got_content = d1.data_received();
+
+ // second, get bzip2 content
+ TestDelegate d2;
+ TestURLRequest req2(server.TestServerPage("realbz2files/google.txt"), &d2);
+ req2.Start();
+ MessageLoop::current()->Run();
+
+ const std::string& got_bz2_content = d2.data_received();
+
+ // compare those two results
+ EXPECT_TRUE(got_content == got_bz2_content);
+}
+
+TEST(URLRequestTest, BZip2ContentTest_IncrementalHeader) {
+ TestServer server(L"net/data/filter_unittests");
+
+ // for localhost domain, we also should support bzip2 encoding
+ // first, get the original file
+ TestDelegate d1;
+ TestURLRequest req1(server.TestServerPage("realfiles/google.txt"), &d1);
+ req1.Start();
+ MessageLoop::current()->Run();
+
+ const std::string& got_content = d1.data_received();
+
+ // second, get bzip2 content. ask the testserver to send the BZ2 header in
+ // two chunks with a delay between them. this tests our fix for bug 867161.
+ TestDelegate d2;
+ TestURLRequest req2(server.TestServerPage("realbz2files/google.txt?incremental-header"), &d2);
+ req2.Start();
+ MessageLoop::current()->Run();
+
+ const std::string& got_bz2_content = d2.data_received();
+
+ // compare those two results
+ EXPECT_TRUE(got_content == got_bz2_content);
+}
+
+TEST(URLRequestTest, ResolveShortcutTest) {
+ std::wstring app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ file_util::AppendToPath(&app_path, L"net");
+ file_util::AppendToPath(&app_path, L"data");
+ file_util::AppendToPath(&app_path, L"url_request_unittest");
+ file_util::AppendToPath(&app_path, L"with-headers.html");
+
+ std::wstring lnk_path = app_path + L".lnk";
+
+ HRESULT result;
+ IShellLink *shell = NULL;
+ IPersistFile *persist = NULL;
+
+ CoInitialize(NULL);
+ // Temporarily create a shortcut for test
+ result = CoCreateInstance(CLSID_ShellLink, NULL,
+ CLSCTX_INPROC_SERVER, IID_IShellLink,
+ reinterpret_cast<LPVOID*>(&shell));
+ EXPECT_TRUE(SUCCEEDED(result));
+ result = shell->QueryInterface(IID_IPersistFile,
+ reinterpret_cast<LPVOID*>(&persist));
+ EXPECT_TRUE(SUCCEEDED(result));
+ result = shell->SetPath(app_path.c_str());
+ EXPECT_TRUE(SUCCEEDED(result));
+ result = shell->SetDescription(L"ResolveShortcutTest");
+ EXPECT_TRUE(SUCCEEDED(result));
+ result = persist->Save(lnk_path.c_str(), TRUE);
+ EXPECT_TRUE(SUCCEEDED(result));
+ if (persist)
+ persist->Release();
+ if (shell)
+ shell->Release();
+
+ TestDelegate d;
+ {
+ TestURLRequest r(net_util::FilePathToFileURL(lnk_path), &d);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ WIN32_FILE_ATTRIBUTE_DATA data;
+ GetFileAttributesEx(app_path.c_str(), GetFileExInfoStandard, &data);
+ HANDLE file = CreateFile(app_path.c_str(), GENERIC_READ,
+ FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ EXPECT_NE(INVALID_HANDLE_VALUE, file);
+ scoped_array<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());
+ CoUninitialize();
+
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+}
+
+TEST(URLRequestTest, ContentTypeNormalizationTest) {
+ TestServer server(L"net/data/url_request_unittest");
+ TestDelegate d;
+ TestURLRequest req(server.TestServerPage(
+ "files/content-type-normalization.html"), &d);
+ req.Start();
+ 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(URLRequestTest, FileDirCancelTest) {
+ // Put in mock resource provider.
+ NetModule::SetResourceProvider(TestNetResourceProvider);
+
+ TestDelegate d;
+ {
+ std::wstring file_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
+ file_util::AppendToPath(&file_path, L"net");
+ file_util::AppendToPath(&file_path, L"data");
+ file_util::AppendToPath(&file_path, L"");
+
+ TestURLRequest req(net_util::FilePathToFileURL(file_path), &d);
+ req.Start();
+ EXPECT_TRUE(req.is_pending());
+
+ d.set_cancel_in_received_data_pending(true);
+
+ MessageLoop::current()->Run();
+ }
+#ifndef NDEBUG
+ DCHECK_EQ(url_request_metrics.object_count,0);
+#endif
+
+ // Take out mock resource provider.
+ NetModule::SetResourceProvider(NULL);
+}
+
+TEST(URLRequestTest, RestrictRedirects) {
+ TestServer server(L"net/data/url_request_unittest");
+ TestDelegate d;
+ TestURLRequest req(server.TestServerPage(
+ "files/redirect-to-file.html"), &d);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, req.status().status());
+ EXPECT_EQ(net::ERR_UNSAFE_REDIRECT, req.status().os_error());
+}
+
+TEST(URLRequestTest, NoUserPassInReferrer) {
+ TestServer server(L"net/data/url_request_unittest");
+ TestDelegate d;
+ TestURLRequest req(server.TestServerPage(
+ "echoheader?Referer"), &d);
+ req.set_referrer("http://user:pass@foo.com/");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(std::string("http://foo.com/"), d.data_received());
+}
+
+TEST(URLRequestTest, CancelRedirect) {
+ TestServer server(L"net/data/url_request_unittest");
+ TestDelegate d;
+ {
+ d.set_cancel_in_received_redirect(true);
+ TestURLRequest req(server.TestServerPage(
+ "files/redirect-test.html"), &d);
+ req.Start();
+ 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(URLRequestTest, VaryHeader) {
+ TestServer server(L"net/data/url_request_unittest");
+
+ scoped_refptr<URLRequestContext> context = new URLRequestHttpCacheContext();
+
+ Time response_time;
+
+ // populate the cache
+ {
+ TestDelegate d;
+ URLRequest req(server.TestServerPage("echoheader?foo"), &d);
+ req.set_context(context);
+ req.SetExtraRequestHeaders("foo:1");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ response_time = req.response_time();
+ }
+
+ // Make sure that the response time of a future response will be in the
+ // future!
+ Sleep(10);
+
+ // expect a cache hit
+ {
+ TestDelegate d;
+ URLRequest req(server.TestServerPage("echoheader?foo"), &d);
+ req.set_context(context);
+ req.SetExtraRequestHeaders("foo:1");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(req.response_time() == response_time);
+ }
+
+ // expect a cache miss
+ {
+ TestDelegate d;
+ URLRequest req(server.TestServerPage("echoheader?foo"), &d);
+ req.set_context(context);
+ req.SetExtraRequestHeaders("foo:2");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_FALSE(req.response_time() == response_time);
+ }
+}
+
+TEST(URLRequestTest, BasicAuth) {
+ scoped_refptr<URLRequestContext> context = new URLRequestHttpCacheContext();
+ TestServer server(L"");
+
+ Time response_time;
+
+ // populate the cache
+ {
+ TestDelegate d;
+ d.set_username(L"user");
+ d.set_password(L"secret");
+
+ URLRequest r(server.TestServerPage("auth-basic"), &d);
+ r.set_context(context);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ response_time = r.response_time();
+ }
+
+ // Let some time pass so we can ensure that a future response will have a
+ // response time value in the future.
+ Sleep(10 /* milliseconds */);
+
+ // 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_username(L"user");
+ d.set_password(L"secret");
+
+ URLRequest r(server.TestServerPage("auth-basic"), &d);
+ r.set_context(context);
+ r.set_load_flags(net::LOAD_VALIDATE_CACHE);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ // Should be the same cached document, which means that the response time
+ // should not have changed.
+ EXPECT_TRUE(response_time == r.response_time());
+ }
+}
diff --git a/net/url_request/url_request_unittest.h b/net/url_request/url_request_unittest.h
new file mode 100644
index 0000000..7e40710
--- /dev/null
+++ b/net/url_request/url_request_unittest.h
@@ -0,0 +1,380 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_URL_REQUEST_URL_REQUEST_UNITTEST_H_
+#define BASE_URL_REQUEST_URL_REQUEST_UNITTEST_H_
+
+#include <sstream>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_network_layer.h"
+#include "net/url_request/url_request.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const int kDefaultPort = 1337;
+const std::string kDefaultHostName("localhost");
+
+// This URLRequestContext does not use a local cache.
+class TestURLRequestContext : public URLRequestContext {
+ public:
+ TestURLRequestContext() {
+ http_transaction_factory_ = net::HttpNetworkLayer::CreateFactory(NULL);
+ }
+
+ virtual ~TestURLRequestContext() {
+ delete http_transaction_factory_;
+ }
+};
+
+class TestDelegate : public URLRequest::Delegate {
+ public:
+ TestDelegate()
+ : cancel_in_rr_(false),
+ cancel_in_rs_(false),
+ cancel_in_rd_(false),
+ cancel_in_rd_pending_(false),
+ quit_on_complete_(true),
+ response_started_count_(0),
+ received_bytes_count_(0),
+ received_redirect_count_(0),
+ received_data_before_response_(false),
+ request_failed_(false) {
+ }
+
+ virtual void OnReceivedRedirect(URLRequest* request, const GURL& new_url) {
+ received_redirect_count_++;
+ if (cancel_in_rr_)
+ request->Cancel();
+ }
+
+ virtual void OnResponseStarted(URLRequest* request) {
+ // It doesn't make sense for the request to have IO pending at this point.
+ DCHECK(!request->status().is_io_pending());
+
+ 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_, sizeof(buf_), &bytes_read))
+ OnReadCompleted(request, bytes_read);
+ else if (!request->status().is_io_pending())
+ OnResponseCompleted(request);
+ }
+ }
+
+ virtual void 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_, 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_, sizeof(buf_), &bytes_read)) {
+ if (bytes_read > 0) {
+ data_received_.append(buf_, 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 OnResponseCompleted(URLRequest* request) {
+ if (quit_on_complete_)
+ MessageLoop::current()->Quit();
+ }
+
+ void OnAuthRequired(URLRequest* request, AuthChallengeInfo* auth_info) {
+ if (!username_.empty() || !password_.empty()) {
+ request->SetAuth(username_, password_);
+ } else {
+ request->CancelAuth();
+ }
+ }
+
+ virtual void OnSSLCertificateError(URLRequest* request,
+ int cert_error,
+ X509Certificate* cert) {
+ // Ignore SSL errors, we test the server is started and shut it down by
+ // performing GETs, no security restrictions should apply as we always want
+ // these GETs to go through.
+ request->ContinueDespiteLastError();
+ }
+
+ 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_username(const std::wstring& u) { username_ = u; }
+ void set_password(const std::wstring& p) { password_ = p; }
+
+ // 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_; }
+
+ private:
+ // 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_;
+
+ std::wstring username_;
+ std::wstring password_;
+
+ // tracks status of callbacks
+ int response_started_count_;
+ int received_bytes_count_;
+ int received_redirect_count_;
+ bool received_data_before_response_;
+ bool request_failed_;
+ std::string data_received_;
+
+ // our read buffer
+ char buf_[4096];
+};
+
+// This object bounds the lifetime of an external python-based HTTP server
+// that can provide various responses useful for testing.
+class TestServer : public process_util::ProcessFilter {
+ public:
+ TestServer(const std::wstring& document_root)
+ : context_(new TestURLRequestContext),
+ process_handle_(NULL),
+ is_shutdown_(true) {
+ Init(kDefaultHostName, kDefaultPort, document_root, std::wstring());
+ }
+
+ virtual ~TestServer() {
+ Shutdown();
+ }
+
+ // Implementation of ProcessFilter
+ virtual bool Includes(uint32 pid, uint32 parent_pid) const {
+ // This function may be called after Shutdown(), in which process_handle_ is
+ // set to NULL. Since no process handle is set, it can't be included in the
+ // filter.
+ if (!process_handle_)
+ return false;
+ return pid == process_util::GetProcId(process_handle_);
+ }
+
+ GURL TestServerPage(const std::string& path) {
+ return GURL(base_address_ + path);
+ }
+
+ GURL TestServerPageW(const std::wstring& path) {
+ return GURL(UTF8ToWide(base_address_) + path);
+ }
+
+ // A subclass may wish to send the request in a different manner
+ virtual bool MakeGETRequest(const std::string& page_name) {
+ TestDelegate d;
+ URLRequest r(TestServerPage(page_name), &d);
+ r.set_context(context_);
+ r.set_method("GET");
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ return r.status().is_success();
+ }
+
+ protected:
+ struct ManualInit {};
+
+ // Used by subclasses that need to defer initialization until they are fully
+ // constructed. The subclass should call Init once it is ready (usually in
+ // its constructor).
+ TestServer(ManualInit)
+ : context_(new TestURLRequestContext),
+ process_handle_(NULL),
+ is_shutdown_(true) {
+ }
+
+ virtual std::string scheme() { return std::string("http"); }
+
+ // This is in a separate function so that we can have assertions and so that
+ // subclasses can call this later.
+ void Init(const std::string& host_name, int port,
+ const std::wstring& document_root,
+ const std::wstring& cert_path) {
+ std::stringstream ss;
+ std::string port_str;
+ ss << port ? port : kDefaultPort;
+ ss >> port_str;
+ base_address_ = scheme() + "://" + host_name + ":" + port_str + "/";
+
+ std::wstring testserver_path;
+ ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path));
+ file_util::AppendToPath(&testserver_path, L"net");
+ file_util::AppendToPath(&testserver_path, L"tools");
+ file_util::AppendToPath(&testserver_path, L"testserver");
+ file_util::AppendToPath(&testserver_path, L"testserver.py");
+
+ ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &python_runtime_));
+ file_util::AppendToPath(&python_runtime_, L"third_party");
+ file_util::AppendToPath(&python_runtime_, L"python_24");
+ file_util::AppendToPath(&python_runtime_, L"python.exe");
+
+ std::wstring test_data_directory;
+ PathService::Get(base::DIR_SOURCE_ROOT, &test_data_directory);
+ std::wstring normalized_document_root = document_root;
+ std::replace(normalized_document_root.begin(),
+ normalized_document_root.end(),
+ L'/', file_util::kPathSeparator);
+ file_util::AppendToPath(&test_data_directory, normalized_document_root);
+
+ std::wstring command_line =
+ L"\"" + python_runtime_ + L"\" " + L"\"" + testserver_path +
+ L"\" --port=" + UTF8ToWide(port_str) + L" --data-dir=\"" +
+ test_data_directory + L"\"";
+ if (!cert_path.empty()) {
+ command_line.append(L" --https=\"");
+ command_line.append(cert_path);
+ command_line.append(L"\"");
+ }
+
+ ASSERT_TRUE(
+ process_util::LaunchApp(command_line, false, true, &process_handle_)) <<
+ "Failed to launch " << command_line;
+
+ // Verify that the webserver is actually started.
+ // Otherwise tests can fail if they run faster than Python can start.
+ int retries = 10;
+ bool success;
+ while ((success = MakeGETRequest("hello.html")) == false && retries > 0) {
+ retries--;
+ ::Sleep(500);
+ }
+ ASSERT_TRUE(success) << "Webserver not starting properly.";
+
+ is_shutdown_ = false;
+ }
+
+ void Shutdown() {
+ if (is_shutdown_)
+ return;
+
+ // here we append the time to avoid problems where the kill page
+ // is being cached rather than being executed on the server
+ std::ostringstream page_name;
+ page_name << "kill?" << GetTickCount();
+ int retry_count = 5;
+ while (retry_count > 0) {
+ bool r = MakeGETRequest(page_name.str());
+ // BUG #1048625 causes the kill GET to fail. For now we just retry.
+ // Once the bug is fixed, we should remove the while loop and put back
+ // the following DCHECK.
+ // DCHECK(r);
+ if (r)
+ break;
+ retry_count--;
+ }
+ // Make sure we were successfull in stopping the testserver.
+ DCHECK(retry_count > 0);
+
+ if (process_handle_) {
+ CloseHandle(process_handle_);
+ process_handle_ = NULL;
+ }
+
+ // Make sure we don't leave any stray testserver processes laying around.
+ std::wstring testserver_name =
+ file_util::GetFilenameFromPath(python_runtime_);
+ process_util::CleanupProcesses(testserver_name, 10000, 1, this);
+ EXPECT_EQ(0, process_util::GetProcessCount(testserver_name, this));
+
+ is_shutdown_ = true;
+ }
+
+ private:
+ scoped_refptr<TestURLRequestContext> context_;
+ std::string base_address_;
+ std::wstring python_runtime_;
+ HANDLE process_handle_;
+ bool is_shutdown_;
+};
+
+class HTTPSTestServer : public TestServer {
+ public:
+ HTTPSTestServer(const std::string& host_name, int port,
+ const std::wstring& document_root,
+ const std::wstring& cert_path) : TestServer(ManualInit()) {
+ Init(host_name, port, document_root, cert_path);
+ }
+
+ virtual std::string scheme() { return std::string("https"); }
+};
+
+#endif // BASE_URL_REQUEST_URL_REQUEST_UNITTEST_H_
diff --git a/net/url_request/url_request_view_cache_job.cc b/net/url_request/url_request_view_cache_job.cc
new file mode 100644
index 0000000..46943a4
--- /dev/null
+++ b/net/url_request/url_request_view_cache_job.cc
@@ -0,0 +1,194 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_view_cache_job.h"
+
+#include "base/string_util.h"
+#include "net/base/escape.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_info.h"
+
+#define VIEW_CACHE_HEAD \
+ "<html><body><table>"
+
+#define VIEW_CACHE_TAIL \
+ "</table></body></html>"
+
+static void 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) {
+ 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)
+ StringAppendF(result, "%02x ", *p++);
+ for (i = row_max; i < kMaxRows; ++i)
+ 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;
+ }
+}
+
+static std::string FormatEntryInfo(disk_cache::Entry* entry) {
+ std::string key = EscapeForHTML(entry->GetKey());
+ std::string row =
+ "<tr><td><a href=\"view-cache:" + key + "\">" + key + "</a></td></tr>";
+ return row;
+}
+
+static std::string FormatEntryDetails(disk_cache::Entry* entry) {
+ std::string result = EscapeForHTML(entry->GetKey());
+
+ net::HttpResponseInfo response;
+ net::HttpCache::ReadResponseInfo(entry, &response);
+
+ if (response.headers) {
+ result.append("<hr><pre>");
+ result.append(EscapeForHTML(response.headers->GetStatusLine()));
+ result.push_back('\n');
+
+ void* iter = NULL;
+ std::string name, value;
+ while (response.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ result.append(EscapeForHTML(name));
+ result.append(": ");
+ result.append(EscapeForHTML(value));
+ result.push_back('\n');
+ }
+ result.append("</pre>");
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ result.append("<hr><pre>");
+
+ int data_size = entry->GetDataSize(i);
+
+ char* data = new char[data_size];
+ if (entry->ReadData(i, 0, data, data_size, NULL) == data_size)
+ HexDump(data, data_size, &result);
+
+ result.append("</pre>");
+ }
+
+ return result;
+}
+
+static std::string FormatStatistics(disk_cache::Backend* disk_cache) {
+ std::vector<std::pair<std::string, std::string> > stats;
+ disk_cache->GetStats(&stats);
+ std::string result;
+
+ for (size_t index = 0; index < stats.size(); index++) {
+ result.append(stats[index].first);
+ result.append(": ");
+ result.append(stats[index].second);
+ result.append("<br/>\n");
+ }
+
+ return result;
+}
+
+// static
+URLRequestJob* URLRequestViewCacheJob::Factory(URLRequest* request,
+ const std::string& scheme) {
+ return new URLRequestViewCacheJob(request);
+}
+
+bool URLRequestViewCacheJob::GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data) const {
+ mime_type->assign("text/html");
+ charset->assign("UTF-8");
+
+ disk_cache::Backend* disk_cache = GetDiskCache();
+ if (!disk_cache) {
+ data->assign("no disk cache");
+ return true;
+ }
+
+ if (request_->url().spec() == "view-cache:") {
+ data->assign(VIEW_CACHE_HEAD);
+ void* iter = NULL;
+ disk_cache::Entry* entry;
+ while (disk_cache->OpenNextEntry(&iter, &entry)) {
+ data->append(FormatEntryInfo(entry));
+ entry->Close();
+ }
+ data->append(VIEW_CACHE_TAIL);
+ } else if (request_->url().spec() == "view-cache:stats") {
+ data->assign(FormatStatistics(disk_cache));
+ } else {
+ disk_cache::Entry* entry;
+ if (disk_cache->OpenEntry(request_->url().path(), &entry)) {
+ data->assign(FormatEntryDetails(entry));
+ entry->Close();
+ } else {
+ data->assign("no matching cache entry");
+ }
+ }
+ return true;
+}
+
+disk_cache::Backend* URLRequestViewCacheJob::GetDiskCache() const {
+ if (!request_->context())
+ return NULL;
+
+ if (!request_->context()->http_transaction_factory())
+ return NULL;
+
+ net::HttpCache* http_cache =
+ request_->context()->http_transaction_factory()->GetCache();
+ if (!http_cache)
+ return NULL;
+
+ return http_cache->disk_cache();
+}
diff --git a/net/url_request/url_request_view_cache_job.h b/net/url_request/url_request_view_cache_job.h
new file mode 100644
index 0000000..db01664
--- /dev/null
+++ b/net/url_request/url_request_view_cache_job.h
@@ -0,0 +1,56 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_VIEW_CACHE_JOB_H__
+#define NET_URL_REQUEST_URL_REQUEST_VIEW_CACHE_JOB_H__
+
+#include "net/url_request/url_request_simple_job.h"
+
+namespace disk_cache {
+class Backend;
+}
+
+// A job subclass that implements the view-cache: protocol, which simply
+// provides a debug view of the cache or of a particular cache entry.
+class URLRequestViewCacheJob : public URLRequestSimpleJob {
+ public:
+ URLRequestViewCacheJob(URLRequest* request) : URLRequestSimpleJob(request) {}
+
+ static URLRequest::ProtocolFactory Factory;
+
+ // override from URLRequestSimpleJob
+ virtual bool GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data) const;
+
+ private:
+ disk_cache::Backend* GetDiskCache() const;
+};
+
+#endif // NET_URL_REQUEST_URL_REQUEST_VIEW_CACHE_JOB_H__