summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/unit')
-rw-r--r--netwerk/test/unit/CA.cert.derbin0 -> 827 bytes
-rw-r--r--netwerk/test/unit/CA.key.pem30
-rw-r--r--netwerk/test/unit/client_cert_chooser.js26
-rw-r--r--netwerk/test/unit/client_cert_chooser.manifest2
-rw-r--r--netwerk/test/unit/data/image.pngbin0 -> 102591 bytes
-rw-r--r--netwerk/test/unit/data/signed_win.exebin0 -> 61064 bytes
-rw-r--r--netwerk/test/unit/data/system_root.lnkbin0 -> 1677 bytes
-rw-r--r--netwerk/test/unit/data/test_psl.txt98
-rw-r--r--netwerk/test/unit/data/test_readline1.txt0
-rw-r--r--netwerk/test/unit/data/test_readline2.txt1
-rw-r--r--netwerk/test/unit/data/test_readline3.txt3
-rw-r--r--netwerk/test/unit/data/test_readline4.txt3
-rw-r--r--netwerk/test/unit/data/test_readline5.txt1
-rw-r--r--netwerk/test/unit/data/test_readline6.txt1
-rw-r--r--netwerk/test/unit/data/test_readline7.txt2
-rw-r--r--netwerk/test/unit/data/test_readline8.txt1
-rw-r--r--netwerk/test/unit/head_cache.js147
-rw-r--r--netwerk/test/unit/head_cache2.js429
-rw-r--r--netwerk/test/unit/head_channels.js218
-rw-r--r--netwerk/test/unit/socks_client_subprocess.js42
-rw-r--r--netwerk/test/unit/test_1073747.js30
-rw-r--r--netwerk/test/unit/test_304_responses.js95
-rw-r--r--netwerk/test/unit/test_307_redirect.js91
-rw-r--r--netwerk/test/unit/test_421.js60
-rw-r--r--netwerk/test/unit/test_MIME_params.js560
-rw-r--r--netwerk/test/unit/test_NetUtil.js867
-rw-r--r--netwerk/test/unit/test_URIs.js608
-rw-r--r--netwerk/test/unit/test_URIs2.js693
-rw-r--r--netwerk/test/unit/test_XHR_redirects.js235
-rw-r--r--netwerk/test/unit/test_about_networking.js96
-rw-r--r--netwerk/test/unit/test_about_protocol.js50
-rw-r--r--netwerk/test/unit/test_aboutblank.js32
-rw-r--r--netwerk/test/unit/test_addr_in_use_error.js32
-rw-r--r--netwerk/test/unit/test_alt-data_simple.js111
-rw-r--r--netwerk/test/unit/test_alt-data_stream.js120
-rw-r--r--netwerk/test/unit/test_altsvc.js378
-rw-r--r--netwerk/test/unit/test_assoc.js102
-rw-r--r--netwerk/test/unit/test_auth_dialog_permission.js255
-rw-r--r--netwerk/test/unit/test_auth_jar.js49
-rw-r--r--netwerk/test/unit/test_auth_proxy.js399
-rw-r--r--netwerk/test/unit/test_authentication.js2074
-rw-r--r--netwerk/test/unit/test_authpromptwrapper.js233
-rw-r--r--netwerk/test/unit/test_backgroundfilesaver.js731
-rw-r--r--netwerk/test/unit/test_be_conservative.js213
-rw-r--r--netwerk/test/unit/test_bug1064258.js153
-rw-r--r--netwerk/test/unit/test_bug1195415.js116
-rw-r--r--netwerk/test/unit/test_bug1218029.js82
-rw-r--r--netwerk/test/unit/test_bug1279246.js97
-rw-r--r--netwerk/test/unit/test_bug203271.js177
-rw-r--r--netwerk/test/unit/test_bug248970_cache.js151
-rw-r--r--netwerk/test/unit/test_bug248970_cookie.js135
-rw-r--r--netwerk/test/unit/test_bug261425.js26
-rw-r--r--netwerk/test/unit/test_bug263127.js61
-rw-r--r--netwerk/test/unit/test_bug282432.js42
-rw-r--r--netwerk/test/unit/test_bug321706.js11
-rw-r--r--netwerk/test/unit/test_bug331825.js42
-rw-r--r--netwerk/test/unit/test_bug336501.js27
-rw-r--r--netwerk/test/unit/test_bug337744.js114
-rw-r--r--netwerk/test/unit/test_bug365133.js111
-rw-r--r--netwerk/test/unit/test_bug368702.js150
-rw-r--r--netwerk/test/unit/test_bug369787.js71
-rw-r--r--netwerk/test/unit/test_bug371473.js44
-rw-r--r--netwerk/test/unit/test_bug376660.js72
-rw-r--r--netwerk/test/unit/test_bug376844.js21
-rw-r--r--netwerk/test/unit/test_bug376865.js20
-rw-r--r--netwerk/test/unit/test_bug379034.js18
-rw-r--r--netwerk/test/unit/test_bug380994.js22
-rw-r--r--netwerk/test/unit/test_bug388281.js24
-rw-r--r--netwerk/test/unit/test_bug396389.js71
-rw-r--r--netwerk/test/unit/test_bug401564.js48
-rw-r--r--netwerk/test/unit/test_bug411952.js35
-rw-r--r--netwerk/test/unit/test_bug412457.js44
-rw-r--r--netwerk/test/unit/test_bug412945.js42
-rw-r--r--netwerk/test/unit/test_bug414122.js58
-rw-r--r--netwerk/test/unit/test_bug427957.js106
-rw-r--r--netwerk/test/unit/test_bug429347.js38
-rw-r--r--netwerk/test/unit/test_bug455311.js128
-rw-r--r--netwerk/test/unit/test_bug455598.js35
-rw-r--r--netwerk/test/unit/test_bug464591.js81
-rw-r--r--netwerk/test/unit/test_bug468426.js100
-rw-r--r--netwerk/test/unit/test_bug468594.js127
-rw-r--r--netwerk/test/unit/test_bug470716.js174
-rw-r--r--netwerk/test/unit/test_bug477578.js50
-rw-r--r--netwerk/test/unit/test_bug479413.js59
-rw-r--r--netwerk/test/unit/test_bug479485.js47
-rw-r--r--netwerk/test/unit/test_bug482601.js233
-rw-r--r--netwerk/test/unit/test_bug484684.js115
-rw-r--r--netwerk/test/unit/test_bug490095.js116
-rw-r--r--netwerk/test/unit/test_bug504014.js69
-rw-r--r--netwerk/test/unit/test_bug510359.js77
-rw-r--r--netwerk/test/unit/test_bug515583.js73
-rw-r--r--netwerk/test/unit/test_bug528292.js90
-rw-r--r--netwerk/test/unit/test_bug536324_64bit_content_length.js64
-rw-r--r--netwerk/test/unit/test_bug540566.js18
-rw-r--r--netwerk/test/unit/test_bug543805.js93
-rw-r--r--netwerk/test/unit/test_bug553970.js44
-rw-r--r--netwerk/test/unit/test_bug561042.js38
-rw-r--r--netwerk/test/unit/test_bug561276.js68
-rw-r--r--netwerk/test/unit/test_bug580508.js26
-rw-r--r--netwerk/test/unit/test_bug586908.js92
-rw-r--r--netwerk/test/unit/test_bug596443.js97
-rw-r--r--netwerk/test/unit/test_bug618835.js115
-rw-r--r--netwerk/test/unit/test_bug633743.js186
-rw-r--r--netwerk/test/unit/test_bug650995.js159
-rw-r--r--netwerk/test/unit/test_bug652761.js17
-rw-r--r--netwerk/test/unit/test_bug654926.js88
-rw-r--r--netwerk/test/unit/test_bug654926_doom_and_read.js77
-rw-r--r--netwerk/test/unit/test_bug654926_test_seek.js63
-rw-r--r--netwerk/test/unit/test_bug659569.js57
-rw-r--r--netwerk/test/unit/test_bug660066.js42
-rw-r--r--netwerk/test/unit/test_bug667818.js21
-rw-r--r--netwerk/test/unit/test_bug667907.js84
-rw-r--r--netwerk/test/unit/test_bug669001.js160
-rw-r--r--netwerk/test/unit/test_bug767025.js275
-rw-r--r--netwerk/test/unit/test_bug770243.js207
-rw-r--r--netwerk/test/unit/test_bug812167.js127
-rw-r--r--netwerk/test/unit/test_bug826063.js107
-rw-r--r--netwerk/test/unit/test_bug856978.js135
-rw-r--r--netwerk/test/unit/test_bug894586.js158
-rw-r--r--netwerk/test/unit/test_bug935499.js7
-rw-r--r--netwerk/test/unit/test_cache-control_request.js385
-rw-r--r--netwerk/test/unit/test_cache2-00-service-get.js16
-rw-r--r--netwerk/test/unit/test_cache2-01-basic.js28
-rw-r--r--netwerk/test/unit/test_cache2-01a-basic-readonly.js28
-rw-r--r--netwerk/test/unit/test_cache2-01b-basic-datasize.js32
-rw-r--r--netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js28
-rw-r--r--netwerk/test/unit/test_cache2-01d-basic-not-wanted.js28
-rw-r--r--netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js35
-rw-r--r--netwerk/test/unit/test_cache2-01f-basic-openTruncate.js24
-rw-r--r--netwerk/test/unit/test_cache2-02-open-non-existing.js28
-rw-r--r--netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js24
-rw-r--r--netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js28
-rw-r--r--netwerk/test/unit/test_cache2-05-visit.js78
-rw-r--r--netwerk/test/unit/test_cache2-06-pb-mode.js41
-rw-r--r--netwerk/test/unit/test_cache2-07-visit-memory.js82
-rw-r--r--netwerk/test/unit/test_cache2-07a-open-memory.js53
-rw-r--r--netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js18
-rw-r--r--netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js21
-rw-r--r--netwerk/test/unit/test_cache2-10-evict-direct.js20
-rw-r--r--netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js21
-rw-r--r--netwerk/test/unit/test_cache2-11-evict-memory.js61
-rw-r--r--netwerk/test/unit/test_cache2-12-evict-disk.js61
-rw-r--r--netwerk/test/unit/test_cache2-13-evict-non-existing.js13
-rw-r--r--netwerk/test/unit/test_cache2-14-concurent-readers.js31
-rw-r--r--netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js48
-rw-r--r--netwerk/test/unit/test_cache2-15-conditional-304.js39
-rw-r--r--netwerk/test/unit/test_cache2-16-conditional-200.js52
-rw-r--r--netwerk/test/unit/test_cache2-17-evict-all.js17
-rw-r--r--netwerk/test/unit/test_cache2-18-not-valid.js30
-rw-r--r--netwerk/test/unit/test_cache2-19-range-206.js44
-rw-r--r--netwerk/test/unit/test_cache2-20-range-200.js45
-rw-r--r--netwerk/test/unit/test_cache2-21-anon-storage.js38
-rw-r--r--netwerk/test/unit/test_cache2-22-anon-visit.js58
-rw-r--r--netwerk/test/unit/test_cache2-23-read-over-chunk.js35
-rw-r--r--netwerk/test/unit/test_cache2-24-exists.js38
-rw-r--r--netwerk/test/unit/test_cache2-25-chunk-memory-limit.js51
-rw-r--r--netwerk/test/unit/test_cache2-26-no-outputstream-open.js27
-rw-r--r--netwerk/test/unit/test_cache2-27-force-valid-for.js37
-rw-r--r--netwerk/test/unit/test_cache2-28-last-access-attrs.js39
-rw-r--r--netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js34
-rw-r--r--netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js72
-rw-r--r--netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js71
-rw-r--r--netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js91
-rw-r--r--netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js95
-rw-r--r--netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js90
-rw-r--r--netwerk/test/unit/test_cache2-30a-entry-pinning.js32
-rw-r--r--netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js38
-rw-r--r--netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js134
-rw-r--r--netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js113
-rw-r--r--netwerk/test/unit/test_cacheForOfflineUse_no-store.js93
-rw-r--r--netwerk/test/unit/test_cache_jar.js126
-rw-r--r--netwerk/test/unit/test_cacheflags.js370
-rw-r--r--netwerk/test/unit/test_channel_close.js59
-rw-r--r--netwerk/test/unit/test_chunked_responses.js175
-rw-r--r--netwerk/test/unit/test_compareURIs.js49
-rw-r--r--netwerk/test/unit/test_compressappend.js80
-rw-r--r--netwerk/test/unit/test_content_encoding_gzip.js114
-rw-r--r--netwerk/test/unit/test_content_length_underrun.js278
-rw-r--r--netwerk/test/unit/test_content_sniffer.js131
-rw-r--r--netwerk/test/unit/test_cookie_blacklist.js19
-rw-r--r--netwerk/test/unit/test_cookie_header.js100
-rw-r--r--netwerk/test/unit/test_cookiejars.js149
-rw-r--r--netwerk/test/unit/test_cookiejars_safebrowsing.js178
-rw-r--r--netwerk/test/unit/test_data_protocol.js58
-rw-r--r--netwerk/test/unit/test_dns_cancel.js83
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv4.js40
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv6.js41
-rw-r--r--netwerk/test/unit/test_dns_localredirect.js31
-rw-r--r--netwerk/test/unit/test_dns_offline.js74
-rw-r--r--netwerk/test/unit/test_dns_onion.js70
-rw-r--r--netwerk/test/unit/test_dns_per_interface.js79
-rw-r--r--netwerk/test/unit/test_dns_proxy_bypass.js77
-rw-r--r--netwerk/test/unit/test_dns_service.js26
-rw-r--r--netwerk/test/unit/test_doomentry.js97
-rw-r--r--netwerk/test/unit/test_duplicate_headers.js605
-rw-r--r--netwerk/test/unit/test_event_sink.js170
-rw-r--r--netwerk/test/unit/test_extract_charset_from_content_type.js163
-rw-r--r--netwerk/test/unit/test_fallback_no-cache-entry_canceled.js112
-rw-r--r--netwerk/test/unit/test_fallback_no-cache-entry_passing.js110
-rw-r--r--netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js115
-rw-r--r--netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js114
-rw-r--r--netwerk/test/unit/test_fallback_request-error_canceled.js121
-rw-r--r--netwerk/test/unit/test_fallback_request-error_passing.js119
-rw-r--r--netwerk/test/unit/test_fallback_response-error_canceled.js116
-rw-r--r--netwerk/test/unit/test_fallback_response-error_passing.js114
-rw-r--r--netwerk/test/unit/test_file_partial_inputstream.js512
-rw-r--r--netwerk/test/unit/test_file_protocol.js251
-rw-r--r--netwerk/test/unit/test_filestreams.js298
-rw-r--r--netwerk/test/unit/test_freshconnection.js30
-rw-r--r--netwerk/test/unit/test_getHost.js68
-rw-r--r--netwerk/test/unit/test_gre_resources.js31
-rw-r--r--netwerk/test/unit/test_gzipped_206.js94
-rw-r--r--netwerk/test/unit/test_head.js150
-rw-r--r--netwerk/test/unit/test_header_Accept-Language.js91
-rw-r--r--netwerk/test/unit/test_header_Accept-Language_case.js47
-rw-r--r--netwerk/test/unit/test_headers.js186
-rw-r--r--netwerk/test/unit/test_http2.js1119
-rw-r--r--netwerk/test/unit/test_httpResponseTimeout.js162
-rw-r--r--netwerk/test/unit/test_http_headers.js70
-rw-r--r--netwerk/test/unit/test_httpauth.js99
-rw-r--r--netwerk/test/unit/test_httpcancel.js114
-rw-r--r--netwerk/test/unit/test_httpsuspend.js80
-rw-r--r--netwerk/test/unit/test_idn_blacklist.js170
-rw-r--r--netwerk/test/unit/test_idn_urls.js345
-rw-r--r--netwerk/test/unit/test_idna2008.js60
-rw-r--r--netwerk/test/unit/test_idnservice.js25
-rw-r--r--netwerk/test/unit/test_immutable.js180
-rw-r--r--netwerk/test/unit/test_inhibit_caching.js76
-rw-r--r--netwerk/test/unit/test_large_port.js36
-rw-r--r--netwerk/test/unit/test_link.desktop3
-rw-r--r--netwerk/test/unit/test_link.url5
-rw-r--r--netwerk/test/unit/test_localstreams.js87
-rw-r--r--netwerk/test/unit/test_mismatch_last-modified.js154
-rw-r--r--netwerk/test/unit/test_mozTXTToHTMLConv.js199
-rw-r--r--netwerk/test/unit/test_multipart_byteranges.js113
-rw-r--r--netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js109
-rw-r--r--netwerk/test/unit/test_multipart_streamconv.js93
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js89
-rw-r--r--netwerk/test/unit/test_nestedabout_serialize.js35
-rw-r--r--netwerk/test/unit/test_net_addr.js199
-rw-r--r--netwerk/test/unit/test_nojsredir.js62
-rw-r--r--netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js179
-rw-r--r--netwerk/test/unit/test_offline_status.js15
-rw-r--r--netwerk/test/unit/test_offlinecache_custom-directory.js151
-rw-r--r--netwerk/test/unit/test_original_sent_received_head.js220
-rw-r--r--netwerk/test/unit/test_parse_content_type.js200
-rw-r--r--netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js84
-rw-r--r--netwerk/test/unit/test_permmgr.js119
-rw-r--r--netwerk/test/unit/test_ping_aboutnetworking.js86
-rw-r--r--netwerk/test/unit/test_pinned_app_cache.js277
-rw-r--r--netwerk/test/unit/test_plaintext_sniff.js194
-rw-r--r--netwerk/test/unit/test_post.js120
-rw-r--r--netwerk/test/unit/test_predictor.js596
-rw-r--r--netwerk/test/unit/test_private_cookie_changed.js34
-rw-r--r--netwerk/test/unit/test_private_necko_channel.js53
-rw-r--r--netwerk/test/unit/test_progress.js128
-rw-r--r--netwerk/test/unit/test_protocolproxyservice.js958
-rw-r--r--netwerk/test/unit/test_proxy-failover_canceled.js53
-rw-r--r--netwerk/test/unit/test_proxy-failover_passing.js43
-rw-r--r--netwerk/test/unit/test_proxy-replace_canceled.js55
-rw-r--r--netwerk/test/unit/test_proxy-replace_passing.js43
-rw-r--r--netwerk/test/unit/test_psl.js36
-rw-r--r--netwerk/test/unit/test_range_requests.js434
-rw-r--r--netwerk/test/unit/test_readline.js60
-rw-r--r--netwerk/test/unit/test_redirect-caching_canceled.js68
-rw-r--r--netwerk/test/unit/test_redirect-caching_failure.js55
-rw-r--r--netwerk/test/unit/test_redirect-caching_passing.js59
-rw-r--r--netwerk/test/unit/test_redirect_baduri.js42
-rw-r--r--netwerk/test/unit/test_redirect_canceled.js51
-rw-r--r--netwerk/test/unit/test_redirect_different-protocol.js51
-rw-r--r--netwerk/test/unit/test_redirect_failure.js45
-rw-r--r--netwerk/test/unit/test_redirect_from_script.js258
-rw-r--r--netwerk/test/unit/test_redirect_from_script_after-open_passing.js258
-rw-r--r--netwerk/test/unit/test_redirect_history.js64
-rw-r--r--netwerk/test/unit/test_redirect_loop.js86
-rw-r--r--netwerk/test/unit/test_redirect_passing.js57
-rw-r--r--netwerk/test/unit/test_reentrancy.js105
-rw-r--r--netwerk/test/unit/test_referrer.js109
-rw-r--r--netwerk/test/unit/test_referrer_policy.js95
-rw-r--r--netwerk/test/unit/test_reopen.js141
-rw-r--r--netwerk/test/unit/test_reply_without_content_type.js91
-rw-r--r--netwerk/test/unit/test_resumable_channel.js402
-rw-r--r--netwerk/test/unit/test_resumable_truncate.js88
-rw-r--r--netwerk/test/unit/test_safeoutputstream.js66
-rw-r--r--netwerk/test/unit/test_safeoutputstream_append.js42
-rw-r--r--netwerk/test/unit/test_separate_connections.js99
-rw-r--r--netwerk/test/unit/test_signature_extraction.js206
-rw-r--r--netwerk/test/unit/test_simple.js56
-rw-r--r--netwerk/test/unit/test_sockettransportsvc_available.js8
-rw-r--r--netwerk/test/unit/test_socks.js525
-rw-r--r--netwerk/test/unit/test_speculative_connect.js333
-rw-r--r--netwerk/test/unit/test_standardurl.js455
-rw-r--r--netwerk/test/unit/test_standardurl_default_port.js51
-rw-r--r--netwerk/test/unit/test_standardurl_port.js56
-rw-r--r--netwerk/test/unit/test_streamcopier.js53
-rw-r--r--netwerk/test/unit/test_suspend_channel_before_connect.js102
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_modified.js175
-rw-r--r--netwerk/test/unit/test_synthesized_response.js243
-rw-r--r--netwerk/test/unit/test_throttlechannel.js41
-rw-r--r--netwerk/test/unit/test_throttlequeue.js23
-rw-r--r--netwerk/test/unit/test_throttling.js57
-rw-r--r--netwerk/test/unit/test_tldservice_nextsubdomain.js28
-rw-r--r--netwerk/test/unit/test_tls_server.js237
-rw-r--r--netwerk/test/unit/test_tls_server_multiple_clients.js141
-rw-r--r--netwerk/test/unit/test_traceable_channel.js150
-rw-r--r--netwerk/test/unit/test_udp_multicast.js114
-rw-r--r--netwerk/test/unit/test_udpsocket.js63
-rw-r--r--netwerk/test/unit/test_unescapestring.js31
-rw-r--r--netwerk/test/unit/test_unix_domain.js545
-rw-r--r--netwerk/test/unit/test_websocket_offline.js51
-rw-r--r--netwerk/test/unit/test_xmlhttprequest.js54
-rw-r--r--netwerk/test/unit/xpcshell.ini369
312 files changed, 38633 insertions, 0 deletions
diff --git a/netwerk/test/unit/CA.cert.der b/netwerk/test/unit/CA.cert.der
new file mode 100644
index 000000000..67157cabd
--- /dev/null
+++ b/netwerk/test/unit/CA.cert.der
Binary files differ
diff --git a/netwerk/test/unit/CA.key.pem b/netwerk/test/unit/CA.key.pem
new file mode 100644
index 000000000..2153c9dd5
--- /dev/null
+++ b/netwerk/test/unit/CA.key.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIoXjtIGzP1OkCAggA
+MBQGCCqGSIb3DQMHBAg7NkhJaDJEVwSCBMhYUI4JRAIdvJtCmP7lKl30QR+HG4JC
+9gUJflQrCsa1WhxKCHa7VXzqlItQTu0FTMwn9GRFUOtqpY8ZE6XJFuvzKPox3RMa
+1TMod6v3NS3KIs6/l2Pb2HcGtcMzOJVei1nwtjsT5fvq36x3eoKzgqd9l0fLcvlD
+wf+byzgY0Nsau+ER8DWy65jXF8bPsQQcKTc+U+p4moO3UuXG4+Pnd8ooSaM4X2on
+1jIYDU1aFoSDUvze8+MvQCD32QLuO63iK7ox4sFharG7KucYqeWCihDx5rlGaVGB
+5647v4oHRysEdLVTkU12mIC/Hx/yPXcLhHYmawmnYwEoh1S+wd7rOo9Wn/l16NTK
+8BcDuvfM8km4T5oO/UFaNDIBLBQsNM5sNHDYFDlhmR4x6d5nXeERJ6DQbvhQtgnV
+bTtT9h24rsC8Irflz/abcvTvqqp8I1+gYEzmhgDRUgp9zAPZUoH3E4DKk5rVgApR
+ARX9Y88S7k/OBnU8r+cT+0CjsusbbIv5W2nAFqEX9jMend0cHzYvq3m6v1Jqxjfn
+kQRP1n+SagmAPBIAzy1wSHGV43+COk6FB+blfAGbO55lLglEM9PLH7Nnl0XrPtaE
+dXx5RTtdBnb349Ow8H3WnleTfKspUbIVNyM48aPaXJu6Y784pUXDOC13ISFVbOew
+dPr/s/GoHgBUIm9gxkhNQYUlcSNrJCyJ6bqvrYbOmVQRusO/SaM6ozY8wFL8LDnS
+GeXmg3dAslHhuaHlFN7atF7rBtTWPsH+oQdHNKcLDK7nYq45v8VfjPUrWPfYc2nB
+l+zT4LozY3VPfPW7BG2zVBTyxXkiynz0w7tJaN/HokZGAUDqWXqjSceJqc9Q4XAG
+slIxbxkfxEJUEmJ2wHEnure6T0dJOIfbJzkCqWAeJjkrbI5mdKLuXFj94VgSlfK2
+iq3J20/5HVdHqoVGRZ5rxBUIaVEgSXB3/+9C/M0U0uxx23zxRmVkMGdhhCqXQRh/
+jFUkBzq4x3yibxJW3fRe7jXEJdo1DAAfgBnDvCUWH7lRX8hDkx6OIX4ZS4D7Va0j
+ogSC04IdZWxOP3YJ4gGwx8vvgHWnBLyFfmdFnfHXUr9A8HDDJQTupYg25PDUGHla
+SxukgOYdQ2O6jUCW0TYeUzX7y/P/Za93kWJp7XqA4v76fQ+C9d3CZT/TY0CqNgxB
+C5+PWRGvxtcy+Bne8QYCJhvNPEhfgFa9fU3Rd4w43lvTb9rsy7eBR3jJKdPLKExJ
+zEPIgVUGaMf0lawL0fIgoUI5Q3kRCmrswkTK9kr4+rSA//p0NralnZtHCWRvgs9W
+Lg4hkf1vXxsa9f16Nk6PxqU/OnJmhTnTnv9MzFoX3Sce2neD86H5c7tdguySbrsj
+5fww64rH1UwHhn/i49i3hkseax48gOAZPA8rl+L70FS8dXLpHOm4ihmv6ubVjr82
+yOxi4WmaoXfmOPBgOgGhz1nTFAaetwfhZIsgEtysuWAOsApOUyjlD/wM25988bAa
+m5FwslUGLWQfBIV1N9PC+Q0ui1ywRuLoKHNiKDSE+T5iOuv2Yf7du4nncoM/ANmU
+FnWJL3Aj1VE/O+OeUyuNEPWLHvVX5TChe5mFXZO4bXfTR4tgdJJ15HWf4LKMQdcl
+BEA=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/netwerk/test/unit/client_cert_chooser.js b/netwerk/test/unit/client_cert_chooser.js
new file mode 100644
index 000000000..610e8a1cf
--- /dev/null
+++ b/netwerk/test/unit/client_cert_chooser.js
@@ -0,0 +1,26 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+var { utils: Cu, interfaces: Ci } = Components;
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+var Prompter = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
+ alert: function() {} // Do nothing when asked to show an alert
+};
+
+function WindowWatcherService() {}
+WindowWatcherService.prototype = {
+ classID: Components.ID("{01ae923c-81bb-45db-b860-d423b0fc4fe1}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]),
+
+ getNewPrompter: function() {
+ return Prompter;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
+ WindowWatcherService
+]);
diff --git a/netwerk/test/unit/client_cert_chooser.manifest b/netwerk/test/unit/client_cert_chooser.manifest
new file mode 100644
index 000000000..e604c92d0
--- /dev/null
+++ b/netwerk/test/unit/client_cert_chooser.manifest
@@ -0,0 +1,2 @@
+component {01ae923c-81bb-45db-b860-d423b0fc4fe1} cert_dialog.js
+contract @mozilla.org/embedcomp/window-watcher;1 {01ae923c-81bb-45db-b860-d423b0fc4fe1}
diff --git a/netwerk/test/unit/data/image.png b/netwerk/test/unit/data/image.png
new file mode 100644
index 000000000..e0c5d3d6a
--- /dev/null
+++ b/netwerk/test/unit/data/image.png
Binary files differ
diff --git a/netwerk/test/unit/data/signed_win.exe b/netwerk/test/unit/data/signed_win.exe
new file mode 100644
index 000000000..de3bb40e8
--- /dev/null
+++ b/netwerk/test/unit/data/signed_win.exe
Binary files differ
diff --git a/netwerk/test/unit/data/system_root.lnk b/netwerk/test/unit/data/system_root.lnk
new file mode 100644
index 000000000..e5885ce9a
--- /dev/null
+++ b/netwerk/test/unit/data/system_root.lnk
Binary files differ
diff --git a/netwerk/test/unit/data/test_psl.txt b/netwerk/test/unit/data/test_psl.txt
new file mode 100644
index 000000000..da6a637ad
--- /dev/null
+++ b/netwerk/test/unit/data/test_psl.txt
@@ -0,0 +1,98 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// null input.
+checkPublicSuffix(null, null);
+// Mixed case.
+checkPublicSuffix('COM', null);
+checkPublicSuffix('example.COM', 'example.com');
+checkPublicSuffix('WwW.example.COM', 'example.com');
+// Leading dot.
+checkPublicSuffix('.com', null);
+checkPublicSuffix('.example', null);
+checkPublicSuffix('.example.com', null);
+checkPublicSuffix('.example.example', null);
+// Unlisted TLD.
+checkPublicSuffix('example', null);
+checkPublicSuffix('example.example', 'example.example');
+checkPublicSuffix('b.example.example', 'example.example');
+checkPublicSuffix('a.b.example.example', 'example.example');
+// Listed, but non-Internet, TLD.
+//checkPublicSuffix('local', null);
+//checkPublicSuffix('example.local', null);
+//checkPublicSuffix('b.example.local', null);
+//checkPublicSuffix('a.b.example.local', null);
+// TLD with only 1 rule.
+checkPublicSuffix('biz', null);
+checkPublicSuffix('domain.biz', 'domain.biz');
+checkPublicSuffix('b.domain.biz', 'domain.biz');
+checkPublicSuffix('a.b.domain.biz', 'domain.biz');
+// TLD with some 2-level rules.
+checkPublicSuffix('com', null);
+checkPublicSuffix('example.com', 'example.com');
+checkPublicSuffix('b.example.com', 'example.com');
+checkPublicSuffix('a.b.example.com', 'example.com');
+checkPublicSuffix('uk.com', null);
+checkPublicSuffix('example.uk.com', 'example.uk.com');
+checkPublicSuffix('b.example.uk.com', 'example.uk.com');
+checkPublicSuffix('a.b.example.uk.com', 'example.uk.com');
+checkPublicSuffix('test.ac', 'test.ac');
+// TLD with only 1 (wildcard) rule.
+checkPublicSuffix('il', null);
+checkPublicSuffix('c.il', null);
+checkPublicSuffix('b.c.il', 'b.c.il');
+checkPublicSuffix('a.b.c.il', 'b.c.il');
+// More complex TLD.
+checkPublicSuffix('jp', null);
+checkPublicSuffix('test.jp', 'test.jp');
+checkPublicSuffix('www.test.jp', 'test.jp');
+checkPublicSuffix('ac.jp', null);
+checkPublicSuffix('test.ac.jp', 'test.ac.jp');
+checkPublicSuffix('www.test.ac.jp', 'test.ac.jp');
+checkPublicSuffix('kyoto.jp', null);
+checkPublicSuffix('test.kyoto.jp', 'test.kyoto.jp');
+checkPublicSuffix('ide.kyoto.jp', null);
+checkPublicSuffix('b.ide.kyoto.jp', 'b.ide.kyoto.jp');
+checkPublicSuffix('a.b.ide.kyoto.jp', 'b.ide.kyoto.jp');
+checkPublicSuffix('c.kobe.jp', null);
+checkPublicSuffix('b.c.kobe.jp', 'b.c.kobe.jp');
+checkPublicSuffix('a.b.c.kobe.jp', 'b.c.kobe.jp');
+checkPublicSuffix('city.kobe.jp', 'city.kobe.jp');
+checkPublicSuffix('www.city.kobe.jp', 'city.kobe.jp');
+// TLD with a wildcard rule and exceptions.
+checkPublicSuffix('ck', null);
+checkPublicSuffix('test.ck', null);
+checkPublicSuffix('b.test.ck', 'b.test.ck');
+checkPublicSuffix('a.b.test.ck', 'b.test.ck');
+checkPublicSuffix('www.ck', 'www.ck');
+checkPublicSuffix('www.www.ck', 'www.ck');
+// US K12.
+checkPublicSuffix('us', null);
+checkPublicSuffix('test.us', 'test.us');
+checkPublicSuffix('www.test.us', 'test.us');
+checkPublicSuffix('ak.us', null);
+checkPublicSuffix('test.ak.us', 'test.ak.us');
+checkPublicSuffix('www.test.ak.us', 'test.ak.us');
+checkPublicSuffix('k12.ak.us', null);
+checkPublicSuffix('test.k12.ak.us', 'test.k12.ak.us');
+checkPublicSuffix('www.test.k12.ak.us', 'test.k12.ak.us');
+// IDN labels.
+checkPublicSuffix('食狮.com.cn', '食狮.com.cn');
+checkPublicSuffix('食狮.公司.cn', '食狮.公司.cn');
+checkPublicSuffix('www.食狮.公司.cn', '食狮.公司.cn');
+checkPublicSuffix('shishi.公司.cn', 'shishi.公司.cn');
+checkPublicSuffix('公司.cn', null);
+checkPublicSuffix('食狮.中国', '食狮.中国');
+checkPublicSuffix('www.食狮.中国', '食狮.中国');
+checkPublicSuffix('shishi.中国', 'shishi.中国');
+checkPublicSuffix('中国', null);
+// Same as above, but punycoded.
+checkPublicSuffix('xn--85x722f.com.cn', 'xn--85x722f.com.cn');
+checkPublicSuffix('xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn');
+checkPublicSuffix('www.xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn');
+checkPublicSuffix('shishi.xn--55qx5d.cn', 'shishi.xn--55qx5d.cn');
+checkPublicSuffix('xn--55qx5d.cn', null);
+checkPublicSuffix('xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s');
+checkPublicSuffix('www.xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s');
+checkPublicSuffix('shishi.xn--fiqs8s', 'shishi.xn--fiqs8s');
+checkPublicSuffix('xn--fiqs8s', null);
diff --git a/netwerk/test/unit/data/test_readline1.txt b/netwerk/test/unit/data/test_readline1.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline1.txt
diff --git a/netwerk/test/unit/data/test_readline2.txt b/netwerk/test/unit/data/test_readline2.txt
new file mode 100644
index 000000000..67c329761
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline2.txt
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/netwerk/test/unit/data/test_readline3.txt b/netwerk/test/unit/data/test_readline3.txt
new file mode 100644
index 000000000..decdc5187
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline3.txt
@@ -0,0 +1,3 @@
+
+
+
diff --git a/netwerk/test/unit/data/test_readline4.txt b/netwerk/test/unit/data/test_readline4.txt
new file mode 100644
index 000000000..ca25c3654
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline4.txt
@@ -0,0 +1,3 @@
+1
+ 23 456
+78901
diff --git a/netwerk/test/unit/data/test_readline5.txt b/netwerk/test/unit/data/test_readline5.txt
new file mode 100644
index 000000000..8463b7858
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline5.txt
@@ -0,0 +1 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE \ No newline at end of file
diff --git a/netwerk/test/unit/data/test_readline6.txt b/netwerk/test/unit/data/test_readline6.txt
new file mode 100644
index 000000000..872c40afc
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline6.txt
@@ -0,0 +1 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE
diff --git a/netwerk/test/unit/data/test_readline7.txt b/netwerk/test/unit/data/test_readline7.txt
new file mode 100644
index 000000000..59ee122ce
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline7.txt
@@ -0,0 +1,2 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE
+ \ No newline at end of file
diff --git a/netwerk/test/unit/data/test_readline8.txt b/netwerk/test/unit/data/test_readline8.txt
new file mode 100644
index 000000000..ff6fc09a4
--- /dev/null
+++ b/netwerk/test/unit/data/test_readline8.txt
@@ -0,0 +1 @@
+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE \ No newline at end of file
diff --git a/netwerk/test/unit/head_cache.js b/netwerk/test/unit/head_cache.js
new file mode 100644
index 000000000..6c8cf0d4a
--- /dev/null
+++ b/netwerk/test/unit/head_cache.js
@@ -0,0 +1,147 @@
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+var _CSvc;
+function get_cache_service() {
+ if (_CSvc)
+ return _CSvc;
+
+ return _CSvc = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+}
+
+function evict_cache_entries(where)
+{
+ var clearDisk = (!where || where == "disk" || where == "all");
+ var clearMem = (!where || where == "memory" || where == "all");
+ var clearAppCache = (where == "appcache");
+
+ var svc = get_cache_service();
+ var storage;
+
+ if (clearMem) {
+ storage = svc.memoryCacheStorage(LoadContextInfo.default);
+ storage.asyncEvictStorage(null);
+ }
+
+ if (clearDisk) {
+ storage = svc.diskCacheStorage(LoadContextInfo.default, false);
+ storage.asyncEvictStorage(null);
+ }
+
+ if (clearAppCache) {
+ storage = svc.appCacheStorage(LoadContextInfo.default, null);
+ storage.asyncEvictStorage(null);
+ }
+}
+
+function createURI(urispec)
+{
+ var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ return ioServ.newURI(urispec, null, null);
+}
+
+function getCacheStorage(where, lci, appcache)
+{
+ if (!lci) lci = LoadContextInfo.default;
+ var svc = get_cache_service();
+ switch (where) {
+ case "disk": return svc.diskCacheStorage(lci, false);
+ case "memory": return svc.memoryCacheStorage(lci);
+ case "appcache": return svc.appCacheStorage(lci, appcache);
+ case "pin": return svc.pinningCacheStorage(lci);
+ }
+ return null;
+}
+
+function asyncOpenCacheEntry(key, where, flags, lci, callback, appcache)
+{
+ key = createURI(key);
+
+ function CacheListener() { }
+ CacheListener.prototype = {
+ _appCache: appcache,
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Components.interfaces.nsICacheEntryOpenCallback) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onCacheEntryCheck: function(entry, appCache) {
+ if (typeof callback === "object")
+ return callback.onCacheEntryCheck(entry, appCache);
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function (entry, isnew, appCache, status) {
+ if (typeof callback === "object") {
+ // Root us at the callback
+ callback.__cache_listener_root = this;
+ callback.onCacheEntryAvailable(entry, isnew, appCache, status);
+ }
+ else
+ callback(status, entry, appCache);
+ },
+
+ run: function () {
+ var storage = getCacheStorage(where, lci, this._appCache);
+ storage.asyncOpenURI(key, "", flags, this);
+ }
+ };
+
+ (new CacheListener()).run();
+}
+
+function syncWithCacheIOThread(callback, force)
+{
+ if (!newCacheBackEndUsed() || force) {
+ asyncOpenCacheEntry(
+ "http://nonexistententry/", "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ function(status, entry) {
+ do_check_eq(status, Components.results.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ callback();
+ });
+ }
+ else {
+ callback();
+ }
+}
+
+function get_device_entry_count(where, lci, continuation) {
+ var storage = getCacheStorage(where, lci);
+ if (!storage) {
+ continuation(-1, 0);
+ return;
+ }
+
+ var visitor = {
+ onCacheStorageInfo: function (entryCount, consumption) {
+ do_execute_soon(function() {
+ continuation(entryCount, consumption);
+ });
+ },
+ };
+
+ // get the device entry count
+ storage.asyncVisitStorage(visitor, false);
+}
+
+function asyncCheckCacheEntryPresence(key, where, shouldExist, continuation, appCache)
+{
+ asyncOpenCacheEntry(key, where, Ci.nsICacheStorage.OPEN_READONLY, null,
+ function(status, entry) {
+ if (shouldExist) {
+ dump("TEST-INFO | checking cache key " + key + " exists @ " + where);
+ do_check_eq(status, Cr.NS_OK);
+ do_check_true(!!entry);
+ } else {
+ dump("TEST-INFO | checking cache key " + key + " doesn't exist @ " + where);
+ do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ do_check_null(entry);
+ }
+ continuation();
+ }, appCache);
+}
diff --git a/netwerk/test/unit/head_cache2.js b/netwerk/test/unit/head_cache2.js
new file mode 100644
index 000000000..decf04f90
--- /dev/null
+++ b/netwerk/test/unit/head_cache2.js
@@ -0,0 +1,429 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+function newCacheBackEndUsed()
+{
+ var cache1srv = Components.classes["@mozilla.org/network/cache-service;1"]
+ .getService(Components.interfaces.nsICacheService);
+ var cache2srv = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+
+ return cache1srv.cacheIOTarget != cache2srv.ioTarget;
+}
+
+var callbacks = new Array();
+
+// Expect an existing entry
+const NORMAL = 0;
+// Expect a new entry
+const NEW = 1 << 0;
+// Return early from onCacheEntryCheck and set the callback to state it expects onCacheEntryCheck to happen
+const NOTVALID = 1 << 1;
+// Throw from onCacheEntryAvailable
+const THROWAVAIL = 1 << 2;
+// Open entry for reading-only
+const READONLY = 1 << 3;
+// Expect the entry to not be found
+const NOTFOUND = 1 << 4;
+// Return ENTRY_NEEDS_REVALIDATION from onCacheEntryCheck
+const REVAL = 1 << 5;
+// Return ENTRY_PARTIAL from onCacheEntryCheck, in combo with NEW or RECREATE bypasses check for emptiness of the entry
+const PARTIAL = 1 << 6
+// Expect the entry is doomed, i.e. the output stream should not be possible to open
+const DOOMED = 1 << 7;
+// Don't trigger the go-on callback until the entry is written
+const WAITFORWRITE = 1 << 8;
+// Don't write data (i.e. don't open output stream)
+const METAONLY = 1 << 9;
+// Do recreation of an existing cache entry
+const RECREATE = 1 << 10;
+// Do not give me the entry
+const NOTWANTED = 1 << 11;
+// Tell the cache to wait for the entry to be completely written first
+const COMPLETE = 1 << 12;
+// Don't write meta/data and don't set valid in the callback, consumer will do it manually
+const DONTFILL = 1 << 13;
+// Used in combination with METAONLY, don't call setValid() on the entry after metadata has been set
+const DONTSETVALID = 1 << 14;
+// Notify before checking the data, useful for proper callback ordering checks
+const NOTIFYBEFOREREAD = 1 << 15;
+// It's allowed to not get an existing entry (result of opening is undetermined)
+const MAYBE_NEW = 1 << 16;
+
+var log_c2 = true;
+function LOG_C2(o, m)
+{
+ if (!log_c2) return;
+ if (!m)
+ dump("TEST-INFO | CACHE2: " + o + "\n");
+ else
+ dump("TEST-INFO | CACHE2: callback #" + o.order + "(" + (o.workingData ? o.workingData.substr(0, 10) : "---") + ") " + m + "\n");
+}
+
+function pumpReadStream(inputStream, goon)
+{
+ if (inputStream.isNonBlocking()) {
+ // non-blocking stream, must read via pump
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Ci.nsIInputStreamPump);
+ pump.init(inputStream, -1, -1, 0, 0, true);
+ var data = "";
+ pump.asyncRead({
+ onStartRequest: function (aRequest, aContext) { },
+ onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, aCount)
+ {
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ wrapper.init(aInputStream);
+ var str = wrapper.read(wrapper.available());
+ LOG_C2("reading data '" + str.substring(0,5) + "'");
+ data += str;
+ },
+ onStopRequest: function (aRequest, aContext, aStatusCode)
+ {
+ LOG_C2("done reading data: " + aStatusCode);
+ do_check_eq(aStatusCode, Cr.NS_OK);
+ goon(data);
+ },
+ }, null);
+ }
+ else {
+ // blocking stream
+ var data = read_stream(inputStream, inputStream.available());
+ goon(data);
+ }
+}
+
+OpenCallback.prototype =
+{
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheEntryOpenCallback)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onCacheEntryCheck: function(entry, appCache)
+ {
+ LOG_C2(this, "onCacheEntryCheck");
+ do_check_true(!this.onCheckPassed);
+ this.onCheckPassed = true;
+
+ if (this.behavior & NOTVALID) {
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ }
+
+ if (this.behavior & NOTWANTED) {
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NOT_WANTED");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_NOT_WANTED;
+ }
+
+ do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata);
+
+ // check for sane flag combination
+ do_check_neq(this.behavior & (REVAL|PARTIAL), REVAL|PARTIAL);
+
+ if (this.behavior & (REVAL|PARTIAL)) {
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NEEDS_REVALIDATION");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_NEEDS_REVALIDATION;
+ }
+
+ if (this.behavior & COMPLETE) {
+ LOG_C2(this, "onCacheEntryCheck DONE, return RECHECK_AFTER_WRITE_FINISHED");
+ if (newCacheBackEndUsed()) {
+ // Specific to the new backend because of concurrent read/write:
+ // when a consumer returns RECHECK_AFTER_WRITE_FINISHED from onCacheEntryCheck
+ // the cache calls this callback again after the entry write has finished.
+ // This gives the consumer a chance to recheck completeness of the entry
+ // again.
+ // Thus, we reset state as onCheck would have never been called.
+ this.onCheckPassed = false;
+ // Don't return RECHECK_AFTER_WRITE_FINISHED on second call of onCacheEntryCheck.
+ this.behavior &= ~COMPLETE;
+ }
+ return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED;
+ }
+
+ LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable: function(entry, isnew, appCache, status)
+ {
+ if ((this.behavior & MAYBE_NEW) && isnew) {
+ this.behavior |= NEW;
+ }
+
+ LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
+ do_check_true(!this.onAvailPassed);
+ this.onAvailPassed = true;
+
+ do_check_eq(isnew, !!(this.behavior & NEW));
+
+ if (this.behavior & (NOTFOUND|NOTWANTED)) {
+ do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ do_check_false(!!entry);
+ if (this.behavior & THROWAVAIL)
+ this.throwAndNotify(entry);
+ this.goon(entry);
+ }
+ else if (this.behavior & (NEW|RECREATE)) {
+ do_check_true(!!entry);
+
+ if (this.behavior & RECREATE) {
+ entry = entry.recreate();
+ do_check_true(!!entry);
+ }
+
+ if (this.behavior & THROWAVAIL)
+ this.throwAndNotify(entry);
+
+ if (!(this.behavior & WAITFORWRITE))
+ this.goon(entry);
+
+ if (!(this.behavior & PARTIAL)) {
+ try {
+ entry.getMetaDataElement("meto");
+ do_check_true(false);
+ }
+ catch (ex) {}
+ }
+
+ if (this.behavior & DONTFILL) {
+ do_check_false(this.behavior & WAITFORWRITE);
+ return;
+ }
+
+ var self = this;
+ do_execute_soon(function() { // emulate network latency
+ entry.setMetaDataElement("meto", self.workingMetadata);
+ entry.metaDataReady();
+ if (self.behavior & METAONLY) {
+ // Since forcing GC/CC doesn't trigger OnWriterClosed, we have to set the entry valid manually :(
+ if (!(self.behavior & DONTSETVALID))
+ entry.setValid();
+
+ entry.close();
+ if (self.behavior & WAITFORWRITE)
+ self.goon(entry);
+
+ return;
+ }
+ do_execute_soon(function() { // emulate more network latency
+ if (self.behavior & DOOMED) {
+ LOG_C2(self, "checking doom state");
+ try {
+ var os = entry.openOutputStream(0);
+ // Unfortunately, in the undetermined state we cannot even check whether the entry
+ // is actually doomed or not.
+ os.close();
+ do_check_true(!!(self.behavior & MAYBE_NEW));
+ } catch (ex) {
+ do_check_true(true);
+ }
+ if (self.behavior & WAITFORWRITE)
+ self.goon(entry);
+ return;
+ }
+
+ var offset = (self.behavior & PARTIAL)
+ ? entry.dataSize
+ : 0;
+ LOG_C2(self, "openOutputStream @ " + offset);
+ var os = entry.openOutputStream(offset);
+ LOG_C2(self, "writing data");
+ var wrt = os.write(self.workingData, self.workingData.length);
+ do_check_eq(wrt, self.workingData.length);
+ os.close();
+ if (self.behavior & WAITFORWRITE)
+ self.goon(entry);
+
+ entry.close();
+ })
+ })
+ }
+ else { // NORMAL
+ do_check_true(!!entry);
+ do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata);
+ if (this.behavior & THROWAVAIL)
+ this.throwAndNotify(entry);
+ if (this.behavior & NOTIFYBEFOREREAD)
+ this.goon(entry, true);
+
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ var self = this;
+ pumpReadStream(entry.openInputStream(0), function(data) {
+ do_check_eq(data, self.workingData);
+ self.onDataCheckPassed = true;
+ LOG_C2(self, "entry read done");
+ self.goon(entry);
+ entry.close();
+ });
+ }
+ },
+ selfCheck: function()
+ {
+ LOG_C2(this, "selfCheck");
+
+ do_check_true(this.onCheckPassed || (this.behavior & MAYBE_NEW));
+ do_check_true(this.onAvailPassed);
+ do_check_true(this.onDataCheckPassed || (this.behavior & MAYBE_NEW));
+ },
+ throwAndNotify: function(entry)
+ {
+ LOG_C2(this, "Throwing");
+ var self = this;
+ do_execute_soon(function() {
+ LOG_C2(self, "Notifying");
+ self.goon(entry);
+ });
+ throw Cr.NS_ERROR_FAILURE;
+ }
+};
+
+function OpenCallback(behavior, workingMetadata, workingData, goon)
+{
+ this.behavior = behavior;
+ this.workingMetadata = workingMetadata;
+ this.workingData = workingData;
+ this.goon = goon;
+ this.onCheckPassed = (!!(behavior & (NEW|RECREATE)) || !workingMetadata) && !(behavior & NOTVALID);
+ this.onAvailPassed = false;
+ this.onDataCheckPassed = !!(behavior & (NEW|RECREATE|NOTWANTED)) || !workingMetadata;
+ callbacks.push(this);
+ this.order = callbacks.length;
+}
+
+VisitCallback.prototype =
+{
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheStorageVisitor)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onCacheStorageInfo: function(num, consumption)
+ {
+ LOG_C2(this, "onCacheStorageInfo: num=" + num + ", size=" + consumption);
+ do_check_eq(this.num, num);
+ if (newCacheBackEndUsed()) {
+ // Consumption is as expected only in the new backend
+ do_check_eq(this.consumption, consumption);
+ }
+ if (!this.entries)
+ this.notify();
+ },
+ onCacheEntryInfo: function(aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime)
+ {
+ var key = (aIdEnhance ? (aIdEnhance + ":") : "") + aURI.asciiSpec;
+ LOG_C2(this, "onCacheEntryInfo: key=" + key);
+
+ do_check_true(!!this.entries);
+
+ var index = this.entries.indexOf(key);
+ do_check_true(index > -1);
+
+ this.entries.splice(index, 1);
+ },
+ onCacheEntryVisitCompleted: function()
+ {
+ LOG_C2(this, "onCacheEntryVisitCompleted");
+ if (this.entries)
+ do_check_eq(this.entries.length, 0);
+ this.notify();
+ },
+ notify: function()
+ {
+ do_check_true(!!this.goon);
+ var goon = this.goon;
+ this.goon = null;
+ do_execute_soon(goon);
+ },
+ selfCheck: function()
+ {
+ do_check_true(!this.entries || !this.entries.length);
+ }
+};
+
+function VisitCallback(num, consumption, entries, goon)
+{
+ this.num = num;
+ this.consumption = consumption;
+ this.entries = entries;
+ this.goon = goon;
+ callbacks.push(this);
+ this.order = callbacks.length;
+}
+
+EvictionCallback.prototype =
+{
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheEntryDoomCallback)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onCacheEntryDoomed: function(result)
+ {
+ do_check_eq(this.expectedSuccess, result == Cr.NS_OK);
+ this.goon();
+ },
+ selfCheck: function() {}
+}
+
+function EvictionCallback(success, goon)
+{
+ this.expectedSuccess = success;
+ this.goon = goon;
+ callbacks.push(this);
+ this.order = callbacks.length;
+}
+
+MultipleCallbacks.prototype =
+{
+ fired: function()
+ {
+ if (--this.pending == 0)
+ {
+ var self = this;
+ if (this.delayed)
+ do_execute_soon(function() { self.goon(); });
+ else
+ this.goon();
+ }
+ },
+ add: function()
+ {
+ ++this.pending;
+ }
+}
+
+function MultipleCallbacks(number, goon, delayed)
+{
+ this.pending = number;
+ this.goon = goon;
+ this.delayed = delayed;
+}
+
+function wait_for_cache_index(continue_func)
+{
+ // This callback will not fire before the index is in the ready state. nsICacheStorage.exists() will
+ // no longer throw after this point.
+ get_cache_service().asyncGetDiskConsumption({
+ onNetworkCacheDiskConsumption: function() { continue_func(); },
+ QueryInterface() { return this; }
+ });
+}
+
+function finish_cache2_test()
+{
+ callbacks.forEach(function(callback, index) {
+ callback.selfCheck();
+ });
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/head_channels.js b/netwerk/test/unit/head_channels.js
new file mode 100644
index 000000000..5d7171668
--- /dev/null
+++ b/netwerk/test/unit/head_channels.js
@@ -0,0 +1,218 @@
+/**
+ * Read count bytes from stream and return as a String object
+ */
+function read_stream(stream, count) {
+ /* assume stream has non-ASCII data */
+ var wrapper =
+ Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ wrapper.setInputStream(stream);
+ /* JS methods can be called with a maximum of 65535 arguments, and input
+ streams don't have to return all the data they make .available() when
+ asked to .read() that number of bytes. */
+ var data = [];
+ while (count > 0) {
+ var bytes = wrapper.readByteArray(Math.min(65535, count));
+ data.push(String.fromCharCode.apply(null, bytes));
+ count -= bytes.length;
+ if (bytes.length == 0)
+ do_throw("Nothing read from input stream!");
+ }
+ return data.join('');
+}
+
+const CL_EXPECT_FAILURE = 0x1;
+const CL_EXPECT_GZIP = 0x2;
+const CL_EXPECT_3S_DELAY = 0x4;
+const CL_SUSPEND = 0x8;
+const CL_ALLOW_UNKNOWN_CL = 0x10;
+const CL_EXPECT_LATE_FAILURE = 0x20;
+const CL_FROM_CACHE = 0x40; // Response must be from the cache
+const CL_NOT_FROM_CACHE = 0x80; // Response must NOT be from the cache
+const CL_IGNORE_CL = 0x100; // don't bother to verify the content-length
+
+const SUSPEND_DELAY = 3000;
+
+/**
+ * A stream listener that calls a callback function with a specified
+ * context and the received data when the channel is loaded.
+ *
+ * Signature of the closure:
+ * void closure(in nsIRequest request, in ACString data, in JSObject context);
+ *
+ * This listener makes sure that various parts of the channel API are
+ * implemented correctly and that the channel's status is a success code
+ * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags
+ * to allow a failure code)
+ *
+ * Note that it also requires a valid content length on the channel and
+ * is thus not fully generic.
+ */
+function ChannelListener(closure, ctx, flags) {
+ this._closure = closure;
+ this._closurectx = ctx;
+ this._flags = flags;
+}
+ChannelListener.prototype = {
+ _closure: null,
+ _closurectx: null,
+ _buffer: "",
+ _got_onstartrequest: false,
+ _got_onstoprequest: false,
+ _contentLen: -1,
+ _lastEvent: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ try {
+ if (this._got_onstartrequest)
+ do_throw("Got second onStartRequest event!");
+ this._got_onstartrequest = true;
+ this._lastEvent = Date.now();
+
+ request.QueryInterface(Components.interfaces.nsIChannel);
+ try {
+ this._contentLen = request.contentLength;
+ }
+ catch (ex) {
+ if (!(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL)))
+ do_throw("Could not get contentLength");
+ }
+ if (!request.isPending())
+ do_throw("request reports itself as not pending from onStartRequest!");
+ if (this._contentLen == -1 && !(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL)))
+ do_throw("Content length is unknown in onStartRequest!");
+
+ if ((this._flags & CL_FROM_CACHE)) {
+ request.QueryInterface(Ci.nsICachingChannel);
+ if (!request.isFromCache()) {
+ do_throw("Response is not from the cache (CL_FROM_CACHE)");
+ }
+ }
+ if ((this._flags & CL_NOT_FROM_CACHE)) {
+ request.QueryInterface(Ci.nsICachingChannel);
+ if (request.isFromCache()) {
+ do_throw("Response is from the cache (CL_NOT_FROM_CACHE)");
+ }
+ }
+
+ if (this._flags & CL_SUSPEND) {
+ request.suspend();
+ do_timeout(SUSPEND_DELAY, function() { request.resume(); });
+ }
+
+ } catch (ex) {
+ do_throw("Error in onStartRequest: " + ex);
+ }
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ try {
+ let current = Date.now();
+
+ if (!this._got_onstartrequest)
+ do_throw("onDataAvailable without onStartRequest event!");
+ if (this._got_onstoprequest)
+ do_throw("onDataAvailable after onStopRequest event!");
+ if (!request.isPending())
+ do_throw("request reports itself as not pending from onDataAvailable!");
+ if (this._flags & CL_EXPECT_FAILURE)
+ do_throw("Got data despite expecting a failure");
+
+ if (current - this._lastEvent >= SUSPEND_DELAY &&
+ !(this._flags & CL_EXPECT_3S_DELAY))
+ do_throw("Data received after significant unexpected delay");
+ else if (current - this._lastEvent < SUSPEND_DELAY &&
+ this._flags & CL_EXPECT_3S_DELAY)
+ do_throw("Data received sooner than expected");
+ else if (current - this._lastEvent >= SUSPEND_DELAY &&
+ this._flags & CL_EXPECT_3S_DELAY)
+ this._flags &= ~CL_EXPECT_3S_DELAY; // No more delays expected
+
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ this._lastEvent = current;
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest: function(request, context, status) {
+ try {
+ var success = Components.isSuccessCode(status);
+ if (!this._got_onstartrequest)
+ do_throw("onStopRequest without onStartRequest event!");
+ if (this._got_onstoprequest)
+ do_throw("Got second onStopRequest event!");
+ this._got_onstoprequest = true;
+ if ((this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && success)
+ do_throw("Should have failed to load URL (status is " + status.toString(16) + ")");
+ else if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !success)
+ do_throw("Failed to load URL: " + status.toString(16));
+ if (status != request.status)
+ do_throw("request.status does not match status arg to onStopRequest!");
+ if (request.isPending())
+ do_throw("request reports itself as pending from onStopRequest!");
+ if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE | CL_IGNORE_CL)) &&
+ !(this._flags & CL_EXPECT_GZIP) &&
+ this._contentLen != -1)
+ do_check_eq(this._buffer.length, this._contentLen)
+ } catch (ex) {
+ do_throw("Error in onStopRequest: " + ex);
+ }
+ try {
+ this._closure(request, this._buffer, this._closurectx);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ }
+};
+
+var ES_ABORT_REDIRECT = 0x01;
+
+function ChannelEventSink(flags)
+{
+ this._flags = flags;
+}
+
+ChannelEventSink.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIInterfaceRequestor) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function(iid) {
+ if (iid.equals(Ci.nsIChannelEventSink))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
+ if (this._flags & ES_ABORT_REDIRECT)
+ throw Cr.NS_BINDING_ABORTED;
+
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+};
+
+/**
+ * A helper class to construct origin attributes.
+ */
+function OriginAttributes(appId, inIsolatedMozBrowser, privateId) {
+ this.appId = appId;
+ this.inIsolatedMozBrowser = inIsolatedMozBrowser;
+ this.privateBrowsingId = privateId;
+}
+OriginAttributes.prototype = {
+ appId: 0,
+ inIsolatedMozBrowser: false,
+ privateBrowsingId: 0
+};
diff --git a/netwerk/test/unit/socks_client_subprocess.js b/netwerk/test/unit/socks_client_subprocess.js
new file mode 100644
index 000000000..144bc8757
--- /dev/null
+++ b/netwerk/test/unit/socks_client_subprocess.js
@@ -0,0 +1,42 @@
+var CC = Components.Constructor;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+const ProtocolProxyService = CC("@mozilla.org/network/protocol-proxy-service;1",
+ "nsIProtocolProxyService");
+var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+function launchConnection(socks_vers, socks_port, dest_host, dest_port, dns)
+{
+ var pi_flags = 0;
+ if (dns == 'remote')
+ pi_flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+
+ var pps = new ProtocolProxyService();
+ var pi = pps.newProxyInfo(socks_vers, 'localhost', socks_port,
+ pi_flags, -1, null);
+ var trans = sts.createTransport(null, 0, dest_host, dest_port, pi);
+ var input = trans.openInputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
+ var output = trans.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
+ var bin = new BinaryInputStream(input);
+ var data = bin.readBytes(5);
+ if (data == 'PING!') {
+ print('client: got ping, sending pong.');
+ output.write('PONG!', 5);
+ } else {
+ print('client: wrong data from server:', data);
+ output.write('Error: wrong data received.', 27);
+ }
+ output.close();
+}
+
+for (var arg of arguments) {
+ print('client: running test', arg);
+ test = arg.split('|');
+ launchConnection(test[0], parseInt(test[1]), test[2],
+ parseInt(test[3]), test[4]);
+}
diff --git a/netwerk/test/unit/test_1073747.js b/netwerk/test/unit/test_1073747.js
new file mode 100644
index 000000000..c930514e7
--- /dev/null
+++ b/netwerk/test/unit/test_1073747.js
@@ -0,0 +1,30 @@
+// Test based on submitted one from Peter B Shalimoff
+
+var test = function(s, funcName){
+ function Arg(){};
+ Arg.prototype.toString = function(){
+ do_print("Testing " + funcName + " with null args");
+ return this.value;
+ };
+ // create a generic arg lits of null, -1, and 10 nulls
+ var args = [s, -1];
+ for (var i = 0; i < 10; ++i) {
+ args.push(new Arg());
+ }
+ var up = Components.classes["@mozilla.org/network/url-parser;1?auth=maybe"].getService(Components.interfaces.nsIURLParser);
+ try {
+ up[funcName].apply(up, args);
+ return args;
+ } catch (x) {
+ do_check_true(true); // make sure it throws an exception instead of crashing
+ return x;
+ }
+ // should always have an exception to catch
+ do_check_true(false);
+};
+var s = null;
+var funcs = ["parseAuthority", "parseFileName", "parseFilePath", "parsePath", "parseServerInfo", "parseURL", "parseUserInfo"];
+
+function run_test() {
+ funcs.forEach(function(f){test(s, f);});
+}
diff --git a/netwerk/test/unit/test_304_responses.js b/netwerk/test/unit/test_304_responses.js
new file mode 100644
index 000000000..033c337b7
--- /dev/null
+++ b/netwerk/test/unit/test_304_responses.js
@@ -0,0 +1,95 @@
+"use strict";
+// https://bugzilla.mozilla.org/show_bug.cgi?id=761228
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+const testFileName = "test_customConditionalRequest_304";
+const basePath = "/" + testFileName + "/";
+
+XPCOMUtils.defineLazyGetter(this, "baseURI", function() {
+ return URL + basePath;
+});
+
+const unexpected304 = "unexpected304";
+const existingCached304 = "existingCached304";
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function clearCache() {
+ var service = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ service.clear();
+}
+
+function alwaysReturn304Handler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ response.setHeader("Returned-From-Handler", "1");
+}
+
+function run_test() {
+ evict_cache_entries();
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(basePath + unexpected304,
+ alwaysReturn304Handler);
+ httpServer.registerPathHandler(basePath + existingCached304,
+ alwaysReturn304Handler);
+ httpServer.start(-1);
+ run_next_test();
+}
+
+function finish_test(request, buffer) {
+ httpServer.stop(do_test_finished);
+}
+
+function consume304(request, buffer) {
+ request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 304);
+ do_check_eq(request.getResponseHeader("Returned-From-Handler"), "1");
+ run_next_test();
+}
+
+// Test that we return a 304 response to the caller when we are not expecting
+// a 304 response (i.e. when the server shouldn't have sent us one).
+add_test(function test_unexpected_304() {
+ var chan = make_channel(baseURI + unexpected304);
+ chan.asyncOpen2(new ChannelListener(consume304, null));
+});
+
+// Test that we can cope with a 304 response that was (erroneously) stored in
+// the cache.
+add_test(function test_304_stored_in_cache() {
+ asyncOpenCacheEntry(
+ baseURI + existingCached304, "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ function (entryStatus, cacheEntry) {
+ cacheEntry.setMetaDataElement("request-method", "GET");
+ cacheEntry.setMetaDataElement("response-head",
+ "HTTP/1.1 304 Not Modified\r\n" +
+ "\r\n");
+ cacheEntry.metaDataReady();
+ cacheEntry.close();
+
+ var chan = make_channel(baseURI + existingCached304);
+
+ // make it a custom conditional request
+ chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.setRequestHeader("If-None-Match", '"foo"', false);
+
+ chan.asyncOpen2(new ChannelListener(consume304, null));
+ });
+});
diff --git a/netwerk/test/unit/test_307_redirect.js b/netwerk/test/unit/test_307_redirect.js
new file mode 100644
index 000000000..fbbcdf000
--- /dev/null
+++ b/netwerk/test/unit/test_307_redirect.js
@@ -0,0 +1,91 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return URL + "/redirect";
+});
+
+XPCOMUtils.defineLazyGetter(this, "noRedirectURI", function() {
+ return URL + "/content";
+});
+
+var httpserver = null;
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const requestBody = "request body";
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
+ response.setHeader("Location", noRedirectURI, false);
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.writeFrom(metadata.bodyInputStream,
+ metadata.bodyInputStream.available());
+}
+
+function noRedirectStreamObserver(request, buffer)
+{
+ do_check_eq(buffer, requestBody);
+ var chan = make_channel(uri);
+ var uploadStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ uploadStream.setData(requestBody, requestBody.length);
+ chan.QueryInterface(Ci.nsIUploadChannel).setUploadStream(uploadStream,
+ "text/plain",
+ -1);
+ chan.asyncOpen2(new ChannelListener(noHeaderStreamObserver, null));
+}
+
+function noHeaderStreamObserver(request, buffer)
+{
+ do_check_eq(buffer, requestBody);
+ var chan = make_channel(uri);
+ var uploadStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ var streamBody = "Content-Type: text/plain\r\n" +
+ "Content-Length: " + requestBody.length + "\r\n\r\n" +
+ requestBody;
+ uploadStream.setData(streamBody, streamBody.length);
+ chan.QueryInterface(Ci.nsIUploadChannel).setUploadStream(uploadStream, "", -1);
+ chan.asyncOpen2(new ChannelListener(headerStreamObserver, null));
+}
+
+function headerStreamObserver(request, buffer)
+{
+ do_check_eq(buffer, requestBody);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/redirect", redirectHandler);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ var prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.setBoolPref("network.http.prompt-temp-redirect", false);
+
+ var chan = make_channel(noRedirectURI);
+ var uploadStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ uploadStream.setData(requestBody, requestBody.length);
+ chan.QueryInterface(Ci.nsIUploadChannel).setUploadStream(uploadStream,
+ "text/plain",
+ -1);
+ chan.asyncOpen2(new ChannelListener(noRedirectStreamObserver, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_421.js b/netwerk/test/unit/test_421.js
new file mode 100644
index 000000000..7a9e07029
--- /dev/null
+++ b/netwerk/test/unit/test_421.js
@@ -0,0 +1,60 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/421";
+var httpbody = "0123456789";
+var channel;
+var ios;
+
+function run_test() {
+ setup_test();
+ do_test_pending();
+}
+
+function setup_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ channel = setupChannel(testpath);
+
+ channel.asyncOpen2(new ChannelListener(checkRequestResponse, channel));
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({uri: URL + path, loadUsingSystemPrincipal: true});
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+var iters = 0;
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+
+ if (!iters) {
+ response.setStatusLine("1.1", 421, "Not Authoritative " + iters);
+ } else {
+ response.setStatusLine("1.1", 200, "OK");
+ }
+ ++iters;
+
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function checkRequestResponse(request, data, context) {
+ do_check_eq(channel.responseStatus, 200);
+ do_check_eq(channel.responseStatusText, "OK");
+ do_check_true(channel.requestSucceeded);
+
+ do_check_eq(channel.contentType, "text/plain");
+ do_check_eq(channel.contentLength, httpbody.length);
+ do_check_eq(data, httpbody);
+
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_MIME_params.js b/netwerk/test/unit/test_MIME_params.js
new file mode 100644
index 000000000..2c46a061c
--- /dev/null
+++ b/netwerk/test/unit/test_MIME_params.js
@@ -0,0 +1,560 @@
+/**
+ * Tests for parsing header fields using the syntax used in
+ * Content-Disposition and Content-Type
+ *
+ * See also https://bugzilla.mozilla.org/show_bug.cgi?id=609667
+ */
+
+var BS = '\\';
+var DQUOTE = '"';
+
+// Test array:
+// - element 0: "Content-Disposition" header to test
+// under MIME (email):
+// - element 1: correct value returned for disposition-type (empty param name)
+// - element 2: correct value for filename returned
+// under HTTP:
+// (currently supports continuations; expected results without continuations
+// are commented out for now)
+// - element 3: correct value returned for disposition-type (empty param name)
+// - element 4: correct value for filename returned
+//
+// 3 and 4 may be left out if they are identical
+
+var tests = [
+ // No filename parameter: return nothing
+ ["attachment;",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // basic
+ ["attachment; filename=basic",
+ "attachment", "basic"],
+
+ // extended
+ ["attachment; filename*=UTF-8''extended",
+ "attachment", "extended"],
+
+ // prefer extended to basic (bug 588781)
+ ["attachment; filename=basic; filename*=UTF-8''extended",
+ "attachment", "extended"],
+
+ // prefer extended to basic (bug 588781)
+ ["attachment; filename*=UTF-8''extended; filename=basic",
+ "attachment", "extended"],
+
+ // use first basic value (invalid; error recovery)
+ ["attachment; filename=first; filename=wrong",
+ "attachment", "first"],
+
+ // old school bad HTTP servers: missing 'attachment' or 'inline'
+ // (invalid; error recovery)
+ ["filename=old",
+ "filename=old", "old"],
+
+ ["attachment; filename*=UTF-8''extended",
+ "attachment", "extended"],
+
+ // continuations not part of RFC 5987 (bug 610054)
+ ["attachment; filename*0=foo; filename*1=bar",
+ "attachment", "foobar",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */],
+
+ // Return first continuation (invalid; error recovery)
+ ["attachment; filename*0=first; filename*0=wrong; filename=basic",
+ "attachment", "first",
+ /* "attachment", "basic" */],
+
+ // Only use correctly ordered continuations (invalid; error recovery)
+ ["attachment; filename*0=first; filename*1=second; filename*0=wrong",
+ "attachment", "firstsecond",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */],
+
+ // prefer continuation to basic (unless RFC 5987)
+ ["attachment; filename=basic; filename*0=foo; filename*1=bar",
+ "attachment", "foobar",
+ /* "attachment", "basic" */],
+
+ // Prefer extended to basic and/or (broken or not) continuation
+ // (invalid; error recovery)
+ ["attachment; filename=basic; filename*0=first; filename*0=wrong; filename*=UTF-8''extended",
+ "attachment", "extended"],
+
+ // RFC 2231 not clear on correct outcome: we prefer non-continued extended
+ // (invalid; error recovery)
+ ["attachment; filename=basic; filename*=UTF-8''extended; filename*0=foo; filename*1=bar",
+ "attachment", "extended"],
+
+ // Gaps should result in returning only value until gap hit
+ // (invalid; error recovery)
+ ["attachment; filename*0=foo; filename*2=bar",
+ "attachment", "foo",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */],
+
+ // Don't allow leading 0's (*01) (invalid; error recovery)
+ ["attachment; filename*0=foo; filename*01=bar",
+ "attachment", "foo",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */],
+
+ // continuations should prevail over non-extended (unless RFC 5987)
+ ["attachment; filename=basic; filename*0*=UTF-8''multi;\r\n"
+ + " filename*1=line;\r\n"
+ + " filename*2*=%20extended",
+ "attachment", "multiline extended",
+ /* "attachment", "basic" */],
+
+ // Gaps should result in returning only value until gap hit
+ // (invalid; error recovery)
+ ["attachment; filename=basic; filename*0*=UTF-8''multi;\r\n"
+ + " filename*1=line;\r\n"
+ + " filename*3*=%20extended",
+ "attachment", "multiline",
+ /* "attachment", "basic" */],
+
+ // First series, only please, and don't slurp up higher elements (*2 in this
+ // case) from later series into earlier one (invalid; error recovery)
+ ["attachment; filename=basic; filename*0*=UTF-8''multi;\r\n"
+ + " filename*1=line;\r\n"
+ + " filename*0*=UTF-8''wrong;\r\n"
+ + " filename*1=bad;\r\n"
+ + " filename*2=evil",
+ "attachment", "multiline",
+ /* "attachment", "basic" */],
+
+ // RFC 2231 not clear on correct outcome: we prefer non-continued extended
+ // (invalid; error recovery)
+ ["attachment; filename=basic; filename*0=UTF-8''multi\r\n;"
+ + " filename*=UTF-8''extended;\r\n"
+ + " filename*1=line;\r\n"
+ + " filename*2*=%20extended",
+ "attachment", "extended"],
+
+ // sneaky: if unescaped, make sure we leave UTF-8'' in value
+ ["attachment; filename*0=UTF-8''unescaped;\r\n"
+ + " filename*1*=%20so%20includes%20UTF-8''%20in%20value",
+ "attachment", "UTF-8''unescaped so includes UTF-8'' in value",
+ /* "attachment", Cr.NS_ERROR_INVALID_ARG */],
+
+ // sneaky: if unescaped, make sure we leave UTF-8'' in value
+ ["attachment; filename=basic; filename*0=UTF-8''unescaped;\r\n"
+ + " filename*1*=%20so%20includes%20UTF-8''%20in%20value",
+ "attachment", "UTF-8''unescaped so includes UTF-8'' in value",
+ /* "attachment", "basic" */],
+
+ // Prefer basic over invalid continuation
+ // (invalid; error recovery)
+ ["attachment; filename=basic; filename*1=multi;\r\n"
+ + " filename*2=line;\r\n"
+ + " filename*3*=%20extended",
+ "attachment", "basic"],
+
+ // support digits over 10
+ ["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ + " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n"
+ + " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n"
+ + " filename*11=b; filename*12=c;filename*13=d;filename*14=e;filename*15=f\r\n",
+ "attachment", "0123456789abcdef",
+ /* "attachment", "basic" */],
+
+ // support digits over 10 (detect gaps)
+ ["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ + " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n"
+ + " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n"
+ + " filename*11=b; filename*12=c;filename*14=e\r\n",
+ "attachment", "0123456789abc",
+ /* "attachment", "basic" */],
+
+ // return nothing: invalid
+ // (invalid; error recovery)
+ ["attachment; filename*1=multi;\r\n"
+ + " filename*2=line;\r\n"
+ + " filename*3*=%20extended",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // Bug 272541: Empty disposition type treated as "attachment"
+
+ // sanity check
+ ["attachment; filename=foo.html",
+ "attachment", "foo.html",
+ "attachment", "foo.html"],
+
+ // the actual bug
+ ["; filename=foo.html",
+ Cr.NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY, "foo.html",
+ Cr.NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY, "foo.html"],
+
+ // regression check, but see bug 671204
+ ["filename=foo.html",
+ "filename=foo.html", "foo.html",
+ "filename=foo.html", "foo.html"],
+
+ // Bug 384571: RFC 2231 parameters not decoded when appearing in reversed order
+
+ // check ordering
+ ["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ + " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n"
+ + " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n"
+ + " filename*11=b; filename*12=c;filename*13=d;filename*15=f;filename*14=e;\r\n",
+ "attachment", "0123456789abcdef",
+ /* "attachment", "basic" */],
+
+ // check non-digits in sequence numbers
+ ["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ + " filename*1a=1\r\n",
+ "attachment", "0",
+ /* "attachment", "basic" */],
+
+ // check duplicate sequence numbers
+ ["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ + " filename*0=bad; filename*1=1;\r\n",
+ "attachment", "0",
+ /* "attachment", "basic" */],
+
+ // check overflow
+ ["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ + " filename*11111111111111111111111111111111111111111111111111111111111=1",
+ "attachment", "0",
+ /* "attachment", "basic" */],
+
+ // check underflow
+ ["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ + " filename*-1=1",
+ "attachment", "0",
+ /* "attachment", "basic" */],
+
+ // check mixed token/quoted-string
+ ["attachment; filename=basic; filename*0=\"0\";\r\n"
+ + " filename*1=1;\r\n"
+ + " filename*2*=%32",
+ "attachment", "012",
+ /* "attachment", "basic" */],
+
+ // check empty sequence number
+ ["attachment; filename=basic; filename**=UTF-8''0\r\n",
+ "attachment", "basic",
+ "attachment", "basic"],
+
+
+ // Bug 419157: ensure that a MIME parameter with no charset information
+ // fallbacks to Latin-1
+
+ ["attachment;filename=IT839\x04\xB5(m8)2.pdf;",
+ "attachment", "IT839\u0004\u00b5(m8)2.pdf"],
+
+ // Bug 588389: unescaping backslashes in quoted string parameters
+
+ // '\"', should be parsed as '"'
+ ["attachment; filename=" + DQUOTE + (BS + DQUOTE) + DQUOTE,
+ "attachment", DQUOTE],
+
+ // 'a\"b', should be parsed as 'a"b'
+ ["attachment; filename=" + DQUOTE + 'a' + (BS + DQUOTE) + 'b' + DQUOTE,
+ "attachment", "a" + DQUOTE + "b"],
+
+ // '\x', should be parsed as 'x'
+ ["attachment; filename=" + DQUOTE + (BS + "x") + DQUOTE,
+ "attachment", "x"],
+
+ // test empty param (quoted-string)
+ ["attachment; filename=" + DQUOTE + DQUOTE,
+ "attachment", ""],
+
+ // test empty param
+ ["attachment; filename=",
+ "attachment", ""],
+
+ // Bug 601933: RFC 2047 does not apply to parameters (at least in HTTP)
+ ["attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=",
+ "attachment", "foo-\u00e4.html",
+ /* "attachment", "=?ISO-8859-1?Q?foo-=E4.html?=" */],
+
+ ["attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"",
+ "attachment", "foo-\u00e4.html",
+ /* "attachment", "=?ISO-8859-1?Q?foo-=E4.html?=" */],
+
+ // format sent by GMail as of 2012-07-23 (5987 overrides 2047)
+ ["attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"; filename*=UTF-8''5987",
+ "attachment", "5987"],
+
+ // Bug 651185: double quotes around 2231/5987 encoded param
+ // Change reverted to backwards compat issues with various web services,
+ // such as OWA (Bug 703015), plus similar problems in Thunderbird. If this
+ // is tried again in the future, email probably needs to be special-cased.
+
+ // sanity check
+ ["attachment; filename*=utf-8''%41",
+ "attachment", "A"],
+
+ // the actual bug
+ ["attachment; filename*=" + DQUOTE + "utf-8''%41" + DQUOTE,
+ "attachment", "A"],
+ // previously with the fix for 651185:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // Bug 670333: Content-Disposition parser does not require presence of "="
+ // in params
+
+ // sanity check
+ ["attachment; filename*=UTF-8''foo-%41.html",
+ "attachment", "foo-A.html"],
+
+ // the actual bug
+ ["attachment; filename *=UTF-8''foo-%41.html",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // the actual bug, without 2231/5987 encoding
+ ["attachment; filename X",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // sanity check with WS on both sides
+ ["attachment; filename = foo-A.html",
+ "attachment", "foo-A.html"],
+
+ // Bug 685192: in RFC2231/5987 encoding, a missing charset field should be
+ // treated as error
+
+ // the actual bug
+ ["attachment; filename*=''foo",
+ "attachment", "foo"],
+ // previously with the fix for 692574:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // sanity check
+ ["attachment; filename*=a''foo",
+ "attachment", "foo"],
+
+ // Bug 692574: RFC2231/5987 decoding should not tolerate missing single
+ // quotes
+
+ // one missing
+ ["attachment; filename*=UTF-8'foo-%41.html",
+ "attachment", "foo-A.html"],
+ // previously with the fix for 692574:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // both missing
+ ["attachment; filename*=foo-%41.html",
+ "attachment","foo-A.html"],
+ // previously with the fix for 692574:
+ // "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // make sure fallback works
+ ["attachment; filename*=UTF-8'foo-%41.html; filename=bar.html",
+ "attachment", "foo-A.html"],
+ // previously with the fix for 692574:
+ // "attachment", "bar.html"],
+
+ // Bug 693806: RFC2231/5987 encoding: charset information should be treated
+ // as authoritative
+
+ // UTF-8 labeled ISO-8859-1
+ ["attachment; filename*=ISO-8859-1''%c3%a4",
+ "attachment", "\u00c3\u00a4"],
+
+ // UTF-8 labeled ISO-8859-1, but with octets not allowed in ISO-8859-1
+ // accepts x82, understands it as Win1252, maps it to Unicode \u20a1
+ ["attachment; filename*=ISO-8859-1''%e2%82%ac",
+ "attachment", "\u00e2\u201a\u00ac"],
+
+ // defective UTF-8
+ ["attachment; filename*=UTF-8''A%e4B",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // defective UTF-8, with fallback
+ ["attachment; filename*=UTF-8''A%e4B; filename=fallback",
+ "attachment", "fallback"],
+
+ // defective UTF-8 (continuations), with fallback
+ ["attachment; filename*0*=UTF-8''A%e4B; filename=fallback",
+ "attachment", "fallback"],
+
+ // check that charsets aren't mixed up
+ ["attachment; filename*0*=ISO-8859-15''euro-sign%3d%a4; filename*=ISO-8859-1''currency-sign%3d%a4",
+ "attachment", "currency-sign=\u00a4"],
+
+ // same as above, except reversed
+ ["attachment; filename*=ISO-8859-1''currency-sign%3d%a4; filename*0*=ISO-8859-15''euro-sign%3d%a4",
+ "attachment", "currency-sign=\u00a4"],
+
+ // Bug 704989: add workaround for broken Outlook Web App (OWA)
+ // attachment handling
+
+ ["attachment; filename*=\"a%20b\"",
+ "attachment", "a b"],
+
+ // Bug 717121: crash nsMIMEHeaderParamImpl::DoParameterInternal
+
+ ["attachment; filename=\"",
+ "attachment", ""],
+
+ // We used to read past string if last param w/o = and ;
+ // Note: was only detected on windows PGO builds
+ ["attachment; filename=foo; trouble",
+ "attachment", "foo"],
+
+ // Same, followed by space, hits another case
+ ["attachment; filename=foo; trouble ",
+ "attachment", "foo"],
+
+ ["attachment",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // Bug 730574: quoted-string in RFC2231-continuations not handled
+
+ ['attachment; filename=basic; filename*0="foo"; filename*1="\\b\\a\\r.html"',
+ "attachment", "foobar.html",
+ /* "attachment", "basic" */],
+
+ // unmatched escape char
+ ['attachment; filename=basic; filename*0="foo"; filename*1="\\b\\a\\',
+ "attachment", "fooba\\",
+ /* "attachment", "basic" */],
+
+ // Bug 732369: Content-Disposition parser does not require presence of ";" between params
+ // optimally, this would not even return the disposition type "attachment"
+
+ ["attachment; extension=bla filename=foo",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ ["attachment; filename=foo extension=bla",
+ "attachment", "foo"],
+
+ ["attachment filename=foo",
+ "attachment", Cr.NS_ERROR_INVALID_ARG],
+
+ // Bug 777687: handling of broken %escapes
+
+ ["attachment; filename*=UTF-8''f%oo; filename=bar",
+ "attachment", "bar"],
+
+ ["attachment; filename*=UTF-8''foo%; filename=bar",
+ "attachment", "bar"],
+
+ // Bug 783502 - xpcshell test netwerk/test/unit/test_MIME_params.js fails on AddressSanitizer
+ ['attachment; filename="\\b\\a\\',
+ "attachment", "ba\\"],
+];
+
+var rfc5987paramtests = [
+ [ // basic test
+ "UTF-8'language'value", "value", "language", Cr.NS_OK ],
+ [ // percent decoding
+ "UTF-8''1%202", "1 2", "", Cr.NS_OK ],
+ [ // UTF-8
+ "UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", "\u00a3 and \u20ac rates", "", Cr.NS_OK ],
+ [ // missing charset
+ "''abc", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // ISO-8859-1: unsupported
+ "ISO-8859-1''%A3%20rates", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // unknown charset
+ "foo''abc", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // missing component
+ "abc", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // missing component
+ "'abc", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // illegal chars
+ "UTF-8''a b", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // broken % escapes
+ "UTF-8''a%zz", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // broken % escapes
+ "UTF-8''a%b", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // broken % escapes
+ "UTF-8''a%", "", "", Cr.NS_ERROR_INVALID_ARG ],
+ [ // broken UTF-8
+ "UTF-8''%A3%20rates", "", "", 0x8050000E /* NS_ERROR_UDEC_ILLEGALINPUT */ ],
+];
+
+function do_tests(whichRFC)
+{
+ var mhp = Components.classes["@mozilla.org/network/mime-hdrparam;1"]
+ .getService(Components.interfaces.nsIMIMEHeaderParam);
+
+ var unused = { value : null };
+
+ for (var i = 0; i < tests.length; ++i) {
+ dump("Testing #" + i + ": " + tests[i] + "\n");
+
+ // check disposition type
+ var expectedDt = tests[i].length == 3 || whichRFC == 0 ? tests[i][1] : tests[i][3];
+
+ try {
+ var result;
+
+ if (whichRFC == 0)
+ result = mhp.getParameter(tests[i][0], "", "UTF-8", true, unused);
+ else
+ result = mhp.getParameterHTTP(tests[i][0], "", "UTF-8", true, unused);
+
+ do_check_eq(result, expectedDt);
+ }
+ catch (e) {
+ // Tests can also succeed by expecting to fail with given error code
+ if (e.result) {
+ // Allow following tests to run by catching exception from do_check_eq()
+ try {
+ do_check_eq(e.result, expectedDt);
+ } catch(e) {}
+ }
+ continue;
+ }
+
+ // check filename parameter
+ var expectedFn = tests[i].length == 3 || whichRFC == 0 ? tests[i][2] : tests[i][4];
+
+ try {
+ var result;
+
+ if (whichRFC == 0)
+ result = mhp.getParameter(tests[i][0], "filename", "UTF-8", true, unused);
+ else
+ result = mhp.getParameterHTTP(tests[i][0], "filename", "UTF-8", true, unused);
+
+ do_check_eq(result, expectedFn);
+ }
+ catch (e) {
+ // Tests can also succeed by expecting to fail with given error code
+ if (e.result) {
+ // Allow following tests to run by catching exception from do_check_eq()
+ try {
+ do_check_eq(e.result, expectedFn);
+ } catch(e) {}
+ }
+ continue;
+ }
+ }
+}
+
+function test_decode5987Param() {
+ var mhp = Components.classes["@mozilla.org/network/mime-hdrparam;1"]
+ .getService(Components.interfaces.nsIMIMEHeaderParam);
+
+ for (var i = 0; i < rfc5987paramtests.length; ++i) {
+ dump("Testing #" + i + ": " + rfc5987paramtests[i] + "\n");
+
+ var lang = {};
+ try {
+ var decoded = mhp.decodeRFC5987Param(rfc5987paramtests[i][0], lang);
+ if (rfc5987paramtests[i][3] == Cr.NS_OK) {
+ do_check_eq(rfc5987paramtests[i][1], decoded);
+ do_check_eq(rfc5987paramtests[i][2], lang.value);
+ }
+ else {
+ do_check_eq(rfc5987paramtests[i][3], "instead got: " + decoded);
+ }
+ }
+ catch (e) {
+ do_check_eq(rfc5987paramtests[i][3], e.result);
+ }
+ }
+}
+
+function run_test() {
+
+ // Test RFC 2231 (complete header field values)
+ do_tests(0);
+
+ // Test RFC 5987 (complete header field values)
+ do_tests(1);
+
+ // tests for RFC5987 parameter parsing
+ test_decode5987Param();
+}
diff --git a/netwerk/test/unit/test_NetUtil.js b/netwerk/test/unit/test_NetUtil.js
new file mode 100644
index 000000000..7f446ecc4
--- /dev/null
+++ b/netwerk/test/unit/test_NetUtil.js
@@ -0,0 +1,867 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file tests the methods on NetUtil.jsm.
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// We need the profile directory so the test harness will clean up our test
+// files.
+do_get_profile();
+
+const OUTPUT_STREAM_CONTRACT_ID = "@mozilla.org/network/file-output-stream;1";
+const SAFE_OUTPUT_STREAM_CONTRACT_ID = "@mozilla.org/network/safe-file-output-stream;1";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helper Methods
+
+/**
+ * Reads the contents of a file and returns it as a string.
+ *
+ * @param aFile
+ * The file to return from.
+ * @return the contents of the file in the form of a string.
+ */
+function getFileContents(aFile)
+{
+ "use strict";
+
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(aFile, -1, 0, 0);
+
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let string = {};
+ cstream.readString(-1, string);
+ cstream.close();
+ return string.value;
+}
+
+
+/**
+ * Tests asynchronously writing a file using NetUtil.asyncCopy.
+ *
+ * @param aContractId
+ * The contract ID to use for the output stream
+ * @param aDeferOpen
+ * Whether to use DEFER_OPEN in the output stream.
+ */
+function async_write_file(aContractId, aDeferOpen)
+{
+ do_test_pending();
+
+ // First, we need an output file to write to.
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-async-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ // Then, we need an output stream to our output file.
+ let ostream = Cc[aContractId].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0);
+
+ // Finally, we need an input stream to take data from.
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ NetUtil.asyncCopy(istream, ostream, function(aResult) {
+ // Make sure the copy was successful!
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check the file contents.
+ do_check_eq(TEST_DATA, getFileContents(file));
+
+ // Finish the test.
+ do_test_finished();
+ run_next_test();
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+// Test NetUtil.asyncCopy for all possible buffering scenarios
+function test_async_copy()
+{
+ // Create a data sample
+ function make_sample(text) {
+ let data = [];
+ for (let i = 0; i <= 100; ++i) {
+ data.push(text);
+ }
+ return data.join();
+ }
+
+ // Create an input buffer holding some data
+ function make_input(isBuffered, data) {
+ if (isBuffered) {
+ // String input streams are buffered
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ istream.setData(data, data.length);
+ return istream;
+ }
+
+ // File input streams are not buffered, so let's create a file
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-asyncFetch-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, 0);
+ ostream.write(data, data.length);
+ ostream.close();
+
+ let istream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ istream.init(file, -1, 0, 0);
+
+ return istream;
+ }
+
+ // Create an output buffer holding some data
+ function make_output(isBuffered) {
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-asyncFetch-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, 0);
+
+ if (!isBuffered) {
+ return {file: file, sink: ostream};
+ }
+
+ let bstream = Cc["@mozilla.org/network/buffered-output-stream;1"].
+ createInstance(Ci.nsIBufferedOutputStream);
+ bstream.init(ostream, 256);
+ return {file: file, sink: bstream};
+ }
+ Task.spawn(function*() {
+ do_test_pending();
+ for (let bufferedInput of [true, false]) {
+ for (let bufferedOutput of [true, false]) {
+ let text = "test_async_copy with "
+ + (bufferedInput?"buffered input":"unbuffered input")
+ + ", "
+ + (bufferedOutput?"buffered output":"unbuffered output");
+ do_print(text);
+ let TEST_DATA = "[" + make_sample(text) + "]";
+ let source = make_input(bufferedInput, TEST_DATA);
+ let {file, sink} = make_output(bufferedOutput);
+ let deferred = Promise.defer();
+ NetUtil.asyncCopy(source, sink, deferred.resolve);
+ let result = yield deferred.promise;
+
+ // Make sure the copy was successful!
+ if (!Components.isSuccessCode(result)) {
+ do_throw(new Components.Exception("asyncCopy error", result));
+ }
+
+ // Check the file contents.
+ do_check_eq(TEST_DATA, getFileContents(file));
+ }
+ }
+
+ do_test_finished();
+ run_next_test();
+ });
+}
+
+function test_async_write_file() {
+ async_write_file(OUTPUT_STREAM_CONTRACT_ID);
+}
+
+function test_async_write_file_deferred() {
+ async_write_file(OUTPUT_STREAM_CONTRACT_ID, true);
+}
+
+function test_async_write_file_safe() {
+ async_write_file(SAFE_OUTPUT_STREAM_CONTRACT_ID);
+}
+
+function test_async_write_file_safe_deferred() {
+ async_write_file(SAFE_OUTPUT_STREAM_CONTRACT_ID, true);
+}
+
+function test_newURI_no_spec_throws()
+{
+ try {
+ NetUtil.newURI();
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_newURI()
+{
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ // Check that we get the same URI back from the IO service and the utility
+ // method.
+ const TEST_URI = "http://mozilla.org";
+ let iosURI = ios.newURI(TEST_URI, null, null);
+ let NetUtilURI = NetUtil.newURI(TEST_URI);
+ do_check_true(iosURI.equals(NetUtilURI));
+
+ run_next_test();
+}
+
+function test_newURI_takes_nsIFile()
+{
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ // Create a test file that we can pass into NetUtil.newURI
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-test-file.tmp");
+
+ // Check that we get the same URI back from the IO service and the utility
+ // method.
+ let iosURI = ios.newFileURI(file);
+ let NetUtilURI = NetUtil.newURI(file);
+ do_check_true(iosURI.equals(NetUtilURI));
+
+ run_next_test();
+}
+
+function test_ioService()
+{
+ do_check_true(NetUtil.ioService instanceof Ci.nsIIOService);
+ run_next_test();
+}
+
+function test_asyncFetch_no_channel()
+{
+ try {
+ NetUtil.asyncFetch(null, function() { });
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_asyncFetch_no_callback()
+{
+ try {
+ NetUtil.asyncFetch({ });
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_asyncFetch_with_nsIChannel()
+{
+ const TEST_DATA = "this is a test string";
+
+ // Start the http server, and register our handler.
+ let server = new HttpServer();
+ server.registerPathHandler("/test", function(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain", false);
+ aResponse.write(TEST_DATA);
+ });
+ server.start(-1);
+
+ // Create our channel.
+ let channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/test",
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Open our channel asynchronously.
+ NetUtil.asyncFetch(channel, function(aInputStream, aResult) {
+ // Check that we had success.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ do_check_eq(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ });
+}
+
+function test_asyncFetch_with_nsIURI()
+{
+ const TEST_DATA = "this is a test string";
+
+ // Start the http server, and register our handler.
+ let server = new HttpServer();
+ server.registerPathHandler("/test", function(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain", false);
+ aResponse.write(TEST_DATA);
+ });
+ server.start(-1);
+
+ // Create our URI.
+ let uri = NetUtil.newURI("http://localhost:" +
+ server.identity.primaryPort + "/test");
+
+ // Open our URI asynchronously.
+ NetUtil.asyncFetch({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }, function(aInputStream, aResult) {
+ // Check that we had success.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ do_check_eq(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+}
+
+function test_asyncFetch_with_string()
+{
+ const TEST_DATA = "this is a test string";
+
+ // Start the http server, and register our handler.
+ let server = new HttpServer();
+ server.registerPathHandler("/test", function(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
+ aResponse.setHeader("Content-Type", "text/plain", false);
+ aResponse.write(TEST_DATA);
+ });
+ server.start(-1);
+
+ // Open our location asynchronously.
+ NetUtil.asyncFetch({
+ uri: "http://localhost:" + server.identity.primaryPort + "/test",
+ loadUsingSystemPrincipal: true,
+ }, function(aInputStream, aResult) {
+ // Check that we had success.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ do_check_eq(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+}
+
+function test_asyncFetch_with_nsIFile()
+{
+ const TEST_DATA = "this is a test string";
+
+ // First we need a file to read from.
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-asyncFetch-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ // Write the test data to the file.
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, 0);
+ ostream.write(TEST_DATA, TEST_DATA.length);
+
+ // Sanity check to make sure the data was written.
+ do_check_eq(TEST_DATA, getFileContents(file));
+
+ // Open our file asynchronously.
+ // Note that this causes main-tread I/O and should be avoided in production.
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(file),
+ loadUsingSystemPrincipal: true,
+ }, function(aInputStream, aResult) {
+ // Check that we had success.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(aInputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ is.init(aInputStream);
+ let result = is.read(TEST_DATA.length);
+ do_check_eq(TEST_DATA, result);
+
+ run_next_test();
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+}
+
+function test_asyncFetch_with_nsIInputString()
+{
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ // Read the input stream asynchronously.
+ NetUtil.asyncFetch(istream, function(aInputStream, aResult) {
+ // Check that we had success.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(aInputStream.available(), TEST_DATA.length);
+ do_check_eq(NetUtil.readInputStreamToString(aInputStream, TEST_DATA.length),
+ TEST_DATA);
+
+ run_next_test();
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+}
+
+function test_asyncFetch_does_not_block()
+{
+ // Create our channel that has no data.
+ let channel = NetUtil.newChannel({
+ uri: "data:text/plain,",
+ loadUsingSystemPrincipal: true,
+ });
+
+ // Open our channel asynchronously.
+ NetUtil.asyncFetch(channel, function(aInputStream, aResult) {
+ // Check that we had success.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that reading a byte throws that the stream was closed (as opposed
+ // saying it would block).
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ is.init(aInputStream);
+ try {
+ is.read(1);
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_BASE_STREAM_CLOSED);
+ }
+
+ run_next_test();
+ });
+}
+
+function test_newChannel_no_specifier()
+{
+ try {
+ NetUtil.newChannel();
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_newChannel_with_string()
+{
+ const TEST_SPEC = "http://mozilla.org";
+
+ // Check that we get the same URI back from channel the IO service creates and
+ // the channel the utility method creates.
+ let ios = NetUtil.ioService;
+ let iosChannel = ios.newChannel2(TEST_SPEC,
+ null,
+ null,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let NetUtilChannel = NetUtil.newChannel({
+ uri: TEST_SPEC,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_true(iosChannel.URI.equals(NetUtilChannel.URI));
+
+ run_next_test();
+}
+
+function test_newChannel_with_nsIURI()
+{
+ const TEST_SPEC = "http://mozilla.org";
+
+ // Check that we get the same URI back from channel the IO service creates and
+ // the channel the utility method creates.
+ let uri = NetUtil.newURI(TEST_SPEC);
+ let iosChannel = NetUtil.ioService.newChannelFromURI2(uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let NetUtilChannel = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_true(iosChannel.URI.equals(NetUtilChannel.URI));
+
+ run_next_test();
+}
+
+function test_newChannel_with_options()
+{
+ let uri = "data:text/plain,";
+
+ let iosChannel = NetUtil.ioService.newChannelFromURI2(NetUtil.newURI(uri),
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+
+ function checkEqualToIOSChannel(channel) {
+ do_check_true(iosChannel.URI.equals(channel.URI));
+ }
+
+ checkEqualToIOSChannel(NetUtil.newChannel({
+ uri,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ }));
+
+ checkEqualToIOSChannel(NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }));
+
+ run_next_test();
+}
+
+function test_newChannel_with_wrong_options()
+{
+ let uri = "data:text/plain,";
+ let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+ Assert.throws(() => {
+ NetUtil.newChannel({ uri, loadUsingSystemPrincipal: true }, null, null);
+ }, /requires a single object argument/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({});
+ }, /requires the 'uri' property/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({ uri });
+ }, /requires at least one of the 'loadingNode'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({
+ uri,
+ loadingPrincipal: systemPrincipal,
+ });
+ }, /requires the 'contentPolicyType'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: systemPrincipal,
+ });
+ }, /to be 'true' or 'undefined'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({
+ uri,
+ loadingPrincipal: systemPrincipal,
+ loadUsingSystemPrincipal: true,
+ });
+ }, /does not accept 'loadUsingSystemPrincipal'/);
+
+ run_next_test();
+}
+
+function test_deprecated_newChannel_API_with_string() {
+ const TEST_SPEC = "http://mozilla.org";
+ let uri = NetUtil.newURI(TEST_SPEC);
+ let oneArgChannel = NetUtil.newChannel(TEST_SPEC);
+ let threeArgChannel = NetUtil.newChannel(TEST_SPEC, null, null);
+ do_check_true(uri.equals(oneArgChannel.URI));
+ do_check_true(uri.equals(threeArgChannel.URI));
+
+ run_next_test();
+}
+
+function test_deprecated_newChannel_API_with_nsIFile()
+{
+ const TEST_DATA = "this is a test string";
+
+ // First we need a file to read from.
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("NetUtil-deprecated-newchannel-api-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ // Write the test data to the file.
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, 0);
+ ostream.write(TEST_DATA, TEST_DATA.length);
+
+ // Sanity check to make sure the data was written.
+ do_check_eq(TEST_DATA, getFileContents(file));
+
+ // create a channel using the file
+ let channel = NetUtil.newChannel(file);
+
+ // Create a pipe that will create our output stream that we can use once
+ // we have gotten all the data.
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, 0, null);
+
+ let listener = Cc["@mozilla.org/network/simple-stream-listener;1"].
+ createInstance(Ci.nsISimpleStreamListener);
+ listener.init(pipe.outputStream, {
+ onStartRequest: function(aRequest, aContext) {},
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ pipe.outputStream.close();
+ do_check_true(Components.isSuccessCode(aContext));
+
+ // Check that we got the right data.
+ do_check_eq(pipe.inputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ is.init(pipe.inputStream);
+ let result = is.read(TEST_DATA.length);
+ do_check_eq(TEST_DATA, result);
+ run_next_test();
+ }
+ });
+ channel.asyncOpen2(listener);
+}
+
+function test_readInputStreamToString()
+{
+ const TEST_DATA = "this is a test string\0 with an embedded null";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsISupportsCString);
+ istream.data = TEST_DATA;
+
+ do_check_eq(NetUtil.readInputStreamToString(istream, TEST_DATA.length),
+ TEST_DATA);
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_no_input_stream()
+{
+ try {
+ NetUtil.readInputStreamToString("hi", 2);
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_no_bytes_arg()
+{
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ try {
+ NetUtil.readInputStreamToString(istream);
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_blocking_stream()
+{
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, 0, null);
+
+ try {
+ NetUtil.readInputStreamToString(pipe.inputStream, 10);
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_BASE_STREAM_WOULD_BLOCK);
+ }
+ run_next_test();
+}
+
+function test_readInputStreamToString_too_many_bytes()
+{
+ const TEST_DATA = "this is a test string";
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ istream.setData(TEST_DATA, TEST_DATA.length);
+
+ try {
+ NetUtil.readInputStreamToString(istream, TEST_DATA.length + 10);
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_FAILURE);
+ }
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_with_charset()
+{
+ const TEST_DATA = "\uff10\uff11\uff12\uff13";
+ const TEST_DATA_UTF8 = "\xef\xbc\x90\xef\xbc\x91\xef\xbc\x92\xef\xbc\x93";
+ const TEST_DATA_SJIS = "\x82\x4f\x82\x50\x82\x51\x82\x52";
+
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+
+ istream.setData(TEST_DATA_UTF8, TEST_DATA_UTF8.length);
+ do_check_eq(NetUtil.readInputStreamToString(istream,
+ TEST_DATA_UTF8.length,
+ { charset: "UTF-8"}),
+ TEST_DATA);
+
+ istream.setData(TEST_DATA_SJIS, TEST_DATA_SJIS.length);
+ do_check_eq(NetUtil.readInputStreamToString(istream,
+ TEST_DATA_SJIS.length,
+ { charset: "Shift_JIS"}),
+ TEST_DATA);
+
+ run_next_test();
+}
+
+function test_readInputStreamToString_invalid_sequence()
+{
+ const TEST_DATA = "\ufffd\ufffd\ufffd\ufffd";
+ const TEST_DATA_UTF8 = "\xaa\xaa\xaa\xaa";
+
+ let istream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+
+ istream.setData(TEST_DATA_UTF8, TEST_DATA_UTF8.length);
+ try {
+ NetUtil.readInputStreamToString(istream,
+ TEST_DATA_UTF8.length,
+ { charset: "UTF-8" });
+ do_throw("should throw!");
+ } catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_INPUT);
+ }
+
+ istream.setData(TEST_DATA_UTF8, TEST_DATA_UTF8.length);
+ do_check_eq(NetUtil.readInputStreamToString(istream,
+ TEST_DATA_UTF8.length, {
+ charset: "UTF-8",
+ replacement: Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER}),
+ TEST_DATA);
+
+ run_next_test();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Runner
+
+[
+ test_async_copy,
+ test_async_write_file,
+ test_async_write_file_deferred,
+ test_async_write_file_safe,
+ test_async_write_file_safe_deferred,
+ test_newURI_no_spec_throws,
+ test_newURI,
+ test_newURI_takes_nsIFile,
+ test_ioService,
+ test_asyncFetch_no_channel,
+ test_asyncFetch_no_callback,
+ test_asyncFetch_with_nsIChannel,
+ test_asyncFetch_with_nsIURI,
+ test_asyncFetch_with_string,
+ test_asyncFetch_with_nsIFile,
+ test_asyncFetch_with_nsIInputString,
+ test_asyncFetch_does_not_block,
+ test_newChannel_no_specifier,
+ test_newChannel_with_string,
+ test_newChannel_with_nsIURI,
+ test_newChannel_with_options,
+ test_newChannel_with_wrong_options,
+ test_deprecated_newChannel_API_with_string,
+ test_deprecated_newChannel_API_with_nsIFile,
+ test_readInputStreamToString,
+ test_readInputStreamToString_no_input_stream,
+ test_readInputStreamToString_no_bytes_arg,
+ test_readInputStreamToString_blocking_stream,
+ test_readInputStreamToString_too_many_bytes,
+ test_readInputStreamToString_with_charset,
+ test_readInputStreamToString_invalid_sequence,
+].forEach(add_test);
+var index = 0;
+
+function run_test()
+{
+ run_next_test();
+}
+
diff --git a/netwerk/test/unit/test_URIs.js b/netwerk/test/unit/test_URIs.js
new file mode 100644
index 000000000..b68c4f787
--- /dev/null
+++ b/netwerk/test/unit/test_URIs.js
@@ -0,0 +1,608 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+
+// Run by: cd objdir; make -C netwerk/test/ xpcshell-tests
+// or: cd objdir; make SOLO_FILE="test_URIs.js" -C netwerk/test/ check-one
+
+// See also test_URIs2.js.
+
+// Relevant RFCs: 1738, 1808, 2396, 3986 (newer than the code)
+// http://greenbytes.de/tech/webdav/rfc3986.html#rfc.section.5.4
+// http://greenbytes.de/tech/tc/uris/
+
+// TEST DATA
+// ---------
+var gTests = [
+ { spec: "about:blank",
+ scheme: "about",
+ prePath: "about:",
+ path: "blank",
+ ref: "",
+ nsIURL: false, nsINestedURI: true, immutable: true },
+ { spec: "about:foobar",
+ scheme: "about",
+ prePath: "about:",
+ path: "foobar",
+ ref: "",
+ nsIURL: false, nsINestedURI: false, immutable: true },
+ { spec: "chrome://foobar/somedir/somefile.xml",
+ scheme: "chrome",
+ prePath: "chrome://foobar",
+ path: "/somedir/somefile.xml",
+ ref: "",
+ nsIURL: true, nsINestedURI: false, immutable: true },
+ { spec: "data:text/html;charset=utf-8,<html></html>",
+ scheme: "data",
+ prePath: "data:",
+ path: "text/html;charset=utf-8,<html></html>",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "data:text/html;charset=utf-8,<html>\r\n\t</html>",
+ scheme: "data",
+ prePath: "data:",
+ path: "text/html;charset=utf-8,<html></html>",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "data:text/plain,hello world",
+ scheme: "data",
+ prePath: "data:",
+ path: "text/plain,hello%20world",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "file:///dir/afile",
+ scheme: "data",
+ prePath: "data:",
+ path: "text/plain,2",
+ ref: "",
+ relativeURI: "data:te\nxt/plain,2",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "file://",
+ scheme: "file",
+ prePath: "file://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///",
+ scheme: "file",
+ prePath: "file://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///myFile.html",
+ scheme: "file",
+ prePath: "file://",
+ path: "/myFile.html",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///dir/afile",
+ scheme: "file",
+ prePath: "file://",
+ path: "/dir/data/text/plain,2",
+ ref: "",
+ relativeURI: "data/text/plain,2",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///dir/dir2/",
+ scheme: "file",
+ prePath: "file://",
+ path: "/dir/dir2/data/text/plain,2",
+ ref: "",
+ relativeURI: "data/text/plain,2",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp://",
+ scheme: "ftp",
+ prePath: "ftp://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp:///",
+ scheme: "ftp",
+ prePath: "ftp://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp://ftp.mozilla.org/pub/mozilla.org/README",
+ scheme: "ftp",
+ prePath: "ftp://ftp.mozilla.org",
+ path: "/pub/mozilla.org/README",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp://foo:bar@ftp.mozilla.org:100/pub/mozilla.org/README",
+ scheme: "ftp",
+ prePath: "ftp://foo:bar@ftp.mozilla.org:100",
+ port: 100,
+ username: "foo",
+ password: "bar",
+ path: "/pub/mozilla.org/README",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp://foo:@ftp.mozilla.org:100/pub/mozilla.org/README",
+ scheme: "ftp",
+ prePath: "ftp://foo:@ftp.mozilla.org:100",
+ port: 100,
+ username: "foo",
+ password: "",
+ path: "/pub/mozilla.org/README",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ //Bug 706249
+ { spec: "gopher://mozilla.org/",
+ scheme: "gopher",
+ prePath: "gopher:",
+ path: "//mozilla.org/",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "http://",
+ scheme: "http",
+ prePath: "http://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http:///",
+ scheme: "http",
+ prePath: "http://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://www.example.com/",
+ scheme: "http",
+ prePath: "http://www.example.com",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://www.exa\nmple.com/",
+ scheme: "http",
+ prePath: "http://www.example.com",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://10.32.4.239/",
+ scheme: "http",
+ prePath: "http://10.32.4.239",
+ host: "10.32.4.239",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://[::192.9.5.5]/ipng",
+ scheme: "http",
+ prePath: "http://[::192.9.5.5]",
+ host: "::192.9.5.5",
+ path: "/ipng",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:8888/index.html",
+ scheme: "http",
+ prePath: "http://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:8888",
+ host: "fedc:ba98:7654:3210:fedc:ba98:7654:3210",
+ port: 8888,
+ path: "/index.html",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://bar:foo@www.mozilla.org:8080/pub/mozilla.org/README.html",
+ scheme: "http",
+ prePath: "http://bar:foo@www.mozilla.org:8080",
+ port: 8080,
+ username: "bar",
+ password: "foo",
+ host: "www.mozilla.org",
+ path: "/pub/mozilla.org/README.html",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "jar:resource://!/",
+ scheme: "jar",
+ prePath: "jar:",
+ path: "resource:///!/",
+ ref: "",
+ nsIURL: true, nsINestedURI: true },
+ { spec: "jar:resource://gre/chrome.toolkit.jar!/",
+ scheme: "jar",
+ prePath: "jar:",
+ path: "resource://gre/chrome.toolkit.jar!/",
+ ref: "",
+ nsIURL: true, nsINestedURI: true },
+ { spec: "mailto:webmaster@mozilla.com",
+ scheme: "mailto",
+ prePath: "mailto:",
+ path: "webmaster@mozilla.com",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "javascript:new Date()",
+ scheme: "javascript",
+ prePath: "javascript:",
+ path: "new%20Date()",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "blob:123456",
+ scheme: "blob",
+ prePath: "blob:",
+ path: "123456",
+ ref: "",
+ nsIURL: false, nsINestedURI: false, immutable: true },
+ { spec: "place:sort=8&maxResults=10",
+ scheme: "place",
+ prePath: "place:",
+ path: "sort=8&maxResults=10",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "resource://gre/",
+ scheme: "resource",
+ prePath: "resource://gre",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "resource://gre/components/",
+ scheme: "resource",
+ prePath: "resource://gre",
+ path: "/components/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+
+ // Adding more? Consider adding to test_URIs2.js instead, so that neither
+ // test runs for *too* long, risking timeouts on slow platforms.
+];
+
+var gHashSuffixes = [
+ "#",
+ "#myRef",
+ "#myRef?a=b",
+ "#myRef#",
+ "#myRef#x:yz"
+];
+
+// TEST HELPER FUNCTIONS
+// ---------------------
+function do_info(text, stack) {
+ if (!stack)
+ stack = Components.stack.caller;
+
+ dump( "\n" +
+ "TEST-INFO | " + stack.filename + " | [" + stack.name + " : " +
+ stack.lineNumber + "] " + text + "\n");
+}
+
+// Checks that the URIs satisfy equals(), in both possible orderings.
+// Also checks URI.equalsExceptRef(), because equal URIs should also be equal
+// when we ignore the ref.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of do_check_true.
+function do_check_uri_eq(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ do_info("(uri equals check: '" + aURI1.spec + "' == '" + aURI2.spec + "')");
+ aCheckTrueFunc(aURI1.equals(aURI2));
+ do_info("(uri equals check: '" + aURI2.spec + "' == '" + aURI1.spec + "')");
+ aCheckTrueFunc(aURI2.equals(aURI1));
+
+ // (Only take the extra step of testing 'equalsExceptRef' when we expect the
+ // URIs to really be equal. In 'todo' cases, the URIs may or may not be
+ // equal when refs are ignored - there's no way of knowing in general.)
+ if (aCheckTrueFunc == do_check_true) {
+ do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc);
+ }
+}
+
+// Checks that the URIs satisfy equalsExceptRef(), in both possible orderings.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of do_check_true.
+function do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ do_info("(uri equalsExceptRef check: '" +
+ aURI1.spec + "' == '" + aURI2.spec + "')");
+ aCheckTrueFunc(aURI1.equalsExceptRef(aURI2));
+ do_info("(uri equalsExceptRef check: '" +
+ aURI2.spec + "' == '" + aURI1.spec + "')");
+ aCheckTrueFunc(aURI2.equalsExceptRef(aURI1));
+}
+
+// Checks that the given property on aURI matches the corresponding property
+// in the test bundle (or matches some function of that corresponding property,
+// if aTestFunctor is passed in).
+function do_check_property(aTest, aURI, aPropertyName, aTestFunctor) {
+ if (aTest[aPropertyName]) {
+ var expectedVal = aTestFunctor ?
+ aTestFunctor(aTest[aPropertyName]) :
+ aTest[aPropertyName];
+
+ do_info("testing " + aPropertyName + " of " +
+ (aTestFunctor ? "modified '" : "'" ) + aTest.spec +
+ "' is '" + expectedVal + "'");
+ do_check_eq(aURI[aPropertyName], expectedVal);
+ }
+}
+
+// Test that a given URI parses correctly into its various components.
+function do_test_uri_basic(aTest) {
+ var URI;
+
+ do_info("Basic tests for " + aTest.spec + " relative URI: " + aTest.relativeURI);
+
+ try {
+ URI = NetUtil.newURI(aTest.spec);
+ } catch(e) {
+ do_info("Caught error on parse of" + aTest.spec + " Error: " + e.result);
+ if (aTest.fail) {
+ do_check_eq(e.result, aTest.result);
+ return;
+ }
+ do_throw(e.result);
+ }
+
+ if (aTest.relativeURI) {
+ var relURI;
+
+ try {
+ relURI = gIoService.newURI(aTest.relativeURI, null, URI);
+ } catch (e) {
+ do_info("Caught error on Relative parse of " + aTest.spec + " + " + aTest.relativeURI +" Error: " + e.result);
+ if (aTest.relativeFail) {
+ do_check_eq(e.result, aTest.relativeFail);
+ return;
+ }
+ do_throw(e.result);
+ }
+ do_info("relURI.path = " + relURI.path + ", was " + URI.path);
+ URI = relURI;
+ do_info("URI.path now = " + URI.path);
+ }
+
+ // Sanity-check
+ do_info("testing " + aTest.spec + " equals a clone of itself");
+ do_check_uri_eq(URI, URI.clone());
+ do_check_uri_eqExceptRef(URI, URI.cloneIgnoringRef());
+ do_info("testing " + aTest.spec + " instanceof nsIURL");
+ do_check_eq(URI instanceof Ci.nsIURL, aTest.nsIURL);
+ do_info("testing " + aTest.spec + " instanceof nsINestedURI");
+ do_check_eq(URI instanceof Ci.nsINestedURI,
+ aTest.nsINestedURI);
+
+ do_info("testing that " + aTest.spec + " throws or returns false " +
+ "from equals(null)");
+ // XXXdholbert At some point it'd probably be worth making this behavior
+ // (throwing vs. returning false) consistent across URI implementations.
+ var threw = false;
+ var isEqualToNull;
+ try {
+ isEqualToNull = URI.equals(null);
+ } catch(e) {
+ threw = true;
+ }
+ do_check_true(threw || !isEqualToNull);
+
+
+ // Check the various components
+ do_check_property(aTest, URI, "scheme");
+ do_check_property(aTest, URI, "prePath");
+ do_check_property(aTest, URI, "path");
+ do_check_property(aTest, URI, "ref");
+ do_check_property(aTest, URI, "port");
+ do_check_property(aTest, URI, "username");
+ do_check_property(aTest, URI, "password");
+ do_check_property(aTest, URI, "host");
+ do_check_property(aTest, URI, "specIgnoringRef");
+ if ("hasRef" in aTest) {
+ do_info("testing hasref: " + aTest.hasRef + " vs " + URI.hasRef);
+ do_check_eq(aTest.hasRef, URI.hasRef);
+ }
+}
+
+// Test that a given URI parses correctly when we add a given ref to the end
+function do_test_uri_with_hash_suffix(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ do_check_eq(aSuffix[0], "#");
+
+ var origURI = NetUtil.newURI(aTest.spec);
+ var testURI;
+
+ if (aTest.relativeURI) {
+ try {
+ origURI = gIoService.newURI(aTest.relativeURI, null, origURI);
+ } catch (e) {
+ do_info("Caught error on Relative parse of " + aTest.spec + " + " + aTest.relativeURI +" Error: " + e.result);
+ return;
+ }
+ try {
+ testURI = gIoService.newURI(aSuffix, null, origURI);
+ } catch (e) {
+ do_info("Caught error adding suffix to " + aTest.spec + " + " + aTest.relativeURI + ", suffix " + aSuffix + " Error: " + e.result);
+ return;
+ }
+ } else {
+ testURI = NetUtil.newURI(aTest.spec + aSuffix);
+ }
+
+ do_info("testing " + aTest.spec + " with '" + aSuffix + "' appended " +
+ "equals a clone of itself");
+ do_check_uri_eq(testURI, testURI.clone());
+
+ do_info("testing " + aTest.spec +
+ " doesn't equal self with '" + aSuffix + "' appended");
+
+ do_check_false(origURI.equals(testURI));
+
+ do_info("testing " + aTest.spec +
+ " is equalExceptRef to self with '" + aSuffix + "' appended");
+ do_check_uri_eqExceptRef(origURI, testURI);
+
+ do_check_eq(testURI.hasRef, true);
+
+ if (!origURI.ref) {
+ // These tests fail if origURI has a ref
+ do_info("testing cloneIgnoringRef on " + testURI.spec +
+ " is equal to no-ref version but not equal to ref version");
+ var cloneNoRef = testURI.cloneIgnoringRef();
+ do_check_uri_eq(cloneNoRef, origURI);
+ do_check_false(cloneNoRef.equals(testURI));
+
+ do_info("testing cloneWithNewRef on " + testURI.spec +
+ " with an empty ref is equal to no-ref version but not equal to ref version");
+ var cloneNewRef = testURI.cloneWithNewRef("");
+ do_check_uri_eq(cloneNewRef, origURI);
+ do_check_uri_eq(cloneNewRef, cloneNoRef);
+ do_check_false(cloneNewRef.equals(testURI));
+
+ do_info("testing cloneWithNewRef on " + origURI.spec +
+ " with the same new ref is equal to ref version and not equal to no-ref version");
+ cloneNewRef = origURI.cloneWithNewRef(aSuffix);
+ do_check_uri_eq(cloneNewRef, testURI);
+ do_check_true(cloneNewRef.equals(testURI));
+ }
+
+ do_check_property(aTest, testURI, "scheme");
+ do_check_property(aTest, testURI, "prePath");
+ if (!origURI.ref) {
+ // These don't work if it's a ref already because '+' doesn't give the right result
+ do_check_property(aTest, testURI, "path",
+ function(aStr) { return aStr + aSuffix; });
+ do_check_property(aTest, testURI, "ref",
+ function(aStr) { return aSuffix.substr(1); });
+ }
+}
+
+// Tests various ways of setting & clearing a ref on a URI.
+function do_test_mutate_ref(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ do_check_eq(aSuffix[0], "#");
+
+ var refURIWithSuffix = NetUtil.newURI(aTest.spec + aSuffix);
+ var refURIWithoutSuffix = NetUtil.newURI(aTest.spec);
+
+ var testURI = NetUtil.newURI(aTest.spec);
+
+ // First: Try setting .ref to our suffix
+ do_info("testing that setting .ref on " + aTest.spec +
+ " to '" + aSuffix + "' does what we expect");
+ testURI.ref = aSuffix;
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+
+ // Now try setting .ref but leave off the initial hash (expect same result)
+ var suffixLackingHash = aSuffix.substr(1);
+ if (suffixLackingHash) { // (skip this our suffix was *just* a #)
+ do_info("testing that setting .ref on " + aTest.spec +
+ " to '" + suffixLackingHash + "' does what we expect");
+ testURI.ref = suffixLackingHash;
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+ }
+
+ // Now, clear .ref (should get us back the original spec)
+ do_info("testing that clearing .ref on " + testURI.spec +
+ " does what we expect");
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ if (!aTest.relativeURI) {
+ // TODO: These tests don't work as-is for relative URIs.
+
+ // Now try setting .spec directly (including suffix) and then clearing .ref
+ var specWithSuffix = aTest.spec + aSuffix;
+ do_info("testing that setting spec to " +
+ specWithSuffix + " and then clearing ref does what we expect");
+ testURI.spec = specWithSuffix;
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // XXX nsIJARURI throws an exception in SetPath(), so skip it for next part.
+ if (!(testURI instanceof Ci.nsIJARURI)) {
+ // Now try setting .path directly (including suffix) and then clearing .ref
+ // (same as above, but with now with .path instead of .spec)
+ testURI = NetUtil.newURI(aTest.spec);
+
+ var pathWithSuffix = aTest.path + aSuffix;
+ do_info("testing that setting path to " +
+ pathWithSuffix + " and then clearing ref does what we expect");
+ testURI.path = pathWithSuffix;
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // Also: make sure that clearing .path also clears .ref
+ testURI.path = pathWithSuffix;
+ do_info("testing that clearing path from " +
+ pathWithSuffix + " also clears .ref");
+ testURI.path = "";
+ do_check_eq(testURI.ref, "");
+ }
+ }
+}
+
+// Tests that normally-mutable properties can't be modified on
+// special URIs that are known to be immutable.
+function do_test_immutable(aTest) {
+ do_check_true(aTest.immutable);
+
+ var URI = NetUtil.newURI(aTest.spec);
+ // All the non-readonly attributes on nsIURI.idl:
+ var propertiesToCheck = ["spec", "scheme", "userPass", "username", "password",
+ "hostPort", "host", "port", "path", "ref"];
+
+ propertiesToCheck.forEach(function(aProperty) {
+ var threw = false;
+ try {
+ URI[aProperty] = "anothervalue";
+ } catch(e) {
+ threw = true;
+ }
+
+ do_info("testing that setting '" + aProperty +
+ "' on immutable URI '" + aTest.spec + "' will throw");
+ do_check_true(threw);
+ });
+}
+
+
+// TEST MAIN FUNCTION
+// ------------------
+function run_test()
+{
+ // UTF-8 check - From bug 622981
+ // ASCII
+ let base = gIoService.newURI("http://example.org/xenia?", null, null);
+ let resolved = gIoService.newURI("?x", null, base);
+ let expected = gIoService.newURI("http://example.org/xenia?x",
+ null, null);
+ do_info("Bug 662981: ACSII - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(resolved.equals(expected));
+
+ // UTF-8 character "è"
+ // Bug 622981 was triggered by an empty query string
+ base = gIoService.newURI("http://example.org/xènia?", null, null);
+ resolved = gIoService.newURI("?x", null, base);
+ expected = gIoService.newURI("http://example.org/xènia?x",
+ null, null);
+ do_info("Bug 662981: UTF8 - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(resolved.equals(expected));
+
+ gTests.forEach(function(aTest) {
+ // Check basic URI functionality
+ do_test_uri_basic(aTest);
+
+ if (!aTest.fail) {
+ // Try adding various #-prefixed strings to the ends of the URIs
+ gHashSuffixes.forEach(function(aSuffix) {
+ do_test_uri_with_hash_suffix(aTest, aSuffix);
+ if (!aTest.immutable) {
+ do_test_mutate_ref(aTest, aSuffix);
+ }
+ });
+
+ // For URIs that we couldn't mutate above due to them being immutable:
+ // Now we check that they're actually immutable.
+ if (aTest.immutable) {
+ do_test_immutable(aTest);
+ }
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_URIs2.js b/netwerk/test/unit/test_URIs2.js
new file mode 100644
index 000000000..201473f58
--- /dev/null
+++ b/netwerk/test/unit/test_URIs2.js
@@ -0,0 +1,693 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+
+// Run by: cd objdir; make -C netwerk/test/ xpcshell-tests
+// or: cd objdir; make SOLO_FILE="test_URIs2.js" -C netwerk/test/ check-one
+
+// This is a clone of test_URIs.js, with a different set of test data in gTests.
+// The original test data in test_URIs.js was split between test_URIs and test_URIs2.js
+// because test_URIs.js was running for too long on slow platforms, causing
+// intermittent timeouts.
+
+// Relevant RFCs: 1738, 1808, 2396, 3986 (newer than the code)
+// http://greenbytes.de/tech/webdav/rfc3986.html#rfc.section.5.4
+// http://greenbytes.de/tech/tc/uris/
+
+// TEST DATA
+// ---------
+var gTests = [
+ { spec: "view-source:about:blank",
+ scheme: "view-source",
+ prePath: "view-source:",
+ path: "about:blank",
+ ref: "",
+ nsIURL: false, nsINestedURI: true, immutable: true },
+ { spec: "view-source:http://www.mozilla.org/",
+ scheme: "view-source",
+ prePath: "view-source:",
+ path: "http://www.mozilla.org/",
+ ref: "",
+ nsIURL: false, nsINestedURI: true, immutable: true },
+ { spec: "x-external:",
+ scheme: "x-external",
+ prePath: "x-external:",
+ path: "",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "x-external:abc",
+ scheme: "x-external",
+ prePath: "x-external:",
+ path: "abc",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "http://www2.example.com/",
+ relativeURI: "a/b/c/d",
+ scheme: "http",
+ prePath: "http://www2.example.com",
+ path: "/a/b/c/d",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ // relative URL testcases from http://greenbytes.de/tech/webdav/rfc3986.html#rfc.section.5.4
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g:h",
+ scheme: "g",
+ prePath: "g:",
+ path: "h",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "/g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "?y",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/d;p?y",
+ ref: "",// fix
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g?y",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g?y",
+ ref: "",// fix
+ specIgnoringRef: "http://a/b/c/g?y",
+ hasRef: false,
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "#s",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/d;p?q#s",
+ ref: "s",// fix
+ specIgnoringRef: "http://a/b/c/d;p?q",
+ hasRef: true,
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g#s",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g#s",
+ ref: "s",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g?y#s",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g?y#s",
+ ref: "s",
+ nsIURL: true, nsINestedURI: false },
+ /*
+ Bug xxxxxx - we return a path of b/c/;x
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ";x",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/d;x",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ */
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g;x",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x?y#s",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g;x?y#s",
+ ref: "s",
+ nsIURL: true, nsINestedURI: false },
+ /*
+ Can't easily specify a relative URI of "" to the test code
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/d",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ */
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ".",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "..",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../..",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+
+ // abnormal examples
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../../../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+
+ // coalesce
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "/./g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "/../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g.",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g.",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ".g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/.g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g..",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g..",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "..g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/..g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ".",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./g/.",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/./h",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g/h",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/../h",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/h",
+ ref: "",// fix
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x=1/./y",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g;x=1/y",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g;x=1/../y",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/y",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ // protocol-relative http://tools.ietf.org/html/rfc3986#section-4.2
+ { spec: "http://www2.example.com/",
+ relativeURI: "//www3.example2.com/bar",
+ scheme: "http",
+ prePath: "http://www3.example2.com",
+ path: "/bar",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "https://www2.example.com/",
+ relativeURI: "//www3.example2.com/bar",
+ scheme: "https",
+ prePath: "https://www3.example2.com",
+ path: "/bar",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+];
+
+var gHashSuffixes = [
+ "#",
+ "#myRef",
+ "#myRef?a=b",
+ "#myRef#",
+ "#myRef#x:yz"
+];
+
+// TEST HELPER FUNCTIONS
+// ---------------------
+function do_info(text, stack) {
+ if (!stack)
+ stack = Components.stack.caller;
+
+ dump( "\n" +
+ "TEST-INFO | " + stack.filename + " | [" + stack.name + " : " +
+ stack.lineNumber + "] " + text + "\n");
+}
+
+// Checks that the URIs satisfy equals(), in both possible orderings.
+// Also checks URI.equalsExceptRef(), because equal URIs should also be equal
+// when we ignore the ref.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of do_check_true.
+function do_check_uri_eq(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ do_info("(uri equals check: '" + aURI1.spec + "' == '" + aURI2.spec + "')");
+ aCheckTrueFunc(aURI1.equals(aURI2));
+ do_info("(uri equals check: '" + aURI2.spec + "' == '" + aURI1.spec + "')");
+ aCheckTrueFunc(aURI2.equals(aURI1));
+
+ // (Only take the extra step of testing 'equalsExceptRef' when we expect the
+ // URIs to really be equal. In 'todo' cases, the URIs may or may not be
+ // equal when refs are ignored - there's no way of knowing in general.)
+ if (aCheckTrueFunc == do_check_true) {
+ do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc);
+ }
+}
+
+// Checks that the URIs satisfy equalsExceptRef(), in both possible orderings.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of do_check_true.
+function do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ do_info("(uri equalsExceptRef check: '" +
+ aURI1.spec + "' == '" + aURI2.spec + "')");
+ aCheckTrueFunc(aURI1.equalsExceptRef(aURI2));
+ do_info("(uri equalsExceptRef check: '" +
+ aURI2.spec + "' == '" + aURI1.spec + "')");
+ aCheckTrueFunc(aURI2.equalsExceptRef(aURI1));
+}
+
+// Checks that the given property on aURI matches the corresponding property
+// in the test bundle (or matches some function of that corresponding property,
+// if aTestFunctor is passed in).
+function do_check_property(aTest, aURI, aPropertyName, aTestFunctor) {
+ if (aTest[aPropertyName]) {
+ var expectedVal = aTestFunctor ?
+ aTestFunctor(aTest[aPropertyName]) :
+ aTest[aPropertyName];
+
+ do_info("testing " + aPropertyName + " of " +
+ (aTestFunctor ? "modified '" : "'" ) + aTest.spec +
+ "' is '" + expectedVal + "'");
+ do_check_eq(aURI[aPropertyName], expectedVal);
+ }
+}
+
+// Test that a given URI parses correctly into its various components.
+function do_test_uri_basic(aTest) {
+ var URI;
+
+ do_info("Basic tests for " + aTest.spec + " relative URI: " + aTest.relativeURI);
+
+ try {
+ URI = NetUtil.newURI(aTest.spec);
+ } catch(e) {
+ do_info("Caught error on parse of" + aTest.spec + " Error: " + e.result);
+ if (aTest.fail) {
+ do_check_eq(e.result, aTest.result);
+ return;
+ }
+ do_throw(e.result);
+ }
+
+ if (aTest.relativeURI) {
+ var relURI;
+
+ try {
+ relURI = gIoService.newURI(aTest.relativeURI, null, URI);
+ } catch (e) {
+ do_info("Caught error on Relative parse of " + aTest.spec + " + " + aTest.relativeURI +" Error: " + e.result);
+ if (aTest.relativeFail) {
+ do_check_eq(e.result, aTest.relativeFail);
+ return;
+ }
+ do_throw(e.result);
+ }
+ do_info("relURI.path = " + relURI.path + ", was " + URI.path);
+ URI = relURI;
+ do_info("URI.path now = " + URI.path);
+ }
+
+ // Sanity-check
+ do_info("testing " + aTest.spec + " equals a clone of itself");
+ do_check_uri_eq(URI, URI.clone());
+ do_check_uri_eqExceptRef(URI, URI.cloneIgnoringRef());
+ do_info("testing " + aTest.spec + " instanceof nsIURL");
+ do_check_eq(URI instanceof Ci.nsIURL, aTest.nsIURL);
+ do_info("testing " + aTest.spec + " instanceof nsINestedURI");
+ do_check_eq(URI instanceof Ci.nsINestedURI,
+ aTest.nsINestedURI);
+
+ do_info("testing that " + aTest.spec + " throws or returns false " +
+ "from equals(null)");
+ // XXXdholbert At some point it'd probably be worth making this behavior
+ // (throwing vs. returning false) consistent across URI implementations.
+ var threw = false;
+ var isEqualToNull;
+ try {
+ isEqualToNull = URI.equals(null);
+ } catch(e) {
+ threw = true;
+ }
+ do_check_true(threw || !isEqualToNull);
+
+
+ // Check the various components
+ do_check_property(aTest, URI, "scheme");
+ do_check_property(aTest, URI, "prePath");
+ do_check_property(aTest, URI, "path");
+ do_check_property(aTest, URI, "ref");
+ do_check_property(aTest, URI, "port");
+ do_check_property(aTest, URI, "username");
+ do_check_property(aTest, URI, "password");
+ do_check_property(aTest, URI, "host");
+ do_check_property(aTest, URI, "specIgnoringRef");
+ if ("hasRef" in aTest) {
+ do_info("testing hasref: " + aTest.hasRef + " vs " + URI.hasRef);
+ do_check_eq(aTest.hasRef, URI.hasRef);
+ }
+}
+
+// Test that a given URI parses correctly when we add a given ref to the end
+function do_test_uri_with_hash_suffix(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ do_check_eq(aSuffix[0], "#");
+
+ var origURI = NetUtil.newURI(aTest.spec);
+ var testURI;
+
+ if (aTest.relativeURI) {
+ try {
+ origURI = gIoService.newURI(aTest.relativeURI, null, origURI);
+ } catch (e) {
+ do_info("Caught error on Relative parse of " + aTest.spec + " + " + aTest.relativeURI +" Error: " + e.result);
+ return;
+ }
+ try {
+ testURI = gIoService.newURI(aSuffix, null, origURI);
+ } catch (e) {
+ do_info("Caught error adding suffix to " + aTest.spec + " + " + aTest.relativeURI + ", suffix " + aSuffix + " Error: " + e.result);
+ return;
+ }
+ } else {
+ testURI = NetUtil.newURI(aTest.spec + aSuffix);
+ }
+
+ do_info("testing " + aTest.spec + " with '" + aSuffix + "' appended " +
+ "equals a clone of itself");
+ do_check_uri_eq(testURI, testURI.clone());
+
+ do_info("testing " + aTest.spec +
+ " doesn't equal self with '" + aSuffix + "' appended");
+
+ do_check_false(origURI.equals(testURI));
+
+ do_info("testing " + aTest.spec +
+ " is equalExceptRef to self with '" + aSuffix + "' appended");
+ do_check_uri_eqExceptRef(origURI, testURI);
+
+ do_check_eq(testURI.hasRef, true);
+
+ if (!origURI.ref) {
+ // These tests fail if origURI has a ref
+ do_info("testing cloneIgnoringRef on " + testURI.spec +
+ " is equal to no-ref version but not equal to ref version");
+ var cloneNoRef = testURI.cloneIgnoringRef();
+ do_check_uri_eq(cloneNoRef, origURI);
+ do_check_false(cloneNoRef.equals(testURI));
+ }
+
+ do_check_property(aTest, testURI, "scheme");
+ do_check_property(aTest, testURI, "prePath");
+ if (!origURI.ref) {
+ // These don't work if it's a ref already because '+' doesn't give the right result
+ do_check_property(aTest, testURI, "path",
+ function(aStr) { return aStr + aSuffix; });
+ do_check_property(aTest, testURI, "ref",
+ function(aStr) { return aSuffix.substr(1); });
+ }
+}
+
+// Tests various ways of setting & clearing a ref on a URI.
+function do_test_mutate_ref(aTest, aSuffix) {
+ do_info("making sure caller is using suffix that starts with '#'");
+ do_check_eq(aSuffix[0], "#");
+
+ var refURIWithSuffix = NetUtil.newURI(aTest.spec + aSuffix);
+ var refURIWithoutSuffix = NetUtil.newURI(aTest.spec);
+
+ var testURI = NetUtil.newURI(aTest.spec);
+
+ // First: Try setting .ref to our suffix
+ do_info("testing that setting .ref on " + aTest.spec +
+ " to '" + aSuffix + "' does what we expect");
+ testURI.ref = aSuffix;
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+
+ // Now try setting .ref but leave off the initial hash (expect same result)
+ var suffixLackingHash = aSuffix.substr(1);
+ if (suffixLackingHash) { // (skip this our suffix was *just* a #)
+ do_info("testing that setting .ref on " + aTest.spec +
+ " to '" + suffixLackingHash + "' does what we expect");
+ testURI.ref = suffixLackingHash;
+ do_check_uri_eq(testURI, refURIWithSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
+ }
+
+ // Now, clear .ref (should get us back the original spec)
+ do_info("testing that clearing .ref on " + testURI.spec +
+ " does what we expect");
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ if (!aTest.relativeURI) {
+ // TODO: These tests don't work as-is for relative URIs.
+
+ // Now try setting .spec directly (including suffix) and then clearing .ref
+ var specWithSuffix = aTest.spec + aSuffix;
+ do_info("testing that setting spec to " +
+ specWithSuffix + " and then clearing ref does what we expect");
+ testURI.spec = specWithSuffix;
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // XXX nsIJARURI throws an exception in SetPath(), so skip it for next part.
+ if (!(testURI instanceof Ci.nsIJARURI)) {
+ // Now try setting .path directly (including suffix) and then clearing .ref
+ // (same as above, but with now with .path instead of .spec)
+ testURI = NetUtil.newURI(aTest.spec);
+
+ var pathWithSuffix = aTest.path + aSuffix;
+ do_info("testing that setting path to " +
+ pathWithSuffix + " and then clearing ref does what we expect");
+ testURI.path = pathWithSuffix;
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // Also: make sure that clearing .path also clears .ref
+ testURI.path = pathWithSuffix;
+ do_info("testing that clearing path from " +
+ pathWithSuffix + " also clears .ref");
+ testURI.path = "";
+ do_check_eq(testURI.ref, "");
+ }
+ }
+}
+
+// Tests that normally-mutable properties can't be modified on
+// special URIs that are known to be immutable.
+function do_test_immutable(aTest) {
+ do_check_true(aTest.immutable);
+
+ var URI = NetUtil.newURI(aTest.spec);
+ // All the non-readonly attributes on nsIURI.idl:
+ var propertiesToCheck = ["spec", "scheme", "userPass", "username", "password",
+ "hostPort", "host", "port", "path", "ref"];
+
+ propertiesToCheck.forEach(function(aProperty) {
+ var threw = false;
+ try {
+ URI[aProperty] = "anothervalue";
+ } catch(e) {
+ threw = true;
+ }
+
+ do_info("testing that setting '" + aProperty +
+ "' on immutable URI '" + aTest.spec + "' will throw");
+ do_check_true(threw);
+ });
+}
+
+
+// TEST MAIN FUNCTION
+// ------------------
+function run_test()
+{
+ // UTF-8 check - From bug 622981
+ // ASCII
+ let base = gIoService.newURI("http://example.org/xenia?", null, null);
+ let resolved = gIoService.newURI("?x", null, base);
+ let expected = gIoService.newURI("http://example.org/xenia?x",
+ null, null);
+ do_info("Bug 662981: ACSII - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(resolved.equals(expected));
+
+ // UTF-8 character "è"
+ // Bug 622981 was triggered by an empty query string
+ base = gIoService.newURI("http://example.org/xènia?", null, null);
+ resolved = gIoService.newURI("?x", null, base);
+ expected = gIoService.newURI("http://example.org/xènia?x",
+ null, null);
+ do_info("Bug 662981: UTF8 - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(resolved.equals(expected));
+
+ gTests.forEach(function(aTest) {
+ // Check basic URI functionality
+ do_test_uri_basic(aTest);
+
+ if (!aTest.fail) {
+ // Try adding various #-prefixed strings to the ends of the URIs
+ gHashSuffixes.forEach(function(aSuffix) {
+ do_test_uri_with_hash_suffix(aTest, aSuffix);
+ if (!aTest.immutable) {
+ do_test_mutate_ref(aTest, aSuffix);
+ }
+ });
+
+ // For URIs that we couldn't mutate above due to them being immutable:
+ // Now we check that they're actually immutable.
+ if (aTest.immutable) {
+ do_test_immutable(aTest);
+ }
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_XHR_redirects.js b/netwerk/test/unit/test_XHR_redirects.js
new file mode 100644
index 000000000..8c9b42437
--- /dev/null
+++ b/netwerk/test/unit/test_XHR_redirects.js
@@ -0,0 +1,235 @@
+// This file tests whether XmlHttpRequests correctly handle redirects,
+// including rewriting POSTs to GETs (on 301/302/303), as well as
+// prompting for redirects of other unsafe methods (such as PUTs, DELETEs,
+// etc--see HttpBaseChannel::IsSafeMethod). Since no prompting is possible
+// in xpcshell, we get an error for prompts, and the request fails.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+var sSame;
+var sOther;
+var sRedirectPromptPref;
+
+const BUGID = "676059";
+const OTHERBUGID = "696849";
+
+XPCOMUtils.defineLazyGetter(this, "pSame", function() {
+ return sSame.identity.primaryPort;
+});
+XPCOMUtils.defineLazyGetter(this, "pOther", function() {
+ return sOther.identity.primaryPort;
+});
+
+function createXHR(async, method, path)
+{
+ var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open(method, "http://localhost:" + pSame + path, async);
+ return xhr;
+}
+
+function checkResults(xhr, method, status, unsafe)
+{
+ if (unsafe) {
+ if (sRedirectPromptPref) {
+ // The method is null if we prompt for unsafe redirects
+ method = null;
+ } else {
+ // The status code is 200 when we don't prompt for unsafe redirects
+ status = 200;
+ }
+ }
+
+ if (xhr.readyState != 4)
+ return false;
+ do_check_eq(xhr.status, status);
+
+ if (status == 200) {
+ // if followed then check for echoed method name
+ do_check_eq(xhr.getResponseHeader("X-Received-Method"), method);
+ }
+
+ return true;
+}
+
+function run_test() {
+ // start servers
+ sSame = new HttpServer();
+
+ // same-origin redirects
+ sSame.registerPathHandler("/bug" + BUGID + "-redirect301", bug676059redirect301);
+ sSame.registerPathHandler("/bug" + BUGID + "-redirect302", bug676059redirect302);
+ sSame.registerPathHandler("/bug" + BUGID + "-redirect303", bug676059redirect303);
+ sSame.registerPathHandler("/bug" + BUGID + "-redirect307", bug676059redirect307);
+ sSame.registerPathHandler("/bug" + BUGID + "-redirect308", bug676059redirect308);
+
+ // cross-origin redirects
+ sSame.registerPathHandler("/bug" + OTHERBUGID + "-redirect301", bug696849redirect301);
+ sSame.registerPathHandler("/bug" + OTHERBUGID + "-redirect302", bug696849redirect302);
+ sSame.registerPathHandler("/bug" + OTHERBUGID + "-redirect303", bug696849redirect303);
+ sSame.registerPathHandler("/bug" + OTHERBUGID + "-redirect307", bug696849redirect307);
+ sSame.registerPathHandler("/bug" + OTHERBUGID + "-redirect308", bug696849redirect308);
+
+ // same-origin target
+ sSame.registerPathHandler("/bug" + BUGID + "-target", echoMethod);
+ sSame.start(-1);
+
+ // cross-origin target
+ sOther = new HttpServer();
+ sOther.registerPathHandler("/bug" + OTHERBUGID + "-target", echoMethod);
+ sOther.start(-1);
+
+ // format: redirectType, methodToSend, redirectedMethod, finalStatus
+ // redirectType sets the URI the initial request goes to
+ // methodToSend is the HTTP method to send
+ // redirectedMethod is the method to use for the redirect, if any
+ // finalStatus is 200 when the redirect takes place, redirectType otherwise
+
+ // Note that unsafe methods should not follow the redirect automatically
+ // Of the methods below, DELETE, POST and PUT are unsafe
+
+ sRedirectPromptPref = Preferences.get("network.http.prompt-temp-redirect");
+ // Following Bug 677754 we don't prompt for unsafe redirects
+
+ // same-origin variant
+ var tests = [
+ // 301: rewrite just POST
+ [301, "DELETE", "DELETE", 301, true],
+ [301, "GET", "GET", 200, false],
+ [301, "HEAD", "HEAD", 200, false],
+ [301, "POST", "GET", 200, false],
+ [301, "PUT", "PUT", 301, true],
+ [301, "PROPFIND", "PROPFIND", 200, false],
+ // 302: see 301
+ [302, "DELETE", "DELETE", 302, true],
+ [302, "GET", "GET", 200, false],
+ [302, "HEAD", "HEAD", 200, false],
+ [302, "POST", "GET", 200, false],
+ [302, "PUT", "PUT", 302, true],
+ [302, "PROPFIND", "PROPFIND", 200, false],
+ // 303: rewrite to GET except HEAD
+ [303, "DELETE", "GET", 200, false],
+ [303, "GET", "GET", 200, false],
+ [303, "HEAD", "HEAD", 200, false],
+ [303, "POST", "GET", 200, false],
+ [303, "PUT", "GET", 200, false],
+ [303, "PROPFIND", "GET", 200, false],
+ // 307: never rewrite
+ [307, "DELETE", "DELETE", 307, true],
+ [307, "GET", "GET", 200, false],
+ [307, "HEAD", "HEAD", 200, false],
+ [307, "POST", "POST", 307, true],
+ [307, "PUT", "PUT", 307, true],
+ [307, "PROPFIND", "PROPFIND", 200, false],
+ // 308: never rewrite
+ [308, "DELETE", "DELETE", 308, true],
+ [308, "GET", "GET", 200, false],
+ [308, "HEAD", "HEAD", 200, false],
+ [308, "POST", "POST", 308, true],
+ [308, "PUT", "PUT", 308, true],
+ [308, "PROPFIND", "PROPFIND", 200, false],
+ ];
+
+ // cross-origin variant
+ var othertests = tests; // for now these have identical results
+
+ var xhr;
+
+ for (var i = 0; i < tests.length; ++i) {
+ dump("Testing " + tests[i] + "\n");
+ xhr = createXHR(false, tests[i][1], "/bug" + BUGID + "-redirect" + tests[i][0]);
+ xhr.send(null);
+ checkResults(xhr, tests[i][2], tests[i][3], tests[i][4]);
+ }
+
+ for (var i = 0; i < othertests.length; ++i) {
+ dump("Testing " + othertests[i] + " (cross-origin)\n");
+ xhr = createXHR(false, othertests[i][1], "/bug" + OTHERBUGID + "-redirect" + othertests[i][0]);
+ xhr.send(null);
+ checkResults(xhr, othertests[i][2], tests[i][3], tests[i][4]);
+ }
+
+ sSame.stop(do_test_finished);
+ sOther.stop(do_test_finished);
+}
+
+function redirect(metadata, response, status, port, bugid) {
+ // set a proper reason string to avoid confusion when looking at the
+ // HTTP messages
+ var reason;
+ if (status == 301) {
+ reason = "Moved Permanently";
+ }
+ else if (status == 302) {
+ reason = "Found";
+ }
+ else if (status == 303) {
+ reason = "See Other";
+ }
+ else if (status == 307) {
+ reason = "Temporary Redirect";
+ }
+ else if (status == 308) {
+ reason = "Permanent Redirect";
+ }
+
+ response.setStatusLine(metadata.httpVersion, status, reason);
+ response.setHeader("Location", "http://localhost:" + port + "/bug" + bugid + "-target");
+}
+
+// PATH HANDLER FOR /bug676059-redirect301
+function bug676059redirect301(metadata, response) {
+ redirect(metadata, response, 301, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect301
+function bug696849redirect301(metadata, response) {
+ redirect(metadata, response, 301, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect302
+function bug676059redirect302(metadata, response) {
+ redirect(metadata, response, 302, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect302
+function bug696849redirect302(metadata, response) {
+ redirect(metadata, response, 302, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect303
+function bug676059redirect303(metadata, response) {
+ redirect(metadata, response, 303, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect303
+function bug696849redirect303(metadata, response) {
+ redirect(metadata, response, 303, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect307
+function bug676059redirect307(metadata, response) {
+ redirect(metadata, response, 307, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug676059-redirect308
+function bug676059redirect308(metadata, response) {
+ redirect(metadata, response, 308, pSame, BUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect307
+function bug696849redirect307(metadata, response) {
+ redirect(metadata, response, 307, pOther, OTHERBUGID);
+}
+
+// PATH HANDLER FOR /bug696849-redirect308
+function bug696849redirect308(metadata, response) {
+ redirect(metadata, response, 308, pOther, OTHERBUGID);
+}
+
+// Echo the request method in "X-Received-Method" header field
+function echoMethod(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("X-Received-Method", metadata.method);
+}
diff --git a/netwerk/test/unit/test_about_networking.js b/netwerk/test/unit/test_about_networking.js
new file mode 100644
index 000000000..efcd5910e
--- /dev/null
+++ b/netwerk/test/unit/test_about_networking.js
@@ -0,0 +1,96 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const gDashboard = Cc['@mozilla.org/network/dashboard;1']
+ .getService(Ci.nsIDashboard);
+
+const gServerSocket = Components.classes["@mozilla.org/network/server-socket;1"]
+ .createInstance(Components.interfaces.nsIServerSocket);
+const gHttpServer = new HttpServer();
+
+add_test(function test_http() {
+ gDashboard.requestHttpConnections(function(data) {
+ let found = false;
+ for (let i = 0; i < data.connections.length; i++) {
+ if (data.connections[i].host == "localhost") {
+ found = true;
+ break;
+ }
+ }
+ do_check_eq(found, true);
+
+ run_next_test();
+ });
+});
+
+add_test(function test_dns() {
+ gDashboard.requestDNSInfo(function(data) {
+ let found = false;
+ for (let i = 0; i < data.entries.length; i++) {
+ if (data.entries[i].hostname == "localhost") {
+ found = true;
+ break;
+ }
+ }
+ do_check_eq(found, true);
+
+ do_test_pending();
+ gHttpServer.stop(do_test_finished);
+
+ run_next_test();
+ });
+});
+
+add_test(function test_sockets() {
+ let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+ let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ let transport = sts.createTransport(null, 0, "127.0.0.1",
+ gServerSocket.port, null);
+ let listener = {
+ onTransportStatus: function(aTransport, aStatus, aProgress, aProgressMax) {
+ if (aStatus == Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ gDashboard.requestSockets(function(data) {
+ gServerSocket.close();
+ let found = false;
+ for (let i = 0; i < data.sockets.length; i++) {
+ if (data.sockets[i].host == "127.0.0.1") {
+ found = true;
+ break;
+ }
+ }
+ do_check_eq(found, true);
+
+ run_next_test();
+ });
+ }
+ }
+ };
+ transport.setEventSink(listener, threadManager.currentThread);
+
+ transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);
+});
+
+function run_test() {
+ let ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ gHttpServer.start(-1);
+
+ let uri = ioService.newURI("http://localhost:" + gHttpServer.identity.primaryPort,
+ null, null);
+ let channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+
+ channel.open2();
+
+ gServerSocket.init(-1, true, -1);
+
+ run_next_test();
+}
+
diff --git a/netwerk/test/unit/test_about_protocol.js b/netwerk/test/unit/test_about_protocol.js
new file mode 100644
index 000000000..8f45d1c18
--- /dev/null
+++ b/netwerk/test/unit/test_about_protocol.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+var unsafeAboutModule = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+ newChannel: function (aURI, aLoadInfo) {
+ var uri = Services.io.newURI("about:blank", null, null);
+ let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
+ chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
+ return chan;
+ },
+ getURIFlags: function (aURI) {
+ return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT;
+ }
+};
+
+var factory = {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return unsafeAboutModule.QueryInterface(aIID);
+ },
+ lockFactory: function(aLock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+function run_test() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ let classID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+ registrar.registerFactory(classID, "", "@mozilla.org/network/protocol/about;1?what=unsafe", factory);
+
+ let aboutUnsafeChan = NetUtil.newChannel({
+ uri: "about:unsafe",
+ loadUsingSystemPrincipal: true
+ });
+
+ do_check_null(aboutUnsafeChan.owner, "URI_SAFE_FOR_UNTRUSTED_CONTENT channel has no owner");
+
+ registrar.unregisterFactory(classID, factory);
+}
diff --git a/netwerk/test/unit/test_aboutblank.js b/netwerk/test/unit/test_aboutblank.js
new file mode 100644
index 000000000..2abe3c576
--- /dev/null
+++ b/netwerk/test/unit/test_aboutblank.js
@@ -0,0 +1,32 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test() {
+ var base = NetUtil.newURI("http://www.example.com", null, null);
+ var about1 = NetUtil.newURI("about:blank", null, null);
+ var about2 = NetUtil.newURI("about:blank", null, base);
+
+ var chan1 = NetUtil.newChannel({
+ uri: about1,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIPropertyBag2);
+
+ var chan2 = NetUtil.newChannel({
+ uri: about2,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIPropertyBag2);
+
+ var haveProp = false;
+ var propVal = null;
+ try {
+ propVal = chan1.getPropertyAsInterface("baseURI",
+ Components.interfaces.nsIURI);
+ haveProp = true;
+ } catch (e if e.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
+ // Property shouldn't be there.
+ }
+ do_check_eq(propVal, null);
+ do_check_eq(haveProp, false);
+ do_check_eq(chan2.getPropertyAsInterface("baseURI",
+ Components.interfaces.nsIURI),
+ base);
+}
diff --git a/netwerk/test/unit/test_addr_in_use_error.js b/netwerk/test/unit/test_addr_in_use_error.js
new file mode 100644
index 000000000..736282a11
--- /dev/null
+++ b/netwerk/test/unit/test_addr_in_use_error.js
@@ -0,0 +1,32 @@
+// Opening a second listening socket on the same address as an extant
+// socket should elicit NS_ERROR_SOCKET_ADDRESS_IN_USE on non-Windows
+// machines.
+
+var CC = Components.Constructor;
+
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+
+function testAddrInUse()
+{
+ // Windows lets us have as many sockets listening on the same address as
+ // we like, evidently.
+ if (mozinfo.os == "win") {
+ return;
+ }
+
+ // Create listening socket:
+ // any port (-1), loopback only (true), default backlog (-1)
+ let listener = ServerSocket(-1, true, -1);
+ do_check_true(listener instanceof Ci.nsIServerSocket);
+
+ // Try to create another listening socket on the same port, whatever that was.
+ do_check_throws_nsIException(() => ServerSocket(listener.port, true, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE");
+}
+
+function run_test()
+{
+ testAddrInUse();
+}
diff --git a/netwerk/test/unit/test_alt-data_simple.js b/netwerk/test/unit/test_alt-data_simple.js
new file mode 100644
index 000000000..a14080923
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -0,0 +1,111 @@
+/**
+ * Test for the "alternative data stream" stored withing a cache entry.
+ *
+ * - we load a URL with preference for an alt data (check what we get is the raw data,
+ * since there was nothing previously cached)
+ * - we store the alt data along the channel (to the cache entry)
+ * - we flush the HTTP cache
+ * - we reload the same URL using a new channel, again prefering the alt data be loaded
+ * - this time the alt data must arive
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseContent = "response body";
+const altContent = "!@#$%^&*()";
+const altContentType = "text/binary";
+
+var servedNotModified = false;
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("ETag", "test-etag1");
+
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch(ex) {
+ var etag = "";
+ }
+
+ if (etag == "test-etag1") {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ servedNotModified = true;
+ } else {
+ response.bodyOutputStream.write(responseContent, responseContent.length);
+ }
+}
+
+function run_test()
+{
+ do_get_profile();
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(URL);
+
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(altContentType);
+
+ chan.asyncOpen2(new ChannelListener(readServerContent, null));
+ do_test_pending();
+}
+
+function readServerContent(request, buffer)
+{
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ do_check_eq(buffer, responseContent);
+ do_check_eq(cc.alternativeDataType, "");
+
+ do_execute_soon(() => {
+ var os = cc.openAlternativeOutputStream(altContentType);
+ os.write(altContent, altContent.length);
+ os.close();
+
+ do_execute_soon(flushAndOpenAltChannel);
+ });
+}
+
+// needs to be rooted
+var cacheFlushObserver = cacheFlushObserver = { observe: function() {
+ cacheFlushObserver = null;
+
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(altContentType);
+
+ chan.asyncOpen2(new ChannelListener(readAltContent, null));
+}};
+
+function flushAndOpenAltChannel()
+{
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ gc();
+ Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver);
+}
+
+function readAltContent(request, buffer)
+{
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ do_check_eq(servedNotModified, true);
+ do_check_eq(cc.alternativeDataType, altContentType);
+ do_check_eq(buffer, altContent);
+
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_alt-data_stream.js b/netwerk/test/unit/test_alt-data_stream.js
new file mode 100644
index 000000000..da3794dd0
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_stream.js
@@ -0,0 +1,120 @@
+/**
+ * Test for the "alternative data stream" stored withing a cache entry.
+ *
+ * - we load a URL with preference for an alt data (check what we get is the raw data,
+ * since there was nothing previously cached)
+ * - we write a big chunk of alt-data to the output stream
+ * - we load the URL again, expecting to get alt-data
+ * - we check that the alt-data is streamed. We should get the first chunk, then
+ * the rest of the alt-data is written, and we check that it is received in
+ * the proper order.
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseContent = "response body";
+// We need a large content in order to make sure that the IPDL stream is cut
+// into several different chunks.
+// We fill each chunk with a different character for easy debugging.
+const altContent = "a".repeat(128*1024) +
+ "b".repeat(128*1024) +
+ "c".repeat(128*1024) +
+ "d".repeat(128*1024) +
+ "e".repeat(128*1024) +
+ "f".repeat(128*1024) +
+ "g".repeat(128*1024) +
+ "h".repeat(128*1024) +
+ "i".repeat(13); // Just so the chunk size doesn't match exactly.
+
+const firstChunkSize = Math.floor(altContent.length / 4);
+const altContentType = "text/binary";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "max-age=86400");
+
+ response.bodyOutputStream.write(responseContent, responseContent.length);
+}
+
+function run_test()
+{
+ do_get_profile();
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(URL);
+
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(altContentType);
+
+ chan.asyncOpen2(new ChannelListener(readServerContent, null));
+ do_test_pending();
+}
+
+// Output stream used to write alt-data to the cache entry.
+var os;
+
+function readServerContent(request, buffer)
+{
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ do_check_eq(buffer, responseContent);
+ do_check_eq(cc.alternativeDataType, "");
+
+ do_execute_soon(() => {
+ os = cc.openAlternativeOutputStream(altContentType);
+ // Write a quarter of the alt data content
+ os.write(altContent, firstChunkSize);
+
+ do_execute_soon(openAltChannel);
+ });
+}
+
+function openAltChannel()
+{
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(altContentType);
+
+ chan.asyncOpen2(listener);
+}
+
+var listener = {
+ buffer: "",
+ onStartRequest: function(request, context) { },
+ onDataAvailable: function(request, context, stream, offset, count) {
+ let string = NetUtil.readInputStreamToString(stream, count);
+ this.buffer += string;
+
+ // XXX: this condition might be a bit volatile. If this test times out,
+ // it probably means that for some reason, the listener didn't get all the
+ // data in the first chunk.
+ if (this.buffer.length == firstChunkSize) {
+ // write the rest of the content
+ os.write(altContent.substring(firstChunkSize, altContent.length), altContent.length - firstChunkSize);
+ os.close();
+ }
+ },
+ onStopRequest: function(request, context, status) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+ do_check_eq(cc.alternativeDataType, altContentType);
+ do_check_eq(this.buffer.length, altContent.length);
+ do_check_eq(this.buffer, altContent);
+ httpServer.stop(do_test_finished);
+ },
+};
diff --git a/netwerk/test/unit/test_altsvc.js b/netwerk/test/unit/test_altsvc.js
new file mode 100644
index 000000000..7dafca028
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc.js
@@ -0,0 +1,378 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var h2Port;
+var prefs;
+var spdypref;
+var http2pref;
+var tlspref;
+var altsvcpref1;
+var altsvcpref2;
+
+// https://foo.example.com:(h2Port)
+// https://bar.example.com:(h2Port) <- invalid for bar, but ok for foo
+var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort)
+var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort)
+
+var h2FooRoute; // foo.example.com:H2PORT
+var h2BarRoute; // bar.example.com:H2PORT
+var h2Route; // :H2PORT
+var httpFooOrigin; // http://foo.exmaple.com:PORT/
+var httpsFooOrigin; // https://foo.exmaple.com:PORT/
+var httpBarOrigin; // http://bar.example.com:PORT/
+var httpsBarOrigin; // https://bar.example.com:PORT/
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ h2Port = env.get("MOZHTTP2_PORT");
+ do_check_neq(h2Port, null);
+ do_check_neq(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+ altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
+ altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+ prefs.setBoolPref("network.http.altsvc.enabled", true);
+ prefs.setBoolPref("network.http.altsvc.oe", true);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com, bar.example.com");
+
+ // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der
+ // so add that cert to the trust list as a signing cert. The same cert is used
+ // for both h2FooRoute and h2BarRoute though it is only valid for
+ // the foo.example.com domain name.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+ addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
+
+ h1Foo = new HttpServer();
+ h1Foo.registerPathHandler("/altsvc-test", h1Server);
+ h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
+ h1Foo.start(-1);
+ h1Foo.identity.setPrimary("http", "foo.example.com", h1Foo.identity.primaryPort);
+
+ h1Bar = new HttpServer();
+ h1Bar.registerPathHandler("/altsvc-test", h1Server);
+ h1Bar.start(-1);
+ h1Bar.identity.setPrimary("http", "bar.example.com", h1Bar.identity.primaryPort);
+
+ h2FooRoute = "foo.example.com:" + h2Port;
+ h2BarRoute = "bar.example.com:" + h2Port;
+ h2Route = ":" + h2Port;
+
+ httpFooOrigin = "http://foo.example.com:" + h1Foo.identity.primaryPort + "/";
+ httpsFooOrigin = "https://" + h2FooRoute + "/";
+ httpBarOrigin = "http://bar.example.com:" + h1Bar.identity.primaryPort + "/";
+ httpsBarOrigin = "https://" + h2BarRoute + "/";
+ dump ("http foo - " + httpFooOrigin + "\n" +
+ "https foo - " + httpsFooOrigin + "\n" +
+ "http bar - " + httpBarOrigin + "\n" +
+ "https bar - " + httpsBarOrigin + "\n");
+
+ doTest1();
+}
+
+function h1Server(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ try {
+ var hval = "h2=" + metadata.getHeader("x-altsvc");
+ response.setHeader("Alt-Svc", hval, false);
+ } catch (e) {}
+
+ var body = "Q: What did 0 say to 8? A: Nice Belt!\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+ response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+ var body = '{"http://foo.example.com:' + h1Foo.identity.primaryPort + '": { "tls-ports": [' + h2Port + '] }}';
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function resetPrefs() {
+ prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
+ prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
+ prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let der = readFile(certFile);
+ certdb.addCert(der, trustString, null);
+}
+
+function makeChan(origin) {
+ return NetUtil.newChannel({
+ uri: origin + "altsvc-test",
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var origin;
+var xaltsvc;
+var retryCounter = 0;
+var loadWithoutClearingMappings = false;
+var nextTest;
+var expectPass = true;
+var waitFor = 0;
+
+var Listener = function() {};
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request, ctx) {
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+
+ if (expectPass) {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+ do_check_eq(request.responseStatus, 200);
+ } else {
+ do_check_eq(Components.isSuccessCode(request.status), false);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ var routed = "";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+ do_check_eq(Components.isSuccessCode(status), expectPass);
+
+ if (waitFor != 0) {
+ do_check_eq(routed, "");
+ do_test_pending();
+ loadWithoutClearingMappings = true;
+ do_timeout(waitFor, doTest);
+ waitFor = 0;
+ xaltsvc = "NA";
+ } else if (xaltsvc == "NA") {
+ do_check_eq(routed, "");
+ nextTest();
+ } else if (routed == xaltsvc) {
+ do_check_eq(routed, xaltsvc); // always true, but a useful log
+ nextTest();
+ } else {
+ dump ("poll later for alt svc mapping\n");
+ do_test_pending();
+ loadWithoutClearingMappings = true;
+ do_timeout(500, doTest);
+ }
+
+ do_test_finished();
+ }
+};
+
+function testsDone()
+{
+ dump("testDone\n");
+ resetPrefs();
+ do_test_pending();
+ h1Foo.stop(do_test_finished);
+ do_test_pending();
+ h1Bar.stop(do_test_finished);
+}
+
+function doTest()
+{
+ dump("execute doTest " + origin + "\n");
+ var chan = makeChan(origin);
+ var listener = new Listener();
+ if (xaltsvc != "NA") {
+ chan.setRequestHeader("x-altsvc", xaltsvc, false);
+ }
+ if (loadWithoutClearingMappings) {
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ } else {
+ chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ }
+ loadWithoutClearingMappings = false;
+ chan.asyncOpen2(listener);
+}
+
+// xaltsvc is overloaded to do two things..
+// 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header
+// 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route)
+//
+// When xaltsvc is set to h2Route (i.e. :port with the implied hostname) it doesn't match the alt-used,
+// which is always explicit, so it needs to be changed after the channel is created but before the
+// listener is invoked
+
+// http://foo served from h2=:port
+function doTest1()
+{
+ dump("doTest1()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2Route;
+ nextTest = doTest2;
+ do_test_pending();
+ doTest();
+ xaltsvc = h2FooRoute;
+}
+
+// http://foo served from h2=foo:port
+function doTest2()
+{
+ dump("doTest2()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2FooRoute;
+ nextTest = doTest3;
+ do_test_pending();
+ doTest();
+}
+
+// http://foo served from h2=bar:port
+// requires cert for foo
+function doTest3()
+{
+ dump("doTest3()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h2BarRoute;
+ nextTest = doTest4;
+ do_test_pending();
+ doTest();
+}
+
+// https://bar should fail because host bar has cert for foo
+function doTest4()
+{
+ dump("doTest4()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = '';
+ expectPass = false;
+ nextTest = doTest5;
+ do_test_pending();
+ doTest();
+}
+
+// https://foo no alt-svc (just check cert setup)
+function doTest5()
+{
+ dump("doTest5()\n");
+ origin = httpsFooOrigin;
+ xaltsvc = 'NA';
+ expectPass = true;
+ nextTest = doTest6;
+ do_test_pending();
+ doTest();
+}
+
+// https://foo via bar (bar has cert for foo)
+function doTest6()
+{
+ dump("doTest6()\n");
+ origin = httpsFooOrigin;
+ xaltsvc = h2BarRoute;
+ nextTest = doTest7;
+ do_test_pending();
+ doTest();
+}
+
+// check again https://bar should fail because host bar has cert for foo
+function doTest7()
+{
+ dump("doTest7()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = '';
+ expectPass = false;
+ nextTest = doTest8;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar via h2 on bar
+// should not use TLS/h2 because h2BarRoute is not auth'd for bar
+// however the test ought to PASS (i.e. get a 200) because fallback
+// to plaintext happens.. thus the timeout
+function doTest8()
+{
+ dump("doTest8()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h2BarRoute;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest9;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar served from h2=:port, which is like the bar route in 8
+function doTest9()
+{
+ dump("doTest9()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h2Route;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = doTest10;
+ do_test_pending();
+ doTest();
+ xaltsvc = h2BarRoute;
+}
+
+// check again https://bar should fail because host bar has cert for foo
+function doTest10()
+{
+ dump("doTest10()\n");
+ origin = httpsBarOrigin;
+ xaltsvc = '';
+ expectPass = false;
+ nextTest = doTest11;
+ do_test_pending();
+ doTest();
+}
+
+// http://bar served from h2=foo, should fail because host foo only has
+// cert for foo. Fail in this case means alt-svc is not used, but content
+// is served
+function doTest11()
+{
+ dump("doTest11()\n");
+ origin = httpBarOrigin;
+ xaltsvc = h2FooRoute;
+ expectPass = true;
+ waitFor = 500;
+ nextTest = testsDone;
+ do_test_pending();
+ doTest();
+}
+
diff --git a/netwerk/test/unit/test_assoc.js b/netwerk/test/unit/test_assoc.js
new file mode 100644
index 000000000..ded2e3d5a
--- /dev/null
+++ b/netwerk/test/unit/test_assoc.js
@@ -0,0 +1,102 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var currentTestIndex = 0;
+
+XPCOMUtils.defineLazyGetter(this, "port", function() {
+ return httpserver.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ // this is valid
+ {url: "/assoc/assoctest?valid",
+ responseheader: ["Assoc-Req: GET http://localhost:" + port +
+ "/assoc/assoctest?valid",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: 0},
+
+ // this is invalid because the method is wrong
+ {url: "/assoc/assoctest?invalid",
+ responseheader: ["Assoc-Req: POST http://localhost:" + port +
+ "/assoc/assoctest?invalid",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: CL_EXPECT_LATE_FAILURE},
+
+ // this is invalid because the url is wrong
+ {url: "/assoc/assoctest?notvalid",
+ responseheader: ["Assoc-Req: GET http://localhost:" + port +
+ "/wrongpath/assoc/assoctest?notvalid",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: CL_EXPECT_LATE_FAILURE},
+
+ // this is invalid because the space between method and URL is missing
+ {url: "/assoc/assoctest?invalid2",
+ responseheader: ["Assoc-Req: GEThttp://localhost:" + port +
+ "/assoc/assoctest?invalid2",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: CL_EXPECT_LATE_FAILURE},
+ ];
+});
+
+var oldPrefVal;
+var domBranch;
+
+function setupChannel(url)
+{
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + port + url,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function startIter()
+{
+ var channel = setupChannel(tests[currentTestIndex].url);
+ channel.asyncOpen2(new ChannelListener(completeIter,
+ channel, tests[currentTestIndex].flags));
+}
+
+function completeIter(request, data, ctx)
+{
+ if (++currentTestIndex < tests.length ) {
+ startIter();
+ } else {
+ domBranch.setBoolPref("enforce", oldPrefVal);
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test()
+{
+ var prefService =
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ domBranch = prefService.getBranch("network.http.assoc-req.");
+ oldPrefVal = domBranch.getBoolPref("enforce");
+ domBranch.setBoolPref("enforce", true);
+
+ httpserver.registerPathHandler("/assoc/assoctest", handler);
+ httpserver.start(-1);
+
+ startIter();
+ do_test_pending();
+}
+
+function handler(metadata, response)
+{
+ var body = "thequickbrownfox";
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var header = tests[currentTestIndex].responseheader;
+ if (header != undefined) {
+ for (var i = 0; i < header.length; i++) {
+ var splitHdr = header[i].split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_auth_dialog_permission.js b/netwerk/test/unit/test_auth_dialog_permission.js
new file mode 100644
index 000000000..a45ef7532
--- /dev/null
+++ b/netwerk/test/unit/test_auth_dialog_permission.js
@@ -0,0 +1,255 @@
+// This file tests authentication prompt depending on pref
+// network.auth.subresource-http-auth-allow:
+// 0 - don't allow sub-resources to open HTTP authentication credentials
+// dialogs
+// 1 - allow sub-resources to open HTTP authentication credentials dialogs,
+// but don't allow it for cross-origin sub-resources
+// 2 - allow the cross-origin authentication as well.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+// Since this test creates a TYPE_DOCUMENT channel via javascript, it will
+// end up using the wrong LoadInfo constructor. Setting this pref will disable
+// the ContentPolicyType assertion in the constructor.
+prefs.setBoolPref("network.loadinfo.skip_type_assertion", true);
+
+function authHandler(metadata, response) {
+ // btoa("guest:guest"), but that function is not available here
+ var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ var body;
+ if (metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader) {
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "success";
+ } else {
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "failed";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var httpserv = new HttpServer();
+httpserv.registerPathHandler("/auth", authHandler);
+httpserv.start(-1);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return httpserv.identity.primaryPort;
+});
+
+function AuthPrompt(promptExpected) {
+ this.promptExpected = promptExpected;
+}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: function authprompt_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt: function(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword: function(title, text, realm, savePW, user, pw) {
+ do_check_true(this.promptExpected,
+ "Not expected the authentication prompt.");
+
+ user.value = this.user;
+ pw.value = this.pass;
+ return true;
+ },
+
+ promptPassword: function(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ }
+
+};
+
+function Requestor(promptExpected) {
+ this.promptExpected = promptExpected;
+}
+
+Requestor.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
+ this.prompter = new AuthPrompt(this.promptExpected);
+ return this.prompter;
+ }
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompter: null
+};
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+function makeChan(loadingUrl, url, contentPolicy) {
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri(loadingUrl);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ contentPolicyType: contentPolicy
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+}
+
+function Test(subresource_http_auth_allow_pref, loadingUri, uri, contentPolicy,
+ expectedCode) {
+ this._subresource_http_auth_allow_pref = subresource_http_auth_allow_pref;
+ this._loadingUri = loadingUri;
+ this._uri = uri;
+ this._contentPolicy = contentPolicy;
+ this._expectedCode = expectedCode;
+}
+
+Test.prototype = {
+ _subresource_http_auth_allow_pref: 1,
+ _loadingUri: null,
+ _uri: null,
+ _contentPolicy: Ci.nsIContentPolicy.TYPE_OTHER,
+ _expectedCode: 200,
+
+ onStartRequest: function(request, ctx) {
+ try {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code!");
+ }
+
+ if (!(request instanceof Components.interfaces.nsIHttpChannel)) {
+ do_throw("Expecting an HTTP channel");
+ }
+
+ do_check_eq(request.responseStatus, this._expectedCode);
+ // The request should be succeeded iff we expect 200
+ do_check_eq(request.requestSucceeded, this._expectedCode == 200);
+
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function(request, ctx, status) {
+ do_check_eq(status, Components.results.NS_ERROR_ABORT);
+
+ // Clear the auth cache.
+ Components.classes["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Components.interfaces.nsIHttpAuthManager)
+ .clearAll();
+
+ do_timeout(0, run_next_test);
+ },
+
+ run: function() {
+ dump("Run test: " + this._subresource_http_auth_allow_pref
+ + this._loadingUri
+ + this._uri
+ + this._contentPolicy
+ + this._expectedCode + " \n");
+
+ prefs.setIntPref("network.auth.subresource-http-auth-allow",
+ this._subresource_http_auth_allow_pref);
+ let chan = makeChan(this._loadingUri, this._uri, this._contentPolicy);
+ chan.notificationCallbacks = new Requestor(this._expectedCode == 200);
+ chan.asyncOpen2(this);
+ }
+};
+
+var tests = [
+ // For the next 3 tests the preference is set to 2 - allow the cross-origin
+ // authentication as well.
+
+ // A cross-origin request.
+ new Test(2, "http://example.com", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER, 200),
+ // A non cross-origin sub-resource request.
+ new Test(2, URL + "/", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER, 200),
+ // A top level document.
+ new Test(2, URL + "/auth", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_DOCUMENT, 200),
+
+ // For the next 3 tests the preference is set to 1 - allow sub-resources to
+ // open HTTP authentication credentials dialogs, but don't allow it for
+ // cross-origin sub-resources
+
+ // A cross-origin request.
+ new Test(1, "http://example.com", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER, 401),
+ // A non cross-origin sub-resource request.
+ new Test(1, URL + "/", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER, 200),
+ // A top level document.
+ new Test(1, URL + "/auth", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_DOCUMENT, 200),
+
+ // For the next 3 tests the preference is set to 0 - don't allow sub-resources
+ // to open HTTP authentication credentials dialogs.
+
+ // A cross-origin request.
+ new Test(0, "http://example.com", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER, 401),
+ // A sub-resource request.
+ new Test(0, URL + "/", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_OTHER, 401),
+ // A top level request.
+ new Test(0, URL + "/auth", URL + "/auth",
+ Ci.nsIContentPolicy.TYPE_DOCUMENT, 200),
+];
+
+function run_next_test() {
+ var nextTest = tests.shift();
+ if (!nextTest) {
+ httpserv.stop(do_test_finished);
+ return;
+ }
+
+ nextTest.run();
+}
+
+function run_test() {
+ do_test_pending();
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_auth_jar.js b/netwerk/test/unit/test_auth_jar.js
new file mode 100644
index 000000000..e3050105e
--- /dev/null
+++ b/netwerk/test/unit/test_auth_jar.js
@@ -0,0 +1,49 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function createURI(s) {
+ let service = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ return service.newURI(s, null, null);
+}
+
+function run_test() {
+ // Set up a profile.
+ do_get_profile();
+
+ var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+ const kURI1 = "http://example.com";
+ var app1 = secMan.createCodebasePrincipal(createURI(kURI1), {appId: 1});
+ var app10 = secMan.createCodebasePrincipal(createURI(kURI1),{appId: 10});
+ var app1browser = secMan.createCodebasePrincipal(createURI(kURI1), {appId: 1, inIsolatedMozBrowser: true});
+
+ var am = Cc["@mozilla.org/network/http-auth-manager;1"].
+ getService(Ci.nsIHttpAuthManager);
+ am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user", "pass", false, app1);
+ am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user3", "pass3", false, app1browser);
+ am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user2", "pass2", false, app10);
+
+ let attrs_inBrowser = JSON.stringify({ appId:1, inIsolatedMozBrowser:true });
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_inBrowser);
+
+ var domain = {value: ""}, user = {value: ""}, pass = {value: ""};
+ try {
+ am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app1browser);
+ do_check_false(true); // no identity should be present
+ } catch (x) {
+ do_check_eq(domain.value, "");
+ do_check_eq(user.value, "");
+ do_check_eq(pass.value, "");
+ }
+
+ am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app1);
+ do_check_eq(domain.value, "example.com");
+ do_check_eq(user.value, "user");
+ do_check_eq(pass.value, "pass");
+
+
+ am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app10);
+ do_check_eq(domain.value, "example.com");
+ do_check_eq(user.value, "user2");
+ do_check_eq(pass.value, "pass2");
+}
diff --git a/netwerk/test/unit/test_auth_proxy.js b/netwerk/test/unit/test_auth_proxy.js
new file mode 100644
index 000000000..ae5260ade
--- /dev/null
+++ b/netwerk/test/unit/test_auth_proxy.js
@@ -0,0 +1,399 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This tests the automatic login to the proxy with password,
+ * if the password is stored and the browser is restarted.
+ *
+ * <copied from="test_authentication.js"/>
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const FLAG_RETURN_FALSE = 1 << 0;
+const FLAG_WRONG_PASSWORD = 1 << 1;
+const FLAG_PREVIOUS_FAILED = 1 << 2;
+
+function AuthPrompt2(proxyFlags, hostFlags) {
+ this.proxyCred.flags = proxyFlags;
+ this.hostCred.flags = hostFlags;
+}
+AuthPrompt2.prototype = {
+ proxyCred : { user: "proxy", pass: "guest",
+ realmExpected: "intern", flags : 0 },
+ hostCred : { user: "host", pass: "guest",
+ realmExpected: "extern", flags : 0 },
+
+ QueryInterface: function authprompt2_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIAuthPrompt2))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ promptAuth:
+ function ap2_promptAuth(channel, encryptionLevel, authInfo)
+ {
+ try {
+
+ // never HOST and PROXY set at the same time in prompt
+ do_check_eq((authInfo.flags & Ci.nsIAuthInformation.AUTH_HOST) != 0,
+ (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) == 0);
+
+ var isProxy = (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) != 0;
+ var cred = isProxy ? this.proxyCred : this.hostCred;
+
+ dump("with flags: " +
+ ((cred.flags & FLAG_WRONG_PASSWORD) !=0 ? "wrong password" : "")+" "+
+ ((cred.flags & FLAG_PREVIOUS_FAILED) !=0 ? "previous failed" : "")+" "+
+ ((cred.flags & FLAG_RETURN_FALSE) !=0 ? "return false" : "") + "\n");
+
+ // PROXY properly set by necko (checked using realm)
+ do_check_eq(cred.realmExpected, authInfo.realm);
+
+ // PREVIOUS_FAILED properly set by necko
+ do_check_eq((cred.flags & FLAG_PREVIOUS_FAILED) != 0,
+ (authInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) != 0);
+
+ if (cred.flags & FLAG_RETURN_FALSE)
+ {
+ cred.flags |= FLAG_PREVIOUS_FAILED;
+ cred.flags &= ~FLAG_RETURN_FALSE;
+ return false;
+ }
+
+ authInfo.username = cred.user;
+ if (cred.flags & FLAG_WRONG_PASSWORD) {
+ authInfo.password = cred.pass + ".wrong";
+ cred.flags |= FLAG_PREVIOUS_FAILED;
+ // Now clear the flag to avoid an infinite loop
+ cred.flags &= ~FLAG_WRONG_PASSWORD;
+ } else {
+ authInfo.password = cred.pass;
+ cred.flags &= ~FLAG_PREVIOUS_FAILED;
+ }
+ return true;
+
+ } catch (e) { do_throw(e); }
+ },
+
+ asyncPromptAuth:
+ function ap2_async(channel, callback, context, encryptionLevel, authInfo)
+ {
+ try {
+ var me = this;
+ var allOverAndDead = false;
+ do_execute_soon(function() {
+ try {
+ if (allOverAndDead)
+ throw "already canceled";
+ var ret = me.promptAuth(channel, encryptionLevel, authInfo);
+ if (!ret)
+ callback.onAuthCancelled(context, true);
+ else
+ callback.onAuthAvailable(context, authInfo);
+ allOverAndDead = true;
+ } catch (e) { do_throw(e); }
+ });
+ return new Cancelable(function() {
+ if (allOverAndDead)
+ throw "can't cancel, already ran";
+ callback.onAuthAvailable(context, authInfo);
+ allOverAndDead = true;
+ });
+ } catch (e) { do_throw(e); }
+ }
+};
+
+function Cancelable(onCancelFunc) {
+ this.onCancelFunc = onCancelFunc;
+}
+Cancelable.prototype = {
+ QueryInterface: function cancelable_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICancelable))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ cancel: function cancel() {
+ try {
+ this.onCancelFunc();
+ } catch (e) { do_throw(e); }
+ }
+};
+
+function Requestor(proxyFlags, hostFlags) {
+ this.proxyFlags = proxyFlags;
+ this.hostFlags = hostFlags;
+}
+Requestor.prototype = {
+ QueryInterface: function requestor_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIInterfaceRequestor))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt)) {
+ dump("authprompt1 not implemented\n");
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ try {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2)
+ this.prompt2 = new AuthPrompt2(this.proxyFlags, this.hostFlags);
+ return this.prompt2;
+ } catch (e) { do_throw(e); }
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt2: null
+};
+
+var listener = {
+ expectedCode: -1, // uninitialized
+
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ // Proxy auth cancellation return failures to avoid spoofing
+ if (!Components.isSuccessCode(request.status) &&
+ (this.expectedCode != 407))
+ do_throw("Channel should have a success code!");
+
+ if (!(request instanceof Ci.nsIHttpChannel))
+ do_throw("Expecting an HTTP channel");
+
+ do_check_eq(this.expectedCode, request.responseStatus);
+ // If we expect 200, the request should have succeeded
+ do_check_eq(this.expectedCode == 200, request.requestSucceeded);
+
+ var cookie = "";
+ try {
+ cookie = request.getRequestHeader("Cookie");
+ } catch (e) { }
+ do_check_eq(cookie, "");
+
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Cr.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ do_check_eq(status, Cr.NS_ERROR_ABORT);
+
+ if (current_test < (tests.length - 1)) {
+ // First, need to clear the auth cache
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+
+ do_test_finished();
+ }
+};
+
+function makeChan(url) {
+ if (!url)
+ url = "http://somesite/";
+
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+var current_test = 0;
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", proxyAuthHandler);
+ httpserv.identity.add("http", "somesite", 80);
+ httpserv.start(-1);
+
+ const prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefs.setCharPref("network.proxy.http", "localhost");
+ prefs.setIntPref("network.proxy.http_port", httpserv.identity.primaryPort);
+ prefs.setCharPref("network.proxy.no_proxies_on", "");
+ prefs.setIntPref("network.proxy.type", 1);
+
+ // Turn off the authentication dialog blocking for this test.
+ prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+ tests[current_test]();
+}
+
+function test_proxy_returnfalse() {
+ dump("\ntest: proxy returnfalse\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0);
+ listener.expectedCode = 407; // Proxy Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_proxy_wrongpw() {
+ dump("\ntest: proxy wrongpw\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 0);
+ listener.expectedCode = 200; // Eventually OK
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
+
+function test_all_ok() {
+ dump("\ntest: all ok\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, 0);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
+
+function test_proxy_407_cookie() {
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0);
+ chan.setRequestHeader("X-Set-407-Cookie", "1", false);
+ listener.expectedCode = 407; // Proxy Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_proxy_200_cookie() {
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, 0);
+ chan.setRequestHeader("X-Set-407-Cookie", "1", false);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
+
+function test_host_returnfalse() {
+ dump("\ntest: host returnfalse\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, FLAG_RETURN_FALSE);
+ listener.expectedCode = 401; // Host Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_host_wrongpw() {
+ dump("\ntest: host wrongpw\n");
+ var chan = makeChan();
+ chan.notificationCallbacks = new Requestor(0, FLAG_WRONG_PASSWORD);
+ listener.expectedCode = 200; // Eventually OK
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
+
+function test_proxy_wrongpw_host_wrongpw() {
+ dump("\ntest: proxy wrongpw, host wrongpw\n");
+ var chan = makeChan();
+ chan.notificationCallbacks =
+ new Requestor(FLAG_WRONG_PASSWORD, FLAG_WRONG_PASSWORD);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
+
+function test_proxy_wrongpw_host_returnfalse() {
+ dump("\ntest: proxy wrongpw, host return false\n");
+ var chan = makeChan();
+ chan.notificationCallbacks =
+ new Requestor(FLAG_WRONG_PASSWORD, FLAG_RETURN_FALSE);
+ listener.expectedCode = 401; // Host Unauthorized
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
+
+var tests = [test_proxy_returnfalse, test_proxy_wrongpw, test_all_ok,
+ test_proxy_407_cookie, test_proxy_200_cookie,
+ test_host_returnfalse, test_host_wrongpw,
+ test_proxy_wrongpw_host_wrongpw, test_proxy_wrongpw_host_returnfalse];
+
+
+// PATH HANDLERS
+
+// Proxy
+function proxyAuthHandler(metadata, response) {
+ try {
+ var realm = "intern";
+ // btoa("proxy:guest"), but that function is not available here
+ var expectedHeader = "Basic cHJveHk6Z3Vlc3Q=";
+
+ var body;
+ if (metadata.hasHeader("Proxy-Authorization") &&
+ metadata.getHeader("Proxy-Authorization") == expectedHeader)
+ {
+ dump("proxy password ok\n");
+ response.setHeader("Proxy-Authenticate",
+ 'Basic realm="' + realm + '"', false);
+
+ hostAuthHandler(metadata, response);
+ }
+ else
+ {
+ dump("proxy password required\n");
+ response.setStatusLine(metadata.httpVersion, 407,
+ "Unauthorized by HTTP proxy");
+ response.setHeader("Proxy-Authenticate",
+ 'Basic realm="' + realm + '"', false);
+ if (metadata.hasHeader("X-Set-407-Cookie")) {
+ response.setHeader("Set-Cookie", "chewy", false);
+ }
+ body = "failed";
+ response.bodyOutputStream.write(body, body.length);
+ }
+ } catch (e) { do_throw(e); }
+}
+
+// Host /auth
+function hostAuthHandler(metadata, response) {
+ try {
+ var realm = "extern";
+ // btoa("host:guest"), but that function is not available here
+ var expectedHeader = "Basic aG9zdDpndWVzdA==";
+
+ var body;
+ if (metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader)
+ {
+ dump("host password ok\n");
+ response.setStatusLine(metadata.httpVersion, 200,
+ "OK, authorized for host");
+ response.setHeader("WWW-Authenticate",
+ 'Basic realm="' + realm + '"', false);
+ body = "success";
+ }
+ else
+ {
+ dump("host password required\n");
+ response.setStatusLine(metadata.httpVersion, 401,
+ "Unauthorized by HTTP server host");
+ response.setHeader("WWW-Authenticate",
+ 'Basic realm="' + realm + '"', false);
+ body = "failed";
+ }
+ response.bodyOutputStream.write(body, body.length);
+ } catch (e) { do_throw(e); }
+}
diff --git a/netwerk/test/unit/test_authentication.js b/netwerk/test/unit/test_authentication.js
new file mode 100644
index 000000000..a7e059a2b
--- /dev/null
+++ b/netwerk/test/unit/test_authentication.js
@@ -0,0 +1,2074 @@
+// This file tests authentication prompt callbacks
+// TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// Turn off the authentication dialog blocking for this test.
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return httpserv.identity.primaryPort;
+});
+
+const FLAG_RETURN_FALSE = 1 << 0;
+const FLAG_WRONG_PASSWORD = 1 << 1;
+const FLAG_BOGUS_USER = 1 << 2;
+const FLAG_PREVIOUS_FAILED = 1 << 3;
+const CROSS_ORIGIN = 1 << 4;
+
+const nsIAuthPrompt2 = Components.interfaces.nsIAuthPrompt2;
+const nsIAuthInformation = Components.interfaces.nsIAuthInformation;
+
+
+function AuthPrompt1(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt1.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: function authprompt_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword:
+ function ap1_promptUP(title, text, realm, savePW, user, pw)
+ {
+ // Note that the realm here isn't actually the realm. it's a pw mgr key.
+ do_check_eq(URL + " (" + this.expectedRealm + ")", realm);
+ if (!(this.flags & CROSS_ORIGIN)) {
+ if (text.indexOf(this.expectedRealm) == -1) {
+ do_throw("Text must indicate the realm");
+ }
+ } else {
+ if (text.indexOf(this.expectedRealm) != -1) {
+ do_throw("There should not be realm for cross origin");
+ }
+ }
+ if (text.indexOf("localhost") == -1)
+ do_throw("Text must indicate the hostname");
+ if (text.indexOf(String(PORT)) == -1)
+ do_throw("Text must indicate the port");
+ if (text.indexOf("-1") != -1)
+ do_throw("Text must contain negative numbers");
+
+ if (this.flags & FLAG_RETURN_FALSE)
+ return false;
+
+ if (this.flags & FLAG_BOGUS_USER)
+ this.user = "foo\nbar";
+
+ user.value = this.user;
+ if (this.flags & FLAG_WRONG_PASSWORD) {
+ pw.value = this.pass + ".wrong";
+ // Now clear the flag to avoid an infinite loop
+ this.flags &= ~FLAG_WRONG_PASSWORD;
+ } else {
+ pw.value = this.pass;
+ }
+ return true;
+ },
+
+ promptPassword: function ap1_promptPW(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ }
+
+};
+
+function AuthPrompt2(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt2.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: function authprompt2_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ promptAuth:
+ function ap2_promptAuth(channel, level, authInfo)
+ {
+ var isNTLM = channel.URI.path.indexOf("ntlm") != -1;
+ var isDigest = channel.URI.path.indexOf("digest") != -1;
+
+ if (isNTLM)
+ this.expectedRealm = ""; // NTLM knows no realms
+
+ do_check_eq(this.expectedRealm, authInfo.realm);
+
+ var expectedLevel = (isNTLM || isDigest) ?
+ nsIAuthPrompt2.LEVEL_PW_ENCRYPTED :
+ nsIAuthPrompt2.LEVEL_NONE;
+ do_check_eq(expectedLevel, level);
+
+ var expectedFlags = nsIAuthInformation.AUTH_HOST;
+
+ if (this.flags & FLAG_PREVIOUS_FAILED)
+ expectedFlags |= nsIAuthInformation.PREVIOUS_FAILED;
+
+ if (this.flags & CROSS_ORIGIN)
+ expectedFlags |= nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;
+
+ if (isNTLM)
+ expectedFlags |= nsIAuthInformation.NEED_DOMAIN;
+
+ const kAllKnownFlags = 63; // Don't fail test for newly added flags
+ do_check_eq(expectedFlags, authInfo.flags & kAllKnownFlags);
+
+ var expectedScheme = isNTLM ? "ntlm" : isDigest ? "digest" : "basic";
+ do_check_eq(expectedScheme, authInfo.authenticationScheme);
+
+ // No passwords in the URL -> nothing should be prefilled
+ do_check_eq(authInfo.username, "");
+ do_check_eq(authInfo.password, "");
+ do_check_eq(authInfo.domain, "");
+
+ if (this.flags & FLAG_RETURN_FALSE)
+ {
+ this.flags |= FLAG_PREVIOUS_FAILED;
+ return false;
+ }
+
+ if (this.flags & FLAG_BOGUS_USER)
+ this.user = "foo\nbar";
+
+ authInfo.username = this.user;
+ if (this.flags & FLAG_WRONG_PASSWORD) {
+ authInfo.password = this.pass + ".wrong";
+ this.flags |= FLAG_PREVIOUS_FAILED;
+ // Now clear the flag to avoid an infinite loop
+ this.flags &= ~FLAG_WRONG_PASSWORD;
+ } else {
+ authInfo.password = this.pass;
+ this.flags &= ~FLAG_PREVIOUS_FAILED;
+ }
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+function Requestor(flags, versions) {
+ this.flags = flags;
+ this.versions = versions;
+}
+
+Requestor.prototype = {
+ QueryInterface: function requestor_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function requestor_gi(iid) {
+ if (this.versions & 1 &&
+ iid.equals(Components.interfaces.nsIAuthPrompt)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt1)
+ this.prompt1 = new AuthPrompt1(this.flags);
+ return this.prompt1;
+ }
+ if (this.versions & 2 &&
+ iid.equals(Components.interfaces.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2)
+ this.prompt2 = new AuthPrompt2(this.flags);
+ return this.prompt2;
+ }
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt1: null,
+ prompt2: null
+};
+
+function RealmTestRequestor() {}
+
+RealmTestRequestor.prototype = {
+ QueryInterface: function realmtest_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function realmtest_interface(iid) {
+ if (iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
+ do_check_eq(authInfo.realm, '\"foo_bar');
+
+ return false;
+ },
+
+ asyncPromptAuth: function realmtest_async(chan, cb, ctx, lvl, info) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+var listener = {
+ expectedCode: -1, // Uninitialized
+
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ if (!Components.isSuccessCode(request.status))
+ do_throw("Channel should have a success code!");
+
+ if (!(request instanceof Components.interfaces.nsIHttpChannel))
+ do_throw("Expecting an HTTP channel");
+
+ do_check_eq(request.responseStatus, this.expectedCode);
+ // The request should be succeeded iff we expect 200
+ do_check_eq(request.requestSucceeded, this.expectedCode == 200);
+
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ do_check_eq(status, Components.results.NS_ERROR_ABORT);
+
+ if (current_test < (tests.length - 1)) {
+ // First, gotta clear the auth cache
+ Components.classes["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Components.interfaces.nsIHttpAuthManager)
+ .clearAll();
+
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+
+ do_test_finished();
+ }
+};
+
+function makeChan(url, loadingUrl) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var principal = ssm.createCodebasePrincipal(ios.newURI(loadingUrl, null, null), {});
+ return NetUtil.newChannel(
+ { uri: url, loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ contentPolicyType: Components.interfaces.nsIContentPolicy.TYPE_OTHER
+ });
+}
+
+var tests = [test_noauth, test_returnfalse1, test_wrongpw1, test_prompt1,
+ test_prompt1CrossOrigin, test_prompt2CrossOrigin,
+ test_returnfalse2, test_wrongpw2, test_prompt2, test_ntlm,
+ test_basicrealm, test_digest_noauth, test_digest,
+ test_digest_bogus_user, test_large_realm, test_large_domain];
+
+var current_test = 0;
+
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+
+ httpserv.registerPathHandler("/auth", authHandler);
+ httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
+ httpserv.registerPathHandler("/auth/realm", authRealm);
+ httpserv.registerPathHandler("/auth/digest", authDigest);
+ httpserv.registerPathHandler("/largeRealm", largeRealm);
+ httpserv.registerPathHandler("/largeDomain", largeDomain);
+
+ httpserv.start(-1);
+
+ tests[0]();
+}
+
+function test_noauth() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_returnfalse1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 1);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_wrongpw1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 1);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_prompt1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 1);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_prompt1CrossOrigin() {
+ var chan = makeChan(URL + "/auth", "http://example.org");
+
+ chan.notificationCallbacks = new Requestor(16, 1);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_prompt2CrossOrigin() {
+ var chan = makeChan(URL + "/auth", "http://example.org");
+
+ chan.notificationCallbacks = new Requestor(16, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_returnfalse2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_wrongpw2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_prompt2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_ntlm() {
+ var chan = makeChan(URL + "/auth/ntlm/simple", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_basicrealm() {
+ var chan = makeChan(URL + "/auth/realm", URL);
+
+ chan.notificationCallbacks = new RealmTestRequestor();
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_digest_noauth() {
+ var chan = makeChan(URL + "/auth/digest", URL);
+
+ //chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_digest() {
+ var chan = makeChan(URL + "/auth/digest", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_digest_bogus_user() {
+ var chan = makeChan(URL + "/auth/digest", URL);
+ chan.notificationCallbacks = new Requestor(FLAG_BOGUS_USER, 2);
+ listener.expectedCode = 401; // unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+// PATH HANDLERS
+
+// /auth
+function authHandler(metadata, response) {
+ // btoa("guest:guest"), but that function is not available here
+ var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ var body;
+ if (metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "success";
+ }
+ else
+ {
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ body = "failed";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /auth/ntlm/simple
+function authNtlmSimple(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "NTLM" /* + ' realm="secret"' */, false);
+
+ var body = "NOTE: This just sends an NTLM challenge, it never\n" +
+ "accepts the authentication. It also closes\n" +
+ "the connection after sending the challenge\n";
+
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /auth/realm
+function authRealm(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="\\"f\\oo_bar"', false);
+ var body = "success";
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+//
+// Digest functions
+//
+function bytesFromString(str) {
+ var converter =
+ Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var data = converter.convertToByteArray(str);
+ return data;
+}
+
+// return the two-digit hexadecimal code for a byte
+function toHexString(charCode) {
+ return ("0" + charCode.toString(16)).slice(-2);
+}
+
+function H(str) {
+ var data = bytesFromString(str);
+ var ch = Components.classes["@mozilla.org/security/hash;1"]
+ .createInstance(Components.interfaces.nsICryptoHash);
+ ch.init(Components.interfaces.nsICryptoHash.MD5);
+ ch.update(data, data.length);
+ var hash = ch.finish(false);
+ return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+}
+
+//
+// Digest handler
+//
+// /auth/digest
+function authDigest(metadata, response) {
+ var nonce = "6f93719059cf8d568005727f3250e798";
+ var opaque = "1234opaque1234";
+ var cnonceRE = /cnonce="(\w+)"/;
+ var responseRE = /response="(\w+)"/;
+ var usernameRE = /username="(\w+)"/;
+ var authenticate = 'Digest realm="secret", domain="/", qop=auth,' +
+ 'algorithm=MD5, nonce="' + nonce+ '" opaque="' +
+ opaque + '"';
+ var body;
+ // check creds if we have them
+ if (metadata.hasHeader("Authorization")) {
+ var auth = metadata.getHeader("Authorization");
+ var cnonce = (auth.match(cnonceRE))[1];
+ var clientDigest = (auth.match(responseRE))[1];
+ var username = (auth.match(usernameRE))[1];
+ var nc = "00000001";
+
+ if (username != "guest") {
+ response.setStatusLine(metadata.httpVersion, 400, "bad request");
+ body = "should never get here";
+ } else {
+ // see RFC2617 for the description of this calculation
+ var A1 = "guest:secret:guest";
+ var A2 = "GET:/auth/digest";
+ var noncebits = [nonce, nc, cnonce, "auth", H(A2)].join(":");
+ var digest = H([H(A1), noncebits].join(":"));
+
+ if (clientDigest == digest) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ body = "success";
+ } else {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", authenticate, false);
+ body = "auth failed";
+ }
+ }
+ } else {
+ // no header, send one
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", authenticate, false);
+ body = "failed, no header";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function largeRealm(metadata, response) {
+ // test > 32KB realm tokens
+ var body;
+
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate",
+ 'Digest realm="' +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ '", domain="foo"');
+
+ body = "need to authenticate";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function largeDomain(metadata, response) {
+ // test > 32KB domain tokens
+ var body;
+
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate",
+ 'Digest realm="foo", domain="' +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ '"');
+
+ body = "need to authenticate";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function test_large_realm() {
+ var chan = makeChan(URL + "/largeRealm", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_large_domain() {
+ var chan = makeChan(URL + "/largeDomain", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_authpromptwrapper.js b/netwerk/test/unit/test_authpromptwrapper.js
new file mode 100644
index 000000000..d8a1dc40c
--- /dev/null
+++ b/netwerk/test/unit/test_authpromptwrapper.js
@@ -0,0 +1,233 @@
+// NOTE: This tests code outside of Necko. The test still lives here because
+// the contract is part of Necko.
+
+// TODO:
+// - HTTPS
+// - Proxies
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+const nsIAuthInformation = Components.interfaces.nsIAuthInformation;
+const nsIAuthPromptAdapterFactory = Components.interfaces.nsIAuthPromptAdapterFactory;
+
+function run_test() {
+ const contractID = "@mozilla.org/network/authprompt-adapter-factory;1";
+ if (!(contractID in Components.classes)) {
+ print("No adapter factory found, skipping testing");
+ return;
+ }
+ var adapter = Components.classes[contractID].getService();
+ do_check_eq(adapter instanceof nsIAuthPromptAdapterFactory, true);
+
+ // NOTE: xpconnect lets us get away with passing an empty object here
+ // For this part of the test, we only care that this function returns
+ // success
+ do_check_neq(adapter.createAdapter({}), null);
+
+ const host = "www.mozilla.org";
+
+ var info = {
+ username: "",
+ password: "",
+ domain: "",
+
+ flags: nsIAuthInformation.AUTH_HOST,
+ authenticationScheme: "basic",
+ realm: "secretrealm"
+ };
+
+ const CALLED_PROMPT = 1 << 0;
+ const CALLED_PROMPTUP = 1 << 1;
+ const CALLED_PROMPTP = 1 << 2;
+ function Prompt1() {}
+ Prompt1.prototype = {
+ called: 0,
+ rv: true,
+
+ user: "foo\\bar",
+ pw: "bar",
+
+ scheme: "http",
+
+ QueryInterface: function authprompt_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+ this.called |= CALLED_PROMPT;
+ this.doChecks(text, realm);
+ return this.rv;
+ },
+
+ promptUsernameAndPassword:
+ function ap1_promptUP(title, text, realm, savePW, user, pw)
+ {
+ this.called |= CALLED_PROMPTUP;
+ this.doChecks(text, realm);
+ user.value = this.user;
+ pw.value = this.pw;
+ return this.rv;
+ },
+
+ promptPassword: function ap1_promptPW(title, text, realm, save, pwd) {
+ this.called |= CALLED_PROMPTP;
+ this.doChecks(text, realm);
+ pwd.value = this.pw;
+ return this.rv;
+ },
+
+ doChecks: function ap1_check(text, realm) {
+ do_check_eq(this.scheme + "://" + host + " (" + info.realm + ")", realm);
+
+ do_check_neq(text.indexOf(host), -1);
+ if (info.flags & nsIAuthInformation.ONLY_PASSWORD) {
+ // Should have the username in the text
+ do_check_neq(text.indexOf(info.username), -1);
+ } else {
+ // Make sure that we show the realm if we have one and that we don't
+ // show "" otherwise
+ if (info.realm != "")
+ do_check_neq(text.indexOf(info.realm), -1);
+ else
+ do_check_eq(text.indexOf('""'), -1);
+ // No explicit port in the URL; message should not contain -1
+ // for those cases
+ do_check_eq(text.indexOf("-1"), -1);
+ }
+ }
+ };
+
+
+ // Also have to make up a channel
+ var uri = NetUtil.newURI("http://" + host, "", null)
+ var chan = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+
+ function do_tests(expectedRV) {
+ var prompt1;
+ var wrapper;
+
+ // 1: The simple case
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ wrapper = adapter.createAdapter(prompt1);
+
+ var rv = wrapper.promptAuth(chan, 0, info);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user);
+ do_check_eq(info.password, prompt1.pw);
+ }
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 2: Only ask for a PW
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ info.flags |= nsIAuthInformation.ONLY_PASSWORD;
+
+ // Initialize the username so that the prompt can show it
+ info.username = prompt1.user;
+
+ wrapper = adapter.createAdapter(prompt1);
+ rv = wrapper.promptAuth(chan, 0, info);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user); // we initialized this
+ do_check_eq(info.password, prompt1.pw);
+ }
+
+ info.flags &= ~nsIAuthInformation.ONLY_PASSWORD;
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 3: user, pw and domain
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ info.flags |= nsIAuthInformation.NEED_DOMAIN;
+
+ wrapper = adapter.createAdapter(prompt1);
+ rv = wrapper.promptAuth(chan, 0, info);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "foo");
+ do_check_eq(info.username, "bar");
+ do_check_eq(info.password, prompt1.pw);
+ }
+
+ info.flags &= ~nsIAuthInformation.NEED_DOMAIN;
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 4: username that doesn't contain a domain
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ info.flags |= nsIAuthInformation.NEED_DOMAIN;
+
+ prompt1.user = "foo";
+
+ wrapper = adapter.createAdapter(prompt1);
+ rv = wrapper.promptAuth(chan, 0, info);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user);
+ do_check_eq(info.password, prompt1.pw);
+ }
+
+ info.flags &= ~nsIAuthInformation.NEED_DOMAIN;
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 5: FTP
+ var uri2 = NetUtil.newURI("ftp://" + host, "", null);
+ var ftpchan = NetUtil.newChannel({
+ uri: uri2,
+ loadUsingSystemPrincipal: true
+ });
+
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ prompt1.scheme = "ftp";
+
+ wrapper = adapter.createAdapter(prompt1);
+ var rv = wrapper.promptAuth(ftpchan, 0, info);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user);
+ do_check_eq(info.password, prompt1.pw);
+ }
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+ }
+ do_tests(true);
+ do_tests(false);
+}
+
diff --git a/netwerk/test/unit/test_backgroundfilesaver.js b/netwerk/test/unit/test_backgroundfilesaver.js
new file mode 100644
index 000000000..a545f596f
--- /dev/null
+++ b/netwerk/test/unit/test_backgroundfilesaver.js
@@ -0,0 +1,731 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests components that implement nsIBackgroundFileSaver.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+const BackgroundFileSaverOutputStream = Components.Constructor(
+ "@mozilla.org/network/background-file-saver;1?mode=outputstream",
+ "nsIBackgroundFileSaver");
+
+const BackgroundFileSaverStreamListener = Components.Constructor(
+ "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
+ "nsIBackgroundFileSaver");
+
+const StringInputStream = Components.Constructor(
+ "@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream",
+ "setData");
+
+const REQUEST_SUSPEND_AT = 1024 * 1024 * 4;
+const TEST_DATA_SHORT = "This test string is written to the file.";
+const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
+const TEST_FILE_NAME_2 = "test-backgroundfilesaver-2.txt";
+const TEST_FILE_NAME_3 = "test-backgroundfilesaver-3.txt";
+
+// A map of test data length to the expected SHA-256 hashes
+const EXPECTED_HASHES = {
+ // No data
+ 0 : "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ // TEST_DATA_SHORT
+ 40 : "f37176b690e8744ee990a206c086cba54d1502aa2456c3b0c84ef6345d72a192",
+ // TEST_DATA_SHORT + TEST_DATA_SHORT
+ 80 : "780c0e91f50bb7ec922cc11e16859e6d5df283c0d9470f61772e3d79f41eeb58",
+ // TEST_DATA_LONG
+ 8388608 : "e3611a47714c42bdf326acfb2eb6ed9fa4cca65cb7d7be55217770a5bf5e7ff0",
+ // TEST_DATA_LONG + TEST_DATA_LONG
+ 16777216 : "03a0db69a30140f307587ee746a539247c181bafd85b85c8516a3533c7d9ea1d"
+};
+
+const gTextDecoder = new TextDecoder();
+
+// Generate a long string of data in a moderately fast way.
+const TEST_256_CHARS = new Array(257).join("-");
+const DESIRED_LENGTH = REQUEST_SUSPEND_AT * 2;
+const TEST_DATA_LONG = new Array(1 + DESIRED_LENGTH / 256).join(TEST_256_CHARS);
+do_check_eq(TEST_DATA_LONG.length, DESIRED_LENGTH);
+
+/**
+ * Returns a reference to a temporary file. If the file is then created, it
+ * will be removed when tests in this file finish.
+ */
+function getTempFile(aLeafName) {
+ let file = FileUtils.getFile("TmpD", [aLeafName]);
+ do_register_cleanup(function GTF_cleanup() {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ });
+ return file;
+}
+
+/**
+ * Helper function for converting a binary blob to its hex equivalent.
+ *
+ * @param str
+ * String possibly containing non-printable chars.
+ * @return A hex-encoded string.
+ */
+function toHex(str) {
+ var hex = '';
+ for (var i = 0; i < str.length; i++) {
+ hex += ('0' + str.charCodeAt(i).toString(16)).slice(-2);
+ }
+ return hex;
+}
+
+/**
+ * Ensures that the given file contents are equal to the given string.
+ *
+ * @param aFile
+ * nsIFile whose contents should be verified.
+ * @param aExpectedContents
+ * String containing the octets that are expected in the file.
+ *
+ * @return {Promise}
+ * @resolves When the operation completes.
+ * @rejects Never.
+ */
+function promiseVerifyContents(aFile, aExpectedContents) {
+ let deferred = Promise.defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(aFile),
+ loadUsingSystemPrincipal: true
+ }, function(aInputStream, aStatus) {
+ do_check_true(Components.isSuccessCode(aStatus));
+ let contents = NetUtil.readInputStreamToString(aInputStream,
+ aInputStream.available());
+ if (contents.length <= TEST_DATA_SHORT.length * 2) {
+ do_check_eq(contents, aExpectedContents);
+ } else {
+ // Do not print the entire content string to the test log.
+ do_check_eq(contents.length, aExpectedContents.length);
+ do_check_true(contents == aExpectedContents);
+ }
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Waits for the given saver object to complete.
+ *
+ * @param aSaver
+ * The saver, with the output stream or a stream listener implementation.
+ * @param aOnTargetChangeFn
+ * Optional callback invoked with the target file name when it changes.
+ *
+ * @return {Promise}
+ * @resolves When onSaveComplete is called with a success code.
+ * @rejects With an exception, if onSaveComplete is called with a failure code.
+ */
+function promiseSaverComplete(aSaver, aOnTargetChangeFn) {
+ let deferred = Promise.defer();
+ aSaver.observer = {
+ onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget)
+ {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus)
+ {
+ if (Components.isSuccessCode(aStatus)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception("Saver failed.", aStatus));
+ }
+ },
+ };
+ return deferred.promise;
+}
+
+/**
+ * Feeds a string to a BackgroundFileSaverOutputStream.
+ *
+ * @param aSourceString
+ * The source data to copy.
+ * @param aSaverOutputStream
+ * The BackgroundFileSaverOutputStream to feed.
+ * @param aCloseWhenDone
+ * If true, the output stream will be closed when the copy finishes.
+ *
+ * @return {Promise}
+ * @resolves When the copy completes with a success code.
+ * @rejects With an exception, if the copy fails.
+ */
+function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) {
+ let deferred = Promise.defer();
+ let inputStream = new StringInputStream(aSourceString, aSourceString.length);
+ let copier = Cc["@mozilla.org/network/async-stream-copier;1"]
+ .createInstance(Ci.nsIAsyncStreamCopier);
+ copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true,
+ aCloseWhenDone);
+ copier.asyncCopy({
+ onStartRequest: function () { },
+ onStopRequest: function (aRequest, aContext, aStatusCode)
+ {
+ if (Components.isSuccessCode(aStatusCode)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception(aResult));
+ }
+ },
+ }, null);
+ return deferred.promise;
+}
+
+/**
+ * Feeds a string to a BackgroundFileSaverStreamListener.
+ *
+ * @param aSourceString
+ * The source data to copy.
+ * @param aSaverStreamListener
+ * The BackgroundFileSaverStreamListener to feed.
+ * @param aCloseWhenDone
+ * If true, the output stream will be closed when the copy finishes.
+ *
+ * @return {Promise}
+ * @resolves When the operation completes with a success code.
+ * @rejects With an exception, if the operation fails.
+ */
+function promisePumpToSaver(aSourceString, aSaverStreamListener,
+ aCloseWhenDone) {
+ let deferred = Promise.defer();
+ aSaverStreamListener.QueryInterface(Ci.nsIStreamListener);
+ let inputStream = new StringInputStream(aSourceString, aSourceString.length);
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Ci.nsIInputStreamPump);
+ pump.init(inputStream, -1, -1, 0, 0, true);
+ pump.asyncRead({
+ onStartRequest: function PPTS_onStartRequest(aRequest, aContext)
+ {
+ aSaverStreamListener.onStartRequest(aRequest, aContext);
+ },
+ onStopRequest: function PPTS_onStopRequest(aRequest, aContext, aStatusCode)
+ {
+ aSaverStreamListener.onStopRequest(aRequest, aContext, aStatusCode);
+ if (Components.isSuccessCode(aStatusCode)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception(aResult));
+ }
+ },
+ onDataAvailable: function PPTS_onDataAvailable(aRequest, aContext,
+ aInputStream, aOffset,
+ aCount)
+ {
+ aSaverStreamListener.onDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ },
+ }, null);
+ return deferred.promise;
+}
+
+var gStillRunning = true;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+function run_test()
+{
+ run_next_test();
+}
+
+add_task(function test_setup()
+{
+ // Wait 10 minutes, that is half of the external xpcshell timeout.
+ do_timeout(10 * 60 * 1000, function() {
+ if (gStillRunning) {
+ do_throw("Test timed out.");
+ }
+ })
+});
+
+add_task(function test_normal()
+{
+ // This test demonstrates the most basic use case.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Create the object implementing the output stream.
+ let saver = new BackgroundFileSaverOutputStream();
+
+ // Set up callbacks for completion and target file name change.
+ let receivedOnTargetChange = false;
+ function onTargetChange(aTarget) {
+ do_check_true(destFile.equals(aTarget));
+ receivedOnTargetChange = true;
+ }
+ let completionPromise = promiseSaverComplete(saver, onTargetChange);
+
+ // Set the target file.
+ saver.setTarget(destFile, false);
+
+ // Write some data and close the output stream.
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ // Indicate that we are ready to finish, and wait for a successful callback.
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Only after we receive the completion notification, we can also be sure that
+ // we've received the target file name change notification before it.
+ do_check_true(receivedOnTargetChange);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_combinations()
+{
+ let initialFile = getTempFile(TEST_FILE_NAME_1);
+ let renamedFile = getTempFile(TEST_FILE_NAME_2);
+
+ // Keep track of the current file.
+ let currentFile = null;
+ function onTargetChange(aTarget) {
+ currentFile = null;
+ do_print("Target file changed to: " + aTarget.leafName);
+ currentFile = aTarget;
+ }
+
+ // Tests various combinations of events and behaviors for both the stream
+ // listener and the output stream implementations.
+ for (let testFlags = 0; testFlags < 32; testFlags++) {
+ let keepPartialOnFailure = !!(testFlags & 1);
+ let renameAtSomePoint = !!(testFlags & 2);
+ let cancelAtSomePoint = !!(testFlags & 4);
+ let useStreamListener = !!(testFlags & 8);
+ let useLongData = !!(testFlags & 16);
+
+ let startTime = Date.now();
+ do_print("Starting keepPartialOnFailure = " + keepPartialOnFailure +
+ ", renameAtSomePoint = " + renameAtSomePoint +
+ ", cancelAtSomePoint = " + cancelAtSomePoint +
+ ", useStreamListener = " + useStreamListener +
+ ", useLongData = " + useLongData);
+
+ // Create the object and register the observers.
+ currentFile = null;
+ let saver = useStreamListener
+ ? new BackgroundFileSaverStreamListener()
+ : new BackgroundFileSaverOutputStream();
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver, onTargetChange);
+
+ // Start feeding the first chunk of data to the saver. In case we are using
+ // the stream listener, we only write one chunk.
+ let testData = useLongData ? TEST_DATA_LONG : TEST_DATA_SHORT;
+ let feedPromise = useStreamListener
+ ? promisePumpToSaver(testData + testData, saver)
+ : promiseCopyToSaver(testData, saver, false);
+
+ // Set a target output file.
+ saver.setTarget(initialFile, keepPartialOnFailure);
+
+ // Wait for the first chunk of data to be copied.
+ yield feedPromise;
+
+ if (renameAtSomePoint) {
+ saver.setTarget(renamedFile, keepPartialOnFailure);
+ }
+
+ if (cancelAtSomePoint) {
+ saver.finish(Cr.NS_ERROR_FAILURE);
+ }
+
+ // Feed the second chunk of data to the saver.
+ if (!useStreamListener) {
+ yield promiseCopyToSaver(testData, saver, true);
+ }
+
+ // Wait for completion, and ensure we succeeded or failed as expected.
+ if (!cancelAtSomePoint) {
+ saver.finish(Cr.NS_OK);
+ }
+ try {
+ yield completionPromise;
+ if (cancelAtSomePoint) {
+ do_throw("Failure expected.");
+ }
+ } catch (ex if cancelAtSomePoint && ex.result == Cr.NS_ERROR_FAILURE) { }
+
+ if (!cancelAtSomePoint) {
+ // In this case, the file must exist.
+ do_check_true(currentFile.exists());
+ let expectedContents = testData + testData;
+ yield promiseVerifyContents(currentFile, expectedContents);
+ do_check_eq(EXPECTED_HASHES[expectedContents.length],
+ toHex(saver.sha256Hash));
+ currentFile.remove(false);
+
+ // If the target was really renamed, the old file should not exist.
+ if (renamedFile.equals(currentFile)) {
+ do_check_false(initialFile.exists());
+ }
+ } else if (!keepPartialOnFailure) {
+ // In this case, the file must not exist.
+ do_check_false(initialFile.exists());
+ do_check_false(renamedFile.exists());
+ } else {
+ // In this case, the file may or may not exist, because canceling can
+ // interrupt the asynchronous operation at any point, even before the file
+ // has been created for the first time.
+ if (initialFile.exists()) {
+ initialFile.remove(false);
+ }
+ if (renamedFile.exists()) {
+ renamedFile.remove(false);
+ }
+ }
+
+ do_print("Test case completed in " + (Date.now() - startTime) + " ms.");
+ }
+});
+
+add_task(function test_setTarget_after_close_stream()
+{
+ // This test checks the case where we close the output stream before we call
+ // the setTarget method. All the data should be buffered and written anyway.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver);
+
+ // Copy some data to the output stream of the file saver. This data must
+ // be shorter than the internal component's pipe buffer for the test to
+ // succeed, because otherwise the test would block waiting for the write to
+ // complete.
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ // Set the target file and wait for the output to finish.
+ saver.setTarget(destFile, false);
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
+ do_check_eq(EXPECTED_HASHES[TEST_DATA_SHORT.length],
+ toHex(saver.sha256Hash));
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_setTarget_fast()
+{
+ // This test checks a fast rename of the target file.
+ let destFile1 = getTempFile(TEST_FILE_NAME_1);
+ let destFile2 = getTempFile(TEST_FILE_NAME_2);
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ // Set the initial name after the stream is closed, then rename immediately.
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+ saver.setTarget(destFile1, false);
+ saver.setTarget(destFile2, false);
+
+ // Wait for all the operations to complete.
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results and clean up.
+ do_check_false(destFile1.exists());
+ yield promiseVerifyContents(destFile2, TEST_DATA_SHORT);
+ destFile2.remove(false);
+});
+
+add_task(function test_setTarget_multiple()
+{
+ // This test checks multiple renames of the target file.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ // Rename both before and after the stream is closed.
+ saver.setTarget(getTempFile(TEST_FILE_NAME_2), false);
+ saver.setTarget(getTempFile(TEST_FILE_NAME_3), false);
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+ saver.setTarget(getTempFile(TEST_FILE_NAME_2), false);
+ saver.setTarget(destFile, false);
+
+ // Wait for all the operations to complete.
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results and clean up.
+ do_check_false(getTempFile(TEST_FILE_NAME_2).exists());
+ do_check_false(getTempFile(TEST_FILE_NAME_3).exists());
+ yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
+ destFile.remove(false);
+});
+
+add_task(function test_enableAppend()
+{
+ // This test checks append mode with hashing disabled.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableAppend();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver(TEST_DATA_LONG, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ let expectedContents = (i == 0 ? TEST_DATA_LONG
+ : TEST_DATA_LONG + TEST_DATA_LONG);
+ yield promiseVerifyContents(destFile, expectedContents);
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_enableAppend_setTarget_fast()
+{
+ // This test checks a fast rename of the target file in append mode.
+ let destFile1 = getTempFile(TEST_FILE_NAME_1);
+ let destFile2 = getTempFile(TEST_FILE_NAME_2);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableAppend();
+ let completionPromise = promiseSaverComplete(saver);
+
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ // The first time, we start appending to the first file and rename to the
+ // second file. The second time, we start appending to the second file,
+ // that was created the first time, and rename back to the first file.
+ let firstFile = (i == 0) ? destFile1 : destFile2;
+ let secondFile = (i == 0) ? destFile2 : destFile1;
+ saver.setTarget(firstFile, false);
+ saver.setTarget(secondFile, false);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ do_check_false(firstFile.exists());
+ let expectedContents = (i == 0 ? TEST_DATA_SHORT
+ : TEST_DATA_SHORT + TEST_DATA_SHORT);
+ yield promiseVerifyContents(secondFile, expectedContents);
+ }
+
+ // Clean up.
+ destFile1.remove(false);
+});
+
+add_task(function test_enableAppend_hash()
+{
+ // This test checks append mode, also verifying that the computed hash
+ // includes the contents of the existing data.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test the case where the file does not already exists first, then the case
+ // where the file already exists.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ saver.enableAppend();
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver(TEST_DATA_LONG, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ let expectedContents = (i == 0 ? TEST_DATA_LONG
+ : TEST_DATA_LONG + TEST_DATA_LONG);
+ yield promiseVerifyContents(destFile, expectedContents);
+ do_check_eq(EXPECTED_HASHES[expectedContents.length],
+ toHex(saver.sha256Hash));
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_finish_only()
+{
+ // This test checks creating the object and doing nothing.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+ let saver = new BackgroundFileSaverOutputStream();
+ function onTargetChange(aTarget) {
+ do_throw("Should not receive the onTargetChange notification.");
+ }
+ let completionPromise = promiseSaverComplete(saver, onTargetChange);
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+});
+
+add_task(function test_empty()
+{
+ // This test checks we still create an empty file when no data is fed.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver("", saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ do_check_true(destFile.exists());
+ do_check_eq(destFile.fileSize, 0);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_empty_hash()
+{
+ // This test checks the hash of an empty file, both in normal and append mode.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ // Test normal mode first, then append mode.
+ for (let i = 0; i < 2; i++) {
+ let saver = new BackgroundFileSaverOutputStream();
+ if (i == 1) {
+ saver.enableAppend();
+ }
+ saver.enableSha256();
+ let completionPromise = promiseSaverComplete(saver);
+
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver("", saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ do_check_eq(destFile.fileSize, 0);
+ do_check_eq(EXPECTED_HASHES[0], toHex(saver.sha256Hash));
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_invalid_hash()
+{
+ let saver = new BackgroundFileSaverStreamListener();
+ let completionPromise = promiseSaverComplete(saver);
+ // We shouldn't be able to get the hash if hashing hasn't been enabled
+ try {
+ let hash = saver.sha256Hash;
+ do_throw("Shouldn't be able to get hash if hashing not enabled");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+ // Enable hashing, but don't feed any data to saver
+ saver.enableSha256();
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+ saver.setTarget(destFile, false);
+ // We don't wait on promiseSaverComplete, so the hash getter can run before
+ // or after onSaveComplete is called. However, the expected behavior is the
+ // same in both cases since the hash is only valid when the save completes
+ // successfully.
+ saver.finish(Cr.NS_ERROR_FAILURE);
+ try {
+ let hash = saver.sha256Hash;
+ do_throw("Shouldn't be able to get hash if save did not succeed");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+ // Wait for completion so that the worker thread finishes dealing with the
+ // target file. We expect it to fail.
+ try {
+ yield completionPromise;
+ do_throw("completionPromise should throw");
+ } catch (ex if ex.result == Cr.NS_ERROR_FAILURE) { }
+});
+
+add_task(function test_signature()
+{
+ // Check that we get a signature if the saver is finished.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ try {
+ let signatureInfo = saver.signatureInfo;
+ do_throw("Can't get signature if saver is not complete");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+
+ saver.enableSignatureInfo();
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+ yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
+
+ // signatureInfo is an empty nsIArray
+ do_check_eq(0, saver.signatureInfo.length);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_signature_not_enabled()
+{
+ // Check that we get a signature if the saver is finished on Windows.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+ try {
+ let signatureInfo = saver.signatureInfo;
+ do_throw("Can't get signature if not enabled");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_teardown()
+{
+ gStillRunning = false;
+});
diff --git a/netwerk/test/unit/test_be_conservative.js b/netwerk/test/unit/test_be_conservative.js
new file mode 100644
index 000000000..2c6ac46ad
--- /dev/null
+++ b/netwerk/test/unit/test_be_conservative.js
@@ -0,0 +1,213 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+
+// Tests that nsIHttpChannelInternal.beConservative correctly limits the use of
+// advanced TLS features that may cause compatibility issues. Does so by
+// starting a TLS server that requires the advanced features and then ensuring
+// that a client that is set to be conservative will fail when connecting.
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+ Cu.import("resource://gre/modules/Promise.jsm", {});
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+function getCert() {
+ let deferred = promise.defer();
+ let certService = Cc["@mozilla.org/security/local-cert-service;1"]
+ .getService(Ci.nsILocalCertService);
+ certService.getOrCreateCert("beConservative-test", {
+ handleCert: function(c, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(c);
+ }
+ });
+ return deferred.promise;
+}
+
+class InputStreamCallback {
+ constructor(output) {
+ this.output = output;
+ this.stopped = false;
+ }
+
+ onInputStreamReady(stream) {
+ do_print("input stream ready");
+ if (this.stopped) {
+ do_print("input stream callback stopped - bailing");
+ return;
+ }
+ let available = 0;
+ try {
+ available = stream.available();
+ } catch (e) {
+ // onInputStreamReady may fire when the stream has been closed.
+ equal(e.result, Cr.NS_BASE_STREAM_CLOSED,
+ "error should be NS_BASE_STREAM_CLOSED");
+ }
+ if (available > 0) {
+ let request = NetUtil.readInputStreamToString(stream, available,
+ { charset: "utf8"});
+ ok(request.startsWith("GET / HTTP/1.1\r\n"),
+ "Should get a simple GET / HTTP/1.1 request");
+ let response = "HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 2\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\nOK";
+ let written = this.output.write(response, response.length);
+ equal(written, response.length,
+ "should have been able to write entire response");
+ }
+ this.output.close();
+ do_print("done with input stream ready");
+ }
+
+ stop() {
+ this.stopped = true;
+ }
+}
+
+class TLSServerSecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ this.callbacks = [];
+ this.stopped = false;
+ }
+
+ onHandshakeDone(socket, status) {
+ do_print("TLS handshake done");
+ do_print(`TLS version used: ${status.tlsVersionUsed}`);
+
+ if (this.stopped) {
+ do_print("handshake done callback stopped - closing streams and bailing");
+ this.input.close();
+ this.output.close();
+ return;
+ }
+
+ let callback = new InputStreamCallback(this.output);
+ this.callbacks.push(callback);
+ this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
+ }
+
+ stop() {
+ this.stopped = true;
+ this.callbacks.forEach((callback) => {
+ callback.stop();
+ });
+ }
+}
+
+class ServerSocketListener {
+ constructor() {
+ this.securityObservers = [];
+ }
+
+ onSocketAccepted(socket, transport) {
+ do_print("accepted TLS client connection");
+ let connectionInfo = transport.securityInfo
+ .QueryInterface(Ci.nsITLSServerConnectionInfo);
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ let securityObserver = new TLSServerSecurityObserver(input, output);
+ this.securityObservers.push(securityObserver);
+ connectionInfo.setSecurityObserver(securityObserver);
+ }
+
+ // For some reason we get input stream callback events after we've stopped
+ // listening, so this ensures we just drop those events.
+ onStopListening() {
+ do_print("onStopListening");
+ this.securityObservers.forEach((observer) => {
+ observer.stop();
+ });
+ }
+}
+
+function startServer(cert, minServerVersion, maxServerVersion) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"]
+ .createInstance(Ci.nsITLSServerSocket);
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+ tlsServer.setVersionRange(minServerVersion, maxServerVersion);
+ tlsServer.setSessionCache(false);
+ tlsServer.setSessionTickets(false);
+ tlsServer.asyncListen(new ServerSocketListener());
+ return tlsServer;
+}
+
+const hostname = "example.com"
+
+function storeCertOverride(port, cert) {
+ let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(hostname, port, cert,
+ overrideBits, true);
+}
+
+function startClient(port, beConservative, expectSuccess) {
+ let req = new XMLHttpRequest();
+ req.open("GET", `https://${hostname}:${port}`);
+ let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.beConservative = beConservative;
+ let deferred = promise.defer();
+ req.onload = () => {
+ ok(expectSuccess,
+ `should ${expectSuccess ? "" : "not "}have gotten load event`);
+ equal(req.responseText, "OK", "response text should be 'OK'");
+ deferred.resolve();
+ };
+ req.onerror = () => {
+ ok(!expectSuccess,
+ `should ${!expectSuccess ? "" : "not "}have gotten an error`);
+ deferred.resolve();
+ };
+
+ req.send();
+ return deferred.promise;
+}
+
+add_task(function*() {
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ Services.prefs.setCharPref("network.dns.localDomains", hostname);
+ let cert = yield getCert();
+
+ // First run a server that accepts TLS 1.2 and 1.3. A conservative client
+ // should succeed in connecting.
+ let server = startServer(cert, Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3);
+ storeCertOverride(server.port, cert);
+ yield startClient(server.port, true /*be conservative*/,
+ true /*should succeed*/);
+ server.close();
+
+ // Now run a server that only accepts TLS 1.3. A conservative client will not
+ // succeed in this case.
+ server = startServer(cert, Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+ Ci.nsITLSClientStatus.TLS_VERSION_1_3);
+ storeCertOverride(server.port, cert);
+ yield startClient(server.port, true /*be conservative*/,
+ false /*should fail*/);
+
+ // However, a non-conservative client should succeed.
+ yield startClient(server.port, false /*don't be conservative*/,
+ true /*should succeed*/);
+ server.close();
+});
+
+do_register_cleanup(function() {
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
diff --git a/netwerk/test/unit/test_bug1064258.js b/netwerk/test/unit/test_bug1064258.js
new file mode 100644
index 000000000..3f76837ae
--- /dev/null
+++ b/netwerk/test/unit/test_bug1064258.js
@@ -0,0 +1,153 @@
+/**
+ * Check how nsICachingChannel.cacheOnlyMetadata works.
+ * - all channels involved in this test are set cacheOnlyMetadata = true
+ * - do a previously uncached request for a long living content
+ * - check we have downloaded the content from the server (channel provides it)
+ * - check the entry has metadata, but zero-length content
+ * - load the same URL again, now cached
+ * - check the channel is giving no content (no call to OnDataAvailable) but succeeds
+ * - repeat again, but for a different URL that is not cached (immediately expires)
+ * - only difference is that we get a newer version of the content from the server during the second request
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody1 = "response body 1";
+const responseBody2a = "response body 2a";
+const responseBody2b = "response body 2b";
+
+function contentHandler1(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-control", "max-age=999999");
+ response.bodyOutputStream.write(responseBody1, responseBody1.length);
+}
+
+var content2passCount = 0;
+
+function contentHandler2(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-control", "no-cache");
+ switch (content2passCount++) {
+ case 0:
+ response.setHeader("ETag", "testetag");
+ response.bodyOutputStream.write(responseBody2a, responseBody2a.length);
+ break;
+ case 1:
+ do_check_true(metadata.hasHeader("If-None-Match"));
+ do_check_eq(metadata.getHeader("If-None-Match"), "testetag");
+ response.bodyOutputStream.write(responseBody2b, responseBody2b.length);
+ break;
+ default:
+ throw "Unexpected request in the test";
+ }
+}
+
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content1", contentHandler1);
+ httpServer.registerPathHandler("/content2", contentHandler2);
+ httpServer.start(-1);
+
+ run_test_content1a();
+ do_test_pending();
+}
+
+function run_test_content1a()
+{
+ var chan = make_channel(URL + "/content1");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener1a, null));
+}
+
+function contentListener1a(request, buffer)
+{
+ do_check_eq(buffer, responseBody1);
+
+ asyncOpenCacheEntry(URL + "/content1", "disk", 0, null, cacheCheck1)
+}
+
+function cacheCheck1(status, entry)
+{
+ do_check_eq(status, 0);
+ do_check_eq(entry.dataSize, 0);
+ try {
+ do_check_neq(entry.getMetaDataElement("response-head"), null);
+ }
+ catch (ex) {
+ do_throw("Missing response head");
+ }
+
+ var chan = make_channel(URL + "/content1");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener1b, null, CL_IGNORE_CL));
+}
+
+function contentListener1b(request, buffer)
+{
+ request.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(request.requestMethod, "GET");
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(request.getResponseHeader("Cache-control"), "max-age=999999");
+
+ do_check_eq(buffer, "");
+ run_test_content2a();
+}
+
+// Now same set of steps but this time for an immediately expiring content.
+
+function run_test_content2a()
+{
+ var chan = make_channel(URL + "/content2");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener2a, null));
+}
+
+function contentListener2a(request, buffer)
+{
+ do_check_eq(buffer, responseBody2a);
+
+ asyncOpenCacheEntry(URL + "/content2", "disk", 0, null, cacheCheck2)
+}
+
+function cacheCheck2(status, entry)
+{
+ do_check_eq(status, 0);
+ do_check_eq(entry.dataSize, 0);
+ try {
+ do_check_neq(entry.getMetaDataElement("response-head"), null);
+ do_check_true(entry.getMetaDataElement("response-head").match('Etag: testetag'));
+ }
+ catch (ex) {
+ do_throw("Missing response head");
+ }
+
+ var chan = make_channel(URL + "/content2");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener2b, null));
+}
+
+function contentListener2b(request, buffer)
+{
+ do_check_eq(buffer, responseBody2b);
+
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_bug1195415.js b/netwerk/test/unit/test_bug1195415.js
new file mode 100644
index 000000000..b97d209d3
--- /dev/null
+++ b/netwerk/test/unit/test_bug1195415.js
@@ -0,0 +1,116 @@
+// Test for bug 1195415
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+
+ // NON-UNICODE
+ var uri = ios.newURI("http://foo.com/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com");
+ uri.port = 90;
+ var prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com:90");
+ do_check_eq(prin.origin, "http://foo.com:90");
+
+ uri = ios.newURI("http://foo.com:10/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:10");
+ uri.port = 500;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com:500");
+ do_check_eq(prin.origin, "http://foo.com:500");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:5000");
+ uri.port = 20;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com:20");
+ do_check_eq(prin.origin, "http://foo.com:20");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:5000");
+ uri.port = -1;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com");
+ do_check_eq(prin.origin, "http://foo.com");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:5000");
+ uri.port = 80;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com");
+ do_check_eq(prin.origin, "http://foo.com");
+
+ // UNICODE
+ uri = ios.newURI("http://jos\u00e9.example.net.ch/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ uri.port = 90;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:90");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch:90");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:10/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:10");
+ uri.port = 500;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:500");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch:500");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri.port = 20;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:20");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch:20");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri.port = -1;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri.port = 80;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch");
+
+ // ipv6
+ uri = ios.newURI("http://[123:45::678]/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]");
+ uri.port = 90;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:90");
+ do_check_eq(prin.origin, "http://[123:45::678]:90");
+
+ uri = ios.newURI("http://[123:45::678]:10/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:10");
+ uri.port = 500;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:500");
+ do_check_eq(prin.origin, "http://[123:45::678]:500");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:5000");
+ uri.port = 20;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:20");
+ do_check_eq(prin.origin, "http://[123:45::678]:20");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:5000");
+ uri.port = -1;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]");
+ do_check_eq(prin.origin, "http://[123:45::678]");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:5000");
+ uri.port = 80;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]");
+ do_check_eq(prin.origin, "http://[123:45::678]");
+}
diff --git a/netwerk/test/unit/test_bug1218029.js b/netwerk/test/unit/test_bug1218029.js
new file mode 100644
index 000000000..cbab52797
--- /dev/null
+++ b/netwerk/test/unit/test_bug1218029.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var tests = [
+ {data: '', chunks: [], status: Cr.NS_OK, consume: [],
+ dataChunks: ['']},
+ {data: 'TWO-PARTS', chunks: [4, 5], status: Cr.NS_OK, consume: [4, 5],
+ dataChunks: ['TWO-', 'PARTS', '']},
+ {data: 'TWO-PARTS', chunks: [4, 5], status: Cr.NS_OK, consume: [0, 0],
+ dataChunks: ['TWO-', 'TWO-PARTS', 'TWO-PARTS']},
+ {data: '3-PARTS', chunks: [1, 1, 5], status: Cr.NS_OK, consume: [0, 2, 5],
+ dataChunks: ['3', '3-', 'PARTS', '']},
+ {data: 'ALL-AT-ONCE', chunks: [11], status: Cr.NS_OK, consume: [0],
+ dataChunks: ['ALL-AT-ONCE', 'ALL-AT-ONCE']},
+ {data: 'ALL-AT-ONCE', chunks: [11], status: Cr.NS_OK, consume: [11],
+ dataChunks: ['ALL-AT-ONCE', '']},
+ {data: 'ERROR', chunks: [1], status: Cr.NS_ERROR_OUT_OF_MEMORY, consume: [0],
+ dataChunks: ['E', 'E']}
+];
+
+/**
+ * @typedef TestData
+ * @property {string} data - data for the test.
+ * @property {Array} chunks - lengths of the chunks that are incrementally sent
+ * to the loader.
+ * @property {number} status - final status sent on onStopRequest.
+ * @property {Array} consume - lengths of consumed data that is reported at
+ * the onIncrementalData callback.
+ * @property {Array} dataChunks - data chunks that are reported at the
+ * onIncrementalData and onStreamComplete callbacks.
+ */
+
+function execute_test(test) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.data = test.data;
+
+ let channel = {
+ contentLength: -1,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ let chunkIndex = 0;
+
+ let observer = {
+ onStreamComplete: function(loader, context, status, length, data) {
+ equal(chunkIndex, test.dataChunks.length - 1);
+ var expectedChunk = test.dataChunks[chunkIndex];
+ equal(length, expectedChunk.length);
+ equal(String.fromCharCode.apply(null, data), expectedChunk);
+
+ equal(status, test.status);
+ },
+ onIncrementalData: function (loader, context, length, data, consumed) {
+ ok(chunkIndex < test.dataChunks.length - 1);
+ var expectedChunk = test.dataChunks[chunkIndex];
+ equal(length, expectedChunk.length);
+ equal(String.fromCharCode.apply(null, data), expectedChunk);
+
+ consumed.value = test.consume[chunkIndex];
+ chunkIndex++;
+ },
+ QueryInterface:
+ XPCOMUtils.generateQI([Ci.nsIIncrementalStreamLoaderObserver])
+ };
+
+ let listener = Cc["@mozilla.org/network/incremental-stream-loader;1"]
+ .createInstance(Ci.nsIIncrementalStreamLoader);
+ listener.init(observer);
+
+ listener.onStartRequest(channel, null);
+ var offset = 0;
+ test.chunks.forEach(function (chunkLength) {
+ listener.onDataAvailable(channel, null, stream, offset, chunkLength);
+ offset += chunkLength;
+ });
+ listener.onStopRequest(channel, null, test.status);
+}
+
+function run_test() {
+ tests.forEach(execute_test);
+}
diff --git a/netwerk/test/unit/test_bug1279246.js b/netwerk/test/unit/test_bug1279246.js
new file mode 100644
index 000000000..8111a6c4b
--- /dev/null
+++ b/netwerk/test/unit/test_bug1279246.js
@@ -0,0 +1,97 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var pass = 0;
+var responseBody = [0x0B, 0x02, 0x80, 0x74, 0x65, 0x73, 0x74, 0x0A, 0x03];
+var responseLen = 5;
+var testUrl = "/test/brotli";
+
+
+function setupChannel() {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + testUrl,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function Listener() {}
+
+Listener.prototype = {
+ _buffer: null,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, ctx) {
+ do_check_eq(request.status, Cr.NS_OK);
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, cx, stream, offset, cnt) {
+ if (pass == 0) {
+ this._buffer = this._buffer.concat(read_stream(stream, cnt));
+ } else {
+ request.QueryInterface(Ci.nsICachingChannel);
+ if (!request.isFromCache()) {
+ do_throw("Response is not from the cache");
+ }
+
+ request.cancel(Cr.NS_ERROR_ABORT);
+ }
+ },
+
+ onStopRequest: function(request, ctx, status) {
+ if (pass == 0) {
+ do_check_eq(this._buffer.length, responseLen);
+ pass++;
+
+ var channel = setupChannel();
+ channel.loadFlags = Ci.nsIRequest.VALIDATE_NEVER;
+ channel.asyncOpen2(new Listener());
+ } else {
+ httpserver.stop(do_test_finished);
+ prefs.setCharPref("network.http.accept-encoding", cePref);
+ }
+ }
+};
+
+var prefs;
+var cePref;
+function run_test() {
+ do_get_profile();
+
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ cePref = prefs.getCharPref("network.http.accept-encoding");
+ prefs.setCharPref("network.http.accept-encoding", "gzip, deflate, br");
+
+ httpserver.registerPathHandler(testUrl, handler);
+ httpserver.start(-1);
+
+ var channel = setupChannel();
+ channel.asyncOpen2(new Listener());
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ do_check_eq(pass, 0); // the second response must be server from the cache
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "br", false);
+ response.setHeader("Content-Length", "" + responseBody.length, false);
+
+ var bos = Components.classes["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(responseBody, responseBody.length);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_bug203271.js b/netwerk/test/unit/test_bug203271.js
new file mode 100644
index 000000000..34463811e
--- /dev/null
+++ b/netwerk/test/unit/test_bug203271.js
@@ -0,0 +1,177 @@
+//
+// Tests if a response with an Expires-header in the past
+// and Cache-Control: max-age in the future works as
+// specified in RFC 2616 section 14.9.3 by letting max-age
+// take precedence
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+const BUGID = "203271";
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ // original problem described in bug#203271
+ {url: "/precedence", server: "0", expected: "0",
+ responseheader: [ "Expires: " + getDateString(-1),
+ "Cache-Control: max-age=3600"]},
+
+ {url: "/precedence?0", server: "0", expected: "0",
+ responseheader: [ "Cache-Control: max-age=3600",
+ "Expires: " + getDateString(-1)]},
+
+ // max-age=1s, expires=1 year from now
+ {url: "/precedence?1", server: "0", expected: "0",
+ responseheader: [ "Expires: " + getDateString(1),
+ "Cache-Control: max-age=1"]},
+
+ // expires=now
+ {url: "/precedence?2", server: "0", expected: "0",
+ responseheader: [ "Expires: " + getDateString(0)]},
+
+ // max-age=1s
+ {url: "/precedence?3", server: "0", expected: "0",
+ responseheader: ["Cache-Control: max-age=1"]},
+
+ // The test below is the example from
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=203271#c27
+ //
+ // max-age=2592000s (1 month), expires=1 year from now, date=1 year ago
+ {url: "/precedence?4", server: "0", expected: "0",
+ responseheader: [ "Cache-Control: private, max-age=2592000",
+ "Expires: " + getDateString(+1)],
+ explicitDate: getDateString(-1)},
+
+ // The two tests below are also examples of clocks really out of synch
+ // max-age=1s, date=1 year from now
+ {url: "/precedence?5", server: "0", expected: "0",
+ responseheader: [ "Cache-Control: max-age=1"],
+ explicitDate: getDateString(1)},
+
+ // max-age=60s, date=1 year from now
+ {url: "/precedence?6", server: "0", expected: "0",
+ responseheader: [ "Cache-Control: max-age=60"],
+ explicitDate: getDateString(1)},
+
+ // this is just to get a pause of 3s to allow cache-entries to expire
+ {url: "/precedence?999", server: "0", expected: "0", delay: "3000"},
+
+ // Below are the cases which actually matters
+ {url: "/precedence", server: "1", expected: "0"}, // should be cached
+
+ {url: "/precedence?0", server: "1", expected: "0"}, // should be cached
+
+ {url: "/precedence?1", server: "1", expected: "1"}, // should have expired
+
+ {url: "/precedence?2", server: "1", expected: "1"}, // should have expired
+
+ {url: "/precedence?3", server: "1", expected: "1"}, // should have expired
+
+ {url: "/precedence?4", server: "1", expected: "1"}, // should have expired
+
+ {url: "/precedence?5", server: "1", expected: "1"}, // should have expired
+
+ {url: "/precedence?6", server: "1", expected: "0"}, // should be cached
+
+];
+
+function logit(i, data, ctx) {
+ dump("requested [" + tests[i].server + "] " +
+ "got [" + data + "] " +
+ "expected [" + tests[i].expected + "]");
+
+ if (tests[i].responseheader)
+ dump("\t[" + tests[i].responseheader + "]");
+ dump("\n");
+ // Dump all response-headers
+ dump("\n===================================\n")
+ ctx.visitResponseHeaders({
+ visitHeader: function(key, val) {
+ dump("\t" + key + ":"+val + "\n");
+ }}
+ );
+ dump("===================================\n")
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET"; // default value, just being paranoid...
+ httpChan.setRequestHeader("x-request", value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server);
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, channel));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data, ctx);
+ do_check_eq(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ var delay = tests[index++].delay;
+ if (delay) {
+ do_timeout(delay, triggerNextTest);
+ } else {
+ triggerNextTest();
+ }
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/precedence", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ triggerNextTest();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = metadata.getHeader("x-request");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var date = tests[index].explicitDate;
+ if (date == undefined) {
+ response.setHeader("Date", getDateString(0), false);
+ } else {
+ response.setHeader("Date", date, false);
+ }
+
+ var header = tests[index].responseheader;
+ if (header == undefined) {
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ } else {
+ for (var i = 0; i < header.length; i++) {
+ var splitHdr = header[i].split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+ 'Sep', 'Oct', 'Nov', 'Dec'];
+ var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+
+ var d = new Date();
+ return days[d.getUTCDay()] + ", " +
+ d.getUTCDate() + " " +
+ months[d.getUTCMonth()] + " " +
+ (d.getUTCFullYear() + yearDelta) + " " +
+ d.getUTCHours() + ":" + d.getUTCMinutes() + ":" +
+ d.getUTCSeconds() + " UTC";
+}
diff --git a/netwerk/test/unit/test_bug248970_cache.js b/netwerk/test/unit/test_bug248970_cache.js
new file mode 100644
index 000000000..ee6930c60
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cache.js
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// names for cache devices
+const kDiskDevice = "disk";
+const kMemoryDevice = "memory";
+const kOfflineDevice = "appcache";
+
+const kCacheA = "http://cache/A";
+const kCacheA2 = "http://cache/A2";
+const kCacheB = "http://cache/B";
+const kCacheC = "http://cache/C";
+const kTestContent = "test content";
+
+function make_input_stream_scriptable(input) {
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ wrapper.init(input);
+ return wrapper;
+}
+
+const entries = [
+// key content device should exist after leaving PB
+ [kCacheA, kTestContent, kMemoryDevice, true],
+ [kCacheA2, kTestContent, kDiskDevice, false],
+ [kCacheB, kTestContent, kDiskDevice, true],
+ [kCacheC, kTestContent, kOfflineDevice, true]
+]
+
+var store_idx;
+var store_cb = null;
+var appCache = null;
+
+function store_entries(cb)
+{
+ if (cb) {
+ store_cb = cb;
+ store_idx = 0;
+ }
+
+ if (store_idx == entries.length) {
+ do_execute_soon(store_cb);
+ return;
+ }
+
+ asyncOpenCacheEntry(entries[store_idx][0],
+ entries[store_idx][2],
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ LoadContextInfo.custom(false,
+ {privateBrowsingId : entries[store_idx][3] ? 0 : 1}),
+ store_data,
+ appCache);
+}
+
+var store_data = function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+
+ var written = os.write(entries[store_idx][1], entries[store_idx][1].length);
+ if (written != entries[store_idx][1].length) {
+ do_throw("os.write has not written all data!\n" +
+ " Expected: " + entries[store_idx][1].length + "\n" +
+ " Actual: " + written + "\n");
+ }
+ os.close();
+ entry.close();
+ store_idx++;
+ do_execute_soon(store_entries);
+};
+
+var check_idx;
+var check_cb = null;
+var check_pb_exited;
+function check_entries(cb, pbExited)
+{
+ if (cb) {
+ check_cb = cb;
+ check_idx = 0;
+ check_pb_exited = pbExited;
+ }
+
+ if (check_idx == entries.length) {
+ do_execute_soon(check_cb);
+ return;
+ }
+
+ asyncOpenCacheEntry(entries[check_idx][0],
+ entries[check_idx][2],
+ Ci.nsICacheStorage.OPEN_READONLY,
+ LoadContextInfo.custom(false,
+ {privateBrowsingId : entries[check_idx][3] ? 0 : 1}),
+ check_data,
+ appCache);
+}
+
+var check_data = function (status, entry) {
+ var cont = function() {
+ check_idx++;
+ do_execute_soon(check_entries);
+ }
+
+ if (!check_pb_exited || entries[check_idx][3]) {
+ do_check_eq(status, Cr.NS_OK);
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function(read) {
+ entry.close();
+ do_check_eq(read, entries[check_idx][1]);
+ cont();
+ });
+ } else {
+ do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ cont();
+ }
+};
+
+function run_test() {
+ // Simulate a profile dir for xpcshell
+ do_get_profile();
+
+ appCache = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService).
+ getApplicationCache("fake-client-id|fake-group-id");
+
+ // Start off with an empty cache
+ evict_cache_entries();
+
+ // Store cache-A, cache-A2, cache-B and cache-C
+ store_entries(run_test2);
+
+ do_test_pending();
+}
+
+function run_test2() {
+ // Check if cache-A, cache-A2, cache-B and cache-C are available
+ check_entries(run_test3, false);
+}
+
+function run_test3() {
+ // Simulate all private browsing instances being closed
+ var obsvc = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obsvc.notifyObservers(null, "last-pb-context-exited", null);
+
+ // Make sure the memory device is not empty
+ get_device_entry_count(kMemoryDevice, null, function(count) {
+ do_check_eq(count, 1);
+ // Check if cache-A is gone, and cache-B and cache-C are still available
+ check_entries(do_test_finished, true);
+ });
+}
diff --git a/netwerk/test/unit/test_bug248970_cookie.js b/netwerk/test/unit/test_bug248970_cookie.js
new file mode 100644
index 000000000..41c45e105
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cookie.js
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver;
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+function makeChan(path) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/" + path,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function setup_chan(path, isPrivate, callback) {
+ var chan = makeChan(path);
+ chan.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(isPrivate);
+ chan.asyncOpen2(new ChannelListener(callback));
+ }
+
+function set_cookie(value, callback) {
+ return setup_chan('set?cookie=' + value, false, callback);
+}
+
+function set_private_cookie(value, callback) {
+ return setup_chan('set?cookie=' + value, true, callback);
+}
+
+function check_cookie_presence(value, isPrivate, expected, callback) {
+ var chan = setup_chan('present?cookie=' + value.replace('=','|'), isPrivate, function(req) {
+ req.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(req.responseStatus, expected ? 200 : 404);
+ callback(req);
+ });
+}
+
+function presentHandler(metadata, response) {
+ var present = false;
+ var match = /cookie=([^&]*)/.exec(metadata.queryString);
+ if (match) {
+ try {
+ present = metadata.getHeader("Cookie").indexOf(match[1].replace("|","=")) != -1;
+ } catch (x) {
+ }
+ }
+ response.setStatusLine("1.0", present ? 200 : 404, "");
+}
+
+function setHandler(metadata, response) {
+ response.setStatusLine("1.0", 200, "Cookie set");
+ var match = /cookie=([^&]*)/.exec(metadata.queryString);
+ if (match) {
+ response.setHeader("Set-Cookie", match[1]);
+ }
+}
+
+function run_test() {
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess())
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/set", setHandler);
+ httpserver.registerPathHandler("/present", presentHandler);
+ httpserver.start(-1);
+
+ do_test_pending();
+
+ function check_cookie(req) {
+ req.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(req.responseStatus, 200);
+ try {
+ do_check_true(req.getResponseHeader("Set-Cookie") != "", "expected a Set-Cookie header");
+ } catch (x) {
+ do_throw("missing Set-Cookie header");
+ }
+
+ runNextTest();
+ }
+
+ let tests = [];
+
+ function runNextTest() {
+ do_execute_soon(tests.shift());
+ }
+
+ tests.push(function() {
+ set_cookie("C1=V1", check_cookie);
+ });
+ tests.push(function() {
+ set_private_cookie("C2=V2", check_cookie);
+ });
+ tests.push(function() {
+ // Check that the first cookie is present in a non-private request
+ check_cookie_presence("C1=V1", false, true, runNextTest);
+ });
+ tests.push(function() {
+ // Check that the second cookie is present in a private request
+ check_cookie_presence("C2=V2", true, true, runNextTest);
+ });
+ tests.push(function() {
+ // Check that the first cookie is not present in a private request
+ check_cookie_presence("C1=V1", true, false, runNextTest);
+ });
+ tests.push(function() {
+ // Check that the second cookie is not present in a non-private request
+ check_cookie_presence("C2=V2", false, false, runNextTest);
+ });
+
+ // The following test only works in a non-e10s situation at the moment,
+ // since the notification needs to run in the parent process but there is
+ // no existing mechanism to make that happen.
+ if (!inChildProcess()) {
+ tests.push(function() {
+ // Simulate all private browsing instances being closed
+ var obsvc = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obsvc.notifyObservers(null, "last-pb-context-exited", null);
+ // Check that all private cookies are now unavailable in new private requests
+ check_cookie_presence("C2=V2", true, false, runNextTest);
+ });
+ }
+
+ tests.push(function() { httpserver.stop(do_test_finished); });
+
+ runNextTest();
+}
diff --git a/netwerk/test/unit/test_bug261425.js b/netwerk/test/unit/test_bug261425.js
new file mode 100644
index 000000000..4f4de6037
--- /dev/null
+++ b/netwerk/test/unit/test_bug261425.js
@@ -0,0 +1,26 @@
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var newURI = ios.newURI("http://foo.com", null, null);
+
+ var success = false;
+ try {
+ newURI.spec = "http: //foo.com";
+ }
+ catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success)
+ do_throw("We didn't throw NS_ERROR_MALFORMED_URI when a space was passed in the hostname!");
+
+ success = false;
+ try {
+ newURI.host = " foo.com";
+ }
+ catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success)
+ do_throw("We didn't throw NS_ERROR_MALFORMED_URI when a space was passed in the hostname!");
+}
diff --git a/netwerk/test/unit/test_bug263127.js b/netwerk/test/unit/test_bug263127.js
new file mode 100644
index 000000000..8262c07e8
--- /dev/null
+++ b/netwerk/test/unit/test_bug263127.js
@@ -0,0 +1,61 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var server;
+const BUGID = "263127";
+
+var listener = {
+ QueryInterface: function(iid) {
+ if (!iid.equals(nsIDownloadObserver) &&
+ !iid.equals(nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+
+ onDownloadComplete: function(downloader, request, ctxt, status, file) {
+ do_test_pending();
+ server.stop(do_test_finished);
+
+ if (!file)
+ do_throw("Download failed");
+
+ try {
+ file.remove(false);
+ }
+ catch (e) {
+ do_throw(e);
+ }
+
+ do_check_false(file.exists());
+
+ do_test_finished();
+ }
+}
+
+function run_test() {
+ // start server
+ server = new HttpServer();
+ server.start(-1);
+
+ // Initialize downloader
+ var channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/",
+ loadUsingSystemPrincipal: true
+ });
+ var targetFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ targetFile.append("bug" + BUGID + ".test");
+ if (targetFile.exists())
+ targetFile.remove(false);
+
+ var downloader = Cc["@mozilla.org/network/downloader;1"]
+ .createInstance(Ci.nsIDownloader);
+ downloader.init(listener, targetFile);
+
+ // Start download
+ channel.asyncOpen2(downloader);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug282432.js b/netwerk/test/unit/test_bug282432.js
new file mode 100644
index 000000000..f8da54356
--- /dev/null
+++ b/netwerk/test/unit/test_bug282432.js
@@ -0,0 +1,42 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test() {
+ do_test_pending();
+
+ function StreamListener() {}
+
+ StreamListener.prototype = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIStreamListener) ||
+ aIID.equals(Components.interfaces.nsIRequestObserver) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onStartRequest: function(aRequest, aContext) {},
+
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ // Make sure we can catch the error NS_ERROR_FILE_NOT_FOUND here.
+ do_check_eq(aStatusCode, Components.results.NS_ERROR_FILE_NOT_FOUND);
+ do_test_finished();
+ },
+
+ onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
+ do_throw("The channel must not call onDataAvailable().");
+ }
+ };
+
+ let listener = new StreamListener();
+ let ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ // This file does not exist.
+ let file = do_get_file("_NOT_EXIST_.txt", true);
+ do_check_false(file.exists());
+ let channel = NetUtil.newChannel({
+ uri: ios.newFileURI(file),
+ loadUsingSystemPrincipal: true
+ });
+ channel.asyncOpen2(listener);
+}
diff --git a/netwerk/test/unit/test_bug321706.js b/netwerk/test/unit/test_bug321706.js
new file mode 100644
index 000000000..8ddbce6e7
--- /dev/null
+++ b/netwerk/test/unit/test_bug321706.js
@@ -0,0 +1,11 @@
+const url = "http://foo.com/folder/file?/.";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var newURI = ios.newURI(url, null, null);
+ do_check_eq(newURI.spec, url);
+ do_check_eq(newURI.path, "/folder/file?/.");
+ do_check_eq(newURI.resolve("./file?/."), url);
+}
diff --git a/netwerk/test/unit/test_bug331825.js b/netwerk/test/unit/test_bug331825.js
new file mode 100644
index 000000000..0533e9bc5
--- /dev/null
+++ b/netwerk/test/unit/test_bug331825.js
@@ -0,0 +1,42 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var server;
+const BUGID = "331825";
+
+function TestListener() {
+}
+TestListener.prototype.onStartRequest = function(request, context) {
+}
+TestListener.prototype.onStopRequest = function(request, context, status) {
+ var channel = request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ do_check_eq(channel.responseStatus, 304);
+
+ server.stop(do_test_finished);
+}
+
+function run_test() {
+ // start server
+ server = new HttpServer();
+
+ server.registerPathHandler("/bug" + BUGID, bug331825);
+
+ server.start(-1);
+
+ // make request
+ var channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/bug" + BUGID,
+ loadUsingSystemPrincipal: true
+ });
+
+ channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+ channel.setRequestHeader("If-None-Match", "foobar", false);
+ channel.asyncOpen2(new TestListener());
+
+ do_test_pending();
+}
+
+// PATH HANDLER FOR /bug331825
+function bug331825(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+}
diff --git a/netwerk/test/unit/test_bug336501.js b/netwerk/test/unit/test_bug336501.js
new file mode 100644
index 000000000..c27aff5e9
--- /dev/null
+++ b/netwerk/test/unit/test_bug336501.js
@@ -0,0 +1,27 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ var f = do_get_file('test_bug336501.js');
+
+ var fis =
+ Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(f, -1, -1, 0);
+
+ var bis =
+ Cc["@mozilla.org/network/buffered-input-stream;1"].
+ createInstance(Ci.nsIBufferedInputStream);
+ bis.init(fis, 32);
+
+ var sis =
+ Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(bis);
+
+ sis.read(45);
+ sis.close();
+
+ var data = sis.read(45);
+ do_check_eq(data.length, 0);
+}
diff --git a/netwerk/test/unit/test_bug337744.js b/netwerk/test/unit/test_bug337744.js
new file mode 100644
index 000000000..bcf8cdb74
--- /dev/null
+++ b/netwerk/test/unit/test_bug337744.js
@@ -0,0 +1,114 @@
+/* verify that certain invalid URIs are not parsed by the resource
+ protocol handler */
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const specs = [
+ "resource://res-test//",
+ "resource://res-test/?foo=http:",
+ "resource://res-test/?foo=" + encodeURIComponent("http://example.com/"),
+ "resource://res-test/?foo=" + encodeURIComponent("x\\y"),
+ "resource://res-test/..%2F",
+ "resource://res-test/..%2f",
+ "resource://res-test/..%2F..",
+ "resource://res-test/..%2f..",
+ "resource://res-test/../../",
+ "resource://res-test/http://www.mozilla.org/",
+ "resource://res-test/file:///",
+];
+
+const error_specs = [
+ "resource://res-test/..\\",
+ "resource://res-test/..\\..\\",
+ "resource://res-test/..%5C",
+ "resource://res-test/..%5c",
+];
+
+// Create some fake principal that has not enough
+// privileges to access any resource: uri.
+var uri = NetUtil.newURI("http://www.example.com", null, null);
+var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+
+function get_channel(spec)
+{
+ var channelURI = NetUtil.newURI(spec, null, null);
+
+ var channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(spec, null, null),
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ });
+
+ try {
+ channel.asyncOpen2(null);
+ ok(false, "asyncOpen2() of URI: " + spec + "should throw");
+ }
+ catch (e) {
+ // make sure we get the right error code in the exception
+ // ERROR code for NS_ERROR_DOM_BAD_URI is 1012
+ equal(e.code, 1012);
+ }
+
+ try {
+ channel.open2();
+ ok(false, "Open2() of uri: " + spec + "should throw");
+ }
+ catch (e) {
+ // make sure we get the right error code in the exception
+ // ERROR code for NS_ERROR_DOM_BAD_URI is 1012
+ equal(e.code, 1012);
+ }
+
+ return channel;
+}
+
+function check_safe_resolution(spec, rootURI)
+{
+ do_print(`Testing URL "${spec}"`);
+
+ let channel = get_channel(spec);
+
+ ok(channel.name.startsWith(rootURI), `URL resolved safely to ${channel.name}`);
+ ok(!/%2f/i.test(channel.name), `URL contains no escaped / characters`);
+}
+
+function check_resolution_error(spec)
+{
+ try {
+ get_channel(spec);
+ ok(false, "Expected an error");
+ } catch (e) {
+ equal(e.result, Components.results.NS_ERROR_MALFORMED_URI,
+ "Expected a malformed URI error");
+ }
+}
+
+function run_test() {
+ // resource:/// and resource://gre/ are resolved specially, so we need
+ // to create a temporary resource package to test the standard logic
+ // with.
+
+ let resProto = Cc['@mozilla.org/network/protocol;1?name=resource'].getService(Ci.nsIResProtocolHandler);
+ let rootFile = Services.dirsvc.get("GreD", Ci.nsIFile);
+ let rootURI = Services.io.newFileURI(rootFile);
+
+ resProto.setSubstitution("res-test", rootURI);
+ do_register_cleanup(() => {
+ resProto.setSubstitution("res-test", null);
+ });
+
+ let baseRoot = resProto.resolveURI(Services.io.newURI("resource:///", null, null));
+ let greRoot = resProto.resolveURI(Services.io.newURI("resource://gre/", null, null));
+
+ for (var spec of specs) {
+ check_safe_resolution(spec, rootURI.spec);
+ check_safe_resolution(spec.replace("res-test", ""), baseRoot);
+ check_safe_resolution(spec.replace("res-test", "gre"), greRoot);
+ }
+
+ for (var spec of error_specs) {
+ check_resolution_error(spec);
+ }
+}
diff --git a/netwerk/test/unit/test_bug365133.js b/netwerk/test/unit/test_bug365133.js
new file mode 100644
index 000000000..d905f8ae4
--- /dev/null
+++ b/netwerk/test/unit/test_bug365133.js
@@ -0,0 +1,111 @@
+const URL = "ftp://localhost/bug365133/";
+
+const tests = [
+ [ /* Unix style listing, space at the end of filename */
+ "drwxrwxr-x 2 500 500 4096 Jan 01 2000 a \r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"a%20\" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 DIRECTORY \n"
+ ],
+ [ /* Unix style listing, space at the end of link name */
+ "lrwxrwxrwx 1 500 500 2 Jan 01 2000 l -> a \r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"l%20\" 2 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 SYMBOLIC-LINK \n"
+ ],
+ [ /* Unix style listing, regular file with " -> " in name */
+ "-rw-rw-r-- 1 500 500 0 Jan 01 2000 arrow -> in name \r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"arrow%20-%3E%20in%20name%20\" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 FILE \n"
+ ],
+ [ /* Unix style listing, tab at the end of filename */
+ "drwxrwxrwx 2 500 500 4096 Jan 01 2000 t \r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"t%09\" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 DIRECTORY \n"
+ ],
+ [ /* Unix style listing, multiple " -> " in filename */
+ "lrwxrwxrwx 1 500 500 26 Jan 01 2000 symlink with arrow -> in name -> file with arrow -> in name\r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"symlink%20with%20arrow%20-%3E%20in%20name\" 26 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 SYMBOLIC-LINK \n"
+ ],
+ [ /* Unix style listing, multiple " -> " in filename, incorrect filesize */
+ "lrwxrwxrwx 1 500 500 0 Jan 01 2000 symlink with arrow -> in name -> file with arrow -> in name\r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"symlink%20with%20arrow%20-%3E%20in%20name%20-%3E%20file%20with%20arrow\" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 SYMBOLIC-LINK \n"
+ ],
+ [ /* DOS style listing, space at the end of filename, year 1999 */
+ "01-01-99 01:00AM 1024 file \r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"file%20\" 1024 Fri%2C%2001%20Jan%201999%2001%3A00%3A00 FILE \n"
+ ],
+ [ /* DOS style listing, tab at the end of filename, year 2000 */
+ "01-01-00 01:00AM 1024 file \r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"file%09\" 1024 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n"
+ ]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(next_test);
+}
+
+function storeData() {
+ var scs = Cc["@mozilla.org/streamConverters;1"].
+ getService(Ci.nsIStreamConverterService);
+ var converter = scs.asyncConvertData("text/ftp-dir", "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL), null);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.data = tests[0][0];
+
+ var url = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0)
+ do_test_finished();
+ else {
+ storeData();
+ }
+}
+
+function run_test() {
+ do_execute_soon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug368702.js b/netwerk/test/unit/test_bug368702.js
new file mode 100644
index 000000000..8f511e559
--- /dev/null
+++ b/netwerk/test/unit/test_bug368702.js
@@ -0,0 +1,150 @@
+function run_test() {
+ var tld =
+ Cc["@mozilla.org/network/effective-tld-service;1"].
+ getService(Ci.nsIEffectiveTLDService);
+
+ var etld;
+
+ do_check_eq(tld.getPublicSuffixFromHost("localhost"), "localhost");
+ do_check_eq(tld.getPublicSuffixFromHost("localhost."), "localhost.");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.com"), "com");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.com."), "com.");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.co.uk"), "co.uk");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.co.uk."), "co.uk.");
+ do_check_eq(tld.getPublicSuffixFromHost("co.uk"), "co.uk");
+ do_check_eq(tld.getBaseDomainFromHost("domain.co.uk"), "domain.co.uk");
+ do_check_eq(tld.getBaseDomainFromHost("domain.co.uk."), "domain.co.uk.");
+
+ try {
+ etld = tld.getPublicSuffixFromHost("");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("domain.co.uk", 1);
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("co.uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("1.2.3.4");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("2010:836B:4179::836B:4179");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("3232235878");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("::ffff:192.9.5.5");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("::1");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ // Check IP addresses with trailing dot as well, Necko sometimes accepts
+ // those (depending on operating system, see bug 380543)
+ try {
+ etld = tld.getPublicSuffixFromHost("127.0.0.1.");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("::ffff:127.0.0.1.");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ // check normalization: output should be consistent with
+ // nsIURI::GetAsciiHost(), i.e. lowercased and ASCII/ACE encoded
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ var uri = ioService.newURI("http://b\u00FCcher.co.uk", null, null);
+ do_check_eq(tld.getBaseDomain(uri), "xn--bcher-kva.co.uk");
+ do_check_eq(tld.getBaseDomainFromHost("b\u00FCcher.co.uk"), "xn--bcher-kva.co.uk");
+ do_check_eq(tld.getPublicSuffix(uri), "co.uk");
+ do_check_eq(tld.getPublicSuffixFromHost("b\u00FCcher.co.uk"), "co.uk");
+
+ // check that malformed hosts are rejected as invalid args
+ try {
+ etld = tld.getBaseDomainFromHost("domain.co.uk..");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("domain.co..uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost(".domain.co.uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost(".domain.co.uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost(".");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("..");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+}
diff --git a/netwerk/test/unit/test_bug369787.js b/netwerk/test/unit/test_bug369787.js
new file mode 100644
index 000000000..d59bef005
--- /dev/null
+++ b/netwerk/test/unit/test_bug369787.js
@@ -0,0 +1,71 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const BUGID = "369787";
+var server = null;
+var channel = null;
+
+function change_content_type() {
+ var origType = channel.contentType;
+ const newType = "x-foo/x-bar";
+ channel.contentType = newType;
+ do_check_eq(channel.contentType, newType);
+ channel.contentType = origType;
+ do_check_eq(channel.contentType, origType);
+}
+
+function TestListener() {
+}
+TestListener.prototype.onStartRequest = function(request, context) {
+ try {
+ // request might be different from channel
+ channel = request.QueryInterface(Components.interfaces.nsIChannel);
+
+ change_content_type();
+ } catch (ex) {
+ print(ex);
+ throw ex;
+ }
+}
+TestListener.prototype.onStopRequest = function(request, context, status) {
+ try {
+ change_content_type();
+ } catch (ex) {
+ print(ex);
+ // don't re-throw ex to avoid hanging the test
+ }
+
+ do_timeout(0, after_channel_closed);
+}
+
+function after_channel_closed() {
+ try {
+ change_content_type();
+ } finally {
+ server.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ // start server
+ server = new HttpServer();
+
+ server.registerPathHandler("/bug" + BUGID, bug369787);
+
+ server.start(-1);
+
+ // make request
+ channel = NetUtil.newChannel({
+ uri: "http://localhost:" + server.identity.primaryPort + "/bug" + BUGID,
+ loadUsingSystemPrincipal: true
+ });
+ channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+ channel.asyncOpen2(new TestListener());
+
+ do_test_pending();
+}
+
+// PATH HANDLER FOR /bug369787
+function bug369787(metadata, response) {
+ /* do nothing */
+}
diff --git a/netwerk/test/unit/test_bug371473.js b/netwerk/test/unit/test_bug371473.js
new file mode 100644
index 000000000..75752d383
--- /dev/null
+++ b/netwerk/test/unit/test_bug371473.js
@@ -0,0 +1,44 @@
+function test_not_too_long() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var spec = "jar:http://example.com/bar.jar!/";
+ try {
+ var newURI = ios.newURI(spec, null, null);
+ }
+ catch (e) {
+ do_throw("newURI threw even though it wasn't passed a large nested URI?");
+ }
+}
+
+function test_too_long() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var i;
+ var prefix = "jar:";
+ for (i = 0; i < 16; i++) {
+ prefix = prefix + prefix;
+ }
+ var suffix = "!/";
+ for (i = 0; i < 16; i++) {
+ suffix = suffix + suffix;
+ }
+
+ var spec = prefix + "http://example.com/bar.jar" + suffix;
+ try {
+ // The following will produce a recursive call that if
+ // unchecked would lead to a stack overflow. If we
+ // do not crash here and thus an exception is caught
+ // we have passed the test.
+ var newURI = ios.newURI(spec, null, null);
+ }
+ catch (e) {
+ return;
+ }
+}
+
+function run_test() {
+ test_not_too_long();
+ test_too_long();
+}
diff --git a/netwerk/test/unit/test_bug376660.js b/netwerk/test/unit/test_bug376660.js
new file mode 100644
index 000000000..8208eef6a
--- /dev/null
+++ b/netwerk/test/unit/test_bug376660.js
@@ -0,0 +1,72 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var listener = {
+ expect_failure: false,
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIUnicharStreamLoaderObserver)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onDetermineCharset : function onDetermineCharset(loader, context, data)
+ {
+ return "us-ascii";
+ },
+ onStreamComplete : function onStreamComplete (loader, context, status, data)
+ {
+ try {
+ if (this.expect_failure)
+ do_check_false(Components.isSuccessCode(status));
+ else
+ do_check_eq(status, Components.results.NS_OK);
+ do_check_eq(data, "");
+ do_check_neq(loader.channel, null);
+ tests[current_test++]();
+ } finally {
+ do_test_finished();
+ }
+ }
+};
+
+var current_test = 0;
+var tests = [test1, test2, done];
+
+function run_test() {
+ tests[current_test++]();
+}
+
+function test1() {
+ var f =
+ Cc["@mozilla.org/network/unichar-stream-loader;1"].
+ createInstance(Ci.nsIUnicharStreamLoader);
+ f.init(listener);
+
+ var chan = NetUtil.newChannel({
+ uri: "data:text/plain,",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(f);
+ do_test_pending();
+}
+
+function test2() {
+ var f =
+ Cc["@mozilla.org/network/unichar-stream-loader;1"].
+ createInstance(Ci.nsIUnicharStreamLoader);
+ f.init(listener);
+
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:0/",
+ loadUsingSystemPrincipal: true
+ });
+ listener.expect_failure = true;
+ chan.asyncOpen2(f);
+ do_test_pending();
+}
+
+function done() {
+}
diff --git a/netwerk/test/unit/test_bug376844.js b/netwerk/test/unit/test_bug376844.js
new file mode 100644
index 000000000..9a21b0171
--- /dev/null
+++ b/netwerk/test/unit/test_bug376844.js
@@ -0,0 +1,21 @@
+const testURLs = [
+ ["http://example.com/<", "http://example.com/%3C"],
+ ["http://example.com/>", "http://example.com/%3E"],
+ ["http://example.com/'", "http://example.com/'"],
+ ["http://example.com/\"", "http://example.com/%22"],
+ ["http://example.com/?<", "http://example.com/?%3C"],
+ ["http://example.com/?>", "http://example.com/?%3E"],
+ ["http://example.com/?'", "http://example.com/?%27"],
+ ["http://example.com/?\"", "http://example.com/?%22"]
+]
+
+function run_test() {
+ var ioServ =
+ Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ for (var i = 0; i < testURLs.length; i++) {
+ var uri = ioServ.newURI(testURLs[i][0], null, null);
+ do_check_eq(uri.spec, testURLs[i][1]);
+ }
+}
diff --git a/netwerk/test/unit/test_bug376865.js b/netwerk/test/unit/test_bug376865.js
new file mode 100644
index 000000000..a068c555a
--- /dev/null
+++ b/netwerk/test/unit/test_bug376865.js
@@ -0,0 +1,20 @@
+function run_test() {
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsISupportsCString);
+ stream.data = "foo bar baz";
+
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"].
+ createInstance(Ci.nsIInputStreamPump);
+ pump.init(stream, -1, -1, 0, 0, false);
+
+ // When we pass a null listener argument too asyncRead we expect it to throw
+ // instead of crashing.
+ try {
+ pump.asyncRead(null, null);
+ }
+ catch (e) {
+ return;
+ }
+
+ do_throw("asyncRead didn't throw when passed a null listener argument.");
+}
diff --git a/netwerk/test/unit/test_bug379034.js b/netwerk/test/unit/test_bug379034.js
new file mode 100644
index 000000000..0177e904c
--- /dev/null
+++ b/netwerk/test/unit/test_bug379034.js
@@ -0,0 +1,18 @@
+function run_test() {
+ const ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ var base = ios.newURI("http://localhost/bug379034/index.html", null, null);
+
+ var uri = ios.newURI("http:a.html", null, base);
+ do_check_eq(uri.spec, "http://localhost/bug379034/a.html");
+
+ uri = ios.newURI("HtTp:b.html", null, base);
+ do_check_eq(uri.spec, "http://localhost/bug379034/b.html");
+
+ uri = ios.newURI("https:c.html", null, base);
+ do_check_eq(uri.spec, "https://c.html/");
+
+ uri = ios.newURI("./https:d.html", null, base);
+ do_check_eq(uri.spec, "http://localhost/bug379034/https:d.html");
+}
diff --git a/netwerk/test/unit/test_bug380994.js b/netwerk/test/unit/test_bug380994.js
new file mode 100644
index 000000000..b9b9d5bd6
--- /dev/null
+++ b/netwerk/test/unit/test_bug380994.js
@@ -0,0 +1,22 @@
+/* check resource: protocol for traversal problems */
+
+const specs = [
+ "resource:///chrome/../plugins",
+ "resource:///chrome%2f../plugins",
+ "resource:///chrome/..%2fplugins",
+ "resource:///chrome%2f%2e%2e%2fplugins",
+ "resource:///../../../..",
+ "resource:///..%2f..%2f..%2f..",
+ "resource:///%2e%2e"
+];
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ for (var spec of specs) {
+ var uri = ios.newURI(spec, null, null);
+ if (uri.spec.indexOf("..") != -1)
+ do_throw("resource: traversal remains: '"+spec+"' ==> '"+uri.spec+"'");
+ }
+}
diff --git a/netwerk/test/unit/test_bug388281.js b/netwerk/test/unit/test_bug388281.js
new file mode 100644
index 000000000..112678d00
--- /dev/null
+++ b/netwerk/test/unit/test_bug388281.js
@@ -0,0 +1,24 @@
+function run_test() {
+ const ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var uri = ios.newURI("http://foo.com/file.txt", null, null);
+ uri.port = 90;
+ do_check_eq(uri.hostPort, "foo.com:90");
+
+ uri = ios.newURI("http://foo.com:10/file.txt", null, null);
+ uri.port = 500;
+ do_check_eq(uri.hostPort, "foo.com:500");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ uri.port = 20;
+ do_check_eq(uri.hostPort, "foo.com:20");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ uri.port = -1;
+ do_check_eq(uri.hostPort, "foo.com");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ uri.port = 80;
+ do_check_eq(uri.hostPort, "foo.com");
+}
diff --git a/netwerk/test/unit/test_bug396389.js b/netwerk/test/unit/test_bug396389.js
new file mode 100644
index 000000000..0bcfa8362
--- /dev/null
+++ b/netwerk/test/unit/test_bug396389.js
@@ -0,0 +1,71 @@
+function round_trip(uri) {
+ var objectOutStream = Cc["@mozilla.org/binaryoutputstream;1"].
+ createInstance(Ci.nsIObjectOutputStream);
+ var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ objectOutStream.setOutputStream(pipe.outputStream);
+ objectOutStream.writeCompoundObject(uri, Ci.nsISupports, true);
+ objectOutStream.close();
+
+ var objectInStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIObjectInputStream);
+ objectInStream.setInputStream(pipe.inputStream);
+ return objectInStream.readObject(true).QueryInterface(Ci.nsIURI);
+}
+
+var prefData =
+ [
+ {
+ name: "network.IDN_show_punycode",
+ newVal: false
+ },
+ {
+ name: "network.IDN.whitelist.ch",
+ newVal: true
+ }
+ ];
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var uri1 = ios.newURI("file:///", null, null);
+ do_check_true(uri1 instanceof Ci.nsIFileURL);
+
+ var uri2 = uri1.clone();
+ do_check_true(uri2 instanceof Ci.nsIFileURL);
+ do_check_true(uri1.equals(uri2));
+
+ var uri3 = round_trip(uri1);
+ do_check_true(uri3 instanceof Ci.nsIFileURL);
+ do_check_true(uri1.equals(uri3));
+
+ // Make sure our prefs are set such that this test actually means something
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ for (var pref of prefData) {
+ try {
+ pref.oldVal = prefs.getBoolPref(pref.name);
+ } catch(e) {
+ }
+ prefs.setBoolPref(pref.name, pref.newVal);
+ }
+
+ try {
+ // URI stolen from
+ // http://lists.w3.org/Archives/Public/public-iri/2004Mar/0012.html
+ var uri4 = ios.newURI("http://xn--jos-dma.example.net.ch/", null, null);
+ do_check_eq(uri4.asciiHost, "xn--jos-dma.example.net.ch");
+ do_check_eq(uri4.host, "jos\u00e9.example.net.ch");
+
+ var uri5 = round_trip(uri4);
+ do_check_true(uri4.equals(uri5));
+ do_check_eq(uri4.host, uri5.host);
+ do_check_eq(uri4.asciiHost, uri5.asciiHost);
+ } finally {
+ for (var pref of prefData) {
+ if (prefs.prefHasUserValue(pref.name))
+ prefs.clearUserPref(pref.name);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_bug401564.js b/netwerk/test/unit/test_bug401564.js
new file mode 100644
index 000000000..e7643fa9d
--- /dev/null
+++ b/netwerk/test/unit/test_bug401564.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+"use strict";
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+const noRedirectURI = "/content";
+const pageValue = "Final page";
+const acceptType = "application/json";
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 302, "Moved Temporarily");
+ response.setHeader("Location", noRedirectURI, false);
+}
+
+function contentHandler(metadata, response)
+{
+ do_check_eq(metadata.getHeader("Accept"), acceptType);
+ httpserver.stop(do_test_finished);
+}
+
+function dummyHandler(request, buffer)
+{
+}
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/redirect", redirectHandler);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ var prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.setBoolPref("network.http.prompt-temp-redirect", false);
+
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/redirect",
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.setRequestHeader("Accept", acceptType, false);
+
+ chan.asyncOpen2(new ChannelListener(dummyHandler, null));
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug411952.js b/netwerk/test/unit/test_bug411952.js
new file mode 100644
index 000000000..9ac9d1d74
--- /dev/null
+++ b/netwerk/test/unit/test_bug411952.js
@@ -0,0 +1,35 @@
+function run_test() {
+ try {
+ var cm = Cc["@mozilla.org/cookiemanager;1"].
+ getService(Ci.nsICookieManager2);
+ do_check_neq(cm, null, "Retrieving the cookie manager failed");
+
+ const time = (new Date("Jan 1, 2030")).getTime() / 1000;
+ cm.add("example.com", "/", "C", "V", false, true, false, time, {});
+ const now = Math.floor((new Date()).getTime() / 1000);
+
+ var enumerator = cm.enumerator, found = false;
+ while (enumerator.hasMoreElements()) {
+ var cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ if (cookie.host == "example.com" &&
+ cookie.path == "/" &&
+ cookie.name == "C") {
+ do_check_true("creationTime" in cookie,
+ "creationTime attribute is not accessible on the cookie");
+ var creationTime = Math.floor(cookie.creationTime / 1000000);
+ // allow the times to slip by one second at most,
+ // which should be fine under normal circumstances.
+ do_check_true(Math.abs(creationTime - now) <= 1,
+ "Cookie's creationTime is set incorrectly");
+ found = true;
+ break;
+ }
+ }
+
+ do_check_true(found, "Didn't find the cookie we were after");
+ } catch (e) {
+ do_throw("Unexpected exception: " + e.toString());
+ }
+
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_bug412457.js b/netwerk/test/unit/test_bug412457.js
new file mode 100644
index 000000000..dd4358413
--- /dev/null
+++ b/netwerk/test/unit/test_bug412457.js
@@ -0,0 +1,44 @@
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ // check if hostname is unescaped before applying IDNA
+ var newURI = ios.newURI("http://\u5341%2ecom/", null, null);
+ do_check_eq(newURI.asciiHost, "xn--kkr.com");
+
+ // escaped UTF8
+ newURI.spec = "http://%e5%8d%81.com";
+ do_check_eq(newURI.asciiHost, "xn--kkr.com");
+
+ // There should be only allowed characters in hostname after
+ // unescaping and attempting to apply IDNA. "\x80" is illegal in
+ // UTF-8, so IDNA fails, and 0x80 is illegal in DNS too.
+ Assert.throws(() => { newURI.spec = "http://%80.com"; }, "illegal UTF character");
+
+ // test parsing URL with all possible host terminators
+ newURI.spec = "http://example.com?foo";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ newURI.spec = "http://example.com#foo";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ newURI.spec = "http://example.com:80";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ newURI.spec = "http://example.com/foo";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ // Characters that are invalid in the host, shouldn't be decoded.
+ newURI.spec = "http://example.com%3ffoo";
+ do_check_eq(newURI.asciiHost, "example.com%3ffoo");
+ newURI.spec = "http://example.com%23foo";
+ do_check_eq(newURI.asciiHost, "example.com%23foo");
+ newURI.spec = "http://example.com%3bfoo";
+ do_check_eq(newURI.asciiHost, "example.com%3bfoo");
+ newURI.spec = "http://example.com%3a80";
+ do_check_eq(newURI.asciiHost, "example.com%3a80");
+ newURI.spec = "http://example.com%2ffoo";
+ do_check_eq(newURI.asciiHost, "example.com%2ffoo");
+ newURI.spec = "http://example.com%00";
+ do_check_eq(newURI.asciiHost, "example.com%00");
+} \ No newline at end of file
diff --git a/netwerk/test/unit/test_bug412945.js b/netwerk/test/unit/test_bug412945.js
new file mode 100644
index 000000000..e8b39774b
--- /dev/null
+++ b/netwerk/test/unit/test_bug412945.js
@@ -0,0 +1,42 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv;
+
+function TestListener() {
+}
+
+TestListener.prototype.onStartRequest = function(request, context) {
+}
+
+TestListener.prototype.onStopRequest = function(request, context, status) {
+ httpserv.stop(do_test_finished);
+}
+
+function run_test() {
+ httpserv = new HttpServer();
+
+ httpserv.registerPathHandler("/bug412945", bug412945);
+
+ httpserv.start(-1);
+
+ // make request
+ var channel = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserv.identity.primaryPort + "/bug412945",
+ loadUsingSystemPrincipal: true
+ });
+
+ channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+ channel.requestMethod = "POST";
+ channel.asyncOpen2(new TestListener(), null);
+
+ do_test_pending();
+}
+
+function bug412945(metadata, response) {
+ if (!metadata.hasHeader("Content-Length") ||
+ metadata.getHeader("Content-Length") != "0")
+ {
+ do_throw("Content-Length header not found!");
+ }
+}
diff --git a/netwerk/test/unit/test_bug414122.js b/netwerk/test/unit/test_bug414122.js
new file mode 100644
index 000000000..7e5a418d0
--- /dev/null
+++ b/netwerk/test/unit/test_bug414122.js
@@ -0,0 +1,58 @@
+const PR_RDONLY = 0x1;
+
+var etld = Cc["@mozilla.org/network/effective-tld-service;1"]
+ .getService(Ci.nsIEffectiveTLDService);
+var idn = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+
+function run_test()
+{
+ var fis = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fis.init(do_get_file("effective_tld_names.dat"),
+ PR_RDONLY, 0o444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ var lis = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ lis.init(fis, "UTF-8", 1024, 0);
+ lis.QueryInterface(Ci.nsIUnicharLineInputStream);
+
+ var out = { value: "" };
+ do
+ {
+ var more = lis.readLine(out);
+ var line = out.value;
+
+ line = line.replace(/^\s+/, "");
+ var firstTwo = line.substring(0, 2); // a misnomer, but whatever
+ if (firstTwo == "" || firstTwo == "//")
+ continue;
+
+ var space = line.search(/[ \t]/);
+ line = line.substring(0, space == -1 ? line.length : space);
+
+ if ("*." == firstTwo)
+ {
+ let rest = line.substring(2);
+ checkPublicSuffix("foo.SUPER-SPECIAL-AWESOME-PREFIX." + rest,
+ "SUPER-SPECIAL-AWESOME-PREFIX." + rest);
+ }
+ else if ("!" == line.charAt(0))
+ {
+ checkPublicSuffix(line.substring(1),
+ line.substring(line.indexOf(".") + 1));
+ }
+ else
+ {
+ checkPublicSuffix("SUPER-SPECIAL-AWESOME-PREFIX." + line, line);
+ }
+ }
+ while (more);
+}
+
+function checkPublicSuffix(host, expectedSuffix)
+{
+ expectedSuffix = idn.convertUTF8toACE(expectedSuffix).toLowerCase();
+ var actualSuffix = etld.getPublicSuffixFromHost(host);
+ do_check_eq(actualSuffix, expectedSuffix);
+}
diff --git a/netwerk/test/unit/test_bug427957.js b/netwerk/test/unit/test_bug427957.js
new file mode 100644
index 000000000..e869725a4
--- /dev/null
+++ b/netwerk/test/unit/test_bug427957.js
@@ -0,0 +1,106 @@
+/**
+ * Test for Bidi restrictions on IDNs from RFC 3454
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var idnService;
+
+function expected_pass(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ do_check_eq(displayIDN, inputIDN);
+}
+
+function expected_fail(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = "";
+
+ try {
+ displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ }
+ catch(e) {}
+
+ do_check_neq(displayIDN, inputIDN);
+}
+
+function run_test() {
+ // add an IDN whitelist pref
+ var pbi = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ pbi.setBoolPref("network.IDN.whitelist.com", true);
+
+ idnService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ /*
+ * In any profile that specifies bidirectional character handling, all
+ * three of the following requirements MUST be met:
+ *
+ * 1) The characters in section 5.8 MUST be prohibited.
+ */
+
+ // 0340; COMBINING GRAVE TONE MARK
+ expected_fail("foo\u0340bar.com");
+ // 0341; COMBINING ACUTE TONE MARK
+ expected_fail("foo\u0341bar.com");
+ // 200E; LEFT-TO-RIGHT MARK
+ expected_fail("foo\200ebar.com");
+ // 200F; RIGHT-TO-LEFT MARK
+ // Note: this is an RTL IDN so that it doesn't fail test 2) below
+ expected_fail("\u200f\u0645\u062B\u0627\u0644.\u0622\u0632\u0645\u0627\u06CC\u0634\u06CC");
+ // 202A; LEFT-TO-RIGHT EMBEDDING
+ expected_fail("foo\u202abar.com");
+ // 202B; RIGHT-TO-LEFT EMBEDDING
+ expected_fail("foo\u202bbar.com");
+ // 202C; POP DIRECTIONAL FORMATTING
+ expected_fail("foo\u202cbar.com");
+ // 202D; LEFT-TO-RIGHT OVERRIDE
+ expected_fail("foo\u202dbar.com");
+ // 202E; RIGHT-TO-LEFT OVERRIDE
+ expected_fail("foo\u202ebar.com");
+ // 206A; INHIBIT SYMMETRIC SWAPPING
+ expected_fail("foo\u206abar.com");
+ // 206B; ACTIVATE SYMMETRIC SWAPPING
+ expected_fail("foo\u206bbar.com");
+ // 206C; INHIBIT ARABIC FORM SHAPING
+ expected_fail("foo\u206cbar.com");
+ // 206D; ACTIVATE ARABIC FORM SHAPING
+ expected_fail("foo\u206dbar.com");
+ // 206E; NATIONAL DIGIT SHAPES
+ expected_fail("foo\u206ebar.com");
+ // 206F; NOMINAL DIGIT SHAPES
+ expected_fail("foo\u206fbar.com");
+
+ /*
+ * 2) If a string contains any RandALCat character, the string MUST NOT
+ * contain any LCat character.
+ */
+
+ // www.מיץpetel.com is invalid
+ expected_fail("www.\u05DE\u05D9\u05E5petel.com");
+ // But www.מיץפטל.com is fine because the ltr and rtl characters are in
+ // different labels
+ expected_pass("www.\u05DE\u05D9\u05E5\u05E4\u05D8\u05DC.com");
+
+ /*
+ * 3) If a string contains any RandALCat character, a RandALCat
+ * character MUST be the first character of the string, and a
+ * RandALCat character MUST be the last character of the string.
+ */
+
+ // www.1מיץ.com is invalid
+ expected_fail("www.1\u05DE\u05D9\u05E5.com");
+ // www.!מיץ.com is invalid
+ expected_fail("www.!\u05DE\u05D9\u05E5.com");
+ // www.מיץ!.com is invalid
+ expected_fail("www.\u05DE\u05D9\u05E5!.com");
+
+ // XXX TODO: add a test for an RTL label ending with a digit. This was
+ // invalid in IDNA2003 but became valid in IDNA2008
+
+ // But www.מיץ1פטל.com is fine
+ expected_pass("www.\u05DE\u05D9\u05E51\u05E4\u05D8\u05DC.com");
+}
+
diff --git a/netwerk/test/unit/test_bug429347.js b/netwerk/test/unit/test_bug429347.js
new file mode 100644
index 000000000..a6f1452c2
--- /dev/null
+++ b/netwerk/test/unit/test_bug429347.js
@@ -0,0 +1,38 @@
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var uri1 = ios.newURI("http://example.com#bar", null, null);
+ var uri2 = ios.newURI("http://example.com/#bar", null, null);
+ do_check_true(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com?bar";
+ uri2.spec = "http://example.com/?bar";
+ do_check_true(uri1.equals(uri2));
+
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=665706
+ // ";" is not parsed as special anymore and thus ends up
+ // in the authority component (see RFC 3986)
+ uri1.spec = "http://example.com;bar";
+ uri2.spec = "http://example.com/;bar";
+ do_check_false(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com#";
+ uri2.spec = "http://example.com/#";
+ do_check_true(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com?";
+ uri2.spec = "http://example.com/?";
+ do_check_true(uri1.equals(uri2));
+
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=665706
+ // ";" is not parsed as special anymore and thus ends up
+ // in the authority component (see RFC 3986)
+ uri1.spec = "http://example.com;";
+ uri2.spec = "http://example.com/;";
+ do_check_false(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com";
+ uri2.spec = "http://example.com/";
+ do_check_true(uri1.equals(uri2));
+}
diff --git a/netwerk/test/unit/test_bug455311.js b/netwerk/test/unit/test_bug455311.js
new file mode 100644
index 000000000..564831df3
--- /dev/null
+++ b/netwerk/test/unit/test_bug455311.js
@@ -0,0 +1,128 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function getLinkFile()
+{
+ if (mozinfo.os == "win") {
+ return do_get_file("test_link.url");
+ }
+ if (mozinfo.os == "linux") {
+ return do_get_file("test_link.desktop");
+ }
+ do_throw("Unexpected platform");
+ return null;
+}
+
+const ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+var link;
+var linkURI;
+const newURI = ios.newURI("http://www.mozilla.org/", null, null);
+
+function NotificationCallbacks(origURI, newURI)
+{
+ this._origURI = origURI;
+ this._newURI = newURI;
+}
+NotificationCallbacks.prototype = {
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIInterfaceRequestor) ||
+ iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ getInterface: function (iid)
+ {
+ return this.QueryInterface(iid);
+ },
+ asyncOnChannelRedirect: function(oldChan, newChan, flags, callback)
+ {
+ do_check_eq(oldChan.URI.spec, this._origURI.spec);
+ do_check_eq(oldChan.URI, this._origURI);
+ do_check_eq(oldChan.originalURI.spec, this._origURI.spec);
+ do_check_eq(oldChan.originalURI, this._origURI);
+ do_check_eq(newChan.originalURI.spec, this._newURI.spec);
+ do_check_eq(newChan.originalURI, newChan.URI);
+ do_check_eq(newChan.URI.spec, this._newURI.spec);
+ throw Cr.NS_ERROR_ABORT;
+ }
+};
+
+function RequestObserver(origURI, newURI, nextTest)
+{
+ this._origURI = origURI;
+ this._newURI = newURI;
+ this._nextTest = nextTest;
+}
+RequestObserver.prototype = {
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsIStreamListener)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function (req, ctx)
+ {
+ var chan = req.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.URI.spec, this._origURI.spec);
+ do_check_eq(chan.URI, this._origURI);
+ do_check_eq(chan.originalURI.spec, this._origURI.spec);
+ do_check_eq(chan.originalURI, this._origURI);
+ },
+ onDataAvailable: function(req, ctx, stream, offset, count)
+ {
+ do_throw("Unexpected call to onDataAvailable");
+ },
+ onStopRequest: function (req, ctx, status)
+ {
+ var chan = req.QueryInterface(Ci.nsIChannel);
+ try {
+ do_check_eq(chan.URI.spec, this._origURI.spec);
+ do_check_eq(chan.URI, this._origURI);
+ do_check_eq(chan.originalURI.spec, this._origURI.spec);
+ do_check_eq(chan.originalURI, this._origURI);
+ do_check_eq(status, Cr.NS_ERROR_ABORT);
+ do_check_false(chan.isPending());
+ } catch(e) {}
+ this._nextTest();
+ }
+};
+
+function test_cancel()
+{
+ var chan = NetUtil.newChannel({
+ uri: linkURI,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_eq(chan.URI, linkURI);
+ do_check_eq(chan.originalURI, linkURI);
+ chan.asyncOpen2(new RequestObserver(linkURI, newURI, do_test_finished));
+ do_check_true(chan.isPending());
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ do_check_true(chan.isPending());
+}
+
+function run_test()
+{
+ if (mozinfo.os != "win" && mozinfo.os != "linux") {
+ return;
+ }
+
+ link = getLinkFile();
+ linkURI = ios.newFileURI(link);
+
+ do_test_pending();
+ var chan = NetUtil.newChannel({
+ uri: linkURI,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_eq(chan.URI, linkURI);
+ do_check_eq(chan.originalURI, linkURI);
+ chan.notificationCallbacks = new NotificationCallbacks(linkURI, newURI);
+ chan.asyncOpen2(new RequestObserver(linkURI, newURI, test_cancel));
+ do_check_true(chan.isPending());
+}
diff --git a/netwerk/test/unit/test_bug455598.js b/netwerk/test/unit/test_bug455598.js
new file mode 100644
index 000000000..d0f9e459e
--- /dev/null
+++ b/netwerk/test/unit/test_bug455598.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ const time = (new Date("Jan 1, 2030")).getTime() / 1000;
+ var cookie = {
+ name: "foo",
+ value: "bar",
+ isDomain: true,
+ host: "example.com",
+ path: "/baz",
+ isSecure: false,
+ expires: time,
+ status: 0,
+ policy: 0,
+ isSession: false,
+ expiry: time,
+ isHttpOnly: true,
+ QueryInterface: function(iid) {
+ const validIIDs = [Components.interfaces.nsISupports,
+ Components.interfaces.nsICookie,
+ Components.interfaces.nsICookie2];
+ for (var i = 0; i < validIIDs.length; ++i)
+ if (iid == validIIDs[i])
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ };
+ var cm = Components.classes["@mozilla.org/cookiemanager;1"].
+ getService(Components.interfaces.nsICookieManager2);
+ do_check_false(cm.cookieExists(cookie));
+ // if the above line does not crash, the test was successful
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_bug464591.js b/netwerk/test/unit/test_bug464591.js
new file mode 100644
index 000000000..34d7c46b5
--- /dev/null
+++ b/netwerk/test/unit/test_bug464591.js
@@ -0,0 +1,81 @@
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+
+ // 1.percent-encoded IDN that contains blacklisted character should be converted
+ // to punycode, not UTF-8 string
+ // 2.only hostname-valid percent encoded ASCII characters should be decoded
+ // 3.IDN convertion must not bypassed by %00
+let reference = [
+ ["www.example.com%e2%88%95www.mozill%d0%b0.com%e2%81%84www.mozilla.org",
+ "www.example.xn--comwww-re3c.xn--mozill-8nf.xn--comwww-rq0c.mozilla.org"],
+ ["www.mozill%61%2f.org", "www.mozilla%2f.org"], // a slash is not valid in the hostname
+ ["www.e%00xample.com%e2%88%95www.mozill%d0%b0.com%e2%81%84www.mozill%61.org",
+ "www.e%00xample.xn--comwww-re3c.xn--mozill-8nf.xn--comwww-rq0c.mozilla.org"],
+];
+
+let prefData =
+ [
+ {
+ name: "network.enableIDN",
+ newVal: true
+ },
+ {
+ name: "network.IDN_show_punycode",
+ newVal: false
+ },
+ {
+ name: "network.IDN.whitelist.org",
+ newVal: true
+ }
+ ];
+
+ let prefIdnBlackList = {
+ name: "network.IDN.blacklist_chars",
+ minimumList: "\u2215\u0430\u2044",
+ };
+
+function stringToURL(str) {
+ return (new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 80,
+ str, "UTF-8", null))
+ .QueryInterface(Ci.nsIURL);
+}
+
+function run_test() {
+ // Make sure our prefs are set such that this test actually means something
+ let prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ for (let pref of prefData) {
+ prefs.setBoolPref(pref.name, pref.newVal);
+ }
+
+ prefIdnBlackList.set = false;
+ try {
+ prefIdnBlackList.oldVal = prefs.getComplexValue(prefIdnBlackList.name,
+ Ci.nsIPrefLocalizedString).data;
+ prefs.getComplexValue(prefIdnBlackList.name,
+ Ci.nsIPrefLocalizedString).data=prefIdnBlackList.minimumList;
+ prefIdnBlackList.set = true;
+ } catch (e) {
+ }
+
+ do_register_cleanup(function() {
+ for (let pref of prefData) {
+ prefs.clearUserPref(pref.name);
+ }
+ if (prefIdnBlackList.set) {
+ prefs.getComplexValue(prefIdnBlackList.name,
+ Ci.nsIPrefLocalizedString).data = prefIdnBlackList.oldVal;
+ }
+ });
+
+ for (let i = 0; i < reference.length; ++i) {
+ try {
+ let result = stringToURL("http://" + reference[i][0]).host;
+ equal(result, reference[i][1]);
+ } catch (e) {
+ ok(false, "Error testing "+reference[i][0]);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_bug468426.js b/netwerk/test/unit/test_bug468426.js
new file mode 100644
index 000000000..95ce32674
--- /dev/null
+++ b/netwerk/test/unit/test_bug468426.js
@@ -0,0 +1,100 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ // Initial request. Cached variant will have no cookie
+ { url : "/bug468426", server : "0", expected : "0", cookie: null},
+
+ // Cache now contains a variant with no value for cookie. If we don't
+ // set cookie we expect to receive the cached variant
+ { url : "/bug468426", server : "1", expected : "0", cookie: null},
+
+ // Cache still contains a variant with no value for cookie. If we
+ // set a value for cookie we expect a fresh value
+ { url : "/bug468426", server : "2", expected : "2", cookie: "c=2"},
+
+ // Cache now contains a variant with cookie "c=2". If the request
+ // also set cookie "c=2", we expect to receive the cached variant.
+ { url : "/bug468426", server : "3", expected : "2", cookie: "c=2"},
+
+ // Cache still contains a variant with cookie "c=2". When setting
+ // cookie "c=4" in the request we expect a fresh value
+ { url : "/bug468426", server : "4", expected : "4", cookie: "c=4"},
+
+ // Cache now contains a variant with cookie "c=4". When setting
+ // cookie "c=4" in the request we expect the cached variant
+ { url : "/bug468426", server : "5", expected : "4", cookie: "c=4"},
+
+ // Cache still contains a variant with cookie "c=4". When setting
+ // no cookie in the request we expect a fresh value
+ { url : "/bug468426", server : "6", expected : "6", cookie: null},
+
+];
+
+function setupChannel(suffix, value, cookie) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ })
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ if (cookie != null)
+ httpChan.setRequestHeader("Cookie", cookie, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server, tests[index].cookie);
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ do_check_eq(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ index++;
+ // This call happens in onStopRequest from the channel. Opening a new
+ // channel to the same url here is no good idea! Post it instead...
+ do_timeout(1, triggerNextTest);
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/bug468426", handler);
+ httpserver.start(-1);
+
+ // Clear cache and trigger the first test
+ evict_cache_entries();
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = "unset";
+ try {
+ body = metadata.getHeader("x-request");
+ } catch(e) { }
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ response.setHeader("Vary", "Cookie", false);
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+ 'Sep', 'Oct', 'Nov', 'Dec' ];
+ var days = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
+
+ var d = new Date();
+ return days[d.getUTCDay()] + ", " + d.getUTCDate() + " "
+ + months[d.getUTCMonth()] + " " + (d.getUTCFullYear() + yearDelta)
+ + " " + d.getUTCHours() + ":" + d.getUTCMinutes() + ":"
+ + d.getUTCSeconds() + " UTC";
+}
diff --git a/netwerk/test/unit/test_bug468594.js b/netwerk/test/unit/test_bug468594.js
new file mode 100644
index 000000000..66d463190
--- /dev/null
+++ b/netwerk/test/unit/test_bug468594.js
@@ -0,0 +1,127 @@
+//
+// This script emulates the test called "Freshness"
+// by Mark Nottingham, located at
+//
+// http://mnot.net/javascript/xmlhttprequest/cache.html
+//
+// The issue with Mr. Nottinghams page is that the server
+// always seems to send an Expires-header in the response,
+// breaking the finer details of the test. This script has
+// full control of response-headers, however, and can perform
+// the intended testing plus some extra stuff.
+//
+// Please see RFC 2616 section 13.2.1 6th paragraph for the
+// definition of "explicit expiration time" being used here.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ {url: "/freshness", server: "0", expected: "0"},
+ {url: "/freshness", server: "1", expected: "0"}, // cached
+
+ // RFC 2616 section 13.9 2nd paragraph says not to heuristically cache
+ // querystring, but we allow it to maintain web compat
+ {url: "/freshness?a", server: "2", expected: "2"},
+ {url: "/freshness?a", server: "3", expected: "2"},
+
+ // explicit expiration dates in the future should be cached
+ {url: "/freshness?b", server: "4", expected: "4",
+ responseheader: "Expires: "+getDateString(1)},
+ {url: "/freshness?b", server: "5", expected: "4"},// cached due to Expires
+
+ {url: "/freshness?c", server: "6", expected: "6",
+ responseheader: "Cache-Control: max-age=3600"},
+ {url: "/freshness?c", server: "7", expected: "6"}, // cached due to max-age
+
+ // explicit expiration dates in the past should NOT be cached
+ {url: "/freshness?d", server: "8", expected: "8",
+ responseheader: "Expires: "+getDateString(-1)},
+ {url: "/freshness?d", server: "9", expected: "9"},
+
+ {url: "/freshness?e", server: "10", expected: "10",
+ responseheader: "Cache-Control: max-age=0"},
+ {url: "/freshness?e", server: "11", expected: "11"},
+
+ {url: "/freshness", server: "99", expected: "0"}, // cached
+];
+
+function logit(i, data) {
+ dump(tests[i].url + "\t requested [" + tests[i].server + "]" +
+ " got [" + data + "] expected [" + tests[i].expected + "]");
+ if (tests[i].responseheader)
+ dump("\t[" + tests[i].responseheader + "]");
+ dump("\n");
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server);
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data);
+ do_check_eq(tests[index].expected, data);
+
+ if (index < tests.length-1) {
+ index++;
+ triggerNextTest();
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/freshness", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = metadata.getHeader("x-request");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Date", getDateString(0), false);
+
+ var header = tests[index].responseheader;
+ if (header == null) {
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ } else {
+ var splitHdr = header.split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+
+ var d = new Date();
+ return days[d.getUTCDay()] + ", " +
+ d.getUTCDate() + " " +
+ months[d.getUTCMonth()] + " " +
+ (d.getUTCFullYear() + yearDelta) + " " +
+ d.getUTCHours() + ":" + d.getUTCMinutes() +":" +
+ d.getUTCSeconds() + " UTC";
+}
diff --git a/netwerk/test/unit/test_bug470716.js b/netwerk/test/unit/test_bug470716.js
new file mode 100644
index 000000000..7d2ab4bc2
--- /dev/null
+++ b/netwerk/test/unit/test_bug470716.js
@@ -0,0 +1,174 @@
+var CC = Components.Constructor;
+
+const StreamCopier = CC("@mozilla.org/network/async-stream-copier;1",
+ "nsIAsyncStreamCopier",
+ "init");
+
+const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init");
+
+const Pipe = CC("@mozilla.org/pipe;1",
+ "nsIPipe",
+ "init");
+
+var pipe1;
+var pipe2;
+var copier;
+var test_result;
+var test_content;
+var test_source_closed;
+var test_sink_closed;
+var test_nr;
+
+var copyObserver =
+{
+ onStartRequest: function(request, context) { },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ // check status code
+ do_check_eq(statusCode, test_result);
+
+ // check number of copied bytes
+ do_check_eq(pipe2.inputStream.available(), test_content.length);
+
+ // check content
+ var scinp = new ScriptableInputStream(pipe2.inputStream);
+ var content = scinp.read(scinp.available());
+ do_check_eq(content, test_content);
+
+ // check closed sink
+ try {
+ pipe2.outputStream.write("closedSinkTest", 14);
+ do_check_false(test_sink_closed);
+ }
+ catch (ex) {
+ do_check_true(test_sink_closed);
+ }
+
+ // check closed source
+ try {
+ pipe1.outputStream.write("closedSourceTest", 16);
+ do_check_false(test_source_closed);
+ }
+ catch (ex) {
+ do_check_true(test_source_closed);
+ }
+
+ do_timeout(0, do_test);
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function startCopier(closeSource, closeSink) {
+ pipe1 = new Pipe(true /* nonBlockingInput */,
+ true /* nonBlockingOutput */,
+ 0 /* segmentSize */,
+ 0xffffffff /* segmentCount */,
+ null /* segmentAllocator */);
+
+ pipe2 = new Pipe(true /* nonBlockingInput */,
+ true /* nonBlockingOutput */,
+ 0 /* segmentSize */,
+ 0xffffffff /* segmentCount */,
+ null /* segmentAllocator */);
+
+ copier = new StreamCopier(pipe1.inputStream /* aSource */,
+ pipe2.outputStream /* aSink */,
+ null /* aTarget */,
+ true /* aSourceBuffered */,
+ true /* aSinkBuffered */,
+ 8192 /* aChunkSize */,
+ closeSource /* aCloseSource */,
+ closeSink /* aCloseSink */);
+
+ copier.asyncCopy(copyObserver, null);
+}
+
+function do_test() {
+
+ test_nr++;
+ test_content = "test" + test_nr;
+
+ switch (test_nr) {
+ case 1:
+ case 2: // close sink
+ case 3: // close source
+ case 4: // close both
+ // test canceling transfer
+ // use some undefined error code to check if it is successfully passed
+ // to the request observer
+ test_result = 0x87654321;
+
+ test_source_closed = ((test_nr-1)>>1 != 0);
+ test_sink_closed = ((test_nr-1)%2 != 0);
+
+ startCopier(test_source_closed, test_sink_closed);
+ pipe1.outputStream.write(test_content, test_content.length);
+ pipe1.outputStream.flush();
+ do_timeout(20,
+ function(){
+ copier.cancel(test_result);
+ pipe1.outputStream.write("a", 1);});
+ break;
+ case 5:
+ case 6: // close sink
+ case 7: // close source
+ case 8: // close both
+ // test copying with EOF on source
+ test_result = 0;
+
+ test_source_closed = ((test_nr-5)>>1 != 0);
+ test_sink_closed = ((test_nr-5)%2 != 0);
+
+ startCopier(test_source_closed, test_sink_closed);
+ pipe1.outputStream.write(test_content, test_content.length);
+ // we will close the source
+ test_source_closed = true;
+ pipe1.outputStream.close();
+ break;
+ case 9:
+ case 10: // close sink
+ case 11: // close source
+ case 12: // close both
+ // test copying with error on sink
+ // use some undefined error code to check if it is successfully passed
+ // to the request observer
+ test_result = 0x87654321;
+
+ test_source_closed = ((test_nr-9)>>1 != 0);
+ test_sink_closed = ((test_nr-9)%2 != 0);
+
+ startCopier(test_source_closed, test_sink_closed);
+ pipe1.outputStream.write(test_content, test_content.length);
+ pipe1.outputStream.flush();
+ // we will close the sink
+ test_sink_closed = true;
+ do_timeout(20,
+ function()
+ {
+ pipe2.outputStream
+ .QueryInterface(Ci.nsIAsyncOutputStream)
+ .closeWithStatus(test_result);
+ pipe1.outputStream.write("a", 1);});
+ break;
+ case 13:
+ do_test_finished();
+ break;
+ }
+}
+
+function run_test() {
+ test_nr = 0;
+ do_timeout(0, do_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug477578.js b/netwerk/test/unit/test_bug477578.js
new file mode 100644
index 000000000..942ab1e00
--- /dev/null
+++ b/netwerk/test/unit/test_bug477578.js
@@ -0,0 +1,50 @@
+// test that methods are not normalized
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const testMethods = [
+ ["GET"],
+ ["get"],
+ ["Get"],
+ ["gET"],
+ ["gEt"],
+ ["post"],
+ ["POST"],
+ ["head"],
+ ["HEAD"],
+ ["put"],
+ ["PUT"],
+ ["delete"],
+ ["DELETE"],
+ ["connect"],
+ ["CONNECT"],
+ ["options"],
+ ["trace"],
+ ["track"],
+ ["copy"],
+ ["index"],
+ ["lock"],
+ ["m-post"],
+ ["mkcol"],
+ ["move"],
+ ["propfind"],
+ ["proppatch"],
+ ["unlock"],
+ ["link"],
+ ["LINK"],
+ ["foo"],
+ ["foO"],
+ ["fOo"],
+ ["Foo"]
+]
+
+function run_test() {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost/",
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ for (var i = 0; i < testMethods.length; i++) {
+ chan.requestMethod = testMethods[i];
+ do_check_eq(chan.requestMethod, testMethods[i]);
+ }
+}
diff --git a/netwerk/test/unit/test_bug479413.js b/netwerk/test/unit/test_bug479413.js
new file mode 100644
index 000000000..1a5e3335b
--- /dev/null
+++ b/netwerk/test/unit/test_bug479413.js
@@ -0,0 +1,59 @@
+/**
+ * Test for unassigned code points in IDNs (RFC 3454 section 7)
+ */
+
+var idnService;
+
+function expected_pass(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ do_check_eq(displayIDN, inputIDN);
+}
+
+function expected_fail(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = "";
+
+ try {
+ displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ }
+ catch(e) {}
+
+ do_check_neq(displayIDN, inputIDN);
+}
+
+function run_test() {
+ // add an IDN whitelist pref
+ var pbi = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ var whitelistPref = "network.IDN.whitelist.com";
+
+ pbi.setBoolPref(whitelistPref, true);
+
+ idnService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+
+ // assigned code point
+ expected_pass("foo\u0101bar.com");
+
+ // assigned code point in punycode. Should *fail* because the URL will be
+ // converted to Unicode for display
+ expected_fail("xn--foobar-5za.com");
+
+ // unassigned code point
+ expected_fail("foo\u3040bar.com");
+
+ // unassigned code point in punycode. Should *pass* because the URL will not
+ // be converted to Unicode
+ expected_pass("xn--foobar-533e.com");
+
+ // code point assigned since Unicode 3.0
+ // XXX This test will unexpectedly pass when we update to IDNAbis
+ expected_fail("foo\u0370bar.com");
+
+ // reset the pref
+ if (pbi.prefHasUserValue(whitelistPref))
+ pbi.clearUserPref(whitelistPref);
+}
diff --git a/netwerk/test/unit/test_bug479485.js b/netwerk/test/unit/test_bug479485.js
new file mode 100644
index 000000000..05f0bc4f0
--- /dev/null
+++ b/netwerk/test/unit/test_bug479485.js
@@ -0,0 +1,47 @@
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var test_port = function(port, exception_expected)
+ {
+ dump((port || "no port provided") + "\n");
+ var exception_threw = false;
+ try {
+ var newURI = ios.newURI("http://foo.com"+port, null, null);
+ }
+ catch (e) {
+ exception_threw = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (exception_threw != exception_expected)
+ do_throw("We did"+(exception_expected?"n't":"")+" throw NS_ERROR_MALFORMED_URI when creating a new URI with "+port+" as a port");
+ do_check_eq(exception_threw, exception_expected);
+
+ exception_threw = false;
+ newURI = ios.newURI("http://foo.com", null, null);
+ try {
+ newURI.spec = "http://foo.com"+port;
+ }
+ catch (e) {
+ exception_threw = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (exception_threw != exception_expected)
+ do_throw("We did"+(exception_expected?"n't":"")+" throw NS_ERROR_MALFORMED_URI when setting a spec of a URI with "+port+" as a port");
+ do_check_eq(exception_threw, exception_expected);
+ }
+
+ test_port(":invalid", true);
+ test_port(":-2", true);
+ test_port(":-1", true);
+ test_port(":0", false);
+ test_port(":185891548721348172817857824356013651809236172635716571865023757816234081723451516780356", true);
+
+ // Following 3 tests are all failing, we do not throw, although we parse the whole string and use only 5870 as a portnumber
+ test_port(":5870:80", true);
+ test_port(":5870-80", true);
+ test_port(":5870+80", true);
+
+ // Just a regression check
+ test_port(":5870", false);
+ test_port(":80", false);
+ test_port("", false);
+}
diff --git a/netwerk/test/unit/test_bug482601.js b/netwerk/test/unit/test_bug482601.js
new file mode 100644
index 000000000..fde70f005
--- /dev/null
+++ b/netwerk/test/unit/test_bug482601.js
@@ -0,0 +1,233 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv = null;
+var test_nr = 0;
+var observers_called = "";
+var handlers_called = "";
+var buffer = "";
+
+var observer = {
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Ci.nsISupports) ||
+ aIID.equals(Ci.nsIObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data) {
+ if (observers_called.length)
+ observers_called += ",";
+
+ observers_called += topic;
+ }
+};
+
+var listener = {
+ onStartRequest: function (request, ctx) {
+ buffer = "";
+ },
+
+ onDataAvailable: function (request, ctx, stream, offset, count) {
+ buffer = buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest: function (request, ctx, status) {
+ do_check_eq(status, Cr.NS_OK);
+ do_check_eq(buffer, "0123456789");
+ do_check_eq(observers_called, results[test_nr]);
+ test_nr++;
+ do_timeout(0, do_test);
+ }
+};
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/bug482601/nocache", bug482601_nocache);
+ httpserv.registerPathHandler("/bug482601/partial", bug482601_partial);
+ httpserv.registerPathHandler("/bug482601/cached", bug482601_cached);
+ httpserv.registerPathHandler("/bug482601/only_from_cache", bug482601_only_from_cache);
+ httpserv.start(-1);
+
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.addObserver(observer, "http-on-examine-response", false);
+ obs.addObserver(observer, "http-on-examine-merged-response", false);
+ obs.addObserver(observer, "http-on-examine-cached-response", false);
+
+ do_timeout(0, do_test);
+ do_test_pending();
+}
+
+function do_test() {
+ if (test_nr < tests.length) {
+ tests[test_nr]();
+ }
+ else {
+ do_check_eq(handlers_called, "nocache,partial,cached");
+ httpserv.stop(do_test_finished);
+ }
+}
+
+var tests = [test_nocache,
+ test_partial,
+ test_cached,
+ test_only_from_cache];
+
+var results = ["http-on-examine-response",
+ "http-on-examine-response,http-on-examine-merged-response",
+ "http-on-examine-response,http-on-examine-merged-response",
+ "http-on-examine-cached-response"];
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function storeCache(aCacheEntry, aResponseHeads, aContent) {
+ aCacheEntry.setMetaDataElement("request-method", "GET");
+ aCacheEntry.setMetaDataElement("response-head", aResponseHeads);
+ aCacheEntry.setMetaDataElement("charset", "ISO-8859-1");
+
+ var oStream = aCacheEntry.openOutputStream(0);
+ var written = oStream.write(aContent, aContent.length);
+ if (written != aContent.length) {
+ do_throw("oStream.write has not written all data!\n" +
+ " Expected: " + written + "\n" +
+ " Actual: " + aContent.length + "\n");
+ }
+ oStream.close();
+ aCacheEntry.close();
+}
+
+function test_nocache() {
+ observers_called = "";
+
+ var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+ "/bug482601/nocache");
+ chan.asyncOpen2(listener);
+}
+
+function test_partial() {
+ asyncOpenCacheEntry("http://localhost:" + httpserv.identity.primaryPort +
+ "/bug482601/partial",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ test_partial2);
+}
+
+function test_partial2(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ storeCache(entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123");
+
+ observers_called = "";
+
+ var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+ "/bug482601/partial");
+ chan.asyncOpen2(listener);
+}
+
+function test_cached() {
+ asyncOpenCacheEntry("http://localhost:" + httpserv.identity.primaryPort +
+ "/bug482601/cached",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ test_cached2);
+}
+
+function test_cached2(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ storeCache(entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123456789");
+
+ observers_called = "";
+
+ var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+ "/bug482601/cached");
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen2(listener);
+}
+
+function test_only_from_cache() {
+ asyncOpenCacheEntry("http://localhost:" + httpserv.identity.primaryPort +
+ "/bug482601/only_from_cache",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ test_only_from_cache2);
+}
+
+function test_only_from_cache2(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ storeCache(entry,
+ "HTTP/1.1 200 OK\r\n" +
+ "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Server: httpd.js\r\n" +
+ "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+ "Accept-Ranges: bytes\r\n" +
+ "Content-Length: 10\r\n" +
+ "Content-Type: text/plain\r\n",
+ "0123456789");
+
+ observers_called = "";
+
+ var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+ "/bug482601/only_from_cache");
+ chan.loadFlags = Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE;
+ chan.asyncOpen2(listener);
+}
+
+
+// PATHS
+
+// /bug482601/nocache
+function bug482601_nocache(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ var body = "0123456789";
+ response.bodyOutputStream.write(body, body.length);
+ handlers_called += "nocache";
+}
+
+// /bug482601/partial
+function bug482601_partial(metadata, response) {
+ do_check_true(metadata.hasHeader("If-Range"));
+ do_check_eq(metadata.getHeader("If-Range"),
+ "Thu, 1 Jan 2009 00:00:00 GMT");
+ do_check_true(metadata.hasHeader("Range"));
+ do_check_eq(metadata.getHeader("Range"), "bytes=4-");
+
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "bytes 4-9/10", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", "Thu, 1 Jan 2009 00:00:00 GMT");
+
+ var body = "456789";
+ response.bodyOutputStream.write(body, body.length);
+ handlers_called += ",partial";
+}
+
+// /bug482601/cached
+function bug482601_cached(metadata, response) {
+ do_check_true(metadata.hasHeader("If-Modified-Since"));
+ do_check_eq(metadata.getHeader("If-Modified-Since"),
+ "Thu, 1 Jan 2009 00:00:00 GMT");
+
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ handlers_called += ",cached";
+}
+
+// /bug482601/only_from_cache
+function bug482601_only_from_cache(metadata, response) {
+ do_throw("This should not be reached");
+}
diff --git a/netwerk/test/unit/test_bug484684.js b/netwerk/test/unit/test_bug484684.js
new file mode 100644
index 000000000..259d8cec1
--- /dev/null
+++ b/netwerk/test/unit/test_bug484684.js
@@ -0,0 +1,115 @@
+const URL = "ftp://localhost/bug464884/";
+
+const tests = [
+ // standard ls unix format
+ ["-rw-rw-r-- 1 500 500 0 Jan 01 2000 file1\r\n" +
+ "-rw-rw-r-- 1 500 500 0 Jan 01 2000 file2\r\n",
+
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"file1\" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 FILE \n" +
+ "201: \"%20file2\" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 FILE \n"],
+ // old Hellsoft unix format
+ ["-[RWCEMFA] supervisor 214059 Jan 01 2000 file1\r\n" +
+ "-[RWCEMFA] supervisor 214059 Jan 01 2000 file2\r\n",
+
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"file1\" 214059 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 FILE \n" +
+ "201: \"file2\" 214059 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 FILE \n"],
+ // new Hellsoft unix format
+ ["- [RWCEAFMS] jrd 192 Jan 01 2000 file1\r\n"+
+ "- [RWCEAFMS] jrd 192 Jan 01 2000 file2\r\n",
+
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"file1\" 192 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 FILE \n" +
+ "201: \"%20file2\" 192 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 FILE \n"],
+ // DOS format with correct offsets
+ ["01-01-00 01:00AM <DIR> dir1\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction1 -> foo1\r\n" +
+ "01-01-00 01:00AM 95077 file1\r\n" +
+ "01-01-00 01:00AM <DIR> dir2\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction2 -> foo2\r\n" +
+ "01-01-00 01:00AM 95077 file2\r\n",
+
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"dir1\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"junction1\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"file1\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n" +
+ "201: \"%20dir2\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"%20junction2\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"%20file2\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n"],
+ // DOS format with wrong offsets
+ ["01-01-00 01:00AM <DIR> dir1\r\n" +
+ "01-01-00 01:00AM <DIR> dir2\r\n" +
+ "01-01-00 01:00AM <DIR> dir3\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction1 -> foo1\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction2 -> foo2\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction3 -> foo3\r\n" +
+ "01-01-00 01:00AM 95077 file1\r\n" +
+ "01-01-00 01:00AM 95077 file2\r\n",
+
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"dir1\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"dir2\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"dir3\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"junction1\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"junction2\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"junction3\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"file1\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n" +
+ "201: \"file2\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n"]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(next_test);
+}
+
+function storeData(status, entry) {
+ var scs = Cc["@mozilla.org/streamConverters;1"].
+ getService(Ci.nsIStreamConverterService);
+ var converter = scs.asyncConvertData("text/ftp-dir", "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL), null);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.data = tests[0][0];
+
+ var url = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0)
+ do_test_finished();
+ else {
+ storeData();
+ }
+}
+
+function run_test() {
+ do_execute_soon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug490095.js b/netwerk/test/unit/test_bug490095.js
new file mode 100644
index 000000000..8b588b1a8
--- /dev/null
+++ b/netwerk/test/unit/test_bug490095.js
@@ -0,0 +1,116 @@
+//
+// Verify that the VALIDATE_NEVER and LOAD_FROM_CACHE flags override
+// heuristic query freshness as defined in RFC 2616 section 13.9
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ {url: "/freshness?a", server: "0", expected: "0"},
+ {url: "/freshness?a", server: "1", expected: "1"},
+
+ // Setting the VALIDATE_NEVER flag should grab entry from cache
+ {url: "/freshness?a", server: "2", expected: "1",
+ flags: Components.interfaces.nsIRequest.VALIDATE_NEVER },
+
+ // Finally, check that request is validated with no flags set
+ {url: "/freshness?a", server: "99", expected: "99"},
+
+ {url: "/freshness?b", server: "0", expected: "0"},
+ {url: "/freshness?b", server: "1", expected: "1"},
+
+ // Setting the LOAD_FROM_CACHE flag also grab the entry from cache
+ {url: "/freshness?b", server: "2", expected: "1",
+ flags: Components.interfaces.nsIRequest.LOAD_FROM_CACHE },
+
+ // Finally, check that request is validated with no flags set
+ {url: "/freshness?b", server: "99", expected: "99"},
+
+];
+
+function logit(i, data) {
+ dump(tests[i].url + "\t requested [" + tests[i].server + "]" +
+ " got [" + data + "] expected [" + tests[i].expected + "]");
+ if (tests[i].responseheader)
+ dump("\t[" + tests[i].responseheader + "]");
+ dump("\n");
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var test = tests[index];
+ var channel = setupChannel(test.url, test.server);
+ if (test.flags) channel.loadFlags = test.flags;
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data);
+ do_check_eq(tests[index].expected, data);
+
+ if (index < tests.length-1) {
+ index++;
+ // this call happens in onStopRequest from the channel, and opening a
+ // new channel to the same url here is no good idea... post it instead
+ do_timeout(1, triggerNextTest);
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/freshness", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = metadata.getHeader("x-request");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Date", getDateString(0), false);
+ response.setHeader("Cache-Control", "max-age=0", false);
+
+ var header = tests[index].responseheader;
+ if (header == null) {
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ } else {
+ var splitHdr = header.split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function getDateString(yearDelta) {
+ var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+
+ var d = new Date();
+ return days[d.getUTCDay()] + ", " +
+ d.getUTCDate() + " " +
+ months[d.getUTCMonth()] + " " +
+ (d.getUTCFullYear() + yearDelta) + " " +
+ d.getUTCHours() + ":" + d.getUTCMinutes() +":" +
+ d.getUTCSeconds() + " UTC";
+}
diff --git a/netwerk/test/unit/test_bug504014.js b/netwerk/test/unit/test_bug504014.js
new file mode 100644
index 000000000..5c28dfa32
--- /dev/null
+++ b/netwerk/test/unit/test_bug504014.js
@@ -0,0 +1,69 @@
+var valid_URIs = [ "http://[::]/",
+ "http://[::1]/",
+ "http://[1::]/",
+ "http://[::]/",
+ "http://[::1]/",
+ "http://[1::]/",
+ "http://[1:2:3:4:5:6:7::]/",
+ "http://[::1:2:3:4:5:6:7]/",
+ "http://[1:2:a:B:c:D:e:F]/",
+ "http://[1::8]/",
+ "http://[1:2::8]/",
+ "http://[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]/",
+ "http://[::192.168.1.1]/",
+ "http://[1::0.0.0.0]/",
+ "http://[1:2::255.255.255.255]/",
+ "http://[1:2:3::255.255.255.255]/",
+ "http://[1:2:3:4::255.255.255.255]/",
+ "http://[1:2:3:4:5::255.255.255.255]/",
+ "http://[1:2:3:4:5:6:255.255.255.255]/"];
+
+var invalid_URIs = [ "http://[1]/",
+ "http://[192.168.1.1]/",
+ "http://[:::]/",
+ "http://[:::1]/",
+ "http://[1:::]/",
+ "http://[::1::]/",
+ "http://[1:2:3:4:5:6:7:]/",
+ "http://[:2:3:4:5:6:7:8]/",
+ "http://[1:2:3:4:5:6:7:8:]/",
+ "http://[:1:2:3:4:5:6:7:8]/",
+ "http://[1:2:3:4:5:6:7:8::]/",
+ "http://[::1:2:3:4:5:6:7:8]/",
+ "http://[1:2:3:4:5:6:7]/",
+ "http://[1:2:3:4:5:6:7:8:9]/",
+ "http://[00001:2:3:4:5:6:7:8]/",
+ "http://[0001:2:3:4:5:6:7:89abc]/",
+ "http://[A:b:C:d:E:f:G:h]/",
+ "http://[::192.168.1]/",
+ "http://[::192.168.1.]/",
+ "http://[::.168.1.1]/",
+ "http://[::192..1.1]/",
+ "http://[::0192.168.1.1]/",
+ "http://[::256.255.255.255]/",
+ "http://[::1x.255.255.255]/",
+ "http://[::192.4294967464.1.1]/",
+ "http://[1:2:3:4:5:6::255.255.255.255]/",
+ "http://[1:2:3:4:5:6:7:255.255.255.255]/"];
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ for (var i=0 ; i<valid_URIs.length ; i++) {
+ try {
+ var uri = ios.newURI(valid_URIs[i], null, null);
+ } catch (e) {
+ do_throw("cannot create URI:" + valid_URIs[i]);
+ }
+ }
+
+ for (var i=0 ; i<invalid_URIs.length ; i++) {
+ try {
+ var uri = ios.newURI(invalid_URIs[i], null, null);
+ do_throw("should throw: " + invalid_URIs[i]);
+ } catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_MALFORMED_URI);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_bug510359.js b/netwerk/test/unit/test_bug510359.js
new file mode 100644
index 000000000..176b5ed37
--- /dev/null
+++ b/netwerk/test/unit/test_bug510359.js
@@ -0,0 +1,77 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ { url : "/bug510359", server : "0", expected : "0"},
+ { url : "/bug510359", server : "1", expected : "1"},
+];
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ httpChan.setRequestHeader("Cookie", "c="+value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server);
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ do_check_eq(tests[index].expected, data);
+
+ if (index < tests.length - 1) {
+ index++;
+ triggerNextTest();
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/bug510359", handler);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ triggerNextTest();
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ try {
+ var IMS = metadata.getHeader("If-Modified-Since");
+ response.setStatusLine(metadata.httpVersion, 500, "Failed");
+ var msg = "Client should not set If-Modified-Since header";
+ response.bodyOutputStream.write(msg, msg.length);
+ } catch(ex) {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", getDateString(-1), false);
+ response.setHeader("Vary", "Cookie", false);
+ var body = metadata.getHeader("x-request");
+ response.bodyOutputStream.write(body, body.length);
+ }
+}
+
+function getDateString(yearDelta) {
+ var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+ 'Sep', 'Oct', 'Nov', 'Dec' ];
+ var days = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
+
+ var d = new Date();
+ return days[d.getUTCDay()] + ", " + d.getUTCDate() + " "
+ + months[d.getUTCMonth()] + " " + (d.getUTCFullYear() + yearDelta)
+ + " " + d.getUTCHours() + ":" + d.getUTCMinutes() + ":"
+ + d.getUTCSeconds() + " UTC";
+}
diff --git a/netwerk/test/unit/test_bug515583.js b/netwerk/test/unit/test_bug515583.js
new file mode 100644
index 000000000..28e43a3de
--- /dev/null
+++ b/netwerk/test/unit/test_bug515583.js
@@ -0,0 +1,73 @@
+const URL = "ftp://localhost/bug515583/";
+
+const tests = [
+ ["[RWCEM1 4 1-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1] 4 2-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]A 4 3-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]B; 4 4-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1];1 4 5-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]; 4 6-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]C;1D 4 7-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]E;1 4 8-MAR-1993 18:09:01.12\r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"A\" 2048 Wed%2C%2003%20Mar%201993%2018%3A09%3A01 FILE \n" +
+ "201: \"E\" 2048 Mon%2C%2008%20Mar%201993%2018%3A09%3A01 FILE \n"]
+ ,
+ ["\r\r\r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n"]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(next_test);
+}
+
+function storeData(status, entry) {
+ var scs = Cc["@mozilla.org/streamConverters;1"].
+ getService(Ci.nsIStreamConverterService);
+ var converter = scs.asyncConvertData("text/ftp-dir", "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL), null);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.data = tests[0][0];
+
+ var url = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0)
+ do_test_finished();
+ else {
+ storeData();
+ }
+}
+
+function run_test() {
+ do_execute_soon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug528292.js b/netwerk/test/unit/test_bug528292.js
new file mode 100644
index 000000000..ef030baee
--- /dev/null
+++ b/netwerk/test/unit/test_bug528292.js
@@ -0,0 +1,90 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const sentCookieVal = "foo=bar";
+const responseBody = "response body";
+
+XPCOMUtils.defineLazyGetter(this, "baseURL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+const preRedirectPath = "/528292/pre-redirect";
+
+XPCOMUtils.defineLazyGetter(this, "preRedirectURL", function() {
+ return baseURL + preRedirectPath;
+});
+
+const postRedirectPath = "/528292/post-redirect";
+
+XPCOMUtils.defineLazyGetter(this, "postRedirectURL", function() {
+ return baseURL + postRedirectPath;
+});
+
+var httpServer = null;
+var receivedCookieVal = null;
+
+function preRedirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", postRedirectURL, false);
+ return;
+}
+
+function postRedirectHandler(metadata, response)
+{
+ receivedCookieVal = metadata.getHeader("Cookie");
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function run_test()
+{
+ // Start the HTTP server.
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(preRedirectPath, preRedirectHandler);
+ httpServer.registerPathHandler(postRedirectPath, postRedirectHandler);
+ httpServer.start(-1);
+
+ if (!inChildProcess()) {
+ // Disable third-party cookies in general.
+ Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch).
+ setIntPref("network.cookie.cookieBehavior", 1);
+ }
+
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ // Set up a channel with forceAllowThirdPartyCookie set to true. We'll use
+ // the channel both to set a cookie (since nsICookieService::setCookieString
+ // requires such a channel in order to successfully set a cookie) and then
+ // to load the pre-redirect URI.
+ var chan = NetUtil.newChannel({
+ uri: preRedirectURL,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+ chan.forceAllowThirdPartyCookie = true;
+
+ // Set a cookie on one of the URIs. It doesn't matter which one, since
+ // they're both from the same host, which is enough for the cookie service
+ // to send the cookie with both requests.
+ var postRedirectURI = ioService.newURI(postRedirectURL, "", null);
+ Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService).
+ setCookieString(postRedirectURI, null, sentCookieVal, chan);
+
+ // Load the pre-redirect URI.
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
+
+function finish_test(event)
+{
+ do_check_eq(receivedCookieVal, sentCookieVal);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_bug536324_64bit_content_length.js b/netwerk/test/unit/test_bug536324_64bit_content_length.js
new file mode 100644
index 000000000..1dcb475be
--- /dev/null
+++ b/netwerk/test/unit/test_bug536324_64bit_content_length.js
@@ -0,0 +1,64 @@
+/* Test to ensure our 64-bit content length implementation works, at least for
+ a simple HTTP case */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// This C-L is significantly larger than (U)INT32_MAX, to make sure we do
+// 64-bit properly.
+const CONTENT_LENGTH = "1152921504606846975";
+
+var httpServer = null;
+
+var listener = {
+ onStartRequest: function (req, ctx) {
+ },
+
+ onDataAvailable: function (req, ctx, stream, off, count) {
+ do_check_eq(req.getResponseHeader("Content-Length"), CONTENT_LENGTH);
+
+ // We're done here, cancel the channel
+ req.cancel(NS_BINDING_ABORT);
+ },
+
+ onStopRequest: function (req, ctx, stat) {
+ httpServer.stop(do_test_finished);
+ }
+};
+
+function hugeContentLength(metadata, response) {
+ var text = "abcdefghijklmnopqrstuvwxyz";
+ var bytes_written = 0;
+
+ response.seizePower();
+
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Length: " + CONTENT_LENGTH + "\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+
+ // Write enough data to ensure onDataAvailable gets called
+ while (bytes_written < 4096) {
+ response.write(text);
+ bytes_written += text.length;
+ }
+
+ response.finish();
+}
+
+function test_hugeContentLength() {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpServer.identity.primaryPort + "/",
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen2(listener);
+}
+
+add_test(test_hugeContentLength);
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/", hugeContentLength);
+ httpServer.start(-1);
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_bug540566.js b/netwerk/test/unit/test_bug540566.js
new file mode 100644
index 000000000..e44fa9c17
--- /dev/null
+++ b/netwerk/test/unit/test_bug540566.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function continue_test(status, entry) {
+ do_check_eq(status, Components.results.NS_OK);
+ // TODO - mayhemer: remove this tests completely
+ // entry.deviceID;
+ // if the above line does not crash, the test was successful
+ do_test_finished();
+}
+
+function run_test() {
+ asyncOpenCacheEntry("http://some.key/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ continue_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug543805.js b/netwerk/test/unit/test_bug543805.js
new file mode 100644
index 000000000..ed25f3ebe
--- /dev/null
+++ b/netwerk/test/unit/test_bug543805.js
@@ -0,0 +1,93 @@
+const URL = "ftp://localhost/bug543805/";
+
+var dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+var year = new Date().getFullYear().toString();
+var day = dayNames[new Date(year, 0, 1).getDay()];
+
+const tests = [
+ // AIX ls format
+ ["-rw-r--r-- 1 0 11 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 0 22 Jan 1 20:19 test.blankfile\r\n" +
+ "-rw-r--r-- 1 0 33 Apr 1 2008 test2.blankfile\r\n" +
+ "-rw-r--r-- 1 0 44 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 0 55 Jan 1 20:19 test.file\r\n" +
+ "-rw-r--r-- 1 0 66 Apr 1 2008 test2.file\r\n",
+
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"%20nodup.file\" 11 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"%20test.blankfile\" 22 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"%20test2.blankfile\" 33 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 FILE \n" +
+ "201: \"nodup.file\" 44 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test.file\" 55 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test2.file\" 66 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 FILE \n"],
+
+ // standard ls format
+ [
+ "-rw-r--r-- 1 500 500 11 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 500 500 22 Jan 1 20:19 test.blankfile\r\n" +
+ "-rw-r--r-- 1 500 500 33 Apr 1 2008 test2.blankfile\r\n" +
+ "-rw-r--r-- 1 500 500 44 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 500 500 55 Jan 1 20:19 test.file\r\n" +
+ "-rw-r--r-- 1 500 500 66 Apr 1 2008 test2.file\r\n",
+
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ "201: \"%20nodup.file\" 11 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"%20test.blankfile\" 22 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"%20test2.blankfile\" 33 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 FILE \n" +
+ "201: \"nodup.file\" 44 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test.file\" 55 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test2.file\" 66 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 FILE \n"]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(next_test);
+}
+
+function storeData(status, entry) {
+ var scs = Cc["@mozilla.org/streamConverters;1"].
+ getService(Ci.nsIStreamConverterService);
+ var converter = scs.asyncConvertData("text/ftp-dir", "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL), null);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.data = tests[0][0];
+
+ var url = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0)
+ do_test_finished();
+ else {
+ storeData();
+ }
+}
+
+function run_test() {
+ do_execute_soon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug553970.js b/netwerk/test/unit/test_bug553970.js
new file mode 100644
index 000000000..00e222a2c
--- /dev/null
+++ b/netwerk/test/unit/test_bug553970.js
@@ -0,0 +1,44 @@
+function makeURL(spec) {
+ return Cc["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService).
+ newURI(spec, null, null).
+ QueryInterface(Components.interfaces.nsIURL);
+}
+
+// Checks that nsIURL::GetRelativeSpec does what it claims to do.
+function run_test() {
+
+ // Elements of tests have the form [this.spec, aURIToCompare.spec, expectedResult].
+ let tests = [
+ ["http://mozilla.org/", "http://www.mozilla.org/", "http://www.mozilla.org/"],
+ ["http://mozilla.org/", "http://www.mozilla.org", "http://www.mozilla.org/"],
+ ["http://foo.com/bar/", "http://foo.com:80/bar/", "" ],
+ ["http://foo.com/", "http://foo.com/a.htm#b", "a.htm#b" ],
+ ["http://foo.com/a/b/", "http://foo.com/c", "../../c" ],
+ ["http://foo.com/a?b/c/", "http://foo.com/c" , "c" ],
+ ["http://foo.com/a#b/c/", "http://foo.com/c" , "c" ],
+ ["http://foo.com/a;p?b/c/", "http://foo.com/c" , "c" ],
+ ["http://foo.com/a/b?c/d/", "http://foo.com/c", "../c" ],
+ ["http://foo.com/a/b#c/d/", "http://foo.com/c", "../c" ],
+ ["http://foo.com/a/b;p?c/d/", "http://foo.com/c", "../c" ],
+ ["http://foo.com/a/b/c?d/e/", "http://foo.com/f", "../../f" ],
+ ["http://foo.com/a/b/c#d/e/", "http://foo.com/f", "../../f" ],
+ ["http://foo.com/a/b/c;p?d/e/", "http://foo.com/f", "../../f" ],
+ ["http://foo.com/a?b/c/", "http://foo.com/c/d" , "c/d" ],
+ ["http://foo.com/a#b/c/", "http://foo.com/c/d" , "c/d" ],
+ ["http://foo.com/a;p?b/c/", "http://foo.com/c/d" , "c/d" ],
+ ["http://foo.com/a/b?c/d/", "http://foo.com/c/d", "../c/d" ],
+ ["http://foo.com/a/b#c/d/", "http://foo.com/c/d", "../c/d" ],
+ ["http://foo.com/a/b;p?c/d/", "http://foo.com/c/d", "../c/d" ],
+ ["http://foo.com/a/b/c?d/e/", "http://foo.com/f/g/", "../../f/g/" ],
+ ["http://foo.com/a/b/c#d/e/", "http://foo.com/f/g/", "../../f/g/" ],
+ ["http://foo.com/a/b/c;p?d/e/", "http://foo.com/f/g/", "../../f/g/" ],
+ ];
+
+ for (var i = 0; i < tests.length; i++) {
+ let url1 = makeURL(tests[i][0]);
+ let url2 = makeURL(tests[i][1]);
+ let expected = tests[i][2];
+ do_check_eq(expected, url1.getRelativeSpec(url2));
+ }
+}
diff --git a/netwerk/test/unit/test_bug561042.js b/netwerk/test/unit/test_bug561042.js
new file mode 100644
index 000000000..d794aeaf6
--- /dev/null
+++ b/netwerk/test/unit/test_bug561042.js
@@ -0,0 +1,38 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const SERVER_PORT = 8080;
+const baseURL = "http://localhost:" + SERVER_PORT + "/";
+
+var cookie = "";
+for (let i =0; i < 10000; i++) {
+ cookie += " big cookie";
+}
+
+var listener = {
+ onStartRequest: function (request, ctx) {
+ },
+
+ onDataAvailable: function (request, ctx, stream) {
+ },
+
+ onStopRequest: function (request, ctx, status) {
+ do_check_eq(status, Components.results.NS_OK);
+ do_test_finished();
+ },
+
+};
+
+function run_test() {
+ var server = new HttpServer();
+ server.start(SERVER_PORT);
+ server.registerPathHandler('/', function(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Set-Cookie", "BigCookie=" + cookie, false);
+ response.write("Hello world");
+ });
+ var chan = NetUtil.newChannel({uri: baseURL, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug561276.js b/netwerk/test/unit/test_bug561276.js
new file mode 100644
index 000000000..fd67d24dc
--- /dev/null
+++ b/netwerk/test/unit/test_bug561276.js
@@ -0,0 +1,68 @@
+//
+// Verify that we hit the net if we discover a cycle of redirects
+// coming from cache.
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var iteration = 0;
+
+function setupChannel(suffix)
+{
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ return httpChan;
+}
+
+function checkValueAndTrigger(request, data, ctx)
+{
+ do_check_eq("Ok", data);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpserver.registerPathHandler("/redirect1", redirectHandler1);
+ httpserver.registerPathHandler("/redirect2", redirectHandler2);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ // load first time
+ var channel = setupChannel("/redirect1");
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+
+ do_test_pending();
+}
+
+function redirectHandler1(metadata, response)
+{
+ // first time we return a cacheable 302 pointing to next redirect
+ if (iteration < 1) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Location", "/redirect2", false);
+
+ // next time called we return 200
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+ }
+ iteration += 1;
+}
+
+function redirectHandler2(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Location", "/redirect1", false);
+}
diff --git a/netwerk/test/unit/test_bug580508.js b/netwerk/test/unit/test_bug580508.js
new file mode 100644
index 000000000..3e2c495e0
--- /dev/null
+++ b/netwerk/test/unit/test_bug580508.js
@@ -0,0 +1,26 @@
+var ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+var resProt = ioService.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+function run_test() {
+ // Define a resource:// alias that points to another resource:// URI.
+ let greModulesURI = ioService.newURI("resource://gre/modules/", null, null);
+ resProt.setSubstitution("my-gre-modules", greModulesURI);
+
+ // When we ask for the alias, we should not get the resource://
+ // URI that we registered it for but the original file URI.
+ let greFileSpec = ioService.newURI("modules/", null,
+ resProt.getSubstitution("gre")).spec;
+ let aliasURI = resProt.getSubstitution("my-gre-modules");
+ do_check_eq(aliasURI.spec, greFileSpec);
+
+ // Resolving URIs using the original resource path and the alias
+ // should yield the same result.
+ let greNetUtilURI = ioService.newURI("resource://gre/modules/NetUtil.jsm",
+ null, null);
+ let myNetUtilURI = ioService.newURI("resource://my-gre-modules/NetUtil.jsm",
+ null, null);
+ do_check_eq(resProt.resolveURI(greNetUtilURI),
+ resProt.resolveURI(myNetUtilURI));
+}
diff --git a/netwerk/test/unit/test_bug586908.js b/netwerk/test/unit/test_bug586908.js
new file mode 100644
index 000000000..008577da0
--- /dev/null
+++ b/netwerk/test/unit/test_bug586908.js
@@ -0,0 +1,92 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://testing-common/MockRegistrar.jsm");
+
+var httpserv = null;
+
+const CID = Components.ID("{5645d2c1-d6d8-4091-b117-fe7ee4027db7}");
+XPCOMUtils.defineLazyGetter(this, "systemSettings", function() {
+ return {
+ QueryInterface: function (iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsISystemProxySettings))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ mainThreadOnly: true,
+ PACURI: "http://localhost:" + httpserv.identity.primaryPort + "/redirect",
+ getProxyForURI: function(aURI) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ };
+});
+
+function checkValue(request, data, ctx) {
+ do_check_true(called);
+ do_check_eq("ok", data);
+ httpserv.stop(do_test_finished);
+}
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+}
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/redirect", redirect);
+ httpserv.registerPathHandler("/pac", pac);
+ httpserv.registerPathHandler("/target", target);
+ httpserv.start(-1);
+
+ MockRegistrar.register("@mozilla.org/system-proxy-settings;1",
+ systemSettings);
+
+ // Ensure we're using system-properties
+ const prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefs.setIntPref(
+ "network.proxy.type",
+ Components.interfaces.nsIProtocolProxyService.PROXYCONFIG_SYSTEM);
+
+ // clear cache
+ evict_cache_entries();
+
+ var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+ "/target");
+ chan.asyncOpen2(new ChannelListener(checkValue, null));
+
+ do_test_pending();
+}
+
+var called = false, failed = false;
+function redirect(metadata, response) {
+ // If called second time, just return the PAC but set failed-flag
+ if (called) {
+ failed = true;
+ return pac(metadata, response);
+ }
+
+ called = true;
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", "/pac", false);
+ var body = "Moved\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function pac(metadata, response) {
+ var PAC = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
+ response.bodyOutputStream.write(PAC, PAC.length);
+}
+
+function target(metadata, response) {
+ var retval = "ok";
+ if (failed) retval = "failed";
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(retval, retval.length);
+}
diff --git a/netwerk/test/unit/test_bug596443.js b/netwerk/test/unit/test_bug596443.js
new file mode 100644
index 000000000..e86bdf817
--- /dev/null
+++ b/netwerk/test/unit/test_bug596443.js
@@ -0,0 +1,97 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+
+var expectedOnStopRequests = 3;
+
+function setupChannel(suffix, xRequest, flags) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+ if (flags)
+ chan.loadFlags |= flags;
+
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.setRequestHeader("x-request", xRequest, false);
+
+ return httpChan;
+}
+
+function Listener(response) {
+ this._response = response;
+}
+Listener.prototype = {
+ _response: null,
+ _buffer: null,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function (request, ctx) {
+ this._buffer = "";
+ },
+ onDataAvailable: function (request, ctx, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+ onStopRequest: function (request, ctx, status) {
+ do_check_eq(this._buffer, this._response);
+ if (--expectedOnStopRequests == 0)
+ do_timeout(10, function() {
+ httpserver.stop(do_test_finished);
+ });
+ }
+};
+
+function run_test() {
+ httpserver.registerPathHandler("/bug596443", handler);
+ httpserver.start(-1);
+
+ // make sure we have a profile so we can use the disk-cache
+ do_get_profile();
+
+ // clear cache
+ evict_cache_entries();
+
+ var ch0 = setupChannel("/bug596443", "Response0", Ci.nsIRequest.LOAD_BYPASS_CACHE);
+ ch0.asyncOpen2(new Listener("Response0"));
+
+ var ch1 = setupChannel("/bug596443", "Response1", Ci.nsIRequest.LOAD_BYPASS_CACHE);
+ ch1.asyncOpen2(new Listener("Response1"));
+
+ var ch2 = setupChannel("/bug596443", "Should not be used");
+ ch2.asyncOpen2(new Listener("Response1")); // Note param: we expect this to come from cache
+
+ do_test_pending();
+}
+
+function triggerHandlers() {
+ do_timeout(100, handlers[1]);
+ do_timeout(100, handlers[0]);
+}
+
+var handlers = [];
+function handler(metadata, response) {
+ var func = function(body) {
+ return function() {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Length", "" + body.length, false);
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ }};
+
+ response.processAsync();
+ var request = metadata.getHeader("x-request");
+ handlers.push(func(request));
+
+ if (handlers.length > 1)
+ triggerHandlers();
+}
diff --git a/netwerk/test/unit/test_bug618835.js b/netwerk/test/unit/test_bug618835.js
new file mode 100644
index 000000000..811608a61
--- /dev/null
+++ b/netwerk/test/unit/test_bug618835.js
@@ -0,0 +1,115 @@
+//
+// If a response to a non-safe HTTP request-method contains the Location- or
+// Content-Location header, we must make sure to invalidate any cached entry
+// representing the URIs pointed to by either header. RFC 2616 section 13.10
+//
+// This test uses 3 URIs: "/post" is the target of a POST-request and always
+// redirects (301) to "/redirect". The URIs "/redirect" and "/cl" both counts
+// the number of loads from the server (handler). The response from "/post"
+// always contains the headers "Location: /redirect" and "Content-Location:
+// /cl", whose cached entries are to be invalidated. The tests verifies that
+// "/redirect" and "/cl" are loaded from server the expected number of times.
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv;
+
+function setupChannel(path) {
+ return NetUtil.newChannel({uri: path, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+// Verify that Content-Location-URI has been loaded once, load post_target
+function InitialListener() { }
+InitialListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(1, numberOfCLHandlerCalls);
+ do_execute_soon(function() {
+ var channel = setupChannel("http://localhost:" +
+ httpserv.identity.primaryPort + "/post");
+ channel.requestMethod = "POST";
+ channel.asyncOpen2(new RedirectingListener());
+ });
+ }
+};
+
+// Verify that Location-URI has been loaded once, reload post_target
+function RedirectingListener() { }
+RedirectingListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(1, numberOfHandlerCalls);
+ do_execute_soon(function() {
+ var channel = setupChannel("http://localhost:" +
+ httpserv.identity.primaryPort + "/post");
+ channel.requestMethod = "POST";
+ channel.asyncOpen2(new VerifyingListener());
+ });
+ }
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// reload Content-Location-URI
+function VerifyingListener() { }
+VerifyingListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(2, numberOfHandlerCalls);
+ var channel = setupChannel("http://localhost:" +
+ httpserv.identity.primaryPort + "/cl");
+ channel.asyncOpen2(new FinalListener());
+ }
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// stop test
+function FinalListener() { }
+FinalListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(2, numberOfCLHandlerCalls);
+ httpserv.stop(do_test_finished);
+ }
+};
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/cl", content_location);
+ httpserv.registerPathHandler("/post", post_target);
+ httpserv.registerPathHandler("/redirect", redirect_target);
+ httpserv.start(-1);
+
+ // Clear cache
+ evict_cache_entries();
+
+ // Load Content-Location URI into cache and start the chain of loads
+ var channel = setupChannel("http://localhost:" +
+ httpserv.identity.primaryPort + "/cl");
+ channel.asyncOpen2(new InitialListener());
+
+ do_test_pending();
+}
+
+var numberOfCLHandlerCalls = 0;
+function content_location(metadata, response) {
+ numberOfCLHandlerCalls++;
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Cache-Control", "max-age=360000", false);
+}
+
+function post_target(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", "/redirect", false);
+ response.setHeader("Content-Location", "/cl", false);
+ response.setHeader("Cache-Control", "max-age=360000", false);
+}
+
+var numberOfHandlerCalls = 0;
+function redirect_target(metadata, response) {
+ numberOfHandlerCalls++;
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Cache-Control", "max-age=360000", false);
+}
diff --git a/netwerk/test/unit/test_bug633743.js b/netwerk/test/unit/test_bug633743.js
new file mode 100644
index 000000000..e5ac078b4
--- /dev/null
+++ b/netwerk/test/unit/test_bug633743.js
@@ -0,0 +1,186 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const VALUE_HDR_NAME = "X-HTTP-VALUE-HEADER";
+const VARY_HDR_NAME = "X-HTTP-VARY-HEADER";
+const CACHECTRL_HDR_NAME = "X-CACHE-CONTROL-HEADER";
+
+var httpserver = null;
+
+function make_channel(flags, vary, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/bug633743",
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+ return chan.QueryInterface(Ci.nsIHttpChannel);
+}
+
+function Test(flags, varyHdr, sendValue, expectValue, cacheHdr) {
+ this._flags = flags;
+ this._varyHdr = varyHdr;
+ this._sendVal = sendValue;
+ this._expectVal = expectValue;
+ this._cacheHdr = cacheHdr;
+}
+
+Test.prototype = {
+ _buffer: "",
+ _flags: null,
+ _varyHdr: null,
+ _sendVal: null,
+ _expectVal: null,
+ _cacheHdr: null,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) { },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(this._buffer, this._expectVal);
+ do_timeout(0, run_next_test);
+ },
+
+ run: function() {
+ var channel = make_channel();
+ channel.loadFlags = this._flags;
+ channel.setRequestHeader(VALUE_HDR_NAME, this._sendVal, false);
+ channel.setRequestHeader(VARY_HDR_NAME, this._varyHdr, false);
+ if (this._cacheHdr)
+ channel.setRequestHeader(CACHECTRL_HDR_NAME, this._cacheHdr, false);
+
+ channel.asyncOpen2(this);
+ }
+};
+
+var gTests = [
+// Test LOAD_FROM_CACHE: Load cache-entry
+ new Test(Ci.nsIRequest.LOAD_NORMAL,
+ "entity-initial", // hdr-value used to vary
+ "request1", // echoed by handler
+ "request1" // value expected to receive in channel
+ ),
+// Verify that it was cached
+ new Test(Ci.nsIRequest.LOAD_NORMAL,
+ "entity-initial", // hdr-value used to vary
+ "fresh value with LOAD_NORMAL", // echoed by handler
+ "request1" // value expected to receive in channel
+ ),
+// Load same entity with LOAD_FROM_CACHE-flag
+ new Test(Ci.nsIRequest.LOAD_FROM_CACHE,
+ "entity-initial", // hdr-value used to vary
+ "fresh value with LOAD_FROM_CACHE", // echoed by handler
+ "request1" // value expected to receive in channel
+ ),
+// Load different entity with LOAD_FROM_CACHE-flag
+ new Test(Ci.nsIRequest.LOAD_FROM_CACHE,
+ "entity-l-f-c", // hdr-value used to vary
+ "request2", // echoed by handler
+ "request2" // value expected to receive in channel
+ ),
+// Verify that new value was cached
+ new Test(Ci.nsIRequest.LOAD_NORMAL,
+ "entity-l-f-c", // hdr-value used to vary
+ "fresh value with LOAD_NORMAL", // echoed by handler
+ "request2" // value expected to receive in channel
+ ),
+
+// Test VALIDATE_NEVER: Note previous cache-entry
+ new Test(Ci.nsIRequest.VALIDATE_NEVER,
+ "entity-v-n", // hdr-value used to vary
+ "request3", // echoed by handler
+ "request3" // value expected to receive in channel
+ ),
+// Verify that cache-entry was replaced
+ new Test(Ci.nsIRequest.LOAD_NORMAL,
+ "entity-v-n", // hdr-value used to vary
+ "fresh value with LOAD_NORMAL", // echoed by handler
+ "request3" // value expected to receive in channel
+ ),
+
+// Test combination VALIDATE_NEVER && no-store: Load new cache-entry
+ new Test(Ci.nsIRequest.LOAD_NORMAL,
+ "entity-2",// hdr-value used to vary
+ "request4", // echoed by handler
+ "request4", // value expected to receive in channel
+ "no-store" // set no-store on response
+ ),
+// Ensure we validate without IMS header in this case (verified in handler)
+ new Test(Ci.nsIRequest.VALIDATE_NEVER,
+ "entity-2-v-n",// hdr-value used to vary
+ "request5", // echoed by handler
+ "request5" // value expected to receive in channel
+ ),
+
+// Test VALIDATE-ALWAYS: Load new entity
+ new Test(Ci.nsIRequest.LOAD_NORMAL,
+ "entity-3",// hdr-value used to vary
+ "request6", // echoed by handler
+ "request6", // value expected to receive in channel
+ "no-cache" // set no-cache on response
+ ),
+// Ensure we don't send IMS header also in this case (verified in handler)
+ new Test(Ci.nsIRequest.VALIDATE_ALWAYS,
+ "entity-3-v-a",// hdr-value used to vary
+ "request7", // echoed by handler
+ "request7" // value expected to receive in channel
+ ),
+ ];
+
+function run_next_test()
+{
+ if (gTests.length == 0) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+
+ var test = gTests.shift();
+ test.run();
+}
+
+function handler(metadata, response) {
+
+ // None of the tests above should send an IMS
+ do_check_false(metadata.hasHeader("If-Modified-Since"));
+
+ // Pick up requested value to echo
+ var hdr = "default value";
+ try {
+ hdr = metadata.getHeader(VALUE_HDR_NAME);
+ } catch(ex) { }
+
+ // Pick up requested cache-control header-value
+ var cctrlVal = "max-age=10000";
+ try {
+ cctrlVal = metadata.getHeader(CACHECTRL_HDR_NAME);
+ } catch(ex) { }
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", cctrlVal, false);
+ response.setHeader("Vary", VARY_HDR_NAME, false);
+ response.setHeader("Last-Modified", "Tue, 15 Nov 1994 12:45:26 GMT", false);
+ response.bodyOutputStream.write(hdr, hdr.length);
+}
+
+function run_test() {
+
+ // clear the cache
+ evict_cache_entries();
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/bug633743", handler);
+ httpserver.start(-1);
+
+ run_next_test();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug650995.js b/netwerk/test/unit/test_bug650995.js
new file mode 100644
index 000000000..3c1ea8d67
--- /dev/null
+++ b/netwerk/test/unit/test_bug650995.js
@@ -0,0 +1,159 @@
+//
+// Test that "max_entry_size" prefs for disk- and memory-cache prevents
+// caching resources with size out of bounds
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+do_get_profile();
+
+const prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+
+const httpserver = new HttpServer();
+
+// Repeats the given data until the total size is larger than 1K
+function repeatToLargerThan1K(data) {
+ while(data.length <= 1024)
+ data += data;
+ return data;
+}
+
+function setupChannel(suffix, value) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ httpChan.setRequestHeader("x-request", value, false);
+
+ return httpChan;
+}
+
+var tests = [
+ new InitializeCacheDevices(true, false), // enable and create mem-device
+ new TestCacheEntrySize(
+ function() { prefService.setIntPref("browser.cache.memory.max_entry_size", 1); },
+ "012345", "9876543210", "012345"), // expect cached value
+ new TestCacheEntrySize(
+ function() { prefService.setIntPref("browser.cache.memory.max_entry_size", 1); },
+ "0123456789a", "9876543210", "9876543210"), // expect fresh value
+ new TestCacheEntrySize(
+ function() { prefService.setIntPref("browser.cache.memory.max_entry_size", -1); },
+ "0123456789a", "9876543210", "0123456789a"), // expect cached value
+
+ new InitializeCacheDevices(false, true), // enable and create disk-device
+ new TestCacheEntrySize(
+ function() { prefService.setIntPref("browser.cache.disk.max_entry_size", 1); },
+ "012345", "9876543210", "012345"), // expect cached value
+ new TestCacheEntrySize(
+ function() { prefService.setIntPref("browser.cache.disk.max_entry_size", 1); },
+ "0123456789a", "9876543210", "9876543210"), // expect fresh value
+ new TestCacheEntrySize(
+ function() { prefService.setIntPref("browser.cache.disk.max_entry_size", -1); },
+ "0123456789a", "9876543210", "0123456789a"), // expect cached value
+ ];
+
+function nextTest() {
+ // We really want each test to be self-contained. Make sure cache is
+ // cleared and also let all operations finish before starting a new test
+ syncWithCacheIOThread(function() {
+ get_cache_service().clear();
+ syncWithCacheIOThread(runNextTest);
+ });
+}
+
+function runNextTest() {
+ var aTest = tests.shift();
+ if (!aTest) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+ do_execute_soon(function() { aTest.start(); } );
+}
+
+// Just make sure devices are created
+function InitializeCacheDevices(memDevice, diskDevice) {
+ this.start = function() {
+ prefService.setBoolPref("browser.cache.memory.enable", memDevice);
+ if (memDevice) {
+ try {
+ cap = prefService.getIntPref("browser.cache.memory.capacity");
+ }
+ catch(ex) {
+ cap = 0;
+ }
+ if (cap == 0) {
+ prefService.setIntPref("browser.cache.memory.capacity", 1024);
+ }
+ }
+ prefService.setBoolPref("browser.cache.disk.enable", diskDevice);
+ if (diskDevice) {
+ try {
+ cap = prefService.getIntPref("browser.cache.disk.capacity");
+ }
+ catch(ex) {
+ cap = 0;
+ }
+ if (cap == 0) {
+ prefService.setIntPref("browser.cache.disk.capacity", 1024);
+ }
+ }
+ var channel = setupChannel("/bug650995", "Initial value");
+ channel.asyncOpen2(new ChannelListener(nextTest, null));
+ }
+}
+
+function TestCacheEntrySize(setSizeFunc, firstRequest, secondRequest, secondExpectedReply) {
+
+ // Initially, this test used 10 bytes as the limit for caching entries.
+ // Since we now use 1K granularity we have to extend lengths to be larger
+ // than 1K if it is larger than 10
+ if (firstRequest.length > 10)
+ firstRequest = repeatToLargerThan1K(firstRequest);
+ if (secondExpectedReply.length > 10)
+ secondExpectedReply = repeatToLargerThan1K(secondExpectedReply);
+
+ this.start = function() {
+ setSizeFunc();
+ var channel = setupChannel("/bug650995", firstRequest);
+ channel.asyncOpen2(new ChannelListener(this.initialLoad, this));
+ },
+
+ this.initialLoad = function(request, data, ctx) {
+ do_check_eq(firstRequest, data);
+ var channel = setupChannel("/bug650995", secondRequest);
+ do_execute_soon(function() {
+ channel.asyncOpen2(new ChannelListener(ctx.testAndTriggerNext, ctx));
+ });
+ },
+
+ this.testAndTriggerNext = function(request, data, ctx) {
+ do_check_eq(secondExpectedReply, data);
+ do_execute_soon(nextTest);
+ }
+}
+
+function run_test()
+{
+ httpserver.registerPathHandler("/bug650995", handler);
+ httpserver.start(-1);
+
+ prefService.setBoolPref("browser.cache.offline.enable", false);
+
+ nextTest();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = "BOOM!";
+ try {
+ body = metadata.getHeader("x-request");
+ } catch(e) {}
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_bug652761.js b/netwerk/test/unit/test_bug652761.js
new file mode 100644
index 000000000..e2b781da8
--- /dev/null
+++ b/netwerk/test/unit/test_bug652761.js
@@ -0,0 +1,17 @@
+// This is just a crashtest for a url that is rejected at parse time (port 80,000)
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test()
+{
+ // Bug 1301621 makes invalid ports throw
+ Assert.throws(() => {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:80000/",
+ loadUsingSystemPrincipal: true
+ });
+ }, "invalid port");
+
+ do_test_finished();
+}
+
diff --git a/netwerk/test/unit/test_bug654926.js b/netwerk/test/unit/test_bug654926.js
new file mode 100644
index 000000000..83fd7286f
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926.js
@@ -0,0 +1,88 @@
+var _PSvc;
+function get_pref_service() {
+ if (_PSvc)
+ return _PSvc;
+
+ return _PSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+}
+
+function gen_1MiB()
+{
+ var i;
+ var data="x";
+ for (i=0 ; i < 20 ; i++)
+ data+=data;
+ return data;
+}
+
+function write_and_check(str, data, len)
+{
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw("str.write has not written all data!\n" +
+ " Expected: " + len + "\n" +
+ " Actual: " + written + "\n");
+ }
+}
+
+function write_datafile(status, entry)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+ var data = gen_1MiB();
+
+ // write 2MiB
+ var i;
+ for (i=0 ; i<2 ; i++)
+ write_and_check(os, data, data.length);
+
+ os.close();
+ entry.close();
+
+ // now change max_entry_size so that the existing entry is too big
+ get_pref_service().setIntPref("browser.cache.disk.max_entry_size", 1024);
+
+ // append to entry
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ append_datafile);
+}
+
+function append_datafile(status, entry)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize);
+ var data = gen_1MiB();
+
+ // append 1MiB
+ try {
+ write_and_check(os, data, data.length);
+ do_throw();
+ }
+ catch (ex) { }
+
+ // closing the ostream should fail in this case
+ try {
+ os.close();
+ do_throw();
+ }
+ catch (ex) { }
+
+ entry.close();
+
+ do_test_finished();
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ write_datafile);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug654926_doom_and_read.js b/netwerk/test/unit/test_bug654926_doom_and_read.js
new file mode 100644
index 000000000..74c9f66c0
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926_doom_and_read.js
@@ -0,0 +1,77 @@
+function gen_1MiB()
+{
+ var i;
+ var data="x";
+ for (i=0 ; i < 20 ; i++)
+ data+=data;
+ return data;
+}
+
+function write_and_check(str, data, len)
+{
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw("str.write has not written all data!\n" +
+ " Expected: " + len + "\n" +
+ " Actual: " + written + "\n");
+ }
+}
+
+function make_input_stream_scriptable(input) {
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ wrapper.init(input);
+ return wrapper;
+}
+
+function write_datafile(status, entry)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+ var data = gen_1MiB();
+
+ write_and_check(os, data, data.length);
+
+ os.close();
+ entry.close();
+
+ // open, doom, append, read
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ test_read_after_doom);
+
+}
+
+function test_read_after_doom(status, entry)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize);
+ var data = gen_1MiB();
+
+ entry.asyncDoom(null);
+ write_and_check(os, data, data.length);
+
+ os.close();
+
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function(read) {
+ do_check_eq(read.length, 2*1024*1024);
+ is.close();
+
+ entry.close();
+ do_test_finished();
+ });
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ write_datafile);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug654926_test_seek.js b/netwerk/test/unit/test_bug654926_test_seek.js
new file mode 100644
index 000000000..2916b0380
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926_test_seek.js
@@ -0,0 +1,63 @@
+function gen_1MiB()
+{
+ var i;
+ var data="x";
+ for (i=0 ; i < 20 ; i++)
+ data+=data;
+ return data;
+}
+
+function write_and_check(str, data, len)
+{
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw("str.write has not written all data!\n" +
+ " Expected: " + len + "\n" +
+ " Actual: " + written + "\n");
+ }
+}
+
+function write_datafile(status, entry)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+ var data = gen_1MiB();
+
+ write_and_check(os, data, data.length);
+
+ os.close();
+ entry.close();
+
+ // try to open the entry for appending
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ open_for_readwrite);
+}
+
+function open_for_readwrite(status, entry)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize);
+
+ // Opening the entry for appending data calls nsDiskCacheStreamIO::Seek()
+ // which initializes mFD. If no data is written then mBufDirty is false and
+ // mFD won't be closed in nsDiskCacheStreamIO::Flush().
+
+ os.close();
+ entry.close();
+
+ do_test_finished();
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ write_datafile);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug659569.js b/netwerk/test/unit/test_bug659569.js
new file mode 100644
index 000000000..b9b7d105f
--- /dev/null
+++ b/netwerk/test/unit/test_bug659569.js
@@ -0,0 +1,57 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+
+function setupChannel(suffix)
+{
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function checkValueAndTrigger(request, data, ctx)
+{
+ do_check_eq("Ok", data);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test()
+{
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ httpserver.registerPathHandler("/redirect1", redirectHandler1);
+ httpserver.registerPathHandler("/redirect2", redirectHandler2);
+ httpserver.start(-1);
+
+ // clear cache
+ evict_cache_entries();
+
+ // load first time
+ var channel = setupChannel("/redirect1");
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+ do_test_pending();
+}
+
+function redirectHandler1(metadata, response)
+{
+ if (!metadata.hasHeader("Cookie")) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Cache-Control", "max-age=600", false);
+ response.setHeader("Location", "/redirect2?query", false);
+ response.setHeader("Set-Cookie", "MyCookie=1", false);
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+ }
+}
+
+function redirectHandler2(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", "/redirect1", false);
+}
diff --git a/netwerk/test/unit/test_bug660066.js b/netwerk/test/unit/test_bug660066.js
new file mode 100644
index 000000000..99c01c40c
--- /dev/null
+++ b/netwerk/test/unit/test_bug660066.js
@@ -0,0 +1,42 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+const SIMPLEURI_SPEC = "data:text/plain,hello world";
+const BLOBURI_SPEC = "blob:123456";
+
+function do_info(text, stack) {
+ if (!stack)
+ stack = Components.stack.caller;
+
+ dump( "\n" +
+ "TEST-INFO | " + stack.filename + " | [" + stack.name + " : " +
+ stack.lineNumber + "] " + text + "\n");
+}
+
+function do_check_uri_neq(uri1, uri2)
+{
+ do_info("Checking equality in forward direction...");
+ do_check_false(uri1.equals(uri2));
+ do_check_false(uri1.equalsExceptRef(uri2));
+
+ do_info("Checking equality in reverse direction...");
+ do_check_false(uri2.equals(uri1));
+ do_check_false(uri2.equalsExceptRef(uri1));
+}
+
+function run_test()
+{
+ var simpleURI = NetUtil.newURI(SIMPLEURI_SPEC);
+ var fileDataURI = NetUtil.newURI(BLOBURI_SPEC);
+
+ do_info("Checking that " + SIMPLEURI_SPEC + " != " + BLOBURI_SPEC);
+ do_check_uri_neq(simpleURI, fileDataURI);
+
+ do_info("Changing the nsSimpleURI spec to match the nsFileDataURI");
+ simpleURI.spec = BLOBURI_SPEC;
+
+ do_info("Verifying that .spec matches");
+ do_check_eq(simpleURI.spec, fileDataURI.spec);
+
+ do_info("Checking that nsSimpleURI != nsFileDataURI despite their .spec matching")
+ do_check_uri_neq(simpleURI, fileDataURI);
+}
diff --git a/netwerk/test/unit/test_bug667818.js b/netwerk/test/unit/test_bug667818.js
new file mode 100644
index 000000000..e730e74c1
--- /dev/null
+++ b/netwerk/test/unit/test_bug667818.js
@@ -0,0 +1,21 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function makeURI(str) {
+ return Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(str, null, null);
+}
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ var serv = Components.classes["@mozilla.org/cookieService;1"]
+ .getService(Components.interfaces.nsICookieService);
+ var uri = makeURI("http://example.com/");
+ // Try an expiration time before the epoch
+ serv.setCookieString(uri, null, "test=test; path=/; domain=example.com; expires=Sun, 31-Dec-1899 16:00:00 GMT;", null);
+ do_check_eq(serv.getCookieString(uri, null), null);
+ // Now sanity check
+ serv.setCookieString(uri, null, "test2=test2; path=/; domain=example.com;", null);
+ do_check_eq(serv.getCookieString(uri, null), "test2=test2");
+}
diff --git a/netwerk/test/unit/test_bug667907.js b/netwerk/test/unit/test_bug667907.js
new file mode 100644
index 000000000..8e3342274
--- /dev/null
+++ b/netwerk/test/unit/test_bug667907.js
@@ -0,0 +1,84 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+var simplePath = "/simple";
+var normalPath = "/normal";
+var httpbody = "<html></html>";
+
+XPCOMUtils.defineLazyGetter(this, "uri1", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + simplePath;
+});
+
+XPCOMUtils.defineLazyGetter(this, "uri2", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + normalPath;
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var listener_proto = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ this.contentType);
+ request.cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_throw("Unexpected onDataAvailable");
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(status, Cr.NS_BINDING_ABORTED);
+ this.termination_func();
+ }
+};
+
+function listener(contentType, termination_func) {
+ this.contentType = contentType;
+ this.termination_func = termination_func;
+}
+listener.prototype = listener_proto;
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(simplePath, simpleHandler);
+ httpserver.registerPathHandler(normalPath, normalHandler);
+ httpserver.start(-1);
+
+ var channel = make_channel(uri1);
+ channel.asyncOpen2(new listener("text/plain", function() { run_test2();}));
+
+ do_test_pending();
+}
+
+function run_test2()
+{
+ var channel = make_channel(uri2);
+ channel.asyncOpen2(new listener("text/html", function() {
+ httpserver.stop(do_test_finished);
+ }));
+}
+
+function simpleHandler(metadata, response)
+{
+ response.seizePower();
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ response.finish();
+}
+
+function normalHandler(metadata, response)
+{
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_bug669001.js b/netwerk/test/unit/test_bug669001.js
new file mode 100644
index 000000000..bbb376f8f
--- /dev/null
+++ b/netwerk/test/unit/test_bug669001.js
@@ -0,0 +1,160 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+var path = "/bug699001";
+
+XPCOMUtils.defineLazyGetter(this, "URI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + path;
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var fetched;
+
+// The test loads a resource that expires in one year, has an etag and varies only by User-Agent
+// First we load it, then check we load it only from the cache w/o even checking with the server
+// Then we modify our User-Agent and try it again
+// We have to get a new content (even though with the same etag) and again on next load only from
+// cache w/o accessing the server
+// Goal is to check we've updated User-Agent request header in cache after we've got 304 response
+// from the server
+
+var tests = [
+{
+ prepare: function() { },
+ test: function(response) {
+ do_check_true(fetched);
+ }
+},
+{
+ prepare: function() { },
+ test: function(response) {
+ do_check_false(fetched);
+ }
+},
+{
+ prepare: function() {
+ setUA("A different User Agent");
+ },
+ test: function(response) {
+ do_check_true(fetched);
+ }
+},
+{
+ prepare: function() { },
+ test: function(response) {
+ do_check_false(fetched);
+ }
+},
+{
+ prepare: function() {
+ setUA("And another User Agent");
+ },
+ test: function(response) {
+ do_check_true(fetched);
+ }
+},
+{
+ prepare: function() { },
+ test: function(response) {
+ do_check_false(fetched);
+ }
+}
+];
+
+function handler(metadata, response)
+{
+ if (metadata.hasHeader("If-None-Match")) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not modified");
+ }
+ else {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain");
+
+ var body = "body";
+ response.bodyOutputStream.write(body, body.length);
+ }
+
+ fetched = true;
+
+ response.setHeader("Expires", getDateString(+1));
+ response.setHeader("Cache-Control", "private");
+ response.setHeader("Vary", "User-Agent");
+ response.setHeader("ETag", "1234");
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(path, handler);
+ httpServer.start(-1);
+
+ do_test_pending();
+
+ nextTest();
+}
+
+function nextTest()
+{
+ fetched = false;
+ tests[0].prepare();
+
+ dump("Testing with User-Agent: " + getUA() + "\n");
+ var chan = make_channel(URI);
+
+ // Give the old channel a chance to close the cache entry first.
+ // XXX This is actually a race condition that might be considered a bug...
+ do_execute_soon(function() {
+ chan.asyncOpen2(new ChannelListener(checkAndShiftTest, null));
+ });
+}
+
+function checkAndShiftTest(request, response)
+{
+ tests[0].test(response);
+
+ tests.shift();
+ if (tests.length == 0) {
+ httpServer.stop(tearDown);
+ return;
+ }
+
+ nextTest();
+}
+
+function tearDown()
+{
+ setUA("");
+ do_test_finished();
+}
+
+// Helpers
+
+function getUA()
+{
+ var httphandler = Cc["@mozilla.org/network/protocol;1?name=http"].
+ getService(Ci.nsIHttpProtocolHandler);
+ return httphandler.userAgent;
+}
+
+function setUA(value)
+{
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefs.setCharPref("general.useragent.override", value);
+}
+
+function getDateString(yearDelta) {
+ var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+ 'Sep', 'Oct', 'Nov', 'Dec' ];
+ var days = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
+
+ var d = new Date();
+ return days[d.getUTCDay()] + ", " + d.getUTCDate() + " "
+ + months[d.getUTCMonth()] + " " + (d.getUTCFullYear() + yearDelta)
+ + " " + d.getUTCHours() + ":" + d.getUTCMinutes() + ":"
+ + d.getUTCSeconds() + " UTC";
+}
diff --git a/netwerk/test/unit/test_bug767025.js b/netwerk/test/unit/test_bug767025.js
new file mode 100644
index 000000000..e10976559
--- /dev/null
+++ b/netwerk/test/unit/test_bug767025.js
@@ -0,0 +1,275 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+
+Cu.import("resource://testing-common/httpd.js");
+
+/**
+ * This is testcase do following steps to make sure bug767025 removing
+ * files as expection.
+ *
+ * STEPS:
+ * - Schedule a offline cache update for app.manifest.
+ * - pages/foo1, pages/foo2, pages/foo3, and pages/foo4 are cached.
+ * - Activate pages/foo1
+ * - Doom pages/foo1, and pages/foo2.
+ * - pages/foo1 should keep alive while pages/foo2 was gone.
+ * - Activate pages/foo3
+ * - Evict all documents.
+ * - all documents except pages/foo1 are gone since pages/foo1 & pages/foo3
+ * are activated.
+ */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID =
+ "@mozilla.org/offlinecacheupdate-service;1";
+const kNS_CACHESTORAGESERVICE_CONTRACTID =
+ "@mozilla.org/netwerk/cache-storage-service;1";
+const kNS_APPLICATIONCACHESERVICE_CONTRACTID =
+ "@mozilla.org/network/application-cache-service;1";
+
+const kManifest = "CACHE MANIFEST\n" +
+ "/pages/foo1\n" +
+ "/pages/foo2\n" +
+ "/pages/foo3\n" +
+ "/pages/foo4\n";
+
+const kDataFileSize = 1024; // file size for each content page
+const kHttpLocation = "http://localhost:4444/";
+
+function manifest_handler(metadata, response) {
+ do_print("manifest\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest);
+}
+
+function datafile_handler(metadata, response) {
+ do_print("datafile_handler\n");
+ let data = "";
+
+ while(data.length < kDataFileSize) {
+ data = data + Math.random().toString(36).substring(2, 15);
+ }
+
+ response.setHeader("content-type", "text/plain");
+ response.write(data.substring(0, kDataFileSize));
+}
+
+function app_handler(metadata, response) {
+ do_print("app_handler\n");
+ response.setHeader("content-type", "text/html");
+
+ response.write("<html></html>");
+}
+
+var httpServer;
+
+function init_profile() {
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory",
+ Ci.nsILocalFile, do_get_profile());
+ do_print("profile " + do_get_profile());
+}
+
+function init_http_server() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/app.appcache", manifest_handler);
+ httpServer.registerPathHandler("/app", app_handler);
+ for (i = 1; i <= 4; i++) {
+ httpServer.registerPathHandler("/pages/foo" + i, datafile_handler);
+ }
+ httpServer.start(4444);
+}
+
+function clean_app_cache() {
+ let cache_service = Cc[kNS_CACHESTORAGESERVICE_CONTRACTID].
+ getService(Ci.nsICacheStorageService);
+ let storage = cache_service.appCacheStorage(LoadContextInfo.default, null);
+ storage.asyncEvictStorage(null);
+}
+
+function do_app_cache(manifestURL, pageURL) {
+ let update_service = Cc[kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID].
+ getService(Ci.nsIOfflineCacheUpdateService);
+
+ Services.perms.add(manifestURL,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ let update =
+ update_service.scheduleUpdate(manifestURL,
+ pageURL,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null); /* no window */
+
+ return update;
+}
+
+function watch_update(update, stateChangeHandler, cacheAvailHandler) {
+ let observer = {
+ QueryInterface: function QueryInterface(iftype) {
+ return this;
+ },
+
+ updateStateChanged: stateChangeHandler,
+ applicationCacheAvailable: cacheAvailHandler
+ };~
+ update.addObserver(observer, false);
+
+ return update;
+}
+
+function start_and_watch_app_cache(manifestURL,
+ pageURL,
+ stateChangeHandler,
+ cacheAvailHandler) {
+ let ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let update = do_app_cache(ioService.newURI(manifestURL, null, null),
+ ioService.newURI(pageURL, null, null));
+ watch_update(update, stateChangeHandler, cacheAvailHandler);
+ return update;
+}
+
+const {STATE_FINISHED: STATE_FINISHED,
+ STATE_CHECKING: STATE_CHECKING,
+ STATE_ERROR: STATE_ERROR } = Ci.nsIOfflineCacheUpdateObserver;
+
+/*
+ * Start caching app1 as a non-pinned app.
+ */
+function start_cache_nonpinned_app() {
+ do_print("Start non-pinned App1");
+ start_and_watch_app_cache(kHttpLocation + "app.appcache",
+ kHttpLocation + "app",
+ function (update, state) {
+ switch(state) {
+ case STATE_FINISHED:
+ check_bug();
+ break;
+
+ case STATE_ERROR:
+ do_throw("App cache state = " + state);
+ break;
+ }
+ },
+ function (appcache) {
+ do_print("app avail " + appcache + "\n");
+ });
+}
+
+var hold_entry_foo1 = null;
+
+function check_bug() {
+ // activate foo1
+ asyncOpenCacheEntry(
+ kHttpLocation + "pages/foo1",
+ "appcache", Ci.nsICacheStorage.OPEN_READONLY, null,
+ function(status, entry, appcache) {
+ let storage = get_cache_service().appCacheStorage(LoadContextInfo.default, appcache);
+
+ // Doom foo1 & foo2
+ storage.asyncDoomURI(createURI(kHttpLocation + "pages/foo1"), "", { onCacheEntryDoomed: function() {
+ storage.asyncDoomURI(createURI(kHttpLocation + "pages/foo2"), "", { onCacheEntryDoomed: function() {
+ check_evict_cache(appcache);
+ }});
+ }});
+
+ hold_entry_foo1 = entry;
+ });
+}
+
+function check_evict_cache(appcache) {
+ // Only foo2 should be removed.
+ let file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("5");
+ file.append("9");
+ file.append("8379C6596B8CA4-0");
+ do_check_eq(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("C");
+ file.append("2");
+ file.append("5F356A168B5E3B-0");
+ do_check_eq(file.exists(), false);
+
+ // activate foo3
+ asyncOpenCacheEntry(
+ kHttpLocation + "pages/foo3",
+ "appcache", Ci.nsICacheStorage.OPEN_READONLY, null,
+ function(status, entry, appcache) {
+ var hold_entry_foo3 = entry;
+
+ // evict all documents.
+ let storage = get_cache_service().appCacheStorage(LoadContextInfo.default, appcache);
+ storage.asyncEvictStorage(null);
+
+ // All documents are removed except foo1 & foo3.
+ syncWithCacheIOThread(function () {
+ // foo1
+ let file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("5");
+ file.append("9");
+ file.append("8379C6596B8CA4-0");
+ do_check_eq(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("0");
+ file.append("0");
+ file.append("61FEE819921D39-0");
+ do_check_eq(file.exists(), false);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("3");
+ file.append("9");
+ file.append("0D8759F1DE5452-0");
+ do_check_eq(file.exists(), false);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("C");
+ file.append("2");
+ file.append("5F356A168B5E3B-0");
+ do_check_eq(file.exists(), false);
+
+ // foo3
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("D");
+ file.append("C");
+ file.append("1ADCCC843B5C00-0");
+ do_check_eq(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("F");
+ file.append("0");
+ file.append("FC3E6D6C1164E9-0");
+ do_check_eq(file.exists(), false);
+
+ httpServer.stop(do_test_finished);
+ }, true /* force even with the new cache back end */);
+ },
+ appcache
+ );
+}
+
+function run_test() {
+ if (typeof _XPCSHELL_PROCESS == "undefined" ||
+ _XPCSHELL_PROCESS != "child") {
+ init_profile();
+ clean_app_cache();
+ }
+
+ init_http_server();
+ start_cache_nonpinned_app();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug770243.js b/netwerk/test/unit/test_bug770243.js
new file mode 100644
index 000000000..8fd7abcea
--- /dev/null
+++ b/netwerk/test/unit/test_bug770243.js
@@ -0,0 +1,207 @@
+/* this test does the following:
+ Always requests the same resource, while for each request getting:
+ 1. 200 + ETag: "one"
+ 2. 401 followed by 200 + ETag: "two"
+ 3. 401 followed by 304
+ 4. 407 followed by 200 + ETag: "three"
+ 5. 407 followed by 304
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv;
+
+function addCreds(scheme, host)
+{
+ var authMgr = Components.classes['@mozilla.org/network/http-auth-manager;1']
+ .getService(Ci.nsIHttpAuthManager);
+ authMgr.setAuthIdentity(scheme, host, httpserv.identity.primaryPort,
+ "basic", "secret", "/", "", "user", "pass");
+}
+
+function clearCreds()
+{
+ var authMgr = Components.classes['@mozilla.org/network/http-auth-manager;1']
+ .getService(Ci.nsIHttpAuthManager);
+ authMgr.clearAll();
+}
+
+function makeChan() {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserv.identity.primaryPort + "/",
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+// Array of handlers that are called one by one in response to expected requests
+
+var handlers = [
+ // Test 1
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Authorization"), false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("ETag", '"one"', false);
+ response.setHeader("Cache-control", 'no-cache', false);
+ response.setHeader("Content-type", 'text/plain', false);
+ var body = "Response body 1";
+ response.bodyOutputStream.write(body, body.length);
+ },
+
+ // Test 2
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Authorization"), false);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"one"');
+ response.setStatusLine(metadata.httpVersion, 401, "Authenticate");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Authorization"), true);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("ETag", '"two"', false);
+ response.setHeader("Cache-control", 'no-cache', false);
+ response.setHeader("Content-type", 'text/plain', false);
+ var body = "Response body 2";
+ response.bodyOutputStream.write(body, body.length);
+ clearCreds();
+ },
+
+ // Test 3
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Authorization"), false);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 401, "Authenticate");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Authorization"), true);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 304, "OK");
+ response.setHeader("ETag", '"two"', false);
+ clearCreds();
+ },
+
+ // Test 4
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Authorization"), false);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 407, "Proxy Authenticate");
+ response.setHeader("Proxy-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Proxy-Authorization"), true);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("ETag", '"three"', false);
+ response.setHeader("Cache-control", 'no-cache', false);
+ response.setHeader("Content-type", 'text/plain', false);
+ var body = "Response body 3";
+ response.bodyOutputStream.write(body, body.length);
+ clearCreds();
+ },
+
+ // Test 5
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Proxy-Authorization"), false);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"three"');
+ response.setStatusLine(metadata.httpVersion, 407, "Proxy Authenticate");
+ response.setHeader("Proxy-Authenticate", 'Basic realm="secret"', false);
+ addCreds("http", "localhost");
+ },
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Proxy-Authorization"), true);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"three"');
+ response.setStatusLine(metadata.httpVersion, 304, "OK");
+ response.setHeader("ETag", '"three"', false);
+ response.setHeader("Cache-control", 'no-cache', false);
+ clearCreds();
+ }
+];
+
+function handler(metadata, response)
+{
+ handlers.shift()(metadata, response);
+}
+
+// Array of tests to run, self-driven
+
+function sync_and_run_next_test()
+{
+ syncWithCacheIOThread(function() {
+ tests.shift()();
+ });
+}
+
+var tests = [
+ // Test 1: 200 (cacheable)
+ function() {
+ var ch = makeChan();
+ ch.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(body, "Response body 1");
+ sync_and_run_next_test();
+ }, null, CL_NOT_FROM_CACHE));
+ },
+
+ // Test 2: 401 and 200 + new content
+ function() {
+ var ch = makeChan();
+ ch.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(body, "Response body 2");
+ sync_and_run_next_test();
+ }, null, CL_NOT_FROM_CACHE));
+ },
+
+ // Test 3: 401 and 304
+ function() {
+ var ch = makeChan();
+ ch.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(body, "Response body 2");
+ sync_and_run_next_test();
+ }, null, CL_FROM_CACHE));
+ },
+
+ // Test 4: 407 and 200 + new content
+ function() {
+ var ch = makeChan();
+ ch.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(body, "Response body 3");
+ sync_and_run_next_test();
+ }, null, CL_NOT_FROM_CACHE));
+ },
+
+ // Test 5: 407 and 304
+ function() {
+ var ch = makeChan();
+ ch.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(body, "Response body 3");
+ sync_and_run_next_test();
+ }, null, CL_FROM_CACHE));
+ },
+
+ // End of test run
+ function() {
+ httpserv.stop(do_test_finished);
+ }
+];
+
+function run_test()
+{
+ do_get_profile();
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", handler);
+ httpserv.start(-1);
+
+ const prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefs.setCharPref("network.proxy.http", "localhost");
+ prefs.setIntPref("network.proxy.http_port", httpserv.identity.primaryPort);
+ prefs.setCharPref("network.proxy.no_proxies_on", "");
+ prefs.setIntPref("network.proxy.type", 1);
+
+ tests.shift()();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug812167.js b/netwerk/test/unit/test_bug812167.js
new file mode 100644
index 000000000..ecda0780c
--- /dev/null
+++ b/netwerk/test/unit/test_bug812167.js
@@ -0,0 +1,127 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+/*
+- get 302 with Cache-control: no-store
+- check cache entry for the 302 response is cached only in memory device
+- get 302 with Expires: -1
+- check cache entry for the 302 response is not cached at all
+*/
+
+var httpserver = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath1 = "/redirect-no-store/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI1", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + randomPath1;
+});
+
+var randomPath2 = "/redirect-expires-past/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI2", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + randomPath2;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+var redirectHandler_NoStore_calls = 0;
+function redirectHandler_NoStore(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", "http://localhost:" +
+ httpserver.identity.primaryPort + "/content", false);
+ response.setHeader("Cache-control", "no-store");
+ ++redirectHandler_NoStore_calls;
+ return;
+}
+
+var redirectHandler_ExpiresInPast_calls = 0;
+function redirectHandler_ExpiresInPast(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", "http://localhost:" +
+ httpserver.identity.primaryPort + "/content", false);
+ response.setHeader("Expires", "-1");
+ ++redirectHandler_ExpiresInPast_calls;
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function check_response(path, request, buffer, expectedExpiration, continuation)
+{
+ do_check_eq(buffer, responseBody);
+
+ // Entry is always there, old cache wrapping code does session->SetDoomEntriesIfExpired(false),
+ // just check it's not persisted or is expired (dep on the test).
+ asyncOpenCacheEntry(path, "disk", Ci.nsICacheStorage.OPEN_READONLY, null, function(status, entry) {
+ do_check_eq(status, 0);
+
+ // Expired entry is on disk, no-store entry is in memory
+ do_check_eq(entry.persistent, expectedExpiration);
+
+ // Do the request again and check the server handler is called appropriately
+ var chan = make_channel(path);
+ chan.asyncOpen2(new ChannelListener(function(request, buffer) {
+ do_check_eq(buffer, responseBody);
+
+ if (expectedExpiration) {
+ // Handler had to be called second time
+ do_check_eq(redirectHandler_ExpiresInPast_calls, 2);
+ }
+ else {
+ // Handler had to be called second time (no-store forces validate),
+ // and we are just in memory
+ do_check_eq(redirectHandler_NoStore_calls, 2);
+ do_check_true(!entry.persistent);
+ }
+
+ continuation();
+ }, null));
+ });
+}
+
+function run_test_no_store()
+{
+ var chan = make_channel(randomURI1);
+ chan.asyncOpen2(new ChannelListener(function(request, buffer) {
+ // Cache-control: no-store response should only be found in the memory cache.
+ check_response(randomURI1, request, buffer, false, run_test_expires_past);
+ }, null));
+}
+
+function run_test_expires_past()
+{
+ var chan = make_channel(randomURI2);
+ chan.asyncOpen2(new ChannelListener(function(request, buffer) {
+ // Expires: -1 response should not be found in any cache.
+ check_response(randomURI2, request, buffer, true, finish_test);
+ }, null));
+}
+
+function finish_test()
+{
+ httpserver.stop(do_test_finished);
+}
+
+function run_test()
+{
+ do_get_profile();
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(randomPath1, redirectHandler_NoStore);
+ httpserver.registerPathHandler(randomPath2, redirectHandler_ExpiresInPast);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ run_test_no_store();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug826063.js b/netwerk/test/unit/test_bug826063.js
new file mode 100644
index 000000000..233e13a9e
--- /dev/null
+++ b/netwerk/test/unit/test_bug826063.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that nsIPrivateBrowsingChannel.isChannelPrivate yields the correct
+ * result for various combinations of .setPrivate() and nsILoadContexts
+ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+var URIs = [
+ "http://example.org",
+ "https://example.org",
+ "ftp://example.org"
+ ];
+
+function LoadContext(usePrivateBrowsing) {
+ this.usePrivateBrowsing = usePrivateBrowsing;
+}
+LoadContext.prototype = {
+ originAttributes: {},
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext, Ci.nsIInterfaceRequestor]),
+ getInterface: XPCOMUtils.generateQI([Ci.nsILoadContext])
+};
+
+function getChannels() {
+ for (let u of URIs) {
+ yield NetUtil.newChannel({
+ uri: u,
+ loadUsingSystemPrincipal: true
+ });
+ }
+}
+
+function checkPrivate(channel, shouldBePrivate) {
+ do_check_eq(channel.QueryInterface(Ci.nsIPrivateBrowsingChannel).isChannelPrivate,
+ shouldBePrivate);
+}
+
+/**
+ * Default configuration
+ * Default is non-private
+ */
+add_test(function test_plain() {
+ for (let c of getChannels()) {
+ checkPrivate(c, false);
+ }
+ run_next_test();
+});
+
+/**
+ * Explicitly setPrivate(true), no load context
+ */
+add_test(function test_setPrivate_private() {
+ for (let c of getChannels()) {
+ c.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(true);
+ checkPrivate(c, true);
+ }
+ run_next_test();
+});
+
+/**
+ * Explicitly setPrivate(false), no load context
+ */
+add_test(function test_setPrivate_regular() {
+ for (let c of getChannels()) {
+ c.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(false);
+ checkPrivate(c, false);
+ }
+ run_next_test();
+});
+
+/**
+ * Load context mandates private mode
+ */
+add_test(function test_LoadContextPrivate() {
+ let ctx = new LoadContext(true);
+ for (let c of getChannels()) {
+ c.notificationCallbacks = ctx;
+ checkPrivate(c, true);
+ }
+ run_next_test();
+});
+
+/**
+ * Load context mandates regular mode
+ */
+add_test(function test_LoadContextRegular() {
+ let ctx = new LoadContext(false);
+ for (let c of getChannels()) {
+ c.notificationCallbacks = ctx;
+ checkPrivate(c, false);
+ }
+ run_next_test();
+});
+
+
+// Do not test simultanous uses of .setPrivate and load context.
+// There is little merit in doing so, and combining both will assert in
+// Debug builds anyway.
+
+
+function run_test() {
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_bug856978.js b/netwerk/test/unit/test_bug856978.js
new file mode 100644
index 000000000..9624ef64e
--- /dev/null
+++ b/netwerk/test/unit/test_bug856978.js
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure that the authorization header can get deleted e.g. by
+// extensions if they are observing "http-on-modify-request". In a first step
+// the auth cache is filled with credentials which then get added to the
+// following request. On "http-on-modify-request" it is tested whether the
+// authorization header got added at all and if so it gets removed. This test
+// passes iff both succeeds.
+
+Components.utils.import("resource://testing-common/httpd.js");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+var notification = "http-on-modify-request";
+
+var httpServer = null;
+
+var authCredentials = "guest:guest";
+var authPath = "/authTest";
+var authCredsURL = "http://" + authCredentials + "@localhost:8888" + authPath;
+var authURL = "http://localhost:8888" + authPath;
+
+function authHandler(metadata, response) {
+ if (metadata.hasHeader("Test")) {
+ // Lets see if the auth header got deleted.
+ var noAuthHeader = false;
+ if (!metadata.hasHeader("Authorization")) {
+ noAuthHeader = true;
+ }
+ do_check_true(noAuthHeader);
+ } else {
+ // Not our test request yet.
+ if (!metadata.hasHeader("Authorization")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ }
+ }
+}
+
+function RequestObserver() {
+ this.register();
+}
+
+RequestObserver.prototype = {
+ register: function() {
+ do_print("Registering " + notification);
+ Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
+ addObserver(this, notification, true);
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIObserver) || iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic == notification) {
+ if (!(subject instanceof Ci.nsIHttpChannel)) {
+ do_throw(notification + " observed a non-HTTP channel.");
+ }
+ try {
+ let authHeader = subject.getRequestHeader("Authorization");
+ } catch (e) {
+ // Throw if there is no header to delete. We should get one iff caching
+ // the auth credentials is working and the header gets added _before_
+ // "http-on-modify-request" gets called.
+ httpServer.stop(do_test_finished);
+ do_throw("No authorization header found, aborting!");
+ }
+ // We are still here. Let's remove the authorization header now.
+ subject.setRequestHeader("Authorization", null, false);
+ }
+ }
+}
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {},
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ if (current_test < (tests.length - 1)) {
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpServer.stop(do_test_finished);
+ }
+ do_test_finished();
+ }
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+var tests = [startAuthHeaderTest, removeAuthHeaderTest];
+
+var current_test = 0;
+
+var requestObserver = null;
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(authPath, authHandler);
+ httpServer.start(8888);
+
+ tests[0]();
+}
+
+function startAuthHeaderTest() {
+ var chan = makeChan(authCredsURL);
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function removeAuthHeaderTest() {
+ // After caching the auth credentials in the first test, lets try to remove
+ // the authorization header now...
+ requestObserver = new RequestObserver();
+ var chan = makeChan(authURL);
+ // Indicating that the request is coming from the second test.
+ chan.setRequestHeader("Test", "1", false);
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug894586.js b/netwerk/test/unit/test_bug894586.js
new file mode 100644
index 000000000..97b9ee20f
--- /dev/null
+++ b/netwerk/test/unit/test_bug894586.js
@@ -0,0 +1,158 @@
+/*
+ * Tests for bug 894586: nsSyncLoadService::PushSyncStreamToListener
+ * should not fail for channels of unknown size
+ */
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"]
+ .getService(Ci.nsIContentSecurityManager);
+
+function ProtocolHandler() {
+ this.uri = Cc["@mozilla.org/network/simple-uri;1"].
+ createInstance(Ci.nsIURI);
+ this.uri.spec = this.scheme + ":dummy";
+ this.uri.QueryInterface(Ci.nsIMutable).mutable = false;
+}
+
+ProtocolHandler.prototype = {
+ /** nsIProtocolHandler */
+ get scheme() {
+ return "x-bug894586";
+ },
+ get defaultPort() {
+ return -1;
+ },
+ get protocolFlags() {
+ return Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
+ Ci.nsIProtocolHandler.URI_NON_PERSISTABLE |
+ Ci.nsIProtocolHandler.URI_SYNC_LOAD_IS_OK;
+ },
+ newURI: function(aSpec, aOriginCharset, aBaseURI) {
+ return this.uri;
+ },
+ newChannel2: function(aURI, aLoadInfo) {
+ this.loadInfo = aLoadInfo;
+ return this;
+ },
+ newChannel: function(aURI) {
+ return this;
+ },
+ allowPort: function(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsIChannel */
+ get originalURI() {
+ return this.uri;
+ },
+ get URI() {
+ return this.uri;
+ },
+ owner: null,
+ notificationCallbacks: null,
+ get securityInfo() {
+ return null;
+ },
+ get contentType() {
+ return "text/css";
+ },
+ set contentType(val) {
+ },
+ contentCharset: "UTF-8",
+ get contentLength() {
+ return -1;
+ },
+ set contentLength(val) {
+ throw Components.Exception("Setting content length", NS_ERROR_NOT_IMPLEMENTED);
+ },
+ open: function() {
+ var file = do_get_file("test_bug894586.js", false);
+ do_check_true(file.exists());
+ var url = Services.io.newFileURI(file);
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true}).open2();
+ },
+ open2: function() {
+ // throws an error if security checks fail
+ contentSecManager.performSecurityCheck(this, null);
+ return this.open();
+ },
+ asyncOpen: function(aListener, aContext) {
+ throw Components.Exception("Not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ asyncOpen2: function(aListener, aContext) {
+ throw Components.Exception("Not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ contentDisposition: Ci.nsIChannel.DISPOSITION_INLINE,
+ get contentDispositionFilename() {
+ throw Components.Exception("No file name",
+ Cr.NS_ERROR_NOT_AVAILABLE);
+ },
+ get contentDispositionHeader() {
+ throw Components.Exception("No header",
+ Cr.NS_ERROR_NOT_AVAILABLE);
+ },
+
+ /** nsIRequest */
+ get name() {
+ return this.uri.spec;
+ },
+ isPending: () => false,
+ get status() {
+ return Cr.NS_OK;
+ },
+ cancel: function(status) {},
+ loadGroup: null,
+ loadFlags: Ci.nsIRequest.LOAD_NORMAL |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+
+ /** nsIFactory */
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.Exception("createInstance no aggregation",
+ Cr.NS_ERROR_NO_AGGREGATION);
+ }
+ return this.QueryInterface(aIID);
+ },
+ lockFactory: function() {},
+
+ /** nsISupports */
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
+ Ci.nsIRequest,
+ Ci.nsIChannel,
+ Ci.nsIFactory]),
+ classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}")
+};
+
+/**
+ * Attempt a sync load; we use the stylesheet service to do this for us,
+ * based on the knowledge that it forces a sync load under the hood.
+ */
+function run_test()
+{
+ var handler = new ProtocolHandler();
+ var registrar = Components.manager.
+ QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(handler.classID, "",
+ "@mozilla.org/network/protocol;1?name=" + handler.scheme,
+ handler);
+ try {
+ var ss = Cc["@mozilla.org/content/style-sheet-service;1"].
+ getService(Ci.nsIStyleSheetService);
+ ss.loadAndRegisterSheet(handler.uri, Ci.nsIStyleSheetService.AGENT_SHEET);
+ do_check_true(ss.sheetRegistered(handler.uri, Ci.nsIStyleSheetService.AGENT_SHEET));
+ } finally {
+ registrar.unregisterFactory(handler.classID, handler);
+ }
+}
+
+// vim: set et ts=2 :
+
diff --git a/netwerk/test/unit/test_bug935499.js b/netwerk/test/unit/test_bug935499.js
new file mode 100644
index 000000000..5e2ba6569
--- /dev/null
+++ b/netwerk/test/unit/test_bug935499.js
@@ -0,0 +1,7 @@
+function run_test() {
+ var idnService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+
+ var isASCII = {};
+ do_check_eq(idnService.convertToDisplayIDN("xn--", isASCII), "xn--");
+}
diff --git a/netwerk/test/unit/test_cache-control_request.js b/netwerk/test/unit/test_cache-control_request.js
new file mode 100644
index 000000000..bed26de0e
--- /dev/null
+++ b/netwerk/test/unit/test_cache-control_request.js
@@ -0,0 +1,385 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+var cache = null;
+
+var base_url = "http://localhost:" + httpserver.identity.primaryPort;
+var resource_age_100 = "/resource_age_100";
+var resource_age_100_url = base_url + resource_age_100;
+var resource_stale_100 = "/resource_stale_100";
+var resource_stale_100_url = base_url + resource_stale_100;
+var resource_fresh_100 = "/resource_fresh_100";
+var resource_fresh_100_url = base_url + resource_fresh_100;
+
+// Test flags
+var hit_server = false;
+
+
+function make_channel(url, cache_control)
+{
+ // Reset test global status
+ hit_server = false;
+
+ var req = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+ req.QueryInterface(Ci.nsIHttpChannel);
+ if (cache_control) {
+ req.setRequestHeader("Cache-control", cache_control, false);
+ }
+
+ return req;
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+function resource_age_100_handler(metadata, response)
+{
+ hit_server = true;
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Age", "100", false);
+ response.setHeader("Last-Modified", date_string_from_now(-100), false);
+ response.setHeader("Expires", date_string_from_now(+9999), false);
+
+ const body = "data1";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function resource_stale_100_handler(metadata, response)
+{
+ hit_server = true;
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Date", date_string_from_now(-200), false);
+ response.setHeader("Last-Modified", date_string_from_now(-200), false);
+ response.setHeader("Cache-Control", "max-age=100", false);
+ response.setHeader("Expires", date_string_from_now(-100), false);
+
+ const body = "data2";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function resource_fresh_100_handler(metadata, response)
+{
+ hit_server = true;
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Last-Modified", date_string_from_now(0), false);
+ response.setHeader("Cache-Control", "max-age=100", false);
+ response.setHeader("Expires", date_string_from_now(+100), false);
+
+ const body = "data3";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since it depends on the new APIs.");
+ return;
+ }
+
+ do_test_pending();
+
+ httpserver.registerPathHandler(resource_age_100, resource_age_100_handler);
+ httpserver.registerPathHandler(resource_stale_100, resource_stale_100_handler);
+ httpserver.registerPathHandler(resource_fresh_100, resource_fresh_100_handler);
+ cache = getCacheStorage("disk");
+
+ wait_for_cache_index(run_next_test);
+}
+
+// Here starts the list of tests
+
+// ============================================================================
+// Cache-Control: no-store
+
+add_test(() => {
+ // Must not create a cache entry
+ var ch = make_channel(resource_age_100_url, "no-store");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_false(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // Prepare state only, cache the entry
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // Check the prepared cache entry is used when no special directives are added
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // Try again, while we already keep a cache entry,
+ // the channel must not use it, entry should stay in the cache
+ var ch = make_channel(resource_age_100_url, "no-store");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+// ============================================================================
+// Cache-Control: no-cache
+
+add_test(() => {
+ // Check the prepared cache entry is used when no special directives are added
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // The existing entry should be revalidated (we expect a server hit)
+ var ch = make_channel(resource_age_100_url, "no-cache");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+// ============================================================================
+// Cache-Control: max-age
+
+add_test(() => {
+ // Check the prepared cache entry is used when no special directives are added
+ var ch = make_channel(resource_age_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // The existing entry's age is greater than the maximum requested,
+ // should hit server
+ var ch = make_channel(resource_age_100_url, "max-age=10");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // The existing entry's age is greater than the maximum requested,
+ // but the max-stale directive says to use it when it's fresh enough
+ var ch = make_channel(resource_age_100_url, "max-age=10, max-stale=99999");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // The existing entry's age is lesser than the maximum requested,
+ // should go from cache
+ var ch = make_channel(resource_age_100_url, "max-age=1000");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_age_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+// ============================================================================
+// Cache-Control: max-stale
+
+add_test(() => {
+ // Preprate the entry first
+ var ch = make_channel(resource_stale_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ // Must shift the expiration time set on the entry to |now| be in the past
+ do_timeout(1500, run_next_test);
+ }, null));
+});
+
+add_test(() => {
+ // Check it's not reused (as it's stale) when no special directives
+ // are provided
+ var ch = make_channel(resource_stale_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ do_timeout(1500, run_next_test);
+ }, null));
+});
+
+add_test(() => {
+ // Accept cached responses of any stale time
+ var ch = make_channel(resource_stale_100_url, "max-stale");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ do_timeout(1500, run_next_test);
+ }, null));
+});
+
+add_test(() => {
+ // The entry is stale only by 100 seconds, accept it
+ var ch = make_channel(resource_stale_100_url, "max-stale=1000");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ do_timeout(1500, run_next_test);
+ }, null));
+});
+
+add_test(() => {
+ // The entry is stale by 100 seconds but we only accept a 10 seconds stale
+ // entry, go from server
+ var ch = make_channel(resource_stale_100_url, "max-stale=10");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_stale_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+// ============================================================================
+// Cache-Control: min-fresh
+
+add_test(() => {
+ // Preprate the entry first
+ var ch = make_channel(resource_fresh_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // Check it's reused when no special directives are provided
+ var ch = make_channel(resource_fresh_100_url);
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // Entry fresh enough to be served from the cache
+ var ch = make_channel(resource_fresh_100_url, "min-fresh=10");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ // The entry is not fresh enough
+ var ch = make_channel(resource_fresh_100_url, "min-fresh=1000");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+// ============================================================================
+// Parser test, if the Cache-Control header would not parse correctly, the entry
+// doesn't load from the server.
+
+add_test(() => {
+ var ch = make_channel(resource_fresh_100_url, "unknown1,unknown2 = \"a,b\", min-fresh = 1000 ");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+add_test(() => {
+ var ch = make_channel(resource_fresh_100_url, "no-cache = , min-fresh = 10");
+ ch.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(cache.exists(make_uri(resource_fresh_100_url), ""));
+
+ run_next_test();
+ }, null));
+});
+
+// ============================================================================
+// Done
+
+add_test(() => {
+ run_next_test();
+ httpserver.stop(do_test_finished);
+});
+
+// ============================================================================
+// Helpers
+
+function date_string_from_now(delta_secs) {
+ var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+ 'Sep', 'Oct', 'Nov', 'Dec'];
+ var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+
+ var d = new Date();
+ d.setTime(d.getTime() + delta_secs * 1000);
+ return days[d.getUTCDay()] + ", " +
+ d.getUTCDate() + " " +
+ months[d.getUTCMonth()] + " " +
+ d.getUTCFullYear() + " " +
+ d.getUTCHours() + ":" +
+ d.getUTCMinutes() + ":" +
+ d.getUTCSeconds() + " UTC";
+}
diff --git a/netwerk/test/unit/test_cache2-00-service-get.js b/netwerk/test/unit/test_cache2-00-service-get.js
new file mode 100644
index 000000000..6a8b2e10c
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-00-service-get.js
@@ -0,0 +1,16 @@
+function run_test()
+{
+ // Just check the contract ID alias works well.
+ try {
+ var serviceA = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ do_check_true(serviceA);
+ var serviceB = Components.classes["@mozilla.org/network/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ do_check_true(serviceB);
+
+ do_check_eq(serviceA, serviceB);
+ } catch (ex) {
+ do_throw("Cannot instantiate cache storage service: " + ex);
+ }
+}
diff --git a/netwerk/test/unit/test_cache2-01-basic.js b/netwerk/test/unit/test_cache2-01-basic.js
new file mode 100644
index 000000000..dd8c34087
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01-basic.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null,
+ new OpenCallback(NEW, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01a-basic-readonly.js b/netwerk/test/unit/test_cache2-01a-basic-readonly.js
new file mode 100644
index 000000000..bf1d31317
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01a-basic-readonly.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://ro/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://ro/", "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry("http://ro/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null,
+ new OpenCallback(NEW, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://ro/", "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01b-basic-datasize.js b/netwerk/test/unit/test_cache2-01b-basic-datasize.js
new file mode 100644
index 000000000..e46d6ab5f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01b-basic-datasize.js
@@ -0,0 +1,32 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+ // Open for read and check
+ do_check_eq(entry.dataSize, 3);
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ do_check_eq(entry.dataSize, 3);
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null,
+ new OpenCallback(NEW|WAITFORWRITE, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ do_check_eq(entry.dataSize, 3);
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ do_check_eq(entry.dataSize, 3);
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js b/netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js
new file mode 100644
index 000000000..0467656f0
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://mt/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|METAONLY, "a1m", "a1d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://mt/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry("http://mt/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null,
+ new OpenCallback(NEW, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://mt/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01d-basic-not-wanted.js b/netwerk/test/unit/test_cache2-01d-basic-not-wanted.js
new file mode 100644
index 000000000..b07831d7b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01d-basic-not-wanted.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open but don't want the entry
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NOTWANTED, "a1m", "a1d", function(entry) {
+ // Open for read again and check the entry is OK
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js b/netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js
new file mode 100644
index 000000000..156add50e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js
@@ -0,0 +1,35 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Open for write, delay the actual write
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|DONTFILL, "a1m", "a1d", function(entry) {
+ var bypassed = false;
+
+ // Open and bypass
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_BYPASS_IF_BUSY, null,
+ new OpenCallback(NOTFOUND, "", "", function(entry) {
+ do_check_false(bypassed);
+ bypassed = true;
+ })
+ );
+
+ // do_execute_soon for two reasons:
+ // 1. we want finish_cache2_test call for sure after do_test_pending, but all the callbacks here
+ // may invoke synchronously
+ // 2. precaution when the OPEN_BYPASS_IF_BUSY invocation become a post one day
+ do_execute_soon(function() {
+ do_check_true(bypassed);
+ finish_cache2_test();
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-01f-basic-openTruncate.js b/netwerk/test/unit/test_cache2-01f-basic-openTruncate.js
new file mode 100644
index 000000000..d1ef54b5f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01f-basic-openTruncate.js
@@ -0,0 +1,24 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ var storage = getCacheStorage("disk");
+ var entry = storage.openTruncate(createURI("http://new1/"), "");
+ do_check_true(!!entry);
+
+ // Fill the entry, and when done, check it's content
+ (new OpenCallback(NEW, "meta", "data", function() {
+ asyncOpenCacheEntry("http://new1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "meta", "data", function() {
+ finish_cache2_test();
+ })
+ );
+ })).onCacheEntryAvailable(entry, true, null, 0);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-02-open-non-existing.js b/netwerk/test/unit/test_cache2-02-open-non-existing.js
new file mode 100644
index 000000000..584ff7dfa
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-02-open-non-existing.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open non-existing for read, should fail
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ new OpenCallback(NOTFOUND, null, null, function(entry) {
+ // Open the same non-existing for read again, should fail second time
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ new OpenCallback(NOTFOUND, null, null, function(entry) {
+ // Try it again normally, should go
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ // ...and check
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js b/netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js
new file mode 100644
index 000000000..79c367410
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js
@@ -0,0 +1,24 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open but let OCEA throw
+ asyncOpenCacheEntry("http://c/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|THROWAVAIL, null, null, function(entry) {
+ // Try it again, should go
+ asyncOpenCacheEntry("http://c/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "c1m", "c1d", function(entry) {
+ // ...and check
+ asyncOpenCacheEntry("http://c/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(false, "c1m", "c1d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js b/netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js
new file mode 100644
index 000000000..f4e59bd36
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open but let OCEA throw
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|THROWAVAIL, null, null, function(entry) {
+ // Open but let OCEA throw ones again
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|THROWAVAIL, null, null, function(entry) {
+ // Try it again, should go
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "d1m", "d1d", function(entry) {
+ // ...and check
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "d1m", "d1d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-05-visit.js b/netwerk/test/unit/test_cache2-05-visit.js
new file mode 100644
index 000000000..550162271
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-05-visit.js
@@ -0,0 +1,78 @@
+function run_test()
+{
+ do_get_profile();
+
+ var storage = getCacheStorage("disk");
+ var mc = new MultipleCallbacks(4, function() {
+ // Method asyncVisitStorage() gets the data from index on Cache I/O thread
+ // with INDEX priority, so it is ensured that index contains information
+ // about all pending writes. However, OpenCallback emulates network latency
+ // by postponing the writes using do_execute_soon. We must do the same here
+ // to make sure that all writes are posted to Cache I/O thread before we
+ // visit the storage.
+ do_execute_soon(function() {
+ syncWithCacheIOThread(function() {
+
+ var expectedConsumption = newCacheBackEndUsed()
+ ? 4096
+ : 48;
+
+ storage.asyncVisitStorage(
+ // Test should store 4 entries
+ new VisitCallback(4, expectedConsumption, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
+ storage.asyncVisitStorage(
+ // Still 4 entries expected, now don't walk them
+ new VisitCallback(4, expectedConsumption, null, function() {
+ finish_cache2_test();
+ }),
+ false
+ );
+ }),
+ true
+ );
+ });
+ });
+ }, !newCacheBackEndUsed());
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://c/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "c1m", "c1d", function(entry) {
+ asyncOpenCacheEntry("http://c/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "c1m", "c1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "d1m", "d1d", function(entry) {
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "d1m", "d1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-06-pb-mode.js b/netwerk/test/unit/test_cache2-06-pb-mode.js
new file mode 100644
index 000000000..616499df9
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-06-pb-mode.js
@@ -0,0 +1,41 @@
+function exitPB()
+{
+ var obsvc = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obsvc.notifyObservers(null, "last-pb-context-exited", null);
+}
+
+function run_test()
+{
+ do_get_profile();
+
+ // Store PB entry
+ asyncOpenCacheEntry("http://p1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.private,
+ new OpenCallback(NEW, "p1m", "p1d", function(entry) {
+ asyncOpenCacheEntry("http://p1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.private,
+ new OpenCallback(NORMAL, "p1m", "p1d", function(entry) {
+ // Check it's there
+ syncWithCacheIOThread(function() {
+ var storage = getCacheStorage("disk", LoadContextInfo.private);
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 12, ["http://p1/"], function() {
+ // Simulate PB exit
+ exitPB();
+ // Check the entry is gone
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ });
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-07-visit-memory.js b/netwerk/test/unit/test_cache2-07-visit-memory.js
new file mode 100644
index 000000000..03b5aba80
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-07-visit-memory.js
@@ -0,0 +1,82 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Add entry to the memory storage
+ var mc = new MultipleCallbacks(5, function() {
+ // Check it's there by visiting the storage
+ syncWithCacheIOThread(function() {
+ var storage = getCacheStorage("memory");
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 12, ["http://mem1/"], function() {
+ storage = getCacheStorage("disk");
+ storage.asyncVisitStorage(
+ // Previous tests should store 4 disk entries
+ new VisitCallback(4, 4096, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ });
+ });
+
+ asyncOpenCacheEntry("http://mem1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "m1m", "m1d", function(entry) {
+ asyncOpenCacheEntry("http://mem1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "m1m", "m1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://c/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://c/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://d/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-07a-open-memory.js b/netwerk/test/unit/test_cache2-07a-open-memory.js
new file mode 100644
index 000000000..1018a4cba
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-07a-open-memory.js
@@ -0,0 +1,53 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // First check how behaves the memory storage.
+
+ asyncOpenCacheEntry("http://mem-first/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "mem1-meta", "mem1-data", function(entryM1) {
+ do_check_false(entryM1.persistent);
+ asyncOpenCacheEntry("http://mem-first/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "mem1-meta", "mem1-data", function(entryM2) {
+ do_check_false(entryM1.persistent);
+ do_check_false(entryM2.persistent);
+
+ // Now check the disk storage behavior.
+
+ asyncOpenCacheEntry("http://disk-first/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ // Must wait for write, since opening the entry as memory-only before the disk one
+ // is written would cause NS_ERROR_NOT_AVAILABLE from openOutputStream when writing
+ // this disk entry since it's doomed during opening of the memory-only entry for the same URL.
+ new OpenCallback(NEW|WAITFORWRITE, "disk1-meta", "disk1-data", function(entryD1) {
+ do_check_true(entryD1.persistent);
+ // Now open the same URL as a memory-only entry, the disk entry must be doomed.
+ asyncOpenCacheEntry("http://disk-first/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ // This must be recreated
+ new OpenCallback(NEW, "mem2-meta", "mem2-data", function(entryD2) {
+ do_check_true(entryD1.persistent);
+ do_check_false(entryD2.persistent);
+ // Check we get it back, even when opening via the disk storage
+ asyncOpenCacheEntry("http://disk-first/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "mem2-meta", "mem2-data", function(entryD3) {
+ do_check_true(entryD1.persistent);
+ do_check_false(entryD2.persistent);
+ do_check_false(entryD3.persistent);
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js b/netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js
new file mode 100644
index 000000000..246cb789c
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js
@@ -0,0 +1,18 @@
+function run_test()
+{
+ do_get_profile();
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ var storage = getCacheStorage("memory");
+ // Have to fail
+ storage.asyncDoomURI(createURI("http://a/"), "",
+ new EvictionCallback(false, function() {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js b/netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js
new file mode 100644
index 000000000..24c736fe2
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js
@@ -0,0 +1,21 @@
+function run_test()
+{
+ do_get_profile();
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ var storage = getCacheStorage("disk");
+ storage.asyncDoomURI(createURI("http://a/"), "",
+ new EvictionCallback(true, function() {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-10-evict-direct.js b/netwerk/test/unit/test_cache2-10-evict-direct.js
new file mode 100644
index 000000000..edeeee416
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-10-evict-direct.js
@@ -0,0 +1,20 @@
+function run_test()
+{
+ do_get_profile();
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ entry.asyncDoom(
+ new EvictionCallback(true, function() {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js b/netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js
new file mode 100644
index 000000000..0f9048db1
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js
@@ -0,0 +1,21 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|DOOMED, "b1m", "b1d", function(entry) {
+ entry.asyncDoom(
+ new EvictionCallback(true, function() {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-11-evict-memory.js b/netwerk/test/unit/test_cache2-11-evict-memory.js
new file mode 100644
index 000000000..90eced7de
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-11-evict-memory.js
@@ -0,0 +1,61 @@
+function run_test()
+{
+ do_get_profile();
+
+ var storage = getCacheStorage("memory");
+ var mc = new MultipleCallbacks(3, function() {
+ storage.asyncEvictStorage(
+ new EvictionCallback(true, function() {
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function() {
+ var storage = getCacheStorage("disk");
+
+ var expectedConsumption = newCacheBackEndUsed()
+ ? 2048
+ : 24;
+
+ storage.asyncVisitStorage(
+ new VisitCallback(2, expectedConsumption, ["http://a/", "http://b/"], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ })
+ );
+ }, !newCacheBackEndUsed());
+
+ asyncOpenCacheEntry("http://mem1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "m2m", "m2d", function(entry) {
+ asyncOpenCacheEntry("http://mem1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "m2m", "m2d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-12-evict-disk.js b/netwerk/test/unit/test_cache2-12-evict-disk.js
new file mode 100644
index 000000000..0ce7ec084
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-12-evict-disk.js
@@ -0,0 +1,61 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ var mc = new MultipleCallbacks(3, function() {
+ var storage = getCacheStorage("disk");
+ storage.asyncEvictStorage(
+ new EvictionCallback(true, function() {
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function() {
+ var storage = getCacheStorage("memory");
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ })
+ );
+ }, !newCacheBackEndUsed());
+
+ asyncOpenCacheEntry("http://mem1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "m2m", "m2d", function(entry) {
+ asyncOpenCacheEntry("http://mem1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "m2m", "m2d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-13-evict-non-existing.js b/netwerk/test/unit/test_cache2-13-evict-non-existing.js
new file mode 100644
index 000000000..1aa107295
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-13-evict-non-existing.js
@@ -0,0 +1,13 @@
+function run_test()
+{
+ do_get_profile();
+
+ var storage = getCacheStorage("disk");
+ storage.asyncDoomURI(createURI("http://non-existing/"), "",
+ new EvictionCallback(false, function() {
+ finish_cache2_test();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-14-concurent-readers.js b/netwerk/test/unit/test_cache2-14-concurent-readers.js
new file mode 100644
index 000000000..7355a2a9e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-14-concurent-readers.js
@@ -0,0 +1,31 @@
+function run_test()
+{
+ do_get_profile();
+
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "x1m", "x1d", function(entry) {
+ // nothing to do here, we expect concurent callbacks to get
+ // all notified, then the test finishes
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "x1m", "x1d", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "x1m", "x1d", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "x1m", "x1d", function(entry) {
+ mc.fired();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js b/netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js
new file mode 100644
index 000000000..c5ceb99a0
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js
@@ -0,0 +1,48 @@
+function run_test()
+{
+ do_get_profile();
+
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "x1m", "x1d", function(entry) {
+ // nothing to do here, we expect concurent callbacks to get
+ // all notified, then the test finishes
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ var order = 0;
+
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL|COMPLETE|NOTIFYBEFOREREAD, "x1m", "x1d", function(entry, beforeReading) {
+ if (beforeReading) {
+ ++order;
+ do_check_eq(order, newCacheBackEndUsed() ? 3 : 1);
+ } else {
+ mc.fired();
+ }
+ })
+ );
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL|NOTIFYBEFOREREAD, "x1m", "x1d", function(entry, beforeReading) {
+ if (beforeReading) {
+ ++order;
+ do_check_eq(order, newCacheBackEndUsed() ? 1 : 2);
+ } else {
+ mc.fired();
+ }
+ })
+ );
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL|NOTIFYBEFOREREAD, "x1m", "x1d", function(entry, beforeReading) {
+ if (beforeReading) {
+ ++order;
+ do_check_eq(order, newCacheBackEndUsed() ? 2 : 3);
+ } else {
+ mc.fired();
+ }
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-15-conditional-304.js b/netwerk/test/unit/test_cache2-15-conditional-304.js
new file mode 100644
index 000000000..9375cdbe8
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-15-conditional-304.js
@@ -0,0 +1,39 @@
+function run_test()
+{
+ do_get_profile();
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://304/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "31m", "31d", function(entry) {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry("http://304/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(REVAL, "31m", "31d", function(entry) {
+ // emulate 304 from the server
+ do_execute_soon(function() {
+ entry.setValid(); // this will trigger OpenCallbacks bellow
+ });
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry("http://304/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "31m", "31d", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://304/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "31m", "31d", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://304/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "31m", "31d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-16-conditional-200.js b/netwerk/test/unit/test_cache2-16-conditional-200.js
new file mode 100644
index 000000000..601cca97e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-16-conditional-200.js
@@ -0,0 +1,52 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "21m", "21d", function(entry) {
+ asyncOpenCacheEntry("http://200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "21m", "21d", function(entry) {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry("http://200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(REVAL, "21m", "21d", function(entry) {
+ // emulate 200 from server (new content)
+ do_execute_soon(function() {
+ var entry2 = entry.recreate();
+
+ // now fill the new entry, use OpenCallback directly for it
+ (new OpenCallback(NEW, "22m", "22d", function() {}))
+ .onCacheEntryAvailable(entry2, true, null, Cr.NS_OK);
+ });
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry("http://200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "22m", "22d", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "22m", "22d", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "22m", "22d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-17-evict-all.js b/netwerk/test/unit/test_cache2-17-evict-all.js
new file mode 100644
index 000000000..3033e42d6
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-17-evict-all.js
@@ -0,0 +1,17 @@
+function run_test()
+{
+ do_get_profile();
+
+ var svc = get_cache_service();
+ svc.clear();
+
+ var storage = getCacheStorage("disk");
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-18-not-valid.js b/netwerk/test/unit/test_cache2-18-not-valid.js
new file mode 100644
index 000000000..c83a78c7a
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-18-not-valid.js
@@ -0,0 +1,30 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Open for write, write but expect it to fail, since other callback will recreate (and doom)
+ // the first entry before it opens output stream (note: in case of problems the DOOMED flag
+ // can be removed, it is not the test failure when opening the output stream on recreated entry.
+ asyncOpenCacheEntry("http://nv/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|DOOMED, "v1m", "v1d", function(entry) {
+ // Open for rewrite (don't validate), write different meta and data
+ asyncOpenCacheEntry("http://nv/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NOTVALID|RECREATE, "v2m", "v2d", function(entry) {
+ // And check...
+ asyncOpenCacheEntry("http://nv/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "v2m", "v2d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-19-range-206.js b/netwerk/test/unit/test_cache2-19-range-206.js
new file mode 100644
index 000000000..ceb782d4e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-19-range-206.js
@@ -0,0 +1,44 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://r206/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "206m", "206part1-", function(entry) {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry("http://r206/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(PARTIAL, "206m", "206part1-", function(entry) {
+ // emulate 206 from the server, i.e. resume transaction and write content to the output stream
+ (new OpenCallback(NEW|WAITFORWRITE|PARTIAL, "206m", "-part2", function(entry) {
+ entry.setValid();
+ })).onCacheEntryAvailable(entry, true, null, Cr.NS_OK);
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry("http://r206/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "206m", "206part1--part2", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://r206/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "206m", "206part1--part2", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://r206/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "206m", "206part1--part2", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-20-range-200.js b/netwerk/test/unit/test_cache2-20-range-200.js
new file mode 100644
index 000000000..349dd343e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-20-range-200.js
@@ -0,0 +1,45 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://r200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "200m1", "200part1a-", function(entry) {
+ // Open normally but wait for validation from the server
+ asyncOpenCacheEntry("http://r200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(PARTIAL, "200m1", "200part1a-", function(entry) {
+ // emulate 200 from the server, i.e. recreate the entry, resume transaction and
+ // write new content to the output stream
+ (new OpenCallback(NEW|WAITFORWRITE|RECREATE, "200m2", "200part1b--part2b", function(entry) {
+ entry.setValid();
+ })).onCacheEntryAvailable(entry, true, null, Cr.NS_OK);
+ })
+ );
+
+ var mc = new MultipleCallbacks(3, finish_cache2_test);
+
+ asyncOpenCacheEntry("http://r200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "200m2", "200part1b--part2b", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://r200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "200m2", "200part1b--part2b", function(entry) {
+ mc.fired();
+ })
+ );
+ asyncOpenCacheEntry("http://r200/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "200m2", "200part1b--part2b", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-21-anon-storage.js b/netwerk/test/unit/test_cache2-21-anon-storage.js
new file mode 100644
index 000000000..6f325077d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-21-anon-storage.js
@@ -0,0 +1,38 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Create and check an entry anon disk storage
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.anonymous,
+ new OpenCallback(NEW, "an1", "an1", function(entry) {
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.anonymous,
+ new OpenCallback(NORMAL, "an1", "an1", function(entry) {
+ // Create and check an entry non-anon disk storage
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NEW, "na1", "na1", function(entry) {
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NORMAL, "na1", "na1", function(entry) {
+ // check the anon entry is still there and intact
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.anonymous,
+ new OpenCallback(NORMAL, "an1", "an1", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-22-anon-visit.js b/netwerk/test/unit/test_cache2-22-anon-visit.js
new file mode 100644
index 000000000..bc8b15822
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-22-anon-visit.js
@@ -0,0 +1,58 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ function checkNewBackEnd()
+ {
+ var storage = getCacheStorage("disk", LoadContextInfo.default);
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 1024, ["http://an2/"], function() {
+ storage = getCacheStorage("disk", LoadContextInfo.anonymous);
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 1024, ["http://an2/"], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ }
+
+ function checkOldBackEnd()
+ {
+ syncWithCacheIOThread(function() {
+ var storage = getCacheStorage("disk", LoadContextInfo.default);
+ storage.asyncVisitStorage(
+ new VisitCallback(2, 24, ["http://an2/"], function() {
+ storage = getCacheStorage("disk", LoadContextInfo.anonymous);
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, ["http://an2/"], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ });
+ }
+
+ var mc = new MultipleCallbacks(2, newCacheBackEndUsed() ? checkNewBackEnd : checkOldBackEnd, !newCacheBackEndUsed());
+
+ asyncOpenCacheEntry("http://an2/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NEW|WAITFORWRITE, "an2", "an2", function(entry) {
+ mc.fired();
+ })
+ );
+
+ asyncOpenCacheEntry("http://an2/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.anonymous,
+ new OpenCallback(NEW|WAITFORWRITE, "an2", "an2", function(entry) {
+ mc.fired();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-23-read-over-chunk.js b/netwerk/test/unit/test_cache2-23-read-over-chunk.js
new file mode 100644
index 000000000..92959645d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-23-read-over-chunk.js
@@ -0,0 +1,35 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ const kChunkSize = (256 * 1024);
+
+ var payload = "";
+ for (var i = 0; i < (kChunkSize + 10); ++i) {
+ if (i < (kChunkSize - 5))
+ payload += "0";
+ else
+ payload += String.fromCharCode(i + 65);
+ }
+
+ asyncOpenCacheEntry("http://read/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, LoadContextInfo.default,
+ new OpenCallback(NEW|WAITFORWRITE, "", payload, function(entry) {
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function(read) {
+ do_check_eq(read.length, kChunkSize + 10);
+ is.close();
+ do_check_true(read == payload); // not using do_check_eq since logger will fail for the 1/4MB string
+ finish_cache2_test();
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-24-exists.js b/netwerk/test/unit/test_cache2-24-exists.js
new file mode 100644
index 000000000..fee8a2ee7
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-24-exists.js
@@ -0,0 +1,38 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ var mc = new MultipleCallbacks(2, function() {
+ var mem = getCacheStorage("memory");
+ var disk = getCacheStorage("disk");
+
+ do_check_true(disk.exists(createURI("http://m1/"), ""));
+ do_check_true(mem.exists(createURI("http://m1/"), ""));
+ do_check_false(mem.exists(createURI("http://m2/"), ""));
+ do_check_true(disk.exists(createURI("http://d1/"), ""));
+ do_check_throws_nsIException(() => disk.exists(createURI("http://d2/"), ""), 'NS_ERROR_NOT_AVAILABLE');
+
+ finish_cache2_test();
+ });
+
+ asyncOpenCacheEntry("http://d1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "meta", "data", function(entry) {
+ mc.fired();
+ })
+ );
+
+ asyncOpenCacheEntry("http://m1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "meta", "data", function(entry) {
+ mc.fired();
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js b/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
new file mode 100644
index 000000000..0999dc8d2
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
@@ -0,0 +1,51 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function gen_200k()
+{
+ var i;
+ var data="0123456789ABCDEFGHIJLKMNO";
+ for (i=0; i<13; i++)
+ data+=data;
+ return data;
+}
+
+// Keep the output stream of the first entry in a global variable, so the
+// CacheFile and its buffer isn't released before we write the data to the
+// second entry.
+var oStr;
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+ // set max chunks memory so that only one full chunk fits within the limit
+ prefBranch.setIntPref("browser.cache.disk.max_chunks_memory_usage", 300);
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ oStr = entry.openOutputStream(0);
+ var data = gen_200k();
+ do_check_eq(data.length, oStr.write(data, data.length));
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var oStr2 = entry.openOutputStream(0);
+ do_check_throws_nsIException(() => oStr2.write(data, data.length), 'NS_ERROR_OUT_OF_MEMORY');
+ finish_cache2_test();
+ }
+ );
+ }
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-26-no-outputstream-open.js b/netwerk/test/unit/test_cache2-26-no-outputstream-open.js
new file mode 100644
index 000000000..f54bc19a5
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-26-no-outputstream-open.js
@@ -0,0 +1,27 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Open for write, but never write and never mark valid
+ asyncOpenCacheEntry("http://no-data/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW|METAONLY|DONTSETVALID|WAITFORWRITE, "meta", "", function(entry) {
+ // Open again, we must get the callback and zero-length data
+ do_execute_soon(() => {
+ Cu.forceGC(); // invokes OnHandleClosed on the entry
+
+ asyncOpenCacheEntry("http://no-data/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "meta", "", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-27-force-valid-for.js b/netwerk/test/unit/test_cache2-27-force-valid-for.js
new file mode 100644
index 000000000..c3663751d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-27-force-valid-for.js
@@ -0,0 +1,37 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ var mc = new MultipleCallbacks(2, function() {
+ finish_cache2_test();
+ });
+
+ asyncOpenCacheEntry("http://m1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NEW, "meta", "data", function(entry) {
+ // Check the default
+ equal(entry.isForcedValid, false);
+
+ // Forced valid and confirm
+ entry.forceValidFor(2);
+ do_timeout(1000, function() {
+ equal(entry.isForcedValid, true);
+ mc.fired();
+ });
+
+ // Confirm the timeout occurs
+ do_timeout(3000, function() {
+ equal(entry.isForcedValid, false);
+ mc.fired();
+ });
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-28-last-access-attrs.js b/netwerk/test/unit/test_cache2-28-last-access-attrs.js
new file mode 100644
index 000000000..b8d93dc44
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-28-last-access-attrs.js
@@ -0,0 +1,39 @@
+function run_test()
+{
+ do_get_profile();
+ function NowSeconds() {
+ return parseInt((new Date()).getTime() / 1000);
+ }
+ function do_check_time(t, min, max) {
+ do_check_true(t >= min);
+ do_check_true(t <= max);
+ }
+
+ var timeStart = NowSeconds();
+
+ asyncOpenCacheEntry("http://t/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "m", "d", function(entry) {
+
+ var firstOpen = NowSeconds();
+ do_check_eq(entry.fetchCount, 1);
+ do_check_time(entry.lastFetched, timeStart, firstOpen);
+ do_check_time(entry.lastModified, timeStart, firstOpen);
+
+ do_timeout(2000, () => {
+ asyncOpenCacheEntry("http://t/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "m", "d", function(entry) {
+
+ var secondOpen = NowSeconds();
+ do_check_eq(entry.fetchCount, 2);
+ do_check_time(entry.lastFetched, firstOpen, secondOpen);
+ do_check_time(entry.lastModified, timeStart, firstOpen);
+
+ finish_cache2_test();
+ })
+ );
+ })
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js b/netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js
new file mode 100644
index 000000000..fdc66e10f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js
@@ -0,0 +1,34 @@
+function run_test()
+{
+ do_get_profile();
+ function NowSeconds() {
+ return parseInt((new Date()).getTime() / 1000);
+ }
+ function do_check_time(a, b) {
+ do_check_true(Math.abs(a - b) < 0.5);
+ }
+
+ asyncOpenCacheEntry("http://t/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "m", "d", function(entry) {
+
+ var now1 = NowSeconds();
+ do_check_eq(entry.fetchCount, 1);
+ do_check_time(entry.lastFetched, now1);
+ do_check_time(entry.lastModified, now1);
+
+ do_timeout(2000, () => {
+ asyncOpenCacheEntry("http://t/", "disk", Ci.nsICacheStorage.OPEN_SECRETLY, null,
+ new OpenCallback(NORMAL, "m", "d", function(entry) {
+ do_check_eq(entry.fetchCount, 1);
+ do_check_time(entry.lastFetched, now1);
+ do_check_time(entry.lastModified, now1);
+
+ finish_cache2_test();
+ })
+ );
+ })
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js b/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
new file mode 100644
index 000000000..d291b5f66
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
@@ -0,0 +1,72 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits
+This test is using a resumable response.
+- with a profile, set max-entry-size to 0
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry, it's doomed
+- second channel now must engage interrupted concurrent write algorithm and read the content again from the network
+- both channels must deliver full content w/o errors
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ if (metadata.hasHeader("If-Range")) {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "0-12/13");
+ }
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test()
+{
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 0);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js b/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
new file mode 100644
index 000000000..67f6467bb
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
@@ -0,0 +1,71 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This test is using a non-resumable response.
+- with a profile, set max-entry-size to 0
+- first channel makes a request for a non-resumable (chunked) response
+- second channel makes a request for the same resource, concurrent read is bypassed (non-resumable response)
+- first channel writes first bytes to the cache output stream, but that fails because of the max-entry-size limit and entry is doomed
+- cache entry output stream is closed
+- second channel gets the entry, opening the input stream must fail
+- second channel must read the content again from the network
+- both channels must deliver full content w/o errors
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "c\r\ndata reached\r\n3\r\nhej\r\n0\r\n\r\n";
+const responseBodyDecoded = "data reachedhej";
+
+function contentHandler(metadata, response)
+{
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(responseBody);
+ response.finish();
+}
+
+function run_test()
+{
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 0);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null, CL_ALLOW_UNKNOWN_CL));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null, CL_ALLOW_UNKNOWN_CL));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBodyDecoded);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBodyDecoded);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js b/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js
new file mode 100644
index 000000000..f82d685e1
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js
@@ -0,0 +1,91 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29a test, this test checks that cocurrency is resumed when the first channel is interrupted
+in the middle of reading and the second channel already consumed some content from the cache entry.
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- both channels must deliver full content w/o errors
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ if (metadata.hasHeader("If-Range")) {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+
+ let len = responseBody.length;
+ response.setHeader("Content-Range", "0-" + (len - 1) + "/" + len);
+ }
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test()
+{
+ // Static check
+ do_check_true(responseBody.length > 1024);
+
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js b/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js
new file mode 100644
index 000000000..a5461d854
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js
@@ -0,0 +1,95 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29c test, this test checks that a corrupted 206 response is correctly handled (no crashes or asserion failures)
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- the response to the range request is broken (bad Content-Range header)
+- the first must deliver full content w/o errors
+- the second channel must correctly fail
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ if (metadata.hasHeader("If-Range")) {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ // Deliberately broken response header to trigger corrupted content error on the second channel
+ response.setHeader("Content-Range", "0-1/2");
+ }
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test()
+{
+ // Static check
+ do_check_true(responseBody.length > 1024);
+
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null, CL_EXPECT_FAILURE));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js b/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js
new file mode 100644
index 000000000..dc0190eca
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js
@@ -0,0 +1,90 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29c test, this test checks that a corrupted 206 response is correctly handled (no crashes or asserion failures)
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- the response to the range request is plain 200
+- the first must deliver full content w/o errors
+- the second channel must correctly fail
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+// need something bigger than 1024 bytes
+const responseBody =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=99999");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + responseBody.length);
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test()
+{
+ // Static check
+ do_check_true(responseBody.length > 1024);
+
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null, CL_EXPECT_FAILURE));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache2-30a-entry-pinning.js b/netwerk/test/unit/test_cache2-30a-entry-pinning.js
new file mode 100644
index 000000000..3a5db421a
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30a-entry-pinning.js
@@ -0,0 +1,32 @@
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, LoadContextInfo.default,
+ new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+
+ // Now clear the whole cache
+ get_cache_service().clear();
+
+ // The pinned entry should be intact
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js b/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
new file mode 100644
index 000000000..a9410ff75
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
@@ -0,0 +1,38 @@
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+ var lci = LoadContextInfo.default;
+
+ // Open a pinned entry for write, write
+ asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+ new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+
+ // Now clear the disk storage, that should leave the pinned entry in the cache
+ var diskStorage = getCacheStorage("disk", lci);
+ diskStorage.asyncEvictStorage(null);
+
+ // Open for read and check, it should still be there
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+
+ // Now clear the pinning storage, entry should be gone
+ var pinningStorage = getCacheStorage("pin", lci);
+ pinningStorage.asyncEvictStorage(null);
+
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(NEW, "", "", function(entry) {
+ finish_cache2_test();
+ })
+ );
+
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
new file mode 100644
index 000000000..91c621ce5
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
@@ -0,0 +1,134 @@
+/*
+
+This is a complex test checking the internal "deferred doom" functionality in both CacheEntry and CacheFileHandle.
+
+- We create a batch of 10 non-pinned and 10 pinned entries, write something to them.
+- Then we purge them from memory, so they have to reload from disk.
+- After that the IO thread is suspended not to process events on the READ (3) level. This forces opening operation and eviction
+ sync operations happen before we know actual pinning status of already cached entries.
+- We async-open the same batch of the 10+10 entries again, all should open as existing with the expected, previously stored
+ content
+- After all these entries are made to open, we clear the cache. This does some synchronous operations on the entries
+ being open and also on the handles being in an already open state (but before the entry metadata has started to be read.)
+ Expected is to leave the pinned entries only.
+- Now, we resume the IO thread, so it start reading. One could say this is a hack, but this can very well happen in reality
+ on slow disk or when a large number of entries is about to be open at once. Suspending the IO thread is just doing this
+ simulation is a fully deterministic way and actually very easily and elegantly.
+- After the resume we want to open all those 10+10 entries once again (no purgin involved this time.). It is expected
+ to open all the pinning entries intact and loose all the non-pinned entries (get them as new and empty again.)
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ var lci = LoadContextInfo.default;
+ var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+ do_check_true(testingInterface);
+
+ var mc = new MultipleCallbacks(1, function() {
+ // (2)
+
+ mc = new MultipleCallbacks(1, finish_cache2_test);
+ // Release all references to cache entries so that they can be purged
+ // Calling gc() four times is needed to force it to actually release
+ // entries that are obviously unreferenced. Yeah, I know, this is wacky...
+ gc();
+ gc();
+ do_execute_soon(() => {
+ gc();
+ gc();
+ log_("purging");
+
+ // Invokes cacheservice:purge-memory-pools when done.
+ get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
+ });
+ }, true);
+
+ // (1), here we start
+
+ var i;
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ log_("first set of opens");
+
+ // Callbacks 1-20
+ mc.add();
+ asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+ new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+ mc.add();
+ asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+ new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+ }
+
+ mc.fired(); // Goes to (2)
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ os.addObserver({
+ observe: function(subject, topic, data)
+ {
+ // (3)
+
+ log_("after purge, second set of opens");
+ // Prevent the I/O thread from reading the data. We first want to schedule clear of the cache.
+ // This deterministically emulates a slow hard drive.
+ testingInterface.suspendCacheIOThread(3);
+
+ // All entries should load
+ // Callbacks 21-40
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ mc.add();
+ asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+ // Unfortunately we cannot ensure that entries existing in the cache will be delivered to the consumer
+ // when soon after are evicted by some cache API call. It's better to not ensure getting an entry
+ // than allowing to get an entry that was just evicted from the cache. Entries may be delievered
+ // as new, but are already doomed. Output stream cannot be openned, or the file handle is already
+ // writing to a doomed file.
+ //
+ // The API now just ensures that entries removed by any of the cache eviction APIs are never more
+ // available to consumers.
+ mc.add();
+ asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(MAYBE_NEW|DOOMED, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+ }
+
+ log_("clearing");
+ // Now clear everything except pinned, all entries are in state of reading
+ get_cache_service().clear();
+ log_("cleared");
+
+ // Resume reading the cache data, only now the pinning status on entries will be discovered,
+ // the deferred dooming code will trigger.
+ testingInterface.resumeCacheIOThread();
+
+ log_("third set of opens");
+ // Now open again. Pinned entries should be there, disk entries should be the renewed entries.
+ // Callbacks 41-60
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ mc.add();
+ asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+ mc.add();
+ asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
+ }
+
+ mc.fired(); // Finishes this test
+ }
+ }, "cacheservice:purge-memory-pools", false);
+
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
new file mode 100644
index 000000000..07105d535
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
@@ -0,0 +1,113 @@
+/*
+
+This test exercises the CacheFileContextEvictor::WasEvicted API and code using it.
+
+- We store 10+10 (pinned and non-pinned) entries to the cache, wait for them being written.
+- Then we purge the memory pools.
+- Now the IO thread is suspended on the EVICT (7) level to prevent actual deletion of the files.
+- Index is disabled.
+- We do clear() of the cache, this creates the "ce_*" file and posts to the EVICT level
+ the eviction loop mechanics.
+- We open again those 10+10 entries previously stored.
+- IO is resumed
+- We expect to get all the pinned and
+ loose all the non-pinned (common) entries.
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+ var lci = LoadContextInfo.default;
+ var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+ do_check_true(testingInterface);
+
+ var mc = new MultipleCallbacks(1, function() {
+ // (2)
+
+ mc = new MultipleCallbacks(1, finish_cache2_test);
+ // Release all references to cache entries so that they can be purged
+ // Calling gc() four times is needed to force it to actually release
+ // entries that are obviously unreferenced. Yeah, I know, this is wacky...
+ gc();
+ gc();
+ do_execute_soon(() => {
+ gc();
+ gc();
+ log_("purging");
+
+ // Invokes cacheservice:purge-memory-pools when done.
+ get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
+ });
+ }, true);
+
+ // (1), here we start
+
+ log_("first set of opens");
+ var i;
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+
+ // Callbacks 1-20
+ mc.add();
+ asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+ new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+ mc.add();
+ asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+ new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+ }
+
+ mc.fired(); // Goes to (2)
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ os.addObserver({
+ observe: function(subject, topic, data)
+ {
+ // (3)
+
+ log_("after purge");
+ // Prevent the I/O thread from evicting physically the data. We first want to re-open the entries.
+ // This deterministically emulates a slow hard drive.
+ testingInterface.suspendCacheIOThread(7);
+
+ log_("clearing");
+ // Now clear everything except pinned. Stores the "ce_*" file and schedules background eviction.
+ get_cache_service().clear();
+ log_("cleared");
+
+ log_("second set of opens");
+ // Now open again. Pinned entries should be there, disk entries should be the renewed entries.
+ // Callbacks 21-40
+ for (i = 0; i < kENTRYCOUNT; ++i) {
+ mc.add();
+ asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+ mc.add();
+ asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+ new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
+ }
+
+ // Resume IO, this will just pop-off the CacheFileContextEvictor::EvictEntries() because of
+ // an early check on CacheIOThread::YieldAndRerun() in that method.
+ // CacheFileIOManager::OpenFileInternal should now run and CacheFileContextEvictor::WasEvicted
+ // should be checked on.
+ log_("resuming");
+ testingInterface.resumeCacheIOThread();
+ log_("resumed");
+
+ mc.fired(); // Finishes this test
+ }
+ }, "cacheservice:purge-memory-pools", false);
+
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cacheForOfflineUse_no-store.js b/netwerk/test/unit/test_cacheForOfflineUse_no-store.js
new file mode 100644
index 000000000..8a49242ee
--- /dev/null
+++ b/netwerk/test/unit/test_cacheForOfflineUse_no-store.js
@@ -0,0 +1,93 @@
+"use strict";
+// https://bugzilla.mozilla.org/show_bug.cgi?id=760955
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+const testFileName = "test_nsHttpChannel_CacheForOfflineUse-no-store";
+const cacheClientID = testFileName + "|fake-group-id";
+const basePath = "/" + testFileName + "/";
+
+XPCOMUtils.defineLazyGetter(this, "baseURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + basePath;
+});
+
+const normalEntry = "normal";
+const noStoreEntry = "no-store";
+
+var cacheUpdateObserver = null;
+var appCache = null;
+
+function make_channel_for_offline_use(url, callback, ctx) {
+ var chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ appCache = cacheService.getApplicationCache(cacheClientID);
+
+ var appCacheChan = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ appCacheChan.applicationCacheForWrite = appCache;
+ return chan;
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+const responseBody = "response body";
+
+// A HTTP channel for updating the offline cache should normally succeed.
+function normalHandler(metadata, response)
+{
+ do_print("normalHandler");
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+function checkNormal(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ asyncCheckCacheEntryPresence(baseURI + normalEntry, "appcache", true, run_next_test, appCache);
+}
+add_test(function test_normal() {
+ var chan = make_channel_for_offline_use(baseURI + normalEntry);
+ chan.asyncOpen2(new ChannelListener(checkNormal, chan));
+});
+
+// An HTTP channel for updating the offline cache should fail when it gets a
+// response with Cache-Control: no-store.
+function noStoreHandler(metadata, response)
+{
+ do_print("noStoreHandler");
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-store");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+function checkNoStore(request, buffer)
+{
+ do_check_eq(buffer, "");
+ asyncCheckCacheEntryPresence(baseURI + noStoreEntry, "appcache", false, run_next_test, appCache);
+}
+add_test(function test_noStore() {
+ var chan = make_channel_for_offline_use(baseURI + noStoreEntry);
+ // The no-store should cause the channel to fail to load.
+ chan.asyncOpen2(new ChannelListener(checkNoStore, chan, CL_EXPECT_FAILURE));
+});
+
+function run_test()
+{
+ do_get_profile();
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(basePath + normalEntry, normalHandler);
+ httpServer.registerPathHandler(basePath + noStoreEntry, noStoreHandler);
+ httpServer.start(-1);
+ run_next_test();
+}
+
+function finish_test(request, buffer)
+{
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_cache_jar.js b/netwerk/test/unit/test_cache_jar.js
new file mode 100644
index 000000000..126e811f8
--- /dev/null
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -0,0 +1,126 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort + "/cached";
+});
+
+var httpserv = null;
+var handlers_called = 0;
+
+function cached_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ var body = "0123456789";
+ response.bodyOutputStream.write(body, body.length);
+ handlers_called++;
+}
+
+function makeChan(url, appId, inIsolatedMozBrowser, userContextId) {
+ var chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+ chan.loadInfo.originAttributes = { appId: appId,
+ inIsolatedMozBrowser: inIsolatedMozBrowser,
+ userContextId: userContextId,
+ };
+ return chan;
+}
+
+// [appId, inIsolatedMozBrowser, userContextId, expected_handlers_called]
+var firstTests = [
+ [0, false, 0, 1], [0, true, 0, 1], [1, false, 0, 1], [1, true, 0, 1],
+ [0, false, 1, 1], [0, true, 1, 1], [1, false, 1, 1], [1, true, 1, 1]
+];
+var secondTests = [
+ [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 0], [1, true, 0, 1],
+ [0, false, 1, 0], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+var thirdTests = [
+ [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 1], [1, true, 0, 1],
+ [0, false, 1, 0], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+var fourthTests = [
+ [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 0], [1, true, 0, 0],
+ [0, false, 1, 1], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+
+function run_all_tests() {
+ for (let test of firstTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+
+ // We can't easily cause webapp data to be cleared from the child process, so skip
+ // the rest of these tests.
+ let procType = Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).processType;
+ if (procType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
+ return;
+
+ let attrs_inBrowser = JSON.stringify({ appId:1, inIsolatedMozBrowser:true });
+ let attrs_notInBrowser = JSON.stringify({ appId:1 });
+
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_inBrowser);
+
+ for (let test of secondTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_notInBrowser);
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_inBrowser);
+
+ for (let test of thirdTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+
+ let attrs_userContextId = JSON.stringify({ userContextId: 1 });
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_userContextId);
+
+ for (let test of fourthTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+}
+
+var gTests;
+function run_test() {
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+ do_test_pending();
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/cached", cached_handler);
+ httpserv.start(-1);
+ gTests = run_all_tests();
+ gTests.next();
+}
+
+function doneFirstLoad(req, buffer, expected) {
+ // Load it again, make sure it hits the cache
+ var oa = req.loadInfo.originAttributes;
+ var chan = makeChan(URL, oa.appId, oa.isInIsolatedMozBrowserElement, oa.userContextId);
+ chan.asyncOpen2(new ChannelListener(doneSecondLoad, expected));
+}
+
+function doneSecondLoad(req, buffer, expected) {
+ do_check_eq(handlers_called, expected);
+ try {
+ gTests.next();
+ } catch (x) {
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/unit/test_cacheflags.js b/netwerk/test/unit/test_cacheflags.js
new file mode 100644
index 000000000..28c24b14c
--- /dev/null
+++ b/netwerk/test/unit/test_cacheflags.js
@@ -0,0 +1,370 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+
+// Need to randomize, because apparently no one clears our cache
+var suffix = Math.random();
+var httpBase = "http://localhost:" + httpserver.identity.primaryPort;
+var httpsBase = "http://localhost:4445";
+var shortexpPath = "/shortexp" + suffix;
+var longexpPath = "/longexp/" + suffix;
+var longexp2Path = "/longexp/2/" + suffix;
+var nocachePath = "/nocache" + suffix;
+var nostorePath = "/nostore" + suffix;
+var test410Path = "/test410" + suffix;
+var test404Path = "/test404" + suffix;
+
+// We attach this to channel when we want to test Private Browsing mode
+function LoadContext(usePrivateBrowsing) {
+ this.usePrivateBrowsing = usePrivateBrowsing;
+ this.originAttributes.privateBrowsingId = usePrivateBrowsing ? 1 : 0;
+}
+
+LoadContext.prototype = {
+ originAttributes: {
+ privateBrowsingId : 0
+ },
+ usePrivateBrowsing: false,
+ // don't bother defining rest of nsILoadContext fields: don't need 'em
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsILoadContext))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function(iid) {
+ if (iid.equals(Ci.nsILoadContext))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+};
+
+var PrivateBrowsingLoadContext = new LoadContext(true);
+
+function make_channel(url, flags, usePrivateBrowsing) {
+ var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+
+ var uri = Services.io.newURI(url, null, null);
+ var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri,
+ { privateBrowsingId : usePrivateBrowsing ? 1 : 0 });
+
+ var req = NetUtil.newChannel({uri: uri,
+ loadingPrincipal: principal,
+ securityFlags: securityFlags,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER});
+
+ req.loadFlags = flags;
+ if (usePrivateBrowsing) {
+ req.notificationCallbacks = PrivateBrowsingLoadContext;
+ }
+ return req;
+}
+
+function Test(path, flags, expectSuccess, readFromCache, hitServer,
+ usePrivateBrowsing /* defaults to false */) {
+ this.path = path;
+ this.flags = flags;
+ this.expectSuccess = expectSuccess;
+ this.readFromCache = readFromCache;
+ this.hitServer = hitServer;
+ this.usePrivateBrowsing = usePrivateBrowsing;
+}
+
+Test.prototype = {
+ flags: 0,
+ expectSuccess: true,
+ readFromCache: false,
+ hitServer: true,
+ usePrivateBrowsing: false,
+ _buffer: "",
+ _isFromCache: false,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ var cachingChannel = request.QueryInterface(Ci.nsICacheInfoChannel);
+ this._isFromCache = request.isPending() && cachingChannel.isFromCache();
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(Components.isSuccessCode(status), this.expectSuccess);
+ do_check_eq(this._isFromCache, this.readFromCache);
+ do_check_eq(gHitServer, this.hitServer);
+
+ do_timeout(0, run_next_test);
+ },
+
+ run: function() {
+ dump("Running:" +
+ "\n " + this.path +
+ "\n " + this.flags +
+ "\n " + this.expectSuccess +
+ "\n " + this.readFromCache +
+ "\n " + this.hitServer + "\n");
+ gHitServer = false;
+ var channel = make_channel(this.path, this.flags, this.usePrivateBrowsing);
+ channel.asyncOpen2(this);
+ }
+};
+
+var gHitServer = false;
+
+var gTests = [
+
+ new Test(httpBase + shortexpPath, 0,
+ true, // expect success
+ false, // read from cache
+ true, // hit server
+ true), // USE PRIVATE BROWSING, so not cached for later requests
+ new Test(httpBase + shortexpPath, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + shortexpPath, 0,
+ true, // expect success
+ true, // read from cache
+ true), // hit server
+ new Test(httpBase + shortexpPath, Ci.nsIRequest.LOAD_BYPASS_CACHE,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + shortexpPath, Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ false, // expect success
+ false, // read from cache
+ false), // hit server
+ new Test(httpBase + shortexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsIRequest.VALIDATE_NEVER,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+ new Test(httpBase + shortexpPath, Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+
+ new Test(httpBase + longexpPath, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + longexpPath, 0,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+ new Test(httpBase + longexpPath, Ci.nsIRequest.LOAD_BYPASS_CACHE,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + longexpPath,
+ Ci.nsIRequest.VALIDATE_ALWAYS,
+ true, // expect success
+ true, // read from cache
+ true), // hit server
+ new Test(httpBase + longexpPath, Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+ new Test(httpBase + longexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsIRequest.VALIDATE_NEVER,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+ new Test(httpBase + longexpPath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsIRequest.VALIDATE_ALWAYS,
+ false, // expect success
+ false, // read from cache
+ false), // hit server
+ new Test(httpBase + longexpPath, Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+
+ new Test(httpBase + longexp2Path, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + longexp2Path, 0,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+
+ new Test(httpBase + nocachePath, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + nocachePath, 0,
+ true, // expect success
+ true, // read from cache
+ true), // hit server
+ new Test(httpBase + nocachePath, Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ false, // expect success
+ false, // read from cache
+ false), // hit server
+
+ // CACHE2: mayhemer - entry is doomed... I think the logic is wrong, we should not doom them
+ // as they are not valid, but take them as they need to reval
+ /*
+ new Test(httpBase + nocachePath, Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+ */
+
+ // LOAD_ONLY_FROM_CACHE would normally fail (because no-cache forces
+ // a validation), but VALIDATE_NEVER should override that.
+ new Test(httpBase + nocachePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsIRequest.VALIDATE_NEVER,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+
+ // ... however, no-cache over ssl should act like no-store and force
+ // a validation (and therefore failure) even if VALIDATE_NEVER is
+ // set.
+ /* XXX bug 466524: We can't currently start an ssl server in xpcshell tests,
+ so this test is currently disabled.
+ new Test(httpsBase + nocachePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsIRequest.VALIDATE_NEVER,
+ false, // expect success
+ false, // read from cache
+ false) // hit server
+ */
+
+ new Test(httpBase + nostorePath, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + nostorePath, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + nostorePath, Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
+ false, // expect success
+ false, // read from cache
+ false), // hit server
+ new Test(httpBase + nostorePath, Ci.nsIRequest.LOAD_FROM_CACHE,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+ // no-store should force the validation (and therefore failure, with
+ // LOAD_ONLY_FROM_CACHE) even if VALIDATE_NEVER is set.
+ new Test(httpBase + nostorePath,
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsIRequest.VALIDATE_NEVER,
+ false, // expect success
+ false, // read from cache
+ false), // hit server
+
+ new Test(httpBase + test410Path, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + test410Path, 0,
+ true, // expect success
+ true, // read from cache
+ false), // hit server
+
+ new Test(httpBase + test404Path, 0,
+ true, // expect success
+ false, // read from cache
+ true), // hit server
+ new Test(httpBase + test404Path, 0,
+ true, // expect success
+ false, // read from cache
+ true) // hit server
+];
+
+function run_next_test()
+{
+ if (gTests.length == 0) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+
+ var test = gTests.shift();
+ test.run();
+}
+
+function handler(httpStatus, metadata, response) {
+ gHitServer = true;
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch(ex) {
+ var etag = "";
+ }
+ if (etag == "testtag") {
+ // Allow using the cached data
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ } else {
+ response.setStatusLine(metadata.httpVersion, httpStatus, "Useless Phrase");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "testtag", false);
+ const body = "data";
+ response.bodyOutputStream.write(body, body.length);
+ }
+}
+
+function nocache_handler(metadata, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ handler(200, metadata, response);
+}
+
+function nostore_handler(metadata, response) {
+ response.setHeader("Cache-Control", "no-store", false);
+ handler(200, metadata, response);
+}
+
+function test410_handler(metadata, response) {
+ handler(410, metadata, response);
+}
+
+function test404_handler(metadata, response) {
+ handler(404, metadata, response);
+}
+
+function shortexp_handler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age=0", false);
+ handler(200, metadata, response);
+}
+
+function longexp_handler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ handler(200, metadata, response);
+}
+
+// test spaces around max-age value token
+function longexp2_handler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age = 10000", false);
+ handler(200, metadata, response);
+}
+
+function run_test() {
+ httpserver.registerPathHandler(shortexpPath, shortexp_handler);
+ httpserver.registerPathHandler(longexpPath, longexp_handler);
+ httpserver.registerPathHandler(longexp2Path, longexp2_handler);
+ httpserver.registerPathHandler(nocachePath, nocache_handler);
+ httpserver.registerPathHandler(nostorePath, nostore_handler);
+ httpserver.registerPathHandler(test410Path, test410_handler);
+ httpserver.registerPathHandler(test404Path, test404_handler);
+
+ run_next_test();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_channel_close.js b/netwerk/test/unit/test_channel_close.js
new file mode 100644
index 000000000..a7a90e04f
--- /dev/null
+++ b/netwerk/test/unit/test_channel_close.js
@@ -0,0 +1,59 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+var live_channels = [];
+
+function run_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ var local_channel;
+
+ // Opened channel that has no remaining references on shutdown
+ local_channel = setupChannel(testpath);
+ local_channel.asyncOpen2(new ChannelListener(checkRequest, local_channel));
+
+ // Opened channel that has no remaining references after being opened
+ setupChannel(testpath).asyncOpen2(new ChannelListener(function() {}, null));
+
+ // Unopened channel that has remaining references on shutdown
+ live_channels.push(setupChannel(testpath));
+
+ // Opened channel that has remaining references on shutdown
+ live_channels.push(setupChannel(testpath));
+ live_channels[1].asyncOpen2(new ChannelListener(checkRequestFinish, live_channels[1]));
+
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function checkRequest(request, data, context) {
+ do_check_eq(data, httpbody);
+}
+
+function checkRequestFinish(request, data, context) {
+ checkRequest(request, data, context);
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_chunked_responses.js b/netwerk/test/unit/test_chunked_responses.js
new file mode 100644
index 000000000..396e26614
--- /dev/null
+++ b/netwerk/test/unit/test_chunked_responses.js
@@ -0,0 +1,175 @@
+/*
+ * Test Chunked-Encoded response parsing.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var test_flags = new Array();
+var testPathBase = "/chunked_hdrs";
+
+function run_test()
+{
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests()
+{
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: FAIL because of overflowed chunked size. The parser uses long so
+// the test case uses >64bit to fail on all platforms.
+test_flags[1] = CL_EXPECT_LATE_FAILURE|CL_ALLOW_UNKNOWN_CL;
+
+function handler1(metadata, response)
+{
+ var body = "12345678123456789\r\ndata never reached";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest1(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNEXPECTED);
+
+ run_test_number(2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: FAIL because of non-hex in chunked length
+
+test_flags[2] = CL_EXPECT_LATE_FAILURE|CL_ALLOW_UNKNOWN_CL;
+
+function handler2(metadata, response)
+{
+ var body = "junkintheway 123\r\ndata never reached";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest2(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNEXPECTED);
+ run_test_number(3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: OK in spite of non-hex digits after size in the length field
+
+test_flags[3] = CL_ALLOW_UNKNOWN_CL;
+
+function handler3(metadata, response)
+{
+ var body = "c junkafter\r\ndata reached\r\n0\r\n\r\n";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest3(request, data, ctx)
+{
+ do_check_eq(request.status, 0);
+ run_test_number(4);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: Verify a fully compliant chunked response.
+
+test_flags[4] = CL_ALLOW_UNKNOWN_CL;
+
+function handler4(metadata, response)
+{
+ var body = "c\r\ndata reached\r\n3\r\nhej\r\n0\r\n\r\n";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest4(request, data, ctx)
+{
+ do_check_eq(request.status, 0);
+ run_test_number(5);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 5: A chunk size larger than 32 bit but smaller than 64bit also fails
+// This is probabaly subject to get improved at some point.
+
+test_flags[5] = CL_EXPECT_LATE_FAILURE|CL_ALLOW_UNKNOWN_CL;
+
+function handler5(metadata, response)
+{
+ var body = "123456781\r\ndata never reached";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Transfer-Encoding: chunked\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest5(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNEXPECTED);
+ endTests();
+// run_test_number(6);
+}
diff --git a/netwerk/test/unit/test_compareURIs.js b/netwerk/test/unit/test_compareURIs.js
new file mode 100644
index 000000000..8e68fc6a4
--- /dev/null
+++ b/netwerk/test/unit/test_compareURIs.js
@@ -0,0 +1,49 @@
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function do_info(text, stack) {
+ if (!stack)
+ stack = Components.stack.caller;
+
+ dump("TEST-INFO | " + stack.filename + " | [" + stack.name + " : " +
+ stack.lineNumber + "] " + text + "\n");
+}
+function run_test()
+{
+ var tests = [
+ [ "http://mozilla.org/", "http://mozilla.org/somewhere/there", true ],
+ [ "http://mozilla.org/", "http://www.mozilla.org/", false ],
+ [ "http://mozilla.org/", "http://mozilla.org:80", true ],
+ [ "http://mozilla.org/", "http://mozilla.org:90", false ],
+ [ "http://mozilla.org", "https://mozilla.org", false ],
+ [ "http://mozilla.org", "https://mozilla.org:80", false ],
+ [ "http://mozilla.org:443", "https://mozilla.org", false ],
+ [ "https://mozilla.org:443", "https://mozilla.org", true ],
+ [ "https://mozilla.org:443", "https://mozilla.org/somewhere/", true ],
+ [ "about:", "about:", false ],
+ [ "data:text/plain,text", "data:text/plain,text", false ],
+ [ "about:blank", "about:blank", false ],
+ [ "about:", "http://mozilla.org/", false ],
+ [ "about:", "about:config", false ],
+ [ "about:text/plain,text", "data:text/plain,text", false ],
+ [ "jar:http://mozilla.org/!/", "http://mozilla.org/", true ],
+ [ "view-source:http://mozilla.org/", "http://mozilla.org/", true ]
+ ];
+
+ var secman = Components.classes["@mozilla.org/scriptsecuritymanager;1"].getService(Components.interfaces.nsIScriptSecurityManager);
+
+ tests.forEach(function(aTest) {
+ do_info("Comparing " + aTest[0] + " to " + aTest[1]);
+
+ var uri1 = NetUtil.newURI(aTest[0]);
+ var uri2 = NetUtil.newURI(aTest[1]);
+
+ var equal;
+ try {
+ secman.checkSameOriginURI(uri1, uri2, false);
+ equal = true;
+ } catch (e) {
+ equal = false
+ }
+ do_check_eq(equal, aTest[2]);
+ });
+}
diff --git a/netwerk/test/unit/test_compressappend.js b/netwerk/test/unit/test_compressappend.js
new file mode 100644
index 000000000..275c94433
--- /dev/null
+++ b/netwerk/test/unit/test_compressappend.js
@@ -0,0 +1,80 @@
+//
+// Test that data can be appended to a cache entry even when the data is
+// compressed by the cache compression feature - bug 648429.
+//
+
+function write_and_check(str, data, len)
+{
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw("str.write has not written all data!\n" +
+ " Expected: " + len + "\n" +
+ " Actual: " + written + "\n");
+ }
+}
+
+function TestAppend(compress, callback)
+{
+ this._compress = compress;
+ this._callback = callback;
+ this.run();
+}
+
+TestAppend.prototype = {
+ _compress: false,
+ _callback: null,
+
+ run: function() {
+ evict_cache_entries();
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ this.writeData.bind(this));
+ },
+
+ writeData: function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ if (this._compress)
+ entry.setMetaDataElement("uncompressed-len", "0");
+ var os = entry.openOutputStream(0);
+ write_and_check(os, "12345", 5);
+ os.close();
+ entry.close();
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ this.appendData.bind(this));
+ },
+
+ appendData: function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.storageDataSize);
+ write_and_check(os, "abcde", 5);
+ os.close();
+ entry.close();
+
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ this.checkData.bind(this));
+ },
+
+ checkData: function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var self = this;
+ pumpReadStream(entry.openInputStream(0), function(str) {
+ do_check_eq(str.length, 10);
+ do_check_eq(str, "12345abcde");
+ entry.close();
+
+ do_execute_soon(self._callback);
+ });
+ }
+};
+
+function run_test() {
+ do_get_profile();
+ new TestAppend(false, run_test2);
+ do_test_pending();
+}
+
+function run_test2() {
+ new TestAppend(true, do_test_finished);
+}
diff --git a/netwerk/test/unit/test_content_encoding_gzip.js b/netwerk/test/unit/test_content_encoding_gzip.js
new file mode 100644
index 000000000..165080b43
--- /dev/null
+++ b/netwerk/test/unit/test_content_encoding_gzip.js
@@ -0,0 +1,114 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ {url: "/test/cegzip1",
+ flags: CL_EXPECT_GZIP,
+ ce: "gzip",
+ body: [
+ 0x1f, 0x8b, 0x08, 0x08, 0x5a, 0xa0, 0x31, 0x4f, 0x00, 0x03, 0x74, 0x78, 0x74, 0x00, 0x2b, 0xc9,
+ 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x92, 0xd4, 0xe2, 0x12, 0x43, 0x2e, 0x00, 0xb9, 0x23, 0xd7, 0x3b,
+ 0x0e, 0x00, 0x00, 0x00],
+ datalen: 14 // the data length of the uncompressed document
+ },
+
+ {url: "/test/cegzip2",
+ flags: CL_EXPECT_GZIP,
+ ce: "gzip, gzip",
+ body: [
+ 0x1f, 0x8b, 0x08, 0x00, 0x72, 0xa1, 0x31, 0x4f, 0x00, 0x03, 0x93, 0xef, 0xe6, 0xe0, 0x88, 0x5a,
+ 0x60, 0xe8, 0xcf, 0xc0, 0x5c, 0x52, 0x51, 0xc2, 0xa0, 0x7d, 0xf2, 0x84, 0x4e, 0x18, 0xc3, 0xa2,
+ 0x49, 0x57, 0x1e, 0x09, 0x39, 0xeb, 0x31, 0xec, 0x54, 0xbe, 0x6e, 0xcd, 0xc7, 0xc0, 0xc0, 0x00,
+ 0x00, 0x6e, 0x90, 0x7a, 0x85, 0x24, 0x00, 0x00, 0x00],
+ datalen: 14 // the data length of the uncompressed document
+ },
+
+ {url: "/test/cebrotli1",
+ flags: CL_EXPECT_GZIP,
+ ce: "br",
+ body: [0x0B, 0x02, 0x80, 0x74, 0x65, 0x73, 0x74, 0x0A, 0x03],
+
+ datalen: 5 // the data length of the uncompressed document
+ },
+
+ // this is not a brotli document
+ {url: "/test/cebrotli2",
+ flags: CL_EXPECT_GZIP | CL_EXPECT_FAILURE,
+ ce: "br",
+ body: [0x0B, 0x0A, 0x09],
+ datalen: 3
+ },
+
+ // this is brotli but should come through as identity due to prefs
+ {url: "/test/cebrotli3",
+ flags: 0,
+ ce: "br",
+ body: [0x0B, 0x02, 0x80, 0x74, 0x65, 0x73, 0x74, 0x0A, 0x03],
+
+ datalen: 9
+ },
+];
+
+function setupChannel(url) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + url,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function startIter() {
+ if (tests[index].url === "/test/cebrotli3") {
+ // this test wants to make sure we don't do brotli when not in a-e
+ prefs.setCharPref("network.http.accept-encoding", "gzip, deflate");
+ }
+ var channel = setupChannel(tests[index].url);
+ channel.asyncOpen2(new ChannelListener(completeIter, channel, tests[index].flags));
+}
+
+function completeIter(request, data, ctx) {
+ if (!(tests[index].flags & CL_EXPECT_FAILURE)) {
+ do_check_eq(data.length, tests[index].datalen);
+ }
+ if (++index < tests.length) {
+ startIter();
+ } else {
+ httpserver.stop(do_test_finished);
+ prefs.setCharPref("network.http.accept-encoding", cePref);
+ }
+}
+
+var prefs;
+var cePref;
+function run_test() {
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ cePref = prefs.getCharPref("network.http.accept-encoding");
+ prefs.setCharPref("network.http.accept-encoding", "gzip, deflate, br");
+
+ httpserver.registerPathHandler("/test/cegzip1", handler);
+ httpserver.registerPathHandler("/test/cegzip2", handler);
+ httpserver.registerPathHandler("/test/cebrotli1", handler);
+ httpserver.registerPathHandler("/test/cebrotli2", handler);
+ httpserver.registerPathHandler("/test/cebrotli3", handler);
+ httpserver.start(-1);
+
+ startIter();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", tests[index].ce, false);
+ response.setHeader("Content-Length", "" + tests[index].body.length, false);
+
+ var bos = Components.classes["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(tests[index].body, tests[index].body.length);
+ response.finish();
+}
+
diff --git a/netwerk/test/unit/test_content_length_underrun.js b/netwerk/test/unit/test_content_length_underrun.js
new file mode 100644
index 000000000..f9891a7d3
--- /dev/null
+++ b/netwerk/test/unit/test_content_length_underrun.js
@@ -0,0 +1,278 @@
+/*
+ * Test Content-Length underrun behavior
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var test_flags = new Array();
+var testPathBase = "/cl_hdrs";
+
+var prefs;
+var enforcePrefStrict;
+var enforcePrefSoft;
+
+function run_test()
+{
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ enforcePrefStrict = prefs.getBoolPref("network.http.enforce-framing.http1");
+ enforcePrefSoft = prefs.getBoolPref("network.http.enforce-framing.soft");
+ prefs.setBoolPref("network.http.enforce-framing.http1", true);
+
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function run_gzip_test(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+
+ function StreamListener() {}
+
+ StreamListener.prototype = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIStreamListener) ||
+ aIID.equals(Components.interfaces.nsIRequestObserver) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(aRequest, aContext) {},
+
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ // Make sure we catch the error NS_ERROR_NET_PARTIAL_TRANSFER here.
+ do_check_eq(aStatusCode, Components.results.NS_ERROR_NET_PARTIAL_TRANSFER);
+ // do_test_finished();
+ endTests();
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {}
+ };
+
+ let listener = new StreamListener();
+
+ channel.asyncOpen2(listener);
+
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests()
+{
+ // restore the prefs to pre-test values
+ prefs.setBoolPref("network.http.enforce-framing.http1", enforcePrefStrict);
+ prefs.setBoolPref("network.http.enforce-framing.soft", enforcePrefSoft);
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: FAIL because of Content-Length underrun with HTTP 1.1
+test_flags[1] = CL_EXPECT_LATE_FAILURE;
+
+function handler1(metadata, response)
+{
+ var body = "blablabla";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 556677\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest1(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_NET_PARTIAL_TRANSFER);
+
+ run_test_number(11);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 11: PASS because of Content-Length underrun with HTTP 1.1 but non 2xx
+test_flags[11] = CL_IGNORE_CL;
+
+function handler11(metadata, response)
+{
+ var body = "blablabla";
+
+ response.seizePower();
+ response.write("HTTP/1.1 404 NotOK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 556677\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest11(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ run_test_number(2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: Succeed because Content-Length underrun is with HTTP 1.0
+
+test_flags[2] = CL_IGNORE_CL;
+
+function handler2(metadata, response)
+{
+ var body = "short content";
+
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 12345678\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest2(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+
+ // test 3 requires the enforce-framing prefs to be false
+ prefs.setBoolPref("network.http.enforce-framing.http1", false);
+ prefs.setBoolPref("network.http.enforce-framing.soft", false);
+ run_test_number(3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: SUCCEED with bad Content-Length because pref allows it
+test_flags[3] = CL_IGNORE_CL;
+
+function handler3(metadata, response)
+{
+ var body = "blablabla";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 556677\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest3(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ prefs.setBoolPref("network.http.enforce-framing.soft", true);
+ run_test_number(4);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: Succeed because a cut off deflate stream can't be detected
+test_flags[4] = CL_IGNORE_CL;
+
+function handler4(metadata, response)
+{
+ // this is the beginning of a deflate compressed response body
+
+ var body = "\xcd\x57\xcd\x6e\x1b\x37\x10\xbe\x07\xc8\x3b\x0c\x36\x68\x72\xd1" +
+ "\xbf\x92\x22\xb1\x57\x0a\x64\x4b\x6a\x0c\x28\xb6\x61\xa9\x41\x73" +
+ "\x2a\xb8\xbb\x94\x44\x98\xfb\x03\x92\x92\xec\x06\x7d\x97\x1e\xeb" +
+ "\xbe\x86\x5e\xac\xc3\x25\x97\xa2\x64\xb9\x75\x0b\x14\xe8\x69\x87" +
+ "\x33\x9c\x1f\x7e\x33\x9c\xe1\x86\x9f\x66\x9f\x27\xfd\x97\x2f\x20" +
+ "\xfc\x34\x1a\x0c\x35\x01\xa1\x62\x8a\xd3\xfe\xf5\xcd\xd5\xe5\xd5" +
+ "\x6c\x54\x83\x49\xbe\x60\x31\xa3\x1c\x12\x0a\x0b\x2a\x15\xcb\x33" +
+ "\x4d\xae\x19\x05\x19\xe7\x9c\x30\x41\x1b\x61\xd3\x28\x95\xfa\x29" +
+ "\x55\x04\x32\x92\xd2\x5e\x90\x50\x19\x0b\x56\x68\x9d\x00\xe2\x3c" +
+ "\x53\x34\x53\xbd\xc0\x99\x56\xf9\x4a\x51\xe0\x64\xcf\x18\x24\x24" +
+ "\x93\xb0\xca\x40\xd2\x15\x07\x6e\xbd\x37\x60\x82\x3b\x8f\x86\x22" +
+ "\x21\xcb\x15\x95\x35\x20\x91\xa4\x59\xac\xa9\x62\x95\x31\xed\x14" +
+ "\xc9\x98\x2c\x19\x15\x3a\x62\x45\xef\x70\x1b\x50\x05\xa4\x28\xc4" +
+ "\xf6\x21\x66\xa4\xdc\x83\x32\x09\x85\xc8\xe7\x54\xa2\x4b\x81\x74" +
+ "\xbe\x12\xc0\x91\xb9\x7d\x50\x24\xe2\x0c\xd9\x29\x06\x2e\xdd\x79";
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 553677\r\n");
+ response.write("Content-Encoding: deflate\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest4(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+
+ prefs.setBoolPref("network.http.enforce-framing.http1", true);
+ run_gzip_test(99);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 99: FAIL because a cut off gzip stream CAN be detected
+
+// Note that test 99 here is run completely different than the other tests in
+// this file so if you add more tests here, consider adding them before this.
+
+function handler99(metadata, response)
+{
+ // this is the beginning of a gzip compressed response body
+
+ var body = "\x1f\x8b\x08\x00\x80\xb9\x25\x53\x00\x03\xd4\xd9\x79\xb8\x8e\xe5" +
+ "\xba\x00\xf0\x65\x19\x33\x24\x15\x29\xf3\x50\x52\xc6\xac\x85\x10" +
+ "\x8b\x12\x22\x45\xe6\xb6\x21\x9a\x96\x84\x4c\x69\x32\xec\x84\x92" +
+ "\xcc\x99\x6a\xd9\x32\xa5\xd0\x40\xd9\xc6\x14\x15\x95\x28\x62\x9b" +
+ "\x09\xc9\x70\x4a\x25\x53\xec\x8e\x9c\xe5\x1c\x9d\xeb\xfe\x9d\x73" +
+ "\x9d\x3f\xf6\x1f\xe7\xbd\xae\xcf\xf3\xbd\xbf\xef\x7e\x9f\xeb\x79" +
+ "\xef\xf7\x99\xde\xe5\xee\x6e\xdd\x3b\x75\xeb\xd1\xb5\x6c\xb3\xd4" +
+ "\x47\x1f\x48\xf8\x17\x1d\x15\xce\x1d\x55\x92\x93\xcf\x97\xe7\x8e" +
+ "\x8b\xca\xe4\xca\x55\x92\x2a\x54\x4e\x4e\x4e\x4a\xa8\x78\x53\xa5" +
+ "\x8a\x15\x2b\x55\x4a\xfa\xe3\x7b\x85\x8a\x37\x55\x48\xae\x92\x50" +
+ "\xb4\xc2\xbf\xaa\x41\x17\x1f\xbd\x7b\xf6\xba\xaf\x47\xd1\xa2\x09" +
+ "\x3d\xba\x75\xeb\xf5\x3f\xc5\xfd\x6f\xbf\xff\x3f\x3d\xfa\xd7\x6d" +
+ "\x74\x7b\x62\x86\x0c\xff\x79\x9e\x98\x50\x33\xe1\x8f\xb3\x01\xef" +
+ "\xb6\x38\x7f\x9e\x92\xee\xf9\xa7\xee\xcb\x74\x21\x26\x25\xa1\x6a" +
+ "\x42\xf6\x73\xff\x96\x4c\x28\x91\x90\xe5\xdc\x79\xa6\x8b\xe2\x52" +
+ "\xd2\xbf\x5d\x28\x2b\x24\x26\xfc\xa9\xcc\x96\x1e\x97\x31\xfd\xba" +
+ "\xee\xe9\xde\x3d\x31\xe5\x4f\x65\xc1\xf4\xb8\x0b\x65\x86\x8b\xca";
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 553677\r\n");
+ response.write("Content-Encoding: gzip\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_content_sniffer.js b/netwerk/test/unit/test_content_sniffer.js
new file mode 100644
index 000000000..c422e0497
--- /dev/null
+++ b/netwerk/test/unit/test_content_sniffer.js
@@ -0,0 +1,131 @@
+// This file tests nsIContentSniffer, introduced in bug 324985
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const unknownType = "application/x-unknown-content-type";
+const sniffedType = "application/x-sniffed";
+
+const snifferCID = Components.ID("{4c93d2db-8a56-48d7-b261-9cf2a8d998eb}");
+const snifferContract = "@mozilla.org/network/unittest/contentsniffer;1";
+const categoryName = "net-content-sniffers";
+
+var sniffing_enabled = true;
+
+/**
+ * This object is both a factory and an nsIContentSniffer implementation (so, it
+ * is de-facto a service)
+ */
+var sniffer = {
+ QueryInterface: function sniffer_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIFactory) ||
+ iid.equals(Components.interfaces.nsIContentSniffer))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ createInstance: function sniffer_ci(outer, iid) {
+ if (outer)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+ lockFactory: function sniffer_lockf(lock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ getMIMETypeFromContent: function (request, data, length) {
+ return sniffedType;
+ }
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ var chan = request.QueryInterface(Components.interfaces.nsIChannel);
+ if (chan.contentType == unknownType)
+ do_throw("Type should not be unknown!");
+ if (sniffing_enabled && this._iteration > 2 &&
+ chan.contentType != sniffedType) {
+ do_throw("Expecting <" + sniffedType +"> but got <" +
+ chan.contentType + "> for " + chan.URI.spec);
+ } else if (!sniffing_enabled && chan.contentType == sniffedType) {
+ do_throw("Sniffing not enabled but sniffer called for " + chan.URI.spec);
+ }
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ run_test_iteration(this._iteration);
+ do_test_finished();
+ },
+
+ _iteration: 1
+};
+
+function makeChan(url) {
+ var chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true});
+ if (sniffing_enabled)
+ chan.loadFlags |= Components.interfaces.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ return chan;
+}
+
+var httpserv = null;
+var urls = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.start(-1);
+
+ urls = [
+ // NOTE: First URL here runs without our content sniffer
+ "data:" + unknownType + ", Some text",
+ "data:" + unknownType + ", Text", // Make sure sniffing works even if we
+ // used the unknown content sniffer too
+ "data:text/plain, Some more text",
+ "http://localhost:" + httpserv.identity.primaryPort
+];
+
+ Components.manager.nsIComponentRegistrar.registerFactory(snifferCID,
+ "Unit test content sniffer", snifferContract, sniffer);
+
+ run_test_iteration(1);
+}
+
+function run_test_iteration(index) {
+ if (index > urls.length) {
+ if (sniffing_enabled) {
+ sniffing_enabled = false;
+ index = listener._iteration = 1;
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ return; // we're done
+ }
+ }
+
+ if (sniffing_enabled && index == 2) {
+ // Register our sniffer only here
+ // This also makes sure that dynamic registration is working
+ var catMan = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+ catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test",
+ snifferContract, false, true);
+ }
+
+ var chan = makeChan(urls[index - 1]);
+
+ listener._iteration++;
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_cookie_blacklist.js b/netwerk/test/unit/test_cookie_blacklist.js
new file mode 100644
index 000000000..d9ef2922a
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_blacklist.js
@@ -0,0 +1,19 @@
+const GOOD_COOKIE = "GoodCookie=OMNOMNOM";
+const SPACEY_COOKIE = "Spacey Cookie=Major Tom";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var cookieURI = ios.newURI("http://mozilla.org/test_cookie_blacklist.js",
+ null, null);
+
+ var cookieService = Cc["@mozilla.org/cookieService;1"]
+ .getService(Ci.nsICookieService);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie1=\x01", null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie2=\v", null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "Bad\x07Name=illegal", null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, GOOD_COOKIE, null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, SPACEY_COOKIE, null, null);
+
+ var storedCookie = cookieService.getCookieString(cookieURI, null);
+ do_check_eq(storedCookie, GOOD_COOKIE + "; " + SPACEY_COOKIE);
+}
diff --git a/netwerk/test/unit/test_cookie_header.js b/netwerk/test/unit/test_cookie_header.js
new file mode 100644
index 000000000..e3ac76d93
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_header.js
@@ -0,0 +1,100 @@
+// This file tests bug 250375
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort + "/";
+});
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function check_request_header(chan, name, value) {
+ var chanValue;
+ try {
+ chanValue = chan.getRequestHeader(name);
+ } catch (e) {
+ do_throw("Expected to find header '" + name + "' but didn't find it, got exception: " + e);
+ }
+ dump("Value for header '" + name + "' is '" + chanValue + "'\n");
+ do_check_eq(chanValue, value);
+}
+
+var cookieVal = "C1=V1";
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ var chan = request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ check_request_header(chan, "Cookie", cookieVal);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ if (this._iteration == 1) {
+ run_test_continued();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+ do_test_finished();
+ },
+
+ _iteration: 1
+};
+
+function makeChan() {
+ return NetUtil.newChannel({uri: URL, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+}
+
+var httpserv = null;
+
+function run_test() {
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess())
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ httpserv = new HttpServer();
+ httpserv.start(-1);
+
+ var chan = makeChan();
+
+ chan.setRequestHeader("Cookie", cookieVal, false);
+
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function run_test_continued() {
+ var chan = makeChan();
+
+ var cookServ = Components.classes["@mozilla.org/cookieService;1"]
+ .getService(Components.interfaces.nsICookieService);
+ var cookie2 = "C2=V2";
+ cookServ.setCookieString(chan.URI, null, cookie2, chan);
+ chan.setRequestHeader("Cookie", cookieVal, false);
+
+ // We expect that the setRequestHeader overrides the
+ // automatically-added one, so insert cookie2 in front
+ cookieVal = cookie2 + "; " + cookieVal;
+
+ listener._iteration++;
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cookiejars.js b/netwerk/test/unit/test_cookiejars.js
new file mode 100644
index 000000000..2e0fae8f4
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars.js
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that channels with different LoadInfo
+ * are stored in separate namespaces ("cookie jars")
+ */
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+
+var cookieSetPath = "/setcookie";
+var cookieCheckPath = "/checkcookie";
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+// Test array:
+// - element 0: name for cookie, used both to set and later to check
+// - element 1: loadInfo (determines cookie namespace)
+//
+// TODO: bug 722850: make private browsing work per-app, and add tests. For now
+// all values are 'false' for PB.
+
+var tests = [
+ { cookieName: 'LCC_App0_BrowF_PrivF',
+ originAttributes: new OriginAttributes(0, false, 0) },
+ { cookieName: 'LCC_App0_BrowT_PrivF',
+ originAttributes: new OriginAttributes(0, true, 0) },
+ { cookieName: 'LCC_App1_BrowF_PrivF',
+ originAttributes: new OriginAttributes(1, false, 0) },
+ { cookieName: 'LCC_App1_BrowT_PrivF',
+ originAttributes: new OriginAttributes(1, true, 0) },
+];
+
+// test number: index into 'tests' array
+var i = 0;
+
+function setupChannel(path)
+{
+ var chan = NetUtil.newChannel({uri: URL + path, loadUsingSystemPrincipal: true});
+ chan.loadInfo.originAttributes = tests[i].originAttributes;
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function setCookie() {
+ var channel = setupChannel(cookieSetPath);
+ channel.setRequestHeader("foo-set-cookie", tests[i].cookieName, false);
+ channel.asyncOpen2(new ChannelListener(setNextCookie, null));
+}
+
+function setNextCookie(request, data, context)
+{
+ if (++i == tests.length) {
+ // all cookies set: switch to checking them
+ i = 0;
+ checkCookie();
+ } else {
+ do_print("setNextCookie:i=" + i);
+ setCookie();
+ }
+}
+
+// Open channel that should send one and only one correct Cookie: header to
+// server, corresponding to it's namespace
+function checkCookie()
+{
+ var channel = setupChannel(cookieCheckPath);
+ channel.asyncOpen2(new ChannelListener(completeCheckCookie, null));
+}
+
+function completeCheckCookie(request, data, context) {
+ // Look for all cookies in what the server saw: fail if we see any besides the
+ // one expected cookie for each namespace;
+ var expectedCookie = tests[i].cookieName;
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var cookiesSeen = request.getResponseHeader("foo-saw-cookies");
+
+ var j;
+ for (j = 0; j < tests.length; j++) {
+ var cookieToCheck = tests[j].cookieName;
+ found = (cookiesSeen.indexOf(cookieToCheck) != -1);
+ if (found && expectedCookie != cookieToCheck) {
+ do_throw("test index " + i + ": found unexpected cookie '"
+ + cookieToCheck + "': in '" + cookiesSeen + "'");
+ } else if (!found && expectedCookie == cookieToCheck) {
+ do_throw("test index " + i + ": missing expected cookie '"
+ + expectedCookie + "': in '" + cookiesSeen + "'");
+ }
+ }
+ // If we get here we're good.
+ do_print("Saw only correct cookie '" + expectedCookie + "'");
+ do_check_true(true);
+
+
+ if (++i == tests.length) {
+ // end of tests
+ httpserver.stop(do_test_finished);
+ } else {
+ checkCookie();
+ }
+}
+
+function run_test()
+{
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess())
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ httpserver.registerPathHandler(cookieSetPath, cookieSetHandler);
+ httpserver.registerPathHandler(cookieCheckPath, cookieCheckHandler);
+ httpserver.start(-1);
+
+ setCookie();
+ do_test_pending();
+}
+
+function cookieSetHandler(metadata, response)
+{
+ var cookieName = metadata.getHeader("foo-set-cookie");
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Set-Cookie", cookieName + "=1; Path=/", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function cookieCheckHandler(metadata, response)
+{
+ var cookies = metadata.getHeader("Cookie");
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("foo-saw-cookies", cookies, false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
diff --git a/netwerk/test/unit/test_cookiejars_safebrowsing.js b/netwerk/test/unit/test_cookiejars_safebrowsing.js
new file mode 100644
index 000000000..c4e12aff0
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars_safebrowsing.js
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Description of the test:
+ * We show that we can separate the safebrowsing cookie by creating a custom
+ * OriginAttributes using a reserved AppId (UINT_32_MAX - 1). Setting this
+ * custom OriginAttributes on the loadInfo of the channel allows us to query the
+ * AppId and therefore separate the safebrowing cookie in its own cookie-jar.
+ * For testing safebrowsing update we do >> NOT << emulate a response
+ * in the body, rather we only set the cookies in the header of the response
+ * and confirm that cookies are separated in their own cookie-jar.
+ *
+ * 1) We init safebrowsing and simulate an update (cookies are set for localhost)
+ *
+ * 2) We open a channel that should send regular cookies, but not the
+ * safebrowsing cookie.
+ *
+ * 3) We open a channel with a custom callback, simulating a safebrowsing cookie
+ * that should send this simulated safebrowsing cookie as well as the
+ * real safebrowsing cookies. (Confirming that the safebrowsing cookies
+ * actually get stored in the correct jar).
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+ "resource://gre/modules/SafeBrowsing.jsm");
+
+var setCookiePath = "/setcookie";
+var checkCookiePath = "/checkcookie";
+var safebrowsingUpdatePath = "/safebrowsingUpdate";
+var httpserver;
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function cookieSetHandler(metadata, response) {
+ var cookieName = metadata.getHeader("set-cookie");
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("set-Cookie", cookieName + "=1; Path=/", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function cookieCheckHandler(metadata, response) {
+ var cookies = metadata.getHeader("Cookie");
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("saw-cookies", cookies, false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function safebrowsingUpdateHandler(metadata, response) {
+ var cookieName = "sb-update-cookie";
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("set-Cookie", cookieName + "=1; Path=/", false);
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write("Ok", "Ok".length);
+}
+
+function setupChannel(path, originAttributes) {
+ var channel = NetUtil.newChannel({uri: URL + path, loadUsingSystemPrincipal: true});
+ channel.loadInfo.originAttributes = originAttributes;
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ return channel;
+}
+
+function run_test() {
+
+ // Set up a profile
+ do_get_profile();
+
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess())
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(setCookiePath, cookieSetHandler);
+ httpserver.registerPathHandler(checkCookiePath, cookieCheckHandler);
+ httpserver.registerPathHandler(safebrowsingUpdatePath, safebrowsingUpdateHandler);
+
+ httpserver.start(-1);
+ run_next_test();
+}
+
+// this test does not emulate a response in the body,
+// rather we only set the cookies in the header of response.
+add_test(function test_safebrowsing_update() {
+
+ var dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
+ .getService(Ci.nsIUrlClassifierDBService);
+ var streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
+ .getService(Ci.nsIUrlClassifierStreamUpdater);
+
+ function onSuccess() {
+ run_next_test();
+ }
+ function onUpdateError() {
+ do_throw("ERROR: received onUpdateError!");
+ }
+ function onDownloadError() {
+ do_throw("ERROR: received onDownloadError!");
+ }
+
+ streamUpdater.downloadUpdates("test-phish-simple,test-malware-simple", "",
+ true, URL + safebrowsingUpdatePath, onSuccess, onUpdateError, onDownloadError);
+});
+
+add_test(function test_non_safebrowsing_cookie() {
+
+ var cookieName = 'regCookie_id0';
+ var originAttributes = new OriginAttributes(0, false, 0);
+
+ function setNonSafeBrowsingCookie() {
+ var channel = setupChannel(setCookiePath, originAttributes);
+ channel.setRequestHeader("set-cookie", cookieName, false);
+ channel.asyncOpen2(new ChannelListener(checkNonSafeBrowsingCookie, null));
+ }
+
+ function checkNonSafeBrowsingCookie() {
+ var channel = setupChannel(checkCookiePath, originAttributes);
+ channel.asyncOpen2(new ChannelListener(completeCheckNonSafeBrowsingCookie, null));
+ }
+
+ function completeCheckNonSafeBrowsingCookie(request, data, context) {
+ // Confirm that only the >> ONE << cookie is sent over the channel.
+ var expectedCookie = cookieName + "=1";
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var cookiesSeen = request.getResponseHeader("saw-cookies");
+ do_check_eq(cookiesSeen, expectedCookie);
+ run_next_test();
+ }
+
+ setNonSafeBrowsingCookie();
+});
+
+add_test(function test_safebrowsing_cookie() {
+
+ var cookieName = 'sbCookie_id4294967294';
+ var originAttributes = new OriginAttributes(Ci.nsIScriptSecurityManager.SAFEBROWSING_APP_ID, false, 0);
+
+ function setSafeBrowsingCookie() {
+ var channel = setupChannel(setCookiePath, originAttributes);
+ channel.setRequestHeader("set-cookie", cookieName, false);
+ channel.asyncOpen2(new ChannelListener(checkSafeBrowsingCookie, null));
+ }
+
+ function checkSafeBrowsingCookie() {
+ var channel = setupChannel(checkCookiePath, originAttributes);
+ channel.asyncOpen2(new ChannelListener(completeCheckSafeBrowsingCookie, null));
+ }
+
+ function completeCheckSafeBrowsingCookie(request, data, context) {
+ // Confirm that all >> THREE << cookies are sent back over the channel:
+ // a) the safebrowsing cookie set when updating
+ // b) the regular cookie with custom loadcontext defined in this test.
+ var expectedCookies = "sb-update-cookie=1; ";
+ expectedCookies += cookieName + "=1";
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var cookiesSeen = request.getResponseHeader("saw-cookies");
+
+ do_check_eq(cookiesSeen, expectedCookies);
+ httpserver.stop(do_test_finished);
+ }
+
+ setSafeBrowsingCookie();
+});
diff --git a/netwerk/test/unit/test_data_protocol.js b/netwerk/test/unit/test_data_protocol.js
new file mode 100644
index 000000000..a7f02025d
--- /dev/null
+++ b/netwerk/test/unit/test_data_protocol.js
@@ -0,0 +1,58 @@
+/* run some tests on the data: protocol handler */
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// The behaviour wrt spaces is:
+// - Textual content keeps all spaces
+// - Other content strips unescaped spaces
+// - Base64 content strips escaped and unescaped spaces
+var urls = [
+ ["data:,", "text/plain", ""],
+ ["data:,foo", "text/plain", "foo"],
+ ["data:application/octet-stream,foo bar", "application/octet-stream", "foobar"],
+ ["data:application/octet-stream,foo%20bar", "application/octet-stream", "foo bar"],
+ ["data:application/xhtml+xml,foo bar", "application/xhtml+xml", "foo bar"],
+ ["data:application/xhtml+xml,foo%20bar", "application/xhtml+xml", "foo bar"],
+ ["data:text/plain,foo%00 bar", "text/plain", "foo\x00 bar"],
+ ["data:text/plain;x=y,foo%00 bar", "text/plain", "foo\x00 bar"],
+ ["data:;x=y,foo%00 bar", "text/plain", "foo\x00 bar"],
+ ["data:text/plain;base64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ ["DATA:TEXT/PLAIN;BASE64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ ["DaTa:;BaSe64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ ["data:;x=y;base64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"],
+ // Bug 774240
+ ["data:application/octet-stream;base64=y,foobar", "application/octet-stream", "foobar"],
+ // Bug 781693
+ ["data:text/plain;base64;x=y,dGVzdA==", "text/plain", "test"],
+ ["data:text/plain;x=y;base64,dGVzdA==", "text/plain", "test"],
+ ["data:text/plain;x=y;base64,", "text/plain", ""]
+];
+
+function run_test() {
+ dump("*** run_test\n");
+
+ function on_read_complete(request, data, idx) {
+ dump("*** run_test.on_read_complete\n");
+
+ if (request.nsIChannel.contentType != urls[idx][1])
+ do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" + urls[idx][1] + ">");
+
+ /* read completed successfully. now compare the data. */
+ if (data != urls[idx][2])
+ do_throw("Stream contents do not match with direct read! Is <" + data + ">, should be <" + urls[idx][2] + ">");
+ do_test_finished();
+ }
+
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ for (var i = 0; i < urls.length; ++i) {
+ dump("*** opening channel " + i + "\n");
+ do_test_pending();
+ var chan = NetUtil.newChannel({
+ uri: urls[i][0],
+ loadUsingSystemPrincipal: true
+ });
+ chan.contentType = "foo/bar"; // should be ignored
+ chan.asyncOpen2(new ChannelListener(on_read_complete, i));
+ }
+}
+
diff --git a/netwerk/test/unit/test_dns_cancel.js b/netwerk/test/unit/test_dns_cancel.js
new file mode 100644
index 000000000..c2bdd04f7
--- /dev/null
+++ b/netwerk/test/unit/test_dns_cancel.js
@@ -0,0 +1,83 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+
+var hostname1 = "";
+var hostname2 = "";
+var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+for( var i=0; i < 20; i++ ) {
+ hostname1 += possible.charAt(Math.floor(Math.random() * possible.length));
+ hostname2 += possible.charAt(Math.floor(Math.random() * possible.length));
+}
+
+var requestList1Canceled1;
+var requestList1Canceled2;
+var requestList1NotCanceled;
+
+var requestList2Canceled;
+var requestList2NotCanceled;
+
+var listener1 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ // One request should be resolved and two request should be canceled.
+ if (inRequest == requestList1NotCanceled) {
+ // This request should not be canceled.
+ do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ }
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+var listener2 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ // One request should be resolved and the other canceled.
+ if (inRequest == requestList2NotCanceled) {
+ // The request should not be canceled.
+ do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ }
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function run_test() {
+ var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+ var mainThread = threadManager.currentThread;
+
+ var flags = Ci.nsIDNSService.RESOLVE_BYPASS_CACHE;
+
+ // This one will be canceled with cancelAsyncResolve.
+ requestList1Canceled1 = dns.asyncResolve(hostname2, flags, listener1, mainThread);
+ dns.cancelAsyncResolve(hostname2, flags, listener1, Cr.NS_ERROR_ABORT);
+
+ // This one will not be canceled.
+ requestList1NotCanceled = dns.asyncResolve(hostname1, flags, listener1, mainThread);
+
+ // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+ requestList1Canceled2 = dns.asyncResolve(hostname1, flags, listener1, mainThread);
+ requestList1Canceled2.cancel(Cr.NS_ERROR_ABORT);
+
+ // This one will not be canceled.
+ requestList2NotCanceled = dns.asyncResolve(hostname1, flags, listener2, mainThread);
+
+ // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+ requestList2Canceled = dns.asyncResolve(hostname2, flags, listener2, mainThread);
+ requestList2Canceled.cancel(Cr.NS_ERROR_ABORT);
+
+ do_test_pending();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_dns_disable_ipv4.js b/netwerk/test/unit/test_dns_disable_ipv4.js
new file mode 100644
index 000000000..ec334b1f6
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv4.js
@@ -0,0 +1,40 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV4 flag doesn't
+// return any IPv4 addresses.
+//
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ if (inStatus != Cr.NS_OK) {
+ do_check_eq(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+ do_test_finished();
+ return;
+ }
+
+ while (true) {
+ try {
+ var answer = inRecord.getNextAddrAsString();
+ // If there is an answer it should be an IPv6 address
+ dump(answer);
+ do_check_true(answer.indexOf(':') != -1);
+ } catch (e) {
+ break;
+ }
+ }
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ do_test_pending();
+ try {
+ dns.asyncResolve("example.org", Ci.nsIDNSService.RESOLVE_DISABLE_IPV4, listener, null);
+ } catch (e) {
+ dump(e);
+ do_check_true(false);
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/unit/test_dns_disable_ipv6.js b/netwerk/test/unit/test_dns_disable_ipv6.js
new file mode 100644
index 000000000..af5558d53
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv6.js
@@ -0,0 +1,41 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV6 flag doesn't
+// return any IPv6 addresses.
+//
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ if (inStatus != Cr.NS_OK) {
+ do_check_eq(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+ do_test_finished();
+ return;
+ }
+
+ while (true) {
+ try {
+ var answer = inRecord.getNextAddrAsString();
+ // If there is an answer it should be an IPv4 address
+ dump(answer);
+ do_check_true(answer.indexOf(':') == -1);
+ do_check_true(answer.indexOf('.') != -1);
+ } catch (e) {
+ break;
+ }
+ }
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ do_test_pending();
+ try {
+ dns.asyncResolve("example.com", Ci.nsIDNSService.RESOLVE_DISABLE_IPV6, listener, null);
+ } catch (e) {
+ dump(e);
+ do_check_true(false);
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/unit/test_dns_localredirect.js b/netwerk/test/unit/test_dns_localredirect.js
new file mode 100644
index 000000000..71e312ebc
--- /dev/null
+++ b/netwerk/test/unit/test_dns_localredirect.js
@@ -0,0 +1,31 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+
+ prefs.clearUserPref("network.dns.localDomains");
+
+ do_test_finished();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function run_test() {
+ prefs.setCharPref("network.dns.localDomains", "local.vingtetun.org");
+
+ var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+ var mainThread = threadManager.currentThread;
+ dns.asyncResolve("local.vingtetun.org", 0, listener, mainThread);
+
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_dns_offline.js b/netwerk/test/unit/test_dns_offline.js
new file mode 100644
index 000000000..87a9ad8b1
--- /dev/null
+++ b/netwerk/test/unit/test_dns_offline.js
@@ -0,0 +1,74 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+var mainThread = threadManager.currentThread;
+
+var listener1 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ do_check_eq(inStatus, Cr.NS_ERROR_OFFLINE);
+ test2();
+ do_test_finished();
+ }
+};
+
+var listener2 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ do_check_eq(inStatus, Cr.NS_OK);
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+ test3();
+ do_test_finished();
+ }
+};
+
+var listener3 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ do_check_eq(inStatus, Cr.NS_OK);
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+ cleanup();
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ do_test_pending();
+ prefs.setBoolPref("network.dns.offline-localhost", false);
+ ioService.offline = true;
+ try {
+ dns.asyncResolve("localhost", 0, listener1, mainThread);
+ } catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_OFFLINE);
+ test2();
+ do_test_finished();
+ }
+}
+
+function test2() {
+ do_test_pending();
+ prefs.setBoolPref("network.dns.offline-localhost", true);
+ ioService.offline = false;
+ ioService.offline = true;
+ // we need to let the main thread run and apply the changes
+ do_timeout(0, test2Continued);
+}
+
+function test2Continued() {
+ dns.asyncResolve("localhost", 0, listener2, mainThread);
+}
+
+function test3() {
+ do_test_pending();
+ ioService.offline = false;
+ // we need to let the main thread run and apply the changes
+ do_timeout(0, test3Continued);
+}
+
+function test3Continued() {
+ dns.asyncResolve("localhost", 0, listener3, mainThread);
+}
+
+function cleanup() {
+ prefs.clearUserPref("network.dns.offline-localhost");
+}
diff --git a/netwerk/test/unit/test_dns_onion.js b/netwerk/test/unit/test_dns_onion.js
new file mode 100644
index 000000000..02647b964
--- /dev/null
+++ b/netwerk/test/unit/test_dns_onion.js
@@ -0,0 +1,70 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+var mainThread = threadManager.currentThread;
+
+var onionPref;
+var localdomainPref;
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+// check that we don't lookup .onion
+var listenerBlock = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ do_check_false(Components.isSuccessCode(inStatus));
+ do_test_dontBlock();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+// check that we do lookup .onion (via pref)
+var listenerDontBlock = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+ all_done();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function do_test_dontBlock() {
+ prefs.setBoolPref("network.dns.blockDotOnion", false);
+ dns.asyncResolve("private.onion", 0, listenerDontBlock, mainThread);
+}
+
+function do_test_block() {
+ prefs.setBoolPref("network.dns.blockDotOnion", true);
+ try {
+ dns.asyncResolve("private.onion", 0, listenerBlock, mainThread);
+ } catch (e) {
+ // it is ok for this negative test to fail fast
+ do_check_true(true);
+ do_test_dontBlock();
+ }
+}
+
+function all_done() {
+ // reset locally modified prefs
+ prefs.setCharPref("network.dns.localDomains", localdomainPref);
+ prefs.setBoolPref("network.dns.blockDotOnion", onionPref);
+ do_test_finished();
+}
+
+function run_test() {
+ onionPref = prefs.getBoolPref("network.dns.blockDotOnion");
+ localdomainPref = prefs.getCharPref("network.dns.localDomains");
+ prefs.setCharPref("network.dns.localDomains", "private.onion");
+ do_test_block();
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_dns_per_interface.js b/netwerk/test/unit/test_dns_per_interface.js
new file mode 100644
index 000000000..b4c69f8c3
--- /dev/null
+++ b/netwerk/test/unit/test_dns_per_interface.js
@@ -0,0 +1,79 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+
+// This test checks DNSService host resolver when a network interface is supplied
+// as well. In the test 3 request are sent: two with a network interface set
+// and one without a network interface.
+// All requests have the same host to be resolved and the same flags.
+// One of the request with the network interface will be canceled.
+// The request with and without a network interface should not be mixed during
+// the requests lifetime.
+
+var netInterface1 = "interface1";
+var netInterface2 = "interface2";
+
+// We are not using localhost because on e10s a host resolve callback is almost
+// always faster than a cancel request, therefore cancel operation would not be
+// tested.
+var hostname = "thisshouldnotexist.mozilla.com";
+
+// 3 requests.
+var requestWithInterfaceCanceled;
+var requestWithoutInterfaceNotCanceled;
+var requestWithInterfaceNotCanceled;
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ // Two requests should be resolved and one request should be canceled.
+ // Since cancalation of a request is racy we will check only for not
+ // canceled request - they should not be canceled.
+ if ((inRequest == requestWithoutInterfaceNotCanceled) ||
+ (inRequest == requestWithInterfaceNotCanceled)) {
+ // This request should not be canceled.
+ do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ } else if (inRequest == requestWithInterfaceCanceled) {
+ // We do not check the outcome for this one because it is racy -
+ // whether the request cancelation is faster than resolving the request.
+ do_test_finished();
+ }
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function run_test() {
+ var threadManager = Cc["@mozilla.org/thread-manager;1"]
+ .getService(Ci.nsIThreadManager);
+ var mainThread = threadManager.currentThread;
+
+ var flags = Ci.nsIDNSService.RESOLVE_BYPASS_CACHE;
+
+ // This one will be canceled.
+ requestWithInterfaceCanceled = dns.asyncResolveExtended(hostname, flags,
+ netInterface1,
+ listener,
+ mainThread);
+ requestWithInterfaceCanceled.cancel(Cr.NS_ERROR_ABORT);
+
+ // This one will not be canceled. This is the request without a network
+ // interface.
+ requestWithoutInterfaceNotCanceled = dns.asyncResolve(hostname, flags,
+ listener, mainThread);
+
+ // This one will not be canceled.
+ requestWithInterfaceNotCanceled = dns.asyncResolveExtended(hostname, flags,
+ netInterface2,
+ listener,
+ mainThread);
+ // We wait for notifications for the requests.
+ // For each request onLookupComplete will be called.
+ do_test_pending();
+ do_test_pending();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_dns_proxy_bypass.js b/netwerk/test/unit/test_dns_proxy_bypass.js
new file mode 100644
index 000000000..6443f7ade
--- /dev/null
+++ b/netwerk/test/unit/test_dns_proxy_bypass.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+var url = "ws://dnsleak.example.com";
+
+var dnsRequestObserver = {
+ register: function() {
+ this.obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ this.obs.addObserver(this, "dns-resolution-request", false);
+ },
+
+ unregister: function() {
+ if (this.obs) {
+ this.obs.removeObserver(this, "dns-resolution-request");
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic == "dns-resolution-request") {
+ do_print(data);
+ if (data.indexOf("dnsleak.example.com") > -1) {
+ try {
+ do_check_true(false);
+ } catch (e) {}
+ }
+ }
+ }
+};
+
+var listener = {
+ onAcknowledge: function(aContext, aSize) {},
+ onBinaryMessageAvailable: function(aContext, aMsg) {},
+ onMessageAvailable: function(aContext, aMsg) {},
+ onServerClose: function(aContext, aCode, aReason) {},
+ onStart: function(aContext) {},
+ onStop: function(aContext, aStatusCode) {
+ prefs.clearUserPref("network.proxy.socks");
+ prefs.clearUserPref("network.proxy.socks_port");
+ prefs.clearUserPref("network.proxy.type");
+ prefs.clearUserPref("network.proxy.socks_remote_dns");
+ prefs.clearUserPref("network.dns.notifyResolution");
+ dnsRequestObserver.unregister();
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ dnsRequestObserver.register();
+ prefs.setBoolPref("network.dns.notifyResolution", true);
+ prefs.setCharPref("network.proxy.socks", "127.0.0.1");
+ prefs.setIntPref("network.proxy.socks_port", 9000);
+ prefs.setIntPref("network.proxy.type", 1);
+ prefs.setBoolPref("network.proxy.socks_remote_dns", true);
+ var chan = Cc["@mozilla.org/network/protocol;1?name=ws"].
+ createInstance(Components.interfaces.nsIWebSocketChannel);
+
+ chan.initLoadInfo(null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET);
+
+ var uri = ioService.newURI(url, null, null);
+ chan.asyncOpen(uri, url, 0, listener, null);
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_dns_service.js b/netwerk/test/unit/test_dns_service.js
new file mode 100644
index 000000000..04c1faeef
--- /dev/null
+++ b/netwerk/test/unit/test_dns_service.js
@@ -0,0 +1,26 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+
+ do_test_finished();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function run_test() {
+ var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+ var mainThread = threadManager.currentThread;
+ dns.asyncResolve("localhost", 0, listener, mainThread);
+
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_doomentry.js b/netwerk/test/unit/test_doomentry.js
new file mode 100644
index 000000000..592952306
--- /dev/null
+++ b/netwerk/test/unit/test_doomentry.js
@@ -0,0 +1,97 @@
+/**
+ * Test for nsICacheStorage.asyncDoomURI().
+ * It tests dooming
+ * - an existent inactive entry
+ * - a non-existent inactive entry
+ * - an existent active entry
+ */
+
+function doom(url, callback)
+{
+ get_cache_service()
+ .diskCacheStorage(LoadContextInfo.default, false)
+ .asyncDoomURI(createURI(url), "", {
+ onCacheEntryDoomed: function(result) {
+ callback(result);
+ }
+ });
+}
+
+function write_and_check(str, data, len)
+{
+ var written = str.write(data, len);
+ if (written != len) {
+ do_throw("str.write has not written all data!\n" +
+ " Expected: " + len + "\n" +
+ " Actual: " + written + "\n");
+ }
+}
+
+function write_entry()
+{
+ asyncOpenCacheEntry("http://testentry/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null, function(status, entry) {
+ write_entry_cont(entry, entry.openOutputStream(0));
+ });
+}
+
+function write_entry_cont(entry, ostream)
+{
+ var data = "testdata";
+ write_and_check(ostream, data, data.length);
+ ostream.close();
+ entry.close();
+ doom("http://testentry/", check_doom1);
+}
+
+function check_doom1(status)
+{
+ do_check_eq(status, Cr.NS_OK);
+ doom("http://nonexistententry/", check_doom2);
+}
+
+function check_doom2(status)
+{
+ do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE);
+ asyncOpenCacheEntry("http://testentry/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null, function(status, entry) {
+ write_entry2(entry, entry.openOutputStream(0));
+ });
+}
+
+var gEntry;
+var gOstream;
+function write_entry2(entry, ostream)
+{
+ // write some data and doom the entry while it is active
+ var data = "testdata";
+ write_and_check(ostream, data, data.length);
+ gEntry = entry;
+ gOstream = ostream;
+ doom("http://testentry/", check_doom3);
+}
+
+function check_doom3(status)
+{
+ do_check_eq(status, Cr.NS_OK);
+ // entry was doomed but writing should still succeed
+ var data = "testdata";
+ write_and_check(gOstream, data, data.length);
+ gOstream.close();
+ gEntry.close();
+ // dooming the same entry again should fail
+ doom("http://testentry/", check_doom4);
+}
+
+function check_doom4(status)
+{
+ do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE);
+ do_test_finished();
+}
+
+function run_test() {
+ do_get_profile();
+
+ // clear the cache
+ evict_cache_entries();
+ write_entry();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_duplicate_headers.js b/netwerk/test/unit/test_duplicate_headers.js
new file mode 100644
index 000000000..80c170887
--- /dev/null
+++ b/netwerk/test/unit/test_duplicate_headers.js
@@ -0,0 +1,605 @@
+/*
+ * Tests bugs 597706, 655389: prevent duplicate headers with differing values
+ * for some headers like Content-Length, Location, etc.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var test_flags = new Array();
+var testPathBase = "/dupe_hdrs";
+
+function run_test()
+{
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests()
+{
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: FAIL because of conflicting Content-Length headers
+test_flags[1] = CL_EXPECT_FAILURE;
+
+function handler1(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ // Comrades! We must seize power from the petty-bourgeois running dogs of
+ // httpd.js in order to reply with multiple instances of the same header!
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length: 20\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+
+function completeTest1(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: OK to have duplicate same Content-Length headers
+
+function handler2(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest2(request, data, ctx)
+{
+ do_check_eq(request.status, 0);
+ run_test_number(3);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: FAIL: 2nd Content-length is blank
+test_flags[3] = CL_EXPECT_FAILURE;
+
+function handler3(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length:\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest3(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(4);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: ensure that blank C-len header doesn't allow attacker to reset Clen,
+// then insert CRLF attack
+test_flags[4] = CL_EXPECT_FAILURE;
+
+function handler4(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+
+ // Bad Mr Hacker! Bad!
+ var evilBody = "We are the Evil bytes, Evil bytes, Evil bytes!";
+ response.write("Content-Length:\r\n");
+ response.write("Content-Length: %s\r\n\r\n%s" % (evilBody.length, evilBody));
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest4(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(5);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 5: ensure that we take 1st instance of duplicate, nonmerged headers that
+// are permitted : (ex: Referrer)
+
+function handler5(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Referer: naive.org\r\n");
+ response.write("Referer: evil.net\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest5(request, data, ctx)
+{
+ try {
+ referer = request.getResponseHeader("Referer");
+ do_check_eq(referer, "naive.org");
+ } catch (ex) {
+ do_throw("Referer header should be present");
+ }
+
+ run_test_number(6);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 5: FAIL if multiple, different Location: headers present
+// - needed to prevent CRLF injection attacks
+test_flags[6] = CL_EXPECT_FAILURE;
+
+function handler6(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Location: " + URL + "/content\r\n");
+ response.write("Location: http://www.microsoft.com/\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest6(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+// run_test_number(7); // Test 7 leaking under e10s: unrelated bug?
+ run_test_number(8);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 7: OK to have multiple Location: headers with same value
+
+function handler7(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 5
+ response.write("Location: " + URL + testPathBase + "5\r\n");
+ response.write("Location: " + URL + testPathBase + "5\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest7(request, data, ctx)
+{
+ // for some reason need this here
+ request.QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ try {
+ referer = request.getResponseHeader("Referer");
+ do_check_eq(referer, "naive.org");
+ } catch (ex) {
+ do_throw("Referer header should be present");
+ }
+
+ run_test_number(8);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FAIL if 2nd Location: headers blank
+test_flags[8] = CL_EXPECT_FAILURE;
+
+function handler8(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 4
+ response.write("Location: " + URL + testPathBase + "4\r\n");
+ response.write("Location:\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest8(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(9);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 9: ensure that blank Location header doesn't allow attacker to reset,
+// then insert an evil one
+test_flags[9] = CL_EXPECT_FAILURE;
+
+function handler9(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 2
+ response.write("Location: " + URL + testPathBase + "2\r\n");
+ response.write("Location:\r\n");
+ // redirect to previous test handler that completes OK: test 4
+ response.write("Location: " + URL + testPathBase + "4\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest9(request, data, ctx)
+{
+ // All redirection should fail:
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(10);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 10: FAIL: if conflicting values for Content-Dispo
+test_flags[10] = CL_EXPECT_FAILURE;
+
+function handler10(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Disposition: attachment; filename=foo\r\n");
+ response.write("Content-Disposition: attachment; filename=bar\r\n");
+ response.write("Content-Disposition: attachment; filename=baz\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+
+function completeTest10(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(11);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 11: OK to have duplicate same Content-Disposition headers
+
+function handler11(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Disposition: attachment; filename=foo\r\n");
+ response.write("Content-Disposition: attachment; filename=foo\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest11(request, data, ctx)
+{
+ do_check_eq(request.status, 0);
+
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionFilename, "foo");
+ do_check_eq(chan.contentDispositionHeader, "attachment; filename=foo");
+ } catch (ex) {
+ do_throw("error parsing Content-Disposition: " + ex);
+ }
+
+ run_test_number(12);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Bug 716801 OK for Location: header to be blank
+
+function handler12(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Location:\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest12(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(30, data.length);
+
+ run_test_number(13);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Negative content length is ok
+test_flags[13] = CL_ALLOW_UNKNOWN_CL;
+
+function handler13(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: -1\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest13(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(30, data.length);
+
+ run_test_number(14);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// leading negative content length is not ok if paired with positive one
+
+test_flags[14] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+function handler14(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: -1\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest14(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(15);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// trailing negative content length is not ok if paired with positive one
+
+test_flags[15] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+function handler15(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Content-Length: -1\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest15(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(16);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// empty content length is ok
+test_flags[16] = CL_ALLOW_UNKNOWN_CL;
+reran16 = false;
+
+function handler16(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: \r\n");
+ response.write("Cache-Control: max-age=600\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest16(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(30, data.length);
+
+ if (!reran16) {
+ reran16 = true;
+ run_test_number(16);
+ }
+ else {
+ run_test_number(17);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// empty content length paired with non empty is not ok
+test_flags[17] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
+
+function handler17(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: \r\n");
+ response.write("Content-Length: 30\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest17(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(18);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// alpha content-length is just like -1
+test_flags[18] = CL_ALLOW_UNKNOWN_CL;
+
+function handler18(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: seventeen\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest18(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(30, data.length);
+
+ run_test_number(19);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// semi-colons are ok too in the content-length
+test_flags[19] = CL_ALLOW_UNKNOWN_CL;
+
+function handler19(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 200 OK\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30;\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest19(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(30, data.length);
+
+ run_test_number(20);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FAIL if 1st Location: header is blank, followed by non-blank
+test_flags[20] = CL_EXPECT_FAILURE;
+
+function handler20(metadata, response)
+{
+ var body = "012345678901234567890123456789";
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Content-Type: text/plain\r\n");
+ response.write("Content-Length: 30\r\n");
+ // redirect to previous test handler that completes OK: test 4
+ response.write("Location:\r\n");
+ response.write("Location: " + URL + testPathBase + "4\r\n");
+ response.write("Connection: close\r\n");
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
+
+function completeTest20(request, data, ctx)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ endTests();
+}
+
diff --git a/netwerk/test/unit/test_event_sink.js b/netwerk/test/unit/test_event_sink.js
new file mode 100644
index 000000000..45c01683a
--- /dev/null
+++ b/netwerk/test/unit/test_event_sink.js
@@ -0,0 +1,170 @@
+// This file tests channel event sinks (bug 315598 et al)
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+const sinkCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}");
+const sinkContract = "@mozilla.org/network/unittest/channeleventsink;1";
+
+const categoryName = "net-channel-event-sinks";
+
+/**
+ * This object is both a factory and an nsIChannelEventSink implementation (so, it
+ * is de-facto a service). It's also an interface requestor that gives out
+ * itself when asked for nsIChannelEventSink.
+ */
+var eventsink = {
+ QueryInterface: function eventsink_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIFactory) ||
+ iid.equals(Components.interfaces.nsIChannelEventSink))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ createInstance: function eventsink_ci(outer, iid) {
+ if (outer)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+ lockFactory: function eventsink_lockf(lock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ asyncOnChannelRedirect: function eventsink_onredir(oldChan, newChan, flags, callback) {
+ // veto
+ this.called = true;
+ throw Components.results.NS_BINDING_ABORTED;
+ },
+
+ getInterface: function eventsink_gi(iid) {
+ if (iid.equals(Components.interfaces.nsIChannelEventSink))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ called: false
+};
+
+var listener = {
+ expectSinkCall: true,
+
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ // Commenting out this check pending resolution of bug 255119
+ //if (Components.isSuccessCode(request.status))
+ // do_throw("Channel should have a failure code!");
+
+ // The current URI must be the original URI, as all redirects have been
+ // cancelled
+ if (!(request instanceof Components.interfaces.nsIChannel) ||
+ !request.URI.equals(request.originalURI))
+ do_throw("Wrong URI: Is <" + request.URI.spec + ">, should be <" +
+ request.originalURI.spec + ">");
+
+ if (request instanceof Components.interfaces.nsIHttpChannel) {
+ // As we expect a blocked redirect, verify that we have a 3xx status
+ do_check_eq(Math.floor(request.responseStatus / 100), 3);
+ do_check_eq(request.requestSucceeded, false);
+ }
+
+ do_check_eq(eventsink.called, this.expectSinkCall);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ if (this._iteration <= 2) {
+ run_test_continued();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+ do_test_finished();
+ },
+
+ _iteration: 1
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/redirect", redirect);
+ httpserv.registerPathHandler("/redirectfile", redirectfile);
+ httpserv.start(-1);
+
+ Components.manager.nsIComponentRegistrar.registerFactory(sinkCID,
+ "Unit test Event sink", sinkContract, eventsink);
+
+ // Step 1: Set the callbacks on the listener itself
+ var chan = makeChan(URL + "/redirect");
+ chan.notificationCallbacks = eventsink;
+
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function run_test_continued() {
+ eventsink.called = false;
+
+ var catMan = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+
+ var chan;
+ if (listener._iteration == 1) {
+ // Step 2: Category entry
+ catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test",
+ sinkContract, false, true);
+ chan = makeChan(URL + "/redirect")
+ } else {
+ // Step 3: Global contract id
+ catMan.nsICategoryManager.deleteCategoryEntry(categoryName, "unit test",
+ false);
+ listener.expectSinkCall = false;
+ chan = makeChan(URL + "/redirectfile");
+ }
+
+ listener._iteration++;
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+// PATHS
+
+// /redirect
+function redirect(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location",
+ "http://localhost:" + metadata.port + "/",
+ false);
+
+ var body = "Moved\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /redirectfile
+function redirectfile(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Location", "file:///etc/", false);
+
+ var body = "Attempted to move to a file URI, but failed.\n";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/unit/test_extract_charset_from_content_type.js b/netwerk/test/unit/test_extract_charset_from_content_type.js
new file mode 100644
index 000000000..224be7052
--- /dev/null
+++ b/netwerk/test/unit/test_extract_charset_from_content_type.js
@@ -0,0 +1,163 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var charset = {};
+var charsetStart = {};
+var charsetEnd = {};
+var hadCharset;
+
+function reset() {
+ delete charset.value;
+ delete charsetStart.value;
+ delete charsetEnd.value;
+ hadCharset = undefined;
+}
+
+function check(aHadCharset, aCharset, aCharsetStart, aCharsetEnd) {
+ do_check_eq(aHadCharset, hadCharset);
+ do_check_eq(aCharset, charset.value);
+ do_check_eq(aCharsetStart, charsetStart.value);
+ do_check_eq(aCharsetEnd, charsetEnd.value);
+}
+
+function run_test() {
+ var netutil = Components.classes["@mozilla.org/network/util;1"]
+ .getService(Components.interfaces.nsINetUtil);
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html", charset, charsetStart,
+ charsetEnd);
+ check(false, "", 9, 9);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("TEXT/HTML", charset, charsetStart,
+ charsetEnd);
+ check(false, "", 9, 9);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html, text/html", charset,
+ charsetStart, charsetEnd);
+ check(false, "", 9, 9);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html, text/plain",
+ charset, charsetStart, charsetEnd);
+ check(false, "", 21, 21);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/html, ', charset, charsetStart,
+ charsetEnd);
+ check(false, "", 9, 9);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/html, */*', charset,
+ charsetStart, charsetEnd);
+ check(false, "", 9, 9);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/html, foo', charset,
+ charsetStart, charsetEnd);
+ check(false, "", 9, 9);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html; charset=ISO-8859-1",
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 9, 29);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html ; charset=ISO-8859-1",
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 11, 34);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html ; charset=ISO-8859-1 ",
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 11, 36);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html ; charset=ISO-8859-1 ; ",
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 11, 35);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/html; charset="ISO-8859-1"',
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 9, 31);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html; charset='ISO-8859-1'",
+ charset, charsetStart, charsetEnd);
+ check(true, "'ISO-8859-1'", 9, 31);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html; charset=\"ISO-8859-1\", text/html",
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 9, 31);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html; charset=\"ISO-8859-1\", text/html; charset=UTF8",
+ charset, charsetStart, charsetEnd);
+ check(true, "UTF8", 42, 56);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html; charset=ISO-8859-1, TEXT/HTML",
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 9, 29);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/html; charset=ISO-8859-1, TEXT/plain",
+ charset, charsetStart, charsetEnd);
+ check(false, "", 41, 41);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/plain, TEXT/HTML; charset=\"ISO-8859-1\", text/html, TEXT/HTML",
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 21, 43);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 43, 65);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML',
+ charset, charsetStart, charsetEnd);
+ check(true, "ISO-8859-1", 41, 63);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType("text/plain; param= , text/html",
+ charset, charsetStart, charsetEnd);
+ check(false, "", 30, 30);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain; param=", text/html"',
+ charset, charsetStart, charsetEnd);
+ check(false, "", 10, 10);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain; param=", \\" , text/html"',
+ charset, charsetStart, charsetEnd);
+ check(false, "", 10, 10);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain; param=", \\" , text/html , "',
+ charset, charsetStart, charsetEnd);
+ check(false, "", 10, 10);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain param=", \\" , text/html , "',
+ charset, charsetStart, charsetEnd);
+ check(false, "", 38, 38);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain charset=UTF8',
+ charset, charsetStart, charsetEnd);
+ check(false, "", 23, 23);
+
+ hadCharset =
+ netutil.extractCharsetFromContentType('text/plain, TEXT/HTML; param="charset=UTF8"; ; param2="charset=UTF16", text/html, TEXT/HTML',
+ charset, charsetStart, charsetEnd);
+ check(false, "", 21, 21);
+}
diff --git a/netwerk/test/unit/test_fallback_no-cache-entry_canceled.js b/netwerk/test/unit/test_fallback_no-cache-entry_canceled.js
new file mode 100644
index 000000000..5f06b2960
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_no-cache-entry_canceled.js
@@ -0,0 +1,112 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ // In this test the randomURI doesn't exists
+ var chan = make_channel(randomURI);
+ chan.loadFlags = (Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_FROM_CACHE |
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_no-cache-entry_passing.js b/netwerk/test/unit/test_fallback_no-cache-entry_passing.js
new file mode 100644
index 000000000..cd389421b
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_no-cache-entry_passing.js
@@ -0,0 +1,110 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ // In this test the randomURI doesn't exists
+ var chan = make_channel(randomURI);
+ chan.loadFlags = (Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_FROM_CACHE |
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js b/netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js
new file mode 100644
index 000000000..eddfcbec0
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js
@@ -0,0 +1,115 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+const responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js b/netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js
new file mode 100644
index 000000000..a725161ef
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js
@@ -0,0 +1,114 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_request-error_canceled.js b/netwerk/test/unit/test_fallback_request-error_canceled.js
new file mode 100644
index 000000000..02129e6b4
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_request-error_canceled.js
@@ -0,0 +1,121 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ do_test_finished();
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+
+ // doing this to eval the lazy getter, otherwise the make_channel call would hang
+ randomURI;
+ httpServer.stop(function() {
+ // Now shut the server down to have an error in onStartRequest
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ });
+ }}
+
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_request-error_passing.js b/netwerk/test/unit/test_fallback_request-error_passing.js
new file mode 100644
index 000000000..8dc935273
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_request-error_passing.js
@@ -0,0 +1,119 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ do_test_finished();
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var _x = randomURI; // doing this so the lazy value gets computed
+ httpServer.stop(function() {
+ // Now shut the server down to have an error in onstartrequest
+ var chan = make_channel(randomURI);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test));
+ });
+ }}
+
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ systemPrincipal,
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_response-error_canceled.js b/netwerk/test/unit/test_fallback_response-error_canceled.js
new file mode 100644
index 000000000..e025f2967
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_response-error_canceled.js
@@ -0,0 +1,116 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/error/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /error path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nerror /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// error handler returns error
+function errorHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 404, "Bad request");
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, errorHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ systemPrincipal,
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_response-error_passing.js b/netwerk/test/unit/test_fallback_response-error_passing.js
new file mode 100644
index 000000000..e59fad7d0
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_response-error_passing.js
@@ -0,0 +1,114 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/error/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /error path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nerror /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// error handler returns error
+function errorHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 404, "Bad request");
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, errorHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen2(new ChannelListener(finish_test));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ us.scheduleUpdate(make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/manifest"),
+ make_uri("http://localhost:" +
+ httpServer.identity.primaryPort + "/masterEntry"),
+ systemPrincipal,
+ null);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_file_partial_inputstream.js b/netwerk/test/unit/test_file_partial_inputstream.js
new file mode 100644
index 000000000..6eb4a3ac8
--- /dev/null
+++ b/netwerk/test/unit/test_file_partial_inputstream.js
@@ -0,0 +1,512 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test nsIPartialFileInputStream
+// NOTE! These tests often use do_check_true(a == b) rather than
+// do_check_eq(a, b) to avoid outputting characters which confuse
+// the console
+
+var CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+const PR_RDONLY = 0x1; // see prio.h
+
+// We need the profile directory so the test harness will clean up our test
+// files.
+do_get_profile();
+
+var binary_test_file_name = "data/image.png";
+var text_test_file_name = "test_file_partial_inputstream.js";
+// This is a global variable since if it's passed as an argument stack traces
+// become unreadable.
+var test_file_data;
+
+function run_test()
+{
+ // Binary tests
+ let binaryFile = do_get_file(binary_test_file_name);
+ let size = binaryFile.fileSize;
+ // Want to make sure we're working with a large enough file
+ dump("**** binary file size is: " + size + " ****\n");
+ do_check_true(size > 65536);
+
+ let binaryStream = new BinaryInputStream(new_file_input_stream(binaryFile));
+ test_file_data = "";
+ while ((avail = binaryStream.available()) > 0) {
+ test_file_data += binaryStream.readBytes(avail);
+ }
+ do_check_eq(test_file_data.length, size);
+ binaryStream.close();
+
+ test_binary_portion(0, 10);
+ test_binary_portion(0, 20000);
+ test_binary_portion(0, size);
+ test_binary_portion(20000, 10);
+ test_binary_portion(20000, 20000);
+ test_binary_portion(20000, size-20000);
+ test_binary_portion(size-10, 10);
+ test_binary_portion(size-20000, 20000);
+ test_binary_portion(0, 0);
+ test_binary_portion(20000, 0);
+ test_binary_portion(size-1, 1);
+
+
+ // Text-file tests
+ let textFile = do_get_file(binary_test_file_name);
+ size = textFile.fileSize;
+ // Want to make sure we're working with a large enough file
+ dump("**** text file size is: " + size + " ****\n");
+ do_check_true(size > 7000);
+
+ let textStream = new BinaryInputStream(new_file_input_stream(textFile));
+ test_file_data = "";
+ while ((avail = textStream.available()) > 0)
+ test_file_data += textStream.readBytes(avail);
+ do_check_eq(test_file_data.length, size);
+ textStream.close();
+
+ test_text_portion(0, 100);
+ test_text_portion(0, size);
+ test_text_portion(5000, 1000);
+ test_text_portion(size-10, 10);
+ test_text_portion(size-5000, 5000);
+ test_text_portion(10, 0);
+ test_text_portion(size-1, 1);
+
+ // Test auto-closing files
+ // Test behavior when *not* autoclosing
+ let tempFile = create_temp_file("01234567890123456789");
+ let tempInputStream = new_partial_file_input_stream(tempFile, 5, 10);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ try {
+ // This fails on some platforms
+ tempFile.remove(false);
+ }
+ catch (ex) {
+ }
+ tempInputStream.QueryInterface(Ci.nsISeekableStream);
+ tempInputStream.seek(SET, 1);
+ do_check_eq(read_line_stream(tempInputStream)[1], "678901234");
+
+ // Test removing the file when autoclosing
+ tempFile = create_temp_file("01234567890123456789");
+ tempInputStream = new_partial_file_input_stream(tempFile, 5, 10,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF |
+ Ci.nsIFileInputStream.REOPEN_ON_REWIND);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ tempFile.remove(false);
+ tempInputStream.QueryInterface(Ci.nsISeekableStream);
+ try {
+ // The seek should reopen the file, which should fail.
+ tempInputStream.seek(SET, 1);
+ do_check_true(false);
+ }
+ catch (ex) {
+ }
+
+ // Test editing the file when autoclosing
+ tempFile = create_temp_file("01234567890123456789");
+ tempInputStream = new_partial_file_input_stream(tempFile, 5, 10,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF |
+ Ci.nsIFileInputStream.REOPEN_ON_REWIND);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(tempFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ let newData = "abcdefghijklmnopqrstuvwxyz";
+ ostream.write(newData, newData.length);
+ ostream.close();
+ tempInputStream.QueryInterface(Ci.nsISeekableStream);
+ tempInputStream.seek(SET, 1);
+ do_check_eq(read_line_stream(tempInputStream)[1], newData.substr(6,9));
+
+ // Test auto-delete and auto-close together
+ tempFile = create_temp_file("01234567890123456789");
+ tempInputStream = new_partial_file_input_stream(tempFile, 5, 10,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF |
+ Ci.nsIFileInputStream.DELETE_ON_CLOSE);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ do_check_false(tempFile.exists());
+}
+
+function test_binary_portion(start, length) {
+ let subFile = create_temp_file(test_file_data.substr(start, length));
+
+ let streamTests = [
+ test_4k_read,
+ test_max_read,
+ test_seek,
+ test_seek_then_read,
+ ];
+
+ for (var test of streamTests) {
+ let fileStream = new_file_input_stream(subFile);
+ let partialStream = new_partial_file_input_stream(do_get_file(binary_test_file_name),
+ start, length);
+ test(fileStream, partialStream, length);
+ fileStream.close();
+ partialStream.close();
+ }
+}
+
+function test_4k_read(fileStreamA, fileStreamB) {
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+ let streamA = new BinaryInputStream(fileStreamA);
+ let streamB = new BinaryInputStream(fileStreamB);
+
+ while(1) {
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+
+ let availA = streamA.available();
+ let availB = streamB.available();
+ do_check_eq(availA, availB);
+ if (availA == 0)
+ return;
+
+ let readSize = availA > 4096 ? 4096 : availA;
+
+ do_check_true(streamA.readBytes(readSize) ==
+ streamB.readBytes(readSize));
+ }
+}
+
+function test_max_read(fileStreamA, fileStreamB) {
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+ let streamA = new BinaryInputStream(fileStreamA);
+ let streamB = new BinaryInputStream(fileStreamB);
+
+ while(1) {
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+
+ let availA = streamA.available();
+ let availB = streamB.available();
+ do_check_eq(availA, availB);
+ if (availA == 0)
+ return;
+
+ do_check_true(streamA.readBytes(availA) ==
+ streamB.readBytes(availB));
+ }
+}
+
+const SET = Ci.nsISeekableStream.NS_SEEK_SET;
+const CUR = Ci.nsISeekableStream.NS_SEEK_CUR;
+const END = Ci.nsISeekableStream.NS_SEEK_END;
+function test_seek(dummy, partialFileStream, size) {
+ // We can't test the "real" filestream here as our existing file streams
+ // are very broken and allows searching past the end of the file.
+
+ partialFileStream.QueryInterface(Ci.nsISeekableStream);
+
+ var tests = [
+ [SET, 0],
+ [SET, 5],
+ [SET, 1000],
+ [SET, size-10],
+ [SET, size-5],
+ [SET, size-1],
+ [SET, size],
+ [SET, size+10],
+ [SET, 0],
+ [CUR, 5],
+ [CUR, -5],
+ [SET, 5000],
+ [CUR, -100],
+ [CUR, 200],
+ [CUR, -5000],
+ [CUR, 5000],
+ [CUR, size * 2],
+ [SET, 1],
+ [CUR, -1],
+ [CUR, -1],
+ [CUR, -1],
+ [CUR, -1],
+ [CUR, -1],
+ [SET, size-1],
+ [CUR, 1],
+ [CUR, 1],
+ [CUR, 1],
+ [CUR, 1],
+ [CUR, 1],
+ [END, 0],
+ [END, -1],
+ [END, -5],
+ [END, -1000],
+ [END, -size+10],
+ [END, -size+5],
+ [END, -size+1],
+ [END, -size],
+ [END, -size-10],
+ [END, 10],
+ [CUR, 10],
+ [CUR, 10],
+ [CUR, 100],
+ [CUR, 1000],
+ [END, -1000],
+ [CUR, 100],
+ [CUR, 900],
+ [CUR, 100],
+ [CUR, 100],
+ ];
+
+ let pos = 0;
+ for (var test of tests) {
+ let didThrow = false;
+ try {
+ partialFileStream.seek(test[0], test[1]);
+ }
+ catch (ex) {
+ didThrow = true;
+ }
+
+ let newPos = test[0] == SET ? test[1] :
+ test[0] == CUR ? pos + test[1] :
+ size + test[1];
+ if (newPos > size || newPos < 0) {
+ do_check_true(didThrow);
+ }
+ else {
+ do_check_false(didThrow);
+ pos = newPos;
+ }
+
+ do_check_eq(partialFileStream.tell(), pos);
+ do_check_eq(partialFileStream.available(), size - pos);
+ }
+}
+
+function test_seek_then_read(fileStreamA, fileStreamB, size) {
+ // For now we only test seeking inside the file since our existing file
+ // streams behave very strange when seeking to past the end of the file.
+ if (size < 20000) {
+ return;
+ }
+
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+ let streamA = new BinaryInputStream(fileStreamA);
+ let streamB = new BinaryInputStream(fileStreamB);
+
+ let read = {};
+
+ var tests = [
+ [SET, 0],
+ [read, 1000],
+ [read, 1000],
+ [SET, 5],
+ [read, 1000],
+ [read, 5000],
+ [CUR, 100],
+ [read, 1000],
+ [read, 5000],
+ [CUR, -100],
+ [read, 1000],
+ [CUR, -100],
+ [read, 5000],
+ [END, -10],
+ [read, 10],
+ [END, -100],
+ [read, 101],
+ [CUR, -100],
+ [read, 10],
+ [SET, 0],
+ [read, 20000],
+ [read, 1],
+ [read, 100],
+ ];
+
+ for (var test of tests) {
+ if (test[0] === read) {
+
+ let didThrowA = false;
+ let didThrowB = false;
+
+ let bytesA, bytesB;
+ try {
+ bytesA = streamA.readBytes(test[1]);
+ }
+ catch (ex) {
+ didThrowA = true;
+ }
+ try {
+ bytesB = streamB.readBytes(test[1]);
+ }
+ catch (ex) {
+ didThrowB = true;
+ }
+
+ do_check_eq(didThrowA, didThrowB);
+ do_check_true(bytesA == bytesB);
+ }
+ else {
+ fileStreamA.seek(test[0], test[1]);
+ fileStreamB.seek(test[0], test[1]);
+ }
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+ do_check_eq(fileStreamA.available(), fileStreamB.available());
+ }
+}
+
+function test_text_portion(start, length) {
+ let subFile = create_temp_file(test_file_data.substr(start, length));
+
+ let streamTests = [
+ test_readline,
+ test_seek_then_readline,
+ ];
+
+ for (var test of streamTests) {
+ let fileStream = new_file_input_stream(subFile)
+ .QueryInterface(Ci.nsILineInputStream);
+ let partialStream = new_partial_file_input_stream(do_get_file(binary_test_file_name),
+ start, length)
+ .QueryInterface(Ci.nsILineInputStream);
+ test(fileStream, partialStream, length);
+ fileStream.close();
+ partialStream.close();
+ }
+}
+
+function test_readline(fileStreamA, fileStreamB)
+{
+ let moreA = true, moreB;
+ while(moreA) {
+ let lineA, lineB;
+ [moreA, lineA] = read_line_stream(fileStreamA);
+ [moreB, lineB] = read_line_stream(fileStreamB);
+ do_check_eq(moreA, moreB);
+ do_check_true(lineA.value == lineB.value);
+ }
+}
+
+function test_seek_then_readline(fileStreamA, fileStreamB, size) {
+ // For now we only test seeking inside the file since our existing file
+ // streams behave very strange when seeking to past the end of the file.
+ if (size < 100) {
+ return;
+ }
+
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+
+ let read = {};
+
+ var tests = [
+ [SET, 0],
+ [read, 5],
+ [read, 5],
+ [SET, 5],
+ [read, 5],
+ [read, 15],
+ [CUR, 100],
+ [read, 5],
+ [read, 15],
+ [CUR, -100],
+ [read, 5],
+ [CUR, -100],
+ [read, 25],
+ [END, -10],
+ [read, 1],
+ [END, -50],
+ [read, 30],
+ [read, 1],
+ [read, 1],
+ [CUR, -100],
+ [read, 1],
+ [SET, 0],
+ [read, 10000],
+ [read, 1],
+ [read, 1],
+ [SET, 0],
+ [read, 1],
+ ];
+
+ for (var test of tests) {
+ if (test[0] === read) {
+
+ for (let i = 0; i < test[1]; ++i) {
+ let didThrowA = false;
+ let didThrowB = false;
+
+ let lineA, lineB, moreA, moreB;
+ try {
+ [moreA, lineA] = read_line_stream(fileStreamA);
+ }
+ catch (ex) {
+ didThrowA = true;
+ }
+ try {
+ [moreB, lineB] = read_line_stream(fileStreamB);
+ }
+ catch (ex) {
+ didThrowB = true;
+ }
+
+ do_check_eq(didThrowA, didThrowB);
+ do_check_eq(moreA, moreB);
+ do_check_true(lineA == lineB);
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+ do_check_eq(fileStreamA.available(), fileStreamB.available());
+ if (!moreA)
+ break;
+ }
+ }
+ else {
+ if (!(test[0] == CUR && (test[1] > fileStreamA.available() ||
+ test[1] < -fileStreamA.tell()))) {
+ fileStreamA.seek(test[0], test[1]);
+ fileStreamB.seek(test[0], test[1]);
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+ do_check_eq(fileStreamA.available(), fileStreamB.available());
+ }
+ }
+ }
+}
+
+function read_line_stream(stream) {
+ let line = {};
+ let more = stream.readLine(line);
+ return [more, line.value];
+}
+
+function new_file_input_stream(file) {
+ var stream =
+ Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ stream.init(file, PR_RDONLY, 0, 0);
+ return stream.QueryInterface(Ci.nsIInputStream);
+}
+
+function new_partial_file_input_stream(file, start, length, flags) {
+ var stream =
+ Cc["@mozilla.org/network/partial-file-input-stream;1"]
+ .createInstance(Ci.nsIPartialFileInputStream);
+ stream.init(file, start, length, PR_RDONLY, 0, flags || 0);
+ return stream.QueryInterface(Ci.nsIInputStream);
+}
+
+function create_temp_file(data) {
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("fileinputstream-test-file.tmp");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ do_check_eq(ostream.write(data, data.length), data.length);
+ ostream.close();
+
+ return file;
+}
diff --git a/netwerk/test/unit/test_file_protocol.js b/netwerk/test/unit/test_file_protocol.js
new file mode 100644
index 000000000..2f1c5eb7f
--- /dev/null
+++ b/netwerk/test/unit/test_file_protocol.js
@@ -0,0 +1,251 @@
+/* run some tests on the file:// protocol handler */
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const PR_RDONLY = 0x1; // see prio.h
+
+const special_type = "application/x-our-special-type";
+
+[
+ test_read_file,
+ test_read_dir_1,
+ test_read_dir_2,
+ test_upload_file,
+ test_load_replace,
+ do_test_finished
+].forEach(add_test);
+
+function getFile(key) {
+ var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ return dirSvc.get(key, Components.interfaces.nsILocalFile);
+}
+
+function new_file_input_stream(file, buffered) {
+ var stream =
+ Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ stream.init(file, PR_RDONLY, 0, 0);
+ if (!buffered)
+ return stream;
+
+ var buffer =
+ Cc["@mozilla.org/network/buffered-input-stream;1"].
+ createInstance(Ci.nsIBufferedInputStream);
+ buffer.init(stream, 4096);
+ return buffer;
+}
+
+function new_file_channel(file) {
+ var ios =
+ Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return NetUtil.newChannel({
+ uri: ios.newFileURI(file),
+ loadUsingSystemPrincipal: true
+ });
+}
+
+/*
+ * stream listener
+ * this listener has some additional file-specific tests, so we can't just use
+ * ChannelListener here.
+ */
+function FileStreamListener(closure) {
+ this._closure = closure;
+}
+FileStreamListener.prototype = {
+ _closure: null,
+ _buffer: "",
+ _got_onstartrequest: false,
+ _got_onstoprequest: false,
+ _contentLen: -1,
+
+ _isDir: function(request) {
+ request.QueryInterface(Ci.nsIFileChannel);
+ return request.file.isDirectory();
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ if (this._got_onstartrequest)
+ do_throw("Got second onStartRequest event!");
+ this._got_onstartrequest = true;
+
+ if (!this._isDir(request)) {
+ request.QueryInterface(Ci.nsIChannel);
+ this._contentLen = request.contentLength;
+ if (this._contentLen == -1)
+ do_throw("Content length is unknown in onStartRequest!");
+ }
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ if (!this._got_onstartrequest)
+ do_throw("onDataAvailable without onStartRequest event!");
+ if (this._got_onstoprequest)
+ do_throw("onDataAvailable after onStopRequest event!");
+ if (!request.isPending())
+ do_throw("request reports itself as not pending from onStartRequest!");
+
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest: function(request, context, status) {
+ if (!this._got_onstartrequest)
+ do_throw("onStopRequest without onStartRequest event!");
+ if (this._got_onstoprequest)
+ do_throw("Got second onStopRequest event!");
+ this._got_onstoprequest = true;
+ if (!Components.isSuccessCode(status))
+ do_throw("Failed to load file: " + status.toString(16));
+ if (status != request.status)
+ do_throw("request.status does not match status arg to onStopRequest!");
+ if (request.isPending())
+ do_throw("request reports itself as pending from onStopRequest!");
+ if (this._contentLen != -1 && this._buffer.length != this._contentLen)
+ do_throw("did not read nsIChannel.contentLength number of bytes!");
+
+ this._closure(this._buffer);
+ }
+};
+
+function test_read_file() {
+ dump("*** test_read_file\n");
+
+ var file = do_get_file("../unit/data/test_readline6.txt");
+ var chan = new_file_channel(file);
+
+ function on_read_complete(data) {
+ dump("*** test_read_file.on_read_complete\n");
+
+ // bug 326693
+ if (chan.contentType != special_type)
+ do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" +
+ special_type + ">")
+
+ /* read completed successfully. now read data directly from file,
+ and compare the result. */
+ var stream = new_file_input_stream(file, false);
+ var result = read_stream(stream, stream.available());
+ if (result != data)
+ do_throw("Stream contents do not match with direct read!");
+ run_next_test();
+ }
+
+ chan.contentType = special_type;
+ chan.asyncOpen2(new FileStreamListener(on_read_complete));
+}
+
+function do_test_read_dir(set_type, expected_type) {
+ dump("*** test_read_dir(" + set_type + ", " + expected_type + ")\n");
+
+ var file = do_get_tempdir();
+ var chan = new_file_channel(file);
+
+ function on_read_complete(data) {
+ dump("*** test_read_dir.on_read_complete(" + set_type + ", " + expected_type + ")\n");
+
+ // bug 326693
+ if (chan.contentType != expected_type)
+ do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" +
+ expected_type + ">")
+
+ run_next_test();
+ }
+
+ if (set_type)
+ chan.contentType = expected_type;
+ chan.asyncOpen2(new FileStreamListener(on_read_complete));
+}
+
+function test_read_dir_1() {
+ return do_test_read_dir(false, "application/http-index-format");
+}
+
+function test_read_dir_2() {
+ return do_test_read_dir(true, special_type);
+}
+
+function test_upload_file() {
+ dump("*** test_upload_file\n");
+
+ var file = do_get_file("../unit/data/test_readline6.txt"); // file to upload
+ var dest = do_get_tempdir(); // file upload destination
+ dest.append("junk.dat");
+ dest.createUnique(dest.NORMAL_FILE_TYPE, 0o600);
+
+ var uploadstream = new_file_input_stream(file, true);
+
+ var chan = new_file_channel(dest);
+ chan.QueryInterface(Ci.nsIUploadChannel);
+ chan.setUploadStream(uploadstream, "", file.fileSize);
+
+ function on_upload_complete(data) {
+ dump("*** test_upload_file.on_upload_complete\n");
+
+ // bug 326693
+ if (chan.contentType != special_type)
+ do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" +
+ special_type + ">")
+
+ /* upload of file completed successfully. */
+ if (data.length != 0)
+ do_throw("Upload resulted in data!");
+
+ var oldstream = new_file_input_stream(file, false);
+ var newstream = new_file_input_stream(dest, false);
+ var olddata = read_stream(oldstream, oldstream.available());
+ var newdata = read_stream(newstream, newstream.available());
+ if (olddata != newdata)
+ do_throw("Stream contents do not match after file copy!");
+ oldstream.close();
+ newstream.close();
+
+ /* cleanup... also ensures that the destination file is not in
+ use when OnStopRequest is called. */
+ try {
+ dest.remove(false);
+ } catch (e) {
+ dump(e + "\n");
+ do_throw("Unable to remove uploaded file!\n");
+ }
+
+ run_next_test();
+ }
+
+ chan.contentType = special_type;
+ chan.asyncOpen2(new FileStreamListener(on_upload_complete));
+}
+
+function test_load_replace() {
+ // lnk files should resolve to their targets
+ if (mozinfo.os == "win") {
+ dump("*** test_load_replace\n");
+ file = do_get_file("data/system_root.lnk", false);
+ var chan = new_file_channel(file);
+
+ // The LOAD_REPLACE flag should be set
+ do_check_eq(chan.loadFlags & chan.LOAD_REPLACE, chan.LOAD_REPLACE);
+
+ // The original URI path should differ from the URI path
+ do_check_neq(chan.URI.path, chan.originalURI.path);
+
+ // The original URI path should be the same as the lnk file path
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ do_check_eq(chan.originalURI.path, ios.newFileURI(file).path);
+ }
+ run_next_test();
+}
+
+function run_test() {
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_filestreams.js b/netwerk/test/unit/test_filestreams.js
new file mode 100644
index 000000000..2736527a5
--- /dev/null
+++ b/netwerk/test/unit/test_filestreams.js
@@ -0,0 +1,298 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+// We need the profile directory so the test harness will clean up our test
+// files.
+do_get_profile();
+
+const OUTPUT_STREAM_CONTRACT_ID = "@mozilla.org/network/file-output-stream;1";
+const SAFE_OUTPUT_STREAM_CONTRACT_ID = "@mozilla.org/network/safe-file-output-stream;1";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helper Methods
+
+/**
+ * Generates a leafName for a file that does not exist, but does *not*
+ * create the file. Similar to createUnique except for the fact that createUnique
+ * does create the file.
+ *
+ * @param aFile
+ * The file to modify in order for it to have a unique leafname.
+ */
+function ensure_unique(aFile)
+{
+ ensure_unique.fileIndex = ensure_unique.fileIndex || 0;
+
+ var leafName = aFile.leafName;
+ while (aFile.clone().exists()) {
+ aFile.leafName = leafName + "_" + (ensure_unique.fileIndex++);
+ }
+}
+
+/**
+ * Tests for files being accessed at the right time. Streams that use
+ * DEFER_OPEN should only open or create the file when an operation is
+ * done, and not during Init().
+ *
+ * Note that for writing, we check for actual writing in test_NetUtil (async)
+ * and in sync_operations in this file (sync), whereas in this function we
+ * just check that the file is *not* created during init.
+ *
+ * @param aContractId
+ * The contract ID to use for the output stream
+ * @param aDeferOpen
+ * Whether to check with DEFER_OPEN or not
+ * @param aTrickDeferredOpen
+ * Whether we try to 'trick' deferred opens by changing the file object before
+ * the actual open. The stream should have a clone, so changes to the file
+ * object after Init and before Open should not affect it.
+ */
+function check_access(aContractId, aDeferOpen, aTrickDeferredOpen)
+{
+ const LEAF_NAME = "filestreams-test-file.tmp";
+ const TRICKY_LEAF_NAME = "BetYouDidNotExpectThat.tmp";
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append(LEAF_NAME);
+
+ // Writing
+
+ ensure_unique(file);
+ let ostream = Cc[aContractId].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0);
+ do_check_eq(aDeferOpen, !file.clone().exists()); // If defer, should not exist and vice versa
+ if (aDeferOpen) {
+ // File should appear when we do write to it.
+ if (aTrickDeferredOpen) {
+ // See |@param aDeferOpen| in the JavaDoc comment for this function
+ file.leafName = TRICKY_LEAF_NAME;
+ }
+ ostream.write("data", 4);
+ if (aTrickDeferredOpen) {
+ file.leafName = LEAF_NAME;
+ }
+ // We did a write, so the file should now exist
+ do_check_true(file.clone().exists());
+ }
+ ostream.close();
+
+ // Reading
+
+ ensure_unique(file);
+ let istream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ var initOk, getOk;
+ try {
+ istream.init(file, -1, 0, aDeferOpen ? Ci.nsIFileInputStream.DEFER_OPEN : 0);
+ initOk = true;
+ }
+ catch(e) {
+ initOk = false;
+ }
+ try {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(aFile, -1, 0, 0);
+ getOk = true;
+ }
+ catch(e) {
+ getOk = false;
+ }
+
+ // If the open is deferred, then Init should succeed even though the file we
+ // intend to read does not exist, and then trying to read from it should
+ // fail. The other case is where the open is not deferred, and there we should
+ // get an error when we Init (and also when we try to read).
+ do_check_true( (aDeferOpen && initOk && !getOk) ||
+ (!aDeferOpen && !initOk && !getOk) );
+ istream.close();
+}
+
+/**
+ * We test async operations in test_NetUtil.js, and here test for simple sync
+ * operations on input streams.
+ *
+ * @param aDeferOpen
+ * Whether to use DEFER_OPEN in the streams.
+ */
+function sync_operations(aDeferOpen)
+{
+ const TEST_DATA = "this is a test string";
+ const LEAF_NAME = "filestreams-test-file.tmp";
+
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append(LEAF_NAME);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let ostream = Cc[OUTPUT_STREAM_CONTRACT_ID].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, aDeferOpen ? Ci.nsIFileOutputStream.DEFER_OPEN : 0);
+
+ ostream.write(TEST_DATA, TEST_DATA.length);
+ ostream.close();
+
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, aDeferOpen ? Ci.nsIFileInputStream.DEFER_OPEN : 0);
+
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let string = {};
+ cstream.readString(-1, string);
+ cstream.close();
+ fstream.close();
+
+ do_check_eq(string.value, TEST_DATA);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+function test_access()
+{
+ check_access(OUTPUT_STREAM_CONTRACT_ID, false, false);
+}
+
+function test_access_trick()
+{
+ check_access(OUTPUT_STREAM_CONTRACT_ID, false, true);
+}
+
+function test_access_defer()
+{
+ check_access(OUTPUT_STREAM_CONTRACT_ID, true, false);
+}
+
+function test_access_defer_trick()
+{
+ check_access(OUTPUT_STREAM_CONTRACT_ID, true, true);
+}
+
+function test_access_safe()
+{
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, false, false);
+}
+
+function test_access_safe_trick()
+{
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, false, true);
+}
+
+function test_access_safe_defer()
+{
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, true, false);
+}
+
+function test_access_safe_defer_trick()
+{
+ check_access(SAFE_OUTPUT_STREAM_CONTRACT_ID, true, true);
+}
+
+function test_sync_operations()
+{
+ sync_operations();
+}
+
+function test_sync_operations_deferred()
+{
+ sync_operations(true);
+}
+
+function do_test_zero_size_buffered(disableBuffering)
+{
+ const LEAF_NAME = "filestreams-test-file.tmp";
+ const BUFFERSIZE = 4096;
+
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append(LEAF_NAME);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF |
+ Ci.nsIFileInputStream.REOPEN_ON_REWIND);
+
+ var buffered = Cc["@mozilla.org/network/buffered-input-stream;1"].
+ createInstance(Ci.nsIBufferedInputStream);
+ buffered.init(fstream, BUFFERSIZE);
+
+ if (disableBuffering) {
+ buffered.QueryInterface(Ci.nsIStreamBufferAccess).disableBuffering();
+ }
+
+ // Scriptable input streams clamp read sizes to the return value of
+ // available(), so don't quite do what we want here.
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ cstream.init(buffered, "UTF-8", 0, 0);
+
+ do_check_eq(buffered.available(), 0);
+
+ // Now try reading from this stream
+ let string = {};
+ do_check_eq(cstream.readString(BUFFERSIZE, string), 0);
+ do_check_eq(string.value, "");
+
+ // Now check that available() throws
+ var exceptionThrown = false;
+ try {
+ do_check_eq(buffered.available(), 0);
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ do_check_true(exceptionThrown);
+
+ // OK, now seek back to start
+ buffered.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+
+ // Now check that available() does not throw
+ exceptionThrown = false;
+ try {
+ do_check_eq(buffered.available(), 0);
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ do_check_false(exceptionThrown);
+}
+
+function test_zero_size_buffered()
+{
+ do_test_zero_size_buffered(false);
+ do_test_zero_size_buffered(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Runner
+
+var tests = [
+ test_access,
+ test_access_trick,
+ test_access_defer,
+ test_access_defer_trick,
+ test_access_safe,
+ test_access_safe_trick,
+ test_access_safe_defer,
+ test_access_safe_defer_trick,
+ test_sync_operations,
+ test_sync_operations_deferred,
+ test_zero_size_buffered,
+];
+
+function run_test()
+{
+ tests.forEach(function(test) {
+ test();
+ });
+}
+
diff --git a/netwerk/test/unit/test_freshconnection.js b/netwerk/test/unit/test_freshconnection.js
new file mode 100644
index 000000000..c1403f517
--- /dev/null
+++ b/netwerk/test/unit/test_freshconnection.js
@@ -0,0 +1,30 @@
+// This is essentially a debug mode crashtest to make sure everything
+// involved in a reload runs on the right thread. It relies on the
+// assertions in necko.
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ do_test_finished();
+ },
+};
+
+function run_test() {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:4444",
+ loadUsingSystemPrincipal: true
+ });
+ chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen2(listener);
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_getHost.js b/netwerk/test/unit/test_getHost.js
new file mode 100644
index 000000000..78e84a871
--- /dev/null
+++ b/netwerk/test/unit/test_getHost.js
@@ -0,0 +1,68 @@
+// Test getLocalHost/getLocalPort and getRemoteHost/getRemotePort.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+var gotOnStartRequest = false;
+
+function CheckGetHostListener() {}
+
+CheckGetHostListener.prototype = {
+ onStartRequest: function(request, context) {
+ dump("*** listener onStartRequest\n");
+
+ gotOnStartRequest = true;
+
+ request.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+ try {
+ do_check_eq(request.localAddress, "127.0.0.1");
+ do_check_eq(request.localPort > 0, true);
+ do_check_neq(request.localPort, PORT);
+ do_check_eq(request.remoteAddress, "127.0.0.1");
+ do_check_eq(request.remotePort, PORT);
+ } catch (e) {
+ do_check_true(0, "Get local/remote host/port throws an error!");
+ }
+ },
+
+ onStopRequest: function(request, context, statusCode) {
+ dump("*** listener onStopRequest\n");
+
+ do_check_eq(gotOnStartRequest, true);
+ httpserver.stop(do_test_finished);
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports)
+ )
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+}
+
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ var responseBody = "blah blah";
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/testdir", test_handler);
+
+ var channel = make_channel("http://localhost:" + PORT + "/testdir");
+ channel.asyncOpen2(new CheckGetHostListener());
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_gre_resources.js b/netwerk/test/unit/test_gre_resources.js
new file mode 100644
index 000000000..3287fae66
--- /dev/null
+++ b/netwerk/test/unit/test_gre_resources.js
@@ -0,0 +1,31 @@
+// test that things that are expected to be in gre-resources are still there
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var ios = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService);
+
+function wrapInputStream(input)
+{
+ var nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+ var factory = Components.classes["@mozilla.org/scriptableinputstream;1"];
+ var wrapper = factory.createInstance(nsIScriptableInputStream);
+ wrapper.init(input);
+ return wrapper;
+}
+
+function check_file(file) {
+ var channel = NetUtil.newChannel({
+ uri: "resource://gre-resources/"+file,
+ loadUsingSystemPrincipal: true
+ });
+ try {
+ let instr = wrapInputStream(channel.open2());
+ do_check_true(instr.read(1024).length > 0)
+ } catch (e) {
+ do_throw("Failed to read " + file + " from gre-resources:"+e)
+ }
+}
+
+function run_test() {
+ for (let file of ["ua.css"])
+ check_file(file)
+}
diff --git a/netwerk/test/unit/test_gzipped_206.js b/netwerk/test/unit/test_gzipped_206.js
new file mode 100644
index 000000000..8e4eaa4c7
--- /dev/null
+++ b/netwerk/test/unit/test_gzipped_206.js
@@ -0,0 +1,94 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+var httpserver = null;
+
+// testString = "This is a slightly longer test\n";
+const responseBody = [0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78, 0x74, 0x66, 0x69,
+ 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85,
+ 0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c, 0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85,
+ 0x92, 0xd4, 0xe2, 0x12, 0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00];
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var doRangeResponse = false;
+
+function cachedHandler(metadata, response) {
+ response.setHeader("Content-Type", "application/x-gzip", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=3600000"); // avoid validation
+
+ var body = responseBody;
+
+ if (doRangeResponse) {
+ do_check_true(metadata.hasHeader("Range"));
+ var matches = metadata.getHeader("Range").match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+ var from = (matches[1] === undefined) ? 0 : matches[1];
+ var to = (matches[2] === undefined) ? responseBody.length - 1 : matches[2];
+ if (from >= responseBody.length) {
+ response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
+ response.setHeader("Content-Range", "*/" + responseBody.length, false);
+ return;
+ }
+ body = body.slice(from, to + 1);
+ response.setHeader("Content-Length", "" + (to + 1 - from));
+ // always respond to successful range requests with 206
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", from + "-" + to + "/" + responseBody.length, false);
+ } else {
+ // This response will get cut off prematurely
+ response.setHeader("Content-Length", "" + responseBody.length);
+ response.setHeader("Accept-Ranges", "bytes");
+ body = body.slice(0, 17); // slice off a piece to send first
+ doRangeResponse = true;
+ }
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(body, body.length);
+ response.finish();
+}
+
+function continue_test(request, data) {
+ do_check_eq(17, data.length);
+ var chan = make_channel("http://localhost:" +
+ httpserver.identity.primaryPort + "/cached/test.gz");
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_GZIP));
+}
+
+var enforcePref;
+
+function finish_test(request, data, ctx) {
+ do_check_eq(request.status, 0);
+ do_check_eq(data.length, responseBody.length);
+ for (var i = 0; i < data.length; ++i) {
+ do_check_eq(data.charCodeAt(i), responseBody[i]);
+ }
+ Services.prefs.setBoolPref("network.http.enforce-framing.http1", enforcePref);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ enforcePref = Services.prefs.getBoolPref("network.http.enforce-framing.http1");
+ Services.prefs.setBoolPref("network.http.enforce-framing.http1", false);
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/cached/test.gz", cachedHandler);
+ httpserver.start(-1);
+
+ // wipe out cached content
+ evict_cache_entries();
+
+ var chan = make_channel("http://localhost:" +
+ httpserver.identity.primaryPort + "/cached/test.gz");
+ chan.asyncOpen2(new ChannelListener(continue_test, null, CL_EXPECT_GZIP | CL_IGNORE_CL));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_head.js b/netwerk/test/unit/test_head.js
new file mode 100644
index 000000000..058f47bcb
--- /dev/null
+++ b/netwerk/test/unit/test_head.js
@@ -0,0 +1,150 @@
+//
+// HTTP headers test
+//
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+var channel;
+var ios;
+
+var dbg=0
+if (dbg) { print("============== START =========="); }
+
+function run_test() {
+ setup_test();
+ do_test_pending();
+}
+
+function setup_test() {
+ if (dbg) { print("============== setup_test: in"); }
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ channel = setupChannel(testpath);
+
+ channel.setRequestHeader("ReplaceMe", "initial value", true);
+ var setOK = channel.getRequestHeader("ReplaceMe");
+ do_check_eq(setOK, "initial value");
+ channel.setRequestHeader("ReplaceMe", "replaced", false);
+ setOK = channel.getRequestHeader("ReplaceMe");
+ do_check_eq(setOK, "replaced");
+
+ channel.setRequestHeader("MergeMe", "foo1", true);
+ channel.setRequestHeader("MergeMe", "foo2", true);
+ channel.setRequestHeader("MergeMe", "foo3", true);
+ setOK = channel.getRequestHeader("MergeMe");
+ do_check_eq(setOK, "foo1, foo2, foo3");
+
+ channel.setEmptyRequestHeader("Empty");
+ setOK = channel.getRequestHeader("Empty");
+ do_check_eq(setOK, "");
+
+ channel.setRequestHeader("ReplaceWithEmpty", "initial value", true);
+ setOK = channel.getRequestHeader("ReplaceWithEmpty");
+ do_check_eq(setOK, "initial value");
+ channel.setEmptyRequestHeader("ReplaceWithEmpty");
+ setOK = channel.getRequestHeader("ReplaceWithEmpty");
+ do_check_eq(setOK, "");
+
+ channel.setEmptyRequestHeader("MergeWithEmpty");
+ setOK = channel.getRequestHeader("MergeWithEmpty");
+ do_check_eq(setOK, "");
+ channel.setRequestHeader("MergeWithEmpty", "foo", true);
+ setOK = channel.getRequestHeader("MergeWithEmpty");
+ do_check_eq(setOK, "foo");
+
+ var uri = NetUtil.newURI("http://foo1.invalid:80");
+ channel.referrer = uri;
+ do_check_true(channel.referrer.equals(uri));
+ setOK = channel.getRequestHeader("Referer");
+ do_check_eq(setOK, "http://foo1.invalid/");
+
+ uri = NetUtil.newURI("http://foo2.invalid:90/bar");
+ channel.referrer = uri;
+ setOK = channel.getRequestHeader("Referer");
+ do_check_eq(setOK, "http://foo2.invalid:90/bar");
+
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen2(new ChannelListener(checkRequestResponse, channel));
+
+ if (dbg) { print("============== setup_test: out"); }
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ if (dbg) { print("============== serverHandler: in"); }
+
+ var setOK = metadata.getHeader("ReplaceMe");
+ do_check_eq(setOK, "replaced");
+ setOK = metadata.getHeader("MergeMe");
+ do_check_eq(setOK, "foo1, foo2, foo3");
+ setOK = metadata.getHeader("Empty");
+ do_check_eq(setOK, "");
+ setOK = metadata.getHeader("ReplaceWithEmpty");
+ do_check_eq(setOK, "");
+ setOK = metadata.getHeader("MergeWithEmpty");
+ do_check_eq(setOK, "foo");
+ setOK = metadata.getHeader("Referer");
+ do_check_eq(setOK, "http://foo2.invalid:90/bar");
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setStatusLine("1.1", 200, "OK");
+
+ // note: httpd.js' "Response" class uses ',' (no space) for merge.
+ response.setHeader("httpdMerge", "bar1", false);
+ response.setHeader("httpdMerge", "bar2", true);
+ response.setHeader("httpdMerge", "bar3", true);
+ // Some special headers like Proxy-Authenticate merge with \n
+ response.setHeader("Proxy-Authenticate", "line 1", true);
+ response.setHeader("Proxy-Authenticate", "line 2", true);
+ response.setHeader("Proxy-Authenticate", "line 3", true);
+
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+
+ if (dbg) { print("============== serverHandler: out"); }
+}
+
+function checkRequestResponse(request, data, context) {
+ if (dbg) { print("============== checkRequestResponse: in"); }
+
+ do_check_eq(channel.responseStatus, 200);
+ do_check_eq(channel.responseStatusText, "OK");
+ do_check_true(channel.requestSucceeded);
+
+ var response = channel.getResponseHeader("httpdMerge");
+ do_check_eq(response, "bar1,bar2,bar3");
+ channel.setResponseHeader("httpdMerge", "bar", true);
+ do_check_eq(channel.getResponseHeader("httpdMerge"), "bar1,bar2,bar3, bar");
+
+ response = channel.getResponseHeader("Proxy-Authenticate");
+ do_check_eq(response, "line 1\nline 2\nline 3");
+
+ channel.contentCharset = "UTF-8";
+ do_check_eq(channel.contentCharset, "UTF-8");
+ do_check_eq(channel.contentType, "text/plain");
+ do_check_eq(channel.contentLength, httpbody.length);
+ do_check_eq(data, httpbody);
+
+ httpserver.stop(do_test_finished);
+ if (dbg) { print("============== checkRequestResponse: out"); }
+}
diff --git a/netwerk/test/unit/test_header_Accept-Language.js b/netwerk/test/unit/test_header_Accept-Language.js
new file mode 100644
index 000000000..63ea2552a
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language.js
@@ -0,0 +1,91 @@
+//
+// HTTP Accept-Language header test
+//
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var testpath = "/bug672448";
+
+function run_test() {
+ let intlPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).getBranch("intl.");
+
+ // Save old value of preference for later.
+ let oldPref = intlPrefs.getCharPref("accept_languages");
+
+ // Test different numbers of languages, to test different fractions.
+ let acceptLangTests = [
+ "qaa", // 1
+ "qaa,qab", // 2
+ "qaa,qab,qac,qad", // 4
+ "qaa,qab,qac,qad,qae,qaf,qag,qah", // 8
+ "qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj", // 10
+ "qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj,qak", // 11
+ "qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj,qak,qal,qam,qan,qao,qap,qaq,qar,qas,qat,qau", // 21
+ oldPref, // Restore old value of preference (and test it).
+ ];
+
+ let acceptLangTestsNum = acceptLangTests.length;
+
+ for (let i = 0; i < acceptLangTestsNum; i++) {
+ // Set preference to test value.
+ intlPrefs.setCharPref("accept_languages", acceptLangTests[i]);
+
+ // Test value.
+ test_accepted_languages();
+ }
+}
+
+function test_accepted_languages() {
+ let channel = setupChannel(testpath);
+
+ let AcceptLanguage = channel.getRequestHeader("Accept-Language");
+
+ let acceptedLanguages = AcceptLanguage.split(",");
+
+ let acceptedLanguagesLength = acceptedLanguages.length;
+
+ for (let i = 0; i < acceptedLanguagesLength; i++) {
+ let acceptedLanguage, qualityValue;
+
+ try {
+ // The q-value must conform to the definition in HTTP/1.1 Section 3.9.
+ [_, acceptedLanguage, qualityValue] = acceptedLanguages[i].trim().match(/^([a-z0-9_-]*?)(?:;q=(1(?:\.0{0,3})?|0(?:\.[0-9]{0,3})))?$/i);
+ } catch(e) {
+ do_throw("Invalid language tag or quality value: " + e);
+ }
+
+ if (i == 0) {
+ // The first language shouldn't have a quality value.
+ do_check_eq(qualityValue, undefined);
+ } else {
+ let decimalPlaces;
+
+ // When the number of languages is small, we keep the quality value to only one decimal place.
+ // Otherwise, it can be up to two decimal places.
+ if (acceptedLanguagesLength < 10) {
+ do_check_true(qualityValue.length == 3);
+
+ decimalPlaces = 1;
+ } else {
+ do_check_true(qualityValue.length >= 3);
+ do_check_true(qualityValue.length <= 4);
+
+ decimalPlaces = 2;
+ }
+
+ // All the other languages should have an evenly-spaced quality value.
+ do_check_eq(parseFloat(qualityValue).toFixed(decimalPlaces), (1.0 - ((1 / acceptedLanguagesLength) * i)).toFixed(decimalPlaces));
+ }
+ }
+}
+
+function setupChannel(path) {
+
+ let chan = NetUtil.newChannel ({
+ uri: "http://localhost:4444" + path,
+ loadUsingSystemPrincipal: true
+ });
+
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
diff --git a/netwerk/test/unit/test_header_Accept-Language_case.js b/netwerk/test/unit/test_header_Accept-Language_case.js
new file mode 100644
index 000000000..3ed901ab5
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language_case.js
@@ -0,0 +1,47 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var testpath = "/bug1054739";
+
+function run_test() {
+ let intlPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).getBranch("intl.");
+
+ let oldAcceptLangPref = intlPrefs.getCharPref("accept_languages");
+
+ let testData = [
+ ["en", "en"],
+ ["ast", "ast"],
+ ["fr-ca", "fr-CA"],
+ ["zh-yue", "zh-yue"],
+ ["az-latn", "az-Latn"],
+ ["sl-nedis", "sl-nedis"],
+ ["zh-hant-hk", "zh-Hant-HK"],
+ ["ZH-HANT-HK", "zh-Hant-HK"],
+ ["en-us-x-priv", "en-US-x-priv"],
+ ["en-us-x-twain", "en-US-x-twain"],
+ ["de, en-US, en", "de,en-US;q=0.7,en;q=0.3"],
+ ["de,en-us,en", "de,en-US;q=0.7,en;q=0.3"],
+ ["en-US, en", "en-US,en;q=0.5"],
+ ["EN-US;q=0.2, EN", "en-US,en;q=0.5"],
+ ];
+
+ for (let i = 0; i < testData.length; i++) {
+ let acceptLangPref = testData[i][0];
+ let expectedHeader = testData[i][1];
+
+ intlPrefs.setCharPref("accept_languages", acceptLangPref);
+ let acceptLangHeader = setupChannel(testpath).getRequestHeader("Accept-Language");
+ equal(acceptLangHeader, expectedHeader);
+ }
+
+ intlPrefs.setCharPref("accept_languages", oldAcceptLangPref);
+}
+
+function setupChannel(path) {
+ let uri = NetUtil.newURI("http://localhost:4444" + path, "", null);
+ let chan = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
diff --git a/netwerk/test/unit/test_headers.js b/netwerk/test/unit/test_headers.js
new file mode 100644
index 000000000..d9fecc11e
--- /dev/null
+++ b/netwerk/test/unit/test_headers.js
@@ -0,0 +1,186 @@
+//
+// cleaner HTTP header test infrastructure
+//
+// tests bugs: 589292, [add more here: see hg log for definitive list]
+//
+// TO ADD NEW TESTS:
+// 1) Increment up 'lastTest' to new number (say, "99")
+// 2) Add new test 'handler99' and 'completeTest99' functions.
+// 3) If your test should fail the necko channel, set
+// test_flags[99] = CL_EXPECT_FAILURE.
+//
+// TO DEBUG JUST ONE TEST: temporarily change firstTest and lastTest to equal
+// the test # you're interested in.
+//
+// For tests that need duplicate copies of headers to be sent, see
+// test_duplicate_headers.js
+
+var firstTest = 1; // set to test of interest when debugging
+var lastTest = 4; // set to test of interest when debugging
+////////////////////////////////////////////////////////////////////////////////
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var nextTest = firstTest;
+var test_flags = new Array();
+var testPathBase = "/test_headers";
+
+function run_test()
+{
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(nextTest);
+}
+
+function runNextTest()
+{
+ if (nextTest == lastTest) {
+ endTests();
+ return;
+ }
+ nextTest++;
+ // Make sure test functions exist
+ if (eval("handler" + nextTest) == undefined)
+ do_throw("handler" + nextTest + " undefined!");
+ if (eval("completeTest" + nextTest) == undefined)
+ do_throw("completeTest" + nextTest + " undefined!");
+
+ run_test_number(nextTest);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ return httpChan;
+}
+
+function endTests()
+{
+ httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: test Content-Disposition channel attributes
+function handler1(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Disposition", "attachment; filename=foo");
+ response.setHeader("Content-Type", "text/plain", false);
+ var body = "foo";
+}
+
+function completeTest1(request, data, ctx)
+{
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionFilename, "foo");
+ do_check_eq(chan.contentDispositionHeader, "attachment; filename=foo");
+ } catch (ex) {
+ do_throw("error parsing Content-Disposition: " + ex);
+ }
+ runNextTest();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: no filename
+function handler2(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Disposition", "attachment");
+ var body = "foo";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function completeTest2(request, data, ctx)
+{
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionHeader, "attachment");
+
+ filename = chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ do_print("correctly ate exception");
+ }
+ runNextTest();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: filename missing
+function handler3(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Disposition", "attachment; filename=");
+ var body = "foo";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function completeTest3(request, data, ctx)
+{
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionHeader, "attachment; filename=");
+
+ filename = chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ do_print("correctly ate exception");
+ }
+ runNextTest();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: inline
+function handler4(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Disposition", "inline");
+ var body = "foo";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function completeTest4(request, data, ctx)
+{
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_INLINE);
+ do_check_eq(chan.contentDispositionHeader, "inline");
+
+ filename = chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ do_print("correctly ate exception");
+ }
+ runNextTest();
+}
diff --git a/netwerk/test/unit/test_http2.js b/netwerk/test/unit/test_http2.js
new file mode 100644
index 000000000..3fefe707e
--- /dev/null
+++ b/netwerk/test/unit/test_http2.js
@@ -0,0 +1,1119 @@
+// test HTTP/2
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+// Generate a small and a large post with known pre-calculated md5 sums
+function generateContent(size) {
+ var content = "";
+ for (var i = 0; i < size; i++) {
+ content += "0";
+ }
+ return content;
+}
+
+var posts = [];
+posts.push(generateContent(10));
+posts.push(generateContent(250000));
+posts.push(generateContent(128000));
+
+// pre-calculated md5sums (in hex) of the above posts
+var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
+ '2ef8d3b6c8f329318eb1a119b12622b6'];
+
+var bigListenerData = generateContent(128 * 1024);
+var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836';
+
+function checkIsHttp2(request) {
+ try {
+ if (request.getResponseHeader("X-Firefox-Spdy") == "h2") {
+ if (request.getResponseHeader("X-Connection-Http2") == "yes") {
+ return true;
+ }
+ return false; // Weird case, but the server disagrees with us
+ }
+ } catch (e) {
+ // Nothing to do here
+ }
+ return false;
+}
+
+var Http2CheckListener = function() {};
+
+Http2CheckListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ isHttp2Connection: false,
+ shouldBeHttp2 : true,
+ accum : 0,
+ expected: -1,
+ shouldSucceed: true,
+
+ onStartRequest: function testOnStartRequest(request, ctx) {
+ this.onStartRequestFired = true;
+ if (this.shouldSucceed && !Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ } else if (!this.shouldSucceed && Components.isSuccessCode(request.status)) {
+ do_throw("Channel succeeded unexpectedly!");
+ }
+
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.requestSucceeded, this.shouldSucceed);
+ if (this.shouldSucceed) {
+ do_check_eq(request.responseStatus, 200);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.accum += cnt;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ if (this.expected != -1) {
+ do_check_eq(this.accum, this.expected);
+ }
+ if (this.shouldSucceed) {
+ do_check_true(Components.isSuccessCode(status));
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
+ } else {
+ do_check_false(Components.isSuccessCode(status));
+ }
+
+ run_next_test();
+ do_test_finished();
+ }
+};
+
+/*
+ * Support for testing valid multiplexing of streams
+ */
+
+var multiplexContent = generateContent(30*1024);
+var completed_channels = [];
+function register_completed_channel(listener) {
+ completed_channels.push(listener);
+ if (completed_channels.length == 2) {
+ do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID);
+ run_next_test();
+ do_test_finished();
+ }
+}
+
+/* Listener class to control the testing of multiplexing */
+var Http2MultiplexListener = function() {};
+
+Http2MultiplexListener.prototype = new Http2CheckListener();
+
+Http2MultiplexListener.prototype.streamID = 0;
+Http2MultiplexListener.prototype.buffer = "";
+
+Http2MultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID"));
+ var data = read_stream(stream, cnt);
+ this.buffer = this.buffer.concat(data);
+};
+
+Http2MultiplexListener.prototype.onStopRequest = function(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+ do_check_true(this.buffer == multiplexContent);
+
+ // This is what does most of the hard work for us
+ register_completed_channel(this);
+};
+
+// Does the appropriate checks for header gatewaying
+var Http2HeaderListener = function(name, callback) {
+ this.name = name;
+ this.callback = callback;
+};
+
+Http2HeaderListener.prototype = new Http2CheckListener();
+Http2HeaderListener.prototype.value = "";
+
+Http2HeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ var hvalue = request.getResponseHeader(this.name);
+ do_check_neq(hvalue, "");
+ this.callback(hvalue);
+ read_stream(stream, cnt);
+};
+
+var Http2PushListener = function() {};
+
+Http2PushListener.prototype = new Http2CheckListener();
+
+Http2PushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ if (request.originalURI.spec == "https://localhost:" + serverPort + "/push.js" ||
+ request.originalURI.spec == "https://localhost:" + serverPort + "/push2.js" ||
+ request.originalURI.spec == "https://localhost:" + serverPort + "/push5.js") {
+ do_check_eq(request.getResponseHeader("pushed"), "yes");
+ }
+ read_stream(stream, cnt);
+};
+
+const pushHdrTxt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+const pullHdrTxt = pushHdrTxt.split('').reverse().join('');
+
+function checkContinuedHeaders(getHeader, headerPrefix, headerText) {
+ for (var i = 0; i < 265; i++) {
+ do_check_eq(getHeader(headerPrefix + 1), headerText);
+ }
+}
+
+var Http2ContinuedHeaderListener = function() {};
+
+Http2ContinuedHeaderListener.prototype = new Http2CheckListener();
+
+Http2ContinuedHeaderListener.prototype.onStopsLeft = 2;
+
+Http2ContinuedHeaderListener.prototype.QueryInterface = function (aIID) {
+ if (aIID.equals(Ci.nsIHttpPushListener) ||
+ aIID.equals(Ci.nsIStreamListener))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+};
+
+Http2ContinuedHeaderListener.prototype.getInterface = function(aIID) {
+ return this.QueryInterface(aIID);
+};
+
+Http2ContinuedHeaderListener.prototype.onDataAvailable = function (request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ if (request.originalURI.spec == "https://localhost:" + serverPort + "/continuedheaders") {
+ // This is the original request, so the only one where we'll have continued response headers
+ checkContinuedHeaders(request.getResponseHeader, "X-Pull-Test-Header-", pullHdrTxt);
+ }
+ read_stream(stream, cnt);
+};
+
+Http2ContinuedHeaderListener.prototype.onStopRequest = function (request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(Components.isSuccessCode(status));
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+
+ --this.onStopsLeft;
+ if (this.onStopsLeft === 0) {
+ run_next_test();
+ do_test_finished();
+ }
+};
+
+Http2ContinuedHeaderListener.prototype.onPush = function(associatedChannel, pushChannel) {
+ do_check_eq(associatedChannel.originalURI.spec, "https://localhost:" + serverPort + "/continuedheaders");
+ do_check_eq(pushChannel.getRequestHeader("x-pushed-request"), "true");
+ checkContinuedHeaders(pushChannel.getRequestHeader, "X-Push-Test-Header-", pushHdrTxt);
+
+ pushChannel.asyncOpen2(this);
+};
+
+// Does the appropriate checks for a large GET response
+var Http2BigListener = function() {};
+
+Http2BigListener.prototype = new Http2CheckListener();
+Http2BigListener.prototype.buffer = "";
+
+Http2BigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.buffer = this.buffer.concat(read_stream(stream, cnt));
+ // We know the server should send us the same data as our big post will be,
+ // so the md5 should be the same
+ do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5"));
+};
+
+Http2BigListener.prototype.onStopRequest = function(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+
+ // Don't want to flood output, so don't use do_check_eq
+ do_check_true(this.buffer == bigListenerData);
+
+ run_next_test();
+ do_test_finished();
+};
+
+var Http2HugeSuspendedListener = function() {};
+
+Http2HugeSuspendedListener.prototype = new Http2CheckListener();
+Http2HugeSuspendedListener.prototype.count = 0;
+
+Http2HugeSuspendedListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.count += cnt;
+ read_stream(stream, cnt);
+};
+
+Http2HugeSuspendedListener.prototype.onStopRequest = function(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+ do_check_eq(this.count, 1024 * 1024 * 1); // 1mb of data expected
+ run_next_test();
+ do_test_finished();
+};
+
+// Does the appropriate checks for POSTs
+var Http2PostListener = function(expected_md5) {
+ this.expected_md5 = expected_md5;
+};
+
+Http2PostListener.prototype = new Http2CheckListener();
+Http2PostListener.prototype.expected_md5 = "";
+
+Http2PostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5"));
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+var ResumeStalledChannelListener = function() {};
+
+ResumeStalledChannelListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ isHttp2Connection: false,
+ shouldBeHttp2 : true,
+ resumable : null,
+
+ onStartRequest: function testOnStartRequest(request, ctx) {
+ this.onStartRequestFired = true;
+ if (!Components.isSuccessCode(request.status))
+ do_throw("Channel should have a success code! (" + request.status + ")");
+
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(request.requestSucceeded, true);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(Components.isSuccessCode(status));
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
+ this.resumable.resume();
+ }
+};
+
+// test a large download that creates stream flow control and
+// confirm we can do another independent stream while the download
+// stream is stuck
+function test_http2_blocking_download() {
+ var chan = makeChan("https://localhost:" + serverPort + "/bigdownload");
+ var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.initialRwin = 500000; // make the stream.suspend push back in h2
+ var listener = new Http2CheckListener();
+ listener.expected = 3 * 1024 * 1024;
+ chan.asyncOpen2(listener);
+ chan.suspend();
+ // wait 5 seconds so that stream flow control kicks in and then see if we
+ // can do a basic transaction (i.e. session not blocked). afterwards resume
+ // channel
+ do_timeout(5000, function() {
+ var simpleChannel = makeChan("https://localhost:" + serverPort + "/");
+ var sl = new ResumeStalledChannelListener();
+ sl.resumable = chan;
+ simpleChannel.asyncOpen2(sl);
+ });
+}
+
+// Make sure we make a HTTP2 connection and both us and the server mark it as such
+function test_http2_basic() {
+ var chan = makeChan("https://localhost:" + serverPort + "/");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_basic_unblocked_dep() {
+ var chan = makeChan("https://localhost:" + serverPort + "/basic_unblocked_dep");
+ var cos = chan.QueryInterface(Ci.nsIClassOfService);
+ cos.addClassFlags(Ci.nsIClassOfService.Unblocked);
+ var listener = new Http2CheckListener();
+ chan.asyncOpen2(listener);
+}
+
+// make sure we don't use h2 when disallowed
+function test_http2_nospdy() {
+ var chan = makeChan("https://localhost:" + serverPort + "/");
+ var listener = new Http2CheckListener();
+ var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.allowSpdy = false;
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen2(listener);
+}
+
+// Support for making sure XHR works over SPDY
+function checkXhr(xhr) {
+ if (xhr.readyState != 4) {
+ return;
+ }
+
+ do_check_eq(xhr.status, 200);
+ do_check_eq(checkIsHttp2(xhr), true);
+ run_next_test();
+ do_test_finished();
+}
+
+// Fires off an XHR request over h2
+function test_http2_xhr() {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ req.open("GET", "https://localhost:" + serverPort + "/", true);
+ req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
+ false);
+ req.send(null);
+}
+
+var concurrent_channels = [];
+
+var Http2ConcurrentListener = function() {};
+
+Http2ConcurrentListener.prototype = new Http2CheckListener();
+Http2ConcurrentListener.prototype.count = 0;
+Http2ConcurrentListener.prototype.target = 0;
+Http2ConcurrentListener.prototype.reset = 0;
+Http2ConcurrentListener.prototype.recvdHdr = 0;
+
+Http2ConcurrentListener.prototype.onStopRequest = function(request, ctx, status) {
+ this.count++;
+ do_check_true(this.isHttp2Connection);
+ if (this.recvdHdr > 0) {
+ do_check_eq(request.getResponseHeader("X-Recvd"), this.recvdHdr);
+ }
+
+ if (this.count == this.target) {
+ if (this.reset > 0) {
+ prefs.setIntPref("network.http.spdy.default-concurrent", this.reset);
+ }
+ run_next_test();
+ do_test_finished();
+ }
+};
+
+function test_http2_concurrent() {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.target = 201;
+ concurrent_listener.reset = prefs.getIntPref("network.http.spdy.default-concurrent");
+ prefs.setIntPref("network.http.spdy.default-concurrent", 100);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeChan("https://localhost:" + serverPort + "/750ms");
+ concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ concurrent_channels[i].asyncOpen2(concurrent_listener);
+ }
+}
+
+function test_http2_concurrent_post() {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.target = 8;
+ concurrent_listener.recvdHdr = posts[2].length;
+ concurrent_listener.reset = prefs.getIntPref("network.http.spdy.default-concurrent");
+ prefs.setIntPref("network.http.spdy.default-concurrent", 3);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeChan("https://localhost:" + serverPort + "/750msPost");
+ concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.data = posts[2];
+ var uchan = concurrent_channels[i].QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+ concurrent_channels[i].requestMethod = "POST";
+ concurrent_channels[i].asyncOpen2(concurrent_listener);
+ }
+}
+
+// Test to make sure we get multiplexing right
+function test_http2_multiplex() {
+ var chan1 = makeChan("https://localhost:" + serverPort + "/multiplex1");
+ var chan2 = makeChan("https://localhost:" + serverPort + "/multiplex2");
+ var listener1 = new Http2MultiplexListener();
+ var listener2 = new Http2MultiplexListener();
+ chan1.asyncOpen2(listener1);
+ chan2.asyncOpen2(listener2);
+}
+
+// Test to make sure we gateway non-standard headers properly
+function test_http2_header() {
+ var chan = makeChan("https://localhost:" + serverPort + "/header");
+ var hvalue = "Headers are fun";
+ chan.setRequestHeader("X-Test-Header", hvalue, false);
+ var listener = new Http2HeaderListener("X-Received-Test-Header", function(received_hvalue) {
+ do_check_eq(received_hvalue, hvalue);
+ });
+ chan.asyncOpen2(listener);
+}
+
+// Test to make sure cookies are split into separate fields before compression
+function test_http2_cookie_crumbling() {
+ var chan = makeChan("https://localhost:" + serverPort + "/cookie_crumbling");
+ var cookiesSent = ['a=b', 'c=d01234567890123456789', 'e=f'].sort();
+ chan.setRequestHeader("Cookie", cookiesSent.join('; '), false);
+ var listener = new Http2HeaderListener("X-Received-Header-Pairs", function(pairsReceived) {
+ var cookiesReceived = JSON.parse(pairsReceived).filter(function(pair) {
+ return pair[0] == 'cookie';
+ }).map(function(pair) {
+ return pair[1];
+ }).sort();
+ do_check_eq(cookiesReceived.length, cookiesSent.length);
+ cookiesReceived.forEach(function(cookieReceived, index) {
+ do_check_eq(cookiesSent[index], cookieReceived)
+ });
+ });
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push2() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push3() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push2");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push4() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push2.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push5() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push5");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push6() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push5.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+// this is a basic test where the server sends a simple document with 2 header
+// blocks. bug 1027364
+function test_http2_doubleheader() {
+ var chan = makeChan("https://localhost:" + serverPort + "/doubleheader");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen2(listener);
+}
+
+// Make sure we handle GETs that cover more than 2 frames properly
+function test_http2_big() {
+ var chan = makeChan("https://localhost:" + serverPort + "/big");
+ var listener = new Http2BigListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_huge_suspended() {
+ var chan = makeChan("https://localhost:" + serverPort + "/huge");
+ var listener = new Http2HugeSuspendedListener();
+ chan.asyncOpen2(listener);
+ chan.suspend();
+ do_timeout(500, chan.resume);
+}
+
+// Support for doing a POST
+function do_post(content, chan, listener, method) {
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.data = content;
+
+ var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+
+ chan.requestMethod = method;
+
+ chan.asyncOpen2(listener);
+}
+
+// Make sure we can do a simple POST
+function test_http2_post() {
+ var chan = makeChan("https://localhost:" + serverPort + "/post");
+ var listener = new Http2PostListener(md5s[0]);
+ do_post(posts[0], chan, listener, "POST");
+}
+
+// Make sure we can do a simple PATCH
+function test_http2_patch() {
+ var chan = makeChan("https://localhost:" + serverPort + "/patch");
+ var listener = new Http2PostListener(md5s[0]);
+ do_post(posts[0], chan, listener, "PATCH");
+}
+
+// Make sure we can do a POST that covers more than 2 frames
+function test_http2_post_big() {
+ var chan = makeChan("https://localhost:" + serverPort + "/post");
+ var listener = new Http2PostListener(md5s[1]);
+ do_post(posts[1], chan, listener, "POST");
+}
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv = null;
+var httpserv2 = null;
+var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+var altsvcClientListener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ do_check_eq(request.status, Components.results.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, cx, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var isHttp2Connection = checkIsHttp2(request.QueryInterface(Components.interfaces.nsIHttpChannel));
+ if (!isHttp2Connection) {
+ dump("/altsvc1 not over h2 yet - retry\n");
+ var chan = makeChan("http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1")
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ // we use this header to tell the server to issue a altsvc frame for the
+ // speficied origin we will use in the next part of the test
+ chan.setRequestHeader("x-redirect-origin",
+ "http://foo.example.com:" + httpserv2.identity.primaryPort, false);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen2(altsvcClientListener);
+ } else {
+ do_check_true(isHttp2Connection);
+ var chan = makeChan("http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2")
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen2(altsvcClientListener2);
+ }
+ }
+};
+
+var altsvcClientListener2 = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ do_check_eq(request.status, Components.results.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, cx, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var isHttp2Connection = checkIsHttp2(request.QueryInterface(Components.interfaces.nsIHttpChannel));
+ if (!isHttp2Connection) {
+ dump("/altsvc2 not over h2 yet - retry\n");
+ var chan = makeChan("http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2")
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen2(altsvcClientListener2);
+ } else {
+ do_check_true(isHttp2Connection);
+ run_next_test();
+ do_test_finished();
+ }
+ }
+};
+
+function altsvcHttp1Server(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Alt-Svc", 'h2=":' + serverPort + '"', false);
+ var body = "this is where a cool kid would write something neat.\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+
+ var body = '{"http://foo.example.com:' + httpserv.identity.primaryPort + '": { "tls-ports": [' + serverPort + '] }}';
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function altsvcHttp1Server2(metadata, response) {
+// this server should never be used thanks to an alt svc frame from the
+// h2 server.. but in case of some async lag in setting the alt svc route
+// up we have it.
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Connection", "close", false);
+ var body = "hanging.\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function h1ServerWK2(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+ response.setHeader("Connection", "close", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Method", "GET", false);
+
+ var body = '{"http://foo.example.com:' + httpserv2.identity.primaryPort + '": { "tls-ports": [' + serverPort + '] }}';
+ response.bodyOutputStream.write(body, body.length);
+}
+function test_http2_altsvc() {
+ var chan = makeChan("http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1")
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.asyncOpen2(altsvcClientListener);
+}
+
+var Http2PushApiListener = function() {};
+
+Http2PushApiListener.prototype = {
+ checksPending: 9, // 4 onDataAvailable and 5 onStop
+
+ getInterface: function(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIHttpPushListener) ||
+ aIID.equals(Ci.nsIStreamListener))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIHttpPushListener
+ onPush: function onPush(associatedChannel, pushChannel) {
+ do_check_eq(associatedChannel.originalURI.spec, "https://localhost:" + serverPort + "/pushapi1");
+ do_check_eq (pushChannel.getRequestHeader("x-pushed-request"), "true");
+
+ pushChannel.asyncOpen2(this);
+ if (pushChannel.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/2") {
+ pushChannel.cancel(Components.results.NS_ERROR_ABORT);
+ }
+ },
+
+ // normal Channel listeners
+ onStartRequest: function pushAPIOnStart(request, ctx) {
+ },
+
+ onDataAvailable: function pushAPIOnDataAvailable(request, ctx, stream, offset, cnt) {
+ do_check_neq(request.originalURI.spec, "https://localhost:" + serverPort + "/pushapi1/2");
+
+ var data = read_stream(stream, cnt);
+
+ if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1") {
+ do_check_eq(data[0], '0');
+ --this.checksPending;
+ } else if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/1") {
+ do_check_eq(data[0], '1');
+ --this.checksPending; // twice
+ } else if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/3") {
+ do_check_eq(data[0], '3');
+ --this.checksPending;
+ } else {
+ do_check_eq(true, false);
+ }
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/2") {
+ do_check_eq(request.status, Components.results.NS_ERROR_ABORT);
+ } else {
+ do_check_eq(request.status, Components.results.NS_OK);
+ }
+
+ --this.checksPending; // 5 times - one for each push plus the pull
+ if (!this.checksPending) {
+ run_next_test();
+ do_test_finished();
+ }
+ }
+};
+
+// pushAPI testcase 1 expects
+// 1 to pull /pushapi1 with 0
+// 2 to see /pushapi1/1 with 1
+// 3 to see /pushapi1/1 with 1 (again)
+// 4 to see /pushapi1/2 that it will cancel
+// 5 to see /pushapi1/3 with 3
+
+function test_http2_pushapi_1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/pushapi1");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushApiListener();
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen2(listener);
+}
+
+var WrongSuiteListener = function() {};
+WrongSuiteListener.prototype = new Http2CheckListener();
+WrongSuiteListener.prototype.shouldBeHttp2 = false;
+WrongSuiteListener.prototype.onStopRequest = function(request, ctx, status) {
+ prefs.setBoolPref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", true);
+ Http2CheckListener.prototype.onStopRequest.call(this);
+};
+
+// test that we use h1 without the mandatory cipher suite available
+function test_http2_wrongsuite() {
+ prefs.setBoolPref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", false);
+ var chan = makeChan("https://localhost:" + serverPort + "/wrongsuite");
+ chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION | Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ var listener = new WrongSuiteListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_h11required_stream() {
+ var chan = makeChan("https://localhost:" + serverPort + "/h11required_stream");
+ var listener = new Http2CheckListener();
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen2(listener);
+}
+
+function H11RequiredSessionListener () { }
+H11RequiredSessionListener.prototype = new Http2CheckListener();
+
+H11RequiredSessionListener.prototype.onStopRequest = function (request, ctx, status) {
+ var streamReused = request.getResponseHeader("X-H11Required-Stream-Ok");
+ do_check_eq(streamReused, "yes");
+
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
+
+ run_next_test();
+ do_test_finished();
+};
+
+function test_http2_h11required_session() {
+ var chan = makeChan("https://localhost:" + serverPort + "/h11required_session");
+ var listener = new H11RequiredSessionListener();
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_retry_rst() {
+ var chan = makeChan("https://localhost:" + serverPort + "/rstonce");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_continuations() {
+ var chan = makeChan("https://localhost:" + serverPort + "/continuedheaders");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2ContinuedHeaderListener();
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen2(listener);
+}
+
+function Http2IllegalHpackValidationListener() { }
+Http2IllegalHpackValidationListener.prototype = new Http2CheckListener();
+Http2IllegalHpackValidationListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackValidationListener.prototype.onStopRequest = function (request, ctx, status) {
+ var wentAway = (request.getResponseHeader('X-Did-Goaway') === 'yes');
+ do_check_eq(wentAway, this.shouldGoAway);
+
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
+
+ run_next_test();
+ do_test_finished();
+};
+
+function Http2IllegalHpackListener() { }
+Http2IllegalHpackListener.prototype = new Http2CheckListener();
+Http2IllegalHpackListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackListener.prototype.onStopRequest = function (request, ctx, status) {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpack_validate");
+ var listener = new Http2IllegalHpackValidationListener();
+ listener.shouldGoAway = this.shouldGoAway;
+ chan.asyncOpen2(listener);
+};
+
+function test_http2_illegalhpacksoft() {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpacksoft");
+ var listener = new Http2IllegalHpackListener();
+ listener.shouldGoAway = false;
+ listener.shouldSucceed = false;
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_illegalhpackhard() {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpackhard");
+ var listener = new Http2IllegalHpackListener();
+ listener.shouldGoAway = true;
+ listener.shouldSucceed = false;
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_folded_header() {
+ var chan = makeChan("https://localhost:" + serverPort + "/foldedheader");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2CheckListener();
+ listener.shouldSucceed = false;
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_empty_data() {
+ var chan = makeChan("https://localhost:" + serverPort + "/emptydata");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_complete() {
+ resetPrefs();
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ do_test_pending();
+ httpserv2.stop(do_test_finished);
+
+ do_test_finished();
+ do_timeout(0,run_next_test);
+}
+
+// hack - the header test resets the multiplex object on the server,
+// so make sure header is always run before the multiplex test.
+//
+// make sure post_big runs first to test race condition in restarting
+// a stalled stream when a SETTINGS frame arrives
+var tests = [ test_http2_post_big
+ , test_http2_basic
+ , test_http2_concurrent
+ , test_http2_concurrent_post
+ , test_http2_basic_unblocked_dep
+ , test_http2_nospdy
+ , test_http2_push1
+ , test_http2_push2
+ , test_http2_push3
+ , test_http2_push4
+ , test_http2_push5
+ , test_http2_push6
+ , test_http2_altsvc
+ , test_http2_doubleheader
+ , test_http2_xhr
+ , test_http2_header
+ , test_http2_cookie_crumbling
+ , test_http2_multiplex
+ , test_http2_big
+ , test_http2_huge_suspended
+ , test_http2_post
+ , test_http2_patch
+ , test_http2_pushapi_1
+ , test_http2_continuations
+ , test_http2_blocking_download
+ , test_http2_illegalhpacksoft
+ , test_http2_illegalhpackhard
+ , test_http2_folded_header
+ , test_http2_empty_data
+ // Add new tests above here - best to add new tests before h1
+ // streams get too involved
+ // These next two must always come in this order
+ , test_http2_h11required_stream
+ , test_http2_h11required_session
+ , test_http2_retry_rst
+ , test_http2_wrongsuite
+
+ // cleanup
+ , test_complete
+ ];
+var current_test = 0;
+
+function run_next_test() {
+ if (current_test < tests.length) {
+ dump("starting test number " + current_test + "\n");
+ tests[current_test]();
+ current_test++;
+ do_test_pending();
+ }
+}
+
+// Support for making sure we can talk to the invalid cert the server presents
+var CertOverrideListener = function(host, port, bits) {
+ this.host = host;
+ if (port) {
+ this.port = port;
+ }
+ this.bits = bits;
+};
+
+CertOverrideListener.prototype = {
+ host: null,
+ port: -1,
+ bits: null,
+
+ getInterface: function(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIBadCertListener2) ||
+ aIID.equals(Ci.nsIInterfaceRequestor) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
+ var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+ var cos = Cc["@mozilla.org/security/certoverride;1"].
+ getService(Ci.nsICertOverrideService);
+ cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
+ dump("Certificate Override in place\n");
+ return true;
+ },
+};
+
+function addCertOverride(host, port, bits) {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ try {
+ var url;
+ if (port) {
+ url = "https://" + host + ":" + port + "/";
+ } else {
+ url = "https://" + host + "/";
+ }
+ req.open("GET", url, false);
+ req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
+ req.send(null);
+ } catch (e) {
+ // This will fail since the server is not trusted yet
+ }
+}
+
+var prefs;
+var spdypref;
+var spdypush;
+var http2pref;
+var tlspref;
+var altsvcpref1;
+var altsvcpref2;
+var loadGroup;
+var serverPort;
+var speculativeLimit;
+
+function resetPrefs() {
+ prefs.setIntPref("network.http.speculative-parallel-limit", speculativeLimit);
+ prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
+ prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
+ prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ serverPort = env.get("MOZHTTP2_PORT");
+ do_check_neq(serverPort, null);
+ dump("using port " + serverPort + "\n");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ speculativeLimit = prefs.getIntPref("network.http.speculative-parallel-limit");
+ prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+
+ // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der
+ // so add that cert to the trust list as a signing cert. Some older tests in
+ // this suite use localhost with a TOFU exception, but new ones should use
+ // foo.example.com
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+ addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
+
+ addCertOverride("localhost", serverPort,
+ Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH |
+ Ci.nsICertOverrideService.ERROR_TIME);
+
+ // Enable all versions of spdy to see that we auto negotiate http/2
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+ altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
+ altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.v3-1", true);
+ prefs.setBoolPref("network.http.spdy.allow-push", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+ prefs.setBoolPref("network.http.altsvc.enabled", true);
+ prefs.setBoolPref("network.http.altsvc.oe", true);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com, bar.example.com");
+
+ loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/altsvc1", altsvcHttp1Server);
+ httpserv.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
+ httpserv.start(-1);
+ httpserv.identity.setPrimary("http", "foo.example.com", httpserv.identity.primaryPort);
+
+ httpserv2 = new HttpServer();
+ httpserv2.registerPathHandler("/altsvc2", altsvcHttp1Server2);
+ httpserv2.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK2);
+ httpserv2.start(-1);
+ httpserv2.identity.setPrimary("http", "foo.example.com", httpserv2.identity.primaryPort);
+
+ // And make go!
+ run_next_test();
+}
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let der = readFile(certFile);
+ certdb.addCert(der, trustString, null);
+}
diff --git a/netwerk/test/unit/test_httpResponseTimeout.js b/netwerk/test/unit/test_httpResponseTimeout.js
new file mode 100644
index 000000000..3a40b0a08
--- /dev/null
+++ b/netwerk/test/unit/test_httpResponseTimeout.js
@@ -0,0 +1,162 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var baseURL;
+const kResponseTimeoutPref = "network.http.response.timeout";
+const kResponseTimeout = 1;
+const kShortLivedKeepalivePref =
+ "network.http.tcp_keepalive.short_lived_connections";
+const kLongLivedKeepalivePref =
+ "network.http.tcp_keepalive.long_lived_connections";
+
+const prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+
+var server = new HttpServer();
+
+function TimeoutListener(expectResponse) {
+ this.expectResponse = expectResponse;
+}
+
+TimeoutListener.prototype = {
+ onStartRequest: function (request, ctx) {
+ },
+
+ onDataAvailable: function (request, ctx, stream) {
+ },
+
+ onStopRequest: function (request, ctx, status) {
+ if (this.expectResponse) {
+ do_check_eq(status, Cr.NS_OK);
+ } else {
+ do_check_eq(status, Cr.NS_ERROR_NET_TIMEOUT);
+ }
+
+ run_next_test();
+ },
+};
+
+function serverStopListener() {
+ do_test_finished();
+}
+
+function testTimeout(timeoutEnabled, expectResponse) {
+ // Set timeout pref.
+ if (timeoutEnabled) {
+ prefService.setIntPref(kResponseTimeoutPref, kResponseTimeout);
+ } else {
+ prefService.setIntPref(kResponseTimeoutPref, 0);
+ }
+
+ var chan = NetUtil.newChannel({uri: baseURL, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+ var listener = new TimeoutListener(expectResponse);
+ chan.asyncOpen2(listener);
+}
+
+function testTimeoutEnabled() {
+ // Set a timeout value; expect a timeout and no response.
+ testTimeout(true, false);
+}
+
+function testTimeoutDisabled() {
+ // Set a timeout value of 0; expect a response.
+ testTimeout(false, true);
+}
+
+function testTimeoutDisabledByShortLivedKeepalives() {
+ // Enable TCP Keepalives for short lived HTTP connections.
+ prefService.setBoolPref(kShortLivedKeepalivePref, true);
+ prefService.setBoolPref(kLongLivedKeepalivePref, false);
+
+ // Try to set a timeout value, but expect a response without timeout.
+ testTimeout(true, true);
+}
+
+function testTimeoutDisabledByLongLivedKeepalives() {
+ // Enable TCP Keepalives for long lived HTTP connections.
+ prefService.setBoolPref(kShortLivedKeepalivePref, false);
+ prefService.setBoolPref(kLongLivedKeepalivePref, true);
+
+ // Try to set a timeout value, but expect a response without timeout.
+ testTimeout(true, true);
+}
+
+function testTimeoutDisabledByBothKeepalives() {
+ // Enable TCP Keepalives for short and long lived HTTP connections.
+ prefService.setBoolPref(kShortLivedKeepalivePref, true);
+ prefService.setBoolPref(kLongLivedKeepalivePref, true);
+
+ // Try to set a timeout value, but expect a response without timeout.
+ testTimeout(true, true);
+}
+
+function setup_tests() {
+ // Start tests with timeout enabled, i.e. disable TCP keepalives for HTTP.
+ // Reset pref in cleanup.
+ if (prefService.getBoolPref(kShortLivedKeepalivePref)) {
+ prefService.setBoolPref(kShortLivedKeepalivePref, false);
+ do_register_cleanup(function() {
+ prefService.setBoolPref(kShortLivedKeepalivePref, true);
+ });
+ }
+ if (prefService.getBoolPref(kLongLivedKeepalivePref)) {
+ prefService.setBoolPref(kLongLivedKeepalivePref, false);
+ do_register_cleanup(function() {
+ prefService.setBoolPref(kLongLivedKeepalivePref, true);
+ });
+ }
+
+ var tests = [
+ // Enable with a timeout value >0;
+ testTimeoutEnabled,
+ // Disable with a timeout value of 0;
+ testTimeoutDisabled,
+ // Disable by enabling TCP keepalive for short-lived HTTP connections.
+ testTimeoutDisabledByShortLivedKeepalives,
+ // Disable by enabling TCP keepalive for long-lived HTTP connections.
+ testTimeoutDisabledByLongLivedKeepalives,
+ // Disable by enabling TCP keepalive for both HTTP connection types.
+ testTimeoutDisabledByBothKeepalives
+ ];
+
+ for (var i=0; i < tests.length; i++) {
+ add_test(tests[i]);
+ }
+}
+
+function setup_http_server() {
+ // Start server; will be stopped at test cleanup time.
+ server.start(-1);
+ baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+ do_print("Using baseURL: " + baseURL);
+ server.registerPathHandler('/', function(metadata, response) {
+ // Wait until the timeout should have passed, then respond.
+ response.processAsync();
+
+ do_timeout((kResponseTimeout+1)*1000 /* ms */, function() {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.write("Hello world");
+ response.finish();
+ });
+ });
+ do_register_cleanup(function() {
+ server.stop(serverStopListener);
+ });
+}
+
+function run_test() {
+ setup_http_server();
+
+ setup_tests();
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_http_headers.js b/netwerk/test/unit/test_http_headers.js
new file mode 100644
index 000000000..586e064aa
--- /dev/null
+++ b/netwerk/test/unit/test_http_headers.js
@@ -0,0 +1,70 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function check_request_header(chan, name, value) {
+ var chanValue;
+ try {
+ chanValue = chan.getRequestHeader(name);
+ } catch (e) {
+ do_throw("Expected to find header '" + name + "' but didn't find it");
+ }
+ do_check_eq(chanValue, value);
+}
+
+function run_test() {
+
+ var chan = NetUtil.newChannel ({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ check_request_header(chan, "host", "www.mozilla.org");
+ check_request_header(chan, "Host", "www.mozilla.org");
+
+ chan.setRequestHeader("foopy", "bar", false);
+ check_request_header(chan, "foopy", "bar");
+
+ chan.setRequestHeader("foopy", "baz", true);
+ check_request_header(chan, "foopy", "bar, baz");
+
+ for (var i = 0; i < 100; ++i)
+ chan.setRequestHeader("foopy" + i, i, false);
+
+ for (var i = 0; i < 100; ++i)
+ check_request_header(chan, "foopy" + i, i);
+
+ var x = false;
+ try {
+ chan.setRequestHeader("foo:py", "baz", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x)
+ do_throw("header with colon not rejected");
+
+ x = false;
+ try {
+ chan.setRequestHeader("foopy", "b\naz", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x)
+ do_throw("header value with newline not rejected");
+
+ x = false;
+ try {
+ chan.setRequestHeader("foopy\u0080", "baz", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x)
+ do_throw("header name with non-ASCII not rejected");
+
+ x = false;
+ try {
+ chan.setRequestHeader("foopy", "b\u0000az", false);
+ } catch (e) {
+ x = true;
+ }
+ if (!x)
+ do_throw("header value with null-byte not rejected");
+}
diff --git a/netwerk/test/unit/test_httpauth.js b/netwerk/test/unit/test_httpauth.js
new file mode 100644
index 000000000..65846710e
--- /dev/null
+++ b/netwerk/test/unit/test_httpauth.js
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure the HTTP authenticated sessions are correctly cleared
+// when entering and leaving the private browsing mode.
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ var am = Cc["@mozilla.org/network/http-auth-manager;1"].
+ getService(Ci.nsIHttpAuthManager);
+
+ const kHost1 = "pbtest3.example.com";
+ const kHost2 = "pbtest4.example.com";
+ const kPort = 80;
+ const kHTTP = "http";
+ const kBasic = "basic";
+ const kRealm = "realm";
+ const kDomain = "example.com";
+ const kUser = "user";
+ const kUser2 = "user2";
+ const kPassword = "pass";
+ const kPassword2 = "pass2";
+ const kEmpty = "";
+
+ const PRIVATE = true;
+ const NOT_PRIVATE = false;
+
+ try {
+ var domain = {value: kEmpty}, user = {value: kEmpty}, pass = {value: kEmpty};
+ // simulate a login via HTTP auth outside of the private mode
+ am.setAuthIdentity(kHTTP, kHost1, kPort, kBasic, kRealm, kEmpty, kDomain, kUser, kPassword);
+ // make sure the recently added auth entry is available outside the private browsing mode
+ am.getAuthIdentity(kHTTP, kHost1, kPort, kBasic, kRealm, kEmpty, domain, user, pass, NOT_PRIVATE);
+ do_check_eq(domain.value, kDomain);
+ do_check_eq(user.value, kUser);
+ do_check_eq(pass.value, kPassword);
+
+ // make sure the added auth entry is no longer accessible in private
+ domain = {value: kEmpty}, user = {value: kEmpty}, pass = {value: kEmpty};
+ try {
+ // should throw
+ am.getAuthIdentity(kHTTP, kHost1, kPort, kBasic, kRealm, kEmpty, domain, user, pass, PRIVATE);
+ do_throw("Auth entry should not be retrievable after entering the private browsing mode");
+ } catch (e) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(pass.value, kEmpty);
+ }
+
+ // simulate a login via HTTP auth inside of the private mode
+ am.setAuthIdentity(kHTTP, kHost2, kPort, kBasic, kRealm, kEmpty, kDomain, kUser2, kPassword2, PRIVATE);
+ // make sure the recently added auth entry is available inside the private browsing mode
+ domain = {value: kEmpty}, user = {value: kEmpty}, pass = {value: kEmpty};
+ am.getAuthIdentity(kHTTP, kHost2, kPort, kBasic, kRealm, kEmpty, domain, user, pass, PRIVATE);
+ do_check_eq(domain.value, kDomain);
+ do_check_eq(user.value, kUser2);
+ do_check_eq(pass.value, kPassword2);
+
+ try {
+ // make sure the recently added auth entry is not available outside the private browsing mode
+ domain = {value: kEmpty}, user = {value: kEmpty}, pass = {value: kEmpty};
+ am.getAuthIdentity(kHTTP, kHost2, kPort, kBasic, kRealm, kEmpty, domain, user, pass, NOT_PRIVATE);
+ do_throw("Auth entry should not be retrievable outside of private browsing mode");
+ } catch (x) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(pass.value, kEmpty);
+ }
+
+ // simulate leaving private browsing mode
+ Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+
+ // make sure the added auth entry is no longer accessible in any privacy state
+ domain = {value: kEmpty}, user = {value: kEmpty}, pass = {value: kEmpty};
+ try {
+ // should throw (not available in public mode)
+ am.getAuthIdentity(kHTTP, kHost2, kPort, kBasic, kRealm, kEmpty, domain, user, pass, NOT_PRIVATE);
+ do_throw("Auth entry should not be retrievable after exiting the private browsing mode");
+ } catch (e) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(pass.value, kEmpty);
+ }
+ try {
+ // should throw (no longer available in private mode)
+ am.getAuthIdentity(kHTTP, kHost2, kPort, kBasic, kRealm, kEmpty, domain, user, pass, PRIVATE);
+ do_throw("Auth entry should not be retrievable in private mode after exiting the private browsing mode");
+ } catch (x) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(pass.value, kEmpty);
+ }
+ } catch (e) {
+ do_throw("Unexpected exception while testing HTTP auth manager: " + e);
+ }
+}
+
diff --git a/netwerk/test/unit/test_httpcancel.js b/netwerk/test/unit/test_httpcancel.js
new file mode 100644
index 000000000..49188ca1e
--- /dev/null
+++ b/netwerk/test/unit/test_httpcancel.js
@@ -0,0 +1,114 @@
+// This file ensures that canceling a channel early does not
+// send the request to the server (bug 350790)
+//
+// I've also shoehorned in a test that ENSURE_CALLED_BEFORE_CONNECT works as
+// expected: see comments that start with ENSURE_CALLED_BEFORE_CONNECT:
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+var observer = {
+ QueryInterface: function eventsink_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIObserver))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data) {
+ subject = subject.QueryInterface(Components.interfaces.nsIRequest);
+ subject.cancel(Components.results.NS_BINDING_ABORTED);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: setting values should still work
+ try {
+ subject.QueryInterface(Components.interfaces.nsIHttpChannel);
+ currentReferrer = subject.getRequestHeader("Referer");
+ do_check_eq(currentReferrer, "http://site1.com/");
+ var uri = ios.newURI("http://site2.com", null, null);
+ subject.referrer = uri;
+ } catch (ex) {
+ do_throw("Exception: " + ex);
+ }
+
+ var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.removeObserver(observer, "http-on-modify-request");
+ }
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ do_check_eq(request.status, Components.results.NS_BINDING_ABORTED);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: setting referrer should now fail
+ try {
+ request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ currentReferrer = request.getRequestHeader("Referer");
+ do_check_eq(currentReferrer, "http://site2.com/");
+ var uri = ios.newURI("http://site3.com/", null, null);
+
+ // Need to set NECKO_ERRORS_ARE_FATAL=0 else we'll abort process
+ var env = Components.classes["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+ env.set("NECKO_ERRORS_ARE_FATAL", "0");
+ // we expect setting referrer to fail
+ try {
+ request.referrer = uri;
+ do_throw("Error should have been thrown before getting here");
+ } catch (ex) { }
+ } catch (ex) {
+ do_throw("Exception: " + ex);
+ }
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ httpserv.stop(do_test_finished);
+ }
+};
+
+function makeChan(url) {
+ var chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: set original value
+ var uri = ios.newURI("http://site1.com", null, null);
+ chan.referrer = uri;
+
+ return chan;
+}
+
+var httpserv = null;
+
+function execute_test() {
+ var chan = makeChan("http://localhost:" +
+ httpserv.identity.primaryPort + "/failtest");
+
+ var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.addObserver(observer, "http-on-modify-request", false);
+
+ chan.asyncOpen2(listener);
+}
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/failtest", failtest);
+ httpserv.start(-1);
+
+ execute_test();
+
+ do_test_pending();
+}
+
+// PATHS
+
+// /failtest
+function failtest(metadata, response) {
+ do_throw("This should not be reached");
+}
diff --git a/netwerk/test/unit/test_httpsuspend.js b/netwerk/test/unit/test_httpsuspend.js
new file mode 100644
index 000000000..7d0b1326f
--- /dev/null
+++ b/netwerk/test/unit/test_httpsuspend.js
@@ -0,0 +1,80 @@
+// This file ensures that suspending a channel directly after opening it
+// suspends future notifications correctly.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+const MIN_TIME_DIFFERENCE = 3000;
+const RESUME_DELAY = 5000;
+
+var listener = {
+ _lastEvent: 0,
+ _gotData: false,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, ctx) {
+ this._lastEvent = Date.now();
+ request.QueryInterface(Ci.nsIRequest);
+
+ // Insert a delay between this and the next callback to ensure message buffering
+ // works correctly
+ request.suspend();
+ request.suspend();
+ do_timeout(RESUME_DELAY, function() { request.resume(); });
+ do_timeout(RESUME_DELAY + 1000, function() { request.resume(); });
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_check_true(Date.now() - this._lastEvent >= MIN_TIME_DIFFERENCE);
+ read_stream(stream, count);
+
+ // Ensure that suspending and resuming inside a callback works correctly
+ request.suspend();
+ request.suspend();
+ request.resume();
+ request.resume();
+
+ this._gotData = true;
+ },
+
+ onStopRequest: function(request, ctx, status) {
+ do_check_true(this._gotData);
+ httpserv.stop(do_test_finished);
+ }
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/woo", data);
+ httpserv.start(-1);
+
+ var chan = makeChan(URL + "/woo");
+ chan.QueryInterface(Ci.nsIRequest);
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function data(metadata, response) {
+ let httpbody = "0123456789";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
diff --git a/netwerk/test/unit/test_idn_blacklist.js b/netwerk/test/unit/test_idn_blacklist.js
new file mode 100644
index 000000000..5ca0173bb
--- /dev/null
+++ b/netwerk/test/unit/test_idn_blacklist.js
@@ -0,0 +1,170 @@
+// Test that URLs containing characters in the IDN blacklist are
+// always displayed as punycode
+const testcases = [
+ // Original Punycode or
+ // normalized form
+ //
+ ["\u00BC", "xn--14-c6t"],
+ ["\u00BD", "xn--12-c6t"],
+ ["\u00BE", "xn--34-c6t"],
+ ["\u01C3", "xn--ija"],
+ ["\u02D0", "xn--6qa"],
+ ["\u0337", "xn--4ta"],
+ ["\u0338", "xn--5ta"],
+ ["\u0589", "xn--3bb"],
+ ["\u05C3", "xn--rdb"],
+ ["\u05F4", "xn--5eb"],
+ ["\u0609", "xn--rfb"],
+ ["\u060A", "xn--sfb"],
+ ["\u066A", "xn--jib"],
+ ["\u06D4", "xn--klb"],
+ ["\u0701", "xn--umb"],
+ ["\u0702", "xn--vmb"],
+ ["\u0703", "xn--wmb"],
+ ["\u0704", "xn--xmb"],
+ ["\u115F", "xn--osd"],
+ ["\u1160", "xn--psd"],
+ ["\u1735", "xn--d0e"],
+ ["\u2027", "xn--svg"],
+ ["\u2028", "xn--tvg"],
+ ["\u2029", "xn--uvg"],
+ ["\u2039", "xn--bwg"],
+ ["\u203A", "xn--cwg"],
+ ["\u2041", "xn--jwg"],
+ ["\u2044", "xn--mwg"],
+ ["\u2052", "xn--0wg"],
+ ["\u2153", "xn--13-c6t"],
+ ["\u2154", "xn--23-c6t"],
+ ["\u2155", "xn--15-c6t"],
+ ["\u2156", "xn--25-c6t"],
+ ["\u2157", "xn--35-c6t"],
+ ["\u2158", "xn--45-c6t"],
+ ["\u2159", "xn--16-c6t"],
+ ["\u215A", "xn--56-c6t"],
+ ["\u215B", "xn--18-c6t"],
+ ["\u215C", "xn--38-c6t"],
+ ["\u215D", "xn--58-c6t"],
+ ["\u215E", "xn--78-c6t"],
+ ["\u215F", "xn--1-zjn"],
+ ["\u2215", "xn--w9g"],
+ ["\u2236", "xn--ubh"],
+ ["\u23AE", "xn--lmh"],
+ ["\u2571", "xn--hzh"],
+ ["\u29F6", "xn--jxi"],
+ ["\u29F8", "xn--lxi"],
+ ["\u2AFB", "xn--z4i"],
+ ["\u2AFD", "xn--14i"],
+ ["\u2FF0", "xn--85j"],
+ ["\u2FF1", "xn--95j"],
+ ["\u2FF2", "xn--b6j"],
+ ["\u2FF3", "xn--c6j"],
+ ["\u2FF4", "xn--d6j"],
+ ["\u2FF5", "xn--e6j"],
+ ["\u2FF6", "xn--f6j"],
+ ["\u2FF7", "xn--g6j"],
+ ["\u2FF8", "xn--h6j"],
+ ["\u2FF9", "xn--i6j"],
+ ["\u2FFA", "xn--j6j"],
+ ["\u2FFB", "xn--k6j"],
+ ["\u3014", "xn--96j"],
+ ["\u3015", "xn--b7j"],
+ ["\u3033", "xn--57j"],
+ ["\u3164", "xn--psd"],
+ ["\u321D", "xn--()-357j35d"],
+ ["\u321E", "xn--()-357jf36c"],
+ ["\u33AE", "xn--rads-id9a"],
+ ["\u33AF", "xn--rads2-4d6b"],
+ ["\u33C6", "xn--ckg-tc2a"],
+ ["\u33DF", "xn--am-6bv"],
+ ["\uA789", "xn--058a"],
+ ["\uFE3F", "xn--x6j"],
+ ["\uFE5D", "xn--96j"],
+ ["\uFE5E", "xn--b7j"],
+ ["\uFFA0", "xn--psd"],
+ ["\uFFF9", "xn--vn7c"],
+ ["\uFFFA", "xn--wn7c"],
+ ["\uFFFB", "xn--xn7c"],
+ ["\uFFFC", "xn--yn7c"],
+ ["\uFFFD", "xn--zn7c"],
+
+ // Characters from the IDN blacklist that normalize to ASCII
+ // If we start using STD3ASCIIRules these will be blocked (bug 316444)
+ ["\u00A0", " "],
+ ["\u2000", " "],
+ ["\u2001", " "],
+ ["\u2002", " "],
+ ["\u2003", " "],
+ ["\u2004", " "],
+ ["\u2005", " "],
+ ["\u2006", " "],
+ ["\u2007", " "],
+ ["\u2008", " "],
+ ["\u2009", " "],
+ ["\u200A", " "],
+ ["\u2024", "."],
+ ["\u202F", " "],
+ ["\u205F", " "],
+ ["\u3000", " "],
+ ["\u3002", "."],
+ ["\uFE14", ";"],
+ ["\uFE15", "!"],
+ ["\uFF0E", "."],
+ ["\uFF0F", "/"],
+ ["\uFF61", "."],
+
+ // Characters from the IDN blacklist that are stripped by Nameprep
+ ["\u200B", ""],
+ ["\uFEFF", ""],
+];
+
+
+function run_test() {
+ var pbi = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ var oldProfile = pbi.getCharPref("network.IDN.restriction_profile", "moderate");
+ var oldWhiteListCom;
+ try {
+ oldWhitelistCom = pbi.getBoolPref("network.IDN.whitelist.com");
+ } catch(e) {
+ oldWhitelistCom = false;
+ }
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
+
+ pbi.setCharPref("network.IDN.restriction_profile", "moderate");
+ pbi.setBoolPref("network.IDN.whitelist.com", false);
+
+ for (var j = 0; j < testcases.length; ++j) {
+ var test = testcases[j];
+ var URL = test[0] + ".com";
+ var punycodeURL = test[1] + ".com";
+ var isASCII = {};
+
+ var result;
+ try {
+ result = idnService.convertToDisplayIDN(URL, isASCII);
+ } catch(e) {
+ result = ".com";
+ }
+ // If the punycode URL is equivalent to \ufffd.com (i.e. the
+ // blacklisted character has been replaced by a unicode
+ // REPLACEMENT CHARACTER, skip the test
+ if (result != "xn--zn7c.com") {
+
+ if (punycodeURL.substr(0, 4) == "xn--") {
+ // test convertToDisplayIDN with a Unicode URL and with a
+ // Punycode URL if we have one
+ equal(escape(result), escape(punycodeURL));
+
+ result = idnService.convertToDisplayIDN(punycodeURL, isASCII);
+ equal(escape(result), escape(punycodeURL));
+ } else {
+ // The "punycode" URL isn't punycode. This happens in testcases
+ // where the Unicode URL has become normalized to an ASCII URL,
+ // so, even though expectedUnicode is true, the expected result
+ // is equal to punycodeURL
+ equal(escape(result), escape(punycodeURL));
+ }
+ }
+ }
+ pbi.setBoolPref("network.IDN.whitelist.com", oldWhitelistCom);
+ pbi.setCharPref("network.IDN.restriction_profile", oldProfile);
+}
diff --git a/netwerk/test/unit/test_idn_urls.js b/netwerk/test/unit/test_idn_urls.js
new file mode 100644
index 000000000..06a53032b
--- /dev/null
+++ b/netwerk/test/unit/test_idn_urls.js
@@ -0,0 +1,345 @@
+// Test algorithm for unicode display of IDNA URL (bug 722299)
+const testcases = [
+ // Original Punycode or Expected UTF-8 by profile
+ // URL normalized form ASCII-Only, High, Moderate
+ //
+ // Latin script
+ ["cuillère", "xn--cuillre-6xa", false, true, true],
+
+ // repeated non-spacing marks
+ ["gruz̀̀ere", "xn--gruzere-ogea", false, false, false],
+
+ // non-XID character
+ ["I♥NY", "xn--iny-zx5a", false, false, false],
+
+/*
+ Behaviour of this test changed in IDNA2008, replacing the non-XID
+ character with U+FFFD replacement character - when all platforms use
+ IDNA2008 it can be uncommented and the punycode URL changed to
+ "xn--mgbl3eb85703a"
+
+ // new non-XID character in Unicode 6.3
+ ["حلا\u061cل", "xn--bgbvr6gc", false, false, false],
+*/
+
+ // U+30FB KATAKANA MIDDLE DOT is excluded from non-XID characters (bug 857490)
+ ["乾燥肌・石けん", "xn--08j4gylj12hz80b0uhfup", false, true, true],
+
+ // Cyrillic alone
+ ["толсто́й", "xn--lsa83dealbred", false, true, true],
+
+ // Mixed script Cyrillic/Latin
+ ["толсто́й-in-Russian",
+ "xn---in-russian-1jg071b0a8bb4cpd", false, false, false],
+
+ // Mixed script Latin/Cyrillic
+ ["war-and-миръ", "xn--war-and--b9g3b7b3h", false, false, false],
+
+ // Cherokee (Restricted script)
+ ["ᏣᎳᎩ", "xn--f9dt7l", false, false, false],
+
+ // Yi (former Aspirational script, now Restricted per Unicode 10.0 update to UAX 31)
+ ["ꆈꌠꁱꂷ", "xn--4o7a6e1x64c", false, false, false],
+
+ // Greek alone
+ ["πλάτων", "xn--hxa3ahjw4a", false, true, true],
+
+ // Mixed script Greek/Latin
+ ["πλάτωνicrelationship",
+ "xn--icrelationship-96j4t9a3cwe2e", false, false, false],
+
+ // Mixed script Latin/Greek
+ ["spaceὈδύσσεια", "xn--space-h9dui0b0ga2j1562b", false, false, false],
+
+ // Devanagari alone
+ ["मराठी", "xn--d2b1ag0dl", false, true, true],
+
+ // Devanagari with Armenian
+ ["मराठीՀայաստան",
+ "xn--y9aaa1d0ai1cq964f8dwa2o1a", false, false, false],
+
+ // Devanagari with common
+ ["मराठी123", "xn--123-mhh3em2hra", false, true, true],
+
+ // Common with Devanagari
+ ["123मराठी", "xn--123-phh3em2hra", false, true, true],
+
+ // Latin with Han
+ ["chairman毛",
+ "xn--chairman-k65r", false, true, true],
+
+ // Han with Latin
+ ["山葵sauce", "xn--sauce-6j9ii40v", false, true, true],
+
+ // Latin with Han, Hiragana and Katakana
+ ["van語ではドイ", "xn--van-ub4bpb6w0in486d", false, true, true],
+
+ // Latin with Han, Katakana and Hiragana
+ ["van語ドイでは", "xn--van-ub4bpb4w0ip486d", false, true, true],
+
+ // Latin with Hiragana, Han and Katakana
+ ["vanでは語ドイ", "xn--van-ub4bpb6w0ip486d", false, true, true],
+
+ // Latin with Hiragana, Katakana and Han
+ ["vanではドイ語", "xn--van-ub4bpb6w0ir486d", false, true, true],
+
+ // Latin with Katakana, Han and Hiragana
+ ["vanドイ語では", "xn--van-ub4bpb4w0ir486d", false, true, true],
+
+ // Latin with Katakana, Hiragana and Han
+ ["vanドイでは語", "xn--van-ub4bpb4w0it486d", false, true, true],
+
+ // Han with Latin, Hiragana and Katakana
+ ["語vanではドイ", "xn--van-ub4bpb6w0ik486d", false, true, true],
+
+ // Han with Latin, Katakana and Hiragana
+ ["語vanドイでは", "xn--van-ub4bpb4w0im486d", false, true, true],
+
+ // Han with Hiragana, Latin and Katakana
+ ["語ではvanドイ", "xn--van-rb4bpb9w0ik486d", false, true, true],
+
+ // Han with Hiragana, Katakana and Latin
+ ["語ではドイvan", "xn--van-rb4bpb6w0in486d", false, true, true],
+
+ // Han with Katakana, Latin and Hiragana
+ ["語ドイvanでは", "xn--van-ub4bpb1w0ip486d", false, true, true],
+
+ // Han with Katakana, Hiragana and Latin
+ ["語ドイではvan", "xn--van-rb4bpb4w0ip486d", false, true, true],
+
+ // Hiragana with Latin, Han and Katakana
+ ["イツvan語ではド", "xn--van-ub4bpb1wvhsbx330n", false, true, true],
+
+ // Hiragana with Latin, Katakana and Han
+ ["ではvanドイ語", "xn--van-rb4bpb9w0ir486d", false, true, true],
+
+ // Hiragana with Han, Latin and Katakana
+ ["では語vanドイ", "xn--van-rb4bpb9w0im486d", false, true, true],
+
+ // Hiragana with Han, Katakana and Latin
+ ["では語ドイvan", "xn--van-rb4bpb6w0ip486d", false, true, true],
+
+ // Hiragana with Katakana, Latin and Han
+ ["ではドイvan語", "xn--van-rb4bpb6w0iu486d", false, true, true],
+
+ // Hiragana with Katakana, Han and Latin
+ ["ではドイ語van", "xn--van-rb4bpb6w0ir486d", false, true, true],
+
+ // Katakana with Latin, Han and Hiragana
+ ["ドイvan語では", "xn--van-ub4bpb1w0iu486d", false, true, true],
+
+ // Katakana with Latin, Hiragana and Han
+ ["ドイvanでは語", "xn--van-ub4bpb1w0iw486d", false, true, true],
+
+ // Katakana with Han, Latin and Hiragana
+ ["ドイ語vanでは", "xn--van-ub4bpb1w0ir486d", false, true, true],
+
+ // Katakana with Han, Hiragana and Latin
+ ["ドイ語ではvan", "xn--van-rb4bpb4w0ir486d", false, true, true],
+
+ // Katakana with Hiragana, Latin and Han
+ ["ドイではvan語", "xn--van-rb4bpb4w0iw486d", false, true, true],
+
+ // Katakana with Hiragana, Han and Latin
+ ["ドイでは語van", "xn--van-rb4bpb4w0it486d", false, true, true],
+
+ // Han with common
+ ["中国123", "xn--123-u68dy61b", false, true, true],
+
+ // common with Han
+ ["123中国", "xn--123-x68dy61b", false, true, true],
+
+ // Characters that normalize to permitted characters
+ // (also tests Plane 1 supplementary characters)
+ ["super𝟖", "super8", true, true, true],
+
+ // Han from Plane 2
+ ["𠀀𠀁𠀂", "xn--j50icd", false, true, true],
+
+ // Han from Plane 2 with js (UTF-16) escapes
+ ["\uD840\uDC00\uD840\uDC01\uD840\uDC02",
+ "xn--j50icd", false, true, true],
+
+ // Same with a lone high surrogate at the end
+ ["\uD840\uDC00\uD840\uDC01\uD840",
+ "xn--zn7c0336bda", false, false, false],
+
+ // Latin text and Bengali digits
+ ["super৪", "xn--super-k2l", false, false, true],
+
+ // Bengali digits and Latin text
+ ["৫ab", "xn--ab-x5f", false, false, true],
+
+ // Bengali text and Latin digits
+ ["অঙ্কুর8", "xn--8-70d2cp0j6dtd", false, true, true],
+
+ // Latin digits and Bengali text
+ ["5াব", "xn--5-h3d7c", false, true, true],
+
+ // Mixed numbering systems
+ ["٢٠۰٠", "xn--8hbae38c", false, false, false],
+
+ // Traditional Chinese
+ ["萬城", "xn--uis754h", false, true, true],
+
+ // Simplified Chinese
+ ["万城", "xn--chq31v", false, true, true],
+
+ // Simplified-only and Traditional-only Chinese in the same label
+ ["万萬城", "xn--chq31vsl1b", false, true, true],
+
+ // Traditional-only and Simplified-only Chinese in the same label
+ ["萬万城", "xn--chq31vrl1b", false, true, true],
+
+ // Han and Latin and Bopomofo
+ ["注音符号bopomofoㄅㄆㄇㄈ",
+ "xn--bopomofo-hj5gkalm1637i876cuw0brk5f",
+ false, true, true],
+
+ // Han, bopomofo, Latin
+ ["注音符号ㄅㄆㄇㄈbopomofo",
+ "xn--bopomofo-8i5gkalm9637i876cuw0brk5f",
+ false, true, true],
+
+ // Latin, Han, Bopomofo
+ ["bopomofo注音符号ㄅㄆㄇㄈ",
+ "xn--bopomofo-hj5gkalm9637i876cuw0brk5f",
+ false, true, true],
+
+ // Latin, Bopomofo, Han
+ ["bopomofoㄅㄆㄇㄈ注音符号",
+ "xn--bopomofo-hj5gkalm3737i876cuw0brk5f",
+ false, true, true],
+
+ // Bopomofo, Han, Latin
+ ["ㄅㄆㄇㄈ注音符号bopomofo",
+ "xn--bopomofo-8i5gkalm3737i876cuw0brk5f",
+ false, true, true],
+
+ // Bopomofo, Latin, Han
+ ["ㄅㄆㄇㄈbopomofo注音符号",
+ "xn--bopomofo-8i5gkalm1837i876cuw0brk5f",
+ false, true, true],
+
+ // Han, bopomofo and katakana
+ ["注音符号ㄅㄆㄇㄈボポモフォ",
+ "xn--jckteuaez1shij0450gylvccz9asi4e",
+ false, false, false],
+
+ // Han, katakana, bopomofo
+ ["注音符号ボポモフォㄅㄆㄇㄈ",
+ "xn--jckteuaez6shij5350gylvccz9asi4e",
+ false, false, false],
+
+ // bopomofo, han, katakana
+ ["ㄅㄆㄇㄈ注音符号ボポモフォ",
+ "xn--jckteuaez1shij4450gylvccz9asi4e",
+ false, false, false],
+
+ // bopomofo, katakana, han
+ ["ㄅㄆㄇㄈボポモフォ注音符号",
+ "xn--jckteuaez1shij9450gylvccz9asi4e",
+ false, false, false],
+
+ // katakana, Han, bopomofo
+ ["ボポモフォ注音符号ㄅㄆㄇㄈ",
+ "xn--jckteuaez6shij0450gylvccz9asi4e",
+ false, false, false],
+
+ // katakana, bopomofo, Han
+ ["ボポモフォㄅㄆㄇㄈ注音符号",
+ "xn--jckteuaez6shij4450gylvccz9asi4e",
+ false, false, false],
+
+ // Han, Hangul and Latin
+ ["韓한글hangul",
+ "xn--hangul-2m5ti09k79ze", false, true, true],
+
+ // Han, Latin and Hangul
+ ["韓hangul한글",
+ "xn--hangul-2m5to09k79ze", false, true, true],
+
+ // Hangul, Han and Latin
+ ["한글韓hangul",
+ "xn--hangul-2m5th09k79ze", false, true, true],
+
+ // Hangul, Latin and Han
+ ["한글hangul韓",
+ "xn--hangul-8m5t898k79ze", false, true, true],
+
+ // Latin, Han and Hangul
+ ["hangul韓한글",
+ "xn--hangul-8m5ti09k79ze", false, true, true],
+
+ // Latin, Hangul and Han
+ ["hangul한글韓",
+ "xn--hangul-8m5th09k79ze", false, true, true],
+
+ // Hangul and katakana
+ ["한글ハングル",
+ "xn--qck1c2d4a9266lkmzb", false, false, false],
+
+ // Katakana and Hangul
+ ["ハングル한글",
+ "xn--qck1c2d4a2366lkmzb", false, false, false],
+
+ // Thai (also tests that node with over 63 UTF-8 octets doesn't fail)
+ ["เครื่องทําน้ําทําน้ําแข็ง",
+ "xn--22cdjb2fanb9fyepcbbb9dwh4a3igze4fdcd",
+ false, true, true]
+];
+
+
+const profiles = ["ASCII", "high", "moderate"];
+
+function run_test() {
+ var pbi = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ var oldProfile = pbi.getCharPref("network.IDN.restriction_profile", "moderate");
+ var oldWhiteListCom;
+ try {
+ oldWhitelistCom = pbi.getBoolPref("network.IDN.whitelist.com");
+ } catch(e) {
+ oldWhitelistCom = false;
+ }
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
+
+ for (var i = 0; i < profiles.length; ++i) {
+ pbi.setCharPref("network.IDN.restriction_profile", profiles[i]);
+ pbi.setBoolPref("network.IDN.whitelist.com", false);
+
+ dump("testing " + profiles[i] + " profile");
+
+ for (var j = 0; j < testcases.length; ++j) {
+ var test = testcases[j];
+ var URL = test[0] + ".com";
+ var punycodeURL = test[1] + ".com";
+ var expectedUnicode = test[2 + i];
+ var isASCII = {};
+
+ var result;
+ try {
+ result = idnService.convertToDisplayIDN(URL, isASCII);
+ } catch(e) {
+ result = ".com";
+ }
+ if (punycodeURL.substr(0, 4) == "xn--") {
+ // test convertToDisplayIDN with a Unicode URL and with a
+ // Punycode URL if we have one
+ do_check_eq(escape(result),
+ expectedUnicode ? escape(URL) : escape(punycodeURL));
+
+ result = idnService.convertToDisplayIDN(punycodeURL, isASCII);
+ do_check_eq(escape(result),
+ expectedUnicode ? escape(URL) : escape(punycodeURL));
+ } else {
+ // The "punycode" URL isn't punycode. This happens in testcases
+ // where the Unicode URL has become normalized to an ASCII URL,
+ // so, even though expectedUnicode is true, the expected result
+ // is equal to punycodeURL
+ do_check_eq(escape(result), escape(punycodeURL));
+ }
+ }
+ }
+ pbi.setBoolPref("network.IDN.whitelist.com", oldWhitelistCom);
+ pbi.setCharPref("network.IDN.restriction_profile", oldProfile);
+}
diff --git a/netwerk/test/unit/test_idna2008.js b/netwerk/test/unit/test_idna2008.js
new file mode 100644
index 000000000..9221a0e60
--- /dev/null
+++ b/netwerk/test/unit/test_idna2008.js
@@ -0,0 +1,60 @@
+const kTransitionalProcessing = false;
+
+// Four characters map differently under non-transitional processing:
+const labels = [
+ // U+00DF LATIN SMALL LETTER SHARP S to "ss"
+ "stra\u00dfe",
+ // U+03C2 GREEK SMALL LETTER FINAL SIGMA to U+03C3 GREEK SMALL LETTER SIGMA
+ "\u03b5\u03bb\u03bb\u03ac\u03c2",
+ // U+200C ZERO WIDTH NON-JOINER in Indic script
+ "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc",
+ // U+200D ZERO WIDTH JOINER in Arabic script
+ "\u0dc1\u0dca\u200d\u0dbb\u0dd3",
+
+ // But CONTEXTJ rules prohibit ZWJ and ZWNJ in non-Arabic or Indic scripts
+ // U+200C ZERO WIDTH NON-JOINER in Latin script
+ "m\200cn",
+ // U+200D ZERO WIDTH JOINER in Latin script
+ "p\200dq",
+];
+
+const transitionalExpected = [
+ "strasse",
+ "xn--hxarsa5b",
+ "xn--mgba3gch31f",
+ "xn--10cl1a0b",
+ "",
+ ""
+];
+
+const nonTransitionalExpected = [
+ "xn--strae-oqa",
+ "xn--hxarsa0b",
+ "xn--mgba3gch31f060k",
+ "xn--10cl1a0b660p",
+ "",
+ ""
+];
+
+// Test options for converting IDN URLs under IDNA2008
+function run_test()
+{
+ var idnService = Components.classes["@mozilla.org/network/idn-service;1"]
+ .getService(Components.interfaces.nsIIDNService);
+
+
+ for (var i = 0; i < labels.length; ++i) {
+ var result;
+ try {
+ result = idnService.convertUTF8toACE(labels[i]);
+ } catch(e) {
+ result = "";
+ }
+
+ if (kTransitionalProcessing) {
+ equal(result, transitionalExpected[i]);
+ } else {
+ equal(result, nonTransitionalExpected[i]);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_idnservice.js b/netwerk/test/unit/test_idnservice.js
new file mode 100644
index 000000000..e6d659857
--- /dev/null
+++ b/netwerk/test/unit/test_idnservice.js
@@ -0,0 +1,25 @@
+// Tests nsIIDNService
+
+var reference = [
+ // The 3rd element indicates whether the second element
+ // is ACE-encoded
+ ["asciihost", "asciihost", false],
+ ["b\u00FCcher", "xn--bcher-kva", true]
+ ];
+
+function run_test() {
+ var idnService = Components.classes["@mozilla.org/network/idn-service;1"]
+ .getService(Components.interfaces.nsIIDNService);
+
+ for (var i = 0; i < reference.length; ++i) {
+ dump("Testing " + reference[i] + "\n");
+ // We test the following:
+ // - Converting UTF-8 to ACE and back gives us the expected answer
+ // - Converting the ASCII string UTF-8 -> ACE leaves the string unchanged
+ // - isACE returns true when we expect it to (third array elem true)
+ do_check_eq(idnService.convertUTF8toACE(reference[i][0]), reference[i][1]);
+ do_check_eq(idnService.convertUTF8toACE(reference[i][1]), reference[i][1]);
+ do_check_eq(idnService.convertACEtoUTF8(reference[i][1]), reference[i][0]);
+ do_check_eq(idnService.isACE(reference[i][1]), reference[i][2]);
+ }
+}
diff --git a/netwerk/test/unit/test_immutable.js b/netwerk/test/unit/test_immutable.js
new file mode 100644
index 000000000..d3438bbe1
--- /dev/null
+++ b/netwerk/test/unit/test_immutable.js
@@ -0,0 +1,180 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var prefs;
+var spdypref;
+var http2pref;
+var tlspref;
+var origin;
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ var h2Port = env.get("MOZHTTP2_PORT");
+ do_check_neq(h2Port, null);
+ do_check_neq(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com, bar.example.com");
+
+ // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der
+ // so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+ addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
+
+ origin = "https://foo.example.com:" + h2Port;
+ dump ("origin - " + origin + "\n");
+ doTest1();
+}
+
+function resetPrefs() {
+ prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let der = readFile(certFile);
+ certdb.addCert(der, trustString, null);
+}
+
+function makeChan(origin, path) {
+ return NetUtil.newChannel({
+ uri: origin + path,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var nextTest;
+var expectPass = true;
+var expectConditional = false;
+
+var Listener = function() {};
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request, ctx) {
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+
+ if (expectPass) {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+ do_check_eq(request.responseStatus, 200);
+ } else {
+ do_check_eq(Components.isSuccessCode(request.status), false);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ if (expectConditional) {
+ do_check_eq(request.getResponseHeader("x-conditional"), "true");
+ } else {
+ try { do_check_neq(request.getResponseHeader("x-conditional"), "true"); }
+ catch (e) { do_check_true(true); }
+ }
+ nextTest();
+ do_test_finished();
+ }
+};
+
+function testsDone()
+{
+ dump("testDone\n");
+ resetPrefs();
+}
+
+function doTest1()
+{
+ dump("execute doTest1 - resource without immutable. initial request\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan(origin, "/immutable-test-without-attribute");
+ var listener = new Listener();
+ nextTest = doTest2;
+ chan.asyncOpen2(listener);
+}
+
+function doTest2()
+{
+ dump("execute doTest2 - resource without immutable. reload\n");
+ do_test_pending();
+ expectConditional = true;
+ var chan = makeChan(origin, "/immutable-test-without-attribute");
+ var listener = new Listener();
+ nextTest = doTest3;
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen2(listener);
+}
+
+function doTest3()
+{
+ dump("execute doTest3 - resource without immutable. shift reload\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan(origin, "/immutable-test-without-attribute");
+ var listener = new Listener();
+ nextTest = doTest4;
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen2(listener);
+}
+
+function doTest4()
+{
+ dump("execute doTest1 - resource with immutable. initial request\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan(origin, "/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = doTest5;
+ chan.asyncOpen2(listener);
+}
+
+function doTest5()
+{
+ dump("execute doTest5 - resource with immutable. reload\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan(origin, "/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = doTest6;
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen2(listener);
+}
+
+function doTest6()
+{
+ dump("execute doTest3 - resource with immutable. shift reload\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan(origin, "/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = testsDone;
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen2(listener);
+}
+
+
diff --git a/netwerk/test/unit/test_inhibit_caching.js b/netwerk/test/unit/test_inhibit_caching.js
new file mode 100644
index 000000000..7e81eb696
--- /dev/null
+++ b/netwerk/test/unit/test_inhibit_caching.js
@@ -0,0 +1,76 @@
+Cu.import('resource://gre/modules/LoadContextInfo.jsm');
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var first = true;
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", 'text/plain');
+ var body = "first";
+ if (!first) {
+ body = "second";
+ }
+ first = false;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = null;
+
+function run_test()
+{
+ // setup test
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/test", contentHandler);
+ httpserver.start(-1);
+
+ add_test(test_first_response);
+ add_test(test_inhibit_caching);
+
+ run_next_test();
+}
+
+// Makes a regular request
+function test_first_response() {
+ var chan = NetUtil.newChannel({uri: uri+"/test", loadUsingSystemPrincipal: true});
+ chan.asyncOpen2(new ChannelListener(check_first_response, null));
+}
+
+// Checks that we got the appropriate response
+function check_first_response(request, buffer) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(buffer, "first");
+ // Open the cache entry to check its contents
+ asyncOpenCacheEntry(uri+"/test","disk", Ci.nsICacheStorage.OPEN_READONLY, null, cache_entry_callback);
+}
+
+// Checks that the cache entry has the correct contents
+function cache_entry_callback(status, entry) {
+ equal(status, Cr.NS_OK);
+ var inputStream = entry.openInputStream(0);
+ pumpReadStream(inputStream, function(read) {
+ inputStream.close();
+ equal(read,"first");
+ run_next_test();
+ });
+}
+
+// Makes a request with the INHIBIT_CACHING load flag
+function test_inhibit_caching() {
+ var chan = NetUtil.newChannel({uri: uri+"/test", loadUsingSystemPrincipal: true});
+ chan.QueryInterface(Ci.nsIRequest).loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ chan.asyncOpen2(new ChannelListener(check_second_response, null));
+}
+
+// Checks that we got a different response from the first request
+function check_second_response(request, buffer) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(buffer, "second");
+ // Checks that the cache entry still contains the content from the first request
+ asyncOpenCacheEntry(uri+"/test","disk", Ci.nsICacheStorage.OPEN_READONLY, null, cache_entry_callback);
+}
diff --git a/netwerk/test/unit/test_large_port.js b/netwerk/test/unit/test_large_port.js
new file mode 100644
index 000000000..d2480582f
--- /dev/null
+++ b/netwerk/test/unit/test_large_port.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Ensure that non-16-bit URIs are rejected
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+function run_test()
+{
+ // Bug 1301621 makes invalid ports throw
+ Assert.throws(() => {
+ new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 65536,
+ "http://localhost", "UTF-8", null)
+ }, "invalid port during creation");
+ let url = new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 65535,
+ "http://localhost", "UTF-8", null)
+ .QueryInterface(Ci.nsIStandardURL)
+
+ Assert.throws(() => {
+ url.setDefaultPort(65536);
+ }, "invalid port in setDefaultPort");
+ Assert.throws(() => {
+ url.port = 65536;
+ }, "invalid port in port setter");
+
+ do_check_eq(url.QueryInterface(Ci.nsIURI).port, -1);
+ do_test_finished();
+}
+
diff --git a/netwerk/test/unit/test_link.desktop b/netwerk/test/unit/test_link.desktop
new file mode 100644
index 000000000..b1798202e
--- /dev/null
+++ b/netwerk/test/unit/test_link.desktop
@@ -0,0 +1,3 @@
+[Desktop Entry]
+Type=Link
+URL=http://www.mozilla.org/
diff --git a/netwerk/test/unit/test_link.url b/netwerk/test/unit/test_link.url
new file mode 100644
index 000000000..05f827554
--- /dev/null
+++ b/netwerk/test/unit/test_link.url
@@ -0,0 +1,5 @@
+[InternetShortcut]
+URL=http://www.mozilla.org/
+IDList=
+[{000214A0-0000-0000-C000-000000000046}]
+Prop3=19,2
diff --git a/netwerk/test/unit/test_localstreams.js b/netwerk/test/unit/test_localstreams.js
new file mode 100644
index 000000000..8e926f571
--- /dev/null
+++ b/netwerk/test/unit/test_localstreams.js
@@ -0,0 +1,87 @@
+// Tests bug 304414
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const PR_RDONLY = 0x1; // see prio.h
+
+// Does some sanity checks on the stream and returns the number of bytes read
+// when the checks passed.
+function test_stream(stream) {
+ // This test only handles blocking streams; that's desired for file streams
+ // anyway.
+ do_check_eq(stream.isNonBlocking(), false);
+
+ // Check that the stream is not buffered
+ do_check_eq(Components.classes["@mozilla.org/io-util;1"]
+ .getService(Components.interfaces.nsIIOUtil)
+ .inputStreamIsBuffered(stream),
+ false);
+
+ // Wrap it in a binary stream (to avoid wrong results that
+ // scriptablestream would produce with binary content)
+ var binstream = Components.classes['@mozilla.org/binaryinputstream;1']
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ binstream.setInputStream(stream);
+
+ var numread = 0;
+ for (;;) {
+ do_check_eq(stream.available(), binstream.available());
+ var avail = stream.available();
+ do_check_neq(avail, -1);
+
+ // PR_UINT32_MAX and PR_INT32_MAX; the files we're testing with aren't that
+ // large.
+ do_check_neq(avail, Math.pow(2, 32) - 1);
+ do_check_neq(avail, Math.pow(2, 31) - 1);
+
+ if (!avail) {
+ // For blocking streams, available() only returns 0 on EOF
+ // Make sure that there is really no data left
+ var could_read = false;
+ try {
+ binstream.readByteArray(1);
+ could_read = true;
+ } catch (e) {
+ // We expect the exception, so do nothing here
+ }
+ if (could_read)
+ do_throw("Data readable when available indicated EOF!");
+ return numread;
+ }
+
+ dump("Trying to read " + avail + " bytes\n");
+ // Note: Verification that this does return as much bytes as we asked for is
+ // done in the binarystream implementation
+ var data = binstream.readByteArray(avail);
+
+ numread += avail;
+ }
+ return numread;
+}
+
+function stream_for_file(file) {
+ var str = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ str.init(file, PR_RDONLY, 0, 0);
+ return str;
+}
+
+function stream_from_channel(file) {
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var uri = ios.newFileURI(file);
+ return NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ }).open2();
+}
+
+function run_test() {
+ // Get a file and a directory in order to do some testing
+ var file = do_get_file("../unit/data/test_readline6.txt");
+ var len = file.fileSize;
+ do_check_eq(test_stream(stream_for_file(file)), len);
+ do_check_eq(test_stream(stream_from_channel(file)), len);
+ var dir = file.parent;
+ test_stream(stream_from_channel(dir)); // Can't do size checking
+}
+
diff --git a/netwerk/test/unit/test_mismatch_last-modified.js b/netwerk/test/unit/test_mismatch_last-modified.js
new file mode 100644
index 000000000..f675a123f
--- /dev/null
+++ b/netwerk/test/unit/test_mismatch_last-modified.js
@@ -0,0 +1,154 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+var httpserver = new HttpServer();
+
+var ios;
+
+// Test the handling of a cache revalidation with mismatching last-modified
+// headers. If we get such a revalidation the cache entry should be purged.
+// see bug 717350
+
+// In this test the wrong data is from 11-16-1994 with a value of 'A',
+// and the right data is from 11-15-1994 with a value of 'B'.
+
+// the same URL is requested 3 times. the first time the wrong data comes
+// back, the second time that wrong data is revalidated with a 304 but
+// a L-M header of the right data (this triggers a cache purge), and
+// the third time the right data is returned.
+
+var listener_3 = {
+ // this listener is used to process the the request made after
+ // the cache invalidation. it expects to see the 'right data'
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function test_onStartR(request, ctx) {},
+
+ onDataAvailable: function test_ODA(request, cx, inputStream,
+ offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+
+ do_check_eq(data[0], "B".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ httpserver.stop(do_test_finished);
+ }
+};
+
+XPCOMUtils.defineLazyGetter(this, "listener_2", function() {
+ return {
+ // this listener is used to process the revalidation of the
+ // corrupted cache entry. its revalidation prompts it to be cleaned
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function test_onStartR(request, ctx) {},
+
+ onDataAvailable: function test_ODA(request, cx, inputStream,
+ offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+
+ // This is 'A' from a cache revalidation, but that reval will clean the cache
+ // because of mismatched last-modified response headers
+
+ do_check_eq(data[0], "A".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var channel = request.QueryInterface(Ci.nsIHttpChannel);
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/test1",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(listener_3);
+ }
+};
+});
+
+XPCOMUtils.defineLazyGetter(this, "listener_1", function() {
+ return {
+ // this listener processes the initial request from a empty cache.
+ // the server responds with the wrong data ('A')
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function test_onStartR(request, ctx) {},
+
+ onDataAvailable: function test_ODA(request, cx, inputStream,
+ offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+ do_check_eq(data[0], "A".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var channel = request.QueryInterface(Ci.nsIHttpChannel);
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/test1",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(listener_2);
+ }
+};
+});
+
+function run_test() {
+ do_get_profile();
+ ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ evict_cache_entries();
+
+ httpserver.registerPathHandler("/test1", handler);
+ httpserver.start(-1);
+
+ var port = httpserver.identity.primaryPort;
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + port + "/test1",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(listener_1);
+
+ do_test_pending();
+}
+
+var iter=0;
+function handler(metadata, response) {
+ iter++;
+ if (metadata.hasHeader("If-Modified-Since")) {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ response.setHeader("Last-Modified", "Tue, 15 Nov 1994 12:45:26 GMT", false);
+ }
+ else {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Cache-Control", "max-age=0", false)
+ if (iter == 1) {
+ // simulated wrong response
+ response.setHeader("Last-Modified", "Wed, 16 Nov 1994 00:00:00 GMT", false);
+ response.bodyOutputStream.write("A", 1);
+ }
+ if (iter == 3) {
+ // 'correct' response
+ response.setHeader("Last-Modified", "Tue, 15 Nov 1994 12:45:26 GMT", false);
+ response.bodyOutputStream.write("B", 1);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_mozTXTToHTMLConv.js b/netwerk/test/unit/test_mozTXTToHTMLConv.js
new file mode 100644
index 000000000..075323d7c
--- /dev/null
+++ b/netwerk/test/unit/test_mozTXTToHTMLConv.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that mozITXTToHTMLConv works properly.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ let converter = Cc["@mozilla.org/txttohtmlconv;1"]
+ .getService(Ci.mozITXTToHTMLConv);
+
+ const scanTXTtests = [
+ // -- RFC1738
+ {
+ input: "RFC1738: <URL:http://mozilla.org> then",
+ url: "http://mozilla.org"
+ },
+ // -- RFC2396E
+ {
+ input: "RFC2396E: <http://mozilla.org/> then",
+ url: "http://mozilla.org/"
+ },
+ // -- abbreviated
+ {
+ input: "see www.mozilla.org maybe",
+ url: "http://www.mozilla.org"
+ },
+ // -- freetext
+ {
+ input:"I mean http://www.mozilla.org/.",
+ url: "http://www.mozilla.org/"
+ },
+ {
+ input:"you mean http://mozilla.org:80, right?",
+ url: "http://mozilla.org:80"
+ },
+ {
+ input:"go to http://mozilla.org; then go home",
+ url: "http://mozilla.org"
+ },
+ {
+ input:"http://mozilla.org! yay!",
+ url: "http://mozilla.org"
+ },
+ {
+ input:"er, http://mozilla.com?",
+ url: "http://mozilla.com"
+ },
+ {
+ input:"http://example.org- where things happen",
+ url: "http://example.org"
+ },
+ {
+ input:"see http://mozilla.org: front page",
+ url: "http://mozilla.org"
+ },
+ {
+ input:"'http://mozilla.org/': that's the url",
+ url: "http://mozilla.org/"
+ },
+ {
+ input:"some special http://mozilla.org/?x=.,;!-:x",
+ url: "http://mozilla.org/?x=.,;!-:x"
+ },
+ {
+ // escape & when producing html
+ input:"'http://example.org/?test=true&success=true': ok",
+ url: "http://example.org/?test=true&amp;success=true"
+ },
+ {
+ input: "bracket: http://localhost/[1] etc.",
+ url: "http://localhost/"
+ },
+ {
+ input: "parenthesis: (http://localhost/) etc.",
+ url: "http://localhost/"
+ },
+ {
+ input: "(thunderbird)http://mozilla.org/thunderbird",
+ url: "http://mozilla.org/thunderbird"
+ },
+ {
+ input: "()http://mozilla.org",
+ url: "http://mozilla.org"
+ },
+ {
+ input: "parenthesis included: http://kb.mozillazine.org/Performance_(Thunderbird) etc.",
+ url: "http://kb.mozillazine.org/Performance_(Thunderbird)"
+ },
+ {
+ input: "parenthesis slash bracket: (http://localhost/)[1] etc.",
+ url: "http://localhost/"
+ },
+ {
+ input: "parenthesis bracket: (http://example.org[1]) etc.",
+ url: "http://example.org"
+ },
+ {
+ input: "ipv6 1: https://[1080::8:800:200C:417A]/foo?bar=x test",
+ url: "https://[1080::8:800:200C:417A]/foo?bar=x"
+ },
+ {
+ input: "ipv6 2: http://[::ffff:127.0.0.1]/#yay test",
+ url: "http://[::ffff:127.0.0.1]/#yay"
+ },
+ {
+ input: "ipv6 parenthesis port: (http://[2001:db8::1]:80/) test",
+ url: "http://[2001:db8::1]:80/"
+ },
+ {
+ input: "test http://www.map.com/map.php?t=Nova_Scotia&markers=//Not_a_survey||description=plm2 test",
+ url: "http://www.map.com/map.php?t=Nova_Scotia&amp;markers=//Not_a_survey||description=plm2"
+ }
+ ];
+
+ const scanHTMLtests = [
+ {
+ input: "http://foo.example.com",
+ shouldChange: true
+ },
+ {
+ input: " <a href='http://a.example.com/'>foo</a>",
+ shouldChange: false
+ },
+ {
+ input: "<abbr>see http://abbr.example.com</abbr>",
+ shouldChange: true
+ },
+ {
+ input: "<!-- see http://comment.example.com/ -->",
+ shouldChange: false
+ },
+ {
+ input: "<!-- greater > -->",
+ shouldChange: false
+ },
+ {
+ input: "<!-- lesser < -->",
+ shouldChange: false
+ },
+ {
+ input: "<style id='ex'>background-image: url(http://example.com/ex.png);</style>",
+ shouldChange: false
+ },
+ {
+ input: "<style>body > p, body > div { color:blue }</style>",
+ shouldChange: false
+ },
+ {
+ input: "<script>window.location='http://script.example.com/';</script>",
+ shouldChange: false
+ },
+ {
+ input: "<head><title>http://head.example.com/</title></head>",
+ shouldChange: false
+ },
+ {
+ input: "<header>see http://header.example.com</header>",
+ shouldChange: true
+ },
+ {
+ input: "<iframe src='http://iframe.example.com/' />",
+ shouldChange: false
+ },
+ {
+ input: "broken end <script",
+ shouldChange: false
+ },
+ ];
+
+ function hrefLink(url) {
+ return ' href="' + url + '"';
+ }
+
+ for (let i = 0; i < scanTXTtests.length; i++) {
+ let t = scanTXTtests[i];
+ let output = converter.scanTXT(t.input, Ci.mozITXTToHTMLConv.kURLs);
+ let link = hrefLink(t.url);
+ if (output.indexOf(link) == -1)
+ do_throw("Unexpected conversion by scanTXT: input=" + t.input +
+ ", output=" + output + ", link=" + link);
+ }
+
+ for (let i = 0; i < scanHTMLtests.length; i++) {
+ let t = scanHTMLtests[i];
+ let output = converter.scanHTML(t.input, Ci.mozITXTToHTMLConv.kURLs);
+ let changed = (t.input != output);
+ if (changed != t.shouldChange) {
+ do_throw("Unexpected change by scanHTML: changed=" + changed +
+ ", shouldChange=" + t.shouldChange +
+ ", \ninput=" + t.input +
+ ", \noutput=" + output);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_multipart_byteranges.js b/netwerk/test/unit/test_multipart_byteranges.js
new file mode 100644
index 000000000..367615fff
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_byteranges.js
@@ -0,0 +1,113 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var multipartBody = "--boundary\r\n"+
+"Content-type: text/plain\r\n"+
+"Content-range: bytes 0-2/10\r\n"+
+"\r\n"+
+"aaa\r\n"+
+"--boundary\r\n"+
+"Content-type: text/plain\r\n"+
+"Content-range: bytes 3-7/10\r\n"+
+"\r\n"+
+"bbbbb"+
+"\r\n"+
+"--boundary\r\n"+
+"Content-type: text/plain\r\n"+
+"Content-range: bytes 8-9/10\r\n"+
+"\r\n"+
+"cc"+
+"\r\n"+
+"--boundary--";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", 'multipart/byteranges; boundary="boundary"');
+ response.bodyOutputStream.write(multipartBody, multipartBody.length);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData =
+ [
+ { data: "aaa", type: "text/plain", isByteRangeRequest: true, startRange: 0, endRange: 2 },
+ { data: "bbbbb", type: "text/plain", isByteRangeRequest: true, startRange: 3, endRange: 7 },
+ { data: "cc", type: "text/plain", isByteRangeRequest: true, startRange: 8, endRange: 9 }
+ ];
+
+function responseHandler(request, buffer)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ do_check_eq(request.QueryInterface(Ci.nsIByteRangeRequest).isByteRangeRequest,
+ testData[testNum].isByteRangeRequest);
+ do_check_eq(request.QueryInterface(Ci.nsIByteRangeRequest).startRange,
+ testData[testNum].startRange);
+ do_check_eq(request.QueryInterface(Ci.nsIByteRangeRequest).endRange,
+ testData[testNum].endRange);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest: function(request, context, status) {
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ }
+};
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+ var conv = streamConv.asyncConvertData("multipart/byteranges",
+ "*/*",
+ multipartListener,
+ null);
+
+ var chan = make_channel(uri);
+ chan.asyncOpen2(conv, null);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js b/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js
new file mode 100644
index 000000000..bab964ee8
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js
@@ -0,0 +1,109 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var multipartBody = "--boundary\r\n\r\nSome text\r\n--boundary\r\n\r\n<?xml version='1.0'?><root/>\r\n--boundary--";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", 'multipart/mixed; boundary="boundary"');
+ response.processAsync();
+
+ var body = multipartBody;
+ function byteByByte()
+ {
+ if (!body.length) {
+ response.finish();
+ return;
+ }
+
+ var onebyte = body[0];
+ response.bodyOutputStream.write(onebyte, 1);
+ body = body.substring(1);
+ do_timeout(1, byteByByte);
+ }
+
+ do_timeout(1, byteByByte);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData =
+ [
+ { data: "Some text", type: "text/plain" },
+ { data: "<?xml version='1.0'?><root/>", type: "text/xml" }
+ ];
+
+function responseHandler(request, buffer)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+ _index: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest: function(request, context, status) {
+ this._index++;
+ // Second part should be last part
+ do_check_eq(request.QueryInterface(Ci.nsIMultiPartChannel).isLastPart, this._index == 2);
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ }
+};
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+ var conv = streamConv.asyncConvertData("multipart/mixed",
+ "*/*",
+ multipartListener,
+ null);
+
+ var chan = make_channel(uri);
+ chan.asyncOpen2(conv);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_multipart_streamconv.js b/netwerk/test/unit/test_multipart_streamconv.js
new file mode 100644
index 000000000..c35628faa
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv.js
@@ -0,0 +1,93 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var multipartBody = "--boundary\r\n\r\nSome text\r\n--boundary\r\n\r\n<?xml version='1.0'?><root/>\r\n--boundary--";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", 'multipart/mixed; boundary="boundary"');
+ response.bodyOutputStream.write(multipartBody, multipartBody.length);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData =
+ [
+ { data: "Some text", type: "text/plain" },
+ { data: "<?xml version='1.0'?><root/>", type: "text/xml" }
+ ];
+
+function responseHandler(request, buffer)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+ _index: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest: function(request, context, status) {
+ this._index++;
+ // Second part should be last part
+ do_check_eq(request.QueryInterface(Ci.nsIMultiPartChannel).isLastPart, this._index == 2);
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ }
+};
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+ var conv = streamConv.asyncConvertData("multipart/mixed",
+ "*/*",
+ multipartListener,
+ null);
+
+ var chan = make_channel(uri);
+ chan.asyncOpen2(conv);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js b/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js
new file mode 100644
index 000000000..97924ccb1
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js
@@ -0,0 +1,89 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/multipart";
+});
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+var multipartBody = "\r\nSome text\r\n--boundary\r\n\r\n<?xml version='1.0'?><root/>\r\n--boundary--";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", 'multipart/mixed; boundary="boundary"');
+ response.bodyOutputStream.write(multipartBody, multipartBody.length);
+}
+
+var numTests = 2;
+var testNum = 0;
+
+var testData =
+ [
+ { data: "Some text", type: "text/plain" },
+ { data: "<?xml version='1.0'?><root/>", type: "text/xml" }
+ ];
+
+function responseHandler(request, buffer)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ try {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ dump("BUFFEEE: " + this._buffer + "\n\n");
+ } catch (ex) {
+ do_throw("Error in onDataAvailable: " + ex);
+ }
+ },
+
+ onStopRequest: function(request, context, status) {
+ try {
+ responseHandler(request, this._buffer);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ }
+};
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/multipart", contentHandler);
+ httpserver.start(-1);
+
+ var streamConv = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+ var conv = streamConv.asyncConvertData("multipart/mixed",
+ "*/*",
+ multipartListener,
+ null);
+
+ var chan = make_channel(uri);
+ chan.asyncOpen2(conv);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_nestedabout_serialize.js b/netwerk/test/unit/test_nestedabout_serialize.js
new file mode 100644
index 000000000..58fff91c4
--- /dev/null
+++ b/netwerk/test/unit/test_nestedabout_serialize.js
@@ -0,0 +1,35 @@
+const BinaryInputStream =
+ Components.Constructor("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream", "setInputStream");
+const BinaryOutputStream =
+ Components.Constructor("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream", "setOutputStream");
+
+const Pipe =
+ Components.Constructor("@mozilla.org/pipe;1", "nsIPipe", "init");
+
+const kNestedAboutCID = "{2f277c00-0eaf-4ddb-b936-41326ba48aae}";
+
+function run_test()
+{
+ var ios = Cc["@mozilla.org/network/io-service;1"].createInstance(Ci.nsIIOService);
+
+ var baseURI = ios.newURI("http://example.com/", "UTF-8", null);
+
+ // This depends on the redirector for about:license having the
+ // nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT flag.
+ var aboutLicense = ios.newURI("about:license", "UTF-8", baseURI);
+
+ var pipe = new Pipe(false, false, 0, 0, null);
+ var output = new BinaryOutputStream(pipe.outputStream);
+ var input = new BinaryInputStream(pipe.inputStream);
+ output.QueryInterface(Ci.nsIObjectOutputStream);
+ input.QueryInterface(Ci.nsIObjectInputStream);
+
+ output.writeCompoundObject(aboutLicense, Ci.nsIURI, true);
+ var copy = input.readObject(true);
+ copy.QueryInterface(Ci.nsIURI);
+
+ do_check_eq(copy.asciiSpec, aboutLicense.asciiSpec);
+ do_check_true(copy.equals(aboutLicense));
+}
diff --git a/netwerk/test/unit/test_net_addr.js b/netwerk/test/unit/test_net_addr.js
new file mode 100644
index 000000000..732ecd42f
--- /dev/null
+++ b/netwerk/test/unit/test_net_addr.js
@@ -0,0 +1,199 @@
+var CC = Components.Constructor;
+
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+
+/**
+ * TestServer: A single instance of this is created as |serv|. When created,
+ * it starts listening on the loopback address on port |serv.port|. Tests will
+ * connect to it after setting |serv.acceptCallback|, which is invoked after it
+ * accepts a connection.
+ *
+ * Within |serv.acceptCallback|, various properties of |serv| can be used to
+ * run checks. After the callback, the connection is closed, but the server
+ * remains listening until |serv.stop|
+ *
+ * Note: TestServer can only handle a single connection at a time. Tests
+ * should use run_next_test at the end of |serv.acceptCallback| to start the
+ * following test which creates a connection.
+ */
+function TestServer() {
+ this.reset();
+
+ // start server.
+ // any port (-1), loopback only (true), default backlog (-1)
+ this.listener = ServerSocket(-1, true, -1);
+ this.port = this.listener.port;
+ do_print('server: listening on ' + this.port);
+ this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+ onSocketAccepted: function(socket, trans) {
+ do_print('server: got client connection');
+
+ // one connection at a time.
+ if (this.input !== null) {
+ try { socket.close(); } catch(ignore) {}
+ do_throw("Test written to handle one connection at a time.");
+ }
+
+ try {
+ this.input = trans.openInputStream(0, 0, 0);
+ this.output = trans.openOutputStream(0, 0, 0);
+ this.selfAddr = trans.getScriptableSelfAddr();
+ this.peerAddr = trans.getScriptablePeerAddr();
+
+ this.acceptCallback();
+ } catch(e) {
+ /* In a native callback such as onSocketAccepted, exceptions might not
+ * get output correctly or logged to test output. Send them through
+ * do_throw, which fails the test immediately. */
+ do_report_unexpected_exception(e, "in TestServer.onSocketAccepted");
+ }
+
+ this.reset();
+ } ,
+
+ onStopListening: function(socket) {} ,
+
+ /**
+ * Called to close a connection and clean up properties.
+ */
+ reset: function() {
+ if (this.input)
+ try { this.input.close(); } catch(ignore) {}
+ if (this.output)
+ try { this.output.close(); } catch(ignore) {}
+
+ this.input = null;
+ this.output = null;
+ this.acceptCallback = null;
+ this.selfAddr = null;
+ this.peerAddr = null;
+ } ,
+
+ /**
+ * Cleanup for TestServer and this test case.
+ */
+ stop: function() {
+ this.reset();
+ try { this.listener.close(); } catch(ignore) {}
+ }
+};
+
+
+/**
+ * Helper function.
+ * Compares two nsINetAddr objects and ensures they are logically equivalent.
+ */
+function checkAddrEqual(lhs, rhs) {
+ do_check_eq(lhs.family, rhs.family);
+
+ if (lhs.family === Ci.nsINetAddr.FAMILY_INET) {
+ do_check_eq(lhs.address, rhs.address);
+ do_check_eq(lhs.port, rhs.port);
+ }
+
+ /* TODO: fully support ipv6 and local */
+}
+
+
+/**
+ * An instance of SocketTransportService, used to create connections.
+ */
+var sts;
+
+/**
+ * Single instance of TestServer
+ */
+var serv;
+
+/**
+ * Connections have 5 seconds to be made, or a timeout function fails this
+ * test. This prevents the test from hanging and bringing down the entire
+ * xpcshell test chain.
+ */
+var connectTimeout = 5*1000;
+
+/**
+ * A place for individual tests to place Objects of importance for access
+ * throughout asynchronous testing. Particularly important for any output or
+ * input streams opened, as cleanup of those objects (by the garbage collector)
+ * causes the stream to close and may have other side effects.
+ */
+var testDataStore = null;
+
+/**
+ * IPv4 test.
+ */
+function testIpv4() {
+ testDataStore = {
+ transport : null ,
+ ouput : null
+ }
+
+ serv.acceptCallback = function() {
+ // disable the timeoutCallback
+ serv.timeoutCallback = function(){};
+
+ var selfAddr = testDataStore.transport.getScriptableSelfAddr();
+ var peerAddr = testDataStore.transport.getScriptablePeerAddr();
+
+ // check peerAddr against expected values
+ do_check_eq(peerAddr.family, Ci.nsINetAddr.FAMILY_INET);
+ do_check_eq(peerAddr.port, testDataStore.transport.port);
+ do_check_eq(peerAddr.port, serv.port);
+ do_check_eq(peerAddr.address, "127.0.0.1");
+
+ // check selfAddr against expected values
+ do_check_eq(selfAddr.family, Ci.nsINetAddr.FAMILY_INET);
+ do_check_eq(selfAddr.address, "127.0.0.1");
+
+ // check that selfAddr = server.peerAddr and vice versa.
+ checkAddrEqual(selfAddr, serv.peerAddr);
+ checkAddrEqual(peerAddr, serv.selfAddr);
+
+ testDataStore = null;
+ do_execute_soon(run_next_test);
+ };
+
+ // Useful timeout for debugging test hangs
+ /*serv.timeoutCallback = function(tname) {
+ if (tname === 'testIpv4')
+ do_throw('testIpv4 never completed a connection to TestServ');
+ };
+ do_timeout(connectTimeout, function(){ serv.timeoutCallback('testIpv4'); });*/
+
+ testDataStore.transport = sts.createTransport(null, 0, '127.0.0.1', serv.port, null);
+ /*
+ * Need to hold |output| so that the output stream doesn't close itself and
+ * the associated connection.
+ */
+ testDataStore.output = testDataStore.transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
+
+ /* NEXT:
+ * openOutputStream -> onSocketAccepted -> acceptedCallback -> run_next_test
+ * OR (if the above timeout is uncommented)
+ * <connectTimeout lapses> -> timeoutCallback -> do_throw
+ */
+}
+
+
+/**
+ * Running the tests.
+ */
+function run_test() {
+ sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+ serv = new TestServer();
+
+ do_register_cleanup(function(){ serv.stop(); });
+
+ add_test(testIpv4);
+ /* TODO: testIpv6 */
+ /* TODO: testLocal */
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_nojsredir.js b/netwerk/test/unit/test_nojsredir.js
new file mode 100644
index 000000000..d61c41a18
--- /dev/null
+++ b/netwerk/test/unit/test_nojsredir.js
@@ -0,0 +1,62 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var index = 0;
+var tests = [
+ {url : "/test/test",
+ datalen : 16},
+
+ // Test that the http channel fails and the response body is suppressed
+ // bug 255119
+ {url: "/test/test",
+ responseheader: [ "Location: javascript:alert()"],
+ flags : CL_EXPECT_FAILURE,
+ datalen : 0},
+];
+
+function setupChannel(url) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + url,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function startIter() {
+ var channel = setupChannel(tests[index].url);
+ channel.asyncOpen2(new ChannelListener(completeIter, channel, tests[index].flags));
+}
+
+function completeIter(request, data, ctx) {
+ do_check_true(data.length == tests[index].datalen);
+ if (++index < tests.length) {
+ startIter();
+ } else {
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test() {
+ httpserver.registerPathHandler("/test/test", handler);
+ httpserver.start(-1);
+
+ startIter();
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ var body = "thequickbrownfox";
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var header = tests[index].responseheader;
+ if (header != undefined) {
+ for (var i = 0; i < header.length; i++) {
+ var splitHdr = header[i].split(": ");
+ response.setHeader(splitHdr[0], splitHdr[1], false);
+ }
+ }
+
+ response.setStatusLine(metadata.httpVersion, 302, "Redirected");
+ response.bodyOutputStream.write(body, body.length);
+}
+
diff --git a/netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js b/netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js
new file mode 100644
index 000000000..72169cc24
--- /dev/null
+++ b/netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js
@@ -0,0 +1,179 @@
+function run_test() { run_next_test(); }
+
+var CC = Components.Constructor;
+
+var Pipe = CC('@mozilla.org/pipe;1', Ci.nsIPipe, 'init');
+var BufferedOutputStream = CC('@mozilla.org/network/buffered-output-stream;1',
+ Ci.nsIBufferedOutputStream, 'init');
+var ScriptableInputStream = CC('@mozilla.org/scriptableinputstream;1',
+ Ci.nsIScriptableInputStream, 'init');
+
+// Verify that pipes behave as we expect. Subsequent tests assume
+// pipes behave as demonstrated here.
+add_test(function checkWouldBlockPipe() {
+ // Create a pipe with a one-byte buffer
+ var pipe = new Pipe(true, true, 1, 1);
+
+ // Writing two bytes should transfer only one byte, and
+ // return a partial count, not would-block.
+ do_check_eq(pipe.outputStream.write('xy', 2), 1);
+ do_check_eq(pipe.inputStream.available(), 1);
+
+ do_check_throws_nsIException(() => pipe.outputStream.write('y', 1),
+ 'NS_BASE_STREAM_WOULD_BLOCK');
+
+ // Check that nothing was written to the pipe.
+ do_check_eq(pipe.inputStream.available(), 1);
+ run_next_test();
+});
+
+// A writeFrom to a buffered stream should return
+// NS_BASE_STREAM_WOULD_BLOCK if no data was written.
+add_test(function writeFromBlocksImmediately() {
+ // Create a full pipe for our output stream. This will 'would-block' when
+ // written to.
+ var outPipe = new Pipe(true, true, 1, 1);
+ do_check_eq(outPipe.outputStream.write('x', 1), 1);
+
+ // Create a buffered stream, and fill its buffer, so the next write will
+ // try to flush.
+ var buffered = new BufferedOutputStream(outPipe.outputStream, 10);
+ do_check_eq(buffered.write('0123456789', 10), 10);
+
+ // Create a pipe with some data to be our input stream for the writeFrom
+ // call.
+ var inPipe = new Pipe(true, true, 1, 1);
+ do_check_eq(inPipe.outputStream.write('y', 1), 1);
+
+ do_check_eq(inPipe.inputStream.available(), 1);
+ do_check_throws_nsIException(() => buffered.writeFrom(inPipe.inputStream, 1),
+ 'NS_BASE_STREAM_WOULD_BLOCK');
+
+ // No data should have been consumed from the pipe.
+ do_check_eq(inPipe.inputStream.available(), 1);
+
+ run_next_test();
+});
+
+// A writeFrom to a buffered stream should return a partial count if any
+// data is written, when the last Flush call can only flush a portion of
+// the data.
+add_test(function writeFromReturnsPartialCountOnPartialFlush() {
+ // Create a pipe for our output stream. This will accept five bytes, and
+ // then 'would-block'.
+ var outPipe = new Pipe(true, true, 5, 1);
+
+ // Create a reference to the pipe's readable end that can be used
+ // from JavaScript.
+ var outPipeReadable = new ScriptableInputStream(outPipe.inputStream);
+
+ // Create a buffered stream whose buffer is too large to be flushed
+ // entirely to the output pipe.
+ var buffered = new BufferedOutputStream(outPipe.outputStream, 7);
+
+ // Create a pipe to be our input stream for the writeFrom call.
+ var inPipe = new Pipe(true, true, 15, 1);
+
+ // Write some data to our input pipe, for the rest of the test to consume.
+ do_check_eq(inPipe.outputStream.write('0123456789abcde', 15), 15);
+ do_check_eq(inPipe.inputStream.available(), 15);
+
+ // Write from the input pipe to the buffered stream. The buffered stream
+ // will fill its seven-byte buffer; and then the flush will only succeed
+ // in writing five bytes to the output pipe. The writeFrom call should
+ // return the number of bytes it consumed from inputStream.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 11), 7);
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(inPipe.inputStream.available(), 8);
+
+ // The partially-successful Flush should have created five bytes of
+ // available space in the buffered stream's buffer, so we should be able
+ // to write five bytes to it without blocking.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 5), 5);
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(inPipe.inputStream.available(), 3);
+
+ // Attempting to write any more data should would-block.
+ do_check_throws_nsIException(() => buffered.writeFrom(inPipe.inputStream, 1),
+ 'NS_BASE_STREAM_WOULD_BLOCK');
+
+ // No data should have been consumed from the pipe.
+ do_check_eq(inPipe.inputStream.available(), 3);
+
+ // Push the rest of the data through, checking that it all came through.
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), '01234');
+ // Flush returns NS_ERROR_FAILURE if it can't transfer the full amount.
+ do_check_throws_nsIException(() => buffered.flush(), 'NS_ERROR_FAILURE');
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), '56789');
+ buffered.flush();
+ do_check_eq(outPipeReadable.available(), 2);
+ do_check_eq(outPipeReadable.read(2), 'ab');
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 3), 3);
+ buffered.flush();
+ do_check_eq(outPipeReadable.available(), 3);
+ do_check_eq(outPipeReadable.read(3), 'cde');
+
+ run_next_test();
+});
+
+// A writeFrom to a buffered stream should return a partial count if any
+// data is written, when the last Flush call blocks.
+add_test(function writeFromReturnsPartialCountOnBlock() {
+ // Create a pipe for our output stream. This will accept five bytes, and
+ // then 'would-block'.
+ var outPipe = new Pipe(true, true, 5, 1);
+
+ // Create a reference to the pipe's readable end that can be used
+ // from JavaScript.
+ var outPipeReadable = new ScriptableInputStream(outPipe.inputStream);
+
+ // Create a buffered stream whose buffer is too large to be flushed
+ // entirely to the output pipe.
+ var buffered = new BufferedOutputStream(outPipe.outputStream, 7);
+
+ // Create a pipe to be our input stream for the writeFrom call.
+ var inPipe = new Pipe(true, true, 15, 1);
+
+ // Write some data to our input pipe, for the rest of the test to consume.
+ do_check_eq(inPipe.outputStream.write('0123456789abcde', 15), 15);
+ do_check_eq(inPipe.inputStream.available(), 15);
+
+ // Write enough from the input pipe to the buffered stream to fill the
+ // output pipe's buffer, and then flush it. Nothing should block or fail,
+ // but the output pipe should now be full.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 5), 5);
+ buffered.flush();
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(inPipe.inputStream.available(), 10);
+
+ // Now try to write more from the input pipe than the buffered stream's
+ // buffer can hold. It will attempt to flush, but the output pipe will
+ // would-block without accepting any data. writeFrom should return the
+ // correct partial count.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 10), 7);
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(inPipe.inputStream.available(), 3);
+
+ // Attempting to write any more data should would-block.
+ do_check_throws_nsIException(() => buffered.writeFrom(inPipe.inputStream, 3),
+ 'NS_BASE_STREAM_WOULD_BLOCK');
+
+ // No data should have been consumed from the pipe.
+ do_check_eq(inPipe.inputStream.available(), 3);
+
+ // Push the rest of the data through, checking that it all came through.
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), '01234');
+ // Flush returns NS_ERROR_FAILURE if it can't transfer the full amount.
+ do_check_throws_nsIException(() => buffered.flush(), 'NS_ERROR_FAILURE');
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), '56789');
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 3), 3);
+ buffered.flush();
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), 'abcde');
+
+ run_next_test();
+});
diff --git a/netwerk/test/unit/test_offline_status.js b/netwerk/test/unit/test_offline_status.js
new file mode 100644
index 000000000..ac462a540
--- /dev/null
+++ b/netwerk/test/unit/test_offline_status.js
@@ -0,0 +1,15 @@
+function run_test() {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ try {
+ var linkService = Components.classes["@mozilla.org/network/network-link-service;1"]
+ .getService(Components.interfaces.nsINetworkLinkService);
+
+ // The offline status should depends on the link status
+ do_check_neq(ioService.offline, linkService.isLinkUp);
+ } catch (e) {
+ // The network link service might not be available
+ do_check_eq(ioService.offline, false);
+ }
+}
diff --git a/netwerk/test/unit/test_offlinecache_custom-directory.js b/netwerk/test/unit/test_offlinecache_custom-directory.js
new file mode 100644
index 000000000..44e7957dc
--- /dev/null
+++ b/netwerk/test/unit/test_offlinecache_custom-directory.js
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test executes nsIOfflineCacheUpdateService.scheduleAppUpdate API
+ * 1. preloads an app with a manifest to a custom sudir in the profile (for simplicity)
+ * 2. observes progress and completion of the update
+ * 3. checks presence of index.sql and files in the expected location
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+var httpServer = null;
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response)
+{
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response)
+{
+ var manifestContent = "CACHE MANIFEST\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// finally check we got fallback content
+function finish_test(customDir)
+{
+ var offlineCacheDir = customDir.clone();
+ offlineCacheDir.append("OfflineCache");
+
+ var indexSqlFile = offlineCacheDir.clone();
+ indexSqlFile.append('index.sqlite');
+ do_check_eq(indexSqlFile.exists(), true);
+
+ var file1 = offlineCacheDir.clone();
+ file1.append("2");
+ file1.append("E");
+ file1.append("2C99DE6E7289A5-0");
+ do_check_eq(file1.exists(), true);
+
+ var file2 = offlineCacheDir.clone();
+ file2.append("8");
+ file2.append("6");
+ file2.append("0B457F75198B29-0");
+ do_check_eq(file2.exists(), true);
+
+ // This must not throw an exception. After the update has finished
+ // the index file can be freely removed. This way we check this process
+ // is no longer keeping the file open. Check like this will probably
+ // work only Windows systems.
+
+ // This test could potentially randomaly fail when we start closing
+ // the offline cache database off the main thread. Tries in a loop
+ // may be a solution then.
+ try {
+ indexSqlFile.remove(false);
+ do_check_true(true);
+ }
+ catch (ex) {
+ do_throw("Could not remove the sqlite.index file, we still keep it open \n" + ex + "\n");
+ }
+
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.start(4444);
+
+ var profileDir = do_get_profile();
+ var customDir = profileDir.clone();
+ customDir.append("customOfflineCacheDir" + Math.random());
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri("http://localhost:4444");
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump("Previous test failed to clear offline-app permission! Expect failures.\n");
+ }
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ // Set this pref to mimic the default browser behavior.
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, profileDir);
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ var update = us.scheduleAppUpdate(
+ make_uri("http://localhost:4444/manifest"),
+ make_uri("http://localhost:4444/masterEntry"),
+ systemPrincipal,
+ customDir);
+
+ var expectedStates = [
+ Ci.nsIOfflineCacheUpdateObserver.STATE_DOWNLOADING,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMSTARTED,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMPROGRESS,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMCOMPLETED,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED
+ ];
+
+ update.addObserver({
+ updateStateChanged: function(update, state)
+ {
+ do_check_eq(state, expectedStates.shift());
+
+ if (state == Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED)
+ finish_test(customDir);
+ },
+
+ applicationCacheAvailable: function(appCache)
+ {
+ }
+ }, false);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_original_sent_received_head.js b/netwerk/test/unit/test_original_sent_received_head.js
new file mode 100644
index 000000000..c4d02d5d2
--- /dev/null
+++ b/netwerk/test/unit/test_original_sent_received_head.js
@@ -0,0 +1,220 @@
+//
+// HTTP headers test
+// Response headers can be changed after they have been received, e.g. empty
+// headers are deleted, some duplicate header are merged (if no error is
+// thrown), etc.
+//
+// The "original header" is introduced to hold the header array in the order
+// and the form as they have been received from the network.
+// Here, the "original headers" are tested.
+//
+// Original headers will be stored in the cache as well. This test checks
+// that too.
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+var dbg=1
+
+function run_test() {
+
+ if (dbg) { print("============== START =========="); }
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ run_next_test();
+}
+
+add_test(function test_headerChange() {
+ if (dbg) { print("============== test_headerChange setup: in"); }
+
+ var channel1 = setupChannel(testpath);
+ channel1.loadFlags = Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
+
+ // ChannelListener defined in head_channels.js
+ channel1.asyncOpen2(new ChannelListener(checkResponse, null));
+
+ if (dbg) { print("============== test_headerChange setup: out"); }
+});
+
+add_test(function test_fromCache() {
+ if (dbg) { print("============== test_fromCache setup: in"); }
+
+ var channel2 = setupChannel(testpath);
+ channel2.loadFlags = Components.interfaces.nsIRequest.LOAD_FROM_CACHE;
+
+ // ChannelListener defined in head_channels.js
+ channel2.asyncOpen2(new ChannelListener(checkResponse, null));
+
+ if (dbg) { print("============== test_fromCache setup: out"); }
+});
+
+add_test(function finish() {
+ if (dbg) { print("============== STOP =========="); }
+ httpserver.stop(do_test_finished);
+});
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel ({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ if (dbg) { print("============== serverHandler: in"); }
+
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch(ex) {
+ var etag = "";
+ }
+ if (etag == "testtag") {
+ if (dbg) { print("============== 304 answerr: in"); }
+ response.setStatusLine("1.1", 304, "Not Modified");
+ } else {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setStatusLine("1.1", 200, "OK");
+
+ // Set a empty header. A empty link header will not appear in header list,
+ // but in the "original headers", it will be still exactly as received.
+ response.setHeaderNoCheck("Link", "", true);
+ response.setHeaderNoCheck("Link", "value1");
+ response.setHeaderNoCheck("Link", "value2");
+ response.setHeaderNoCheck("Location", "loc");
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setHeader("ETag", "testtag", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ }
+ if (dbg) { print("============== serverHandler: out"); }
+}
+
+function checkResponse(request, data, context) {
+ if (dbg) { print("============== checkResponse: in"); }
+
+ request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(request.responseStatusText, "OK");
+ do_check_true(request.requestSucceeded);
+
+ // Response header have only one link header.
+ var linkHeaderFound = 0;
+ var locationHeaderFound = 0;
+ request.visitResponseHeaders({
+ visitHeader: function visit(aName, aValue) {
+ if (aName == "Link") {
+ linkHeaderFound++;
+ do_check_eq(aValue, "value1, value2");
+ }
+ if (aName == "Location") {
+ locationHeaderFound++;
+ do_check_eq(aValue, "loc");
+ }
+ }
+ });
+ do_check_eq(linkHeaderFound, 1);
+ do_check_eq(locationHeaderFound, 1);
+
+ // The "original header" still contains 3 link headers.
+ var linkOrgHeaderFound = 0;
+ var locationOrgHeaderFound = 0;
+ request.visitOriginalResponseHeaders({
+ visitHeader: function visitOrg(aName, aValue) {
+ if (aName == "Link") {
+ if (linkOrgHeaderFound == 0) {
+ do_check_eq(aValue, "");
+ } else if (linkOrgHeaderFound == 1 ) {
+ do_check_eq(aValue, "value1");
+ } else {
+ do_check_eq(aValue, "value2");
+ }
+ linkOrgHeaderFound++;
+ }
+ if (aName == "Location") {
+ locationOrgHeaderFound++;
+ do_check_eq(aValue, "loc");
+ }
+ }
+ });
+ do_check_eq(linkOrgHeaderFound, 3);
+ do_check_eq(locationOrgHeaderFound, 1);
+
+ if (dbg) { print("============== Remove headers"); }
+ // Remove header.
+ request.setResponseHeader("Link", "", false);
+ request.setResponseHeader("Location", "", false);
+
+ var linkHeaderFound2 = false;
+ var locationHeaderFound2 = 0;
+ request.visitResponseHeaders({
+ visitHeader: function visit(aName, aValue) {
+ if (aName == "Link") {
+ linkHeaderFound2 = true;
+ }
+ if (aName == "Location") {
+ locationHeaderFound2 = true;
+ }
+ }
+ });
+ do_check_false(linkHeaderFound2, "There should be no link header");
+ do_check_false(locationHeaderFound2, "There should be no location headers.");
+
+ // The "original header" still contains the empty header.
+ var linkOrgHeaderFound2 = 0;
+ var locationOrgHeaderFound2 = 0;
+ request.visitOriginalResponseHeaders({
+ visitHeader: function visitOrg(aName, aValue) {
+ if (aName == "Link") {
+ if (linkOrgHeaderFound2 == 0) {
+ do_check_eq(aValue, "");
+ } else if (linkOrgHeaderFound2 == 1 ) {
+ do_check_eq(aValue, "value1");
+ } else {
+ do_check_eq(aValue, "value2");
+ }
+ linkOrgHeaderFound2++;
+ }
+ if (aName == "Location") {
+ locationOrgHeaderFound2++;
+ do_check_eq(aValue, "loc");
+ }
+ }
+ });
+ do_check_true(linkOrgHeaderFound2 == 3,
+ "Original link header still here.");
+ do_check_true(locationOrgHeaderFound2 == 1,
+ "Original location header still here.");
+
+ if (dbg) { print("============== Test GetResponseHeader"); }
+ var linkOrgHeaderFound3 = 0;
+ request.getOriginalResponseHeader("Link",{
+ visitHeader: function visitOrg(aName, aValue) {
+ if (linkOrgHeaderFound3 == 0) {
+ do_check_eq(aValue, "");
+ } else if (linkOrgHeaderFound3 == 1 ) {
+ do_check_eq(aValue, "value1");
+ } else {
+ do_check_eq(aValue, "value2");
+ }
+ linkOrgHeaderFound3++;
+ }
+ });
+ do_check_true(linkOrgHeaderFound2 == 3,
+ "Original link header still here.");
+
+ if (dbg) { print("============== checkResponse: out"); }
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_parse_content_type.js b/netwerk/test/unit/test_parse_content_type.js
new file mode 100644
index 000000000..d804be673
--- /dev/null
+++ b/netwerk/test/unit/test_parse_content_type.js
@@ -0,0 +1,200 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var charset = {};
+var hadCharset = {};
+var type;
+
+function reset() {
+ delete charset.value;
+ delete hadCharset.value;
+ type = undefined;
+}
+
+function check(aType, aCharset, aHadCharset) {
+ do_check_eq(type, aType);
+ do_check_eq(aCharset, charset.value);
+ do_check_eq(aHadCharset, hadCharset.value);
+ reset();
+}
+
+function run_test() {
+ var netutil = Components.classes["@mozilla.org/network/util;1"]
+ .getService(Components.interfaces.nsINetUtil);
+
+ type = netutil.parseRequestContentType("text/html", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseResponseContentType("text/html", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType("TEXT/HTML", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseResponseContentType("TEXT/HTML", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType("text/html, text/html", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/html, text/html", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType("text/html, text/plain",
+ charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/html, text/plain",
+ charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType('text/html, ', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/html, ', charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType('text/html, */*', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/html, */*', charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType('text/html, foo', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/html, foo', charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType("text/html; charset=ISO-8859-1",
+ charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseResponseContentType("text/html; charset=ISO-8859-1",
+ charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType('text/html; charset="ISO-8859-1"',
+ charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseResponseContentType('text/html; charset="ISO-8859-1"',
+ charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType("text/html; charset='ISO-8859-1'",
+ charset, hadCharset);
+ check("text/html", "'ISO-8859-1'", true);
+
+ type = netutil.parseResponseContentType("text/html; charset='ISO-8859-1'",
+ charset, hadCharset);
+ check("text/html", "'ISO-8859-1'", true);
+
+ type = netutil.parseRequestContentType("text/html; charset=\"ISO-8859-1\", text/html",
+ charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/html; charset=\"ISO-8859-1\", text/html",
+ charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType("text/html; charset=\"ISO-8859-1\", text/html; charset=UTF8",
+ charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/html; charset=\"ISO-8859-1\", text/html; charset=UTF8",
+ charset, hadCharset);
+ check("text/html", "UTF8", true);
+
+ type = netutil.parseRequestContentType("text/html; charset=ISO-8859-1, TEXT/HTML", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/html; charset=ISO-8859-1, TEXT/HTML", charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType("text/html; charset=ISO-8859-1, TEXT/plain", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/html; charset=ISO-8859-1, TEXT/plain", charset, hadCharset);
+ check("text/plain", "", true);
+
+ type = netutil.parseRequestContentType("text/plain, TEXT/HTML; charset=ISO-8859-1, text/html, TEXT/HTML", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/plain, TEXT/HTML; charset=ISO-8859-1, text/html, TEXT/HTML", charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType('text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML', charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML', charset, hadCharset);
+ check("text/html", "ISO-8859-1", true);
+
+ type = netutil.parseRequestContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseRequestContentType("text/plain; param= , text/html", charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType("text/plain; param= , text/html", charset, hadCharset);
+ check("text/html", "", false);
+
+ type = netutil.parseRequestContentType('text/plain; param=", text/html"', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseResponseContentType('text/plain; param=", text/html"', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType('text/plain; param=", \\" , text/html"', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseResponseContentType('text/plain; param=", \\" , text/html"', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType('text/plain; param=", \\" , text/html , "', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseResponseContentType('text/plain; param=", \\" , text/html , "', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType('text/plain param=", \\" , text/html , "', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/plain param=", \\" , text/html , "', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType('text/plain charset=UTF8', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/plain charset=UTF8', charset, hadCharset);
+ check("text/plain", "", false);
+
+ type = netutil.parseRequestContentType('text/plain, TEXT/HTML; param="charset=UTF8"; ; param2="charset=UTF16", text/html, TEXT/HTML', charset, hadCharset);
+ check("", "", false);
+
+ type = netutil.parseResponseContentType('text/plain, TEXT/HTML; param="charset=UTF8"; ; param2="charset=UTF16", text/html, TEXT/HTML', charset, hadCharset);
+ check("text/html", "", false);
+
+ // Bug 562915 - correctness: "\x" is "x"
+ type = netutil.parseResponseContentType('text/plain; charset="UTF\\-8"', charset, hadCharset);
+ check("text/plain", "UTF-8", true);
+
+ // Bug 700589
+
+ // check that single quote doesn't confuse parsing of subsequent parameters
+ type = netutil.parseResponseContentType("text/plain; x='; charset=\"UTF-8\"", charset, hadCharset);
+ check("text/plain", "UTF-8", true);
+
+ // check that single quotes do not get removed from extracted charset
+ type = netutil.parseResponseContentType("text/plain; charset='UTF-8'", charset, hadCharset);
+ check("text/plain", "'UTF-8'", true);
+}
diff --git a/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js b/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js
new file mode 100644
index 000000000..fed809483
--- /dev/null
+++ b/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js
@@ -0,0 +1,84 @@
+/*
+
+ This is only a crash test. We load a partial content, cache it. Then we change the limit
+ for single cache entry size (shrink it) so that the next request for the rest of the content
+ will hit that limit and doom/remove the entry. We change the size manually, but in reality
+ it's being changed by cache smart size.
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+// Have 2kb response (8 * 2 ^ 8)
+var responseBody = "response";
+for (var i = 0; i < 8; ++i) responseBody += responseBody;
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "range");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+
+ if (!metadata.hasHeader("If-Range")) {
+ response.setHeader("Content-Length", responseBody.length + "");
+ response.processAsync();
+ var slice = responseBody.slice(0, 100);
+ response.bodyOutputStream.write(slice, slice.length);
+ response.finish();
+ } else {
+ var slice = responseBody.slice(100);
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range",
+ (responseBody.length - slice.length).toString() + "-" +
+ (responseBody.length - 1).toString() + "/" +
+ (responseBody.length).toString());
+
+ response.setHeader("Content-Length", slice.length + "");
+ response.bodyOutputStream.write(slice, slice.length);
+ }
+}
+
+var enforcePref;
+
+function run_test()
+{
+ enforcePref = Services.prefs.getBoolPref("network.http.enforce-framing.soft");
+ Services.prefs.setBoolPref("network.http.enforce-framing.soft", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(URL + "/content");
+ chan.asyncOpen2(new ChannelListener(firstTimeThrough, null, CL_IGNORE_CL));
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ // Change single cache entry limit to 1 kb. This emulates smart size change.
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ var chan = make_channel(URL + "/content");
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ Services.prefs.setBoolPref("network.http.enforce-framing.soft", enforcePref);
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_permmgr.js b/netwerk/test/unit/test_permmgr.js
new file mode 100644
index 000000000..0e735fc91
--- /dev/null
+++ b/netwerk/test/unit/test_permmgr.js
@@ -0,0 +1,119 @@
+// tests nsIPermissionManager
+
+var hosts = [
+ // format: [host, type, permission]
+ ["http://mozilla.org", "cookie", 1],
+ ["http://mozilla.org", "image", 2],
+ ["http://mozilla.org", "popup", 3],
+ ["http://mozilla.com", "cookie", 1],
+ ["http://www.mozilla.com", "cookie", 2],
+ ["http://dev.mozilla.com", "cookie", 3]
+];
+
+var results = [
+ // format: [host, type, testPermission result, testExactPermission result]
+ // test defaults
+ ["http://localhost", "cookie", 0, 0],
+ ["http://spreadfirefox.com", "cookie", 0, 0],
+ // test different types
+ ["http://mozilla.org", "cookie", 1, 1],
+ ["http://mozilla.org", "image", 2, 2],
+ ["http://mozilla.org", "popup", 3, 3],
+ // test subdomains
+ ["http://www.mozilla.org", "cookie", 1, 0],
+ ["http://www.dev.mozilla.org", "cookie", 1, 0],
+ // test different permissions on subdomains
+ ["http://mozilla.com", "cookie", 1, 1],
+ ["http://www.mozilla.com", "cookie", 2, 2],
+ ["http://dev.mozilla.com", "cookie", 3, 3],
+ ["http://www.dev.mozilla.com", "cookie", 3, 0]
+];
+
+function run_test() {
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Components.interfaces.nsIScriptSecurityManager);
+
+ // nsIPermissionManager implementation is an extension; don't fail if it's not there
+ if (!pm)
+ return;
+
+ // put a few hosts in
+ for (var i = 0; i < hosts.length; ++i) {
+ let uri = ioService.newURI(hosts[i][0], null, null);
+ let principal = secMan.createCodebasePrincipal(uri, {});
+
+ pm.addFromPrincipal(principal, hosts[i][1], hosts[i][2]);
+ }
+
+ // test the result
+ for (var i = 0; i < results.length; ++i) {
+ let uri = ioService.newURI(results[i][0], null, null);
+ let principal = secMan.createCodebasePrincipal(uri, {});
+
+ do_check_eq(pm.testPermissionFromPrincipal(principal, results[i][1]), results[i][2]);
+ do_check_eq(pm.testExactPermissionFromPrincipal(principal, results[i][1]), results[i][3]);
+ }
+
+ // test the enumerator ...
+ var j = 0;
+ var perms = new Array();
+ var enumerator = pm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ perms[j] = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ ++j;
+ }
+ do_check_eq(perms.length, hosts.length);
+
+ // ... remove all the hosts ...
+ for (var j = 0; j < perms.length; ++j) {
+ pm.removePermission(perms[j]);
+ }
+
+ // ... ensure each and every element is equal ...
+ for (var i = 0; i < hosts.length; ++i) {
+ for (var j = 0; j < perms.length; ++j) {
+ if (perms[j].matchesURI(ioService.newURI(hosts[i][0], null, null), true) &&
+ hosts[i][1] == perms[j].type &&
+ hosts[i][2] == perms[j].capability) {
+ perms.splice(j, 1);
+ break;
+ }
+ }
+ }
+ do_check_eq(perms.length, 0);
+
+ // ... and check the permmgr's empty
+ do_check_eq(pm.enumerator.hasMoreElements(), false);
+
+ // test UTF8 normalization behavior: expect ASCII/ACE host encodings
+ var utf8 = "b\u00FCcher.dolske.org"; // "bücher.dolske.org"
+ var aceref = "xn--bcher-kva.dolske.org";
+ var uri = ioService.newURI("http://" + utf8, null, null);
+ pm.add(uri, "utf8", 1);
+ var enumerator = pm.enumerator;
+ do_check_eq(enumerator.hasMoreElements(), true);
+ var ace = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ do_check_eq(ace.principal.URI.asciiHost, aceref);
+ do_check_eq(enumerator.hasMoreElements(), false);
+
+ // test removeAll()
+ pm.removeAll();
+ do_check_eq(pm.enumerator.hasMoreElements(), false);
+
+ uri = ioService.newURI("https://www.example.com", null, null);
+ pm.add(uri, "offline-app", pm.ALLOW_ACTION);
+ principal = secMan.createCodebasePrincipalFromOrigin("https://www.example.com");
+ // Remove existing entry.
+ perm = pm.getPermissionObject(principal, "offline-app", true);
+ pm.removePermission(perm);
+ // Try to remove already deleted entry.
+ perm = pm.getPermissionObject(principal, "offline-app", true);
+ pm.removePermission(perm);
+ do_check_eq(pm.enumerator.hasMoreElements(), false);
+}
diff --git a/netwerk/test/unit/test_ping_aboutnetworking.js b/netwerk/test/unit/test_ping_aboutnetworking.js
new file mode 100644
index 000000000..6db46cb93
--- /dev/null
+++ b/netwerk/test/unit/test_ping_aboutnetworking.js
@@ -0,0 +1,86 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const gDashboard = Cc['@mozilla.org/network/dashboard;1']
+ .getService(Ci.nsIDashboard);
+
+function connectionFailed(status) {
+ let status_ok = [
+ "NS_NET_STATUS_RESOLVING_HOST"
+ ,"NS_NET_STATUS_RESOLVED_HOST"
+ ,"NS_NET_STATUS_CONNECTING_TO"
+ ,"NS_NET_STATUS_CONNECTED_TO"
+ ];
+ for (let i = 0; i < status_ok.length; i++) {
+ if (status == status_ok[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function test_sockets(serverSocket) {
+ do_test_pending();
+ gDashboard.requestSockets(function(data) {
+ let index = -1;
+ do_print("requestSockets: " + JSON.stringify(data.sockets));
+ for (let i = 0; i < data.sockets.length; i++) {
+ if (data.sockets[i].host == "127.0.0.1") {
+ index = i;
+ break;
+ }
+ }
+ do_check_neq(index, -1);
+ do_check_eq(data.sockets[index].port, serverSocket.port);
+ do_check_eq(data.sockets[index].tcp, 1);
+
+ do_test_finished();
+ });
+}
+
+function run_test() {
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ // disable network changed events to avoid the the risk of having the dns
+ // cache getting flushed behind our back
+ ps.setBoolPref("network.notify.changed", false);
+
+ do_register_cleanup(function() {
+ ps.clearUserPref("network.notify.changed");
+ });
+
+ let serverSocket = Components.classes["@mozilla.org/network/server-socket;1"]
+ .createInstance(Ci.nsIServerSocket);
+ serverSocket.init(-1, true, -1);
+
+ do_test_pending();
+ gDashboard.requestConnection("localhost", serverSocket.port,
+ "tcp", 15, function(connInfo) {
+ if (connInfo.status == "NS_NET_STATUS_CONNECTED_TO") {
+ do_test_pending();
+ gDashboard.requestDNSInfo(function(data) {
+ let found = false;
+ do_print("requestDNSInfo: " + JSON.stringify(data.entries));
+ for (let i = 0; i < data.entries.length; i++) {
+ if (data.entries[i].hostname == "localhost") {
+ found = true;
+ break;
+ }
+ }
+ do_check_eq(found, true);
+
+ do_test_finished();
+ test_sockets(serverSocket);
+ });
+
+ do_test_finished();
+ }
+ if (connectionFailed(connInfo.status)) {
+ do_throw(connInfo.status);
+ }
+ });
+}
+
diff --git a/netwerk/test/unit/test_pinned_app_cache.js b/netwerk/test/unit/test_pinned_app_cache.js
new file mode 100644
index 000000000..39b1c764a
--- /dev/null
+++ b/netwerk/test/unit/test_pinned_app_cache.js
@@ -0,0 +1,277 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/*
+ * This testcase performs 3 requests against the offline cache. They
+ * are
+ *
+ * - start_cache_nonpinned_app1()
+ *
+ * - Request nsOfflineCacheService to skip pages (4) of app1 on
+ * the cache storage.
+ *
+ * - The offline cache storage is empty at this monent.
+ *
+ * - start_cache_nonpinned_app2_for_partial()
+ *
+ * - Request nsOfflineCacheService to skip pages of app2 on the
+ * cache storage.
+ *
+ * - The offline cache storage has only enough space for one more
+ * additional page. Only first of pages is really in the cache.
+ *
+ * - start_cache_pinned_app2_for_success()
+ *
+ * - Request nsOfflineCacheService to skip pages of app2 on the
+ * cache storage.
+ *
+ * - The offline cache storage has only enough space for one
+ * additional page. But, this is a pinned request,
+ * nsOfflineCacheService will make more space for this request
+ * by discarding app1 (non-pinned)
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+
+// const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID =
+ "@mozilla.org/offlinecacheupdate-service;1";
+
+const kManifest1 = "CACHE MANIFEST\n" +
+ "/pages/foo1\n" +
+ "/pages/foo2\n" +
+ "/pages/foo3\n" +
+ "/pages/foo4\n";
+const kManifest2 = "CACHE MANIFEST\n" +
+ "/pages/foo5\n" +
+ "/pages/foo6\n" +
+ "/pages/foo7\n" +
+ "/pages/foo8\n";
+
+const kDataFileSize = 1024; // file size for each content page
+const kCacheSize = kDataFileSize * 5; // total space for offline cache storage
+
+XPCOMUtils.defineLazyGetter(this, "kHttpLocation", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/";
+});
+
+XPCOMUtils.defineLazyGetter(this, "kHttpLocation_ip", function() {
+ return "http://127.0.0.1:" + httpServer.identity.primaryPort + "/";
+});
+
+function manifest1_handler(metadata, response) {
+ do_print("manifest1\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest1);
+}
+
+function manifest2_handler(metadata, response) {
+ do_print("manifest2\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest2);
+}
+
+function app_handler(metadata, response) {
+ do_print("app_handler\n");
+ response.setHeader("content-type", "text/html");
+
+ response.write("<html></html>");
+}
+
+function datafile_handler(metadata, response) {
+ do_print("datafile_handler\n");
+ let data = "";
+
+ while(data.length < kDataFileSize) {
+ data = data + Math.random().toString(36).substring(2, 15);
+ }
+
+ response.setHeader("content-type", "text/plain");
+ response.write(data.substring(0, kDataFileSize));
+}
+
+var httpServer;
+
+function init_profile() {
+ var ps = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setComplexValue("browser.cache.offline.parent_directory",
+ Ci.nsILocalFile, do_get_profile());
+}
+
+function init_http_server() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/app1", app_handler);
+ httpServer.registerPathHandler("/app2", app_handler);
+ httpServer.registerPathHandler("/app1.appcache", manifest1_handler);
+ httpServer.registerPathHandler("/app2.appcache", manifest2_handler);
+ for (i = 1; i <= 8; i++) {
+ httpServer.registerPathHandler("/pages/foo" + i, datafile_handler);
+ }
+ httpServer.start(-1);
+}
+
+function init_cache_capacity() {
+ let prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.setIntPref("browser.cache.offline.capacity", kCacheSize / 1024);
+}
+
+function clean_app_cache() {
+ evict_cache_entries("appcache");
+}
+
+function do_app_cache(manifestURL, pageURL, pinned) {
+ let update_service = Cc[kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID].
+ getService(Ci.nsIOfflineCacheUpdateService);
+
+ Services.perms.add(manifestURL,
+ "pin-app",
+ pinned ?
+ Ci.nsIPermissionManager.ALLOW_ACTION :
+ Ci.nsIPermissionManager.DENY_ACTION);
+
+ let update =
+ update_service.scheduleUpdate(manifestURL,
+ pageURL,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null); /* no window */
+
+ return update;
+}
+
+function watch_update(update, stateChangeHandler, cacheAvailHandler) {
+ let observer = {
+ QueryInterface: function QueryInterface(iftype) {
+ return this;
+ },
+
+ updateStateChanged: stateChangeHandler,
+ applicationCacheAvailable: cacheAvailHandler
+ };
+ update.addObserver(observer, false);
+
+ return update;
+}
+
+function start_and_watch_app_cache(manifestURL,
+ pageURL,
+ pinned,
+ stateChangeHandler,
+ cacheAvailHandler) {
+ let ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let update = do_app_cache(ioService.newURI(manifestURL, null, null),
+ ioService.newURI(pageURL, null, null),
+ pinned);
+ watch_update(update, stateChangeHandler, cacheAvailHandler);
+ return update;
+}
+
+const {STATE_FINISHED: STATE_FINISHED,
+ STATE_CHECKING: STATE_CHECKING,
+ STATE_ERROR: STATE_ERROR } = Ci.nsIOfflineCacheUpdateObserver;
+
+/*
+ * Start caching app1 as a non-pinned app.
+ */
+function start_cache_nonpinned_app() {
+ do_print("Start non-pinned App1");
+ start_and_watch_app_cache(kHttpLocation + "app1.appcache",
+ kHttpLocation + "app1",
+ false,
+ function (update, state) {
+ switch(state) {
+ case STATE_FINISHED:
+ start_cache_nonpinned_app2_for_partial();
+ break;
+
+ case STATE_ERROR:
+ do_throw("App1 cache state = " + state);
+ break;
+ }
+ },
+ function (appcahe) {
+ do_print("app1 avail " + appcache + "\n");
+ });
+}
+
+/*
+ * Start caching app2 as a non-pinned app.
+ *
+ * This cache request is supposed to be saved partially in the cache
+ * storage for running out of the cache storage. The offline cache
+ * storage can hold 5 files at most. (kDataFileSize bytes for each
+ * file)
+ */
+function start_cache_nonpinned_app2_for_partial() {
+ let error_count = [0];
+ do_print("Start non-pinned App2 for partial\n");
+ start_and_watch_app_cache(kHttpLocation_ip + "app2.appcache",
+ kHttpLocation_ip + "app2",
+ false,
+ function (update, state) {
+ switch(state) {
+ case STATE_FINISHED:
+ start_cache_pinned_app2_for_success();
+ break;
+
+ case STATE_ERROR:
+ do_throw("App2 cache state = " + state);
+ break;
+ }
+ },
+ function (appcahe) {
+ });
+}
+
+/*
+ * Start caching app2 as a pinned app.
+ *
+ * This request use IP address (127.0.0.1) as the host name instead of
+ * the one used by app1. Because, app1 is also pinned when app2 is
+ * pinned if they have the same host name (localhost).
+ */
+function start_cache_pinned_app2_for_success() {
+ let error_count = [0];
+ do_print("Start pinned App2 for success\n");
+ start_and_watch_app_cache(kHttpLocation_ip + "app2.appcache",
+ kHttpLocation_ip + "app2",
+ true,
+ function (update, state) {
+ switch(state) {
+ case STATE_FINISHED:
+ do_check_true(error_count[0] == 0,
+ "Do not discard app1?");
+ httpServer.stop(do_test_finished);
+ break;
+
+ case STATE_ERROR:
+ do_print("STATE_ERROR\n");
+ error_count[0]++;
+ break;
+ }
+ },
+ function (appcahe) {
+ do_print("app2 avail " + appcache + "\n");
+ });
+}
+
+function run_test() {
+ if (typeof _XPCSHELL_PROCESS == "undefined" ||
+ _XPCSHELL_PROCESS != "child") {
+ init_profile();
+ init_cache_capacity();
+ clean_app_cache();
+ }
+
+ init_http_server();
+ start_cache_nonpinned_app();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_plaintext_sniff.js b/netwerk/test/unit/test_plaintext_sniff.js
new file mode 100644
index 000000000..9955a86b9
--- /dev/null
+++ b/netwerk/test/unit/test_plaintext_sniff.js
@@ -0,0 +1,194 @@
+// Test the plaintext-or-binary sniffer
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// List of Content-Type headers to test. For each header we have an array.
+// The first element in the array is the Content-Type header string. The
+// second element in the array is a boolean indicating whether we allow
+// sniffing for that type.
+var contentTypeHeaderList =
+[
+ [ "text/plain", true ],
+ [ "text/plain; charset=ISO-8859-1", true ],
+ [ "text/plain; charset=iso-8859-1", true ],
+ [ "text/plain; charset=UTF-8", true ],
+ [ "text/plain; charset=unknown", false ],
+ [ "text/plain; param", false ],
+ [ "text/plain; charset=ISO-8859-1; param", false ],
+ [ "text/plain; charset=iso-8859-1; param", false ],
+ [ "text/plain; charset=UTF-8; param", false ],
+ [ "text/plain; charset=utf-8", false ],
+ [ "text/plain; charset=utf8", false ],
+ [ "text/plain; charset=UTF8", false ],
+ [ "text/plain; charset=iSo-8859-1", false ]
+];
+
+// List of response bodies to test. For each response we have an array. The
+// first element in the array is the body string. The second element in the
+// array is a boolean indicating whether that string should sniff as binary.
+var bodyList =
+[
+ [ "Plaintext", false ]
+];
+
+// List of possible BOMs
+var BOMList =
+[
+ "\xFE\xFF", // UTF-16BE
+ "\xFF\xFE", // UTF-16LE
+ "\xEF\xBB\xBF", // UTF-8
+ "\x00\x00\xFE\xFF", // UCS-4BE
+ "\x00\x00\xFF\xFE" // UCS-4LE
+];
+
+// Build up bodyList. The things we treat as binary are ASCII codes 0-8,
+// 14-26, 28-31. That is, the control char range, except for tab, newline,
+// vertical tab, form feed, carriage return, and ESC (this last being used by
+// Shift_JIS, apparently).
+function isBinaryChar(ch) {
+ return (0 <= ch && ch <= 8) || (14 <= ch && ch <= 26) ||
+ (28 <= ch && ch <= 31);
+}
+
+// Test chars on their own
+var i;
+for (i = 0; i <= 127; ++i) {
+ bodyList.push([ String.fromCharCode(i), isBinaryChar(i) ]);
+}
+
+// Test that having a BOM prevents plaintext sniffing
+var j;
+for (i = 0; i <= 127; ++i) {
+ for (j = 0; j < BOMList.length; ++j) {
+ bodyList.push([ BOMList[j] + String.fromCharCode(i, i), false ]);
+ }
+}
+
+// Test that having a BOM requires at least 4 chars to kick in
+for (i = 0; i <= 127; ++i) {
+ for (j = 0; j < BOMList.length; ++j) {
+ bodyList.push([ BOMList[j] + String.fromCharCode(i),
+ BOMList[j].length == 2 && isBinaryChar(i) ]);
+ }
+}
+
+function makeChan(headerIdx, bodyIdx) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserv.identity.primaryPort +
+ "/" + headerIdx + "/" + bodyIdx,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ chan.loadFlags |=
+ Components.interfaces.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ return chan;
+}
+
+function makeListener(headerIdx, bodyIdx) {
+ var listener = {
+ onStartRequest : function test_onStartR(request, ctx) {
+ try {
+ var chan = request.QueryInterface(Components.interfaces.nsIChannel);
+
+ do_check_eq(chan.status, Components.results.NS_OK);
+
+ var type = chan.contentType;
+
+ var expectedType =
+ contentTypeHeaderList[headerIdx][1] && bodyList[bodyIdx][1] ?
+ "application/x-vnd.mozilla.guess-from-ext" : "text/plain";
+ if (expectedType != type) {
+ do_throw("Unexpected sniffed type '" + type + "'. " +
+ "Should be '" + expectedType + "'. " +
+ "Header is ['" +
+ contentTypeHeaderList[headerIdx][0] + "', " +
+ contentTypeHeaderList[headerIdx][1] + "]. " +
+ "Body is ['" +
+ bodyList[bodyIdx][0].toSource() + "', " +
+ bodyList[bodyIdx][1] +
+ "].");
+ }
+ do_check_eq(expectedType, type);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ // Advance to next test
+ ++headerIdx;
+ if (headerIdx == contentTypeHeaderList.length) {
+ headerIdx = 0;
+ ++bodyIdx;
+ }
+
+ if (bodyIdx == bodyList.length) {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ } else {
+ doTest(headerIdx, bodyIdx);
+ }
+
+ do_test_finished();
+ }
+ };
+
+ return listener;
+}
+
+function doTest(headerIdx, bodyIdx) {
+ var chan = makeChan(headerIdx, bodyIdx);
+
+ var listener = makeListener(headerIdx, bodyIdx);
+
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function createResponse(headerIdx, bodyIdx, metadata, response) {
+ response.setHeader("Content-Type", contentTypeHeaderList[headerIdx][0], false);
+ response.bodyOutputStream.write(bodyList[bodyIdx][0],
+ bodyList[bodyIdx][0].length);
+}
+
+function makeHandler(headerIdx, bodyIdx) {
+ var f =
+ function handlerClosure(metadata, response) {
+ return createResponse(headerIdx, bodyIdx, metadata, response);
+ };
+ return f;
+}
+
+var httpserv;
+function run_test() {
+ // disable again for everything for now (causes sporatic oranges)
+ return;
+
+ // disable on Windows for now, because it seems to leak sockets and die.
+ // Silly operating system!
+ // This is a really nasty way to detect Windows. I wish we could do better.
+ if (mozinfo.os == "win") {
+ return;
+ }
+
+ httpserv = new HttpServer();
+
+ for (i = 0; i < contentTypeHeaderList.length; ++i) {
+ for (j = 0; j < bodyList.length; ++j) {
+ httpserv.registerPathHandler("/" + i + "/" + j, makeHandler(i, j));
+ }
+ }
+
+ httpserv.start(-1);
+
+ doTest(0, 0);
+}
diff --git a/netwerk/test/unit/test_post.js b/netwerk/test/unit/test_post.js
new file mode 100644
index 000000000..934719e7d
--- /dev/null
+++ b/netwerk/test/unit/test_post.js
@@ -0,0 +1,120 @@
+//
+// POST test
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+
+var testfile = do_get_file("../unit/data/test_readline6.txt");
+
+const BOUNDARY = "AaB03x";
+var teststring1 = "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"body\"\r\n\r\n"
+ + "0123456789\r\n"
+ + "--" + BOUNDARY + "\r\n"
+ + "Content-Disposition: form-data; name=\"files\"; filename=\"" + testfile.leafName + "\"\r\n"
+ + "Content-Type: application/octet-stream\r\n"
+ + "Content-Length: " + testfile.fileSize + "\r\n\r\n";
+var teststring2 = "--" + BOUNDARY + "--\r\n";
+
+const BUFFERSIZE = 4096;
+var correctOnProgress = false;
+
+var listenerCallback = {
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIProgressEventSink))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function (iid) {
+ if (iid.equals(Ci.nsIProgressEventSink))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onProgress: function (request, context, progress, progressMax) {
+ // this works because the response is 0 bytes and does not trigger onprogress
+ if (progress === progressMax) {
+ correctOnProgress = true;
+ }
+ },
+
+ onStatus: function (request, context, status, statusArg) { },
+};
+
+function run_test() {
+ var sstream1 = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ sstream1.data = teststring1;
+
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(testfile, -1, -1, 0);
+
+ var buffered = Cc["@mozilla.org/network/buffered-input-stream;1"].
+ createInstance(Ci.nsIBufferedInputStream);
+ buffered.init(fstream, BUFFERSIZE);
+
+ var sstream2 = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ sstream2.data = teststring2;
+
+ var multi = Cc["@mozilla.org/io/multiplex-input-stream;1"].
+ createInstance(Ci.nsIMultiplexInputStream);
+ multi.appendStream(sstream1);
+ multi.appendStream(buffered);
+ multi.appendStream(sstream2);
+
+ var mime = Cc["@mozilla.org/network/mime-input-stream;1"].
+ createInstance(Ci.nsIMIMEInputStream);
+ mime.addHeader("Content-Type", "multipart/form-data; boundary="+BOUNDARY);
+ mime.setData(multi);
+ mime.addContentLength = true;
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ var channel = setupChannel(testpath);
+
+ channel.QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(mime, "", mime.available());
+ channel.requestMethod = "POST";
+ channel.notificationCallbacks = listenerCallback;
+ channel.asyncOpen2(new ChannelListener(checkRequest, channel));
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ return NetUtil.newChannel({uri: URL + path, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function serverHandler(metadata, response) {
+ do_check_eq(metadata.method, "POST");
+
+ var data = read_stream(metadata.bodyInputStream,
+ metadata.bodyInputStream.available());
+
+ var testfile_stream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ testfile_stream.init(testfile, -1, -1, 0);
+
+ do_check_eq(teststring1 +
+ read_stream(testfile_stream, testfile_stream.available()) +
+ teststring2,
+ data);
+}
+
+function checkRequest(request, data, context) {
+ do_check_true(correctOnProgress);
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_predictor.js b/netwerk/test/unit/test_predictor.js
new file mode 100644
index 000000000..2f4f580f4
--- /dev/null
+++ b/netwerk/test/unit/test_predictor.js
@@ -0,0 +1,596 @@
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var Cc = Components.classes;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+
+var running_single_process = false;
+
+var predictor = null;
+
+function is_child_process() {
+ return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+}
+
+function extract_origin(uri) {
+ var o = uri.scheme + "://" + uri.asciiHost;
+ if (uri.port !== -1) {
+ o = o + ":" + uri.port;
+ }
+ return o;
+}
+
+var LoadContext = function _loadContext() {
+};
+
+LoadContext.prototype = {
+ usePrivateBrowsing: false,
+
+ getInterface: function loadContext_getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface: function loadContext_QueryInterface(iid) {
+ if (iid.equals(Ci.nsINetworkPredictorVerifier) ||
+ iid.equals(Ci.nsILoadContext)) {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ originAttributes: {}
+};
+
+var load_context = new LoadContext();
+
+var ValidityChecker = function(verifier, httpStatus) {
+ this.verifier = verifier;
+ this.httpStatus = httpStatus;
+};
+
+ValidityChecker.prototype = {
+ verifier: null,
+ httpStatus: 0,
+
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheEntryOpenCallback)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onCacheEntryCheck: function(entry, appCache)
+ {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function(entry, isnew, appCache, status)
+ {
+ // Check if forced valid
+ do_check_eq(entry.isForcedValid, this.httpStatus === 200);
+ this.verifier.maybe_run_next_test();
+ }
+}
+
+var Verifier = function _verifier(testing, expected_prefetches, expected_preconnects, expected_preresolves) {
+ this.verifying = testing;
+ this.expected_prefetches = expected_prefetches;
+ this.expected_preconnects = expected_preconnects;
+ this.expected_preresolves = expected_preresolves;
+};
+
+Verifier.prototype = {
+ complete: false,
+ verifying: null,
+ expected_prefetches: null,
+ expected_preconnects: null,
+ expected_preresolves: null,
+
+ getInterface: function verifier_getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface: function verifier_QueryInterface(iid) {
+ if (iid.equals(Ci.nsINetworkPredictorVerifier) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ maybe_run_next_test: function verifier_maybe_run_next_test() {
+ if (this.expected_prefetches.length === 0 &&
+ this.expected_preconnects.length === 0 &&
+ this.expected_preresolves.length === 0 &&
+ !this.complete) {
+ this.complete = true;
+ do_check_true(true, "Well this is unexpected...");
+ // This kicks off the ability to run the next test
+ reset_predictor();
+ }
+ },
+
+ onPredictPrefetch: function verifier_onPredictPrefetch(uri, status) {
+ var index = this.expected_prefetches.indexOf(uri.asciiSpec);
+ if (index == -1 && !this.complete) {
+ do_check_true(false, "Got prefetch for unexpected uri " + uri.asciiSpec);
+ } else {
+ this.expected_prefetches.splice(index, 1);
+ }
+
+ dump("checking validity of entry for " + uri.spec + "\n");
+ var checker = new ValidityChecker(this, status);
+ asyncOpenCacheEntry(uri.spec, "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ checker);
+ },
+
+ onPredictPreconnect: function verifier_onPredictPreconnect(uri) {
+ var origin = extract_origin(uri);
+ var index = this.expected_preconnects.indexOf(origin);
+ if (index == -1 && !this.complete) {
+ do_check_true(false, "Got preconnect for unexpected uri " + origin);
+ } else {
+ this.expected_preconnects.splice(index, 1);
+ }
+ this.maybe_run_next_test();
+ },
+
+ onPredictDNS: function verifier_onPredictDNS(uri) {
+ var origin = extract_origin(uri);
+ var index = this.expected_preresolves.indexOf(origin);
+ if (index == -1 && !this.complete) {
+ do_check_true(false, "Got preresolve for unexpected uri " + origin);
+ } else {
+ this.expected_preresolves.splice(index, 1);
+ }
+ this.maybe_run_next_test();
+ }
+};
+
+function reset_predictor() {
+ if (running_single_process || is_child_process()) {
+ predictor.reset();
+ } else {
+ sendCommand("predictor.reset();");
+ }
+}
+
+function newURI(s) {
+ return Services.io.newURI(s, null, null);
+}
+
+var prepListener = {
+ numEntriesToOpen: 0,
+ numEntriesOpened: 0,
+ continueCallback: null,
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsICacheEntryOpenCallback)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ init: function (entriesToOpen, cb) {
+ this.numEntriesOpened = 0;
+ this.numEntriesToOpen = entriesToOpen;
+ this.continueCallback = cb;
+ },
+
+ onCacheEntryCheck: function (entry, appCache) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function (entry, isNew, appCache, result) {
+ do_check_eq(result, Cr.NS_OK);
+ entry.setMetaDataElement("predictor_test", "1");
+ entry.metaDataReady();
+ this.numEntriesOpened++;
+ if (this.numEntriesToOpen == this.numEntriesOpened) {
+ this.continueCallback();
+ }
+ }
+};
+
+function open_and_continue(uris, continueCallback) {
+ var ds = Services.cache2.diskCacheStorage(LoadContextInfo.default, false);
+
+ prepListener.init(uris.length, continueCallback);
+ for (var i = 0; i < uris.length; ++i) {
+ ds.asyncOpenURI(uris[i], "", Ci.nsICacheStorage.OPEN_NORMALLY,
+ prepListener);
+ }
+}
+
+function test_link_hover() {
+ if (!running_single_process && !is_child_process()) {
+ // This one we can just proxy to the child and be done with, no extra setup
+ // is necessary.
+ sendCommand("test_link_hover();");
+ return;
+ }
+
+ var uri = newURI("http://localhost:4444/foo/bar");
+ var referrer = newURI("http://localhost:4444/foo");
+ var preconns = ["http://localhost:4444"];
+
+ var verifier = new Verifier("hover", [], preconns, []);
+ predictor.predict(uri, referrer, predictor.PREDICT_LINK, load_context, verifier);
+}
+
+const pageload_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_pageload() {
+ var subresources = [
+ "http://localhost:4444/style.css",
+ "http://localhost:4443/jquery.js",
+ "http://localhost:4444/image.png"
+ ];
+
+ // This is necessary to learn the origin stuff
+ predictor.learn(pageload_toplevel, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+ var preconns = [];
+ for (var i = 0; i < subresources.length; i++) {
+ var sruri = newURI(subresources[i]);
+ predictor.learn(sruri, pageload_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ preconns.push(extract_origin(sruri));
+ }
+
+ var verifier = new Verifier("pageload", [], preconns, []);
+ predictor.predict(pageload_toplevel, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_pageload() {
+ open_and_continue([pageload_toplevel], function () {
+ if (running_single_process) {
+ continue_test_pageload();
+ } else {
+ sendCommand("continue_test_pageload();");
+ }
+ });
+}
+
+const redirect_inituri = newURI("http://localhost:4443/redirect");
+const redirect_targeturi = newURI("http://localhost:4444/index.html");
+
+function continue_test_redrect() {
+ var subresources = [
+ "http://localhost:4444/style.css",
+ "http://localhost:4443/jquery.js",
+ "http://localhost:4444/image.png"
+ ];
+
+ predictor.learn(redirect_inituri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+ predictor.learn(redirect_targeturi, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+ predictor.learn(redirect_targeturi, redirect_inituri, predictor.LEARN_LOAD_REDIRECT, load_context);
+
+ var preconns = [];
+ preconns.push(extract_origin(redirect_targeturi));
+ for (var i = 0; i < subresources.length; i++) {
+ var sruri = newURI(subresources[i]);
+ predictor.learn(sruri, redirect_targeturi, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ preconns.push(extract_origin(sruri));
+ }
+
+ var verifier = new Verifier("redirect", [], preconns, []);
+ predictor.predict(redirect_inituri, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_redirect() {
+ open_and_continue([redirect_inituri, redirect_targeturi], function () {
+ if (running_single_process) {
+ continue_test_redirect();
+ } else {
+ sendCommand("continue_test_redirect();");
+ }
+ });
+}
+
+function test_startup() {
+ if (!running_single_process && !is_child_process()) {
+ // This one we can just proxy to the child and be done with, no extra setup
+ // is necessary.
+ sendCommand("test_startup();");
+ return;
+ }
+
+ var uris = [
+ "http://localhost:4444/startup",
+ "http://localhost:4443/startup"
+ ];
+ var preconns = [];
+ for (var i = 0; i < uris.length; i++) {
+ var uri = newURI(uris[i]);
+ predictor.learn(uri, null, predictor.LEARN_STARTUP, load_context);
+ preconns.push(extract_origin(uri));
+ }
+
+ var verifier = new Verifier("startup", [], preconns, []);
+ predictor.predict(null, null, predictor.PREDICT_STARTUP, load_context, verifier);
+}
+
+const dns_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_dns() {
+ var subresource = "http://localhost:4443/jquery.js";
+
+ predictor.learn(dns_toplevel, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+ var sruri = newURI(subresource);
+ predictor.learn(sruri, dns_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+
+ var preresolves = [extract_origin(sruri)];
+ var verifier = new Verifier("dns", [], [], preresolves);
+ predictor.predict(dns_toplevel, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_dns() {
+ open_and_continue([dns_toplevel], function () {
+ // Ensure that this will do preresolves
+ Services.prefs.setIntPref("network.predictor.preconnect-min-confidence", 101);
+ if (running_single_process) {
+ continue_test_dns();
+ } else {
+ sendCommand("continue_test_dns();");
+ }
+ });
+}
+
+const origin_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_origin() {
+ var subresources = [
+ "http://localhost:4444/style.css",
+ "http://localhost:4443/jquery.js",
+ "http://localhost:4444/image.png"
+ ];
+ predictor.learn(origin_toplevel, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+ var preconns = [];
+ for (var i = 0; i < subresources.length; i++) {
+ var sruri = newURI(subresources[i]);
+ predictor.learn(sruri, origin_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ var origin = extract_origin(sruri);
+ if (preconns.indexOf(origin) === -1) {
+ preconns.push(origin);
+ }
+ }
+
+ var loaduri = newURI("http://localhost:4444/anotherpage.html");
+ var verifier = new Verifier("origin", [], preconns, []);
+ predictor.predict(loaduri, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_origin() {
+ open_and_continue([origin_toplevel], function () {
+ if (running_single_process) {
+ continue_test_origin();
+ } else {
+ sendCommand("continue_test_origin();");
+ }
+ });
+}
+
+var httpserv = null;
+var prefetch_tluri;
+var prefetch_sruri;
+
+function prefetchHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ var body = "Success (meow meow meow).";
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var prefetchListener = {
+ onStartRequest: function(request, ctx) {
+ do_check_eq(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function(request, cx, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function(request, ctx, status) {
+ run_next_test();
+ }
+};
+
+function test_prefetch_setup() {
+ // Disable preconnects and preresolves
+ Services.prefs.setIntPref("network.predictor.preconnect-min-confidence", 101);
+ Services.prefs.setIntPref("network.predictor.preresolve-min-confidence", 101);
+
+ Services.prefs.setBoolPref("network.predictor.enable-prefetch", true);
+
+ // Makes it so we only have to call test_prefetch_prime twice to make prefetch
+ // do its thing.
+ Services.prefs.setIntPref("network.predictor.prefetch-rolling-load-count", 2);
+
+ // This test does not run in e10s-mode, so we'll just go ahead and skip it.
+ // We've left the e10s test code in below, just in case someone wants to try
+ // to make it work at some point in the future.
+ if (!running_single_process) {
+ dump("skipping test_prefetch_setup due to e10s\n");
+ run_next_test();
+ return;
+ }
+
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/cat.jpg", prefetchHandler);
+ httpserv.start(-1);
+
+ var tluri = "http://127.0.0.1:" + httpserv.identity.primaryPort + "/index.html";
+ var sruri = "http://127.0.0.1:" + httpserv.identity.primaryPort + "/cat.jpg";
+ prefetch_tluri = newURI(tluri);
+ prefetch_sruri = newURI(sruri);
+ if (!running_single_process && !is_child_process()) {
+ // Give the child process access to these values
+ sendCommand("prefetch_tluri = newURI(\"" + tluri + "\");");
+ sendCommand("prefetch_sruri = newURI(\"" + sruri + "\");");
+ }
+
+ run_next_test();
+}
+
+// Used to "prime the pump" for prefetch - it makes sure all our learns go
+// through as expected so that prefetching will happen.
+function test_prefetch_prime() {
+ // This test does not run in e10s-mode, so we'll just go ahead and skip it.
+ // We've left the e10s test code in below, just in case someone wants to try
+ // to make it work at some point in the future.
+ if (!running_single_process) {
+ dump("skipping test_prefetch_prime due to e10s\n");
+ run_next_test();
+ return;
+ }
+
+ open_and_continue([prefetch_tluri], function() {
+ if (running_single_process) {
+ predictor.learn(prefetch_tluri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+ predictor.learn(prefetch_sruri, prefetch_tluri, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ } else {
+ sendCommand("predictor.learn(prefetch_tluri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);");
+ sendCommand("predictor.learn(prefetch_sruri, prefetch_tluri, predictor.LEARN_LOAD_SUBRESOURCE, load_context);");
+ }
+
+ // This runs in the parent or only process
+ var channel = NetUtil.newChannel({
+ uri: prefetch_sruri.asciiSpec,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel);
+ channel.requestMethod = "GET";
+ channel.referrer = prefetch_tluri;
+ channel.asyncOpen2(prefetchListener);
+ });
+}
+
+function test_prefetch() {
+ // This test does not run in e10s-mode, so we'll just go ahead and skip it.
+ // We've left the e10s test code in below, just in case someone wants to try
+ // to make it work at some point in the future.
+ if (!running_single_process) {
+ dump("skipping test_prefetch due to e10s\n");
+ run_next_test();
+ return;
+ }
+
+ // Setup for this has all been taken care of by test_prefetch_prime, so we can
+ // continue on without pausing here.
+ if (running_single_process) {
+ continue_test_prefetch();
+ } else {
+ sendCommand("continue_test_prefetch();");
+ }
+}
+
+function continue_test_prefetch() {
+ var prefetches = [prefetch_sruri.asciiSpec];
+ var verifier = new Verifier("prefetch", prefetches, [], []);
+ predictor.predict(prefetch_tluri, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function cleanup() {
+ observer.cleaningUp = true;
+ if (running_single_process) {
+ // The http server is required (and started) by the prefetch test, which
+ // only runs in single-process mode, so don't try to shut it down if we're
+ // in e10s mode.
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+ reset_predictor();
+}
+
+var tests = [
+ // This must ALWAYS come first, to ensure a clean slate
+ reset_predictor,
+ test_link_hover,
+ test_pageload,
+ // TODO: These are disabled until the features are re-written
+ //test_redirect,
+ //test_startup,
+ // END DISABLED TESTS
+ test_origin,
+ test_dns,
+ test_prefetch_setup,
+ test_prefetch_prime,
+ test_prefetch_prime,
+ test_prefetch,
+ // This must ALWAYS come last, to ensure we clean up after ourselves
+ cleanup
+];
+
+var observer = {
+ cleaningUp: false,
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic != "predictor-reset-complete") {
+ return;
+ }
+
+ if (this.cleaningUp) {
+ unregisterObserver();
+ }
+
+ run_next_test();
+ }
+};
+
+function registerObserver() {
+ Services.obs.addObserver(observer, "predictor-reset-complete", false);
+}
+
+function unregisterObserver() {
+ Services.obs.removeObserver(observer, "predictor-reset-complete");
+}
+
+function run_test_real() {
+ tests.forEach(add_test);
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.predictor.enabled", true);
+ Services.prefs.setBoolPref("network.predictor.cleaned-up", true);
+ Services.prefs.setBoolPref("browser.cache.use_new_backend_temp", true);
+ Services.prefs.setIntPref("browser.cache.use_new_backend", 1);
+ Services.prefs.setBoolPref("network.predictor.doing-tests", true);
+
+ predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
+
+ registerObserver();
+
+ do_register_cleanup(() => {
+ Services.prefs.clearUserPref("network.predictor.preconnect-min-confidence");
+ Services.prefs.clearUserPref("network.predictor.enabled");
+ Services.prefs.clearUserPref("network.predictor.cleaned-up");
+ Services.prefs.clearUserPref("browser.cache.use_new_backend_temp");
+ Services.prefs.clearUserPref("browser.cache.use_new_backend");
+ Services.prefs.clearUserPref("network.predictor.preresolve-min-confidence");
+ Services.prefs.clearUserPref("network.predictor.enable-prefetch");
+ Services.prefs.clearUserPref("network.predictor.prefetch-rolling-load-count");
+ Services.prefs.clearUserPref("network.predictor.doing-tests");
+ });
+
+ run_next_test();
+}
+
+function run_test() {
+ // This indirection is necessary to make e10s tests work as expected
+ running_single_process = true;
+ run_test_real();
+}
diff --git a/netwerk/test/unit/test_private_cookie_changed.js b/netwerk/test/unit/test_private_cookie_changed.js
new file mode 100644
index 000000000..6c6f31de4
--- /dev/null
+++ b/netwerk/test/unit/test_private_cookie_changed.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function makeChan(uri, isPrivate) {
+ var chan = NetUtil.newChannel ({
+ uri: uri.spec,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ chan.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(isPrivate);
+ return chan;
+}
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ let publicNotifications = 0;
+ let privateNotifications = 0;
+ Services.obs.addObserver(function() {publicNotifications++;}, "cookie-changed", false);
+ Services.obs.addObserver(function() {privateNotifications++;}, "private-cookie-changed", false);
+
+ let uri = NetUtil.newURI("http://foo.com/");
+ let publicChan = makeChan(uri, false);
+ let svc = Services.cookies.QueryInterface(Ci.nsICookieService);
+ svc.setCookieString(uri, null, "oh=hai", publicChan);
+ let privateChan = makeChan(uri, true);
+ svc.setCookieString(uri, null, "oh=hai", privateChan);
+ do_check_eq(publicNotifications, 1);
+ do_check_eq(privateNotifications, 1);
+}
diff --git a/netwerk/test/unit/test_private_necko_channel.js b/netwerk/test/unit/test_private_necko_channel.js
new file mode 100644
index 000000000..530d4e7e6
--- /dev/null
+++ b/netwerk/test/unit/test_private_necko_channel.js
@@ -0,0 +1,53 @@
+//
+// Private channel test
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+function run_test() {
+ // Simulate a profile dir for xpcshell
+ do_get_profile();
+
+ // Start off with an empty cache
+ evict_cache_entries();
+
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ var channel = setupChannel(testpath);
+ channel.loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance();
+
+ channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ channel.setPrivate(true);
+
+ channel.asyncOpen2(new ChannelListener(checkRequest, channel));
+
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + path,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function checkRequest(request, data, context) {
+ get_device_entry_count("disk", null, function(count) {
+ do_check_eq(count, 0)
+ get_device_entry_count("disk", LoadContextInfo.private, function(count) {
+ do_check_eq(count, 1);
+ httpserver.stop(do_test_finished);
+ });
+ });
+}
diff --git a/netwerk/test/unit/test_progress.js b/netwerk/test/unit/test_progress.js
new file mode 100644
index 000000000..e2dae9c09
--- /dev/null
+++ b/netwerk/test/unit/test_progress.js
@@ -0,0 +1,128 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+
+var last = 0, max = 0;
+
+const STATUS_RECEIVING_FROM = 0x804b0006;
+const LOOPS = 50000;
+
+const TYPE_ONSTATUS = 1;
+const TYPE_ONPROGRESS = 2;
+const TYPE_ONSTARTREQUEST = 3;
+const TYPE_ONDATAAVAILABLE = 4;
+const TYPE_ONSTOPREQUEST = 5;
+
+var progressCallback = {
+ _listener: null,
+ _got_onstartrequest: false,
+ _got_onstatus_after_onstartrequest: false,
+ _last_callback_handled: null,
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIProgressEventSink) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function (iid) {
+ if (iid.equals(Ci.nsIProgressEventSink) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ do_check_eq(this._last_callback_handled, TYPE_ONSTATUS);
+ this._got_onstartrequest = true;
+ this._last_callback_handled = TYPE_ONSTARTREQUEST;
+
+ this._listener = new ChannelListener(checkRequest, request);
+ this._listener.onStartRequest(request, context);
+ },
+
+ onDataAvailable: function(request, context, data, offset, count) {
+ do_check_eq(this._last_callback_handled, TYPE_ONPROGRESS);
+ this._last_callback_handled = TYPE_ONDATAAVAILABLE;
+
+ this._listener.onDataAvailable(request, context, data, offset, count);
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ do_check_true(this._got_onstatus_after_onstartrequest);
+ this._last_callback_handled = TYPE_ONSTOPREQUEST;
+
+ this._listener.onStopRequest(request, context, status);
+ delete this._listener;
+ },
+
+ onProgress: function (request, context, progress, progressMax) {
+ do_check_eq(this._last_callback_handled, TYPE_ONSTATUS);
+ this._last_callback_handled = TYPE_ONPROGRESS;
+
+ do_check_eq(mStatus, STATUS_RECEIVING_FROM);
+ last = progress;
+ max = progressMax;
+ },
+
+ onStatus: function (request, context, status, statusArg) {
+ if (!this._got_onstartrequest) {
+ // Ensure that all messages before onStartRequest are onStatus
+ if (this._last_callback_handled)
+ do_check_eq(this._last_callback_handled, TYPE_ONSTATUS);
+ } else if (this._last_callback_handled == TYPE_ONSTARTREQUEST) {
+ this._got_onstatus_after_onstartrequest = true;
+ } else {
+ do_check_eq(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ }
+ this._last_callback_handled = TYPE_ONSTATUS;
+
+ do_check_eq(statusArg, "localhost");
+ mStatus = status;
+ },
+
+ mStatus: 0,
+};
+
+function run_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ var channel = setupChannel(testpath);
+ channel.asyncOpen2(progressCallback);
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ chan.notificationCallbacks = progressCallback;
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ for (let i = 0; i < LOOPS; i++)
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
+
+function checkRequest(request, data, context) {
+ do_check_eq(last, httpbody.length*LOOPS);
+ do_check_eq(max, httpbody.length*LOOPS);
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_protocolproxyservice.js b/netwerk/test/unit/test_protocolproxyservice.js
new file mode 100644
index 000000000..ff44fb4d8
--- /dev/null
+++ b/netwerk/test/unit/test_protocolproxyservice.js
@@ -0,0 +1,958 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This testcase exercises the Protocol Proxy Service
+
+// These are the major sub tests:
+// run_filter_test();
+// run_filter_test2()
+// run_filter_test3()
+// run_pref_test();
+// run_pac_test();
+// run_pac_cancel_test();
+// run_proxy_host_filters_test();
+// run_myipaddress_test();
+// run_failed_script_test();
+// run_isresolvable_test();
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService();
+var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+/**
+ * Test nsIProtocolHandler that allows proxying, but doesn't allow HTTP
+ * proxying.
+ */
+function TestProtocolHandler() {
+}
+TestProtocolHandler.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolHandler) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ scheme: "moz-test",
+ defaultPort: -1,
+ protocolFlags: Components.interfaces.nsIProtocolHandler.URI_NOAUTH |
+ Components.interfaces.nsIProtocolHandler.URI_NORELATIVE |
+ Components.interfaces.nsIProtocolHandler.ALLOWS_PROXY |
+ Components.interfaces.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
+ newURI: function(spec, originCharset, baseURI) {
+ var uri = Components.classes["@mozilla.org/network/simple-uri;1"]
+ .createInstance(Components.interfaces.nsIURI);
+ uri.spec = spec;
+ return uri;
+ },
+ newChannel2: function(uri, aLoadInfo) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ newChannel: function(uri) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ allowPort: function(port, scheme) {
+ return true;
+ }
+};
+
+function TestProtocolHandlerFactory() {
+}
+TestProtocolHandlerFactory.prototype = {
+ createInstance: function(delegate, iid) {
+ return new TestProtocolHandler().QueryInterface(iid);
+ },
+ lockFactory: function(lock) {
+ }
+};
+
+function register_test_protocol_handler() {
+ var reg = Components.manager.QueryInterface(
+ Components.interfaces.nsIComponentRegistrar);
+ reg.registerFactory(Components.ID("{4ea7dd3a-8cae-499c-9f18-e1de773ca25b}"),
+ "TestProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=moz-test",
+ new TestProtocolHandlerFactory());
+}
+
+function check_proxy(pi, type, host, port, flags, timeout, hasNext) {
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, type);
+ do_check_eq(pi.host, host);
+ do_check_eq(pi.port, port);
+ if (flags != -1)
+ do_check_eq(pi.flags, flags);
+ if (timeout != -1)
+ do_check_eq(pi.failoverTimeout, timeout);
+ if (hasNext)
+ do_check_neq(pi.failoverProxy, null);
+ else
+ do_check_eq(pi.failoverProxy, null);
+}
+
+function TestFilter(type, host, port, flags, timeout) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this._timeout = timeout;
+}
+TestFilter.prototype = {
+ _type: "",
+ _host: "",
+ _port: -1,
+ _flags: 0,
+ _timeout: 0,
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyFilter) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ applyFilter: function(pps, uri, pi) {
+ var pi_tail = pps.newProxyInfo(this._type, this._host, this._port,
+ this._flags, this._timeout, null);
+ if (pi)
+ pi.failoverProxy = pi_tail;
+ else
+ pi = pi_tail;
+ return pi;
+ }
+};
+
+function BasicFilter() {}
+BasicFilter.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyFilter) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ applyFilter: function(pps, uri, pi) {
+ return pps.newProxyInfo("http", "localhost", 8080, 0, 10,
+ pps.newProxyInfo("direct", "", -1, 0, 0, null));
+ }
+};
+
+function BasicChannelFilter() {}
+BasicChannelFilter.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyChannelFilter) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ applyFilter: function(pps, channel, pi) {
+ return pps.newProxyInfo("http", channel.URI.host, 7777, 0, 10,
+ pps.newProxyInfo("direct", "", -1, 0, 0, null));
+ }
+};
+
+function resolveCallback() { }
+resolveCallback.prototype = {
+ nextFunction: null,
+
+ QueryInterface : function (iid) {
+ const interfaces = [Components.interfaces.nsIProtocolProxyCallback,
+ Components.interfaces.nsISupports];
+ if (!interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+ onProxyAvailable : function (req, uri, pi, status) {
+ this.nextFunction(pi);
+ }
+};
+
+function run_filter_test() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+
+ // Verify initial state
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_1;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+var filter01;
+var filter02;
+
+function filter_test0_1(pi) {
+ do_check_eq(pi, null);
+
+ // Push a filter and verify the results
+
+ filter01 = new BasicFilter();
+ filter02 = new BasicFilter();
+ pps.registerFilter(filter01, 10);
+ pps.registerFilter(filter02, 20);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test0_2(pi)
+{
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ pps.unregisterFilter(filter02);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_3;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test0_3(pi)
+{
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ // Remove filter and verify that we return to the initial state
+
+ pps.unregisterFilter(filter01);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_4;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+var filter03;
+
+function filter_test0_4(pi)
+{
+ do_check_eq(pi, null);
+ filter03 = new BasicChannelFilter();
+ pps.registerChannelFilter(filter03, 10);
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test0_5;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test0_5(pi)
+{
+ pps.unregisterChannelFilter(filter03);
+ check_proxy(pi, "http", "www.mozilla.org", 7777, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+ run_filter_test_uri();
+}
+
+function run_filter_test_uri() {
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_1;
+ var uri = ios.newURI("http://www.mozilla.org/", null, null);
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_1(pi) {
+ do_check_eq(pi, null);
+
+ // Push a filter and verify the results
+
+ filter01 = new BasicFilter();
+ filter02 = new BasicFilter();
+ pps.registerFilter(filter01, 10);
+ pps.registerFilter(filter02, 20);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_2;
+ var uri = ios.newURI("http://www.mozilla.org/", null, null);
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_2(pi)
+{
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ pps.unregisterFilter(filter02);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_3;
+ var uri = ios.newURI("http://www.mozilla.org/", null, null);
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_3(pi)
+{
+ check_proxy(pi, "http", "localhost", 8080, 0, 10, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, 0, 0, false);
+
+ // Remove filter and verify that we return to the initial state
+
+ pps.unregisterFilter(filter01);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test_uri0_4;
+ var uri = ios.newURI("http://www.mozilla.org/", null, null);
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_4(pi)
+{
+ do_check_eq(pi, null);
+ run_filter_test2();
+}
+
+var filter11;
+var filter12;
+
+function run_filter_test2() {
+ // Push a filter and verify the results
+
+ filter11 = new TestFilter("http", "foo", 8080, 0, 10);
+ filter12 = new TestFilter("http", "bar", 8090, 0, 10);
+ pps.registerFilter(filter11, 20);
+ pps.registerFilter(filter12, 10);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_1;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_1(pi) {
+ check_proxy(pi, "http", "bar", 8090, 0, 10, true);
+ check_proxy(pi.failoverProxy, "http", "foo", 8080, 0, 10, false);
+
+ pps.unregisterFilter(filter12);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_2(pi) {
+ check_proxy(pi, "http", "foo", 8080, 0, 10, false);
+
+ // Remove filter and verify that we return to the initial state
+
+ pps.unregisterFilter(filter11);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test1_3;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_3(pi) {
+ do_check_eq(pi, null);
+ run_filter_test3();
+}
+
+var filter_3_1;
+
+function run_filter_test3() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ // Push a filter and verify the results asynchronously
+
+ filter_3_1 = new TestFilter("http", "foo", 8080, 0, 10);
+ pps.registerFilter(filter_3_1, 20);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = filter_test3_1;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test3_1(pi) {
+ check_proxy(pi, "http", "foo", 8080, 0, 10, false);
+ pps.unregisterFilter(filter_3_1);
+ run_pref_test();
+}
+
+function run_pref_test() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ // Verify 'direct' setting
+
+ prefs.setIntPref("network.proxy.type", 0);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_1;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_1(pi)
+{
+ do_check_eq(pi, null);
+
+ // Verify 'manual' setting
+ prefs.setIntPref("network.proxy.type", 1);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_2;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_2(pi)
+{
+ // nothing yet configured
+ do_check_eq(pi, null);
+
+ // try HTTP configuration
+ prefs.setCharPref("network.proxy.http", "foopy");
+ prefs.setIntPref("network.proxy.http_port", 8080);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_3;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_3(pi)
+{
+ check_proxy(pi, "http", "foopy", 8080, 0, -1, false);
+
+ prefs.setCharPref("network.proxy.http", "");
+ prefs.setIntPref("network.proxy.http_port", 0);
+
+ // try SOCKS configuration
+ prefs.setCharPref("network.proxy.socks", "barbar");
+ prefs.setIntPref("network.proxy.socks_port", 1203);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = pref_test1_4;
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_4(pi)
+{
+ check_proxy(pi, "socks", "barbar", 1203, 0, -1, false);
+ run_pac_test();
+}
+
+function protocol_handler_test_1(pi)
+{
+ do_check_eq(pi, null);
+ prefs.setCharPref("network.proxy.autoconfig_url", "");
+ prefs.setIntPref("network.proxy.type", 0);
+
+ run_pac_cancel_test();
+}
+
+function TestResolveCallback(type, nexttest) {
+ this.type = type;
+ this.nexttest = nexttest;
+}
+TestResolveCallback.prototype = {
+ QueryInterface:
+ function TestResolveCallback_QueryInterface(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyCallback) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onProxyAvailable:
+ function TestResolveCallback_onProxyAvailable(req, uri, pi, status) {
+ dump("*** uri=" + uri.spec + ", status=" + status + "\n");
+
+ if (this.type == null) {
+ do_check_eq(pi, null);
+ } else {
+ do_check_neq(req, null);
+ do_check_neq(uri, null);
+ do_check_eq(status, 0);
+ do_check_neq(pi, null);
+ check_proxy(pi, this.type, "foopy", 8080, 0, -1, true);
+ check_proxy(pi.failoverProxy, "direct", "", -1, -1, -1, false);
+ }
+
+ this.nexttest();
+ }
+};
+
+var originalTLSProxy;
+
+function run_pac_test() {
+ var pac = 'data:text/plain,' +
+ 'function FindProxyForURL(url, host) {' +
+ ' return "PROXY foopy:8080; DIRECT";' +
+ '}';
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ // Configure PAC
+
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ var req = pps.asyncResolve(channel, 0, new TestResolveCallback("http", run_pac2_test));
+}
+
+function run_pac2_test() {
+ var pac = 'data:text/plain,' +
+ 'function FindProxyForURL(url, host) {' +
+ ' return "HTTPS foopy:8080; DIRECT";' +
+ '}';
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ // Configure PAC
+ originalTLSProxy = prefs.getBoolPref("network.proxy.proxy_over_tls");
+
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ prefs.setBoolPref("network.proxy.proxy_over_tls", true);
+
+ var req = pps.asyncResolve(channel, 0, new TestResolveCallback("https", run_pac3_test));
+}
+
+function run_pac3_test() {
+ var pac = 'data:text/plain,' +
+ 'function FindProxyForURL(url, host) {' +
+ ' return "HTTPS foopy:8080; DIRECT";' +
+ '}';
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ // Configure PAC
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ prefs.setBoolPref("network.proxy.proxy_over_tls", false);
+
+ var req = pps.asyncResolve(channel, 0, new TestResolveCallback(null, run_pac4_test));
+}
+
+function run_pac4_test() {
+ // Bug 1251332
+ let wRange = [
+ ["SUN", "MON", "SAT", "MON"], // for Sun
+ ["SUN", "TUE", "SAT", "TUE"], // for Mon
+ ["MON", "WED", "SAT", "WED"], // for Tue
+ ["TUE", "THU", "SAT", "THU"], // for Wed
+ ["WED", "FRI", "WED", "SUN"], // for Thu
+ ["THU", "SAT", "THU", "SUN"], // for Fri
+ ["FRI", "SAT", "FRI", "SUN"], // for Sat
+ ];
+ let today = (new Date()).getDay();
+ var pac = 'data:text/plain,' +
+ 'function FindProxyForURL(url, host) {' +
+ ' if (weekdayRange("' + wRange[today][0] + '", "' + wRange[today][1] + '") &&' +
+ ' weekdayRange("' + wRange[today][2] + '", "' + wRange[today][3] + '")) {' +
+ ' return "PROXY foopy:8080; DIRECT";' +
+ ' }' +
+ '}';
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ // Configure PAC
+
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+ var req = pps.asyncResolve(channel, 0, new TestResolveCallback("http", finish_pac_test));
+}
+
+function finish_pac_test() {
+ prefs.setBoolPref("network.proxy.proxy_over_tls", originalTLSProxy);
+ run_pac_cancel_test();
+}
+
+function TestResolveCancelationCallback() {
+}
+TestResolveCancelationCallback.prototype = {
+ QueryInterface:
+ function TestResolveCallback_QueryInterface(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyCallback) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onProxyAvailable:
+ function TestResolveCancelationCallback_onProxyAvailable(req, uri, pi, status) {
+ dump("*** uri=" + uri.spec + ", status=" + status + "\n");
+
+ do_check_neq(req, null);
+ do_check_neq(uri, null);
+ do_check_eq(status, Components.results.NS_ERROR_ABORT);
+ do_check_eq(pi, null);
+
+ prefs.setCharPref("network.proxy.autoconfig_url", "");
+ prefs.setIntPref("network.proxy.type", 0);
+
+ run_proxy_host_filters_test();
+ }
+};
+
+function run_pac_cancel_test() {
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ // Configure PAC
+ var pac = 'data:text/plain,' +
+ 'function FindProxyForURL(url, host) {' +
+ ' return "PROXY foopy:8080; DIRECT";' +
+ '}';
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var req = pps.asyncResolve(channel, 0, new TestResolveCancelationCallback());
+ req.cancel(Components.results.NS_ERROR_ABORT);
+}
+
+var hostList;
+var hostIDX;
+var bShouldBeFiltered;
+var hostNextFX;
+
+function check_host_filters(hl, shouldBe, nextFX) {
+ hostList = hl;
+ hostIDX = 0;
+ bShouldBeFiltered = shouldBe;
+ hostNextFX = nextFX;
+
+ if (hostList.length > hostIDX)
+ check_host_filter(hostIDX);
+}
+
+function check_host_filters_cb()
+{
+ hostIDX++;
+ if (hostList.length > hostIDX)
+ check_host_filter(hostIDX);
+ else
+ hostNextFX();
+}
+
+function check_host_filter(i) {
+ var uri;
+ dump("*** uri=" + hostList[i] + " bShouldBeFiltered=" + bShouldBeFiltered + "\n");
+ var channel = NetUtil.newChannel({
+ uri: hostList[i],
+ loadUsingSystemPrincipal: true
+ });
+ var cb = new resolveCallback();
+ cb.nextFunction = host_filter_cb;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function host_filter_cb(proxy)
+{
+ if (bShouldBeFiltered) {
+ do_check_eq(proxy, null);
+ } else {
+ do_check_neq(proxy, null);
+ // Just to be sure, let's check that the proxy is correct
+ // - this should match the proxy setup in the calling function
+ check_proxy(proxy, "http", "foopy", 8080, 0, -1, false);
+ }
+ check_host_filters_cb();
+}
+
+
+// Verify that hists in the host filter list are not proxied
+// refers to "network.proxy.no_proxies_on"
+
+var uriStrUseProxyList;
+var uriStrUseProxyList;
+var hostFilterList;
+
+function run_proxy_host_filters_test() {
+ // Get prefs object from DOM
+ // Setup a basic HTTP proxy configuration
+ // - pps.resolve() needs this to return proxy info for non-filtered hosts
+ prefs.setIntPref("network.proxy.type", 1);
+ prefs.setCharPref("network.proxy.http", "foopy");
+ prefs.setIntPref("network.proxy.http_port", 8080);
+
+ // Setup host filter list string for "no_proxies_on"
+ hostFilterList = "www.mozilla.org, www.google.com, www.apple.com, "
+ + ".domain, .domain2.org"
+ prefs.setCharPref("network.proxy.no_proxies_on", hostFilterList);
+ do_check_eq(prefs.getCharPref("network.proxy.no_proxies_on"), hostFilterList);
+
+ var rv;
+ // Check the hosts that should be filtered out
+ uriStrFilterList = [ "http://www.mozilla.org/",
+ "http://www.google.com/",
+ "http://www.apple.com/",
+ "http://somehost.domain/",
+ "http://someotherhost.domain/",
+ "http://somehost.domain2.org/",
+ "http://somehost.subdomain.domain2.org/" ];
+ check_host_filters(uriStrFilterList, true, host_filters_1);
+}
+
+function host_filters_1()
+{
+ // Check the hosts that should be proxied
+ uriStrUseProxyList = [ "http://www.mozilla.com/",
+ "http://mail.google.com/",
+ "http://somehost.domain.co.uk/",
+ "http://somelocalhost/" ];
+ check_host_filters(uriStrUseProxyList, false, host_filters_2);
+}
+
+function host_filters_2()
+{
+ // Set no_proxies_on to include local hosts
+ prefs.setCharPref("network.proxy.no_proxies_on", hostFilterList + ", <local>");
+ do_check_eq(prefs.getCharPref("network.proxy.no_proxies_on"),
+ hostFilterList + ", <local>");
+ // Amend lists - move local domain to filtered list
+ uriStrFilterList.push(uriStrUseProxyList.pop());
+ check_host_filters(uriStrFilterList, true, host_filters_3);
+}
+
+function host_filters_3()
+{
+ check_host_filters(uriStrUseProxyList, false, host_filters_4);
+}
+
+function host_filters_4()
+{
+ // Cleanup
+ prefs.setCharPref("network.proxy.no_proxies_on", "");
+ do_check_eq(prefs.getCharPref("network.proxy.no_proxies_on"), "");
+
+ run_myipaddress_test();
+}
+
+function run_myipaddress_test()
+{
+ // This test makes sure myIpAddress() comes up with some valid
+ // IP address other than localhost. The DUT must be configured with
+ // an Internet route for this to work - though no Internet traffic
+ // should be created.
+
+ var pac = 'data:text/plain,' +
+ 'var pacUseMultihomedDNS = true;\n' +
+ 'function FindProxyForURL(url, host) {' +
+ ' return "PROXY " + myIpAddress() + ":1234";' +
+ '}';
+
+ // no traffic to this IP is ever sent, it is just a public IP that
+ // does not require DNS to determine a route.
+ var channel = NetUtil.newChannel({
+ uri: "http://192.0.43.10/",
+ loadUsingSystemPrincipal: true
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = myipaddress_callback;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function myipaddress_callback(pi)
+{
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, "http");
+ do_check_eq(pi.port, 1234);
+
+ // make sure we didn't return localhost
+ do_check_neq(pi.host, null);
+ do_check_neq(pi.host, "127.0.0.1");
+ do_check_neq(pi.host, "::1");
+
+ run_myipaddress_test_2();
+}
+
+function run_myipaddress_test_2()
+{
+ // test that myIPAddress() can be used outside of the scope of
+ // FindProxyForURL(). bug 829646.
+
+ var pac = 'data:text/plain,' +
+ 'var pacUseMultihomedDNS = true;\n' +
+ 'var myaddr = myIpAddress(); ' +
+ 'function FindProxyForURL(url, host) {' +
+ ' return "PROXY " + myaddr + ":5678";' +
+ '}';
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = myipaddress2_callback;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function myipaddress2_callback(pi)
+{
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, "http");
+ do_check_eq(pi.port, 5678);
+
+ // make sure we didn't return localhost
+ do_check_neq(pi.host, null);
+ do_check_neq(pi.host, "127.0.0.1");
+ do_check_neq(pi.host, "::1");
+
+ run_failed_script_test();
+}
+
+function run_failed_script_test()
+{
+ // test to make sure we go direct with invalid PAC
+ var pac = 'data:text/plain,' +
+ '\nfor(;\n';
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = failed_script_callback;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+var directFilter;
+
+function failed_script_callback(pi)
+{
+ // we should go direct
+ do_check_eq(pi, null);
+
+ // test that we honor filters when configured to go direct
+ prefs.setIntPref("network.proxy.type", 0);
+ directFilter = new TestFilter("http", "127.0.0.1", 7246, 0, 0);
+ pps.registerFilter(directFilter, 10);
+
+ // test that on-modify-request contains the proxy info too
+ var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.addObserver(directFilterListener, "http-on-modify-request", false);
+
+ var chan = NetUtil.newChannel({
+ uri: "http://127.0.0.1:7247",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(directFilterListener);
+}
+
+var directFilterListener = {
+ onModifyRequestCalled : false,
+
+ onStartRequest: function test_onStart(request, ctx) { },
+ onDataAvailable: function test_OnData() { },
+
+ onStopRequest: function test_onStop(request, ctx, status) {
+ // check on the PI from the channel itself
+ request.QueryInterface(Components.interfaces.nsIProxiedChannel);
+ check_proxy(request.proxyInfo, "http", "127.0.0.1", 7246, 0, 0, false);
+ pps.unregisterFilter(directFilter);
+
+ // check on the PI from on-modify-request
+ do_check_true(this.onModifyRequestCalled);
+ var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.removeObserver(this, "http-on-modify-request");
+
+ run_isresolvable_test();
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic === "http-on-modify-request" &&
+ subject instanceof Components.interfaces.nsIHttpChannel &&
+ subject instanceof Components.interfaces.nsIProxiedChannel) {
+ check_proxy(subject.proxyInfo, "http", "127.0.0.1", 7246, 0, 0, false);
+ this.onModifyRequestCalled = true;
+ }
+ }
+};
+
+function run_isresolvable_test()
+{
+ // test a non resolvable host in the pac file
+
+ var pac = 'data:text/plain,' +
+ 'function FindProxyForURL(url, host) {' +
+ ' if (isResolvable("nonexistant.lan.onion"))' +
+ ' return "DIRECT";' +
+ ' return "PROXY 127.0.0.1:1234";' +
+ '}';
+
+ var channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ });
+ prefs.setIntPref("network.proxy.type", 2);
+ prefs.setCharPref("network.proxy.autoconfig_url", pac);
+
+ var cb = new resolveCallback();
+ cb.nextFunction = isresolvable_callback;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function isresolvable_callback(pi)
+{
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, "http");
+ do_check_eq(pi.port, 1234);
+ do_check_eq(pi.host, "127.0.0.1");
+
+ prefs.setIntPref("network.proxy.type", 0);
+ do_test_finished();
+}
+
+function run_test() {
+ register_test_protocol_handler();
+
+ // start of asynchronous test chain
+ run_filter_test();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-failover_canceled.js b/netwerk/test/unit/test_proxy-failover_canceled.js
new file mode 100644
index 000000000..6c81d093c
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_canceled.js
@@ -0,0 +1,53 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ // we want to cancel the failover proxy engage, so, do not allow
+ // redirects from now.
+
+ var nc = new ChannelEventSink();
+ nc._flags = ES_ABORT_REDIRECT;
+
+ var prefserv = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var prefs = prefserv.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref("autoconfig_url", "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY a_non_existent_domain_x7x6c572v:80; PROXY localhost:" +
+ httpServer.identity.primaryPort + "';}"
+ );
+
+ var chan = make_channel("http://localhost:" +
+ httpServer.identity.primaryPort + "/content");
+ chan.notificationCallbacks = nc;
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-failover_passing.js b/netwerk/test/unit/test_proxy-failover_passing.js
new file mode 100644
index 000000000..b2bf198dd
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_passing.js
@@ -0,0 +1,43 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var prefserv = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var prefs = prefserv.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref("autoconfig_url", "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY a_non_existent_domain_x7x6c572v:80; PROXY localhost:" +
+ httpServer.identity.primaryPort + "';}"
+ );
+
+ var chan = make_channel("http://localhost:" +
+ httpServer.identity.primaryPort + "/content");
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-replace_canceled.js b/netwerk/test/unit/test_proxy-replace_canceled.js
new file mode 100644
index 000000000..d25bfd2c1
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_canceled.js
@@ -0,0 +1,55 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var prefserv = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var prefs = prefserv.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref("autoconfig_url", "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY localhost:" +
+ httpServer.identity.primaryPort + "';}"
+ );
+
+ // this test assumed that a AsyncOnChannelRedirect query is made for
+ // each proxy failover or on the inital proxy only when PAC mode is used.
+ // Neither of those are documented anywhere that I can find and the latter
+ // hasn't been a useful property because it is PAC dependent and the type
+ // is generally unknown and OS driven. 769764 changed that to remove the
+ // internal redirect used to setup the initial proxy/channel as that isn't
+ // a redirect in any sense.
+
+ var chan = make_channel("http://localhost:" +
+ httpServer.identity.primaryPort + "/content");
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_proxy-replace_passing.js b/netwerk/test/unit/test_proxy-replace_passing.js
new file mode 100644
index 000000000..e3447feda
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_passing.js
@@ -0,0 +1,43 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpServer = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var prefserv = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var prefs = prefserv.getBranch("network.proxy.");
+ prefs.setIntPref("type", 2);
+ prefs.setCharPref("autoconfig_url", "data:text/plain," +
+ "function FindProxyForURL(url, host) {return 'PROXY localhost:" +
+ httpServer.identity.primaryPort + "';}"
+ );
+
+ var chan = make_channel("http://localhost:" +
+ httpServer.identity.primaryPort + "/content");
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_psl.js b/netwerk/test/unit/test_psl.js
new file mode 100644
index 000000000..251ffa621
--- /dev/null
+++ b/netwerk/test/unit/test_psl.js
@@ -0,0 +1,36 @@
+var etld = Cc["@mozilla.org/network/effective-tld-service;1"]
+ .getService(Ci.nsIEffectiveTLDService);
+
+var idna = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+
+var Cr = Components.results;
+
+function run_test()
+{
+ var file = do_get_file("data/test_psl.txt");
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ var uri = ios.newFileURI(file);
+ var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader);
+ var srvScope = {};
+ scriptLoader.loadSubScript(uri.spec, srvScope, "utf-8");
+}
+
+function checkPublicSuffix(host, expectedSuffix)
+{
+ var actualSuffix = null;
+ try {
+ actualSuffix = etld.getBaseDomainFromHost(host);
+ } catch (e if e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS ||
+ e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
+ }
+ // The EffectiveTLDService always gives back punycoded labels.
+ // The test suite wants to get back what it put in.
+ if (actualSuffix !== null && expectedSuffix !== null &&
+ /(^|\.)xn--/.test(actualSuffix) && !/(^|\.)xn--/.test(expectedSuffix)) {
+ actualSuffix = idna.convertACEtoUTF8(actualSuffix);
+ }
+ do_check_eq(actualSuffix, expectedSuffix);
+}
diff --git a/netwerk/test/unit/test_range_requests.js b/netwerk/test/unit/test_range_requests.js
new file mode 100644
index 000000000..1850ade43
--- /dev/null
+++ b/netwerk/test/unit/test_range_requests.js
@@ -0,0 +1,434 @@
+//
+// This test makes sure range-requests are sent and treated the way we want
+// See bug #612135 for a thorough discussion on the subject
+//
+// Necko does a range-request for a partial cache-entry iff
+//
+// 1) size of the cached entry < value of the cached Content-Length header
+// (not tested here - see bug #612135 comments 108-110)
+// 2) the size of the cached entry is > 0 (see bug #628607)
+// 3) the cached entry does not have a "no-store" Cache-Control header
+// 4) the cached entry does not have a Content-Encoding (see bug #613159)
+// 5) the request does not have a conditional-request header set by client
+// 6) nsHttpResponseHead::IsResumable() is true for the cached entry
+// 7) a basic positive test that makes sure byte ranges work
+// 8) ensure NS_ERROR_CORRUPTED_CONTENT is thrown when total entity size
+// of 206 does not match content-length of 200
+//
+// The test has one handler for each case and run_tests() fires one request
+// for each. None of the handlers should see a Range-header.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+
+const clearTextBody = "This is a slightly longer test\n";
+const encodedBody = [0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78, 0x74, 0x66, 0x69,
+ 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85,
+ 0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c, 0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85,
+ 0x92, 0xd4, 0xe2, 0x12, 0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00];
+const decodedBody = [0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74,
+ 0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x20, 0x74, 0x65, 0x73, 0x74, 0x0a, 0x0a];
+
+const partial_data_length = 4;
+var port = null; // set in run_test
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+// StreamListener which cancels its request on first data available
+function Canceler(continueFn) {
+ this.continueFn = continueFn;
+}
+Canceler.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function(request, context) { },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ request.QueryInterface(Ci.nsIChannel)
+ .cancel(Components.results.NS_BINDING_ABORTED);
+ },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(status, Components.results.NS_BINDING_ABORTED);
+ this.continueFn(request, null);
+ }
+};
+// Simple StreamListener which performs no validations
+function MyListener(continueFn) {
+ this.continueFn = continueFn;
+ this._buffer = null;
+}
+MyListener.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function(request, context) { this._buffer = ""; },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+ onStopRequest: function(request, context, status) {
+ this.continueFn(request, this._buffer);
+ }
+};
+
+var case_8_range_request = false;
+function FailedChannelListener(continueFn) {
+ this.continueFn = continueFn;
+}
+FailedChannelListener.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function(request, context) { },
+
+ onDataAvailable: function(request, context, stream, offset, count) { },
+
+ onStopRequest: function(request, context, status) {
+ if (case_8_range_request)
+ do_check_eq(status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+ this.continueFn(request, null);
+ }
+};
+
+function received_cleartext(request, data) {
+ do_check_eq(clearTextBody, data);
+ testFinished();
+}
+
+function setStdHeaders(response, length) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age: 360000");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Content-Length", "" + length);
+}
+
+function handler_2(metadata, response) {
+ setStdHeaders(response, clearTextBody.length);
+ do_check_false(metadata.hasHeader("Range"));
+ response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
+}
+function received_partial_2(request, data) {
+ do_check_eq(data, undefined);
+ var chan = make_channel("http://localhost:" + port + "/test_2");
+ chan.asyncOpen2(new ChannelListener(received_cleartext, null));
+}
+
+var case_3_request_no = 0;
+function handler_3(metadata, response) {
+ var body = clearTextBody;
+ setStdHeaders(response, body.length);
+ response.setHeader("Cache-Control", "no-store", false);
+ switch (case_3_request_no) {
+ case 0:
+ do_check_false(metadata.hasHeader("Range"));
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ do_check_false(metadata.hasHeader("Range"));
+ response.bodyOutputStream.write(body, body.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_3_request_no++;
+}
+function received_partial_3(request, data) {
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_3");
+ chan.asyncOpen2(new ChannelListener(received_cleartext, null));
+}
+
+var case_4_request_no = 0;
+function handler_4(metadata, response) {
+ switch (case_4_request_no) {
+ case 0:
+ do_check_false(metadata.hasHeader("Range"));
+ var body = encodedBody;
+ setStdHeaders(response, body.length);
+ response.setHeader("Content-Encoding", "gzip", false);
+ body = body.slice(0, partial_data_length);
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ response.processAsync();
+ bos.writeByteArray(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ do_check_false(metadata.hasHeader("Range"));
+ setStdHeaders(response, clearTextBody.length);
+ response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_4_request_no++;
+}
+function received_partial_4(request, data) {
+// checking length does not work with encoded data
+// do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_4");
+ chan.asyncOpen2(new MyListener(received_cleartext));
+}
+
+var case_5_request_no = 0;
+function handler_5(metadata, response) {
+ var body = clearTextBody;
+ setStdHeaders(response, body.length);
+ switch (case_5_request_no) {
+ case 0:
+ do_check_false(metadata.hasHeader("Range"));
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ do_check_false(metadata.hasHeader("Range"));
+ response.bodyOutputStream.write(body, body.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_5_request_no++;
+}
+function received_partial_5(request, data) {
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_5");
+ chan.setRequestHeader("If-Match", "Some eTag", false);
+ chan.asyncOpen2(new ChannelListener(received_cleartext, null));
+}
+
+var case_6_request_no = 0;
+function handler_6(metadata, response) {
+ switch (case_6_request_no) {
+ case 0:
+ do_check_false(metadata.hasHeader("Range"));
+ var body = clearTextBody;
+ setStdHeaders(response, body.length);
+ response.setHeader("Accept-Ranges", "", false);
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ do_check_false(metadata.hasHeader("Range"));
+ setStdHeaders(response, clearTextBody.length);
+ response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_6_request_no++;
+}
+function received_partial_6(request, data) {
+// would like to verify that the response does not have Accept-Ranges
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_6");
+ chan.asyncOpen2(new ChannelListener(received_cleartext, null));
+}
+
+const simpleBody = "0123456789";
+
+function received_simple(request, data) {
+ do_check_eq(simpleBody, data);
+ testFinished();
+}
+
+var case_7_request_no = 0;
+function handler_7(metadata, response) {
+ switch (case_7_request_no) {
+ case 0:
+ do_check_false(metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test7Etag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
+ response.finish();
+ break;
+ case 1:
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test7Etag");
+ if (metadata.hasHeader("Range")) {
+ do_check_true(metadata.hasHeader("If-Range"));
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "4-9/10");
+ response.setHeader("Content-Length", "6");
+ response.bodyOutputStream.write(simpleBody.slice(4), 6);
+ } else {
+ response.setHeader("Content-Length", "10");
+ response.bodyOutputStream.write(simpleBody, 10);
+ }
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_7_request_no++;
+}
+function received_partial_7(request, data) {
+ // make sure we get the first 4 bytes
+ do_check_eq(4, data.length);
+ // do it again to get the rest
+ var chan = make_channel("http://localhost:" + port + "/test_7");
+ chan.asyncOpen2(new ChannelListener(received_simple, null));
+}
+
+var case_8_request_no = 0;
+function handler_8(metadata, response) {
+ switch (case_8_request_no) {
+ case 0:
+ do_check_false(metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test8Etag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
+ response.finish();
+ break;
+ case 1:
+ if (metadata.hasHeader("Range")) {
+ do_check_true(metadata.hasHeader("If-Range"));
+ case_8_range_request = true;
+ }
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "test8Etag");
+ response.setHeader("Content-Range", "4-8/9"); // intentionally broken
+ response.setHeader("Content-Length", "5");
+ response.bodyOutputStream.write(simpleBody.slice(4), 5);
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_8_request_no++;
+}
+function received_partial_8(request, data) {
+ // make sure we get the first 4 bytes
+ do_check_eq(4, data.length);
+ // do it again to get the rest
+ var chan = make_channel("http://localhost:" + port + "/test_8");
+ chan.asyncOpen2(new FailedChannelListener(testFinished, null, CL_EXPECT_LATE_FAILURE));
+}
+
+var case_9_request_no = 0;
+function handler_9(metadata, response) {
+ switch (case_9_request_no) {
+ case 0:
+ do_check_false(metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "W/test9WeakEtag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
+ response.finish(); // truncated response
+ break;
+ case 1:
+ do_check_false(metadata.hasHeader("Range"));
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "W/test9WeakEtag");
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Cache-Control", "max-age=360000");
+ response.setHeader("Content-Length", "10");
+ response.processAsync();
+ response.bodyOutputStream.write(simpleBody, 10);
+ response.finish(); // full response
+ break;
+ default:
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ }
+ case_9_request_no++;
+}
+function received_partial_9(request, data) {
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_9");
+ chan.asyncOpen2(new ChannelListener(received_simple, null));
+}
+
+// Simple mechanism to keep track of tests and stop the server
+var numTestsFinished = 0;
+function testFinished() {
+ if (++numTestsFinished == 7)
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/test_2", handler_2);
+ httpserver.registerPathHandler("/test_3", handler_3);
+ httpserver.registerPathHandler("/test_4", handler_4);
+ httpserver.registerPathHandler("/test_5", handler_5);
+ httpserver.registerPathHandler("/test_6", handler_6);
+ httpserver.registerPathHandler("/test_7", handler_7);
+ httpserver.registerPathHandler("/test_8", handler_8);
+ httpserver.registerPathHandler("/test_9", handler_9);
+ httpserver.start(-1);
+
+ port = httpserver.identity.primaryPort;
+
+ // wipe out cached content
+ evict_cache_entries();
+
+ // Case 2: zero-length partial entry must not trigger range-request
+ var chan = make_channel("http://localhost:" + port + "/test_2");
+ chan.asyncOpen2(new Canceler(received_partial_2));
+
+ // Case 3: no-store response must not trigger range-request
+ var chan = make_channel("http://localhost:" + port + "/test_3");
+ chan.asyncOpen2(new MyListener(received_partial_3));
+
+ // Case 4: response with content-encoding must not trigger range-request
+ var chan = make_channel("http://localhost:" + port + "/test_4");
+ chan.asyncOpen2(new MyListener(received_partial_4));
+
+ // Case 5: conditional request-header set by client
+ var chan = make_channel("http://localhost:" + port + "/test_5");
+ chan.asyncOpen2(new MyListener(received_partial_5));
+
+ // Case 6: response is not resumable (drop the Accept-Ranges header)
+ var chan = make_channel("http://localhost:" + port + "/test_6");
+ chan.asyncOpen2(new MyListener(received_partial_6));
+
+ // Case 7: a basic positive test
+ var chan = make_channel("http://localhost:" + port + "/test_7");
+ chan.asyncOpen2(new MyListener(received_partial_7));
+
+ // Case 8: check that mismatched 206 and 200 sizes throw error
+ var chan = make_channel("http://localhost:" + port + "/test_8");
+ chan.asyncOpen2(new MyListener(received_partial_8));
+
+ // Case 9: check that weak etag is not used for a range request
+ var chan = make_channel("http://localhost:" + port + "/test_9");
+ chan.asyncOpen2(new MyListener(received_partial_9));
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_readline.js b/netwerk/test/unit/test_readline.js
new file mode 100644
index 000000000..798b2c2a7
--- /dev/null
+++ b/netwerk/test/unit/test_readline.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const PR_RDONLY = 0x1;
+
+function new_file_input_stream(file) {
+ var stream =
+ Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ stream.init(file, PR_RDONLY, 0, 0);
+ return stream;
+}
+
+function new_line_input_stream(filename) {
+ return new_file_input_stream(do_get_file(filename))
+ .QueryInterface(Ci.nsILineInputStream);
+}
+
+var test_array = [
+ { file:"data/test_readline1.txt", lines:[] },
+ { file:"data/test_readline2.txt", lines:[""] },
+ { file:"data/test_readline3.txt", lines:["","","","",""] },
+ { file:"data/test_readline4.txt", lines:["1","23","456","","78901"] },
+ { file:"data/test_readline5.txt", lines:["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE"] },
+ { file:"data/test_readline6.txt", lines:["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE"] },
+ { file:"data/test_readline7.txt", lines:["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE",""] },
+ { file:"data/test_readline8.txt", lines:["zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE"] },
+];
+
+function err(file, lineNo, msg) {
+ do_throw("\""+file+"\" line "+lineNo+", "+msg);
+}
+
+function run_test()
+{
+ for (var test of test_array) {
+ var lineStream = new_line_input_stream(test.file);
+ var lineNo = 0;
+ var more = false;
+ var line = {};
+ more = lineStream.readLine(line);
+ for (var check of test.lines) {
+ ++lineNo;
+ if (lineNo == test.lines.length) {
+ if (more) err(test.file, lineNo, "There should be no more data after the last line");
+ }
+ else {
+ if (!more) err(test.file, lineNo, "There should be more data after this line");
+ }
+ if (line.value != check)
+ err(test.file, lineNo, "Wrong value, got '"+line.value+"' expected '"+check+"'");
+ dump("ok \""+test.file+"\" line "+lineNo+" (length "+line.value.length+"): '"+line.value+"'\n");
+ more = lineStream.readLine(line);
+ }
+ if (more) err(test.file, lineNo, "'more' should be false after reading all lines");
+ dump("ok \""+test.file+"\" succeeded\n");
+ lineStream.close();
+ }
+}
diff --git a/netwerk/test/unit/test_redirect-caching_canceled.js b/netwerk/test/unit/test_redirect-caching_canceled.js
new file mode 100644
index 000000000..237107865
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_canceled.js
@@ -0,0 +1,68 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+ response.setHeader("Cache-control", "max-age=1000", false);
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(secondTimeThrough, null));
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect-caching_failure.js b/netwerk/test/unit/test_redirect-caching_failure.js
new file mode 100644
index 000000000..0e88c0d09
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_failure.js
@@ -0,0 +1,55 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "httpx://localhost:" +
+ httpServer.identity.primaryPort + "/content", false);
+ response.setHeader("Cache-control", "max-age=1000", false);
+}
+
+function makeSureNotInCache(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNKNOWN_PROTOCOL);
+
+ // It's very unlikely that we'd somehow succeed when we try again from cache.
+ // Can't hurt to test though.
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_ONLY_FROM_CACHE;
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNKNOWN_PROTOCOL);
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(makeSureNotInCache, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect-caching_passing.js b/netwerk/test/unit/test_redirect-caching_passing.js
new file mode 100644
index 000000000..e1d3ebe8f
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_passing.js
@@ -0,0 +1,59 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(randomPath, redirectHandler);
+ httpserver.registerPathHandler("/content", contentHandler);
+ httpserver.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_baduri.js b/netwerk/test/unit/test_redirect_baduri.js
new file mode 100644
index 000000000..aa1ce7dc4
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_baduri.js
@@ -0,0 +1,42 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+/*
+ * Test whether we fail bad URIs in HTTP redirect as CORRUPTED_CONTENT.
+ */
+
+var httpServer = null;
+
+var BadRedirectPath = "/BadRedirect";
+XPCOMUtils.defineLazyGetter(this, "BadRedirectURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + BadRedirectPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function BadRedirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ // '>' in URI will fail to parse: we should not render response
+ response.setHeader("Location", 'http://localhost:4444>BadRedirect', false);
+}
+
+function checkFailed(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(BadRedirectPath, BadRedirectHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(BadRedirectURI);
+ chan.asyncOpen2(new ChannelListener(checkFailed, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_canceled.js b/netwerk/test/unit/test_redirect_canceled.js
new file mode 100644
index 000000000..cd2a948ee
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_canceled.js
@@ -0,0 +1,51 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_different-protocol.js b/netwerk/test/unit/test_redirect_different-protocol.js
new file mode 100644
index 000000000..73aea57ca
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_different-protocol.js
@@ -0,0 +1,51 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const redirectTargetBody = "response body";
+const response301Body = "redirect body";
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.bodyOutputStream.write(response301Body, response301Body.length);
+ response.setHeader("Location", "data:text/plain," + redirectTargetBody, false);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, redirectTargetBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(finish_test, null, 0));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_failure.js b/netwerk/test/unit/test_redirect_failure.js
new file mode 100644
index 000000000..b74a9102f
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_failure.js
@@ -0,0 +1,45 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "httpx://localhost:" +
+ httpServer.identity.primaryPort + "/content", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNKNOWN_PROTOCOL);
+
+ do_check_eq(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_from_script.js b/netwerk/test/unit/test_redirect_from_script.js
new file mode 100644
index 000000000..d296ab0d0
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script.js
@@ -0,0 +1,258 @@
+/*
+ * Test whether the rewrite-requests-from-script API implemented here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=765934 is functioning
+ * correctly
+ *
+ * The test has the following components:
+ *
+ * testViaXHR() checks that internal redirects occur correctly for requests
+ * made with nsIXMLHttpRequest objects.
+ *
+ * testViaAsyncOpen() checks that internal redirects occur correctly when made
+ * with nsIHTTPChannel.asyncOpen2().
+ *
+ * Both of the above functions tests four requests:
+ *
+ * Test 1: a simple case that redirects within a server;
+ * Test 2: a second that redirects to a second webserver;
+ * Test 3: internal script redirects in response to a server-side 302 redirect;
+ * Test 4: one internal script redirects in response to another's redirect.
+ *
+ * The successful redirects are confirmed by the presence of a custom response
+ * header.
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// the topic we observe to use the API. http-on-opening-request might also
+// work for some purposes.
+redirectHook = "http-on-modify-request";
+
+var httpServer = null, httpServer2 = null;
+
+XPCOMUtils.defineLazyGetter(this, "port1", function() {
+ return httpServer.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "port2", function() {
+ return httpServer2.identity.primaryPort;
+});
+
+// Test Part 1: a cross-path redirect on a single HTTP server
+// http://localhost:port1/bait -> http://localhost:port1/switch
+var baitPath = "/bait";
+XPCOMUtils.defineLazyGetter(this, "baitURI", function() {
+ return "http://localhost:" + port1 + baitPath;
+});
+var baitText = "you got the worm";
+
+var redirectedPath = "/switch";
+XPCOMUtils.defineLazyGetter(this, "redirectedURI", function() {
+ return "http://localhost:" + port1 + redirectedPath;
+});
+var redirectedText = "worms are not tasty";
+
+// Test Part 2: Now, a redirect to a different server
+// http://localhost:port1/bait2 -> http://localhost:port2/switch
+var bait2Path = "/bait2";
+XPCOMUtils.defineLazyGetter(this, "bait2URI", function() {
+ return "http://localhost:" + port1 + bait2Path;
+});
+
+XPCOMUtils.defineLazyGetter(this, "redirected2URI", function() {
+ return "http://localhost:" + port2 + redirectedPath;
+});
+
+// Test Part 3, begin with a serverside redirect that itself turns into an instance
+// of Test Part 1
+var bait3Path = "/bait3";
+XPCOMUtils.defineLazyGetter(this, "bait3URI", function() {
+ return "http://localhost:" + port1 + bait3Path;
+});
+
+// Test Part 4, begin with this client-side redirect and which then redirects
+// to an instance of Test Part 1
+var bait4Path = "/bait4";
+XPCOMUtils.defineLazyGetter(this, "bait4URI", function() {
+ return "http://localhost:" + port1 + bait4Path;
+});
+
+var testHeaderName = "X-Redirected-By-Script"
+var testHeaderVal = "Success";
+var testHeaderVal2 = "Success on server 2";
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function baitHandler(metadata, response)
+{
+ // Content-Type required: https://bugzilla.mozilla.org/show_bug.cgi?id=748117
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(baitText, baitText.length);
+}
+
+function redirectedHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal);
+}
+
+function redirected2Handler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal2);
+}
+
+function bait3Handler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", baitURI);
+}
+
+function Redirector()
+{
+ this.register();
+}
+
+Redirector.prototype = {
+ // This class observes an event and uses that to
+ // trigger a redirectTo(uri) redirect using the new API
+ register: function()
+ {
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService).
+ addObserver(this, redirectHook, true);
+ },
+
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ observe: function(subject, topic, data)
+ {
+ if (topic == redirectHook) {
+ if (!(subject instanceof Ci.nsIHttpChannel))
+ do_throw(redirectHook + " observed a non-HTTP channel");
+ var channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ var ioservice = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var target = null;
+ if (channel.URI.spec == baitURI) target = redirectedURI;
+ if (channel.URI.spec == bait2URI) target = redirected2URI;
+ if (channel.URI.spec == bait4URI) target = baitURI;
+ // if we have a target, redirect there
+ if (target) {
+ var tURI = ioservice.newURI(target, null, null);
+ try {
+ channel.redirectTo(tURI);
+ } catch (e) {
+ do_throw("Exception in redirectTo " + e + "\n");
+ }
+ }
+ }
+ }
+}
+
+function makeAsyncTest(uri, headerValue, nextTask)
+{
+ // Make a test to check a redirect that is created with channel.asyncOpen2()
+
+ // Produce a callback function which checks for the presence of headerValue,
+ // and then continues to the next async test task
+ var verifier = function(req, buffer)
+ {
+ if (!(req instanceof Ci.nsIHttpChannel))
+ do_throw(req + " is not an nsIHttpChannel, catastrophe imminent!");
+
+ var httpChannel = req.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(httpChannel.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(buffer, redirectedText);
+ nextTask();
+ };
+
+ // Produce a function to run an asyncOpen2 test using the above verifier
+ var test = function()
+ {
+ var chan = make_channel(uri);
+ chan.asyncOpen2(new ChannelListener(verifier));
+ };
+ return test;
+}
+
+// will be defined in run_test because of the lazy getters,
+// since the server's port is defined dynamically
+var testViaAsyncOpen4 = null;
+var testViaAsyncOpen3 = null;
+var testViaAsyncOpen2 = null;
+var testViaAsyncOpen = null;
+
+function testViaXHR()
+{
+ runXHRTest(baitURI, testHeaderVal);
+ runXHRTest(bait2URI, testHeaderVal2);
+ runXHRTest(bait3URI, testHeaderVal);
+ runXHRTest(bait4URI, testHeaderVal);
+}
+
+function runXHRTest(uri, headerValue)
+{
+ // Check that making an XHR request for uri winds up redirecting to a result with the
+ // appropriate headerValue
+ var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
+
+ var req = xhr.createInstance(Ci.nsIXMLHttpRequest);
+ req.open("GET", uri, false);
+ req.send();
+ do_check_eq(req.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(req.response, redirectedText);
+}
+
+function done()
+{
+ httpServer.stop(
+ function ()
+ {
+ httpServer2.stop(do_test_finished);
+ }
+ );
+}
+
+var redirector = new Redirector();
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(baitPath, baitHandler);
+ httpServer.registerPathHandler(bait2Path, baitHandler);
+ httpServer.registerPathHandler(bait3Path, bait3Handler);
+ httpServer.registerPathHandler(bait4Path, baitHandler);
+ httpServer.registerPathHandler(redirectedPath, redirectedHandler);
+ httpServer.start(-1);
+ httpServer2 = new HttpServer();
+ httpServer2.registerPathHandler(redirectedPath, redirected2Handler);
+ httpServer2.start(-1);
+
+ // The tests depend on each other, and therefore need to be defined in the
+ // reverse of the order they are called in. It is therefore best to read this
+ // stanza backwards!
+ testViaAsyncOpen4 = makeAsyncTest(bait4URI, testHeaderVal, done);
+ testViaAsyncOpen3 = makeAsyncTest(bait3URI, testHeaderVal, testViaAsyncOpen4);
+ testViaAsyncOpen2 = makeAsyncTest(bait2URI, testHeaderVal2, testViaAsyncOpen3);
+ testViaAsyncOpen = makeAsyncTest(baitURI, testHeaderVal, testViaAsyncOpen2);
+
+ testViaXHR();
+ testViaAsyncOpen(); // will call done() asynchronously for cleanup
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_from_script_after-open_passing.js b/netwerk/test/unit/test_redirect_from_script_after-open_passing.js
new file mode 100644
index 000000000..195508490
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script_after-open_passing.js
@@ -0,0 +1,258 @@
+/*
+ * Test whether the rewrite-requests-from-script API implemented here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=765934 is functioning
+ * correctly
+ *
+ * The test has the following components:
+ *
+ * testViaXHR() checks that internal redirects occur correctly for requests
+ * made with nsIXMLHttpRequest objects.
+ *
+ * testViaAsyncOpen() checks that internal redirects occur correctly when made
+ * with nsIHTTPChannel.asyncOpen2().
+ *
+ * Both of the above functions tests four requests:
+ *
+ * Test 1: a simple case that redirects within a server;
+ * Test 2: a second that redirects to a second webserver;
+ * Test 3: internal script redirects in response to a server-side 302 redirect;
+ * Test 4: one internal script redirects in response to another's redirect.
+ *
+ * The successful redirects are confirmed by the presence of a custom response
+ * header.
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// the topic we observe to use the API. http-on-opening-request might also
+// work for some purposes.
+redirectHook = "http-on-examine-response";
+
+var httpServer = null, httpServer2 = null;
+
+XPCOMUtils.defineLazyGetter(this, "port1", function() {
+ return httpServer.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "port2", function() {
+ return httpServer2.identity.primaryPort;
+});
+
+// Test Part 1: a cross-path redirect on a single HTTP server
+// http://localhost:port1/bait -> http://localhost:port1/switch
+var baitPath = "/bait";
+XPCOMUtils.defineLazyGetter(this, "baitURI", function() {
+ return "http://localhost:" + port1 + baitPath;
+});
+var baitText = "you got the worm";
+
+var redirectedPath = "/switch";
+XPCOMUtils.defineLazyGetter(this, "redirectedURI", function() {
+ return "http://localhost:" + port1 + redirectedPath;
+});
+var redirectedText = "worms are not tasty";
+
+// Test Part 2: Now, a redirect to a different server
+// http://localhost:port1/bait2 -> http://localhost:port2/switch
+var bait2Path = "/bait2";
+XPCOMUtils.defineLazyGetter(this, "bait2URI", function() {
+ return "http://localhost:" + port1 + bait2Path;
+});
+
+XPCOMUtils.defineLazyGetter(this, "redirected2URI", function() {
+ return "http://localhost:" + port2 + redirectedPath;
+});
+
+// Test Part 3, begin with a serverside redirect that itself turns into an instance
+// of Test Part 1
+var bait3Path = "/bait3";
+XPCOMUtils.defineLazyGetter(this, "bait3URI", function() {
+ return "http://localhost:" + port1 + bait3Path;
+});
+
+// Test Part 4, begin with this client-side redirect and which then redirects
+// to an instance of Test Part 1
+var bait4Path = "/bait4";
+XPCOMUtils.defineLazyGetter(this, "bait4URI", function() {
+ return "http://localhost:" + port1 + bait4Path;
+});
+
+var testHeaderName = "X-Redirected-By-Script"
+var testHeaderVal = "Success";
+var testHeaderVal2 = "Success on server 2";
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function baitHandler(metadata, response)
+{
+ // Content-Type required: https://bugzilla.mozilla.org/show_bug.cgi?id=748117
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(baitText, baitText.length);
+}
+
+function redirectedHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal);
+}
+
+function redirected2Handler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+ response.bodyOutputStream.write(redirectedText, redirectedText.length);
+ response.setHeader(testHeaderName, testHeaderVal2);
+}
+
+function bait3Handler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Location", baitURI);
+}
+
+function Redirector()
+{
+ this.register();
+}
+
+Redirector.prototype = {
+ // This class observes an event and uses that to
+ // trigger a redirectTo(uri) redirect using the new API
+ register: function()
+ {
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService).
+ addObserver(this, redirectHook, true);
+ },
+
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ observe: function(subject, topic, data)
+ {
+ if (topic == redirectHook) {
+ if (!(subject instanceof Ci.nsIHttpChannel))
+ do_throw(redirectHook + " observed a non-HTTP channel");
+ var channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ var ioservice = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var target = null;
+ if (channel.URI.spec == baitURI) target = redirectedURI;
+ if (channel.URI.spec == bait2URI) target = redirected2URI;
+ if (channel.URI.spec == bait4URI) target = baitURI;
+ // if we have a target, redirect there
+ if (target) {
+ var tURI = ioservice.newURI(target, null, null);
+ try {
+ channel.redirectTo(tURI);
+ } catch (e) {
+ do_throw("Exception in redirectTo " + e + "\n");
+ }
+ }
+ }
+ }
+}
+
+function makeAsyncTest(uri, headerValue, nextTask)
+{
+ // Make a test to check a redirect that is created with channel.asyncOpen2()
+
+ // Produce a callback function which checks for the presence of headerValue,
+ // and then continues to the next async test task
+ var verifier = function(req, buffer)
+ {
+ if (!(req instanceof Ci.nsIHttpChannel))
+ do_throw(req + " is not an nsIHttpChannel, catastrophe imminent!");
+
+ var httpChannel = req.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(httpChannel.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(buffer, redirectedText);
+ nextTask();
+ };
+
+ // Produce a function to run an asyncOpen2 test using the above verifier
+ var test = function()
+ {
+ var chan = make_channel(uri);
+ chan.asyncOpen2(new ChannelListener(verifier));
+ };
+ return test;
+}
+
+// will be defined in run_test because of the lazy getters,
+// since the server's port is defined dynamically
+var testViaAsyncOpen4 = null;
+var testViaAsyncOpen3 = null;
+var testViaAsyncOpen2 = null;
+var testViaAsyncOpen = null;
+
+function testViaXHR()
+{
+ runXHRTest(baitURI, testHeaderVal);
+ runXHRTest(bait2URI, testHeaderVal2);
+ runXHRTest(bait3URI, testHeaderVal);
+ runXHRTest(bait4URI, testHeaderVal);
+}
+
+function runXHRTest(uri, headerValue)
+{
+ // Check that making an XHR request for uri winds up redirecting to a result with the
+ // appropriate headerValue
+ var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
+
+ var req = xhr.createInstance(Ci.nsIXMLHttpRequest);
+ req.open("GET", uri, false);
+ req.send();
+ do_check_eq(req.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(req.response, redirectedText);
+}
+
+function done()
+{
+ httpServer.stop(
+ function ()
+ {
+ httpServer2.stop(do_test_finished);
+ }
+ );
+}
+
+var redirector = new Redirector();
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(baitPath, baitHandler);
+ httpServer.registerPathHandler(bait2Path, baitHandler);
+ httpServer.registerPathHandler(bait3Path, bait3Handler);
+ httpServer.registerPathHandler(bait4Path, baitHandler);
+ httpServer.registerPathHandler(redirectedPath, redirectedHandler);
+ httpServer.start(-1);
+ httpServer2 = new HttpServer();
+ httpServer2.registerPathHandler(redirectedPath, redirected2Handler);
+ httpServer2.start(-1);
+
+ // The tests depend on each other, and therefore need to be defined in the
+ // reverse of the order they are called in. It is therefore best to read this
+ // stanza backwards!
+ testViaAsyncOpen4 = makeAsyncTest(bait4URI, testHeaderVal, done);
+ testViaAsyncOpen3 = makeAsyncTest(bait3URI, testHeaderVal, testViaAsyncOpen4);
+ testViaAsyncOpen2 = makeAsyncTest(bait2URI, testHeaderVal2, testViaAsyncOpen3);
+ testViaAsyncOpen = makeAsyncTest(baitURI, testHeaderVal, testViaAsyncOpen2);
+
+ testViaXHR();
+ testViaAsyncOpen(); // will call done() asynchronously for cleanup
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_history.js b/netwerk/test/unit/test_redirect_history.js
new file mode 100644
index 000000000..07da06478
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_history.js
@@ -0,0 +1,64 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+var randomPath = "/redirect/" + Math.random();
+var redirects = [];
+const numRedirects = 10;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function contentHandler(request, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ let chan = request.QueryInterface(Ci.nsIChannel);
+ let redirectChain = chan.loadInfo.redirectChain;
+
+ do_check_eq(numRedirects - 1, redirectChain.length);
+ for (let i = 0; i < numRedirects - 1; ++i) {
+ let principal = redirectChain[i];
+ do_check_eq(URL + redirects[i], principal.URI.spec);
+ }
+ httpServer.stop(do_test_finished);
+}
+
+function redirectHandler(index, request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved");
+ let path = redirects[index + 1];
+ response.setHeader("Location", URL + path, false);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ for (let i = 0; i < numRedirects; ++i) {
+ var randomPath = "/redirect/" + Math.random();
+ redirects.push(randomPath);
+ if (i < numRedirects - 1) {
+ httpServer.registerPathHandler(randomPath, redirectHandler.bind(this, i));
+ } else {
+ // The last one doesn't redirect
+ httpServer.registerPathHandler(redirects[numRedirects - 1],
+ contentHandler);
+ }
+ }
+ httpServer.start(-1);
+
+ var chan = make_channel(URL + redirects[0]);
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_loop.js b/netwerk/test/unit/test_redirect_loop.js
new file mode 100644
index 000000000..9efcecadb
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_loop.js
@@ -0,0 +1,86 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+/*
+ * This xpcshell test checks whether we detect infinite HTTP redirect loops.
+ * We check loops with "Location:" set to 1) full URI, 2) relative URI, and 3)
+ * empty Location header (which resolves to a relative link to the original
+ * URI when the original URI ends in a slash).
+ */
+
+var httpServer = new HttpServer();
+httpServer.start(-1);
+const PORT = httpServer.identity.primaryPort;
+
+var fullLoopPath = "/fullLoop";
+var fullLoopURI = "http://localhost:" + PORT + fullLoopPath;
+
+var relativeLoopPath = "/relativeLoop";
+var relativeLoopURI = "http://localhost:" + PORT + relativeLoopPath;
+
+// must use directory-style URI, so empty Location redirects back to self
+var emptyLoopPath = "/empty/";
+var emptyLoopURI = "http://localhost:" + PORT + emptyLoopPath;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function fullLoopHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://localhost:" + PORT + "/fullLoop", false);
+}
+
+function relativeLoopHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "relativeLoop", false);
+}
+
+function emptyLoopHandler(metadata, response)
+{
+ // Comrades! We must seize power from the petty-bourgeois running dogs of
+ // httpd.js in order to reply with a blank Location header!
+ response.seizePower();
+ response.write("HTTP/1.0 301 Moved\r\n");
+ response.write("Location: \r\n");
+ response.write("Content-Length: 4\r\n");
+ response.write("\r\n");
+ response.write("oops");
+ response.finish();
+}
+
+function testFullLoop(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_REDIRECT_LOOP);
+
+ var chan = make_channel(relativeLoopURI);
+ chan.asyncOpen2(new ChannelListener(testRelativeLoop, null, CL_EXPECT_FAILURE));
+}
+
+function testRelativeLoop(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_REDIRECT_LOOP);
+
+ var chan = make_channel(emptyLoopURI);
+ chan.asyncOpen2(new ChannelListener(testEmptyLoop, null, CL_EXPECT_FAILURE));
+}
+
+function testEmptyLoop(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_REDIRECT_LOOP);
+
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer.registerPathHandler(fullLoopPath, fullLoopHandler);
+ httpServer.registerPathHandler(relativeLoopPath, relativeLoopHandler);
+ httpServer.registerPathHandler(emptyLoopPath, emptyLoopHandler);
+
+ var chan = make_channel(fullLoopURI);
+ chan.asyncOpen2(new ChannelListener(testFullLoop, null, CL_EXPECT_FAILURE));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_redirect_passing.js b/netwerk/test/unit/test_redirect_passing.js
new file mode 100644
index 000000000..a9a515b5e
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_passing.js
@@ -0,0 +1,57 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function redirectHandler(metadata, response)
+{
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", URL + "/content", false);
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_reentrancy.js b/netwerk/test/unit/test_reentrancy.js
new file mode 100644
index 000000000..2f8eec30f
--- /dev/null
+++ b/netwerk/test/unit/test_reentrancy.js
@@ -0,0 +1,105 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "<?xml version='1.0' ?><root>0123456789</root>";
+
+function syncXHR()
+{
+ var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open("GET", URL + testpath, false);
+ xhr.send(null);
+}
+
+const MAX_TESTS = 2;
+
+var listener = {
+ _done_onStart: false,
+ _done_onData: false,
+ _test: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, ctx) {
+ switch(this._test) {
+ case 0:
+ request.suspend();
+ syncXHR();
+ request.resume();
+ break;
+ case 1:
+ request.suspend();
+ syncXHR();
+ do_execute_soon(function() { request.resume(); });
+ break;
+ case 2:
+ do_execute_soon(function() { request.suspend(); });
+ do_execute_soon(function() { request.resume(); });
+ syncXHR();
+ break;
+ }
+
+ this._done_onStart = true;
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_check_true(this._done_onStart);
+ read_stream(stream, count);
+ this._done_onData = true;
+ },
+
+ onStopRequest: function(request, ctx, status) {
+ do_check_true(this._done_onData);
+ this._reset();
+ if (this._test <= MAX_TESTS)
+ next_test();
+ else
+ httpserver.stop(do_test_finished);
+ },
+
+ _reset: function() {
+ this._done_onStart = false;
+ this._done_onData = false;
+ this._test++;
+ }
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function next_test()
+{
+ var chan = makeChan(URL + testpath);
+ chan.QueryInterface(Ci.nsIRequest);
+ chan.asyncOpen2(listener);
+}
+
+function run_test()
+{
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ next_test();
+
+ do_test_pending();
+}
+
+function serverHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/xml", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
diff --git a/netwerk/test/unit/test_referrer.js b/netwerk/test/unit/test_referrer.js
new file mode 100644
index 000000000..5b9fc1c28
--- /dev/null
+++ b/netwerk/test/unit/test_referrer.js
@@ -0,0 +1,109 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function getTestReferrer(server_uri, referer_uri) {
+ var uri = NetUtil.newURI(server_uri, "", null)
+ let referrer = NetUtil.newURI(referer_uri, null, null);
+ let triggeringPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(referrer, {});
+ var chan = NetUtil.newChannel({
+ uri: uri,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ triggeringPrincipal: triggeringPrincipal,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ });
+
+ chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.referrer = referrer;
+ var header = null;
+ try {
+ header = chan.getRequestHeader("Referer");
+ }
+ catch (NS_ERROR_NOT_AVAILABLE) {}
+ return header;
+}
+
+function run_test() {
+ var prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ var server_uri = "http://bar.examplesite.com/path2";
+ var server_uri_2 = "http://bar.example.com/anotherpath";
+ var referer_uri = "http://foo.example.com/path";
+ var referer_uri_2 = "http://bar.examplesite.com/path3?q=blah";
+ var referer_uri_2_anchor = "http://bar.examplesite.com/path3?q=blah#anchor";
+ var referer_uri_idn = "http://sub1.\xe4lt.example/path";
+
+ // for https tests
+ var server_uri_https = "https://bar.example.com/anotherpath";
+ var referer_uri_https = "https://bar.example.com/path3?q=blah";
+ var referer_uri_2_https = "https://bar.examplesite.com/path3?q=blah";
+
+ // tests for sendRefererHeader
+ prefs.setIntPref("network.http.sendRefererHeader", 0);
+ do_check_null(getTestReferrer(server_uri, referer_uri));
+ prefs.setIntPref("network.http.sendRefererHeader", 2);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // test that https ref is not sent to http
+ do_check_null(getTestReferrer(server_uri_2, referer_uri_https));
+
+ // tests for referer.spoofSource
+ prefs.setBoolPref("network.http.referer.spoofSource", true);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), server_uri);
+ prefs.setBoolPref("network.http.referer.spoofSource", false);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // tests for referer.XOriginPolicy
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 2);
+ do_check_null(getTestReferrer(server_uri_2, referer_uri));
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), referer_uri_2);
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri_2, referer_uri), referer_uri);
+ do_check_null(getTestReferrer(server_uri, referer_uri));
+ // https test
+ do_check_eq(getTestReferrer(server_uri_https, referer_uri_https), referer_uri_https);
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 0);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // tests for referer.trimmingPolicy
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/path");
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 2);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/");
+ // https test
+ do_check_eq(getTestReferrer(server_uri_https, referer_uri_https), "https://bar.example.com/");
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
+ // test that anchor is lopped off in ordinary case
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2_anchor), referer_uri_2);
+
+ // tests for referer.XOriginTrimmingPolicy
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), "http://foo.example.com/path");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/path");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3?q=blah");
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 2);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), "http://foo.example.com/");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3?q=blah");
+ // https tests
+ do_check_eq(getTestReferrer(server_uri_https, referer_uri_https), "https://bar.example.com/path3?q=blah");
+ do_check_eq(getTestReferrer(server_uri_https, referer_uri_2_https), "https://bar.examplesite.com/");
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 0);
+ // test that anchor is lopped off in ordinary case
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2_anchor), referer_uri_2);
+
+ // combination test: send spoofed path-only when hosts match
+ var combo_referer_uri = "http://blah.foo.com/path?q=hot";
+ var dest_uri = "http://blah.foo.com:9999/spoofedpath?q=bad";
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ prefs.setBoolPref("network.http.referer.spoofSource", true);
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 2);
+ do_check_eq(getTestReferrer(dest_uri, combo_referer_uri), "http://blah.foo.com:9999/spoofedpath");
+ do_check_null(getTestReferrer(dest_uri, "http://gah.foo.com/anotherpath"));
+}
diff --git a/netwerk/test/unit/test_referrer_policy.js b/netwerk/test/unit/test_referrer_policy.js
new file mode 100644
index 000000000..f1f9dfd5a
--- /dev/null
+++ b/netwerk/test/unit/test_referrer_policy.js
@@ -0,0 +1,95 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function test_policy(test) {
+ do_print("Running test: " + test.toSource());
+
+ var uri = NetUtil.newURI(test.url, "", null)
+ var chan = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+
+ var referrer = NetUtil.newURI(test.referrer, "", null);
+ chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.setReferrerWithPolicy(referrer, test.policy);
+ if (test.expectedReferrerSpec === undefined) {
+ try {
+ chan.getRequestHeader("Referer");
+ do_throw("Should not find a Referer header!");
+ } catch(e) {
+ }
+ do_check_eq(chan.referrer, null);
+ } else {
+ var header = chan.getRequestHeader("Referer");
+ do_check_eq(header, test.expectedReferrerSpec);
+ do_check_eq(chan.referrer.asciiSpec, test.expectedReferrerSpec);
+ }
+}
+
+const nsIHttpChannel = Ci.nsIHttpChannel;
+var gTests = [
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ url: "http://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: undefined
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_NO_REFERRER,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: undefined
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_ORIGIN,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_ORIGIN,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL,
+ url: "http://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL,
+ url: "http://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer"
+ },
+];
+
+function run_test() {
+ gTests.forEach(test => test_policy(test));
+}
diff --git a/netwerk/test/unit/test_reopen.js b/netwerk/test/unit/test_reopen.js
new file mode 100644
index 000000000..b3744dfc5
--- /dev/null
+++ b/netwerk/test/unit/test_reopen.js
@@ -0,0 +1,141 @@
+// This testcase verifies that channels can't be reopened
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=372486
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const NS_ERROR_IN_PROGRESS = 0x804b000f;
+const NS_ERROR_ALREADY_OPENED = 0x804b0049;
+
+var chan = null;
+var httpserv = null;
+
+[
+ test_data_channel,
+ test_http_channel,
+ test_file_channel,
+ // Commented by default as it relies on external ressources
+ //test_ftp_channel,
+ end
+].forEach(add_test);
+
+// Utility functions
+
+function makeChan(url) {
+ return chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIChannel);
+}
+
+function new_file_channel(file) {
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ return NetUtil.newChannel({
+ uri: ios.newFileURI(file),
+ loadUsingSystemPrincipal: true
+ });
+}
+
+
+function check_throws(closure, error) {
+ var thrown = false;
+ try {
+ closure();
+ } catch (e) {
+ if (error instanceof Array) {
+ do_check_neq(error.indexOf(e.result), -1);
+ } else {
+ do_check_eq(e.result, error);
+ }
+ thrown = true;
+ }
+ do_check_true(thrown);
+}
+
+function check_open_throws(error) {
+ check_throws(function() {
+ chan.open2(listener);
+ }, error);
+}
+
+function check_async_open_throws(error) {
+ check_throws(function() {
+ chan.asyncOpen2(listener);
+ }, error);
+}
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+ },
+
+ onDataAvailable: function test_ODA(request, cx, inputStream,
+ offset, count) {
+ new BinaryInputStream(inputStream).readByteArray(count); // required by API
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ // Once onStopRequest is reached, the channel is marked as having been
+ // opened
+ check_async_open_throws(NS_ERROR_ALREADY_OPENED);
+ do_timeout(0, after_channel_closed);
+ }
+};
+
+function after_channel_closed() {
+ check_async_open_throws(NS_ERROR_ALREADY_OPENED);
+
+ run_next_test();
+}
+
+function test_channel(createChanClosure) {
+ // First, synchronous reopening test
+ chan = createChanClosure();
+ var inputStream = chan.open2();
+ check_open_throws(NS_ERROR_IN_PROGRESS);
+ check_async_open_throws([NS_ERROR_IN_PROGRESS, NS_ERROR_ALREADY_OPENED]);
+
+ // Then, asynchronous one
+ chan = createChanClosure();
+ chan.asyncOpen2(listener);
+ check_open_throws(NS_ERROR_IN_PROGRESS);
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+}
+
+function test_data_channel() {
+ test_channel(function() {
+ return makeChan("data:text/plain,foo");
+ });
+}
+
+function test_http_channel() {
+ test_channel(function() {
+ return makeChan("http://localhost:" + httpserv.identity.primaryPort + "/");
+ });
+}
+
+function test_file_channel() {
+ var file = do_get_file("data/test_readline1.txt");
+ test_channel(function() {
+ return new_file_channel(file);
+ });
+}
+
+// Uncomment test_ftp_channel in test_array to test this
+function test_ftp_channel() {
+ test_channel(function() {
+ return makeChan("ftp://ftp.mozilla.org/pub/mozilla.org/README");
+ });
+}
+
+function end() {
+ httpserv.stop(do_test_finished);
+}
+
+function run_test() {
+ // start server
+ httpserv = new HttpServer();
+ httpserv.start(-1);
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_reply_without_content_type.js b/netwerk/test/unit/test_reply_without_content_type.js
new file mode 100644
index 000000000..7fda87209
--- /dev/null
+++ b/netwerk/test/unit/test_reply_without_content_type.js
@@ -0,0 +1,91 @@
+//
+// tests HTTP replies that lack content-type (where we try to sniff content-type).
+//
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var testpath = "/simple_plainText";
+var httpbody = "<html><body>omg hai</body></html>";
+var testpathGZip = "/simple_gzip";
+//this is compressed httpbody;
+var httpbodyGZip = ["0x1f", "0x8b", "0x8", "0x0", "0x0", "0x0", "0x0", "0x0",
+ "0x0", "0x3", "0xb3", "0xc9", "0x28", "0xc9", "0xcd", "0xb1",
+ "0xb3", "0x49", "0xca", "0x4f", "0xa9", "0xb4", "0xcb",
+ "0xcf", "0x4d", "0x57", "0xc8", "0x48", "0xcc", "0xb4",
+ "0xd1", "0x7", "0xf3", "0x6c", "0xf4", "0xc1", "0x52", "0x0",
+ "0x4", "0x99", "0x79", "0x2b", "0x21", "0x0", "0x0", "0x0"];
+var buffer = "";
+
+var dbg=0
+if (dbg) { print("============== START =========="); }
+
+add_test(function test_plainText() {
+ if (dbg) { print("============== test_plainText: in"); }
+ httpserver.registerPathHandler(testpath, serverHandler_plainText);
+ httpserver.start(-1);
+ var channel = setupChannel(testpath);
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen2(new ChannelListener(checkRequest, channel));
+ do_test_pending();
+ if (dbg) { print("============== test_plainText: out"); }
+});
+
+add_test(function test_GZip() {
+ if (dbg) { print("============== test_GZip: in"); }
+ httpserver.registerPathHandler(testpathGZip, serverHandler_GZip);
+ httpserver.start(-1);
+ var channel = setupChannel(testpathGZip);
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen2(new ChannelListener(checkRequest, channel,
+ CL_EXPECT_GZIP));
+ do_test_pending();
+ if (dbg) { print("============== test_GZip: out"); }
+});
+
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + path,
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler_plainText(metadata, response) {
+ if (dbg) { print("============== serverHandler plainText: in"); }
+// no content type set
+// response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ if (dbg) { print("============== serverHandler plainText: out"); }
+}
+
+function serverHandler_GZip(metadata, response) {
+ if (dbg) { print("============== serverHandler GZip: in"); }
+ // no content type set
+ // response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(httpbodyGZip, httpbodyGZip.length);
+ if (dbg) { print("============== serverHandler GZip: out"); }
+}
+
+function checkRequest(request, data, context) {
+ if (dbg) { print("============== checkRequest: in"); }
+ do_check_eq(data, httpbody);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,"text/html");
+ httpserver.stop(do_test_finished);
+ run_next_test();
+ if (dbg) { print("============== checkRequest: out"); }
+}
+
+function run_test() {
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_resumable_channel.js b/netwerk/test/unit/test_resumable_channel.js
new file mode 100644
index 000000000..57a4481b9
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_channel.js
@@ -0,0 +1,402 @@
+/* Tests various aspects of nsIResumableChannel in combination with HTTP */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = null;
+
+const NS_ERROR_ENTITY_CHANGED = 0x804b0020;
+const NS_ERROR_NOT_RESUMABLE = 0x804b0019;
+
+const rangeBody = "Body of the range request handler.\r\n";
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+function AuthPrompt2() {
+}
+
+AuthPrompt2.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: function authprompt2_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ promptAuth:
+ function ap2_promptAuth(channel, level, authInfo)
+ {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
+ throw 0x80004001;
+ }
+};
+
+function Requestor() {
+}
+
+Requestor.prototype = {
+ QueryInterface: function requestor_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Components.interfaces.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2)
+ this.prompt2 = new AuthPrompt2();
+ return this.prompt2;
+ }
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt2: null
+};
+
+function run_test() {
+ dump("*** run_test\n");
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/auth", authHandler);
+ httpserver.registerPathHandler("/range", rangeHandler);
+ httpserver.registerPathHandler("/acceptranges", acceptRangesHandler);
+ httpserver.registerPathHandler("/redir", redirHandler);
+
+ var entityID;
+
+ function get_entity_id(request, data, ctx) {
+ dump("*** get_entity_id()\n");
+ do_check_true(request instanceof Ci.nsIResumableChannel,
+ "must be a resumable channel");
+ entityID = request.entityID;
+ dump("*** entity id = " + entityID + "\n");
+
+ // Try a non-resumable URL (responds with 200)
+ var chan = make_channel(URL);
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen2(new ChannelListener(try_resume, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_resume(request, data, ctx) {
+ dump("*** try_resume()\n");
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a successful resume
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen2(new ChannelListener(try_resume_zero, null));
+ }
+
+ function try_resume_zero(request, data, ctx) {
+ dump("*** try_resume_zero()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody.substring(1));
+
+ // Try a server which doesn't support range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "none", false);
+ chan.asyncOpen2(new ChannelListener(try_no_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_no_range(request, data, ctx) {
+ dump("*** try_no_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which supports "bytes" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytes", false);
+ chan.asyncOpen2(new ChannelListener(try_bytes_range, null));
+ }
+
+ function try_bytes_range(request, data, ctx) {
+ dump("*** try_bytes_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody);
+
+ // Try a server which supports "foo" and "bar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foo, bar", false);
+ chan.asyncOpen2(new ChannelListener(try_foo_bar_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_foo_bar_range(request, data, ctx) {
+ dump("*** try_foo_bar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which supports "foobar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foobar", false);
+ chan.asyncOpen2(new ChannelListener(try_foobar_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_foobar_range(request, data, ctx) {
+ dump("*** try_foobar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which supports "bytes" and "foobar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytes, foobar", false);
+ chan.asyncOpen2(new ChannelListener(try_bytes_foobar_range, null));
+ }
+
+ function try_bytes_foobar_range(request, data, ctx) {
+ dump("*** try_bytes_foobar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody);
+
+ // Try a server which supports "bytesfoo" and "bar" range requests
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytesfoo, bar", false);
+ chan.asyncOpen2(new ChannelListener(try_bytesfoo_bar_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_bytesfoo_bar_range(request, data, ctx) {
+ dump("*** try_bytesfoo_bar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a server which doesn't send Accept-Ranges header at all
+ var chan = make_channel(URL + "/acceptranges");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen2(new ChannelListener(try_no_accept_ranges, null));
+ }
+
+ function try_no_accept_ranges(request, data, ctx) {
+ dump("*** try_no_accept_ranges()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody);
+
+ // Try a successful suspend/resume from 0
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen2(new ChannelListener(try_suspend_resume, null,
+ CL_SUSPEND | CL_EXPECT_3S_DELAY));
+ }
+
+ function try_suspend_resume(request, data, ctx) {
+ dump("*** try_suspend_resume()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody);
+
+ // Try a successful resume from 0
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen2(new ChannelListener(success, null));
+ }
+
+ function success(request, data, ctx) {
+ dump("*** success()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody);
+
+
+ // Authentication (no password; working resume)
+ // (should not give us any data)
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
+ chan.asyncOpen2(new ChannelListener(test_auth_nopw, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_auth_nopw(request, data, ctx) {
+ dump("*** test_auth_nopw()\n");
+ do_check_false(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
+
+ // Authentication + not working resume
+ var chan = make_channel("http://guest:guest@localhost:" +
+ httpserver.identity.primaryPort + "/auth");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.notificationCallbacks = new Requestor();
+ chan.asyncOpen2(new ChannelListener(test_auth, null, CL_EXPECT_FAILURE));
+ }
+ function test_auth(request, data, ctx) {
+ dump("*** test_auth()\n");
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+ do_check_true(request.nsIHttpChannel.responseStatus < 300);
+
+ // Authentication + working resume
+ var chan = make_channel("http://guest:guest@localhost:" +
+ httpserver.identity.primaryPort + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.notificationCallbacks = new Requestor();
+ chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
+ chan.asyncOpen2(new ChannelListener(test_auth_resume, null));
+ }
+
+ function test_auth_resume(request, data, ctx) {
+ dump("*** test_auth_resume()\n");
+ do_check_eq(data, rangeBody.substring(1));
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+
+ // 404 page (same content length as real content)
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.nsIHttpChannel.setRequestHeader("X-Want-404", "true", false);
+ chan.asyncOpen2(new ChannelListener(test_404, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_404(request, data, ctx) {
+ dump("*** test_404()\n");
+ do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
+ do_check_eq(request.nsIHttpChannel.responseStatus, 404);
+
+ // 416 Requested Range Not Satisfiable
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1000, entityID);
+ chan.asyncOpen2(new ChannelListener(test_416, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_416(request, data, ctx) {
+ dump("*** test_416()\n");
+ do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
+ do_check_eq(request.nsIHttpChannel.responseStatus, 416);
+
+ // Redirect + successful resume
+ var chan = make_channel(URL + "/redir");
+ chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/range", false);
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen2(new ChannelListener(test_redir_resume, null));
+ }
+
+ function test_redir_resume(request, data, ctx) {
+ dump("*** test_redir_resume()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody.substring(1));
+ do_check_eq(request.nsIHttpChannel.responseStatus, 206);
+
+ // Redirect + failed resume
+ var chan = make_channel(URL + "/redir");
+ chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/", false);
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen2(new ChannelListener(test_redir_noresume, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_redir_noresume(request, data, ctx) {
+ dump("*** test_redir_noresume()\n");
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ httpserver.stop(do_test_finished);
+ }
+
+ httpserver.start(-1);
+ var chan = make_channel(URL + "/range");
+ chan.asyncOpen2(new ChannelListener(get_entity_id, null));
+ do_test_pending();
+}
+
+// HANDLERS
+
+function handleAuth(metadata, response) {
+ // btoa("guest:guest"), but that function is not available here
+ var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ var body;
+ if (metadata.hasHeader("Authorization") &&
+ metadata.getHeader("Authorization") == expectedHeader)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ return true;
+ }
+ else
+ {
+ // didn't know guest:guest, failure
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+ return false;
+ }
+}
+
+// /auth
+function authHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ var body = handleAuth(metadata, response) ? "success" : "failure";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /range
+function rangeHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+
+ if (metadata.hasHeader("X-Need-Auth")) {
+ if (!handleAuth(metadata, response)) {
+ body = "auth failed";
+ response.bodyOutputStream.write(body, body.length);
+ return;
+ }
+ }
+
+ if (metadata.hasHeader("X-Want-404")) {
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ body = rangeBody;
+ response.bodyOutputStream.write(body, body.length);
+ return;
+ }
+
+ var body = rangeBody;
+
+ if (metadata.hasHeader("Range")) {
+ // Syntax: bytes=[from]-[to] (we don't support multiple ranges)
+ var matches = metadata.getHeader("Range").match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+ var from = (matches[1] === undefined) ? 0 : matches[1];
+ var to = (matches[2] === undefined) ? rangeBody.length - 1 : matches[2];
+ if (from >= rangeBody.length) {
+ response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
+ response.setHeader("Content-Range", "*/" + rangeBody.length, false);
+ return;
+ }
+ body = body.substring(from, to + 1);
+ // always respond to successful range requests with 206
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", from + "-" + to + "/" + rangeBody.length, false);
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /acceptranges
+function acceptRangesHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ if (metadata.hasHeader("X-Range-Type"))
+ response.setHeader("Accept-Ranges", metadata.getHeader("X-Range-Type"), false);
+ response.bodyOutputStream.write(rangeBody, rangeBody.length);
+}
+
+// /redir
+function redirHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 302, "Found");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Location", metadata.getHeader("X-Redir-To"), false);
+ var body = "redirect\r\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+
diff --git a/netwerk/test/unit/test_resumable_truncate.js b/netwerk/test/unit/test_resumable_truncate.js
new file mode 100644
index 000000000..c23a91b71
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_truncate.js
@@ -0,0 +1,88 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+}
+
+const responseBody = "response body";
+
+function cachedHandler(metadata, response) {
+ var body = responseBody;
+ if (metadata.hasHeader("Range")) {
+ var matches = metadata.getHeader("Range").match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+ var from = (matches[1] === undefined) ? 0 : matches[1];
+ var to = (matches[2] === undefined) ? responseBody.length - 1 : matches[2];
+ if (from >= responseBody.length) {
+ response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
+ response.setHeader("Content-Range", "*/" + responseBody.length, false);
+ return;
+ }
+ body = responseBody.slice(from, to + 1);
+ // always respond to successful range requests with 206
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", from + "-" + to + "/" + responseBody.length, false);
+ }
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Accept-Ranges", "bytes");
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function Canceler(continueFn) {
+ this.continueFn = continueFn;
+}
+
+Canceler.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ request.QueryInterface(Ci.nsIChannel)
+ .cancel(Components.results.NS_BINDING_ABORTED);
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(status, Components.results.NS_BINDING_ABORTED);
+ this.continueFn();
+ }
+};
+
+function finish_test() {
+ httpserver.stop(do_test_finished);
+}
+
+function start_cache_read() {
+ var chan = make_channel("http://localhost:" +
+ httpserver.identity.primaryPort + "/cached/test.gz");
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function start_canceler() {
+ var chan = make_channel("http://localhost:" +
+ httpserver.identity.primaryPort + "/cached/test.gz");
+ chan.asyncOpen2(new Canceler(start_cache_read));
+}
+
+function run_test() {
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/cached/test.gz", cachedHandler);
+ httpserver.start(-1);
+
+ var chan = make_channel("http://localhost:" +
+ httpserver.identity.primaryPort + "/cached/test.gz");
+ chan.asyncOpen2(new ChannelListener(start_canceler, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_safeoutputstream.js b/netwerk/test/unit/test_safeoutputstream.js
new file mode 100644
index 000000000..3e41fcbb0
--- /dev/null
+++ b/netwerk/test/unit/test_safeoutputstream.js
@@ -0,0 +1,66 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function write_atomic(file, str) {
+ var stream = Cc["@mozilla.org/network/atomic-file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, -1, -1, 0);
+ do {
+ var written = stream.write(str, str.length);
+ if (written == str.length)
+ break;
+ str = str.substring(written);
+ } while (1);
+ stream.QueryInterface(Ci.nsISafeOutputStream).finish();
+ stream.close();
+}
+
+function write(file, str) {
+ var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, -1, -1, 0);
+ do {
+ var written = stream.write(str, str.length);
+ if (written == str.length)
+ break;
+ str = str.substring(written);
+ } while (1);
+ stream.QueryInterface(Ci.nsISafeOutputStream).finish();
+ stream.close();
+}
+
+function checkFile(file, str) {
+ var stream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ stream.init(file, -1, -1, 0);
+
+ var scriptStream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ scriptStream.init(stream);
+
+ do_check_eq(scriptStream.read(scriptStream.available()), str);
+ scriptStream.close();
+}
+
+function run_test()
+{
+ var filename = "\u0913";
+ var file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ file.append(filename);
+
+ write(file, "First write");
+ checkFile(file, "First write");
+
+ write(file, "Second write");
+ checkFile(file, "Second write");
+
+ write_atomic(file, "First write: Atomic");
+ checkFile(file, "First write: Atomic");
+
+ write_atomic(file, "Second write: Atomic");
+ checkFile(file, "Second write: Atomic");
+}
diff --git a/netwerk/test/unit/test_safeoutputstream_append.js b/netwerk/test/unit/test_safeoutputstream_append.js
new file mode 100644
index 000000000..a7abb06e6
--- /dev/null
+++ b/netwerk/test/unit/test_safeoutputstream_append.js
@@ -0,0 +1,42 @@
+/* atomic-file-output-stream and safe-file-output-stream should throw and
+ * exception if PR_APPEND is explicity specified without PR_TRUNCATE. */
+
+const PR_WRONLY = 0x02;
+const PR_CREATE_FILE = 0x08;
+const PR_APPEND = 0x10;
+const PR_TRUNCATE = 0x20;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+function check_flag(file, contractID, flags, throws) {
+ let stream = Cc[contractID].createInstance(Ci.nsIFileOutputStream);
+
+ if (throws) {
+ /* NS_ERROR_INVALID_ARG is reported as NS_ERROR_ILLEGAL_VALUE, since they
+ * are same value. */
+ Assert.throws(() => stream.init(file, flags, 0o644, 0),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ } else {
+ stream.init(file, flags, 0o644, 0);
+ stream.close();
+ }
+}
+
+function run_test() {
+ let filename = "test.txt";
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append(filename);
+
+ let tests = [
+ [PR_WRONLY | PR_CREATE_FILE | PR_APPEND | PR_TRUNCATE, false],
+ [PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, false],
+ [PR_WRONLY | PR_CREATE_FILE | PR_APPEND, true],
+ [-1, false],
+ ];
+ for (let contractID of ["@mozilla.org/network/atomic-file-output-stream;1",
+ "@mozilla.org/network/safe-file-output-stream;1"]) {
+ for (let [flags, throws] of tests) {
+ check_flag(file, contractID, flags, throws);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_separate_connections.js b/netwerk/test/unit/test_separate_connections.js
new file mode 100644
index 000000000..d49b2885f
--- /dev/null
+++ b/netwerk/test/unit/test_separate_connections.js
@@ -0,0 +1,99 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+// This unit test ensures each container has its own connection pool.
+// We verify this behavior by opening channels with different userContextId,
+// and their connection info's hash keys should be different.
+
+// In the first round of this test, we record the hash key in each container.
+// In the second round, we check if each container's hash key is consistent
+// and different from other container's hash key.
+
+let httpserv = null;
+let gSecondRoundStarted = false;
+
+function handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ let body = "0123456789";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function makeChan(url, userContextId) {
+ let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+ chan.loadInfo.originAttributes = { userContextId: userContextId };
+ return chan;
+}
+
+let previousHashKeys = [];
+
+function Listener(userContextId) {
+ this.userContextId = userContextId;
+}
+
+let gTestsRun = 0;
+Listener.prototype = {
+ onStartRequest: function(request, context) {
+ request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ do_check_eq(request.loadInfo.originAttributes.userContextId, this.userContextId);
+
+ let hashKey = request.connectionInfoHashKey;
+ if (gSecondRoundStarted) {
+ // Compare the hash keys with the previous set ones.
+ // Hash keys should match if and only if their userContextId are the same.
+ for (let userContextId = 0; userContextId < 3; userContextId++) {
+ if (userContextId == this.userContextId) {
+ do_check_eq(hashKey, previousHashKeys[userContextId]);
+ } else {
+ do_check_neq(hashKey, previousHashKeys[userContextId]);
+ }
+ }
+ } else {
+ // Set the hash keys in the first round.
+ previousHashKeys[this.userContextId] = hashKey;
+ }
+ },
+ onDataAvailable: function(request, ctx, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+ onStopRequest: function() {
+ gTestsRun++;
+ if (gTestsRun == 3) {
+ gTestsRun = 0;
+ if (gSecondRoundStarted) {
+ // The second round finishes.
+ httpserv.stop(do_test_finished);
+ } else {
+ // The first round finishes. Do the second round.
+ gSecondRoundStarted = true;
+ doTest();
+ }
+ }
+ },
+};
+
+function doTest() {
+ for (let userContextId = 0; userContextId < 3; userContextId++) {
+ let chan = makeChan(URL, userContextId);
+ let listener = new Listener(userContextId);
+ chan.asyncOpen2(listener);
+ }
+}
+
+function run_test() {
+ do_test_pending();
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", handler);
+ httpserv.start(-1);
+
+ doTest();
+}
+
diff --git a/netwerk/test/unit/test_signature_extraction.js b/netwerk/test/unit/test_signature_extraction.js
new file mode 100644
index 000000000..7db93f55b
--- /dev/null
+++ b/netwerk/test/unit/test_signature_extraction.js
@@ -0,0 +1,206 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests signature extraction using Windows Authenticode APIs of
+ * downloaded files.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const BackgroundFileSaverOutputStream = Components.Constructor(
+ "@mozilla.org/network/background-file-saver;1?mode=outputstream",
+ "nsIBackgroundFileSaver");
+
+const StringInputStream = Components.Constructor(
+ "@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream",
+ "setData");
+
+const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
+
+/**
+ * Returns a reference to a temporary file. If the file is then created, it
+ * will be removed when tests in this file finish.
+ */
+function getTempFile(aLeafName) {
+ let file = FileUtils.getFile("TmpD", [aLeafName]);
+ do_register_cleanup(function GTF_cleanup() {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ });
+ return file;
+}
+
+/**
+ * Waits for the given saver object to complete.
+ *
+ * @param aSaver
+ * The saver, with the output stream or a stream listener implementation.
+ * @param aOnTargetChangeFn
+ * Optional callback invoked with the target file name when it changes.
+ *
+ * @return {Promise}
+ * @resolves When onSaveComplete is called with a success code.
+ * @rejects With an exception, if onSaveComplete is called with a failure code.
+ */
+function promiseSaverComplete(aSaver, aOnTargetChangeFn) {
+ let deferred = Promise.defer();
+ aSaver.observer = {
+ onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget)
+ {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus)
+ {
+ if (Components.isSuccessCode(aStatus)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception("Saver failed.", aStatus));
+ }
+ },
+ };
+ return deferred.promise;
+}
+
+/**
+ * Feeds a string to a BackgroundFileSaverOutputStream.
+ *
+ * @param aSourceString
+ * The source data to copy.
+ * @param aSaverOutputStream
+ * The BackgroundFileSaverOutputStream to feed.
+ * @param aCloseWhenDone
+ * If true, the output stream will be closed when the copy finishes.
+ *
+ * @return {Promise}
+ * @resolves When the copy completes with a success code.
+ * @rejects With an exception, if the copy fails.
+ */
+function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) {
+ let deferred = Promise.defer();
+ let inputStream = new StringInputStream(aSourceString, aSourceString.length);
+ let copier = Cc["@mozilla.org/network/async-stream-copier;1"]
+ .createInstance(Ci.nsIAsyncStreamCopier);
+ copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true,
+ aCloseWhenDone);
+ copier.asyncCopy({
+ onStartRequest: function () { },
+ onStopRequest: function (aRequest, aContext, aStatusCode)
+ {
+ if (Components.isSuccessCode(aStatusCode)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception(aResult));
+ }
+ },
+ }, null);
+ return deferred.promise;
+}
+
+var gStillRunning = true;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+function run_test()
+{
+ run_next_test();
+}
+
+add_task(function test_setup()
+{
+ // Wait 10 minutes, that is half of the external xpcshell timeout.
+ do_timeout(10 * 60 * 1000, function() {
+ if (gStillRunning) {
+ do_throw("Test timed out.");
+ }
+ })
+});
+
+function readFileToString(aFilename) {
+ let f = do_get_file(aFilename);
+ let stream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ stream.init(f, -1, 0, 0);
+ let buf = NetUtil.readInputStreamToString(stream, stream.available());
+ return buf;
+}
+
+add_task(function test_signature()
+{
+ // Check that we get a signature if the saver is finished on Windows.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+
+ let data = readFileToString("data/signed_win.exe");
+ let saver = new BackgroundFileSaverOutputStream();
+ let completionPromise = promiseSaverComplete(saver);
+
+ try {
+ let signatureInfo = saver.signatureInfo;
+ do_throw("Can't get signature before saver is complete.");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+
+ saver.enableSignatureInfo();
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver(data, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // There's only one nsIX509CertList in the signature array.
+ do_check_eq(1, saver.signatureInfo.length);
+ let certLists = saver.signatureInfo.enumerate();
+ do_check_true(certLists.hasMoreElements());
+ let certList = certLists.getNext().QueryInterface(Ci.nsIX509CertList);
+ do_check_false(certLists.hasMoreElements());
+
+ // Check that it has 3 certs.
+ let certs = certList.getEnumerator();
+ do_check_true(certs.hasMoreElements());
+ let signer = certs.getNext().QueryInterface(Ci.nsIX509Cert);
+ do_check_true(certs.hasMoreElements());
+ let issuer = certs.getNext().QueryInterface(Ci.nsIX509Cert);
+ do_check_true(certs.hasMoreElements());
+ let root = certs.getNext().QueryInterface(Ci.nsIX509Cert);
+ do_check_false(certs.hasMoreElements());
+
+ // Check that the certs have expected strings attached.
+ let organization = "Microsoft Corporation";
+ do_check_eq("Microsoft Corporation", signer.commonName);
+ do_check_eq(organization, signer.organization);
+ do_check_eq("Copyright (c) 2002 Microsoft Corp.", signer.organizationalUnit);
+
+ do_check_eq("Microsoft Code Signing PCA", issuer.commonName);
+ do_check_eq(organization, issuer.organization);
+ do_check_eq("Copyright (c) 2000 Microsoft Corp.", issuer.organizationalUnit);
+
+ do_check_eq("Microsoft Root Authority", root.commonName);
+ do_check_false(root.organization);
+ do_check_eq("Copyright (c) 1997 Microsoft Corp.", root.organizationalUnit);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_teardown()
+{
+ gStillRunning = false;
+});
diff --git a/netwerk/test/unit/test_simple.js b/netwerk/test/unit/test_simple.js
new file mode 100644
index 000000000..12ec71779
--- /dev/null
+++ b/netwerk/test/unit/test_simple.js
@@ -0,0 +1,56 @@
+//
+// Simple HTTP test: fetches page
+//
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+var buffer = "";
+
+var dbg=0
+if (dbg) { print("============== START =========="); }
+
+function run_test() {
+ setup_test();
+ do_test_pending();
+}
+
+function setup_test() {
+ if (dbg) { print("============== setup_test: in"); }
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ var channel = setupChannel(testpath);
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen2(new ChannelListener(checkRequest, channel));
+ if (dbg) { print("============== setup_test: out"); }
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + path,
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ if (dbg) { print("============== serverHandler: in"); }
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+ if (dbg) { print("============== serverHandler: out"); }
+}
+
+function checkRequest(request, data, context) {
+ if (dbg) { print("============== checkRequest: in"); }
+ do_check_eq(data, httpbody);
+ httpserver.stop(do_test_finished);
+ if (dbg) { print("============== checkRequest: out"); }
+}
+
diff --git a/netwerk/test/unit/test_sockettransportsvc_available.js b/netwerk/test/unit/test_sockettransportsvc_available.js
new file mode 100644
index 000000000..bfb64e324
--- /dev/null
+++ b/netwerk/test/unit/test_sockettransportsvc_available.js
@@ -0,0 +1,8 @@
+function run_test() {
+ try {
+ var sts = Components.classes["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Components.interfaces.nsISocketTransportService);
+ } catch(e) {}
+
+ do_check_true(!!sts);
+}
diff --git a/netwerk/test/unit/test_socks.js b/netwerk/test/unit/test_socks.js
new file mode 100644
index 000000000..57c947979
--- /dev/null
+++ b/netwerk/test/unit/test_socks.js
@@ -0,0 +1,525 @@
+var CC = Components.Constructor;
+
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+const DirectoryService = CC("@mozilla.org/file/directory_service;1",
+ "nsIProperties");
+const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init");
+
+const currentThread = Cc["@mozilla.org/thread-manager;1"]
+ .getService().currentThread;
+
+var socks_test_server = null;
+var socks_listen_port = -1;
+
+function getAvailableBytes(input)
+{
+ var len = 0;
+
+ try {
+ len = input.available();
+ } catch (e) {
+ }
+
+ return len;
+}
+
+function runScriptSubprocess(script, args)
+{
+ var ds = new DirectoryService();
+ var bin = ds.get("XREExeF", Ci.nsILocalFile);
+ if (!bin.exists()) {
+ do_throw("Can't find xpcshell binary");
+ }
+
+ var script = do_get_file(script);
+ var proc = new Process(bin);
+ var args = [script.path].concat(args);
+
+ proc.run(false, args, args.length);
+
+ return proc;
+}
+
+function buf2ip(buf)
+{
+ if (buf.length == 16) {
+ var ip = (buf[0] << 4 | buf[1]).toString(16) + ':' +
+ (buf[2] << 4 | buf[3]).toString(16) + ':' +
+ (buf[4] << 4 | buf[5]).toString(16) + ':' +
+ (buf[6] << 4 | buf[7]).toString(16) + ':' +
+ (buf[8] << 4 | buf[9]).toString(16) + ':' +
+ (buf[10] << 4 | buf[11]).toString(16) + ':' +
+ (buf[12] << 4 | buf[13]).toString(16) + ':' +
+ (buf[14] << 4 | buf[15]).toString(16);
+ for (var i = 8; i >= 2; i--) {
+ var re = new RegExp("(^|:)(0(:|$)){" + i + "}");
+ var shortip = ip.replace(re, '::');
+ if (shortip != ip) {
+ return shortip;
+ }
+ }
+ return ip;
+ } else {
+ return buf.join('.');
+ }
+}
+
+function buf2int(buf)
+{
+ var n = 0;
+
+ for (var i in buf) {
+ n |= buf[i] << ((buf.length - i - 1) * 8);
+ }
+
+ return n;
+}
+
+function buf2str(buf)
+{
+ return String.fromCharCode.apply(null, buf);
+}
+
+const STATE_WAIT_GREETING = 1;
+const STATE_WAIT_SOCKS4_REQUEST = 2;
+const STATE_WAIT_SOCKS4_USERNAME = 3;
+const STATE_WAIT_SOCKS4_HOSTNAME = 4;
+const STATE_WAIT_SOCKS5_GREETING = 5;
+const STATE_WAIT_SOCKS5_REQUEST = 6;
+const STATE_WAIT_PONG = 7;
+const STATE_GOT_PONG = 8;
+
+function SocksClient(server, client_in, client_out)
+{
+ this.server = server;
+ this.type = '';
+ this.username = '';
+ this.dest_name = '';
+ this.dest_addr = [];
+ this.dest_port = [];
+
+ this.client_in = client_in;
+ this.client_out = client_out;
+ this.inbuf = [];
+ this.outbuf = String();
+ this.state = STATE_WAIT_GREETING;
+ this.waitRead(this.client_in);
+}
+SocksClient.prototype = {
+ onInputStreamReady: function(input)
+ {
+ var len = getAvailableBytes(input);
+
+ if (len == 0) {
+ print('server: client closed!');
+ do_check_eq(this.state, STATE_GOT_PONG);
+ this.server.testCompleted(this);
+ return;
+ }
+
+ var bin = new BinaryInputStream(input);
+ var data = bin.readByteArray(len);
+ this.inbuf = this.inbuf.concat(data);
+
+ switch (this.state) {
+ case STATE_WAIT_GREETING:
+ this.checkSocksGreeting();
+ break;
+ case STATE_WAIT_SOCKS4_REQUEST:
+ this.checkSocks4Request();
+ break;
+ case STATE_WAIT_SOCKS4_USERNAME:
+ this.checkSocks4Username();
+ break;
+ case STATE_WAIT_SOCKS4_HOSTNAME:
+ this.checkSocks4Hostname();
+ break;
+ case STATE_WAIT_SOCKS5_GREETING:
+ this.checkSocks5Greeting();
+ break;
+ case STATE_WAIT_SOCKS5_REQUEST:
+ this.checkSocks5Request();
+ break;
+ case STATE_WAIT_PONG:
+ this.checkPong();
+ break;
+ default:
+ do_throw("server: read in invalid state!");
+ }
+
+ this.waitRead(input);
+ },
+
+ onOutputStreamReady: function(output)
+ {
+ var len = output.write(this.outbuf, this.outbuf.length);
+ if (len != this.outbuf.length) {
+ this.outbuf = this.outbuf.substring(len);
+ this.waitWrite(output);
+ } else
+ this.outbuf = String();
+ },
+
+ waitRead: function(input)
+ {
+ input.asyncWait(this, 0, 0, currentThread);
+ },
+
+ waitWrite: function(output)
+ {
+ output.asyncWait(this, 0, 0, currentThread);
+ },
+
+ write: function(buf)
+ {
+ this.outbuf += buf;
+ this.waitWrite(this.client_out);
+ },
+
+ checkSocksGreeting: function()
+ {
+ if (this.inbuf.length == 0)
+ return;
+
+ if (this.inbuf[0] == 4) {
+ print('server: got socks 4');
+ this.type = 'socks4';
+ this.state = STATE_WAIT_SOCKS4_REQUEST;
+ this.checkSocks4Request();
+ } else if (this.inbuf[0] == 5) {
+ print('server: got socks 5');
+ this.type = 'socks';
+ this.state = STATE_WAIT_SOCKS5_GREETING;
+ this.checkSocks5Greeting();
+ } else {
+ do_throw("Unknown socks protocol!");
+ }
+ },
+
+ checkSocks4Request: function()
+ {
+ if (this.inbuf.length < 8)
+ return;
+
+ do_check_eq(this.inbuf[1], 0x01);
+
+ this.dest_port = this.inbuf.slice(2, 4);
+ this.dest_addr = this.inbuf.slice(4, 8);
+
+ this.inbuf = this.inbuf.slice(8);
+ this.state = STATE_WAIT_SOCKS4_USERNAME;
+ this.checkSocks4Username();
+ },
+
+ readString: function()
+ {
+ var i = this.inbuf.indexOf(0);
+ var str = null;
+
+ if (i >= 0) {
+ var buf = this.inbuf.slice(0,i);
+ str = buf2str(buf);
+ this.inbuf = this.inbuf.slice(i+1);
+ }
+
+ return str;
+ },
+
+ checkSocks4Username: function()
+ {
+ var str = this.readString();
+
+ if (str == null)
+ return;
+
+ this.username = str;
+ if (this.dest_addr[0] == 0 &&
+ this.dest_addr[1] == 0 &&
+ this.dest_addr[2] == 0 &&
+ this.dest_addr[3] != 0) {
+ this.state = STATE_WAIT_SOCKS4_HOSTNAME;
+ this.checkSocks4Hostname();
+ } else {
+ this.sendSocks4Response();
+ }
+ },
+
+ checkSocks4Hostname: function()
+ {
+ var str = this.readString();
+
+ if (str == null)
+ return;
+
+ this.dest_name = str;
+ this.sendSocks4Response();
+ },
+
+ sendSocks4Response: function()
+ {
+ this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00';
+ this.sendPing();
+ },
+
+ checkSocks5Greeting: function()
+ {
+ if (this.inbuf.length < 2)
+ return;
+ var nmethods = this.inbuf[1];
+ if (this.inbuf.length < 2 + nmethods)
+ return;
+
+ do_check_true(nmethods >= 1);
+ var methods = this.inbuf.slice(2, 2 + nmethods);
+ do_check_true(0 in methods);
+
+ this.inbuf = [];
+ this.state = STATE_WAIT_SOCKS5_REQUEST;
+ this.write('\x05\x00');
+ },
+
+ checkSocks5Request: function()
+ {
+ if (this.inbuf.length < 4)
+ return;
+
+ do_check_eq(this.inbuf[0], 0x05);
+ do_check_eq(this.inbuf[1], 0x01);
+ do_check_eq(this.inbuf[2], 0x00);
+
+ var atype = this.inbuf[3];
+ var len;
+ var name = false;
+
+ switch (atype) {
+ case 0x01:
+ len = 4;
+ break;
+ case 0x03:
+ len = this.inbuf[4];
+ name = true;
+ break;
+ case 0x04:
+ len = 16;
+ break;
+ default:
+ do_throw("Unknown address type " + atype);
+ }
+
+ if (name) {
+ if (this.inbuf.length < 4 + len + 1 + 2)
+ return;
+
+ buf = this.inbuf.slice(5, 5 + len);
+ this.dest_name = buf2str(buf);
+ len += 1;
+ } else {
+ if (this.inbuf.length < 4 + len + 2)
+ return;
+
+ this.dest_addr = this.inbuf.slice(4, 4 + len);
+ }
+
+ len += 4;
+ this.dest_port = this.inbuf.slice(len, len + 2);
+ this.inbuf = this.inbuf.slice(len + 2);
+ this.sendSocks5Response();
+ },
+
+ sendSocks5Response: function()
+ {
+ if (this.dest_addr.length == 16) {
+ // send a successful response with the address, [::1]:80
+ this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80';
+ } else {
+ // send a successful response with the address, 127.0.0.1:80
+ this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80';
+ }
+ this.sendPing();
+ },
+
+ sendPing: function()
+ {
+ print('server: sending ping');
+ this.state = STATE_WAIT_PONG;
+ this.outbuf += "PING!";
+ this.inbuf = [];
+ this.waitWrite(this.client_out);
+ this.waitRead(this.client_in);
+ },
+
+ checkPong: function()
+ {
+ var pong = buf2str(this.inbuf);
+ do_check_eq(pong, "PONG!");
+ this.state = STATE_GOT_PONG;
+ this.waitRead(this.client_in);
+ },
+
+ close: function()
+ {
+ this.client_in.close();
+ this.client_out.close();
+ }
+};
+
+function SocksTestServer()
+{
+ this.listener = ServerSocket(-1, true, -1);
+ socks_listen_port = this.listener.port;
+ print('server: listening on', socks_listen_port);
+ this.listener.asyncListen(this);
+ this.test_cases = [];
+ this.client_connections = [];
+ this.client_subprocess = null;
+ // port is used as the ID for test cases
+ this.test_port_id = 8000;
+ this.tests_completed = 0;
+}
+SocksTestServer.prototype = {
+ addTestCase: function(test)
+ {
+ test.finished = false;
+ test.port = this.test_port_id++;
+ this.test_cases.push(test);
+ },
+
+ pickTest: function(id)
+ {
+ for (var i in this.test_cases) {
+ var test = this.test_cases[i];
+ if (test.port == id) {
+ this.tests_completed++;
+ return test;
+ }
+ }
+ do_throw("No test case with id " + id);
+ },
+
+ testCompleted: function(client)
+ {
+ var port_id = buf2int(client.dest_port);
+ var test = this.pickTest(port_id);
+
+ print('server: test finished', test.port);
+ do_check_true(test != null);
+ do_check_eq(test.expectedType || test.type, client.type);
+ do_check_eq(test.port, port_id);
+
+ if (test.remote_dns)
+ do_check_eq(test.host, client.dest_name);
+ else
+ do_check_eq(test.host, buf2ip(client.dest_addr));
+
+ if (this.test_cases.length == this.tests_completed) {
+ print('server: all tests completed');
+ this.close();
+ do_test_finished();
+ }
+ },
+
+ runClientSubprocess: function()
+ {
+ var argv = [];
+
+ // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local>
+ for (var test of this.test_cases) {
+ var arg = test.type + '|' +
+ String(socks_listen_port) + '|' +
+ test.host + '|' + test.port + '|';
+ if (test.remote_dns)
+ arg += 'remote';
+ else
+ arg += 'local';
+ print('server: using test case', arg);
+ argv.push(arg);
+ }
+
+ this.client_subprocess = runScriptSubprocess(
+ 'socks_client_subprocess.js', argv);
+ },
+
+ onSocketAccepted: function(socket, trans)
+ {
+ print('server: got client connection');
+ var input = trans.openInputStream(0, 0, 0);
+ var output = trans.openOutputStream(0, 0, 0);
+ var client = new SocksClient(this, input, output);
+ this.client_connections.push(client);
+ },
+
+ onStopListening: function(socket)
+ {
+ },
+
+ close: function()
+ {
+ if (this.client_subprocess) {
+ try {
+ this.client_subprocess.kill();
+ } catch (x) {
+ do_note_exception(x, 'Killing subprocess failed');
+ }
+ this.client_subprocess = null;
+ }
+ for (var client of this.client_connections)
+ client.close();
+ this.client_connections = [];
+ if (this.listener) {
+ this.listener.close();
+ this.listener = null;
+ }
+ }
+};
+
+function test_timeout()
+{
+ socks_test_server.close();
+ do_throw("SOCKS test took too long!");
+}
+
+function run_test()
+{
+ socks_test_server = new SocksTestServer();
+
+ socks_test_server.addTestCase({
+ type: "socks4",
+ host: '127.0.0.1',
+ remote_dns: false,
+ });
+ socks_test_server.addTestCase({
+ type: "socks4",
+ host: '12345.xxx',
+ remote_dns: true,
+ });
+ socks_test_server.addTestCase({
+ type: "socks4",
+ expectedType: "socks",
+ host: '::1',
+ remote_dns: false,
+ });
+ socks_test_server.addTestCase({
+ type: "socks",
+ host: '127.0.0.1',
+ remote_dns: false,
+ });
+ socks_test_server.addTestCase({
+ type: "socks",
+ host: 'abcdefg.xxx',
+ remote_dns: true,
+ });
+ socks_test_server.addTestCase({
+ type: "socks",
+ host: '::1',
+ remote_dns: false,
+ });
+ socks_test_server.runClientSubprocess();
+
+ do_timeout(120 * 1000, test_timeout);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_speculative_connect.js b/netwerk/test/unit/test_speculative_connect.js
new file mode 100644
index 000000000..51ae82488
--- /dev/null
+++ b/netwerk/test/unit/test_speculative_connect.js
@@ -0,0 +1,333 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* vim: set ts=4 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var CC = Components.Constructor;
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+var serv;
+var ios;
+
+/** Example local IP addresses (literal IP address hostname).
+ *
+ * Note: for IPv6 Unique Local and Link Local, a wider range of addresses is
+ * set aside than those most commonly used. Technically, link local addresses
+ * include those beginning with fe80:: through febf::, although in practise
+ * only fe80:: is used. Necko code blocks speculative connections for the wider
+ * range; hence, this test considers that range too.
+ */
+var localIPv4Literals =
+ [ // IPv4 RFC1918 \
+ "10.0.0.1", "10.10.10.10", "10.255.255.255", // 10/8
+ "172.16.0.1", "172.23.172.12", "172.31.255.255", // 172.16/20
+ "192.168.0.1", "192.168.192.168", "192.168.255.255", // 192.168/16
+ // IPv4 Link Local
+ "169.254.0.1", "169.254.192.154", "169.254.255.255" // 169.254/16
+ ];
+var localIPv6Literals =
+ [ // IPv6 Unique Local fc00::/7
+ "fc00::1", "fdfe:dcba:9876:abcd:ef01:2345:6789:abcd",
+ "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ // IPv6 Link Local fe80::/10
+ "fe80::1", "fe80::abcd:ef01:2345:6789",
+ "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ ];
+var localIPLiterals = localIPv4Literals.concat(localIPv6Literals);
+
+/** Test function list and descriptions.
+ */
+var testList =
+ [ test_speculative_connect,
+ test_hostnames_resolving_to_local_addresses,
+ test_proxies_with_local_addresses
+ ];
+
+var testDescription =
+ [ "Expect pass with localhost",
+ "Expect failure with resolved local IPs",
+ "Expect failure for proxies with local IPs"
+ ];
+
+var testIdx = 0;
+var hostIdx = 0;
+
+
+/** TestServer
+ *
+ * Implements nsIServerSocket for test_speculative_connect.
+ */
+function TestServer() {
+ this.listener = ServerSocket(-1, true, -1);
+ this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIServerSocket) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ onSocketAccepted: function(socket, trans) {
+ try { this.listener.close(); } catch(e) {}
+ do_check_true(true);
+ next_test();
+ },
+
+ onStopListening: function(socket) {}
+};
+
+/** TestFailedStreamCallback
+ *
+ * Implements nsI[Input|Output]StreamCallback for socket layer tests.
+ * Expect failure in all cases
+ */
+function TestFailedStreamCallback(transport, hostname, next) {
+ this.transport = transport;
+ this.hostname = hostname;
+ this.next = next;
+ this.dummyContent = "G";
+}
+
+TestFailedStreamCallback.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ processException: function(e) {
+ do_check_instanceof(e, Ci.nsIException);
+ // A refusal to connect speculatively should throw an error.
+ do_check_eq(e.result, Cr.NS_ERROR_CONNECTION_REFUSED);
+ this.transport.close(Cr.NS_BINDING_ABORTED);
+ return true;
+ },
+ onOutputStreamReady: function(outstream) {
+ do_print("outputstream handler.");
+ do_check_neq(typeof(outstream), undefined);
+ try {
+ outstream.write(this.dummyContent, this.dummyContent.length);
+ } catch (e) {
+ this.processException(e);
+ this.next();
+ return;
+ }
+ do_print("no exception on write. Wait for read.");
+ },
+ onInputStreamReady: function(instream) {
+ do_print("inputstream handler.");
+ do_check_neq(typeof(instream), undefined);
+ try {
+ instream.available();
+ } catch (e) {
+ this.processException(e);
+ this.next();
+ return;
+ }
+ do_throw("Speculative Connect should have failed for " +
+ this.hostname);
+ this.transport.close(Cr.NS_BINDING_ABORTED);
+ this.next();
+ },
+};
+
+/** test_speculative_connect
+ *
+ * Tests a basic positive case using nsIOService.SpeculativeConnect:
+ * connecting to localhost.
+ */
+function test_speculative_connect() {
+ serv = new TestServer();
+ var URI = ios.newURI("http://localhost:" + serv.listener.port + "/just/a/test", null, null);
+ ios.QueryInterface(Ci.nsISpeculativeConnect)
+ .speculativeConnect(URI, null);
+}
+
+/* Speculative connections should not be allowed for hosts with local IP
+ * addresses (Bug 853423). That list includes:
+ * -- IPv4 RFC1918 and Link Local Addresses.
+ * -- IPv6 Unique and Link Local Addresses.
+ *
+ * Two tests are required:
+ * 1. Verify IP Literals passed to the SpeculativeConnect API.
+ * 2. Verify hostnames that need to be resolved at the socket layer.
+ */
+
+/** test_hostnames_resolving_to_addresses
+ *
+ * Common test function for resolved hostnames. Takes a list of hosts, a
+ * boolean to determine if the test is expected to succeed or fail, and a
+ * function to call the next test case.
+ */
+function test_hostnames_resolving_to_addresses(host, next) {
+ do_print(host);
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+ do_check_neq(typeof(sts), undefined);
+ var transport = sts.createTransport(null, 0, host, 80, null);
+ do_check_neq(typeof(transport), undefined);
+
+ transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
+ do_check_eq(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
+
+ var outStream = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED,0,0);
+ var inStream = transport.openInputStream(0,0,0);
+ do_check_neq(typeof(outStream), undefined);
+ do_check_neq(typeof(inStream), undefined);
+
+ var callback = new TestFailedStreamCallback(transport, host, next);
+ do_check_neq(typeof(callback), undefined);
+
+ // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
+ // adds callback to ns*StreamReadyEvent on main thread, and doesn't
+ // addref off the main thread.
+ var gThreadManager = Cc["@mozilla.org/thread-manager;1"]
+ .getService(Ci.nsIThreadManager);
+ var mainThread = gThreadManager.currentThread;
+
+ try {
+ outStream.QueryInterface(Ci.nsIAsyncOutputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ inStream.QueryInterface(Ci.nsIAsyncInputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ } catch (e) {
+ do_throw("asyncWait should not fail!");
+ }
+}
+
+/**
+ * test_hostnames_resolving_to_local_addresses
+ *
+ * Creates an nsISocketTransport and simulates a speculative connect request
+ * for a hostname that resolves to a local IP address.
+ * Runs asynchronously; on test success (i.e. failure to connect), the callback
+ * will call this function again until all hostnames in the test list are done.
+ *
+ * Note: This test also uses an IP literal for the hostname. This should be ok,
+ * as the socket layer will ask for the hostname to be resolved anyway, and DNS
+ * code should return a numerical version of the address internally.
+ */
+function test_hostnames_resolving_to_local_addresses() {
+ if (hostIdx >= localIPLiterals.length) {
+ // No more local IP addresses; move on.
+ next_test();
+ return;
+ }
+ var host = localIPLiterals[hostIdx++];
+ // Test another local IP address when the current one is done.
+ var next = test_hostnames_resolving_to_local_addresses;
+ test_hostnames_resolving_to_addresses(host, next);
+}
+
+/** test_speculative_connect_with_host_list
+ *
+ * Common test function for resolved proxy hosts. Takes a list of hosts, a
+ * boolean to determine if the test is expected to succeed or fail, and a
+ * function to call the next test case.
+ */
+function test_proxies(proxyHost, next) {
+ do_print("Proxy: " + proxyHost);
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+ do_check_neq(typeof(sts), undefined);
+ var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService();
+ do_check_neq(typeof(pps), undefined);
+
+ var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, 0, 1, null);
+ do_check_neq(typeof(proxyInfo), undefined);
+
+ var transport = sts.createTransport(null, 0, "dummyHost", 80, proxyInfo);
+ do_check_neq(typeof(transport), undefined);
+
+ transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
+
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
+ do_check_eq(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
+
+ var outStream = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED,0,0);
+ var inStream = transport.openInputStream(0,0,0);
+ do_check_neq(typeof(outStream), undefined);
+ do_check_neq(typeof(inStream), undefined);
+
+ var callback = new TestFailedStreamCallback(transport, proxyHost, next);
+ do_check_neq(typeof(callback), undefined);
+
+ // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
+ // adds callback to ns*StreamReadyEvent on main thread, and doesn't
+ // addref off the main thread.
+ var gThreadManager = Cc["@mozilla.org/thread-manager;1"]
+ .getService(Ci.nsIThreadManager);
+ var mainThread = gThreadManager.currentThread;
+
+ try {
+ outStream.QueryInterface(Ci.nsIAsyncOutputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ inStream.QueryInterface(Ci.nsIAsyncInputStream)
+ .asyncWait(callback, 0, 0, mainThread);
+ } catch (e) {
+ do_throw("asyncWait should not fail!");
+ }
+}
+
+/**
+ * test_proxies_with_local_addresses
+ *
+ * Creates an nsISocketTransport and simulates a speculative connect request
+ * for a proxy that resolves to a local IP address.
+ * Runs asynchronously; on test success (i.e. failure to connect), the callback
+ * will call this function again until all proxies in the test list are done.
+ *
+ * Note: This test also uses an IP literal for the proxy. This should be ok,
+ * as the socket layer will ask for the proxy to be resolved anyway, and DNS
+ * code should return a numerical version of the address internally.
+ */
+function test_proxies_with_local_addresses() {
+ if (hostIdx >= localIPLiterals.length) {
+ // No more local IP addresses; move on.
+ next_test();
+ return;
+ }
+ var host = localIPLiterals[hostIdx++];
+ // Test another local IP address when the current one is done.
+ var next = test_proxies_with_local_addresses;
+ test_proxies(host, next);
+}
+
+/** next_test
+ *
+ * Calls the next test in testList. Each test is responsible for calling this
+ * function when its test cases are complete.
+ */
+function next_test() {
+ if (testIdx >= testList.length) {
+ // No more tests; we're done.
+ do_test_finished();
+ return;
+ }
+ do_print("SpeculativeConnect: " + testDescription[testIdx]);
+ hostIdx = 0;
+ // Start next test in list.
+ testList[testIdx++]();
+}
+
+/** run_test
+ *
+ * Main entry function for test execution.
+ */
+function run_test() {
+ ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ do_test_pending();
+ next_test();
+}
+
diff --git a/netwerk/test/unit/test_standardurl.js b/netwerk/test/unit/test_standardurl.js
new file mode 100644
index 000000000..c4d44f41f
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl.js
@@ -0,0 +1,455 @@
+"use strict";
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+const nsIStandardURL = Components.interfaces.nsIStandardURL;
+
+function symmetricEquality(expect, a, b)
+{
+ /* Use if/else instead of |do_check_eq(expect, a.spec == b.spec)| so
+ that we get the specs output on the console if the check fails.
+ */
+ if (expect) {
+ /* Check all the sub-pieces too, since that can help with
+ debugging cases when equals() returns something unexpected */
+ /* We don't check port in the loop, because it can be defaulted in
+ some cases. */
+ ["spec", "prePath", "scheme", "userPass", "username", "password",
+ "hostPort", "host", "path", "filePath", "query",
+ "ref", "directory", "fileName", "fileBaseName", "fileExtension"]
+ .map(function(prop) {
+ dump("Testing '"+ prop + "'\n");
+ do_check_eq(a[prop], b[prop]);
+ });
+ } else {
+ do_check_neq(a.spec, b.spec);
+ }
+ do_check_eq(expect, a.equals(b));
+ do_check_eq(expect, b.equals(a));
+}
+
+function stringToURL(str) {
+ return (new StandardURL(nsIStandardURL.URLTYPE_AUTHORITY, 80,
+ str, "UTF-8", null))
+ .QueryInterface(Components.interfaces.nsIURL);
+}
+
+function pairToURLs(pair) {
+ do_check_eq(pair.length, 2);
+ return pair.map(stringToURL);
+}
+
+add_test(function test_setEmptyPath()
+{
+ var pairs =
+ [
+ ["http://example.com", "http://example.com/tests/dom/tests"],
+ ["http://example.com:80", "http://example.com/tests/dom/tests"],
+ ["http://example.com:80/", "http://example.com/tests/dom/test"],
+ ["http://example.com/", "http://example.com/tests/dom/tests"],
+ ["http://example.com/a", "http://example.com/tests/dom/tests"],
+ ["http://example.com:80/a", "http://example.com/tests/dom/tests"],
+ ].map(pairToURLs);
+
+ for (var [provided, target] of pairs)
+ {
+ symmetricEquality(false, target, provided);
+
+ provided.path = "";
+ target.path = "";
+
+ do_check_eq(provided.spec, target.spec);
+ symmetricEquality(true, target, provided);
+ }
+ run_next_test();
+});
+
+add_test(function test_setQuery()
+{
+ var pairs =
+ [
+ ["http://example.com", "http://example.com/?foo"],
+ ["http://example.com/bar", "http://example.com/bar?foo"],
+ ["http://example.com#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/?longerthanfoo#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/?longerthanfoo", "http://example.com/?foo"],
+ /* And one that's nonempty but shorter than "foo" */
+ ["http://example.com/?f#bar", "http://example.com/?foo#bar"],
+ ["http://example.com/?f", "http://example.com/?foo"],
+ ].map(pairToURLs);
+
+ for (var [provided, target] of pairs) {
+ symmetricEquality(false, provided, target);
+
+ provided.query = "foo";
+
+ do_check_eq(provided.spec, target.spec);
+ symmetricEquality(true, provided, target);
+ }
+
+ [provided, target] =
+ ["http://example.com/#", "http://example.com/?foo#bar"].map(stringToURL);
+ symmetricEquality(false, provided, target);
+ provided.query = "foo";
+ symmetricEquality(false, provided, target);
+
+ var newProvided = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI("#bar", null, provided)
+ .QueryInterface(Components.interfaces.nsIURL);
+
+ do_check_eq(newProvided.spec, target.spec);
+ symmetricEquality(true, newProvided, target);
+ run_next_test();
+});
+
+add_test(function test_setRef()
+{
+ var tests =
+ [
+ ["http://example.com", "", "http://example.com/"],
+ ["http://example.com:80", "", "http://example.com:80/"],
+ ["http://example.com:80/", "", "http://example.com:80/"],
+ ["http://example.com/", "", "http://example.com/"],
+ ["http://example.com/a", "", "http://example.com/a"],
+ ["http://example.com:80/a", "", "http://example.com:80/a"],
+
+ ["http://example.com", "x", "http://example.com/#x"],
+ ["http://example.com:80", "x", "http://example.com:80/#x"],
+ ["http://example.com:80/", "x", "http://example.com:80/#x"],
+ ["http://example.com/", "x", "http://example.com/#x"],
+ ["http://example.com/a", "x", "http://example.com/a#x"],
+ ["http://example.com:80/a", "x", "http://example.com:80/a#x"],
+
+ ["http://example.com", "xx", "http://example.com/#xx"],
+ ["http://example.com:80", "xx", "http://example.com:80/#xx"],
+ ["http://example.com:80/", "xx", "http://example.com:80/#xx"],
+ ["http://example.com/", "xx", "http://example.com/#xx"],
+ ["http://example.com/a", "xx", "http://example.com/a#xx"],
+ ["http://example.com:80/a", "xx", "http://example.com:80/a#xx"],
+
+ ["http://example.com", "xxxxxxxxxxxxxx", "http://example.com/#xxxxxxxxxxxxxx"],
+ ["http://example.com:80", "xxxxxxxxxxxxxx", "http://example.com:80/#xxxxxxxxxxxxxx"],
+ ["http://example.com:80/", "xxxxxxxxxxxxxx", "http://example.com:80/#xxxxxxxxxxxxxx"],
+ ["http://example.com/", "xxxxxxxxxxxxxx", "http://example.com/#xxxxxxxxxxxxxx"],
+ ["http://example.com/a", "xxxxxxxxxxxxxx", "http://example.com/a#xxxxxxxxxxxxxx"],
+ ["http://example.com:80/a", "xxxxxxxxxxxxxx", "http://example.com:80/a#xxxxxxxxxxxxxx"],
+ ];
+
+ for (var [before, ref, result] of tests)
+ {
+ /* Test1: starting with empty ref */
+ var a = stringToURL(before);
+ a.ref = ref;
+ var b = stringToURL(result);
+
+ do_check_eq(a.spec, b.spec);
+ do_check_eq(ref, b.ref);
+ symmetricEquality(true, a, b);
+
+ /* Test2: starting with non-empty */
+ a.ref = "yyyy";
+ var c = stringToURL(before);
+ c.ref = "yyyy";
+ symmetricEquality(true, a, c);
+
+ /* Test3: reset the ref */
+ a.ref = "";
+ symmetricEquality(true, a, stringToURL(before));
+
+ /* Test4: verify again after reset */
+ a.ref = ref;
+ symmetricEquality(true, a, b);
+ }
+ run_next_test();
+});
+
+// Bug 960014 - Make nsStandardURL::SetHost less magical around IPv6
+add_test(function test_ipv6()
+{
+ var url = stringToURL("http://example.com");
+ url.host = "[2001::1]";
+ do_check_eq(url.host, "2001::1");
+
+ url = stringToURL("http://example.com");
+ url.hostPort = "[2001::1]:30";
+ do_check_eq(url.host, "2001::1");
+ do_check_eq(url.port, 30);
+ do_check_eq(url.hostPort, "[2001::1]:30");
+
+ url = stringToURL("http://example.com");
+ url.hostPort = "2001:1";
+ do_check_eq(url.host, "0.0.7.209");
+ do_check_eq(url.port, 1);
+ do_check_eq(url.hostPort, "0.0.7.209:1");
+ run_next_test();
+});
+
+add_test(function test_ipv6_fail()
+{
+ var url = stringToURL("http://example.com");
+
+ Assert.throws(() => { url.host = "2001::1"; }, "missing brackets");
+ Assert.throws(() => { url.host = "[2001::1]:20"; }, "url.host with port");
+ Assert.throws(() => { url.host = "[2001::1"; }, "missing last bracket");
+ Assert.throws(() => { url.host = "2001::1]"; }, "missing first bracket");
+ Assert.throws(() => { url.host = "2001[::1]"; }, "bad bracket position");
+ Assert.throws(() => { url.host = "[]"; }, "empty IPv6 address");
+ Assert.throws(() => { url.host = "[hello]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.host = "[192.168.1.1]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "2001::1"; }, "missing brackets");
+ Assert.throws(() => { url.hostPort = "[2001::1]30"; }, "missing : after IP");
+ Assert.throws(() => { url.hostPort = "[2001:1]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1]10"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1]10:20"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1]:10:20"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "2001]:1"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "2001:1]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = ""; }, "Empty hostPort should fail");
+ Assert.throws(() => { url.hostPort = "[2001::1]:"; }, "missing port number");
+ Assert.throws(() => { url.hostPort = "[2001::1]:bad"; }, "bad port number");
+ run_next_test();
+});
+
+add_test(function test_clearedSpec()
+{
+ var url = stringToURL("http://example.com/path");
+ Assert.throws(() => { url.spec = "http: example"; }, "set bad spec");
+ Assert.throws(() => { url.spec = ""; }, "set empty spec");
+ do_check_eq(url.spec, "http://example.com/path");
+ url.host = "allizom.org";
+
+ var ref = stringToURL("http://allizom.org/path");
+ symmetricEquality(true, url, ref);
+ run_next_test();
+});
+
+add_test(function test_escapeBrackets()
+{
+ // Query
+ var url = stringToURL("http://example.com/?a[x]=1");
+ do_check_eq(url.spec, "http://example.com/?a[x]=1");
+
+ url = stringToURL("http://example.com/?a%5Bx%5D=1");
+ do_check_eq(url.spec, "http://example.com/?a%5Bx%5D=1");
+
+ url = stringToURL("http://[2001::1]/?a[x]=1");
+ do_check_eq(url.spec, "http://[2001::1]/?a[x]=1");
+
+ url = stringToURL("http://[2001::1]/?a%5Bx%5D=1");
+ do_check_eq(url.spec, "http://[2001::1]/?a%5Bx%5D=1");
+
+ // Path
+ url = stringToURL("http://example.com/brackets[x]/test");
+ do_check_eq(url.spec, "http://example.com/brackets[x]/test");
+
+ url = stringToURL("http://example.com/a%5Bx%5D/test");
+ do_check_eq(url.spec, "http://example.com/a%5Bx%5D/test");
+ run_next_test();
+});
+
+add_test(function test_apostropheEncoding()
+{
+ // For now, single quote is escaped everywhere _except_ the path.
+ // This policy is controlled by the bitmask in nsEscape.cpp::EscapeChars[]
+ var url = stringToURL("http://example.com/dir'/file'.ext'");
+ do_check_eq(url.spec, "http://example.com/dir'/file'.ext'");
+ run_next_test();
+});
+
+add_test(function test_accentEncoding()
+{
+ var url = stringToURL("http://example.com/?hello=`");
+ do_check_eq(url.spec, "http://example.com/?hello=`");
+ do_check_eq(url.query, "hello=`");
+
+ url = stringToURL("http://example.com/?hello=%2C");
+ do_check_eq(url.spec, "http://example.com/?hello=%2C");
+ do_check_eq(url.query, "hello=%2C");
+ run_next_test();
+});
+
+add_test(function test_percentDecoding()
+{
+ var url = stringToURL("http://%70%61%73%74%65%62%69%6E.com");
+ do_check_eq(url.spec, "http://pastebin.com/");
+
+ // We shouldn't unescape characters that are not allowed in the hostname.
+ url = stringToURL("http://example.com%0a%23.google.com/");
+ do_check_eq(url.spec, "http://example.com%0a%23.google.com/");
+ run_next_test();
+});
+
+add_test(function test_hugeStringThrows()
+{
+ let prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService);
+ let maxLen = prefs.getIntPref("network.standard-url.max-length");
+ let url = stringToURL("http://test:test@example.com");
+
+ let hugeString = new Array(maxLen + 1).fill("a").join("");
+ let properties = ["spec", "scheme", "userPass", "username",
+ "password", "hostPort", "host", "path", "ref",
+ "query", "fileName", "filePath", "fileBaseName", "fileExtension"];
+ for (let prop of properties) {
+ Assert.throws(() => url[prop] = hugeString,
+ /NS_ERROR_MALFORMED_URI/,
+ `Passing a huge string to "${prop}" should throw`);
+ }
+
+ run_next_test();
+});
+
+add_test(function test_filterWhitespace()
+{
+ var url = stringToURL(" \r\n\th\nt\rt\tp://ex\r\n\tample.com/path\r\n\t/\r\n\tto the/fil\r\n\te.e\r\n\txt?que\r\n\try#ha\r\n\tsh \r\n\t ");
+ do_check_eq(url.spec, "http://example.com/path/to%20the/file.ext?query#hash");
+
+ // These setters should escape \r\n\t, not filter them.
+ var url = stringToURL("http://test.com/path?query#hash");
+ url.filePath = "pa\r\n\tth";
+ do_check_eq(url.spec, "http://test.com/pa%0D%0A%09th?query#hash");
+ url.query = "qu\r\n\tery";
+ do_check_eq(url.spec, "http://test.com/pa%0D%0A%09th?qu%0D%0A%09ery#hash");
+ url.ref = "ha\r\n\tsh";
+ do_check_eq(url.spec, "http://test.com/pa%0D%0A%09th?qu%0D%0A%09ery#ha%0D%0A%09sh");
+ url.fileName = "fi\r\n\tle.name";
+ do_check_eq(url.spec, "http://test.com/fi%0D%0A%09le.name?qu%0D%0A%09ery#ha%0D%0A%09sh");
+
+ run_next_test();
+});
+
+add_test(function test_backslashReplacement()
+{
+ var url = stringToURL("http:\\\\test.com\\path/to\\file?query\\backslash#hash\\");
+ do_check_eq(url.spec, "http://test.com/path/to/file?query\\backslash#hash\\");
+
+ url = stringToURL("http:\\\\test.com\\example.org/path\\to/file");
+ do_check_eq(url.spec, "http://test.com/example.org/path/to/file");
+ do_check_eq(url.host, "test.com");
+ do_check_eq(url.path, "/example.org/path/to/file");
+
+ run_next_test();
+});
+
+add_test(function test_trim_C0_and_space()
+{
+ var url = stringToURL("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f http://example.com/ \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ");
+ do_check_eq(url.spec, "http://example.com/");
+ url.spec = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f http://test.com/ \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ";
+ do_check_eq(url.spec, "http://test.com/");
+ Assert.throws(() => { url.spec = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19 "; }, "set empty spec");
+ run_next_test();
+});
+
+// This tests that C0-and-space characters in the path, query and ref are
+// percent encoded.
+add_test(function test_encode_C0_and_space()
+{
+ function toHex(d) {
+ var hex = d.toString(16);
+ if (hex.length == 1)
+ hex = "0"+hex;
+ return hex.toUpperCase();
+ }
+
+ for (var i=0x0; i<=0x20; i++) {
+ // These characters get filtered - they are not encoded.
+ if (String.fromCharCode(i) == '\r' ||
+ String.fromCharCode(i) == '\n' ||
+ String.fromCharCode(i) == '\t') {
+ continue;
+ }
+ var url = stringToURL("http://example.com/pa" + String.fromCharCode(i) + "th?qu" + String.fromCharCode(i) +"ery#ha" + String.fromCharCode(i) + "sh");
+ do_check_eq(url.spec, "http://example.com/pa%" + toHex(i) + "th?qu%" + toHex(i) + "ery#ha%" + toHex(i) + "sh");
+ }
+
+ // Additionally, we need to check the setters.
+ var url = stringToURL("http://example.com/path?query#hash");
+ url.filePath = "pa\0th";
+ do_check_eq(url.spec, "http://example.com/pa%00th?query#hash");
+ url.query = "qu\0ery";
+ do_check_eq(url.spec, "http://example.com/pa%00th?qu%00ery#hash");
+ url.ref = "ha\0sh";
+ do_check_eq(url.spec, "http://example.com/pa%00th?qu%00ery#ha%00sh");
+ url.fileName = "fi\0le.name";
+ do_check_eq(url.spec, "http://example.com/fi%00le.name?qu%00ery#ha%00sh");
+
+ run_next_test();
+});
+
+add_test(function test_ipv4Normalize()
+{
+ var localIPv4s =
+ ["http://127.0.0.1",
+ "http://127.0.1",
+ "http://127.1",
+ "http://2130706433",
+ "http://0177.00.00.01",
+ "http://0177.00.01",
+ "http://0177.01",
+ "http://00000000000000000000000000177.0000000.0000000.0001",
+ "http://000000177.0000001",
+ "http://017700000001",
+ "http://0x7f.0x00.0x00.0x01",
+ "http://0x7f.0x01",
+ "http://0x7f000001",
+ "http://0x007f.0x0000.0x0000.0x0001",
+ "http://000177.0.00000.0x0001",
+ "http://127.0.0.1.",
+ ].map(stringToURL);
+
+ var url;
+ for (url of localIPv4s) {
+ do_check_eq(url.spec, "http://127.0.0.1/");
+ }
+
+ // These should treated as a domain instead of an IPv4.
+ var nonIPv4s =
+ ["http://0xfffffffff/",
+ "http://0x100000000/",
+ "http://4294967296/",
+ "http://1.2.0x10000/",
+ "http://1.0x1000000/",
+ "http://256.0.0.1/",
+ "http://1.256.1/",
+ "http://-1.0.0.0/",
+ "http://1.2.3.4.5/",
+ "http://010000000000000000/",
+ "http://2+3/",
+ "http://0.0.0.-1/",
+ "http://1.2.3.4../",
+ "http://1..2/",
+ "http://.1.2.3.4/",
+ "resource://123/",
+ "resource://4294967296/",
+ ];
+ var spec;
+ for (spec of nonIPv4s) {
+ url = stringToURL(spec);
+ do_check_eq(url.spec, spec);
+ }
+
+ var url = stringToURL("resource://path/to/resource/");
+ url.host = "123";
+ do_check_eq(url.host, "123");
+
+ run_next_test();
+});
+
+add_test(function test_invalidHostChars() {
+ var url = stringToURL("http://example.org/");
+ for (let i = 0; i <= 0x20; i++) {
+ Assert.throws(() => { url.host = "a" + String.fromCharCode(i) + "b"; }, "Trying to set hostname containing char code: " + i);
+ }
+ for (let c of "@[]*<>|:\"") {
+ Assert.throws(() => { url.host = "a" + c; }, "Trying to set hostname containing char: " + c);
+ }
+
+ // It also can't contain /, \, #, ?, but we treat these characters as
+ // hostname separators, so there is no way to set them and fail.
+ run_next_test();
+});
diff --git a/netwerk/test/unit/test_standardurl_default_port.js b/netwerk/test/unit/test_standardurl_default_port.js
new file mode 100644
index 000000000..12c619143
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_default_port.js
@@ -0,0 +1,51 @@
+/* -*- Mode: javascript; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This test exercises the nsIStandardURL "setDefaultPort" API. */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+function run_test() {
+ function stringToURL(str) {
+ return (new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 80,
+ str, "UTF-8", null))
+ .QueryInterface(Ci.nsIStandardURL);
+ }
+
+ // Create a nsStandardURL:
+ var origUrlStr = "http://foo.com/";
+ var stdUrl = stringToURL(origUrlStr);
+ var stdUrlAsUri = stdUrl.QueryInterface(Ci.nsIURI);
+ do_check_eq(-1, stdUrlAsUri.port);
+
+ // Changing default port shouldn't adjust the value returned by "port",
+ // or the string representation.
+ stdUrl.setDefaultPort(100);
+ do_check_eq(-1, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.spec, origUrlStr);
+
+ // Changing port directly should update .port and .spec, though:
+ stdUrlAsUri.port = "200";
+ do_check_eq(200, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.spec, "http://foo.com:200/");
+
+ // ...but then if we change default port to match the custom port,
+ // the custom port should reset to -1 and disappear from .spec:
+ stdUrl.setDefaultPort(200);
+ do_check_eq(-1, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.spec, origUrlStr);
+
+ // And further changes to default port should not make custom port reappear.
+ stdUrl.setDefaultPort(300);
+ do_check_eq(-1, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.spec, origUrlStr);
+}
diff --git a/netwerk/test/unit/test_standardurl_port.js b/netwerk/test/unit/test_standardurl_port.js
new file mode 100644
index 000000000..cc0016964
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_port.js
@@ -0,0 +1,56 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ function makeURI(aURLSpec, aCharset) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(aURLSpec, aCharset, null);
+ }
+
+ var httpURI = makeURI("http://foo.com");
+ do_check_eq(-1, httpURI.port);
+
+ // Setting to default shouldn't cause a change
+ httpURI.port = 80;
+ do_check_eq(-1, httpURI.port);
+
+ // Setting to default after setting to non-default shouldn't cause a change (bug 403480)
+ httpURI.port = 123;
+ do_check_eq(123, httpURI.port);
+ httpURI.port = 80;
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ // URL parsers shouldn't set ports to default value (bug 407538)
+ httpURI.spec = "http://foo.com:81";
+ do_check_eq(81, httpURI.port);
+ httpURI.spec = "http://foo.com:80";
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com:80");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com:80");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("https://foo.com");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/443/.test(httpURI.spec));
+
+ httpURI = makeURI("https://foo.com:443");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/443/.test(httpURI.spec));
+
+ // XXX URL parsers shouldn't set ports to default value, even when changing scheme?
+ // not really possible given current nsIURI impls
+ //httpURI.spec = "https://foo.com:443";
+ //do_check_eq(-1, httpURI.port);
+}
diff --git a/netwerk/test/unit/test_streamcopier.js b/netwerk/test/unit/test_streamcopier.js
new file mode 100644
index 000000000..6354be5d2
--- /dev/null
+++ b/netwerk/test/unit/test_streamcopier.js
@@ -0,0 +1,53 @@
+var testStr = "This is a test. ";
+for (var i = 0; i < 10; ++i) {
+ testStr += testStr;
+}
+
+function run_test() {
+ // Set up our stream to copy
+ var inStr = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ inStr.setData(testStr, testStr.length);
+
+ // Set up our destination stream. Make sure to use segments a good
+ // bit smaller than our data length.
+ do_check_true(testStr.length > 1024*10);
+ var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 1024, 0xffffffff, null);
+
+ var streamCopier = Cc["@mozilla.org/network/async-stream-copier;1"]
+ .createInstance(Ci.nsIAsyncStreamCopier);
+ streamCopier.init(inStr, pipe.outputStream, null, true, true, 1024, true, true);
+
+ var ctx = {
+ };
+ ctx.wrappedJSObject = ctx;
+
+ var observer = {
+ onStartRequest: function(aRequest, aContext) {
+ do_check_eq(aContext.wrappedJSObject, ctx);
+ },
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ do_check_eq(aStatusCode, 0);
+ do_check_eq(aContext.wrappedJSObject, ctx);
+ var sis =
+ Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(pipe.inputStream);
+ var result = "";
+ var temp;
+ try { // Need this because read() can throw at EOF
+ while ((temp = sis.read(1024))) {
+ result += temp;
+ }
+ } catch(e) {
+ do_check_eq(e.result, Components.results.NS_BASE_STREAM_CLOSED);
+ }
+ do_check_eq(result, testStr);
+ do_test_finished();
+ }
+ };
+
+ streamCopier.asyncCopy(observer, ctx);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_suspend_channel_before_connect.js b/netwerk/test/unit/test_suspend_channel_before_connect.js
new file mode 100644
index 000000000..f41932a46
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_before_connect.js
@@ -0,0 +1,102 @@
+
+var CC = Components.Constructor;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+
+var obs = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+// A server that waits for a connect. If a channel is suspended it should not
+// try to connect to the server until it is is resumed or not try at all if it
+// is cancelled as in this test.
+function TestServer() {
+ this.listener = ServerSocket(-1, true, -1);
+ this.port = this.listener.port;
+ this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+ onSocketAccepted: function(socket, trans) {
+ do_check_true(false, "Socket should not have tried to connect!");
+ },
+
+ onStopListening: function(socket) {
+ },
+
+ stop: function() {
+ try { this.listener.close(); } catch(ignore) {}
+ }
+}
+
+var requestListenerObserver = {
+
+ QueryInterface: function queryinterface(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIObserver))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic === "http-on-modify-request" &&
+ subject instanceof Ci.nsIHttpChannel) {
+
+ var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+ chan.suspend();
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.removeObserver(this, "http-on-modify-request");
+
+ // Timers are bad, but we need to wait to see that we are not trying to
+ // connect to the server. There are no other event since nothing should
+ // happen until we resume the channel.
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(() => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ chan.resume();
+ }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ }
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ do_execute_soon(run_next_test);
+ }
+};
+
+// Add observer and start a channel. Observer is going to suspend the channel on
+// "http-on-modify-request" even. If a channel is suspended so early it should
+// not try to connect at all until it is resumed. In this case we are going to
+// wait for some time and cancel the channel before resuming it.
+add_test(function testNoConnectChannelCanceledEarly() {
+
+ serv = new TestServer();
+
+ obs.addObserver(requestListenerObserver, "http-on-modify-request", false);
+ var chan = NetUtil.newChannel({
+ uri:"http://localhost:" + serv.port,
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(listener);
+
+ do_register_cleanup(function(){ serv.stop(); });
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_suspend_channel_on_modified.js b/netwerk/test/unit/test_suspend_channel_on_modified.js
new file mode 100644
index 000000000..a4f7c221e
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_modified.js
@@ -0,0 +1,175 @@
+// This file tests async handling of a channel suspended in http-on-modify-request.
+
+var CC = Components.Constructor;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var obs = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+// baseUrl is always the initial connection attempt and is handled by
+// failResponseHandler since every test expects that request will either be
+// redirected or cancelled.
+var baseUrl;
+
+function failResponseHandler(metadata, response)
+{
+ var text = "failure response";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(text, text.length);
+ do_check_true(false, "Received request when we shouldn't.");
+}
+
+function successResponseHandler(metadata, response)
+{
+ var text = "success response";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.bodyOutputStream.write(text, text.length);
+ do_check_true(true, "Received expected request.");
+}
+
+function onModifyListener(callback) {
+ obs.addObserver({
+ observe: function(subject, topic, data) {
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.removeObserver(this, "http-on-modify-request");
+ callback(subject.QueryInterface(Ci.nsIHttpChannel));
+ }
+ }, "http-on-modify-request", false);
+}
+
+function startChannelRequest(baseUrl, flags, expectedResponse=null) {
+ var chan = NetUtil.newChannel({
+ uri: baseUrl,
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(new ChannelListener((request, data, context) => {
+ if (expectedResponse) {
+ do_check_eq(data, expectedResponse);
+ } else {
+ do_check_true(!!!data, "no response");
+ }
+ do_execute_soon(run_next_test)
+ }, null, flags));
+}
+
+
+add_test(function testSimpleRedirect() {
+ onModifyListener(chan => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/success`));
+ });
+ startChannelRequest(baseUrl, undefined, "success response");
+});
+
+add_test(function testSimpleCancel() {
+ onModifyListener(chan => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+add_test(function testSimpleCancelRedirect() {
+ onModifyListener(chan => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/fail`));
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+// Test a request that will get redirected asynchronously. baseUrl should
+// not be requested, we should receive the request for the redirectedUrl.
+add_test(function testAsyncRedirect() {
+ onModifyListener(chan => {
+ // Suspend the channel then yield to make this async.
+ chan.suspend();
+ Promise.resolve().then(() => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/success`));
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, undefined, "success response");
+});
+
+add_test(function testSyncRedirect() {
+ onModifyListener(chan => {
+ chan.suspend();
+ chan.redirectTo(ios.newURI(`${baseUrl}/success`));
+ Promise.resolve().then(() => {
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, undefined, "success response");
+});
+
+add_test(function testAsyncCancel() {
+ onModifyListener(chan => {
+ // Suspend the channel then yield to make this async.
+ chan.suspend();
+ Promise.resolve().then(() => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+add_test(function testSyncCancel() {
+ onModifyListener(chan => {
+ chan.suspend();
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ Promise.resolve().then(() => {
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+// Test request that will get redirected and cancelled asynchronously,
+// ensure no connection is made.
+add_test(function testAsyncCancelRedirect() {
+ onModifyListener(chan => {
+ // Suspend the channel then yield to make this async.
+ chan.suspend();
+ Promise.resolve().then(() => {
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ chan.redirectTo(ios.newURI(`${baseUrl}/fail`));
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+// Test a request that will get cancelled synchronously, ensure async redirect
+// is not made.
+add_test(function testSyncCancelRedirect() {
+ onModifyListener(chan => {
+ chan.suspend();
+ chan.cancel(Cr.NS_BINDING_ABORTED);
+ Promise.resolve().then(() => {
+ chan.redirectTo(ios.newURI(`${baseUrl}/fail`));
+ chan.resume();
+ });
+ });
+ startChannelRequest(baseUrl, CL_EXPECT_FAILURE);
+});
+
+function run_test() {
+ var httpServer = new HttpServer();
+ httpServer.registerPathHandler("/", failResponseHandler);
+ httpServer.registerPathHandler("/fail", failResponseHandler);
+ httpServer.registerPathHandler("/success", successResponseHandler);
+ httpServer.start(-1);
+
+ baseUrl = `http://localhost:${httpServer.identity.primaryPort}`;
+
+ run_next_test();
+
+ do_register_cleanup(function(){
+ httpServer.stop(() => {});
+ });
+}
diff --git a/netwerk/test/unit/test_synthesized_response.js b/netwerk/test/unit/test_synthesized_response.js
new file mode 100644
index 000000000..bad8047fe
--- /dev/null
+++ b/netwerk/test/unit/test_synthesized_response.js
@@ -0,0 +1,243 @@
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(url, null, null);
+}
+
+// ensure the cache service is prepped when running the test
+Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+
+var gotOnProgress;
+var gotOnStatus;
+
+function make_channel(url, body, cb) {
+ gotOnProgress = false;
+ gotOnStatus = false;
+ var chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+ chan.notificationCallbacks = {
+ numChecks: 0,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterceptController,
+ Ci.nsIInterfaceRequestor,
+ Ci.nsIProgressEventSink]),
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+ onProgress: function(request, context, progress, progressMax) {
+ gotOnProgress = true;
+ },
+ onStatus: function(request, context, status, statusArg) {
+ gotOnStatus = true;
+ },
+ shouldPrepareForIntercept: function() {
+ do_check_eq(this.numChecks, 0);
+ this.numChecks++;
+ return true;
+ },
+ channelIntercepted: function(channel) {
+ channel.QueryInterface(Ci.nsIInterceptedChannel);
+ if (body) {
+ var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ synthesized.data = body;
+
+ NetUtil.asyncCopy(synthesized, channel.responseBody, function() {
+ channel.finishSynthesizedResponse('');
+ });
+ }
+ if (cb) {
+ cb(channel);
+ }
+ return {
+ dispatch: function() { }
+ };
+ },
+ };
+ return chan;
+}
+
+const REMOTE_BODY = "http handler body";
+const NON_REMOTE_BODY = "synthesized body";
+const NON_REMOTE_BODY_2 = "synthesized body #2";
+
+function bodyHandler(metadata, response) {
+ response.setHeader('Content-Type', 'text/plain');
+ response.write(REMOTE_BODY);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler('/body', bodyHandler);
+ httpServer.start(-1);
+
+ run_next_test();
+}
+
+function handle_synthesized_response(request, buffer) {
+ do_check_eq(buffer, NON_REMOTE_BODY);
+ do_check_true(gotOnStatus);
+ do_check_true(gotOnProgress);
+ run_next_test();
+}
+
+function handle_synthesized_response_2(request, buffer) {
+ do_check_eq(buffer, NON_REMOTE_BODY_2);
+ do_check_true(gotOnStatus);
+ do_check_true(gotOnProgress);
+ run_next_test();
+}
+
+function handle_remote_response(request, buffer) {
+ do_check_eq(buffer, REMOTE_BODY);
+ do_check_true(gotOnStatus);
+ do_check_true(gotOnProgress);
+ run_next_test();
+}
+
+// hit the network instead of synthesizing
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(chan) {
+ chan.resetInterception();
+ });
+ chan.asyncOpen2(new ChannelListener(handle_remote_response, null));
+});
+
+// synthesize a response
+add_test(function() {
+ var chan = make_channel(URL + '/body', NON_REMOTE_BODY);
+ chan.asyncOpen2(new ChannelListener(handle_synthesized_response, null, CL_ALLOW_UNKNOWN_CL));
+});
+
+// hit the network instead of synthesizing, to test that no previous synthesized
+// cache entry is used.
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(chan) {
+ chan.resetInterception();
+ });
+ chan.asyncOpen2(new ChannelListener(handle_remote_response, null));
+});
+
+// synthesize a different response to ensure no previous response is cached
+add_test(function() {
+ var chan = make_channel(URL + '/body', NON_REMOTE_BODY_2);
+ chan.asyncOpen2(new ChannelListener(handle_synthesized_response_2, null, CL_ALLOW_UNKNOWN_CL));
+});
+
+// ensure that the channel waits for a decision and synthesizes headers correctly
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(channel) {
+ do_timeout(100, function() {
+ var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+ NetUtil.asyncCopy(synthesized, channel.responseBody, function() {
+ channel.synthesizeHeader("Content-Length", NON_REMOTE_BODY.length);
+ channel.finishSynthesizedResponse('');
+ });
+ });
+ });
+ chan.asyncOpen2(new ChannelListener(handle_synthesized_response, null));
+});
+
+// ensure that the channel waits for a decision
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(chan) {
+ do_timeout(100, function() {
+ chan.resetInterception();
+ });
+ });
+ chan.asyncOpen2(new ChannelListener(handle_remote_response, null));
+});
+
+// ensure that the intercepted channel supports suspend/resume
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(intercepted) {
+ var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+
+ NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
+ // set the content-type to ensure that the stream converter doesn't hold up notifications
+ // and cause the test to fail
+ intercepted.synthesizeHeader("Content-Type", "text/plain");
+ intercepted.finishSynthesizedResponse('');
+ });
+ });
+ chan.asyncOpen2(new ChannelListener(handle_synthesized_response, null,
+ CL_ALLOW_UNKNOWN_CL | CL_SUSPEND | CL_EXPECT_3S_DELAY));
+});
+
+// ensure that the intercepted channel can be cancelled
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(intercepted) {
+ intercepted.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ chan.asyncOpen2(new ChannelListener(run_next_test, null, CL_EXPECT_FAILURE));
+});
+
+// ensure that the channel can't be cancelled via nsIInterceptedChannel after making a decision
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(chan) {
+ chan.resetInterception();
+ do_timeout(0, function() {
+ var gotexception = false;
+ try {
+ chan.cancel();
+ } catch (x) {
+ gotexception = true;
+ }
+ do_check_true(gotexception);
+ });
+ });
+ chan.asyncOpen2(new ChannelListener(handle_remote_response, null));
+});
+
+// ensure that the intercepted channel can be canceled during the response
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(intercepted) {
+ var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+
+ NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
+ let channel = intercepted.channel;
+ intercepted.finishSynthesizedResponse('');
+ channel.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ });
+ chan.asyncOpen2(new ChannelListener(run_next_test, null,
+ CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL));
+});
+
+// ensure that the intercepted channel can be canceled before the response
+add_test(function() {
+ var chan = make_channel(URL + '/body', null, function(intercepted) {
+ var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ synthesized.data = NON_REMOTE_BODY;
+
+ NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
+ intercepted.channel.cancel(Cr.NS_BINDING_ABORTED);
+ intercepted.finishSynthesizedResponse('');
+ });
+ });
+ chan.asyncOpen2(new ChannelListener(run_next_test, null,
+ CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL));
+});
+
+add_test(function() {
+ httpServer.stop(run_next_test);
+});
diff --git a/netwerk/test/unit/test_throttlechannel.js b/netwerk/test/unit/test_throttlechannel.js
new file mode 100644
index 000000000..97c119b99
--- /dev/null
+++ b/netwerk/test/unit/test_throttlechannel.js
@@ -0,0 +1,41 @@
+// Test nsIThrottledInputChannel interface.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function test_handler(metadata, response) {
+ const originalBody = "the response";
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+}
+
+function run_test() {
+ let httpserver = new HttpServer();
+ httpserver.start(-1);
+ const PORT = httpserver.identity.primaryPort;
+
+ httpserver.registerPathHandler("/testdir", test_handler);
+
+ let channel = make_channel("http://localhost:" + PORT + "/testdir");
+
+ let tq = Cc["@mozilla.org/network/throttlequeue;1"]
+ .createInstance(Ci.nsIInputChannelThrottleQueue);
+ tq.init(1000, 1000);
+
+ let tic = channel.QueryInterface(Ci.nsIThrottledInputChannel);
+ tic.throttleQueue = tq;
+
+ channel.asyncOpen2(new ChannelListener(() => {
+ ok(tq.bytesProcessed() > 0, "throttled queue processed some bytes");
+
+ httpserver.stop(do_test_finished);
+ }));
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_throttlequeue.js b/netwerk/test/unit/test_throttlequeue.js
new file mode 100644
index 000000000..fdfa80d1b
--- /dev/null
+++ b/netwerk/test/unit/test_throttlequeue.js
@@ -0,0 +1,23 @@
+// Test ThrottleQueue initialization.
+
+function init(tq, mean, max) {
+ let threw = false;
+ try {
+ tq.init(mean, max);
+ } catch (e) {
+ threw = true;
+ }
+ return !threw;
+}
+
+function run_test() {
+ let tq = Cc["@mozilla.org/network/throttlequeue;1"]
+ .createInstance(Ci.nsIInputChannelThrottleQueue);
+
+ ok(!init(tq, 0, 50), "mean bytes cannot be 0");
+ ok(!init(tq, 50, 0), "max bytes cannot be 0");
+ ok(!init(tq, 0, 0), "mean and max bytes cannot be 0");
+ ok(!init(tq, 70, 20), "max cannot be less than mean");
+
+ ok(init(tq, 2, 2), "valid initialization");
+}
diff --git a/netwerk/test/unit/test_throttling.js b/netwerk/test/unit/test_throttling.js
new file mode 100644
index 000000000..afb827894
--- /dev/null
+++ b/netwerk/test/unit/test_throttling.js
@@ -0,0 +1,57 @@
+// Test nsIThrottledInputChannel interface.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function test_handler(metadata, response) {
+ const originalBody = "the response";
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function run_test() {
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/testdir", test_handler);
+ httpserver.start(-1);
+
+ const PORT = httpserver.identity.primaryPort;
+ const size = 4096;
+
+ let sstream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ sstream.data = 'x'.repeat(size);
+
+ let mime = Cc["@mozilla.org/network/mime-input-stream;1"].
+ createInstance(Ci.nsIMIMEInputStream);
+ mime.addHeader("Content-Type", "multipart/form-data; boundary=zzzzz");
+ mime.setData(sstream);
+ mime.addContentLength = true;
+
+ let tq = Cc["@mozilla.org/network/throttlequeue;1"]
+ .createInstance(Ci.nsIInputChannelThrottleQueue);
+ // Make sure the request takes more than one read.
+ tq.init(100 + size / 2, 100 + size / 2);
+
+ let channel = make_channel("http://localhost:" + PORT + "/testdir");
+ channel.QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(mime, "", mime.available());
+ channel.requestMethod = "POST";
+
+ let tic = channel.QueryInterface(Ci.nsIThrottledInputChannel);
+ tic.throttleQueue = tq;
+
+ let startTime = Date.now();
+ channel.asyncOpen2(new ChannelListener(() => {
+ ok(Date.now() - startTime > 1000, "request took more than one second");
+
+ httpserver.stop(do_test_finished);
+ }));
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_tldservice_nextsubdomain.js b/netwerk/test/unit/test_tldservice_nextsubdomain.js
new file mode 100644
index 000000000..6f3170af0
--- /dev/null
+++ b/netwerk/test/unit/test_tldservice_nextsubdomain.js
@@ -0,0 +1,28 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ var tld = Cc["@mozilla.org/network/effective-tld-service;1"]
+ .getService(Ci.nsIEffectiveTLDService);
+
+ var tests = [
+ { data: "bar.foo.co.uk", result: "foo.co.uk" },
+ { data: "foo.bar.foo.co.uk", result: "bar.foo.co.uk" },
+ { data: "foo.co.uk", throw: true },
+ { data: "co.uk", throw: true },
+ { data: ".co.uk", throw: true },
+ { data: "com", throw: true },
+ { data: "tûlîp.foo.fr", result: "foo.fr" },
+ { data: "tûlîp.fôû.fr", result: "xn--f-xgav.fr" },
+ { data: "file://foo/bar", throw: true },
+ ];
+
+ tests.forEach(function(test) {
+ try {
+ var r = tld.getNextSubDomain(test.data);
+ do_check_eq(r, test.result);
+ } catch (e) {
+ do_check_true(test.throw);
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_tls_server.js b/netwerk/test/unit/test_tls_server.js
new file mode 100644
index 000000000..d805359c7
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server.js
@@ -0,0 +1,237 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Need profile dir to store the key / cert
+do_get_profile();
+// Ensure PSM is initialized
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+ Cu.import("resource://gre/modules/Promise.jsm", {});
+const certService = Cc["@mozilla.org/security/local-cert-service;1"]
+ .getService(Ci.nsILocalCertService);
+const certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+const socketTransportService =
+ Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+const prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+
+function run_test() {
+ run_next_test();
+}
+
+function getCert() {
+ let deferred = promise.defer();
+ certService.getOrCreateCert("tls-test", {
+ handleCert: function(c, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(c);
+ }
+ });
+ return deferred.promise;
+}
+
+function startServer(cert, expectingPeerCert, clientCertificateConfig,
+ expectedVersion, expectedVersionStr) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"]
+ .createInstance(Ci.nsITLSServerSocket);
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+
+ let input, output;
+
+ let listener = {
+ onSocketAccepted: function(socket, transport) {
+ do_print("Accept TLS client connection");
+ let connectionInfo = transport.securityInfo
+ .QueryInterface(Ci.nsITLSServerConnectionInfo);
+ connectionInfo.setSecurityObserver(listener);
+ input = transport.openInputStream(0, 0, 0);
+ output = transport.openOutputStream(0, 0, 0);
+ },
+ onHandshakeDone: function(socket, status) {
+ do_print("TLS handshake done");
+ if (expectingPeerCert) {
+ ok(!!status.peerCert, "Has peer cert");
+ ok(status.peerCert.equals(cert), "Peer cert matches expected cert");
+ } else {
+ ok(!status.peerCert, "No peer cert (as expected)");
+ }
+
+ equal(status.tlsVersionUsed, expectedVersion,
+ "Using " + expectedVersionStr);
+ let expectedCipher;
+ if (expectedVersion >= 772) {
+ expectedCipher = "TLS_AES_128_GCM_SHA256";
+ } else {
+ expectedCipher = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ }
+ equal(status.cipherName, expectedCipher,
+ "Using expected cipher");
+ equal(status.keyLength, 128, "Using 128-bit key");
+ equal(status.macLength, 128, "Using 128-bit MAC");
+
+ input.asyncWait({
+ onInputStreamReady: function(input) {
+ NetUtil.asyncCopy(input, output);
+ }
+ }, 0, 0, Services.tm.currentThread);
+ },
+ onStopListening: function() {}
+ };
+
+ tlsServer.setSessionCache(false);
+ tlsServer.setSessionTickets(false);
+ tlsServer.setRequestClientCertificate(clientCertificateConfig);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer.port;
+}
+
+function storeCertOverride(port, cert) {
+ let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride("127.0.0.1", port, cert,
+ overrideBits, true);
+}
+
+function startClient(port, cert, expectingBadCertAlert) {
+ let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
+ let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
+ let transport =
+ socketTransportService.createTransport(["ssl"], 1, "127.0.0.1", port, null);
+ let input;
+ let output;
+
+ let inputDeferred = promise.defer();
+ let outputDeferred = promise.defer();
+
+ let handler = {
+
+ onTransportStatus: function(transport, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady: function(input) {
+ try {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ equal(data, "HELLO", "Echoed data received");
+ input.close();
+ output.close();
+ ok(!expectingBadCertAlert, "No bad cert alert expected");
+ inputDeferred.resolve();
+ } catch (e) {
+ let errorCode = -1 * (e.result & 0xFFFF);
+ if (expectingBadCertAlert && errorCode == SSL_ERROR_BAD_CERT_ALERT) {
+ inputDeferred.resolve();
+ } else {
+ inputDeferred.reject(e);
+ }
+ }
+ },
+
+ onOutputStreamReady: function(output) {
+ try {
+ // Set the client certificate as appropriate.
+ if (cert) {
+ let clientSecInfo = transport.securityInfo;
+ let tlsControl = clientSecInfo.QueryInterface(Ci.nsISSLSocketControl);
+ tlsControl.clientCert = cert;
+ }
+
+ output.write("HELLO", 5);
+ do_print("Output to server written");
+ outputDeferred.resolve();
+ input = transport.openInputStream(0, 0, 0);
+ input.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ } catch (e) {
+ let errorCode = -1 * (e.result & 0xFFFF);
+ if (errorCode == SSL_ERROR_BAD_CERT_ALERT) {
+ do_print("Server doesn't like client cert");
+ }
+ outputDeferred.reject(e);
+ }
+ }
+
+ };
+
+ transport.setEventSink(handler, Services.tm.currentThread);
+ output = transport.openOutputStream(0, 0, 0);
+
+ return promise.all([inputDeferred.promise, outputDeferred.promise]);
+}
+
+// Replace the UI dialog that prompts the user to pick a client certificate.
+do_load_manifest("client_cert_chooser.manifest");
+
+const tests = [{
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS,
+ sendClientCert: true,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS,
+ sendClientCert: false,
+ expectingBadCertAlert: true
+}, {
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
+ sendClientCert: true,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
+ sendClientCert: false,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
+ sendClientCert: true,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
+ sendClientCert: false,
+ expectingBadCertAlert: false
+}];
+
+const versions = [{
+ prefValue: 3, version: Ci.nsITLSClientStatus.TLS_VERSION_1_2, versionStr: "TLS 1.2"
+}, {
+ prefValue: 4, version: Ci.nsITLSClientStatus.TLS_VERSION_1_3, versionStr: "TLS 1.3"
+}];
+
+add_task(function*() {
+ let cert = yield getCert();
+ ok(!!cert, "Got self-signed cert");
+ for (let v of versions) {
+ prefs.setIntPref("security.tls.version.max", v.prefValue);
+ for (let t of tests) {
+ let port = startServer(cert,
+ t.expectingPeerCert,
+ t.clientCertificateConfig,
+ v.version,
+ v.versionStr);
+ storeCertOverride(port, cert);
+ yield startClient(port, t.sendClientCert ? cert : null, t.expectingBadCertAlert);
+ }
+ }
+});
+
+do_register_cleanup(function() {
+ prefs.clearUserPref("security.tls.version.max");
+});
diff --git a/netwerk/test/unit/test_tls_server_multiple_clients.js b/netwerk/test/unit/test_tls_server_multiple_clients.js
new file mode 100644
index 000000000..b63c0189b
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server_multiple_clients.js
@@ -0,0 +1,141 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Need profile dir to store the key / cert
+do_get_profile();
+// Ensure PSM is initialized
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+ Cu.import("resource://gre/modules/Promise.jsm", {});
+const certService = Cc["@mozilla.org/security/local-cert-service;1"]
+ .getService(Ci.nsILocalCertService);
+const certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+const socketTransportService =
+ Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+function run_test() {
+ run_next_test();
+}
+
+function getCert() {
+ let deferred = promise.defer();
+ certService.getOrCreateCert("tls-test", {
+ handleCert: function(c, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(c);
+ }
+ });
+ return deferred.promise;
+}
+
+function startServer(cert) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"]
+ .createInstance(Ci.nsITLSServerSocket);
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+
+ let input, output;
+
+ let listener = {
+ onSocketAccepted: function(socket, transport) {
+ do_print("Accept TLS client connection");
+ let connectionInfo = transport.securityInfo
+ .QueryInterface(Ci.nsITLSServerConnectionInfo);
+ connectionInfo.setSecurityObserver(listener);
+ input = transport.openInputStream(0, 0, 0);
+ output = transport.openOutputStream(0, 0, 0);
+ },
+ onHandshakeDone: function(socket, status) {
+ do_print("TLS handshake done");
+
+ input.asyncWait({
+ onInputStreamReady: function(input) {
+ NetUtil.asyncCopy(input, output);
+ }
+ }, 0, 0, Services.tm.currentThread);
+ },
+ onStopListening: function() {}
+ };
+
+ tlsServer.setSessionCache(true);
+ tlsServer.setSessionTickets(false);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer.port;
+}
+
+function storeCertOverride(port, cert) {
+ let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride("127.0.0.1", port, cert,
+ overrideBits, true);
+}
+
+function startClient(port) {
+ let transport =
+ socketTransportService.createTransport(["ssl"], 1, "127.0.0.1", port, null);
+ let input;
+ let output;
+
+ let inputDeferred = promise.defer();
+ let outputDeferred = promise.defer();
+
+ let handler = {
+
+ onTransportStatus: function(transport, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady: function(input) {
+ try {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ equal(data, "HELLO", "Echoed data received");
+ input.close();
+ output.close();
+ inputDeferred.resolve();
+ } catch (e) {
+ inputDeferred.reject(e);
+ }
+ },
+
+ onOutputStreamReady: function(output) {
+ try {
+ output.write("HELLO", 5);
+ do_print("Output to server written");
+ outputDeferred.resolve();
+ input = transport.openInputStream(0, 0, 0);
+ input.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ } catch (e) {
+ outputDeferred.reject(e);
+ }
+ }
+
+ };
+
+ transport.setEventSink(handler, Services.tm.currentThread);
+ output = transport.openOutputStream(0, 0, 0);
+
+ return promise.all([inputDeferred.promise, outputDeferred.promise]);
+}
+
+add_task(function*() {
+ let cert = yield getCert();
+ ok(!!cert, "Got self-signed cert");
+ let port = startServer(cert);
+ storeCertOverride(port, cert);
+ yield startClient(port);
+ yield startClient(port);
+});
diff --git a/netwerk/test/unit/test_traceable_channel.js b/netwerk/test/unit/test_traceable_channel.js
new file mode 100644
index 000000000..00ccbb127
--- /dev/null
+++ b/netwerk/test/unit/test_traceable_channel.js
@@ -0,0 +1,150 @@
+// Test nsITraceableChannel interface.
+// Replace original listener with TracingListener that modifies body of HTTP
+// response. Make sure that body received by original channel's listener
+// is correctly modified.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+var pipe = null;
+var streamSink = null;
+
+var originalBody = "original http response body";
+var gotOnStartRequest = false;
+
+function TracingListener() {}
+
+TracingListener.prototype = {
+ onStartRequest: function(request, context) {
+ dump("*** tracing listener onStartRequest\n");
+
+ gotOnStartRequest = true;
+
+ request.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+
+// local/remote addresses broken in e10s: disable for now
+ do_check_eq(request.localAddress, "127.0.0.1");
+ do_check_eq(request.localPort > 0, true);
+ do_check_neq(request.localPort, PORT);
+ do_check_eq(request.remoteAddress, "127.0.0.1");
+ do_check_eq(request.remotePort, PORT);
+
+ // Make sure listener can't be replaced after OnStartRequest was called.
+ request.QueryInterface(Components.interfaces.nsITraceableChannel);
+ try {
+ var newListener = new TracingListener();
+ newListener.listener = request.setNewListener(newListener);
+ } catch(e) {
+ dump("TracingListener.onStartRequest swallowing exception: " + e + "\n");
+ return; // OK
+ }
+ do_throw("replaced channel's listener during onStartRequest.");
+ },
+
+ onStopRequest: function(request, context, statusCode) {
+ dump("*** tracing listener onStopRequest\n");
+
+ do_check_eq(gotOnStartRequest, true);
+
+ try {
+ var sin = Components.classes["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+
+ streamSink.close();
+ var input = pipe.inputStream;
+ sin.init(input);
+ do_check_eq(sin.available(), originalBody.length);
+
+ var result = sin.read(originalBody.length);
+ do_check_eq(result, originalBody);
+
+ input.close();
+ } catch (e) {
+ dump("TracingListener.onStopRequest swallowing exception: " + e + "\n");
+ } finally {
+ httpserver.stop(do_test_finished);
+ }
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports)
+ )
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ listener: null
+}
+
+
+function HttpResponseExaminer() {}
+
+HttpResponseExaminer.prototype = {
+ register: function() {
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Components.interfaces.nsIObserverService).
+ addObserver(this, "http-on-examine-response", true);
+ dump("Did HttpResponseExaminer.register\n");
+ },
+
+ // Replace channel's listener.
+ observe: function(subject, topic, data) {
+ dump("In HttpResponseExaminer.observe\n");
+ try {
+ subject.QueryInterface(Components.interfaces.nsITraceableChannel);
+
+ var tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
+ createInstance(Ci.nsIStreamListenerTee);
+ var newListener = new TracingListener();
+ pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ streamSink = pipe.outputStream;
+
+ var originalListener = subject.setNewListener(tee);
+ tee.init(originalListener, streamSink, newListener);
+ } catch(e) {
+ do_throw("can't replace listener " + e);
+ }
+ dump("Did HttpResponseExaminer.observe\n");
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIObserver) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+}
+
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(originalBody, originalBody.length);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+}
+
+// Check if received body is correctly modified.
+function channel_finished(request, input, ctx) {
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ var observer = new HttpResponseExaminer();
+ observer.register();
+
+ httpserver.registerPathHandler("/testdir", test_handler);
+
+ var channel = make_channel("http://localhost:" + PORT + "/testdir");
+ channel.asyncOpen2(new ChannelListener(channel_finished));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_udp_multicast.js b/netwerk/test/unit/test_udp_multicast.js
new file mode 100644
index 000000000..0afa9c5b2
--- /dev/null
+++ b/netwerk/test/unit/test_udp_multicast.js
@@ -0,0 +1,114 @@
+// Bug 960397: UDP multicast options
+
+var { Constructor: CC } = Components;
+
+const UDPSocket = CC("@mozilla.org/network/udp-socket;1",
+ "nsIUDPSocket",
+ "init");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+Cu.import("resource://gre/modules/Services.jsm");
+
+const ADDRESS_TEST1 = "224.0.0.200";
+const ADDRESS_TEST2 = "224.0.0.201";
+const ADDRESS_TEST3 = "224.0.0.202";
+const ADDRESS_TEST4 = "224.0.0.203";
+
+const TIMEOUT = 2000;
+
+const ua = Cc["@mozilla.org/network/protocol;1?name=http"]
+ .getService(Ci.nsIHttpProtocolHandler).userAgent;
+const isWinXP = ua.indexOf("Windows NT 5.1") != -1;
+
+var gConverter;
+
+function run_test() {
+ setup();
+ run_next_test();
+}
+
+function setup() {
+ gConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ gConverter.charset = "utf8";
+}
+
+function createSocketAndJoin(addr) {
+ let socket = new UDPSocket(-1, false,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ socket.joinMulticast(addr);
+ return socket;
+}
+
+function sendPing(socket, addr) {
+ let ping = "ping";
+ let rawPing = gConverter.convertToByteArray(ping);
+
+ let deferred = promise.defer();
+
+ socket.asyncListen({
+ onPacketReceived: function(s, message) {
+ do_print("Received on port " + socket.port);
+ do_check_eq(message.data, ping);
+ socket.close();
+ deferred.resolve(message.data);
+ },
+ onStopListening: function(socket, status) {}
+ });
+
+ do_print("Multicast send to port " + socket.port);
+ socket.send(addr, socket.port, rawPing, rawPing.length);
+
+ // Timers are bad, but it seems like the only way to test *not* getting a
+ // packet.
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(() => {
+ socket.close();
+ deferred.reject();
+ }, TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+
+ return deferred.promise;
+}
+
+add_test(() => {
+ do_print("Joining multicast group");
+ let socket = createSocketAndJoin(ADDRESS_TEST1);
+ sendPing(socket, ADDRESS_TEST1).then(
+ run_next_test,
+ () => do_throw("Joined group, but no packet received")
+ );
+});
+
+add_test(() => {
+ do_print("Disabling multicast loopback");
+ let socket = createSocketAndJoin(ADDRESS_TEST2);
+ socket.multicastLoopback = false;
+ sendPing(socket, ADDRESS_TEST2).then(
+ () => do_throw("Loopback disabled, but still got a packet"),
+ run_next_test
+ );
+});
+
+// The following multicast interface test doesn't work on Windows XP, as it
+// appears to allow packets no matter what address is given, so we'll skip the
+// test there.
+if (!isWinXP) {
+ add_test(() => {
+ do_print("Changing multicast interface");
+ let socket = createSocketAndJoin(ADDRESS_TEST3);
+ socket.multicastInterface = "127.0.0.1";
+ sendPing(socket, ADDRESS_TEST3).then(
+ () => do_throw("Changed interface, but still got a packet"),
+ run_next_test
+ );
+ });
+
+add_test(() => {
+ do_print("Leaving multicast group");
+ let socket = createSocketAndJoin(ADDRESS_TEST4);
+ socket.leaveMulticast(ADDRESS_TEST4);
+ sendPing(socket, ADDRESS_TEST4).then(
+ () => do_throw("Left group, but still got a packet"),
+ run_next_test
+ );
+});
+}
diff --git a/netwerk/test/unit/test_udpsocket.js b/netwerk/test/unit/test_udpsocket.js
new file mode 100644
index 000000000..c96be003a
--- /dev/null
+++ b/netwerk/test/unit/test_udpsocket.js
@@ -0,0 +1,63 @@
+/* -*- Mode: Javascript; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const HELLO_WORLD = "Hello World";
+
+add_test(function test_udp_message_raw_data() {
+ do_print("test for nsIUDPMessage.rawData");
+
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(Ci.nsIUDPSocket);
+
+ socket.init(-1, true, Services.scriptSecurityManager.getSystemPrincipal());
+ do_print("Port assigned : " + socket.port);
+ socket.asyncListen({
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIUDPSocketListener]),
+ onPacketReceived : function(aSocket, aMessage){
+ let recv_data = String.fromCharCode.apply(null, aMessage.rawData);
+ do_check_eq(recv_data, HELLO_WORLD);
+ do_check_eq(recv_data, aMessage.data);
+ socket.close();
+ run_next_test();
+ },
+ onStopListening: function(aSocket, aStatus){}
+ });
+
+ let rawData = new Uint8Array(HELLO_WORLD.length);
+ for (let i = 0; i < HELLO_WORLD.length; i++) {
+ rawData[i] = HELLO_WORLD.charCodeAt(i);
+ }
+ let written = socket.send("127.0.0.1", socket.port, rawData, rawData.length);
+ do_check_eq(written, HELLO_WORLD.length);
+});
+
+add_test(function test_udp_send_stream() {
+ do_print("test for nsIUDPSocket.sendBinaryStream");
+
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(Ci.nsIUDPSocket);
+
+ socket.init(-1, true, Services.scriptSecurityManager.getSystemPrincipal());
+ socket.asyncListen({
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIUDPSocketListener]),
+ onPacketReceived : function(aSocket, aMessage){
+ let recv_data = String.fromCharCode.apply(null, aMessage.rawData);
+ do_check_eq(recv_data, HELLO_WORLD);
+ socket.close();
+ run_next_test();
+ },
+ onStopListening: function(aSocket, aStatus){}
+ });
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ stream.setData(HELLO_WORLD, HELLO_WORLD.length);
+ socket.sendBinaryStream("127.0.0.1", socket.port, stream);
+});
+
+function run_test(){
+ run_next_test();
+}
+
diff --git a/netwerk/test/unit/test_unescapestring.js b/netwerk/test/unit/test_unescapestring.js
new file mode 100644
index 000000000..d1e9494cd
--- /dev/null
+++ b/netwerk/test/unit/test_unescapestring.js
@@ -0,0 +1,31 @@
+const ONLY_NONASCII = Components.interfaces.nsINetUtil.ESCAPE_URL_ONLY_NONASCII;
+const SKIP_CONTROL = Components.interfaces.nsINetUtil.ESCAPE_URL_SKIP_CONTROL;
+
+
+var tests = [
+ ["foo", "foo", 0],
+ ["foo%20bar", "foo bar", 0],
+ ["foo%2zbar", "foo%2zbar", 0],
+ ["foo%", "foo%", 0],
+ ["%zzfoo", "%zzfoo", 0],
+ ["foo%z", "foo%z", 0],
+ ["foo%00bar", "foo\x00bar", 0],
+ ["foo%ffbar", "foo\xffbar", 0],
+ ["%00%1b%20%61%7f%80%ff", "%00%1b%20%61%7f\x80\xff", ONLY_NONASCII],
+ ["%00%1b%20%61%7f%80%ff", "%00%1b a%7f\x80\xff", SKIP_CONTROL],
+ ["%00%1b%20%61%7f%80%ff", "%00%1b%20%61%7f\x80\xff", ONLY_NONASCII|SKIP_CONTROL],
+ // Test that we do not drop the high-bytes of a UTF-16 string.
+ ["\u30a8\u30c9", "\xe3\x82\xa8\xe3\x83\x89", 0],
+];
+
+function run_test() {
+ var util = Components.classes["@mozilla.org/network/util;1"]
+ .getService(Components.interfaces.nsINetUtil);
+
+ for (var i = 0; i < tests.length; ++i) {
+ dump("Test " + i + " (" + tests[i][0] + ", " + tests[i][2] + ")\n");
+ do_check_eq(util.unescapeString(tests[i][0], tests[i][2]),
+ tests[i][1]);
+ }
+ dump(tests.length + " tests passed\n");
+}
diff --git a/netwerk/test/unit/test_unix_domain.js b/netwerk/test/unit/test_unix_domain.js
new file mode 100644
index 000000000..5dda0c864
--- /dev/null
+++ b/netwerk/test/unit/test_unix_domain.js
@@ -0,0 +1,545 @@
+// Exercise Unix domain sockets.
+
+var CC = Components.Constructor;
+
+const UnixServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initWithFilename");
+
+const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init");
+
+const IOService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+const socketTransportService = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+const allPermissions = parseInt("777", 8);
+
+function run_test()
+{
+ // If we're on Windows, simply check for graceful failure.
+ if (mozinfo.os == "win") {
+ test_not_supported();
+ return;
+ }
+
+ add_test(test_echo);
+ add_test(test_name_too_long);
+ add_test(test_no_directory);
+ add_test(test_no_such_socket);
+ add_test(test_address_in_use);
+ add_test(test_file_in_way);
+ add_test(test_create_permission);
+ add_test(test_connect_permission);
+ add_test(test_long_socket_name);
+ add_test(test_keep_when_offline);
+
+ run_next_test();
+}
+
+// Check that creating a Unix domain socket fails gracefully on Windows.
+function test_not_supported()
+{
+ let socketName = do_get_tempdir();
+ socketName.append('socket');
+ do_print("creating socket: " + socketName.path);
+
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED");
+
+ do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED");
+}
+
+// Actually exchange data with Unix domain sockets.
+function test_echo()
+{
+ let log = '';
+
+ let socketName = do_get_tempdir();
+ socketName.append('socket');
+
+ // Create a server socket, listening for connections.
+ do_print("creating socket: " + socketName.path);
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+ server.asyncListen({
+ onSocketAccepted: function(aServ, aTransport) {
+ do_print("called test_echo's onSocketAccepted");
+ log += 'a';
+
+ do_check_eq(aServ, server);
+
+ let connection = aTransport;
+
+ // Check the server socket's self address.
+ let connectionSelfAddr = connection.getScriptableSelfAddr();
+ do_check_eq(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(connectionSelfAddr.address, socketName.path);
+
+ // The client socket is anonymous, so the server transport should
+ // have an empty peer address.
+ do_check_eq(connection.host, '');
+ do_check_eq(connection.port, 0);
+ let connectionPeerAddr = connection.getScriptablePeerAddr();
+ do_check_eq(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(connectionPeerAddr.address, '');
+
+ let serverAsyncInput = connection.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = connection.openOutputStream(0, 0, 0);
+
+ serverAsyncInput.asyncWait(function (aStream) {
+ do_print("called test_echo's server's onInputStreamReady");
+ let serverScriptableInput = new ScriptableInputStream(aStream);
+
+ // Receive data from the client, and send back a response.
+ do_check_eq(serverScriptableInput.readBytes(17), "Mervyn Murgatroyd");
+ do_print("server has read message from client");
+ serverOutput.write("Ruthven Murgatroyd", 18);
+ do_print("server has written to client");
+ }, 0, 0, threadManager.currentThread);
+ },
+
+ onStopListening: function(aServ, aStatus) {
+ do_print("called test_echo's onStopListening");
+ log += 's';
+
+ do_check_eq(aServ, server);
+ do_check_eq(log, 'acs');
+
+ run_next_test();
+ }
+ });
+
+ // Create a client socket, and connect to the server.
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ do_check_eq(client.host, socketName.path);
+ do_check_eq(client.port, 0);
+
+ let clientAsyncInput = client.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ let clientInput = new ScriptableInputStream(clientAsyncInput);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+
+ clientOutput.write("Mervyn Murgatroyd", 17);
+ do_print("client has written to server");
+
+ clientAsyncInput.asyncWait(function (aStream) {
+ do_print("called test_echo's client's onInputStreamReady");
+ log += 'c';
+
+ do_check_eq(aStream, clientAsyncInput);
+
+ // Now that the connection has been established, we can check the
+ // transport's self and peer addresses.
+ let clientSelfAddr = client.getScriptableSelfAddr();
+ do_check_eq(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(clientSelfAddr.address, '');
+
+ do_check_eq(client.host, socketName.path); // re-check, but hey
+ let clientPeerAddr = client.getScriptablePeerAddr();
+ do_check_eq(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(clientPeerAddr.address, socketName.path);
+
+ do_check_eq(clientInput.readBytes(18), "Ruthven Murgatroyd");
+ do_print("client has read message from server");
+
+ server.close();
+ }, 0, 0, threadManager.currentThread);
+}
+
+// Create client and server sockets using a path that's too long.
+function test_name_too_long()
+{
+ let socketName = do_get_tempdir();
+ // The length limits on all the systems NSPR supports are a bit past 100.
+ socketName.append(new Array(1000).join('x'));
+
+ // The length must be checked before we ever make any system calls --- we
+ // have to create the sockaddr first --- so it's unambiguous which error
+ // we should get here.
+
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NAME_TOO_LONG");
+
+ // Unlike most other client socket errors, this one gets reported
+ // immediately, as we can't even initialize the sockaddr with the given
+ // name.
+ do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_FILE_NAME_TOO_LONG");
+
+ run_next_test();
+}
+
+// Try creating a socket in a directory that doesn't exist.
+function test_no_directory()
+{
+ let socketName = do_get_tempdir();
+ socketName.append('directory-that-does-not-exist');
+ socketName.append('socket');
+
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NOT_FOUND");
+
+ run_next_test();
+}
+
+// Try connecting to a server socket that isn't there.
+function test_no_such_socket()
+{
+ let socketName = do_get_tempdir();
+ socketName.append('nonexistent-socket');
+
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ let clientAsyncInput = client.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ clientAsyncInput.asyncWait(function (aStream) {
+ do_print("called test_no_such_socket's onInputStreamReady");
+
+ do_check_eq(aStream, clientAsyncInput);
+
+ // nsISocketTransport puts off actually creating sockets as long as
+ // possible, so the error in connecting doesn't actually show up until
+ // this point.
+ do_check_throws_nsIException(() => clientAsyncInput.available(),
+ "NS_ERROR_FILE_NOT_FOUND");
+
+ clientAsyncInput.close();
+ client.close(Cr.NS_OK);
+
+ run_next_test();
+ }, 0, 0, threadManager.currentThread);
+}
+
+// Creating a socket with a name that another socket is already using is an
+// error.
+function test_address_in_use()
+{
+ let socketName = do_get_tempdir();
+ socketName.append('socket-in-use');
+
+ // Create one server socket.
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+
+ // Now try to create another with the same name.
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE");
+
+ run_next_test();
+}
+
+// Creating a socket with a name that is already a file is an error.
+function test_file_in_way()
+{
+ let socketName = do_get_tempdir();
+ socketName.append('file_in_way');
+
+ // Create a file with the given name.
+ socketName.create(Ci.nsIFile.NORMAL_FILE_TYPE, allPermissions);
+
+ // Try to create a socket with the same name.
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE");
+
+ // Try to create a socket under a name that uses that as a parent directory.
+ socketName.append('socket');
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NOT_DIRECTORY");
+
+ run_next_test();
+}
+
+// It is not permitted to create a socket in a directory which we are not
+// permitted to execute, or create files in.
+function test_create_permission()
+{
+ let dirName = do_get_tempdir();
+ dirName.append('unfriendly');
+
+ let socketName = dirName.clone();
+ socketName.append('socket');
+
+ // The test harness has difficulty cleaning things up if we don't make
+ // everything writable before we're done.
+ try {
+ // Create a directory which we are not permitted to search.
+ dirName.create(Ci.nsIFile.DIRECTORY_TYPE, 0);
+
+ // Try to create a socket in that directory. Because Linux returns EACCES
+ // when a 'connect' fails because of a local firewall rule,
+ // nsIServerSocket returns NS_ERROR_CONNECTION_REFUSED in this case.
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_CONNECTION_REFUSED");
+
+ // Grant read and execute permission, but not write permission on the directory.
+ dirName.permissions = parseInt("0555", 8);
+
+ // This should also fail; we need write permission.
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_CONNECTION_REFUSED");
+
+ } finally {
+ // Make the directory writable, so the test harness can clean it up.
+ dirName.permissions = allPermissions;
+ }
+
+ // This should succeed, since we now have all the permissions on the
+ // directory we could want.
+ do_check_instanceof(new UnixServerSocket(socketName, allPermissions, -1),
+ Ci.nsIServerSocket);
+
+ run_next_test();
+}
+
+// To connect to a Unix domain socket, we need search permission on the
+// directories containing it, and some kind of permission or other on the
+// socket itself.
+function test_connect_permission()
+{
+ // This test involves a lot of callbacks, but they're written out so that
+ // the actual control flow proceeds from top to bottom.
+ let log = '';
+
+ // Create a directory which we are permitted to search - at first.
+ let dirName = do_get_tempdir();
+ dirName.append('inhospitable');
+ dirName.create(Ci.nsIFile.DIRECTORY_TYPE, allPermissions);
+
+ let socketName = dirName.clone();
+ socketName.append('socket');
+
+ // Create a server socket in that directory, listening for connections,
+ // and accessible.
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+ server.asyncListen({ onSocketAccepted: socketAccepted, onStopListening: stopListening });
+
+ // Make the directory unsearchable.
+ dirName.permissions = 0;
+
+ let client3;
+
+ let client1 = socketTransportService.createUnixDomainTransport(socketName);
+ let client1AsyncInput = client1.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ client1AsyncInput.asyncWait(function (aStream) {
+ do_print("called test_connect_permission's client1's onInputStreamReady");
+ log += '1';
+
+ // nsISocketTransport puts off actually creating sockets as long as
+ // possible, so the error doesn't actually show up until this point.
+ do_check_throws_nsIException(() => client1AsyncInput.available(),
+ "NS_ERROR_CONNECTION_REFUSED");
+
+ client1AsyncInput.close();
+ client1.close(Cr.NS_OK);
+
+ // Make the directory searchable, but make the socket inaccessible.
+ dirName.permissions = allPermissions;
+ socketName.permissions = 0;
+
+ let client2 = socketTransportService.createUnixDomainTransport(socketName);
+ let client2AsyncInput = client2.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ client2AsyncInput.asyncWait(function (aStream) {
+ do_print("called test_connect_permission's client2's onInputStreamReady");
+ log += '2';
+
+ do_check_throws_nsIException(() => client2AsyncInput.available(),
+ "NS_ERROR_CONNECTION_REFUSED");
+
+ client2AsyncInput.close();
+ client2.close(Cr.NS_OK);
+
+ // Now make everything accessible, and try one last time.
+ socketName.permissions = allPermissions;
+
+ client3 = socketTransportService.createUnixDomainTransport(socketName);
+
+ let client3Output = client3.openOutputStream(0, 0, 0);
+ client3Output.write("Hanratty", 8);
+
+ let client3AsyncInput = client3.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ client3AsyncInput.asyncWait(client3InputStreamReady, 0, 0, threadManager.currentThread);
+ }, 0, 0, threadManager.currentThread);
+ }, 0, 0, threadManager.currentThread);
+
+ function socketAccepted(aServ, aTransport) {
+ do_print("called test_connect_permission's onSocketAccepted");
+ log += 'a';
+
+ let serverInput = aTransport.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = aTransport.openOutputStream(0, 0, 0);
+
+ serverInput.asyncWait(function (aStream) {
+ do_print("called test_connect_permission's socketAccepted's onInputStreamReady");
+ log += 'i';
+
+ // Receive data from the client, and send back a response.
+ let serverScriptableInput = new ScriptableInputStream(serverInput);
+ do_check_eq(serverScriptableInput.readBytes(8), "Hanratty");
+ serverOutput.write("Ferlingatti", 11);
+ }, 0, 0, threadManager.currentThread);
+ }
+
+ function client3InputStreamReady(aStream) {
+ do_print("called client3's onInputStreamReady");
+ log += '3';
+
+ let client3Input = new ScriptableInputStream(aStream);
+
+ do_check_eq(client3Input.readBytes(11), "Ferlingatti");
+
+ client3.close(Cr.NS_OK);
+ server.close();
+ }
+
+ function stopListening(aServ, aStatus) {
+ do_print("called test_connect_permission's server's stopListening");
+ log += 's';
+
+ do_check_eq(log, '12ai3s');
+
+ run_next_test();
+ }
+}
+
+// Creating a socket with a long filename doesn't crash.
+function test_long_socket_name()
+{
+ let socketName = do_get_tempdir();
+ socketName.append(new Array(10000).join('long'));
+
+ // Try to create a server socket with the long name.
+ do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_FILE_NAME_TOO_LONG");
+
+ // Try to connect to a socket with the long name.
+ do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_FILE_NAME_TOO_LONG");
+
+ run_next_test();
+}
+
+// Going offline should not shut down Unix domain sockets.
+function test_keep_when_offline()
+{
+ let log = '';
+
+ let socketName = do_get_tempdir();
+ socketName.append('keep-when-offline');
+
+ // Create a listening socket.
+ let listener = new UnixServerSocket(socketName, allPermissions, -1);
+ listener.asyncListen({ onSocketAccepted: onAccepted, onStopListening: onStopListening });
+
+ // Connect a client socket to the listening socket.
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+ let clientInput = client.openInputStream(0, 0, 0);
+ clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
+ let clientScriptableInput = new ScriptableInputStream(clientInput);
+
+ let server, serverInput, serverScriptableInput, serverOutput;
+
+ // How many times has the server invited the client to go first?
+ let count = 0;
+
+ // The server accepted connection callback.
+ function onAccepted(aListener, aServer) {
+ do_print("test_keep_when_offline: onAccepted called");
+ log += 'a';
+ do_check_eq(aListener, listener);
+ server = aServer;
+
+ // Prepare to receive messages from the client.
+ serverInput = server.openInputStream(0, 0, 0);
+ serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
+ serverScriptableInput = new ScriptableInputStream(serverInput);
+
+ // Start a conversation with the client.
+ serverOutput = server.openOutputStream(0, 0, 0);
+ serverOutput.write("After you, Alphonse!", 20);
+ count++;
+ }
+
+ // The client has seen its end of the socket close.
+ function clientReady(aStream) {
+ log += 'c';
+ do_print("test_keep_when_offline: clientReady called: " + log);
+ do_check_eq(aStream, clientInput);
+
+ // If the connection has been closed, end the conversation and stop listening.
+ let available;
+ try {
+ available = clientInput.available();
+ } catch (ex) {
+ do_check_instanceof(ex, Ci.nsIException);
+ do_check_eq(ex.result, Cr.NS_BASE_STREAM_CLOSED);
+
+ do_print("client received end-of-stream; closing client output stream");
+ log += ')';
+
+ client.close(Cr.NS_OK);
+
+ // Now both output streams have been closed, and both input streams
+ // have received the close notification. Stop listening for
+ // connections.
+ listener.close();
+ }
+
+ if (available) {
+ // Check the message from the server.
+ do_check_eq(clientScriptableInput.readBytes(20), "After you, Alphonse!");
+
+ // Write our response to the server.
+ clientOutput.write("No, after you, Gaston!", 22);
+
+ // Ask to be called again, when more input arrives.
+ clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
+ }
+ }
+
+ function serverReady(aStream) {
+ log += 's';
+ do_print("test_keep_when_offline: serverReady called: " + log);
+ do_check_eq(aStream, serverInput);
+
+ // Check the message from the client.
+ do_check_eq(serverScriptableInput.readBytes(22), "No, after you, Gaston!");
+
+ // This should not shut things down: Unix domain sockets should
+ // remain open in offline mode.
+ if (count == 5) {
+ IOService.offline = true;
+ log += 'o';
+ }
+
+ if (count < 10) {
+ // Insist.
+ serverOutput.write("After you, Alphonse!", 20);
+ count++;
+
+ // As long as the input stream is open, always ask to be called again
+ // when more input arrives.
+ serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
+ } else if (count == 10) {
+ // After sending ten times and receiving ten replies, we're not
+ // going to send any more. Close the server's output stream; the
+ // client's input stream should see this.
+ do_print("closing server transport");
+ server.close(Cr.NS_OK);
+ log += '(';
+ }
+ }
+
+ // We have stopped listening.
+ function onStopListening(aServ, aStatus) {
+ do_print("test_keep_when_offline: onStopListening called");
+ log += 'L';
+ do_check_eq(log, 'acscscscscsocscscscscs(c)L');
+
+ do_check_eq(aServ, listener);
+ do_check_eq(aStatus, Cr.NS_BINDING_ABORTED);
+
+ run_next_test();
+ }
+}
diff --git a/netwerk/test/unit/test_websocket_offline.js b/netwerk/test/unit/test_websocket_offline.js
new file mode 100644
index 000000000..f44360221
--- /dev/null
+++ b/netwerk/test/unit/test_websocket_offline.js
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// checking to make sure we don't hang as per 1038304
+// offline so url isn't impt
+var url = "ws://localhost";
+var chan;
+var offlineStatus;
+
+var listener = {
+ onAcknowledge: function(aContext, aSize) {},
+ onBinaryMessageAvailable: function(aContext, aMsg) {},
+ onMessageAvailable: function(aContext, aMsg) {},
+ onServerClose: function(aContext, aCode, aReason) {},
+ onStart: function(aContext)
+ {
+ // onStart is not called when a connection fails
+ do_check_true(false);
+ },
+ onStop: function(aContext, aStatusCode)
+ {
+ do_check_neq(aStatusCode, Cr.NS_OK);
+ Services.io.offline = offlineStatus;
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ offlineStatus = Services.io.offline;
+ Services.io.offline = true;
+
+ try {
+ chan = Cc["@mozilla.org/network/protocol;1?name=ws"].
+ createInstance(Components.interfaces.nsIWebSocketChannel);
+ chan.initLoadInfo(null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET);
+
+ var uri = Services.io.newURI(url, null, null);
+ chan.asyncOpen(uri, url, 0, listener, null);
+ do_test_pending();
+ } catch (x) {
+ dump("throwing " + x);
+ do_throw(x);
+ }
+}
diff --git a/netwerk/test/unit/test_xmlhttprequest.js b/netwerk/test/unit/test_xmlhttprequest.js
new file mode 100644
index 000000000..1f49a1aec
--- /dev/null
+++ b/netwerk/test/unit/test_xmlhttprequest.js
@@ -0,0 +1,54 @@
+
+Cu.import("resource://testing-common/httpd.js");
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "<?xml version='1.0' ?><root>0123456789</root>";
+
+function createXHR(async)
+{
+ var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open("GET", "http://localhost:" +
+ httpserver.identity.primaryPort + testpath, async);
+ return xhr;
+}
+
+function checkResults(xhr)
+{
+ if (xhr.readyState != 4)
+ return false;
+
+ do_check_eq(xhr.status, 200);
+ do_check_eq(xhr.responseText, httpbody);
+
+ var root_node = xhr.responseXML.getElementsByTagName('root').item(0);
+ do_check_eq(root_node.firstChild.data, "0123456789");
+ return true;
+}
+
+function run_test()
+{
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ // Test sync XHR sending
+ var sync = createXHR(false);
+ sync.send(null);
+ checkResults(sync);
+
+ // Test async XHR sending
+ let async = createXHR(true);
+ async.addEventListener("readystatechange", function(event) {
+ if (checkResults(async))
+ httpserver.stop(do_test_finished);
+ }, false);
+ async.send(null);
+ do_test_pending();
+}
+
+function serverHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/xml", false);
+ response.bodyOutputStream.write(httpbody, httpbody.length);
+}
diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini
new file mode 100644
index 000000000..0cbcbc423
--- /dev/null
+++ b/netwerk/test/unit/xpcshell.ini
@@ -0,0 +1,369 @@
+[DEFAULT]
+head = head_channels.js head_cache.js head_cache2.js
+tail =
+support-files =
+ CA.cert.der
+ client_cert_chooser.js
+ client_cert_chooser.manifest
+ data/image.png
+ data/system_root.lnk
+ data/test_psl.txt
+ data/test_readline1.txt
+ data/test_readline2.txt
+ data/test_readline3.txt
+ data/test_readline4.txt
+ data/test_readline5.txt
+ data/test_readline6.txt
+ data/test_readline7.txt
+ data/test_readline8.txt
+ data/signed_win.exe
+ socks_client_subprocess.js
+ test_link.desktop
+ test_link.url
+ ../../dns/effective_tld_names.dat
+
+[test_nsIBufferedOutputStream_writeFrom_block.js]
+[test_cache2-00-service-get.js]
+[test_cache2-01-basic.js]
+[test_cache2-01a-basic-readonly.js]
+[test_cache2-01b-basic-datasize.js]
+[test_cache2-01c-basic-hasmeta-only.js]
+[test_cache2-01d-basic-not-wanted.js]
+[test_cache2-01e-basic-bypass-if-busy.js]
+[test_cache2-01f-basic-openTruncate.js]
+[test_cache2-02-open-non-existing.js]
+[test_cache2-03-oncacheentryavail-throws.js]
+[test_cache2-04-oncacheentryavail-throws2x.js]
+[test_cache2-05-visit.js]
+[test_cache2-06-pb-mode.js]
+[test_cache2-07-visit-memory.js]
+[test_cache2-07a-open-memory.js]
+[test_cache2-08-evict-disk-by-memory-storage.js]
+[test_cache2-09-evict-disk-by-uri.js]
+[test_cache2-10-evict-direct.js]
+[test_cache2-10b-evict-direct-immediate.js]
+[test_cache2-11-evict-memory.js]
+[test_cache2-12-evict-disk.js]
+[test_cache2-13-evict-non-existing.js]
+[test_cache2-14-concurent-readers.js]
+[test_cache2-14b-concurent-readers-complete.js]
+[test_cache2-15-conditional-304.js]
+[test_cache2-16-conditional-200.js]
+[test_cache2-17-evict-all.js]
+[test_cache2-18-not-valid.js]
+[test_cache2-19-range-206.js]
+[test_cache2-20-range-200.js]
+[test_cache2-21-anon-storage.js]
+[test_cache2-22-anon-visit.js]
+[test_cache2-23-read-over-chunk.js]
+[test_cache2-24-exists.js]
+[test_cache2-25-chunk-memory-limit.js]
+[test_cache2-26-no-outputstream-open.js]
+# GC, that this patch is dependent on, doesn't work well on Android.
+skip-if = os == "android"
+[test_cache2-27-force-valid-for.js]
+[test_cache2-28-last-access-attrs.js]
+# This test will be fixed in bug 1067931
+skip-if = true
+[test_cache2-28a-OPEN_SECRETLY.js]
+# This test will be fixed in bug 1067931
+skip-if = true
+[test_cache2-29a-concurrent_read_resumable_entry_size_zero.js]
+[test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js]
+[test_cache2-29c-concurrent_read_half-interrupted.js]
+[test_cache2-29d-concurrent_read_half-corrupted-206.js]
+[test_cache2-29e-concurrent_read_half-non-206-response.js]
+[test_cache2-30a-entry-pinning.js]
+[test_cache2-30b-pinning-storage-clear.js]
+[test_cache2-30c-pinning-deferred-doom.js]
+[test_cache2-30d-pinning-WasEvicted-API.js]
+[test_partial_response_entry_size_smart_shrink.js]
+[test_304_responses.js]
+[test_421.js]
+[test_cacheForOfflineUse_no-store.js]
+[test_307_redirect.js]
+[test_NetUtil.js]
+[test_URIs.js]
+# Intermittent time-outs on Android, bug 1285020
+requesttimeoutfactor = 2
+[test_URIs2.js]
+# Intermittent time-outs on Android, bug 1285020
+requesttimeoutfactor = 2
+[test_aboutblank.js]
+[test_assoc.js]
+[test_auth_jar.js]
+[test_auth_proxy.js]
+[test_authentication.js]
+[test_authpromptwrapper.js]
+[test_auth_dialog_permission.js]
+[test_backgroundfilesaver.js]
+# Runs for a long time, causing intermittent time-outs on Android, bug 995686
+requesttimeoutfactor = 2
+[test_bug203271.js]
+[test_bug248970_cache.js]
+[test_bug248970_cookie.js]
+[test_bug261425.js]
+[test_bug263127.js]
+[test_bug282432.js]
+[test_bug321706.js]
+[test_bug331825.js]
+[test_bug336501.js]
+[test_bug337744.js]
+[test_bug365133.js]
+[test_bug368702.js]
+[test_bug369787.js]
+[test_bug371473.js]
+[test_bug376660.js]
+[test_bug376844.js]
+[test_bug376865.js]
+[test_bug379034.js]
+[test_bug380994.js]
+[test_bug388281.js]
+[test_bug396389.js]
+[test_bug401564.js]
+[test_bug411952.js]
+[test_bug412945.js]
+[test_bug414122.js]
+[test_bug427957.js]
+[test_bug429347.js]
+[test_bug455311.js]
+[test_bug455598.js]
+[test_bug468426.js]
+[test_bug468594.js]
+[test_bug470716.js]
+[test_bug477578.js]
+[test_bug479413.js]
+[test_bug479485.js]
+[test_bug482601.js]
+[test_bug484684.js]
+[test_bug490095.js]
+# Bug 675039: intermittent fail on Android-armv6
+skip-if = os == "android"
+[test_bug504014.js]
+[test_bug510359.js]
+[test_bug515583.js]
+[test_bug528292.js]
+[test_bug536324_64bit_content_length.js]
+[test_bug540566.js]
+[test_bug543805.js]
+[test_bug553970.js]
+[test_bug561042.js]
+# Bug 675039: test fails on Android 4.0
+skip-if = os == "android"
+[test_bug561276.js]
+[test_bug580508.js]
+[test_bug586908.js]
+[test_bug596443.js]
+[test_bug618835.js]
+[test_bug633743.js]
+[test_bug650995.js]
+[test_bug652761.js]
+[test_bug654926.js]
+[test_bug654926_doom_and_read.js]
+[test_bug654926_test_seek.js]
+[test_bug659569.js]
+[test_bug660066.js]
+[test_bug667907.js]
+[test_bug667818.js]
+[test_bug669001.js]
+[test_bug770243.js]
+[test_bug894586.js]
+# Allocating 4GB might actually succeed on 64 bit machines
+skip-if = bits != 32
+[test_bug935499.js]
+[test_bug1064258.js]
+[test_bug1218029.js]
+[test_udpsocket.js]
+[test_doomentry.js]
+[test_cacheflags.js]
+[test_cache_jar.js]
+[test_channel_close.js]
+[test_compareURIs.js]
+[test_compressappend.js]
+[test_content_encoding_gzip.js]
+[test_content_sniffer.js]
+[test_cookie_header.js]
+[test_cookiejars.js]
+[test_cookiejars_safebrowsing.js]
+[test_dns_cancel.js]
+[test_dns_per_interface.js]
+[test_data_protocol.js]
+[test_dns_service.js]
+[test_dns_offline.js]
+[test_dns_onion.js]
+[test_dns_localredirect.js]
+[test_dns_proxy_bypass.js]
+[test_duplicate_headers.js]
+[test_chunked_responses.js]
+[test_content_length_underrun.js]
+[test_event_sink.js]
+[test_extract_charset_from_content_type.js]
+[test_fallback_no-cache-entry_canceled.js]
+[test_fallback_no-cache-entry_passing.js]
+[test_fallback_redirect-to-different-origin_canceled.js]
+[test_fallback_redirect-to-different-origin_passing.js]
+[test_fallback_request-error_canceled.js]
+[test_fallback_request-error_passing.js]
+[test_fallback_response-error_canceled.js]
+[test_fallback_response-error_passing.js]
+[test_file_partial_inputstream.js]
+[test_file_protocol.js]
+[test_filestreams.js]
+[test_freshconnection.js]
+[test_gre_resources.js]
+[test_gzipped_206.js]
+[test_head.js]
+[test_header_Accept-Language.js]
+[test_header_Accept-Language_case.js]
+[test_headers.js]
+[test_http_headers.js]
+[test_httpauth.js]
+[test_httpcancel.js]
+[test_httpResponseTimeout.js]
+[test_httpsuspend.js]
+[test_idnservice.js]
+[test_idn_blacklist.js]
+[test_idn_urls.js]
+[test_idna2008.js]
+# IDNA2008 depends on ICU, not available on android
+skip-if = os == "android"
+[test_immutable.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_localstreams.js]
+[test_large_port.js]
+[test_mismatch_last-modified.js]
+[test_MIME_params.js]
+[test_mozTXTToHTMLConv.js]
+[test_multipart_byteranges.js]
+[test_multipart_streamconv.js]
+[test_multipart_streamconv_missing_lead_boundary.js]
+[test_nestedabout_serialize.js]
+[test_net_addr.js]
+# Bug 732363: test fails on windows for unknown reasons.
+skip-if = os == "win"
+[test_nojsredir.js]
+[test_offline_status.js]
+[test_original_sent_received_head.js]
+[test_parse_content_type.js]
+[test_permmgr.js]
+[test_plaintext_sniff.js]
+[test_post.js]
+[test_private_necko_channel.js]
+[test_private_cookie_changed.js]
+[test_progress.js]
+[test_protocolproxyservice.js]
+[test_proxy-failover_canceled.js]
+[test_proxy-failover_passing.js]
+[test_proxy-replace_canceled.js]
+[test_proxy-replace_passing.js]
+[test_psl.js]
+[test_range_requests.js]
+[test_readline.js]
+[test_redirect-caching_canceled.js]
+[test_redirect-caching_failure.js]
+# Bug 675039: test fails consistently on Android
+fail-if = os == "android"
+[test_redirect-caching_passing.js]
+[test_redirect_canceled.js]
+[test_redirect_failure.js]
+# Bug 675039: test fails consistently on Android
+fail-if = os == "android"
+[test_redirect_from_script.js]
+[test_redirect_from_script_after-open_passing.js]
+[test_redirect_passing.js]
+[test_redirect_loop.js]
+[test_redirect_baduri.js]
+[test_redirect_different-protocol.js]
+[test_reentrancy.js]
+[test_reopen.js]
+[test_resumable_channel.js]
+[test_resumable_truncate.js]
+[test_safeoutputstream.js]
+[test_simple.js]
+[test_sockettransportsvc_available.js]
+[test_socks.js]
+# Bug 675039: test fails consistently on Android
+fail-if = os == "android"
+# http2 unit tests require us to have node available to run the spdy and http2 server
+[test_http2.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_altsvc.js]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_speculative_connect.js]
+[test_standardurl.js]
+[test_standardurl_default_port.js]
+[test_standardurl_port.js]
+[test_streamcopier.js]
+[test_traceable_channel.js]
+[test_unescapestring.js]
+[test_xmlhttprequest.js]
+[test_XHR_redirects.js]
+[test_pinned_app_cache.js]
+[test_offlinecache_custom-directory.js]
+run-sequentially = Hardcoded hash value includes port 4444.
+[test_bug767025.js]
+run-sequentially = Hardcoded hash value includes port 4444.
+[test_bug826063.js]
+[test_bug812167.js]
+[test_tldservice_nextsubdomain.js]
+[test_about_protocol.js]
+[test_bug856978.js]
+[test_unix_domain.js]
+# The xpcshell temp directory on Android doesn't seem to let us create
+# Unix domain sockets. (Perhaps it's a FAT filesystem?)
+skip-if = os == "android"
+[test_addr_in_use_error.js]
+[test_about_networking.js]
+[test_ping_aboutnetworking.js]
+[test_referrer.js]
+[test_referrer_policy.js]
+[test_predictor.js]
+# Android version detection w/in gecko does not work right on infra, so we just
+# disable this test on all android versions, even though it's enabled on 2.3+ in
+# the wild.
+skip-if = os == "android"
+[test_signature_extraction.js]
+skip-if = os != "win"
+[test_synthesized_response.js]
+[test_udp_multicast.js]
+[test_redirect_history.js]
+[test_reply_without_content_type.js]
+[test_websocket_offline.js]
+[test_be_conservative.js]
+# The local cert service used by this test is not currently shipped on Android
+# Disabled on XP in bug 1190674 for intermittent failures
+skip-if = os == "android" || (os == "win" && (os_version == "5.1" || os_version == "5.2"))
+reason = bug 1190674
+firefox-appdir = browser
+[test_tls_server.js]
+# The local cert service used by this test is not currently shipped on Android
+# Disabled on XP in bug 1190674 for intermittent failures
+skip-if = os == "android" || (os == "win" && (os_version == "5.1" || os_version == "5.2"))
+reason = bug 1190674
+firefox-appdir = browser
+[test_tls_server_multiple_clients.js]
+# The local cert service used by this test is not currently shipped on Android
+skip-if = os == "android"
+[test_1073747.js]
+[test_safeoutputstream_append.js]
+[test_suspend_channel_before_connect.js]
+[test_suspend_channel_on_modified.js]
+[test_inhibit_caching.js]
+[test_dns_disable_ipv4.js]
+[test_dns_disable_ipv6.js]
+[test_bug1195415.js]
+[test_cookie_blacklist.js]
+[test_getHost.js]
+[test_bug412457.js]
+[test_bug464591.js]
+[test_alt-data_simple.js]
+[test_alt-data_stream.js]
+[test_cache-control_request.js]
+[test_bug1279246.js]
+[test_throttlequeue.js]
+[test_throttlechannel.js]
+[test_throttling.js]
+[test_separate_connections.js]