summaryrefslogtreecommitdiffstats
path: root/netwerk/test
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test')
-rw-r--r--netwerk/test/NetwerkTestLogging.h23
-rw-r--r--netwerk/test/PropertiesTest.cpp131
-rw-r--r--netwerk/test/ReadNTLM.cpp325
-rw-r--r--netwerk/test/TestBind.cpp210
-rw-r--r--netwerk/test/TestBlockingSocket.cpp127
-rw-r--r--netwerk/test/TestCacheBlockFiles.cpp873
-rw-r--r--netwerk/test/TestCachePrefixKeyParser.cpp78
-rw-r--r--netwerk/test/TestCommon.h51
-rw-r--r--netwerk/test/TestCookie.cpp921
-rw-r--r--netwerk/test/TestDNS.cpp130
-rw-r--r--netwerk/test/TestDNSDaemon.cpp274
-rw-r--r--netwerk/test/TestFileInput2.cpp480
-rw-r--r--netwerk/test/TestIDN.cpp52
-rw-r--r--netwerk/test/TestIOThreads.cpp75
-rw-r--r--netwerk/test/TestIncrementalDownload.cpp133
-rw-r--r--netwerk/test/TestMakeAbs.cpp69
-rw-r--r--netwerk/test/TestNamedPipeService.cpp334
-rw-r--r--netwerk/test/TestOpen.cpp90
-rw-r--r--netwerk/test/TestOverlappedIO.cpp313
-rw-r--r--netwerk/test/TestProtocols.cpp890
-rw-r--r--netwerk/test/TestServ.cpp146
-rw-r--r--netwerk/test/TestServ.js164
-rw-r--r--netwerk/test/TestSocketIO.cpp343
-rw-r--r--netwerk/test/TestSocketInput.cpp154
-rw-r--r--netwerk/test/TestSocketTransport.cpp307
-rw-r--r--netwerk/test/TestStreamLoader.cpp101
-rw-r--r--netwerk/test/TestStreamPump.cpp171
-rw-r--r--netwerk/test/TestStreamTransport.cpp319
-rw-r--r--netwerk/test/TestUDPSocket.cpp473
-rw-r--r--netwerk/test/TestUDPSocketProvider.cpp159
-rw-r--r--netwerk/test/TestURLManipulation.html130
-rw-r--r--netwerk/test/TestURLParser.cpp135
-rw-r--r--netwerk/test/TestUpload.cpp169
-rw-r--r--netwerk/test/TestWriteSpeed.cpp116
-rw-r--r--netwerk/test/browser/browser.ini11
-rw-r--r--netwerk/test/browser/browser_NetUtil.js92
-rw-r--r--netwerk/test/browser/browser_about_cache.js71
-rw-r--r--netwerk/test/browser/browser_child_resource.js256
-rw-r--r--netwerk/test/browser/browser_nsIFormPOSTActionChannel.js284
-rw-r--r--netwerk/test/browser/browser_post_file.js101
-rw-r--r--netwerk/test/browser/dummy.html7
-rw-r--r--netwerk/test/crashtests/1274044-1.html7
-rw-r--r--netwerk/test/crashtests/1334468-1.html25
-rw-r--r--netwerk/test/crashtests/785753-1.html253
-rw-r--r--netwerk/test/crashtests/785753-2.html3
-rw-r--r--netwerk/test/crashtests/crashtests.list4
-rw-r--r--netwerk/test/gtest/TestProtocolProxyService.cpp128
-rw-r--r--netwerk/test/gtest/TestStandardURL.cpp69
-rw-r--r--netwerk/test/gtest/moz.build14
-rw-r--r--netwerk/test/httpserver/README101
-rw-r--r--netwerk/test/httpserver/TODO17
-rw-r--r--netwerk/test/httpserver/httpd.js5376
-rw-r--r--netwerk/test/httpserver/httpd.manifest3
-rw-r--r--netwerk/test/httpserver/moz.build25
-rw-r--r--netwerk/test/httpserver/nsIHttpServer.idl620
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^3
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_both.html2
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^2
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt9
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override.html9
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt1
-rw-r--r--netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/bar.html^^10
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^2
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^1
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^1
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt2
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/foo.html^9
-rw-r--r--netwerk/test/httpserver/test/data/name-scheme/normal-file.txt1
-rw-r--r--netwerk/test/httpserver/test/data/ranges/empty.txt0
-rw-r--r--netwerk/test/httpserver/test/data/ranges/headers.txt1
-rw-r--r--netwerk/test/httpserver/test/data/ranges/headers.txt^headers^1
-rw-r--r--netwerk/test/httpserver/test/data/ranges/range.txt1
-rw-r--r--netwerk/test/httpserver/test/data/sjs/cgi.sjs8
-rw-r--r--netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^2
-rw-r--r--netwerk/test/httpserver/test/data/sjs/object-state.sjs87
-rw-r--r--netwerk/test/httpserver/test/data/sjs/qi.sjs48
-rw-r--r--netwerk/test/httpserver/test/data/sjs/range-checker.sjs3
-rw-r--r--netwerk/test/httpserver/test/data/sjs/sjs4
-rw-r--r--netwerk/test/httpserver/test/data/sjs/state1.sjs42
-rw-r--r--netwerk/test/httpserver/test/data/sjs/state2.sjs42
-rw-r--r--netwerk/test/httpserver/test/data/sjs/thrower.sjs6
-rw-r--r--netwerk/test/httpserver/test/head_utils.js600
-rw-r--r--netwerk/test/httpserver/test/test_async_response_sending.js1683
-rw-r--r--netwerk/test/httpserver/test/test_basic_functionality.js176
-rw-r--r--netwerk/test/httpserver/test/test_body_length.js64
-rw-r--r--netwerk/test/httpserver/test/test_byte_range.js278
-rw-r--r--netwerk/test/httpserver/test/test_cern_meta.js76
-rw-r--r--netwerk/test/httpserver/test/test_default_index_handler.js290
-rw-r--r--netwerk/test/httpserver/test/test_empty_body.js55
-rw-r--r--netwerk/test/httpserver/test/test_errorhandler_exception.js84
-rw-r--r--netwerk/test/httpserver/test/test_header_array.js67
-rw-r--r--netwerk/test/httpserver/test/test_headers.js189
-rw-r--r--netwerk/test/httpserver/test/test_host.js666
-rw-r--r--netwerk/test/httpserver/test/test_linedata.js20
-rw-r--r--netwerk/test/httpserver/test/test_load_module.js16
-rw-r--r--netwerk/test/httpserver/test/test_name_scheme.js90
-rw-r--r--netwerk/test/httpserver/test/test_processasync.js304
-rw-r--r--netwerk/test/httpserver/test/test_qi.js110
-rw-r--r--netwerk/test/httpserver/test/test_registerdirectory.js263
-rw-r--r--netwerk/test/httpserver/test/test_registerfile.js50
-rw-r--r--netwerk/test/httpserver/test/test_registerprefix.js127
-rw-r--r--netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js135
-rw-r--r--netwerk/test/httpserver/test/test_response_write.js55
-rw-r--r--netwerk/test/httpserver/test/test_seizepower.js182
-rw-r--r--netwerk/test/httpserver/test/test_setindexhandler.js67
-rw-r--r--netwerk/test/httpserver/test/test_setstatusline.js172
-rw-r--r--netwerk/test/httpserver/test/test_sjs.js251
-rw-r--r--netwerk/test/httpserver/test/test_sjs_object_state.js290
-rw-r--r--netwerk/test/httpserver/test/test_sjs_state.js186
-rw-r--r--netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js74
-rw-r--r--netwerk/test/httpserver/test/test_start_stop.js189
-rw-r--r--netwerk/test/httpserver/test/xpcshell.ini37
-rw-r--r--netwerk/test/mochitests/empty.html16
-rw-r--r--netwerk/test/mochitests/file_loadinfo_redirectchain.sjs106
-rw-r--r--netwerk/test/mochitests/method.sjs12
-rw-r--r--netwerk/test/mochitests/mochitest.ini26
-rw-r--r--netwerk/test/mochitests/partial_content.sjs151
-rw-r--r--netwerk/test/mochitests/redirect.sjs8
-rw-r--r--netwerk/test/mochitests/redirect_idn.html0
-rw-r--r--netwerk/test/mochitests/redirect_idn.html^headers^3
-rw-r--r--netwerk/test/mochitests/rel_preconnect.sjs15
-rw-r--r--netwerk/test/mochitests/signed_web_packaged_app.sjs78
-rw-r--r--netwerk/test/mochitests/test_arraybufferinputstream.html89
-rw-r--r--netwerk/test/mochitests/test_idn_redirect.html36
-rw-r--r--netwerk/test/mochitests/test_loadinfo_redirectchain.html213
-rw-r--r--netwerk/test/mochitests/test_partially_cached_content.html92
-rw-r--r--netwerk/test/mochitests/test_redirect_ref.html30
-rw-r--r--netwerk/test/mochitests/test_rel_preconnect.html62
-rw-r--r--netwerk/test/mochitests/test_uri_scheme.html51
-rw-r--r--netwerk/test/mochitests/test_user_agent_overrides.html240
-rw-r--r--netwerk/test/mochitests/test_user_agent_updates.html369
-rw-r--r--netwerk/test/mochitests/test_user_agent_updates_reset.html44
-rw-r--r--netwerk/test/mochitests/test_viewsource_unlinkable.html27
-rw-r--r--netwerk/test/mochitests/test_xhr_method_case.html66
-rw-r--r--netwerk/test/mochitests/user_agent.sjs21
-rw-r--r--netwerk/test/mochitests/user_agent_update.sjs10
-rw-r--r--netwerk/test/mochitests/web_packaged_app.sjs35
-rw-r--r--netwerk/test/moz.build70
-rw-r--r--netwerk/test/reftest/658949-1-ref.html1
-rw-r--r--netwerk/test/reftest/658949-1.html1
-rw-r--r--netwerk/test/reftest/bug565432-1-ref.html12
-rw-r--r--netwerk/test/reftest/bug565432-1.html18
-rw-r--r--netwerk/test/reftest/reftest-stylo.list3
-rw-r--r--netwerk/test/reftest/reftest.list2
-rw-r--r--netwerk/test/sites.txt257
-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
-rw-r--r--netwerk/test/unit_ipc/child_app_offline_notifications.js43
-rw-r--r--netwerk/test/unit_ipc/child_channel_id.js45
-rw-r--r--netwerk/test/unit_ipc/head_cc.js4
-rw-r--r--netwerk/test/unit_ipc/head_channels_clone.js8
-rw-r--r--netwerk/test/unit_ipc/test_XHR_redirects.js3
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_simple_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_stream_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_bug528292_wrap.js6
-rw-r--r--netwerk/test/unit_ipc/test_cache_jar_wrap.js4
-rw-r--r--netwerk/test/unit_ipc/test_cacheflags_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_channel_close_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_channel_id.js109
-rw-r--r--netwerk/test/unit_ipc/test_chunked_responses_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_cookie_header_wrap.js11
-rw-r--r--netwerk/test/unit_ipc/test_cookiejars_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_dns_cancel_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_dns_per_interface_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_dns_service_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_duplicate_headers_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_event_sink_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_getHost_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_head_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_headers_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_httpsuspend_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_post_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_predictor_wrap.js36
-rw-r--r--netwerk/test/unit_ipc/test_progress_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_canceled_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_failure_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_from_script_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_redirect_history_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_redirect_passing_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_reentrancy_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_resumable_channel_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_simple_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_synthesized_response_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/xpcshell.ini99
-rw-r--r--netwerk/test/urlparse.dat103
-rw-r--r--netwerk/test/urlparse_mac.dat14
-rw-r--r--netwerk/test/urlparse_unx.dat34
-rw-r--r--netwerk/test/urlparse_win.dat60
-rw-r--r--netwerk/test/urltest.cpp461
-rw-r--r--netwerk/test/urltests.dat43
512 files changed, 65150 insertions, 0 deletions
diff --git a/netwerk/test/NetwerkTestLogging.h b/netwerk/test/NetwerkTestLogging.h
new file mode 100644
index 000000000..68e955fd3
--- /dev/null
+++ b/netwerk/test/NetwerkTestLogging.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef NetwerkTestLogging_h
+#define NetwerkTestLogging_h
+
+#include "mozilla/Logging.h"
+
+// The netwerk standalone cpp unit tests will just use PR_LogPrint as they don't
+// have access to mozilla::detail::log_print. To support that MOZ_LOG is
+// redefined.
+#undef MOZ_LOG
+#define MOZ_LOG(_module,_level,_args) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(_module,_level)) { \
+ PR_LogPrint _args; \
+ } \
+ PR_END_MACRO
+
+#endif
diff --git a/netwerk/test/PropertiesTest.cpp b/netwerk/test/PropertiesTest.cpp
new file mode 100644
index 000000000..5099d7b5b
--- /dev/null
+++ b/netwerk/test/PropertiesTest.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "TestCommon.h"
+#include "mozilla/Sprintf.h"
+#include "nsXPCOM.h"
+#include "nsStringAPI.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIChannel.h"
+#include "nsIComponentManager.h"
+#include <stdio.h>
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsILoadInfo.h"
+#include "nsNetUtil.h"
+
+#define TEST_URL "resource:/res/test.properties"
+static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
+
+/***************************************************************************/
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult ret;
+
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ nsIInputStream* in = nullptr;
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &ret);
+ if (NS_FAILED(ret)) return 1;
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ ret = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ if (NS_FAILED(ret)) return 1;
+
+ nsCOMPtr<nsIURI> uri;
+ ret = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING(TEST_URL));
+ if (NS_FAILED(ret)) return 1;
+
+ nsIChannel *channel = nullptr;
+ ret = NS_NewChannel(&channel,
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(ret)) return 1;
+
+ ret = channel->Open2(&in);
+ if (NS_FAILED(ret)) return 1;
+
+ nsIPersistentProperties* props;
+ ret = CallCreateInstance(kPersistentPropertiesCID, &props);
+ if (NS_FAILED(ret) || (!props)) {
+ printf("create nsIPersistentProperties failed\n");
+ return 1;
+ }
+ ret = props->Load(in);
+ if (NS_FAILED(ret)) {
+ printf("cannot load properties\n");
+ return 1;
+ }
+ int i = 1;
+ while (true) {
+ char name[16];
+ name[0] = 0;
+ SprintfLiteral(name, "%d", i);
+ nsAutoString v;
+ ret = props->GetStringProperty(nsDependentCString(name), v);
+ if (NS_FAILED(ret) || (!v.Length())) {
+ break;
+ }
+ printf("\"%d\"=\"%s\"\n", i, NS_ConvertUTF16toUTF8(v).get());
+ i++;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> propEnum;
+ ret = props->Enumerate(getter_AddRefs(propEnum));
+
+ if (NS_FAILED(ret)) {
+ printf("cannot enumerate properties\n");
+ return 1;
+ }
+
+
+ printf("\nKey\tValue\n");
+ printf( "---\t-----\n");
+
+ bool hasMore;
+ while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsISupports> sup;
+ ret = propEnum->GetNext(getter_AddRefs(sup));
+
+ nsCOMPtr<nsIPropertyElement> propElem = do_QueryInterface(sup, &ret);
+ if (NS_FAILED(ret)) {
+ printf("failed to get current item\n");
+ return 1;
+ }
+
+ nsAutoCString key;
+ nsAutoString value;
+
+ ret = propElem->GetKey(key);
+ if (NS_FAILED(ret)) {
+ printf("failed to get current element's key\n");
+ return 1;
+ }
+ ret = propElem->GetValue(value);
+ if (NS_FAILED(ret)) {
+ printf("failed to get current element's value\n");
+ return 1;
+ }
+
+ printf("%s\t%s\n", key.get(), NS_ConvertUTF16toUTF8(value).get());
+ }
+ return 0;
+}
diff --git a/netwerk/test/ReadNTLM.cpp b/netwerk/test/ReadNTLM.cpp
new file mode 100644
index 000000000..20411e336
--- /dev/null
+++ b/netwerk/test/ReadNTLM.cpp
@@ -0,0 +1,325 @@
+/* vim: set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "plbase64.h"
+#include "nsStringAPI.h"
+#include "prmem.h"
+
+/*
+ * ReadNTLM : reads NTLM messages.
+ *
+ * based on http://davenport.sourceforge.net/ntlm.html
+ */
+
+#define kNegotiateUnicode 0x00000001
+#define kNegotiateOEM 0x00000002
+#define kRequestTarget 0x00000004
+#define kUnknown1 0x00000008
+#define kNegotiateSign 0x00000010
+#define kNegotiateSeal 0x00000020
+#define kNegotiateDatagramStyle 0x00000040
+#define kNegotiateLanManagerKey 0x00000080
+#define kNegotiateNetware 0x00000100
+#define kNegotiateNTLMKey 0x00000200
+#define kUnknown2 0x00000400
+#define kUnknown3 0x00000800
+#define kNegotiateDomainSupplied 0x00001000
+#define kNegotiateWorkstationSupplied 0x00002000
+#define kNegotiateLocalCall 0x00004000
+#define kNegotiateAlwaysSign 0x00008000
+#define kTargetTypeDomain 0x00010000
+#define kTargetTypeServer 0x00020000
+#define kTargetTypeShare 0x00040000
+#define kNegotiateNTLM2Key 0x00080000
+#define kRequestInitResponse 0x00100000
+#define kRequestAcceptResponse 0x00200000
+#define kRequestNonNTSessionKey 0x00400000
+#define kNegotiateTargetInfo 0x00800000
+#define kUnknown4 0x01000000
+#define kUnknown5 0x02000000
+#define kUnknown6 0x04000000
+#define kUnknown7 0x08000000
+#define kUnknown8 0x10000000
+#define kNegotiate128 0x20000000
+#define kNegotiateKeyExchange 0x40000000
+#define kNegotiate56 0x80000000
+
+static const char NTLM_SIGNATURE[] = "NTLMSSP";
+static const char NTLM_TYPE1_MARKER[] = { 0x01, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE2_MARKER[] = { 0x02, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE3_MARKER[] = { 0x03, 0x00, 0x00, 0x00 };
+
+#define NTLM_MARKER_LEN 4
+#define NTLM_TYPE1_HEADER_LEN 32
+#define NTLM_TYPE2_HEADER_LEN 32
+#define NTLM_TYPE3_HEADER_LEN 64
+
+#define LM_HASH_LEN 16
+#define LM_RESP_LEN 24
+
+#define NTLM_HASH_LEN 16
+#define NTLM_RESP_LEN 24
+
+static void PrintFlags(uint32_t flags)
+{
+#define TEST(_flag) \
+ if (flags & k ## _flag) \
+ printf(" 0x%08x (" # _flag ")\n", k ## _flag)
+
+ TEST(NegotiateUnicode);
+ TEST(NegotiateOEM);
+ TEST(RequestTarget);
+ TEST(Unknown1);
+ TEST(NegotiateSign);
+ TEST(NegotiateSeal);
+ TEST(NegotiateDatagramStyle);
+ TEST(NegotiateLanManagerKey);
+ TEST(NegotiateNetware);
+ TEST(NegotiateNTLMKey);
+ TEST(Unknown2);
+ TEST(Unknown3);
+ TEST(NegotiateDomainSupplied);
+ TEST(NegotiateWorkstationSupplied);
+ TEST(NegotiateLocalCall);
+ TEST(NegotiateAlwaysSign);
+ TEST(TargetTypeDomain);
+ TEST(TargetTypeServer);
+ TEST(TargetTypeShare);
+ TEST(NegotiateNTLM2Key);
+ TEST(RequestInitResponse);
+ TEST(RequestAcceptResponse);
+ TEST(RequestNonNTSessionKey);
+ TEST(NegotiateTargetInfo);
+ TEST(Unknown4);
+ TEST(Unknown5);
+ TEST(Unknown6);
+ TEST(Unknown7);
+ TEST(Unknown8);
+ TEST(Negotiate128);
+ TEST(NegotiateKeyExchange);
+ TEST(Negotiate56);
+
+#undef TEST
+}
+
+static void
+PrintBuf(const char *tag, const uint8_t *buf, uint32_t bufLen)
+{
+ int i;
+
+ printf("%s =\n", tag);
+ while (bufLen > 0)
+ {
+ int count = bufLen;
+ if (count > 8)
+ count = 8;
+
+ printf(" ");
+ for (i=0; i<count; ++i)
+ {
+ printf("0x%02x ", int(buf[i]));
+ }
+ for (; i<8; ++i)
+ {
+ printf(" ");
+ }
+
+ printf(" ");
+ for (i=0; i<count; ++i)
+ {
+ if (isprint(buf[i]))
+ printf("%c", buf[i]);
+ else
+ printf(".");
+ }
+ printf("\n");
+
+ bufLen -= count;
+ buf += count;
+ }
+}
+
+static uint16_t
+ReadUint16(const uint8_t *&buf)
+{
+ uint16_t x;
+#ifdef IS_BIG_ENDIAN
+ x = ((uint16_t) buf[1]) | ((uint16_t) buf[0] << 8);
+#else
+ x = ((uint16_t) buf[0]) | ((uint16_t) buf[1] << 8);
+#endif
+ buf += sizeof(x);
+ return x;
+}
+
+static uint32_t
+ReadUint32(const uint8_t *&buf)
+{
+ uint32_t x;
+#ifdef IS_BIG_ENDIAN
+ x = ( (uint32_t) buf[3]) |
+ (((uint32_t) buf[2]) << 8) |
+ (((uint32_t) buf[1]) << 16) |
+ (((uint32_t) buf[0]) << 24);
+#else
+ x = ( (uint32_t) buf[0]) |
+ (((uint32_t) buf[1]) << 8) |
+ (((uint32_t) buf[2]) << 16) |
+ (((uint32_t) buf[3]) << 24);
+#endif
+ buf += sizeof(x);
+ return x;
+}
+
+typedef struct {
+ uint16_t length;
+ uint16_t capacity;
+ uint32_t offset;
+} SecBuf;
+
+static void
+ReadSecBuf(SecBuf *s, const uint8_t *&buf)
+{
+ s->length = ReadUint16(buf);
+ s->capacity = ReadUint16(buf);
+ s->offset = ReadUint32(buf);
+}
+
+static void
+ReadType1MsgBody(const uint8_t *inBuf, uint32_t start)
+{
+ const uint8_t *cursor = inBuf + start;
+ uint32_t flags;
+
+ PrintBuf("flags", cursor, 4);
+ // read flags
+ flags = ReadUint32(cursor);
+ PrintFlags(flags);
+
+ // type 1 message may not include trailing security buffers
+ if ((flags & kNegotiateDomainSupplied) |
+ (flags & kNegotiateWorkstationSupplied))
+ {
+ SecBuf secbuf;
+ ReadSecBuf(&secbuf, cursor);
+ PrintBuf("supplied domain", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor);
+ PrintBuf("supplied workstation", inBuf + secbuf.offset, secbuf.length);
+ }
+}
+
+static void
+ReadType2MsgBody(const uint8_t *inBuf, uint32_t start)
+{
+ uint16_t targetLen, offset;
+ uint32_t flags;
+ const uint8_t *target;
+ const uint8_t *cursor = inBuf + start;
+
+ // read target name security buffer
+ targetLen = ReadUint16(cursor);
+ ReadUint16(cursor); // discard next 16-bit value
+ offset = ReadUint32(cursor); // get offset from inBuf
+ target = inBuf + offset;
+
+ PrintBuf("target", target, targetLen);
+
+ PrintBuf("flags", cursor, 4);
+ // read flags
+ flags = ReadUint32(cursor);
+ PrintFlags(flags);
+
+ // read challenge
+ PrintBuf("challenge", cursor, 8);
+ cursor += 8;
+
+ PrintBuf("context", cursor, 8);
+ cursor += 8;
+
+ SecBuf secbuf;
+ ReadSecBuf(&secbuf, cursor);
+ PrintBuf("target information", inBuf + secbuf.offset, secbuf.length);
+}
+
+static void
+ReadType3MsgBody(const uint8_t *inBuf, uint32_t start)
+{
+ const uint8_t *cursor = inBuf + start;
+
+ SecBuf secbuf;
+
+ ReadSecBuf(&secbuf, cursor); // LM response
+ PrintBuf("LM response", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // NTLM response
+ PrintBuf("NTLM response", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // domain name
+ PrintBuf("domain name", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // user name
+ PrintBuf("user name", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // workstation name
+ PrintBuf("workstation name", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // session key
+ PrintBuf("session key", inBuf + secbuf.offset, secbuf.length);
+
+ uint32_t flags = ReadUint32(cursor);
+ PrintBuf("flags", (const uint8_t *) &flags, sizeof(flags));
+ PrintFlags(flags);
+}
+
+static void
+ReadMsg(const char *base64buf, uint32_t bufLen)
+{
+ uint8_t *inBuf = (uint8_t *) PL_Base64Decode(base64buf, bufLen, nullptr);
+ if (!inBuf)
+ {
+ printf("PL_Base64Decode failed\n");
+ return;
+ }
+
+ const uint8_t *cursor = inBuf;
+
+ PrintBuf("signature", cursor, 8);
+
+ // verify NTLMSSP signature
+ if (memcmp(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) != 0)
+ {
+ printf("### invalid or corrupt NTLM signature\n");
+ }
+ cursor += sizeof(NTLM_SIGNATURE);
+
+ PrintBuf("message type", cursor, 4);
+
+ if (memcmp(cursor, NTLM_TYPE1_MARKER, sizeof(NTLM_MARKER_LEN)) == 0)
+ ReadType1MsgBody(inBuf, 12);
+ else if (memcmp(cursor, NTLM_TYPE2_MARKER, sizeof(NTLM_MARKER_LEN)) == 0)
+ ReadType2MsgBody(inBuf, 12);
+ else if (memcmp(cursor, NTLM_TYPE3_MARKER, sizeof(NTLM_MARKER_LEN)) == 0)
+ ReadType3MsgBody(inBuf, 12);
+ else
+ printf("### invalid or unknown message type\n");
+
+ PR_Free(inBuf);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc == 1)
+ {
+ printf("usage: ntlmread <msg>\n");
+ return -1;
+ }
+ ReadMsg(argv[1], (uint32_t) strlen(argv[1]));
+ return 0;
+}
diff --git a/netwerk/test/TestBind.cpp b/netwerk/test/TestBind.cpp
new file mode 100644
index 000000000..3ce7438b3
--- /dev/null
+++ b/netwerk/test/TestBind.cpp
@@ -0,0 +1,210 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "TestHarness.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIServerSocket.h"
+#include "nsIAsyncInputStream.h"
+#include "nsINetAddr.h"
+#include "mozilla/net/DNS.h"
+#include "prerror.h"
+
+using namespace mozilla::net;
+using namespace mozilla;
+
+class ServerListener: public nsIServerSocketListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ ServerListener();
+
+ // Port that is got from server side will be store here.
+ uint32_t mClientPort;
+ bool mFailed;
+private:
+ virtual ~ServerListener();
+};
+
+NS_IMPL_ISUPPORTS(ServerListener, nsIServerSocketListener)
+
+ServerListener::ServerListener()
+ : mClientPort(-1)
+ , mFailed(false)
+{
+}
+
+ServerListener::~ServerListener() = default;
+
+NS_IMETHODIMP
+ServerListener::OnSocketAccepted(nsIServerSocket *aServ,
+ nsISocketTransport *aTransport)
+{
+ // Run on STS thread.
+ NetAddr peerAddr;
+ nsresult rv = aTransport->GetPeerAddr(&peerAddr);
+ if (NS_FAILED(rv)) {
+ mFailed = true;
+ fail("Server: not able to get peer address.");
+ QuitPumpingEvents();
+ return NS_OK;
+ }
+ mClientPort = PR_ntohs(peerAddr.inet.port);
+ passed("Server: received connection");
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServerListener::OnStopListening(nsIServerSocket *aServ,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+class ClientInputCallback : public nsIInputStreamCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ ClientInputCallback();
+
+ bool mFailed;
+private:
+ virtual ~ClientInputCallback();
+};
+
+NS_IMPL_ISUPPORTS(ClientInputCallback, nsIInputStreamCallback)
+
+ClientInputCallback::ClientInputCallback()
+ : mFailed(false)
+{
+}
+
+ClientInputCallback::~ClientInputCallback() = default;
+
+NS_IMETHODIMP
+ClientInputCallback::OnInputStreamReady(nsIAsyncInputStream *aStream)
+{
+ // Server doesn't send. That means if we are here, we probably have run into
+ // an error.
+ uint64_t avail;
+ nsresult rv = aStream->Available(&avail);
+ if (NS_FAILED(rv)) {
+ mFailed = true;
+ }
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+int
+main(int32_t argc, char *argv[])
+{
+ ScopedXPCOM xpcom("SocketTransport");
+ if (xpcom.failed()) {
+ fail("Unable to initalize XPCOM.");
+ return -1;
+ }
+
+ //
+ // Server side.
+ //
+ nsCOMPtr<nsIServerSocket> server = do_CreateInstance("@mozilla.org/network/server-socket;1");
+ if (!server) {
+ fail("Failed to create server socket.");
+ return -1;
+ }
+
+ nsresult rv = server->Init(-1, true, -1);
+ if (NS_FAILED(rv)) {
+ fail("Failed to initialize server.");
+ return -1;
+ }
+
+ int32_t serverPort;
+ rv = server->GetPort(&serverPort);
+ if (NS_FAILED(rv)) {
+ fail("Unable to get server port.");
+ return -1;
+ }
+
+ // Listening.
+ RefPtr<ServerListener> serverListener = new ServerListener();
+ rv = server->AsyncListen(serverListener);
+ if (NS_FAILED(rv)) {
+ fail("Server fail to start listening.");
+ return -1;
+ }
+
+ //
+ // Client side
+ //
+ uint32_t bindingPort = 20000;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ fail("Unable to get socket transport service.");
+ return -1;
+ }
+
+ for (int32_t tried = 0; tried < 100; tried++) {
+ nsCOMPtr<nsISocketTransport> client;
+ rv = sts->CreateTransport(nullptr, 0, NS_LITERAL_CSTRING("127.0.0.1"),
+ serverPort, nullptr, getter_AddRefs(client));
+ if (NS_FAILED(rv)) {
+ fail("Unable to create transport.");
+ return -1;
+ }
+
+ // Bind to a port. It's possible that we are binding to a port that is
+ // currently in use. If we failed to bind, we try next port.
+ NetAddr bindingAddr;
+ bindingAddr.inet.family = AF_INET;
+ bindingAddr.inet.ip = 0;
+ bindingAddr.inet.port = PR_htons(bindingPort);
+ rv = client->Bind(&bindingAddr);
+ if (NS_FAILED(rv)) {
+ fail("Unable to bind a port.");
+ return -1;
+ }
+
+ // Open IO streams, to make client SocketTransport connect to server.
+ RefPtr<ClientInputCallback> clientCallback = new ClientInputCallback();
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = client->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
+ 0, 0, getter_AddRefs(inputStream));
+ if (NS_FAILED(rv)) {
+ fail("Failed to open an input stream.");
+ return -1;
+ }
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream = do_QueryInterface(inputStream);
+ rv = asyncInputStream->AsyncWait(clientCallback, 0, 0, nullptr);
+
+ // Wait for server's response or callback of input stream.
+ PumpEvents();
+ if (clientCallback->mFailed) {
+ // if client received error, we likely have bound a port that is in use.
+ // we can try another port.
+ bindingPort++;
+ } else {
+ // We are unlocked by server side, leave the loop and check result.
+ break;
+ }
+ }
+
+ if (serverListener->mFailed) {
+ fail("Server failure.");
+ return -1;
+ }
+ if (serverListener->mClientPort != bindingPort) {
+ fail("Port that server got doesn't match what we are expecting.");
+ return -1;
+ }
+ passed("Port matched");
+ return 0;
+}
diff --git a/netwerk/test/TestBlockingSocket.cpp b/netwerk/test/TestBlockingSocket.cpp
new file mode 100644
index 000000000..d4b4a615f
--- /dev/null
+++ b/netwerk/test/TestBlockingSocket.cpp
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "nsIComponentRegistrar.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIOutputStream.h"
+#include "NetwerkTestLogging.h"
+#include "prenv.h"
+#include "prthread.h"
+#include <stdlib.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+static nsresult
+RunBlockingTest(const nsACString &host, int32_t port, nsIFile *file)
+{
+ nsresult rv;
+
+ LOG(("RunBlockingTest\n"));
+
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> input;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(input), file);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISocketTransport> trans;
+ rv = sts->CreateTransport(nullptr, 0, host, port, nullptr, getter_AddRefs(trans));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> output;
+ rv = trans->OpenOutputStream(nsITransport::OPEN_BLOCKING, 100, 10, getter_AddRefs(output));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[120];
+ uint32_t nr, nw;
+ for (;;) {
+ rv = input->Read(buf, sizeof(buf), &nr);
+ if (NS_FAILED(rv) || (nr == 0)) return rv;
+
+/*
+ const char *p = buf;
+ while (nr) {
+ rv = output->Write(p, nr, &nw);
+ if (NS_FAILED(rv)) return rv;
+
+ nr -= nw;
+ p += nw;
+ }
+*/
+
+ rv = output->Write(buf, nr, &nw);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(nr == nw, "not all written");
+ }
+
+ LOG((" done copying data.\n"));
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 4) {
+ printf("usage: %s <host> <port> <file-to-read>\n", argv[0]);
+ return -1;
+ }
+ char* hostName = argv[1];
+ int32_t port = atoi(argv[2]);
+ char* fileName = argv[3];
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsCOMPtr<nsIFile> file;
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileName), false, getter_AddRefs(file));
+ if (NS_FAILED(rv)) return -1;
+
+ rv = RunBlockingTest(nsDependentCString(hostName), port, file);
+ if (NS_FAILED(rv))
+ LOG(("RunBlockingTest failed [rv=%x]\n", rv));
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ LOG(("sleeping for 5 seconds...\n"));
+ PR_Sleep(PR_SecondsToInterval(5));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
diff --git a/netwerk/test/TestCacheBlockFiles.cpp b/netwerk/test/TestCacheBlockFiles.cpp
new file mode 100644
index 000000000..0a309fdf9
--- /dev/null
+++ b/netwerk/test/TestCacheBlockFiles.cpp
@@ -0,0 +1,873 @@
+/*
+ TestCacheBlockFiles.cpp
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <utime.h>
+
+#include <Files.h>
+#include <Strings.h>
+#include <Errors.h>
+#include <Resources.h>
+#include <Aliases.h>
+
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsError.h"
+#include "nsIComponentManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsMemory.h"
+#include "nsIComponentRegistrar.h"
+#include "nsANSIFileStreams.h"
+#include "nsDiskCacheBlockFile.h"
+
+#include "prclist.h"
+
+/**
+ * StressTest()
+ */
+
+typedef struct Allocation {
+ int32_t start;
+ int32_t count;
+} Allocation;
+
+nsresult
+StressTest(nsIFile * localFile, int32_t testNumber, bool readWrite)
+{
+ nsresult rv = NS_OK;
+
+#define ITERATIONS 1024
+#define MAX_ALLOCATIONS 256
+ Allocation block[MAX_ALLOCATIONS];
+ int32_t currentAllocations = 0;
+ int32_t i;
+ uint32_t a;
+
+ char * writeBuf[4];
+ char readBuf[256 * 4];
+
+
+ if (readWrite) {
+ for (i = 0; i < 4; i++) {
+ writeBuf[i] = new char[256 * i];
+ if (!writeBuf[i]) {
+ printf("Test %d: failed - out of memory\n", testNumber);
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ memset(writeBuf[i], i, 256 * i);
+ }
+ }
+
+ nsDiskCacheBlockFile * blockFile = new nsDiskCacheBlockFile;
+ if (!blockFile) {
+ printf("Test %d failed (unable to allocate nsDiskCacheBlockFile", testNumber);
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (Open returned: 0x%.8x)\n", testNumber, rv);
+ goto exit;
+ }
+
+ i = ITERATIONS;
+ while (i > 0) {
+ if ((currentAllocations >= MAX_ALLOCATIONS) ||
+ ((currentAllocations > 0) && (rand() % 4 == 0))) {
+ // deallocate if we've reached the limit, or 25% of the time we have allocations
+ a = rand() % currentAllocations;
+
+ if (readWrite) {
+ // read verify deallocation
+ rv = blockFile->ReadBlocks(readBuf, block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (ReadBlocks() returned 0x%.8x)\n", testNumber, rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * block[a].count; i++) {
+ if (readBuf[i] != block[a].count) {
+ printf("Test %d: failed (verifying buffer 1)\n", testNumber);
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+ }
+
+ rv = blockFile->DeallocateBlocks(block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (DeallocateBlocks() returned %d)\n", testNumber, rv);
+ goto exit;
+ }
+
+ --currentAllocations;
+ if (currentAllocations > 0)
+ block[a] = block[currentAllocations];
+
+ } else {
+ // allocate blocks
+ --i;
+ a = currentAllocations++;
+ block[a].count = rand() % 4 + 1; // allocate 1 to 4 blocks
+ block[a].start = blockFile->AllocateBlocks(block[a].count);
+ if (block[a].start < 0) {
+ printf("Test %d: failed (AllocateBlocks() failed.)\n", testNumber);
+ goto exit;
+ }
+
+ if (readWrite) {
+ // write buffer
+ rv = blockFile->WriteBlocks(writeBuf[block[a].count], block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (WriteBlocks() returned 0x%.8x)\n",testNumber, rv);
+ goto exit;
+ }
+ }
+ }
+ }
+
+ // now deallocate remaining allocations
+ i = currentAllocations;
+ while (i--) {
+
+ if (readWrite) {
+ // read verify deallocation
+ rv = blockFile->ReadBlocks(readBuf, block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (ReadBlocks(1) returned 0x%.8x)\n", testNumber, rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * block[a].count; i++) {
+ if (readBuf[i] != block[a].count) {
+ printf("Test %d: failed (verifying buffer 1)\n", testNumber);
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+ }
+
+ rv = blockFile->DeallocateBlocks(block[i].start, block[i].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (DeallocateBlocks() returned %d)\n", testNumber, rv);
+ goto exit;
+ }
+ }
+
+
+
+exit:
+ nsresult rv2 = blockFile->Close();
+ if (NS_FAILED(rv2)) {
+ printf("Test %d: failed (Close returned: 0x%.8x)\n", testNumber, rv2);
+ }
+
+ return rv ? rv : rv2;
+}
+
+/**
+ * main()
+ */
+
+int
+main(void)
+{
+// OSErr err;
+ printf("hello world\n");
+
+ unsigned long now = time(0);
+ srand(now);
+
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_OK;
+ {
+ // Start up XPCOM
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ // Get default directory
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ printf("NS_GetSpecialDirectory() failed : 0x%.8x\n", rv);
+ goto exit;
+ }
+ char * currentDirPath;
+ rv = file->GetPath(&currentDirPath);
+ if (NS_FAILED(rv)) {
+ printf("currentProcessDir->GetPath() failed : 0x%.8x\n", rv);
+ goto exit;
+ }
+
+ printf("Current Process Directory: %s\n", currentDirPath);
+
+
+ // Generate name for cache block file
+ rv = file->Append("_CACHE_001_");
+ if (NS_FAILED(rv)) goto exit;
+
+ // Delete existing file
+ rv = file->Delete(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) goto exit;
+
+ // Need nsIFile to open
+ localFile = do_QueryInterface(file, &rv);
+ if (NS_FAILED(rv)) {
+ printf("do_QueryInterface(file) failed : 0x%.8x\n", rv);
+ goto exit;
+ }
+
+ nsDiskCacheBlockFile * blockFile = new nsDiskCacheBlockFile;
+ if (!blockFile) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ //----------------------------------------------------------------
+ // local variables used in tests
+ //----------------------------------------------------------------
+ uint32_t bytesWritten = 0;
+ int32_t startBlock;
+ int32_t i = 0;
+
+
+ //----------------------------------------------------------------
+ // Test 1: Open nonexistent file
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 1: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 1: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 1: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 2: Open existing file (with no allocation)
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 2: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 2: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 2: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 3: Open existing file (bad format) size < kBitMapBytes
+ //----------------------------------------------------------------
+
+ // Delete existing file
+ rv = localFile->Delete(false);
+ if (NS_FAILED(rv)) {
+ printf("Test 3 failed (Delete returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // write < kBitMapBytes to file
+ nsANSIFileStream * stream = new nsANSIFileStream;
+ if (!stream) {
+ printf("Test 3 failed (unable to allocate stream\n", rv);
+ goto exit;
+ }
+ NS_ADDREF(stream);
+ rv = stream->Open(localFile);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(stream);
+ printf("Test 3 failed (stream->Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ bytesWritten = 0;
+ rv = stream->Write("Tell me something good.\n", 24, &bytesWritten);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(stream);
+ printf("Test 3 failed (stream->Write returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = stream->Close();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(stream);
+ printf("Test 3 failed (stream->Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+ NS_RELEASE(stream);
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 3: failed (Open erroneously succeeded)\n", rv);
+
+ (void) blockFile->Close();
+ goto exit;
+ }
+
+ printf("Test 3: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 4: Open nonexistent file (again)
+ //----------------------------------------------------------------
+
+ // Delete existing file
+ rv = localFile->Delete(false);
+ if (NS_FAILED(rv)) {
+ printf("Test 4 failed (Delete returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 4: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 4: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 5: AllocateBlocks: invalid block count (0, 5)
+ //----------------------------------------------------------------
+
+
+ startBlock = blockFile->AllocateBlocks(0);
+ if (startBlock > -1) {
+ printf("Test 5: failed (AllocateBlocks(0) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(5);
+ if (startBlock > -1) {
+ printf("Test 5: failed (AllocateBlocks(5) erroneously succeeded)\n");
+ goto exit;
+ }
+ printf("Test 5: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 6: AllocateBlocks: valid block count (1, 2, 3, 4)
+ //----------------------------------------------------------------
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock != 0) {
+ printf("Test 6: failed (AllocateBlocks(1) failed)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock != 1) {
+ printf("Test 6: failed (AllocateBlocks(2) failed)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock != 4) {
+ printf("Test 6: failed (AllocateBlocks(3) failed)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock != 8) {
+ printf("Test 6: failed (AllocateBlocks(4) failed)\n");
+ goto exit;
+ }
+
+ // blocks allocated should be 1220 3330 4444
+ printf("Test 6: passed\n"); // but bits could be mis-allocated
+
+
+
+ //----------------------------------------------------------------
+ // Test 7: VerifyAllocation
+ //----------------------------------------------------------------
+ rv = blockFile->VerifyAllocation(0,1);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(0,1) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->VerifyAllocation(1,2);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(1,2) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->VerifyAllocation(4,3);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(4,3) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->VerifyAllocation(8,4);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(8,4) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+ printf("Test 7: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 8: LastBlock
+ //----------------------------------------------------------------
+ int32_t lastBlock = blockFile->LastBlock();
+ if (lastBlock != 11) {
+ printf("Test 8: failed (LastBlock() returned: %d)\n", lastBlock);
+ goto exit;
+ }
+ printf("Test 8: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 9: DeallocateBlocks: bad startBlock ( < 0)
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(-1, 4);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 9: failed (DeallocateBlocks(-1, 4) erroneously succeeded)\n");
+ goto exit;
+ }
+ printf("Test 9: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 10: DeallocateBlocks: bad numBlocks (0, 5)
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(0, 0);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 10: failed (DeallocateBlocks(0, 0) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(0, 5);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 10: failed (DeallocateBlocks(0, 5) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ printf("Test 10: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 11: DeallocateBlocks: unallocated blocks
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(12, 1);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 11: failed (DeallocateBlocks(12, 1) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ printf("Test 11: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 12: DeallocateBlocks: 1, 2, 3, 4 (allocated in Test 6)
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(0, 1);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(12, 1) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(1, 2);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(1, 2) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(4, 3);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(4, 3) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(8, 4);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(8, 4) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // zero blocks should be allocated
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 12: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 13: Allocate/Deallocate boundary test
+ //----------------------------------------------------------------
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // fully allocate, 1 block at a time
+ for (i=0; i< kBitMapBytes * 8; ++i) {
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(1) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with full bit map
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(1) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // deallocate all the bits
+ for (i=0; i< kBitMapBytes * 8; ++i) {
+ rv = blockFile->DeallocateBlocks(i,1);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,1) returned: 0x%.8x)\n", i,rv);
+ goto exit;
+ }
+ }
+
+ // attempt deallocation beyond end of bit map
+ rv = blockFile->DeallocateBlocks(i,1);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,1) erroneously succeeded)\n", i);
+ goto exit;
+ }
+
+ // bit map should be empty
+
+ // fully allocate, 2 block at a time
+ for (i=0; i< kBitMapBytes * 8; i+=2) {
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(2) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with full bit map
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(2) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // deallocate all the bits
+ for (i=0; i< kBitMapBytes * 8; i+=2) {
+ rv = blockFile->DeallocateBlocks(i,2);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,2) returned: 0x%.8x)\n", i,rv);
+ goto exit;
+ }
+ }
+
+ // bit map should be empty
+
+ // fully allocate, 4 block at a time
+ for (i=0; i< kBitMapBytes * 8; i+=4) {
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(4) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with full bit map
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(4) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // deallocate all the bits
+ for (i=0; i< kBitMapBytes * 8; i+=4) {
+ rv = blockFile->DeallocateBlocks(i,4);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,4) returned: 0x%.8x)\n", i,rv);
+ goto exit;
+ }
+ }
+
+ // bit map should be empty
+
+ // allocate as many triple-blocks as possible
+ for (i=0; i< kBitMapBytes * 8; i+=4) {
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(3) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with "full" bit map
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(3) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // leave some blocks allocated
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 13: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 14: ValidateFile (open existing file w/size < allocated blocks
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 14: failed (Open erroneously succeeded)\n");
+ goto exit;
+ }
+
+ // Delete existing file
+ rv = localFile->Delete(false);
+ if (NS_FAILED(rv)) {
+ printf("Test 14 failed (Delete returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+ printf("Test 14: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 15: Allocate/Deallocate stress test
+ //----------------------------------------------------------------
+
+ rv = StressTest(localFile, 15, false);
+ if (NS_FAILED(rv))
+ goto exit;
+
+ printf("Test 15: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 16: WriteBlocks
+ //----------------------------------------------------------------
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ char * one = new char[256 * 1];
+ char * two = new char[256 * 2];
+ char * three = new char[256 * 3];
+ char * four = new char[256 * 4];
+ if (!one || !two || !three || !four) {
+ printf("Test 16: failed - out of memory\n");
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ memset(one, 1, 256);
+ memset(two, 2, 256 * 2);
+ memset(three, 3, 256 * 3);
+ memset(four, 4, 256 * 4);
+
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock != 0) {
+ printf("Test 16: failed (AllocateBlocks(1) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(one, startBlock, 1);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(1) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock != 1) { // starting with empy map, this allocation should begin at block 1
+ printf("Test 16: failed (AllocateBlocks(2) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(two, startBlock, 2);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(2) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock != 4) { // starting with empy map, this allocation should begin at block 4
+ printf("Test 16: failed (AllocateBlocks(3) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(three, startBlock, 3);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(3) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock != 8) { // starting with empy map, this allocation should begin at block 8
+ printf("Test 16: failed (AllocateBlocks(4) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(four, startBlock, 4);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(4) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 16: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 17: ReadBlocks
+ //----------------------------------------------------------------
+
+ rv = blockFile->ReadBlocks(one, 0, 1);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(1) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256; i++) {
+ if (one[i] != 1) {
+ printf("Test 17: failed (verifying buffer 1)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->ReadBlocks(two, 1, 2);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(2) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * 2; i++) {
+ if (two[i] != 2) {
+ printf("Test 17: failed (verifying buffer 2)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->ReadBlocks(three, 4, 3);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(3) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * 3; i++) {
+ if (three[i] != 3) {
+ printf("Test 17: failed (verifying buffer 3)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->ReadBlocks(four, 8, 4);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(4) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * 4; i++) {
+ if (four[i] != 4) {
+ printf("Test 17: failed (verifying buffer 4)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 17: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 18: ValidateFile (open existing file with blocks allocated)
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 18: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 18: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 18: passed\n");
+
+ //----------------------------------------------------------------
+ // Test 19: WriteBlocks/ReadBlocks stress
+ //----------------------------------------------------------------
+
+ rv = StressTest(localFile, 19, false);
+ if (NS_FAILED(rv))
+ goto exit;
+
+ printf("Test 19: passed\n");
+
+
+exit:
+
+ if (currentDirPath)
+ free(currentDirPath);
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ if (NS_FAILED(rv))
+ printf("Test failed: 0x%.8x\n", rv);
+
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+
+ printf("XPCOM shut down.\n\n");
+ return 0;
+}
+
diff --git a/netwerk/test/TestCachePrefixKeyParser.cpp b/netwerk/test/TestCachePrefixKeyParser.cpp
new file mode 100644
index 000000000..88e544acc
--- /dev/null
+++ b/netwerk/test/TestCachePrefixKeyParser.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "TestHarness.h"
+#include "nsILoadContextInfo.h"
+#include "../cache2/CacheFileUtils.h"
+
+int
+main(int32_t argc, char *argv[])
+{
+ nsCOMPtr<nsILoadContextInfo> info;
+ nsAutoCString key, enh;
+
+#define CHECK(a) MOZ_ASSERT(a)
+
+ info = ParseKey(NS_LITERAL_CSTRING(""));
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING(":"));
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,:"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ info = ParseKey(NS_LITERAL_CSTRING("b,:xxx"));
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 0);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ CHECK(enh.IsEmpty());
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,i123,:xxx"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,c,h***,i123,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,c,h***,i123,~enh,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ CHECK(NS_LITERAL_CSTRING("enh").Equals(enh));
+ info = ParseKey(NS_LITERAL_CSTRING("0x,1,a,b,i123,:xxx"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+
+ nsAutoCString test;
+ AppendTagWithValue(test, '~', NS_LITERAL_CSTRING("e,nh,"));
+ info = ParseKey(test, &enh, &key);
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ CHECK(NS_LITERAL_CSTRING("e,nh,").Equals(enh));
+
+ info = ParseKey(NS_LITERAL_CSTRING("a,i123,b,:xxx"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("a"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("a:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("a:xxx"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123:xxx"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123,x:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i,x,:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i:"));
+ CHECK(!info);
+
+#undef CHECK
+
+ passed("ok");
+ return 0;
+}
diff --git a/netwerk/test/TestCommon.h b/netwerk/test/TestCommon.h
new file mode 100644
index 000000000..72fb89afa
--- /dev/null
+++ b/netwerk/test/TestCommon.h
@@ -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/. */
+
+#ifndef TestCommon_h__
+#define TestCommon_h__
+
+#include <stdlib.h>
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+
+inline int test_common_init(int *argc, char ***argv)
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+static bool gKeepPumpingEvents = false;
+
+class nsQuitPumpingEvent final : public nsIRunnable {
+ ~nsQuitPumpingEvent() {}
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_IMETHOD Run() override {
+ gKeepPumpingEvents = false;
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(nsQuitPumpingEvent, nsIRunnable)
+
+static inline void PumpEvents()
+{
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ gKeepPumpingEvents = true;
+ while (gKeepPumpingEvents)
+ NS_ProcessNextEvent(thread);
+
+ NS_ProcessPendingEvents(thread);
+}
+
+static inline void QuitPumpingEvents()
+{
+ // Dispatch a task that toggles gKeepPumpingEvents so that we flush all
+ // of the pending tasks before exiting from PumpEvents.
+ nsCOMPtr<nsIRunnable> event = new nsQuitPumpingEvent();
+ NS_DispatchToMainThread(event);
+}
+
+#endif
diff --git a/netwerk/test/TestCookie.cpp b/netwerk/test/TestCookie.cpp
new file mode 100644
index 000000000..68c7ff1b3
--- /dev/null
+++ b/netwerk/test/TestCookie.cpp
@@ -0,0 +1,921 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "TestCommon.h"
+#include "TestHarness.h"
+#include "nsIServiceManager.h"
+#include "nsICookieService.h"
+#include "nsICookieManager.h"
+#include "nsICookieManager2.h"
+#include "nsICookie2.h"
+#include <stdio.h>
+#include "plstr.h"
+#include "prprf.h"
+#include "nsNetUtil.h"
+#include "nsISimpleEnumerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsStringAPI.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+
+static NS_DEFINE_CID(kCookieServiceCID, NS_COOKIESERVICE_CID);
+static NS_DEFINE_CID(kPrefServiceCID, NS_PREFSERVICE_CID);
+
+// various pref strings
+static const char kCookiesPermissions[] = "network.cookie.cookieBehavior";
+static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
+static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
+static const char kCookiesLifetimeCurrentSession[] = "network.cookie.lifetime.behavior";
+static const char kCookiesMaxPerHost[] = "network.cookie.maxPerHost";
+static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
+
+static char *sBuffer;
+
+#define OFFSET_ONE_WEEK int64_t(604800) * PR_USEC_PER_SEC
+#define OFFSET_ONE_DAY int64_t(86400) * PR_USEC_PER_SEC
+
+//Set server time or expiry time
+void
+SetTime(PRTime offsetTime,nsAutoCString& serverString,nsAutoCString& cookieString,bool expiry)
+{
+ char timeStringPreset[40];
+ PRTime CurrentTime = PR_Now();
+ PRTime SetCookieTime = CurrentTime + offsetTime;
+ PRTime SetExpiryTime;
+ if (expiry) {
+ SetExpiryTime = SetCookieTime - OFFSET_ONE_DAY;
+ } else {
+ SetExpiryTime = SetCookieTime + OFFSET_ONE_DAY;
+ }
+
+ // Set server time string
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(SetCookieTime , PR_GMTParameters, &explodedTime);
+ PR_FormatTimeUSEnglish(timeStringPreset, 40, "%c GMT", &explodedTime);
+ serverString.Assign(timeStringPreset);
+
+ // Set cookie string
+ PR_ExplodeTime(SetExpiryTime , PR_GMTParameters, &explodedTime);
+ PR_FormatTimeUSEnglish(timeStringPreset, 40, "%c GMT", &explodedTime);
+ cookieString.Replace(0, strlen("test=expiry; expires=") + strlen(timeStringPreset) + 1, "test=expiry; expires=");
+ cookieString.Append(timeStringPreset);
+}
+
+nsresult
+SetACookie(nsICookieService *aCookieService, const char *aSpec1, const char *aSpec2, const char* aCookieString, const char *aServerTime)
+{
+ nsCOMPtr<nsIURI> uri1, uri2;
+ NS_NewURI(getter_AddRefs(uri1), aSpec1);
+ if (aSpec2)
+ NS_NewURI(getter_AddRefs(uri2), aSpec2);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( for host "%s": SET )", aSpec1);
+ nsresult rv = aCookieService->SetCookieStringFromHttp(uri1, uri2, nullptr, (char *)aCookieString, aServerTime, nullptr);
+ // the following code is useless. the cookieservice blindly returns NS_OK
+ // from SetCookieString. we have to call GetCookie to see if the cookie was
+ // set correctly...
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", aCookieString);
+ }
+ return rv;
+}
+
+nsresult
+SetACookieNoHttp(nsICookieService *aCookieService, const char *aSpec, const char* aCookieString)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aSpec);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( for host "%s": SET )", aSpec);
+ nsresult rv = aCookieService->SetCookieString(uri, nullptr, (char *)aCookieString, nullptr);
+ // the following code is useless. the cookieservice blindly returns NS_OK
+ // from SetCookieString. we have to call GetCookie to see if the cookie was
+ // set correctly...
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", aCookieString);
+ }
+ return rv;
+}
+
+// returns true if cookie(s) for the given host were found; else false.
+// the cookie string is returned via aCookie.
+bool
+GetACookie(nsICookieService *aCookieService, const char *aSpec1, const char *aSpec2, char **aCookie)
+{
+ nsCOMPtr<nsIURI> uri1, uri2;
+ NS_NewURI(getter_AddRefs(uri1), aSpec1);
+ if (aSpec2)
+ NS_NewURI(getter_AddRefs(uri2), aSpec2);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( "%s": GOT )", aSpec1);
+ nsresult rv = aCookieService->GetCookieStringFromHttp(uri1, uri2, nullptr, aCookie);
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "XXX GetCookieString() failed!\n");
+ }
+ if (!*aCookie) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", *aCookie);
+ }
+ return *aCookie != nullptr;
+}
+
+// returns true if cookie(s) for the given host were found; else false.
+// the cookie string is returned via aCookie.
+bool
+GetACookieNoHttp(nsICookieService *aCookieService, const char *aSpec, char **aCookie)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aSpec);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( "%s": GOT )", aSpec);
+ nsresult rv = aCookieService->GetCookieString(uri, nullptr, aCookie);
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "XXX GetCookieString() failed!\n");
+ }
+ if (!*aCookie) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", *aCookie);
+ }
+ return *aCookie != nullptr;
+}
+
+// some #defines for comparison rules
+#define MUST_BE_NULL 0
+#define MUST_EQUAL 1
+#define MUST_CONTAIN 2
+#define MUST_NOT_CONTAIN 3
+#define MUST_NOT_EQUAL 4
+
+// a simple helper function to improve readability:
+// takes one of the #defined rules above, and performs the appropriate test.
+// true means the test passed; false means the test failed.
+static inline bool
+CheckResult(const char *aLhs, uint32_t aRule, const char *aRhs = nullptr)
+{
+ switch (aRule) {
+ case MUST_BE_NULL:
+ return !aLhs || !*aLhs;
+
+ case MUST_EQUAL:
+ return !PL_strcmp(aLhs, aRhs);
+
+ case MUST_NOT_EQUAL:
+ return PL_strcmp(aLhs, aRhs);
+
+ case MUST_CONTAIN:
+ return PL_strstr(aLhs, aRhs) != nullptr;
+
+ case MUST_NOT_CONTAIN:
+ return PL_strstr(aLhs, aRhs) == nullptr;
+
+ default:
+ return false; // failure
+ }
+}
+
+// helper function that ensures the first aSize elements of aResult are
+// true (i.e. all tests succeeded). prints the result of the tests (if any
+// tests failed, it prints the zero-based index of each failed test).
+bool
+PrintResult(const bool aResult[], uint32_t aSize)
+{
+ bool failed = false;
+ sBuffer = PR_sprintf_append(sBuffer, "*** tests ");
+ for (uint32_t i = 0; i < aSize; ++i) {
+ if (!aResult[i]) {
+ failed = true;
+ sBuffer = PR_sprintf_append(sBuffer, "%d ", i);
+ }
+ }
+ if (failed) {
+ sBuffer = PR_sprintf_append(sBuffer, "FAILED!\a\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "passed.\n");
+ }
+ return !failed;
+}
+
+void
+InitPrefs(nsIPrefBranch *aPrefBranch)
+{
+ // init some relevant prefs, so the tests don't go awry.
+ // we use the most restrictive set of prefs we can;
+ // however, we don't test third party blocking here.
+ aPrefBranch->SetIntPref(kCookiesPermissions, 0); // accept all
+ aPrefBranch->SetBoolPref(kCookiesLifetimeEnabled, true);
+ aPrefBranch->SetIntPref(kCookiesLifetimeCurrentSession, 0);
+ aPrefBranch->SetIntPref(kCookiesLifetimeDays, 1);
+ aPrefBranch->SetBoolPref(kCookieLeaveSecurityAlone, true);
+ // Set the base domain limit to 50 so we have a known value.
+ aPrefBranch->SetIntPref(kCookiesMaxPerHost, 50);
+}
+
+
+int
+main(int32_t argc, char *argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ bool allTestsPassed = true;
+
+ ScopedXPCOM xpcom("TestCookie");
+
+ {
+ nsresult rv0;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv0);
+ if (NS_FAILED(rv0)) return -1;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(kPrefServiceCID, &rv0);
+ if (NS_FAILED(rv0)) return -1;
+
+ InitPrefs(prefBranch);
+
+ bool rv[20];
+ nsCString cookie;
+
+ /* The basic idea behind these tests is the following:
+ *
+ * we set() some cookie, then try to get() it in various ways. we have
+ * several possible tests we perform on the cookie string returned from
+ * get():
+ *
+ * a) check whether the returned string is null (i.e. we got no cookies
+ * back). this is used e.g. to ensure a given cookie was deleted
+ * correctly, or to ensure a certain cookie wasn't returned to a given
+ * host.
+ * b) check whether the returned string exactly matches a given string.
+ * this is used where we want to make sure our cookie service adheres to
+ * some strict spec (e.g. ordering of multiple cookies), or where we
+ * just know exactly what the returned string should be.
+ * c) check whether the returned string contains/does not contain a given
+ * string. this is used where we don't know/don't care about the
+ * ordering of multiple cookies - we just want to make sure the cookie
+ * string contains them all, in some order.
+ *
+ * the results of each individual testing operation from CheckResult() is
+ * stored in an array of bools, which is then checked against the expected
+ * outcomes (all successes), by PrintResult(). the overall result of all
+ * tests to date is kept in |allTestsPassed|, for convenient display at the
+ * end.
+ *
+ * Interpreting the output:
+ * each setting/getting operation will print output saying exactly what
+ * it's doing and the outcome, respectively. this information is only
+ * useful for debugging purposes; the actual result of the tests is
+ * printed at the end of each block of tests. this will either be "all
+ * tests passed" or "tests X Y Z failed", where X, Y, Z are the indexes
+ * of rv (i.e. zero-based). at the conclusion of all tests, the overall
+ * passed/failed result is printed.
+ *
+ * NOTE: this testsuite is not yet comprehensive or complete, and is
+ * somewhat contrived - still under development, and needs improving!
+ */
+
+ // *** basic tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning basic tests...\n");
+
+ // test some basic variations of the domain & path
+ SetACookie(cookieService, "http://www.basic.com", nullptr, "test=basic", nullptr);
+ GetACookie(cookieService, "http://www.basic.com", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=basic");
+ GetACookie(cookieService, "http://www.basic.com/testPath/testfile.txt", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=basic");
+ GetACookie(cookieService, "http://www.basic.com./", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.basic.com.", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.basic.com./testPath/testfile.txt", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.basic2.com/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://www.basic.com", nullptr, "test=basic; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://www.basic.com/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 7) && allTestsPassed;
+
+
+ // *** domain tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning domain tests...\n");
+
+ // test some variations of the domain & path, for different domains of
+ // a domain cookie
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=domain.com", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://domain.com.", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.domain.com", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=domain.com; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=.domain.com", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://www.domain.com", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://bah.domain.com", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=.domain.com; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=.foo.domain.com", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[9] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=moose.com", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[10] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=domain.com.", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[11] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=..domain.com", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[12] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=..domain.com.", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[13] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, R"(test=taco; path="/bogus")", nullptr);
+ GetACookie(cookieService, "http://path.net/path/file", nullptr, getter_Copies(cookie));
+ rv[14] = CheckResult(cookie.get(), MUST_EQUAL, "test=taco");
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=taco; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/path/file", nullptr, getter_Copies(cookie));
+ rv[15] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 16) && allTestsPassed;
+
+
+ // *** path tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning path tests...\n");
+
+ // test some variations of the domain & path, for different paths of
+ // a path cookie
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path", nullptr);
+ GetACookie(cookieService, "http://path.net/path", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path/hithere.foo", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path?hithere/foo", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path2", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://path.net/path2/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path/", nullptr);
+ GetACookie(cookieService, "http://path.net/path", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path/; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[9] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // note that a site can set a cookie for a path it's not on.
+ // this is an intentional deviation from spec (see comments in
+ // nsCookieService::CheckPath()), so we test this functionality too
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/foo/", nullptr);
+ GetACookie(cookieService, "http://path.net/path", nullptr, getter_Copies(cookie));
+ rv[10] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://path.net/foo", nullptr, getter_Copies(cookie));
+ rv[11] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/foo/; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/foo/", nullptr, getter_Copies(cookie));
+ rv[12] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // bug 373228: make sure cookies with paths longer than 1024 bytes,
+ // and cookies with paths or names containing tabs, are rejected.
+ // the following cookie has a path > 1024 bytes explicitly specified in the cookie
+ SetACookie(cookieService, "http://path.net/", nullptr, "test=path; path=/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/", nullptr);
+ GetACookie(cookieService, "http://path.net/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", nullptr, getter_Copies(cookie));
+ rv[13] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // the following cookie has a path > 1024 bytes implicitly specified by the uri path
+ SetACookie(cookieService, "http://path.net/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/", nullptr, "test=path", nullptr);
+ GetACookie(cookieService, "http://path.net/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/", nullptr, getter_Copies(cookie));
+ rv[14] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // the following cookie includes a tab in the path
+ SetACookie(cookieService, "http://path.net/", nullptr, "test=path; path=/foo\tbar/", nullptr);
+ GetACookie(cookieService, "http://path.net/foo\tbar/", nullptr, getter_Copies(cookie));
+ rv[15] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // the following cookie includes a tab in the name
+ SetACookie(cookieService, "http://path.net/", nullptr, "test\ttabs=tab", nullptr);
+ GetACookie(cookieService, "http://path.net/", nullptr, getter_Copies(cookie));
+ rv[16] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // the following cookie includes a tab in the value - allowed
+ SetACookie(cookieService, "http://path.net/", nullptr, "test=tab\ttest", nullptr);
+ GetACookie(cookieService, "http://path.net/", nullptr, getter_Copies(cookie));
+ rv[17] = CheckResult(cookie.get(), MUST_EQUAL, "test=tab\ttest");
+ SetACookie(cookieService, "http://path.net/", nullptr, "test=tab\ttest; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/", nullptr, getter_Copies(cookie));
+ rv[18] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 19) && allTestsPassed;
+
+
+ // *** expiry & deletion tests
+ // XXX add server time str parsing tests here
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning expiry & deletion tests...\n");
+
+ // test some variations of the expiry time,
+ // and test deletion of previously set cookies
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; expires=bad", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT)", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT")", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=-20", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[9] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=60", nullptr);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "newtest=expiry; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[10] = CheckResult(cookie.get(), MUST_CONTAIN, "test=expiry");
+ rv[11] = CheckResult(cookie.get(), MUST_CONTAIN, "newtest=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=differentvalue; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[12] = CheckResult(cookie.get(), MUST_EQUAL, "newtest=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "newtest=evendifferentvalue; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[13] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://foo.expireme.org/", nullptr, "test=expiry; domain=.expireme.org; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[14] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://bar.expireme.org/", nullptr, "test=differentvalue; domain=.expireme.org; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[15] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ nsAutoCString ServerTime;
+ nsAutoCString CookieString;
+
+ SetTime(-OFFSET_ONE_WEEK, ServerTime, CookieString, true);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[16] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Set server time earlier than client time for one year + one day, and expirty time earlier than server time for one day.
+ SetTime(-(OFFSET_ONE_DAY + OFFSET_ONE_WEEK), ServerTime, CookieString, false);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[17] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Set server time later than client time for one year, and expiry time later than server time for one day.
+ SetTime(OFFSET_ONE_WEEK, ServerTime, CookieString, false);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[18] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ // Set server time later than client time for one year + one day, and expiry time earlier than server time for one day.
+ SetTime((OFFSET_ONE_DAY + OFFSET_ONE_WEEK), ServerTime, CookieString, true);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[19] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ allTestsPassed = PrintResult(rv, 20) && allTestsPassed;
+
+ // *** multiple cookie tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning multiple cookie tests...\n");
+
+ // test the setting of multiple cookies, and test the order of precedence
+ // (a later cookie overwriting an earlier one, in the same header string)
+ SetACookie(cookieService, "http://multiple.cookies/", nullptr, "test=multiple; domain=.multiple.cookies \n test=different \n test=same; domain=.multiple.cookies \n newtest=ciao \n newtest=foo; max-age=-6 \n newtest=reincarnated", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=multiple");
+ rv[1] = CheckResult(cookie.get(), MUST_CONTAIN, "test=different");
+ rv[2] = CheckResult(cookie.get(), MUST_CONTAIN, "test=same");
+ rv[3] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=ciao");
+ rv[4] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=foo");
+ rv[5] = CheckResult(cookie.get(), MUST_CONTAIN, "newtest=reincarnated");
+ SetACookie(cookieService, "http://multiple.cookies/", nullptr, "test=expiry; domain=.multiple.cookies; max-age=0", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=same");
+ SetACookie(cookieService, "http://multiple.cookies/", nullptr, "\n test=different; max-age=0 \n", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=different");
+ SetACookie(cookieService, "http://multiple.cookies/", nullptr, "newtest=dead; max-age=0", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 9) && allTestsPassed;
+
+
+ // *** parser tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning parser tests...\n");
+
+ // test the cookie header parser, under various circumstances.
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=parser; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=parser");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=parser; domain=.parser.test; max-age=0", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=\"fubar! = foo;bar\\\";\" parser; domain=.parser.test; max-age=6\nfive; max-age=2.63,", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_CONTAIN, R"(test="fubar! = foo)");
+ rv[3] = CheckResult(cookie.get(), MUST_CONTAIN, "five");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=kill; domain=.parser.test; max-age=0 \n five; max-age=0", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // test the handling of VALUE-only cookies (see bug 169091),
+ // i.e. "six" should assume an empty NAME, which allows other VALUE-only
+ // cookies to overwrite it
+ SetACookie(cookieService, "http://parser.test/", nullptr, "six", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "six");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "seven", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "seven");
+ SetACookie(cookieService, "http://parser.test/", nullptr, " =eight", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "eight");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=six", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[9] = CheckResult(cookie.get(), MUST_CONTAIN, "test=six");
+
+ allTestsPassed = PrintResult(rv, 10) && allTestsPassed;
+
+
+ // *** path ordering tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning path ordering tests...\n");
+
+ // test that cookies are returned in path order - longest to shortest.
+ // if the header doesn't specify a path, it's taken from the host URI.
+ SetACookie(cookieService, "http://multi.path.tests/", nullptr, "test1=path; path=/one/two/three", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/", nullptr, "test2=path; path=/one \n test3=path; path=/one/two/three/four \n test4=path; path=/one/two \n test5=path; path=/one/two/", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/", nullptr, "test6=path", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/six/", nullptr, "test7=path; path=", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/", nullptr, "test8=path; path=/", nullptr);
+ GetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/six/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test7=path; test6=path; test3=path; test1=path; test5=path; test4=path; test2=path; test8=path");
+
+ allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+
+
+ // *** httponly tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning httponly tests...\n");
+
+ // Since this cookie is NOT set via http, setting it fails
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=httponly; httponly");
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Since this cookie is set via http, it can be retrieved
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=httponly");
+ // ... but not by web content
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Non-Http cookies should not replace HttpOnly cookies
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=not-httponly");
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=httponly");
+ // ... and, if an HttpOnly cookie already exists, should not be set at all
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Non-Http cookies should not delete HttpOnly cookies
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=httponly; max-age=-1");
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "test=httponly");
+ // ... but HttpOnly cookies should
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Non-Httponly cookies can replace HttpOnly cookies when set over http
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=not-httponly", nullptr);
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "test=not-httponly");
+ // scripts should not be able to set httponly cookies by replacing an existing non-httponly cookie
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=not-httponly", nullptr);
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=httponly; httponly");
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_EQUAL, "test=not-httponly");
+
+ allTestsPassed = PrintResult(rv, 9) && allTestsPassed;
+
+
+ // *** Cookie prefix tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning cookie prefix tests...\n");
+
+ // prefixed cookies can't be set from insecure HTTP
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Secure-test1=test", nullptr);
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Secure-test2=test; secure", nullptr);
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Host-test1=test", nullptr);
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Host-test2=test; secure", nullptr);
+ GetACookie(cookieService, "http://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // prefixed cookies won't be set without the secure flag
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Secure-test=test", nullptr);
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Host-test=test", nullptr);
+ GetACookie(cookieService, "https://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // prefixed cookies can be set when done correctly
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Secure-test=test; secure", nullptr);
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Host-test=test; secure", nullptr);
+ GetACookie(cookieService, "https://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_CONTAIN, "__Secure-test=test");
+ rv[3] = CheckResult(cookie.get(), MUST_CONTAIN, "__Host-test=test");
+
+ // but when set must not be returned to the host insecurely
+ GetACookie(cookieService, "http://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // Host-prefixed cookies cannot specify a domain
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-a=test; secure; domain=prefixed.test", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-b=test; secure; domain=.prefixed.test", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-c=test; secure; domain=host.prefixed.test", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-d=test; secure; domain=.host.prefixed.test", nullptr);
+ GetACookie(cookieService, "https://host.prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // Host-prefixed cookies can only have a path of "/"
+ SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-e=test; secure", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-f=test; secure; path=/", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-g=test; secure; path=/some", nullptr);
+ GetACookie(cookieService, "https://host.prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "__Host-f=test");
+
+ allTestsPassed = PrintResult(rv, 7) && allTestsPassed;
+
+ // *** leave-secure-alone tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning leave-secure-alone tests...\n");
+
+ // testing items 0 & 1 for 3.1 of spec Deprecate modification of ’secure’
+ // cookies from non-secure origins
+ SetACookie(cookieService, "http://www.security.test/", nullptr, "test=non-security; secure", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "https://www.security.test/path/", nullptr, "test=security; secure; path=/path/", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/", getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=security");
+ // testing items 2 & 3 & 4 for 3.2 of spec Deprecate modification of ’secure’
+ // cookies from non-secure origins
+ // Secure site can modify cookie value
+ SetACookie(cookieService, "https://www.security.test/path/", nullptr, "test=security2; secure; path=/path/", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/", getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=security2");
+ // If new cookie contains same name, same host and partially matching path with
+ // an existing security cookie on non-security site, it can't modify an existing
+ // security cookie.
+ SetACookie(cookieService, "http://www.security.test/path/foo/", nullptr, "test=non-security; path=/path/foo", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/foo/", getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=security2");
+ // Non-secure cookie can set by same name, same host and non-matching path.
+ SetACookie(cookieService, "http://www.security.test/bar/", nullptr, "test=non-security; path=/bar", nullptr);
+ GetACookieNoHttp(cookieService, "http://www.security.test/bar/", getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_EQUAL, "test=non-security");
+ // Modify value and downgrade secure level.
+ SetACookie(cookieService, "https://www.security.test/", nullptr, "test_modify_cookie=security-cookie; secure; domain=.security.test", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "test_modify_cookie=security-cookie");
+ SetACookie(cookieService, "https://www.security.test/", nullptr, "test_modify_cookie=non-security-cookie; domain=.security.test", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "test_modify_cookie=non-security-cookie");
+ // Test the non-security cookie can set when domain or path not same to secure cookie of same name.
+ SetACookie(cookieService, "https://www.security.test/", nullptr, "test=security3", nullptr);
+ GetACookieNoHttp(cookieService, "http://www.security.test/", getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_CONTAIN, "test=security3");
+ SetACookie(cookieService, "http://www.security.test/", nullptr, "test=non-security2; domain=security.test", nullptr);
+ GetACookieNoHttp(cookieService, "http://www.security.test/", getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_CONTAIN, "test=non-security2");
+
+ allTestsPassed = PrintResult(rv, 9) && allTestsPassed;
+
+ // *** nsICookieManager{2} interface tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning nsICookieManager{2} interface tests...\n");
+ nsCOMPtr<nsICookieManager> cookieMgr = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv0);
+ if (NS_FAILED(rv0)) return -1;
+ nsCOMPtr<nsICookieManager2> cookieMgr2 = do_QueryInterface(cookieMgr);
+ if (!cookieMgr2) return -1;
+
+ mozilla::NeckoOriginAttributes attrs;
+
+ // first, ensure a clean slate
+ rv[0] = NS_SUCCEEDED(cookieMgr->RemoveAll());
+ // add some cookies
+ rv[1] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
+ NS_LITERAL_CSTRING("/foo"), // path
+ NS_LITERAL_CSTRING("test1"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MAX, // expiry time
+ &attrs)); // originAttributes
+ rv[2] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
+ NS_LITERAL_CSTRING("/foo"), // path
+ NS_LITERAL_CSTRING("test2"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ true, // is httponly
+ true, // is session
+ PR_Now() / PR_USEC_PER_SEC + 2, // expiry time
+ &attrs)); // originAttributes
+ rv[3] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"), // domain
+ NS_LITERAL_CSTRING("/rabbit"), // path
+ NS_LITERAL_CSTRING("test3"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MAX, // expiry time
+ &attrs)); // originAttributes
+ // confirm using enumerator
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv[4] = NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator)));
+ int32_t i = 0;
+ bool more;
+ nsCOMPtr<nsICookie2> expiredCookie, newDomainCookie;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> cookie;
+ if (NS_FAILED(enumerator->GetNext(getter_AddRefs(cookie)))) break;
+ ++i;
+
+ // keep tabs on the second and third cookies, so we can check them later
+ nsCOMPtr<nsICookie2> cookie2(do_QueryInterface(cookie));
+ if (!cookie2) break;
+ nsAutoCString name;
+ cookie2->GetName(name);
+ if (name.EqualsLiteral("test2"))
+ expiredCookie = cookie2;
+ else if (name.EqualsLiteral("test3"))
+ newDomainCookie = cookie2;
+ }
+ rv[5] = i == 3;
+ // check the httpOnly attribute of the second cookie is honored
+ GetACookie(cookieService, "http://cookiemgr.test/foo/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_CONTAIN, "test2=yes");
+ GetACookieNoHttp(cookieService, "http://cookiemgr.test/foo/", getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test2=yes");
+ // check CountCookiesFromHost()
+ uint32_t hostCookies = 0;
+ rv[8] = NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)) &&
+ hostCookies == 2;
+ // check CookieExistsNative() using the third cookie
+ bool found;
+ rv[9] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)) && found;
+
+
+ // remove the cookie, block it, and ensure it can't be added again
+ rv[10] = NS_SUCCEEDED(cookieMgr->RemoveNative(NS_LITERAL_CSTRING("new.domain"), // domain
+ NS_LITERAL_CSTRING("test3"), // name
+ NS_LITERAL_CSTRING("/rabbit"), // path
+ true, // is blocked
+ &attrs)); // originAttributes
+ rv[11] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)) && !found;
+ rv[12] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"), // domain
+ NS_LITERAL_CSTRING("/rabbit"), // path
+ NS_LITERAL_CSTRING("test3"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MIN, // expiry time
+ &attrs)); // originAttributes
+ rv[13] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)) && !found;
+ // sleep four seconds, to make sure the second cookie has expired
+ PR_Sleep(4 * PR_TicksPerSecond());
+ // check that both CountCookiesFromHost() and CookieExistsNative() count the
+ // expired cookie
+ rv[14] = NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)) &&
+ hostCookies == 2;
+ rv[15] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(expiredCookie, &attrs, &found)) && found;
+ // double-check RemoveAll() using the enumerator
+ rv[16] = NS_SUCCEEDED(cookieMgr->RemoveAll());
+ rv[17] = NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator))) &&
+ NS_SUCCEEDED(enumerator->HasMoreElements(&more)) &&
+ !more;
+
+ allTestsPassed = PrintResult(rv, 18) && allTestsPassed;
+
+
+ // *** eviction and creation ordering tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning eviction and creation ordering tests...\n");
+
+ // test that cookies are
+ // a) returned by order of creation time (oldest first, newest last)
+ // b) evicted by order of lastAccessed time, if the limit on cookies per host (50) is reached
+ nsAutoCString name;
+ nsAutoCString expected;
+ for (int32_t i = 0; i < 60; ++i) {
+ name = NS_LITERAL_CSTRING("test");
+ name.AppendInt(i);
+ name += NS_LITERAL_CSTRING("=creation");
+ SetACookie(cookieService, "http://creation.ordering.tests/", nullptr, name.get(), nullptr);
+
+ if (i >= 10) {
+ expected += name;
+ if (i < 59)
+ expected += NS_LITERAL_CSTRING("; ");
+ }
+ }
+ GetACookie(cookieService, "http://creation.ordering.tests/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, expected.get());
+
+ allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+
+ // *** eviction and creation ordering tests after enable network.cookie.leave-secure-alone
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning eviction and creation tests after enable nework.cookie.leave-secure-alone...\n");
+ // reset cookie
+ cookieMgr->RemoveAll();
+
+ for (int32_t i = 0; i < 60; ++i) {
+ name = NS_LITERAL_CSTRING("test");
+ name.AppendInt(i);
+ name += NS_LITERAL_CSTRING("=delete_non_security");
+
+ // Create 50 cookies that include the secure flag.
+ if (i < 50) {
+ name += NS_LITERAL_CSTRING("; secure");
+ SetACookie(cookieService, "https://creation.ordering.tests/", nullptr, name.get(), nullptr);
+ } else {
+ // non-security cookies will be removed beside the latest cookie that be created.
+ SetACookie(cookieService, "http://creation.ordering.tests/", nullptr, name.get(), nullptr);
+ }
+ }
+ GetACookie(cookieService, "http://creation.ordering.tests/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+
+
+ // XXX the following are placeholders: add these tests please!
+ // *** "noncompliant cookie" tests
+ // *** IP address tests
+ // *** speed tests
+
+
+ sBuffer = PR_sprintf_append(sBuffer, "\n*** Result: %s!\n\n", allTestsPassed ? "all tests passed" : "TEST(S) FAILED");
+ }
+
+ if (!allTestsPassed) {
+ // print the entire log
+ printf("%s", sBuffer);
+ return 1;
+ }
+
+ PR_smprintf_free(sBuffer);
+ sBuffer = nullptr;
+
+ return 0;
+}
+
+// Stubs to make this test happy
+
+mozilla::dom::OriginAttributesDictionary::OriginAttributesDictionary()
+ : mAppId(0),
+ mInIsolatedMozBrowser(false),
+ mPrivateBrowsingId(0),
+ mUserContextId(0)
+{}
diff --git a/netwerk/test/TestDNS.cpp b/netwerk/test/TestDNS.cpp
new file mode 100644
index 000000000..94a09a62a
--- /dev/null
+++ b/netwerk/test/TestDNS.cpp
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "nsIServiceManager.h"
+#include "nsPIDNSService.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsICancelable.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsNetCID.h"
+#include "prinrval.h"
+#include "prthread.h"
+#include "prnetdb.h"
+#include "nsXPCOM.h"
+#include "nsServiceManagerUtils.h"
+
+class myDNSListener : public nsIDNSListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ myDNSListener(const char *host, int32_t index)
+ : mHost(host)
+ , mIndex(index) {}
+
+ NS_IMETHOD OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status) override
+ {
+ printf("%d: OnLookupComplete called [host=%s status=%x rec=%p]\n",
+ mIndex, mHost.get(), static_cast<uint32_t>(status), (void*)rec);
+
+ if (NS_SUCCEEDED(status)) {
+ nsAutoCString buf;
+
+ rec->GetCanonicalName(buf);
+ printf("%d: canonname=%s\n", mIndex, buf.get());
+
+ bool hasMore;
+ while (NS_SUCCEEDED(rec->HasMore(&hasMore)) && hasMore) {
+ rec->GetNextAddrAsString(buf);
+ printf("%d: => %s\n", mIndex, buf.get());
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ virtual ~myDNSListener() = default;
+
+ nsCString mHost;
+ int32_t mIndex;
+};
+
+
+NS_IMPL_ISUPPORTS(myDNSListener, nsIDNSListener)
+
+static bool IsAscii(const char *s)
+{
+ for (; *s; ++s) {
+ if (*s & 0x80)
+ return false;
+ }
+
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ int sleepLen = 10; // default: 10 seconds
+
+ if (argc == 1) {
+ printf("usage: TestDNS [-N] hostname1 [hostname2 ...]\n");
+ return -1;
+ }
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ nsCOMPtr<nsPIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns)
+ return -1;
+
+ if (argv[1][0] == '-') {
+ sleepLen = atoi(argv[1]+1);
+ argv++;
+ argc--;
+ }
+
+ for (int j=0; j<2; ++j) {
+ for (int i=1; i<argc; ++i) {
+ // assume non-ASCII input is given in the native charset
+ nsAutoCString hostBuf;
+ if (IsAscii(argv[i]))
+ hostBuf.Assign(argv[i]);
+ else
+ hostBuf = NS_ConvertUTF16toUTF8(NS_ConvertASCIItoUTF16(argv[i]));
+
+ nsCOMPtr<nsIDNSListener> listener = new myDNSListener(argv[i], i);
+
+ nsCOMPtr<nsICancelable> req;
+ nsresult rv = dns->AsyncResolve(hostBuf,
+ nsIDNSService::RESOLVE_CANONICAL_NAME,
+ listener, nullptr, getter_AddRefs(req));
+ if (NS_FAILED(rv))
+ printf("### AsyncResolve failed [rv=%x]\n",
+ static_cast<uint32_t>(rv));
+ }
+
+ printf("main thread sleeping for %d seconds...\n", sleepLen);
+ PR_Sleep(PR_SecondsToInterval(sleepLen));
+ }
+
+ printf("shutting down main thread...\n");
+ dns->Shutdown();
+ }
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestDNSDaemon.cpp b/netwerk/test/TestDNSDaemon.cpp
new file mode 100644
index 000000000..fa347aa0c
--- /dev/null
+++ b/netwerk/test/TestDNSDaemon.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+
+#if defined(AIX) || defined(__linux)
+#include <sys/select.h> // for fd_set
+#endif
+
+#if defined(__linux)
+// Didn't find gettdtablehi() or gettdtablesize() on linux. Using FD_SETSIZE
+#define getdtablehi() FD_SETSIZE
+#else
+#define getdtablehi() getdtablesize()
+
+// If you find a system doesn't have getdtablesize try #define getdtablesize
+// to FD_SETSIZE. And if you encounter a system that doesn't even have
+// FD_SETSIZE, just grab your ankles and use 255.
+#endif
+
+
+#include "nspr.h"
+#include "nsCRT.h"
+#include "unix_dns.h"
+
+struct sockaddr_un unix_addr;
+
+int async_dns_lookup(char* hostName)
+{
+ fprintf(stderr, "start async_dns_lookup\n");
+ int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (socket_fd == -1) {
+ fprintf(stderr, "socket returned error.\n");
+ return -1;
+ }
+
+ unix_addr.sun_family = AF_UNIX;
+ strcpy(unix_addr.sun_path, DNS_SOCK_NAME);
+
+ int err = connect(socket_fd,(struct sockaddr*)&unix_addr, sizeof(unix_addr));
+ if (err == -1) {
+ fprintf(stderr, "connect failed (errno = %d).\n",errno);
+ close(socket_fd);
+ return -1;
+ }
+
+ char buf[256];
+ strcpy(buf, "lookup: ");
+ strcpy(&buf[8], hostName);
+
+ err = send(socket_fd, buf, strlen(buf)+1, 0);
+ if (err < 0)
+ fprintf(stderr, "send(%s) returned error (errno=%d).\n",buf, errno);
+
+ // receive 4 byte ID
+ err = recv(socket_fd, buf, 256, 0);
+ if (err < 0)
+ fprintf(stderr, "recv() returned error (errno=%d).\n", errno);
+ else
+ {
+ // printf("recv() returned %d bytes.");
+ int id = *(int *)buf;
+ fprintf(stderr, "id: %d\n", id);
+ }
+
+ return socket_fd;
+}
+
+static char *
+string_trim(char *s)
+{
+ char *s2;
+ if (!s) return 0;
+ s2 = s + strlen(s) - 1;
+ while (s2 > s && (*s2 == '\n' || *s2 == '\r' || *s2 == ' ' || *s2 == '\t'))
+ *s2-- = 0;
+ while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
+ s++;
+ return s;
+}
+
+hostent *
+bytesToHostent(char *buf)
+{
+ int i;
+ // int size = 0;
+ int len, aliasCount, addressCount;
+ int addrtype, addrlength;
+ char* p = buf;
+ char s[1024];
+
+ len = *(int *)p; // length of name
+ p += sizeof(int); // advance past name length
+
+ memcpy(s, p, len); s[len] = 0;
+ fprintf(stderr, "hostname: %s\n", s);
+
+ p += len; // advance past name
+ aliasCount = *(int *)p; // number of aliases
+ p += sizeof(int); // advance past alias count
+
+ for (i=0; i<aliasCount; i++) {
+ len = *(int *)p; // length of alias name
+ p += sizeof(int); // advance past alias name length
+
+ memcpy(s, p, len); s[len] = 0;
+ fprintf(stderr, "alias: %s\n", s);
+
+ p += len; // advance past alias name
+ }
+
+ addrtype = *(int *)p;
+
+ fprintf(stderr, "addrtype: %d\n", addrtype);
+
+ p += sizeof(int);
+ addrlength = *(int *)p;
+
+ fprintf(stderr, "addrlength: %d\n", addrlength);
+
+ p += sizeof(int);
+ addressCount = *(int *)p;
+ p += sizeof(int);
+
+ for (i=0; i<addressCount; i++) {
+ len = *(int *)p;
+ p += sizeof(int);
+
+ fprintf(stderr, "addr len: %d\n", len);
+ fprintf(stderr, "addr : %x\n", *(int *)p);
+
+ p += len;
+ }
+
+ // size = p - buf;
+ // size += 1 + aliasCount;
+ return 0;
+}
+
+int
+main(int argc, char* argv[])
+{
+ PRStatus status;
+
+ // launch daemon
+ printf("### launch daemon...\n");
+
+ PRProcessAttr *attributes = PR_NewProcessAttr();
+ if (attributes == nullptr) {
+ printf("PR_NewProcessAttr() failed.\n");
+ return -1;
+ }
+
+ PRProcess *daemon = PR_CreateProcess("nsDnsAsyncLookup", nullptr, nullptr, attributes);
+ if (daemon == nullptr) {
+ printf("PR_CreateProcess failed.\n");
+ } else {
+ // status = PR_DetachProcess(daemon);
+ //if (status != 0)
+ // printf("PR_DetachProcess returned %d\n", status);
+ //daemon = nullptr;
+ }
+
+ PR_DestroyProcessAttr(attributes);
+
+ // create socket and connect to daemon
+ int socket_fd = 0;
+
+
+ bool notDone = true;
+ char buf[1024];
+
+ while(notDone) {
+ int status = 0;
+ fd_set fdset;
+
+ FD_ZERO(&fdset);
+ FD_SET(fileno(stdin), &fdset);
+ if (socket_fd > 0)
+ FD_SET(socket_fd, &fdset);
+
+ status = select(getdtablehi(), &fdset, 0, 0, 0);
+ if (status <= 0)
+ {
+ fprintf(stderr, "%s: select() returned %d\n", argv[0], status);
+ exit(-1);
+ }
+
+ // which fd is set?
+
+ if (FD_ISSET(fileno(stdin), &fdset))
+ {
+ char *line = fgets(buf, sizeof(buf)-1, stdin);
+ line = string_trim(line);
+
+ if(!strcmp(line, "quit") || !strcmp(line, "exit"))
+ {
+ fprintf(stderr, "bye now.\n");
+ notDone = false;
+ }
+ else if (!strncmp(line, "abort ", 6))
+ {
+ // abort id
+ }
+ else if (strchr(line, ' ') || strchr(line, '\t'))
+ {
+ fprintf(stderr, "%s: unrecognized command %s.\n", argv[0], line);
+ }
+ else
+ {
+ fprintf(stderr, "%s: looking up %s...\n", argv[0], line);
+ // initiate dns lookup
+ socket_fd = async_dns_lookup(line);
+ }
+ }
+
+ if (socket_fd && FD_ISSET(socket_fd, &fdset))
+ {
+ // read from socket, parse results
+ int size = read(socket_fd, buf, 1024);
+ if (size > 0)
+ {
+ // parse buffer into hostent
+ char *p = buf;
+ fprintf(stderr, "bytes read: %d\n", size);
+ fprintf(stderr, "response code: %d\n", *(int *)p);
+ p += sizeof(int);
+
+ for (int i=0; i < size; i++) {
+ if (!(i%8))
+ fprintf(stderr, "\n");
+ fprintf(stderr, "%2.2x ",(unsigned char)buf[i]);
+ }
+ fprintf(stderr, "\n");
+ hostent *h;
+ h = bytesToHostent(p);
+ }
+ close(socket_fd);
+ socket_fd = 0;
+ }
+ }
+
+ return 0;
+}
+
+/*
+buffer
+
+int nameLen;
+
+if (nameLen > 0)
+ char [nameLen+1] name
+
+int aliasCount
+for each alias
+ int aliasNameLen
+ char [aliasNameLen+1] aliasName
+
+int h_addrtype
+int h_length
+int addrCount
+for each addr
+ char[h_length] addr
+
+
+
+*/
diff --git a/netwerk/test/TestFileInput2.cpp b/netwerk/test/TestFileInput2.cpp
new file mode 100644
index 000000000..f51988010
--- /dev/null
+++ b/netwerk/test/TestFileInput2.cpp
@@ -0,0 +1,480 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+#include "nsCOMArray.h"
+#include "nsISimpleEnumerator.h"
+#include "prinrval.h"
+#include "nsIFileStreams.h"
+#include "nsIFileChannel.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include <stdio.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include <math.h>
+#include "prprf.h"
+#include "nsAutoLock.h"
+
+class nsTimeSampler {
+public:
+ nsTimeSampler();
+ void Reset();
+ void StartTime();
+ void EndTime();
+ void AddTime(PRIntervalTime time);
+ PRIntervalTime LastInterval() { return mLastInterval; }
+ char* PrintStats();
+protected:
+ PRIntervalTime mStartTime;
+ double mSquares;
+ double mTotalTime;
+ uint32_t mCount;
+ PRIntervalTime mLastInterval;
+};
+
+nsTimeSampler::nsTimeSampler()
+{
+ Reset();
+}
+
+void
+nsTimeSampler::Reset()
+{
+ mStartTime = 0;
+ mSquares = 0;
+ mTotalTime = 0;
+ mCount = 0;
+ mLastInterval = 0;
+}
+
+void
+nsTimeSampler::StartTime()
+{
+ mStartTime = PR_IntervalNow();
+}
+
+void
+nsTimeSampler::EndTime()
+{
+ NS_ASSERTION(mStartTime != 0, "Forgot to call StartTime");
+ PRIntervalTime endTime = PR_IntervalNow();
+ mLastInterval = endTime - mStartTime;
+ AddTime(mLastInterval);
+ mStartTime = 0;
+}
+
+void
+nsTimeSampler::AddTime(PRIntervalTime time)
+{
+ nsAutoCMonitor mon(this);
+ mTotalTime += time;
+ mSquares += (double)time * (double)time;
+ mCount++;
+}
+
+char*
+nsTimeSampler::PrintStats()
+{
+ double mean = mTotalTime / mCount;
+ double variance = fabs(mSquares / mCount - mean * mean);
+ double stddev = sqrt(variance);
+ uint32_t imean = (uint32_t)mean;
+ uint32_t istddev = (uint32_t)stddev;
+ return PR_smprintf("%d +/- %d ms",
+ PR_IntervalToMilliseconds(imean),
+ PR_IntervalToMilliseconds(istddev));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsTimeSampler gTimeSampler;
+
+typedef nsresult (*CreateFun)(nsIRunnable* *result,
+ nsIFile* inPath,
+ nsIFile* outPath,
+ uint32_t bufferSize);
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+Copy(nsIInputStream* inStr, nsIOutputStream* outStr,
+ char* buf, uint32_t bufSize, uint32_t *copyCount)
+{
+ nsresult rv;
+ while (true) {
+ uint32_t count;
+ rv = inStr->Read(buf, bufSize, &count);
+ if (NS_FAILED(rv)) return rv;
+ if (count == 0) break;
+
+ uint32_t writeCount;
+ rv = outStr->Write(buf, count, &writeCount);
+ if (NS_FAILED(rv)) return rv;
+ NS_ASSERTION(writeCount == count, "didn't write all the data");
+ *copyCount += writeCount;
+ }
+ rv = outStr->Flush();
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class FileSpecWorker : public nsIRunnable {
+public:
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ PRIntervalTime startTime = PR_IntervalNow();
+ PRIntervalTime endTime;
+ nsCOMPtr<nsIInputStream> inStr;
+ nsCOMPtr<nsIOutputStream> outStr;
+ uint32_t copyCount = 0;
+
+ // Open the input stream:
+ nsCOMPtr<nsIInputStream> fileIn;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileIn), mInPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewBufferedInputStream(getter_AddRefs(inStr), fileIn, 65535);
+ if (NS_FAILED(rv)) return rv;
+
+ // Open the output stream:
+ nsCOMPtr<nsIOutputStream> fileOut;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOut),
+ mOutPath,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(outStr), fileOut, 65535);
+ if (NS_FAILED(rv)) return rv;
+
+ // Copy from one to the other
+ rv = Copy(inStr, outStr, mBuffer, mBufferSize, &copyCount);
+ if (NS_FAILED(rv)) return rv;
+
+ endTime = PR_IntervalNow();
+ gTimeSampler.AddTime(endTime - startTime);
+
+ return rv;
+ }
+
+ NS_DECL_ISUPPORTS
+
+ FileSpecWorker()
+ : mInPath(nullptr), mOutPath(nullptr), mBuffer(nullptr),
+ mBufferSize(0)
+ {
+ }
+
+ nsresult Init(nsIFile* inPath, nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ mInPath = inPath;
+ mOutPath = outPath;
+ mBuffer = new char[bufferSize];
+ mBufferSize = bufferSize;
+ return (mInPath && mOutPath && mBuffer)
+ ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ static nsresult Create(nsIRunnable* *result,
+ nsIFile* inPath,
+ nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ FileSpecWorker* worker = new FileSpecWorker();
+ if (worker == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(worker);
+
+ nsresult rv = worker->Init(inPath, outPath, bufferSize);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(worker);
+ return rv;
+ }
+ *result = worker;
+ return NS_OK;
+ }
+
+ virtual ~FileSpecWorker() {
+ delete[] mBuffer;
+ }
+
+protected:
+ nsCOMPtr<nsIFile> mInPath;
+ nsCOMPtr<nsIFile> mOutPath;
+ char* mBuffer;
+ uint32_t mBufferSize;
+};
+
+NS_IMPL_ISUPPORTS(FileSpecWorker, nsIRunnable)
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include "nsIIOService.h"
+#include "nsIChannel.h"
+
+class FileChannelWorker : public nsIRunnable {
+public:
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ PRIntervalTime startTime = PR_IntervalNow();
+ PRIntervalTime endTime;
+ uint32_t copyCount = 0;
+ nsCOMPtr<nsIFileChannel> inCh;
+ nsCOMPtr<nsIFileChannel> outCh;
+ nsCOMPtr<nsIInputStream> inStr;
+ nsCOMPtr<nsIOutputStream> outStr;
+
+ rv = NS_NewLocalFileChannel(getter_AddRefs(inCh), mInPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = inCh->Open(getter_AddRefs(inStr));
+ if (NS_FAILED(rv)) return rv;
+
+ //rv = NS_NewLocalFileChannel(getter_AddRefs(outCh), mOutPath);
+ //if (NS_FAILED(rv)) return rv;
+
+ //rv = outCh->OpenOutputStream(0, -1, 0, getter_AddRefs(outStr));
+ //if (NS_FAILED(rv)) return rv;
+
+ // Copy from one to the other
+ rv = Copy(inStr, outStr, mBuffer, mBufferSize, &copyCount);
+ if (NS_FAILED(rv)) return rv;
+
+ endTime = PR_IntervalNow();
+ gTimeSampler.AddTime(endTime - startTime);
+
+ return rv;
+ }
+
+ NS_DECL_ISUPPORTS
+
+ FileChannelWorker()
+ : mInPath(nullptr), mOutPath(nullptr), mBuffer(nullptr),
+ mBufferSize(0)
+ {
+ }
+
+ nsresult Init(nsIFile* inPath, nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ mInPath = inPath;
+ mOutPath = outPath;
+ mBuffer = new char[bufferSize];
+ mBufferSize = bufferSize;
+ return (mInPath && mOutPath && mBuffer)
+ ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ static nsresult Create(nsIRunnable* *result,
+ nsIFile* inPath,
+ nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ FileChannelWorker* worker = new FileChannelWorker();
+ if (worker == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(worker);
+
+ nsresult rv = worker->Init(inPath, outPath, bufferSize);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(worker);
+ return rv;
+ }
+ *result = worker;
+ return NS_OK;
+ }
+
+ virtual ~FileChannelWorker() {
+ delete[] mBuffer;
+ }
+
+protected:
+ nsCOMPtr<nsIFile> mInPath;
+ nsCOMPtr<nsIFile> mOutPath;
+ char* mBuffer;
+ uint32_t mBufferSize;
+};
+
+NS_IMPL_ISUPPORTS(FileChannelWorker, nsIRunnable)
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test(CreateFun create, uint32_t count,
+ nsIFile* inDirSpec, nsIFile* outDirSpec, uint32_t bufSize)
+{
+ nsresult rv;
+ uint32_t i;
+
+ nsAutoCString inDir;
+ nsAutoCString outDir;
+ (void)inDirSpec->GetNativePath(inDir);
+ (void)outDirSpec->GetNativePath(outDir);
+ printf("###########\nTest: from %s to %s, bufSize = %d\n",
+ inDir.get(), outDir.get(), bufSize);
+ gTimeSampler.Reset();
+ nsTimeSampler testTime;
+ testTime.StartTime();
+
+ nsCOMArray<nsIThread> threads;
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = inDirSpec->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetDirectoryEntries failed");
+
+ i = 0;
+ bool hasMore;
+ while (i < count && NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> next;
+ rv = entries->GetNext(getter_AddRefs(next));
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIFile> inSpec = do_QueryInterface(next, &rv);
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIFile> outSpec;
+ rv = outDirSpec->Clone(getter_AddRefs(outSpec)); // don't munge the original
+ if (NS_FAILED(rv)) goto done;
+
+ nsAutoCString leafName;
+ rv = inSpec->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) goto done;
+
+ rv = outSpec->AppendNative(leafName);
+ if (NS_FAILED(rv)) goto done;
+
+ bool exists;
+ rv = outSpec->Exists(&exists);
+ if (NS_FAILED(rv)) goto done;
+
+ if (exists) {
+ rv = outSpec->Remove(false);
+ if (NS_FAILED(rv)) goto done;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsCOMPtr<nsIRunnable> worker;
+ rv = create(getter_AddRefs(worker),
+ inSpec,
+ outSpec,
+ bufSize);
+ if (NS_FAILED(rv)) goto done;
+
+ rv = NS_NewThread(getter_AddRefs(thread), worker, 0, PR_JOINABLE_THREAD);
+ if (NS_FAILED(rv)) goto done;
+
+ bool inserted = threads.InsertObjectAt(thread, i);
+ NS_ASSERTION(inserted, "not inserted");
+
+ i++;
+ }
+
+ uint32_t j;
+ for (j = 0; j < i; j++) {
+ nsIThread* thread = threads.ObjectAt(j);
+ thread->Join();
+ }
+
+ done:
+ NS_ASSERTION(rv == NS_OK, "failed");
+
+ testTime.EndTime();
+ char* testStats = testTime.PrintStats();
+ char* workerStats = gTimeSampler.PrintStats();
+ printf(" threads = %d\n work time = %s,\n test time = %s\n",
+ i, workerStats, testStats);
+ PR_smprintf_free(workerStats);
+ PR_smprintf_free(testStats);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <in-dir> <out-dir>\n", argv[0]);
+ return -1;
+ }
+ char* inDir = argv[1];
+ char* outDir = argv[2];
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ nsCOMPtr<nsIFile> inDirFile;
+ rv = NS_NewNativeLocalFile(nsDependentCString(inDir), false, getter_AddRefs(inDirFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> outDirFile;
+ rv = NS_NewNativeLocalFile(nsDependentCString(outDir), false, getter_AddRefs(outDirFile));
+ if (NS_FAILED(rv)) return rv;
+
+ CreateFun create = FileChannelWorker::Create;
+ Test(create, 1, inDirFile, outDirFile, 16 * 1024);
+#if 1
+ printf("FileChannelWorker *****************************\n");
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+#endif
+ create = FileSpecWorker::Create;
+ printf("FileSpecWorker ********************************\n");
+#if 1
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+#endif
+#if 1
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+#endif
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/test/TestIDN.cpp b/netwerk/test/TestIDN.cpp
new file mode 100644
index 000000000..77d57f14d
--- /dev/null
+++ b/netwerk/test/TestIDN.cpp
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include <stdio.h>
+#include "nsIIDNService.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsStringAPI.h"
+
+int main(int argc, char **argv) {
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ // Test case from RFC 3492 (7.1 - Simplified Chinese)
+ const char plain[] =
+ "\xE4\xBB\x96\xE4\xBB\xAC\xE4\xB8\xBA\xE4\xBB\x80\xE4\xB9\x88\xE4\xB8\x8D\xE8\xAF\xB4\xE4\xB8\xAD\xE6\x96\x87";
+ const char encoded[] = "xn--ihqwcrb4cv8a8dqg056pqjye";
+
+ nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ NS_ASSERTION(converter, "idnSDK not installed!");
+ if (converter) {
+ nsAutoCString buf;
+ nsresult rv = converter->ConvertUTF8toACE(NS_LITERAL_CSTRING(plain), buf);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error ConvertUTF8toACE");
+ NS_ASSERTION(buf.Equals(NS_LITERAL_CSTRING(encoded)),
+ "encode result incorrect");
+ printf("encoded = %s\n", buf.get());
+
+ buf.Truncate();
+ rv = converter->ConvertACEtoUTF8(NS_LITERAL_CSTRING(encoded), buf);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error ConvertACEtoUTF8");
+ NS_ASSERTION(buf.Equals(NS_LITERAL_CSTRING(plain)),
+ "decode result incorrect");
+ printf("decoded = ");
+ NS_ConvertUTF8toUTF16 utf(buf);
+ const char16_t *u = utf.get();
+ for (int i = 0; u[i]; i++) {
+ printf("U+%.4X ", u[i]);
+ }
+ printf("\n");
+
+ bool isAce;
+ rv = converter->IsACE(NS_LITERAL_CSTRING("www.xn--ihqwcrb4cv8a8dqg056pqjye.com"), &isAce);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error IsACE");
+ NS_ASSERTION(isAce, "IsACE incorrect result");
+ }
+ return 0;
+}
diff --git a/netwerk/test/TestIOThreads.cpp b/netwerk/test/TestIOThreads.cpp
new file mode 100644
index 000000000..94aade27e
--- /dev/null
+++ b/netwerk/test/TestIOThreads.cpp
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "nsXPCOM.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "mozilla/Logging.h"
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+class nsIOEvent : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsIOEvent(int i) : mIndex(i) {}
+
+ NS_IMETHOD Run() override {
+ LOG(("Run [%d]\n", mIndex));
+ return NS_OK;
+ }
+
+private:
+ int mIndex;
+};
+NS_IMPL_ISUPPORTS(nsIOEvent, nsIRunnable)
+
+static nsresult RunTest()
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ for (int i=0; i<10; ++i) {
+ nsCOMPtr<nsIRunnable> event = new nsIOEvent(i);
+ LOG(("Dispatch %d\n", i));
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ gTestLog = PR_NewLogModule("Test");
+
+ rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = RunTest();
+ if (NS_FAILED(rv))
+ LOG(("RunTest failed [rv=%x]\n", rv));
+
+ LOG(("sleeping main thread for 2 seconds...\n"));
+ PR_Sleep(PR_SecondsToInterval(2));
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestIncrementalDownload.cpp b/netwerk/test/TestIncrementalDownload.cpp
new file mode 100644
index 000000000..19308b2fa
--- /dev/null
+++ b/netwerk/test/TestIncrementalDownload.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 cin 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/. */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include "TestCommon.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIIncrementalDownload.h"
+#include "nsIRequestObserver.h"
+#include "nsIProgressEventSink.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "prprf.h"
+#include "prenv.h"
+#include "mozilla/Attributes.h"
+
+//-----------------------------------------------------------------------------
+
+class FetchObserver final : public nsIRequestObserver
+ , public nsIProgressEventSink
+{
+ ~FetchObserver() = default;
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIPROGRESSEVENTSINK
+};
+
+NS_IMPL_ISUPPORTS(FetchObserver, nsIRequestObserver,
+ nsIProgressEventSink)
+
+NS_IMETHODIMP
+FetchObserver::OnStartRequest(nsIRequest *request, nsISupports *context)
+{
+ printf("FetchObserver::OnStartRequest\n");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchObserver::OnProgress(nsIRequest *request, nsISupports *context,
+ int64_t progress, int64_t progressMax)
+{
+ printf("FetchObserver::OnProgress [%" PRId64 "/%" PRId64 "]\n",
+ progress, progressMax);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchObserver::OnStatus(nsIRequest *request, nsISupports *context,
+ nsresult status, const char16_t *statusText)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchObserver::OnStopRequest(nsIRequest *request, nsISupports *context,
+ nsresult status)
+{
+ printf("FetchObserver::OnStopRequest [status=%x]\n",
+ static_cast<uint32_t>(status));
+
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+static nsresult
+DoIncrementalFetch(const char *uriSpec, const char *resultPath, int32_t chunkSize,
+ int32_t interval)
+{
+ nsCOMPtr<nsIFile> resultFile;
+ nsresult rv = NS_NewNativeLocalFile(nsDependentCString(resultPath),
+ false, getter_AddRefs(resultFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIRequestObserver> observer = new FetchObserver();
+ if (!observer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIIncrementalDownload> download =
+ do_CreateInstance(NS_INCREMENTALDOWNLOAD_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = download->Init(uri, resultFile, chunkSize, interval);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = download->Start(observer, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ PumpEvents();
+ return NS_OK;
+}
+
+int
+main(int argc, char **argv)
+{
+ if (PR_GetEnv("MOZ_BREAK_ON_MAIN"))
+ NS_BREAK();
+
+ if (argc < 5) {
+ fprintf(stderr, "USAGE: TestIncrementalDownload <url> <file> <chunksize> <interval-in-seconds>\n");
+ return -1;
+ }
+
+ nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return -1;
+
+ int32_t chunkSize = atoi(argv[3]);
+ int32_t interval = atoi(argv[4]);
+
+ rv = DoIncrementalFetch(argv[1], argv[2], chunkSize, interval);
+ if (NS_FAILED(rv))
+ fprintf(stderr, "ERROR: DoIncrementalFetch failed [%x]\n",
+ static_cast<uint32_t>(rv));
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestMakeAbs.cpp b/netwerk/test/TestMakeAbs.cpp
new file mode 100644
index 000000000..51c21edd9
--- /dev/null
+++ b/netwerk/test/TestMakeAbs.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "nspr.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIIOService.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIURI.h"
+
+static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
+
+nsresult ServiceMakeAbsolute(nsIURI *baseURI, char *relativeInfo, char **_retval) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> serv(do_GetService(kIOServiceCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return serv->MakeAbsolute(relativeInfo, baseURI, _retval);
+}
+
+nsresult URLMakeAbsolute(nsIURI *baseURI, char *relativeInfo, char **_retval) {
+ return baseURI->MakeAbsolute(relativeInfo, _retval);
+}
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv = NS_OK;
+ if (argc < 4) {
+ printf("usage: %s int (loop count) baseURL relativeSpec\n", argv[0]);
+ return -1;
+ }
+
+ uint32_t cycles = atoi(argv[1]);
+ char *base = argv[2];
+ char *rel = argv[3];
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ nsCOMPtr<nsIIOService> serv(do_GetService(kIOServiceCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = serv->NewURI(base, nullptr, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ char *absURLString;
+ uint32_t i = 0;
+ while (i++ < cycles) {
+ rv = ServiceMakeAbsolute(uri, rel, &absURLString);
+ if (NS_FAILED(rv)) return rv;
+ free(absURLString);
+
+ rv = URLMakeAbsolute(uri, rel, &absURLString);
+ free(absURLString);
+ }
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return rv;
+}
diff --git a/netwerk/test/TestNamedPipeService.cpp b/netwerk/test/TestNamedPipeService.cpp
new file mode 100644
index 000000000..7d3f2b58c
--- /dev/null
+++ b/netwerk/test/TestNamedPipeService.cpp
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "TestCommon.h"
+#include "TestHarness.h"
+
+#include <Windows.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "nsINamedPipeService.h"
+#include "nsNetCID.h"
+
+#define PIPE_NAME "\\\\.\\pipe\\TestNPS"
+#define TEST_STR "Hello World"
+
+using namespace mozilla;
+
+class nsNamedPipeDataObserver : public nsINamedPipeDataObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINAMEDPIPEDATAOBSERVER
+
+ explicit nsNamedPipeDataObserver(HANDLE aPipe);
+
+ int Read(void* aBuffer, uint32_t aSize);
+ int Write(const void* aBuffer, uint32_t aSize);
+
+ uint32_t Transferred() const { return mBytesTransferred; }
+
+private:
+ ~nsNamedPipeDataObserver() = default;
+
+ HANDLE mPipe;
+ OVERLAPPED mOverlapped;
+ Atomic<uint32_t> mBytesTransferred;
+ Monitor mMonitor;
+};
+
+NS_IMPL_ISUPPORTS(nsNamedPipeDataObserver, nsINamedPipeDataObserver)
+
+nsNamedPipeDataObserver::nsNamedPipeDataObserver(HANDLE aPipe)
+ : mPipe(aPipe)
+ , mOverlapped()
+ , mBytesTransferred(0)
+ , mMonitor("named-pipe")
+{
+ mOverlapped.hEvent = CreateEventA(nullptr, TRUE, TRUE, "named-pipe");
+}
+
+int
+nsNamedPipeDataObserver::Read(void* aBuffer, uint32_t aSize)
+{
+ DWORD bytesRead = 0;
+ if (!ReadFile(mPipe, aBuffer, aSize, &bytesRead, &mOverlapped)) {
+ switch(GetLastError()) {
+ case ERROR_IO_PENDING:
+ {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+ }
+ if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesRead, FALSE)) {
+ fail("GetOverlappedResult failed");
+ return -1;
+ }
+ if (mBytesTransferred != bytesRead) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+
+ break;
+ default:
+ fail("ReadFile error %d", GetLastError());
+ return -1;
+ }
+ } else {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+
+ if (mBytesTransferred != bytesRead) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+ }
+
+ mBytesTransferred = 0;
+ passed("[read] match");
+ return bytesRead;
+}
+
+int
+nsNamedPipeDataObserver::Write(const void* aBuffer, uint32_t aSize)
+{
+ DWORD bytesWritten = 0;
+ if (!WriteFile(mPipe, aBuffer, aSize, &bytesWritten, &mOverlapped)) {
+ switch(GetLastError()) {
+ case ERROR_IO_PENDING:
+ {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+ }
+ if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesWritten, FALSE)) {
+ fail("GetOverlappedResult failed");
+ return -1;
+ }
+ if (mBytesTransferred != bytesWritten) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+
+ break;
+ default:
+ fail("WriteFile error %d", GetLastError());
+ return -1;
+ }
+ } else {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+
+ if (mBytesTransferred != bytesWritten) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+ }
+
+ mBytesTransferred = 0;
+ passed("[write] match");
+ return bytesWritten;
+}
+
+NS_IMETHODIMP
+nsNamedPipeDataObserver::OnDataAvailable(uint32_t aBytesTransferred,
+ void *aOverlapped)
+{
+ if (aOverlapped != &mOverlapped) {
+ fail("invalid overlapped object");
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD bytesTransferred = 0;
+ BOOL ret = GetOverlappedResult(mPipe,
+ reinterpret_cast<LPOVERLAPPED>(aOverlapped),
+ &bytesTransferred,
+ FALSE);
+
+ if (!ret) {
+ fail("GetOverlappedResult failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bytesTransferred != aBytesTransferred) {
+ fail("GetOverlappedResult mismatch");
+ return NS_ERROR_FAILURE;
+ }
+
+ mBytesTransferred += aBytesTransferred;
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Notify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNamedPipeDataObserver::OnError(uint32_t aError, void *aOverlapped)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+BOOL CreateAndConnectInstance(LPOVERLAPPED aOverlapped, LPHANDLE aPipe);
+BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped);
+
+BOOL CreateAndConnectInstance(LPOVERLAPPED aOverlapped, LPHANDLE aPipe)
+{
+ if (!aPipe) {
+ fail("Parameter aPipe is NULL\n");
+ return FALSE;
+ }
+
+ // FIXME: adjust parameters
+ *aPipe = CreateNamedPipeA(
+ PIPE_NAME,
+ PIPE_ACCESS_DUPLEX |
+ FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_MESSAGE |
+ PIPE_READMODE_MESSAGE |
+ PIPE_WAIT,
+ 1,
+ 65536,
+ 65536,
+ 3000,
+ NULL);
+
+ if (*aPipe == INVALID_HANDLE_VALUE) {
+ fail("CreateNamedPipe failed [%d]\n", GetLastError());
+ return FALSE;
+ }
+
+ return ConnectToNewClient(*aPipe, aOverlapped);
+}
+
+BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped)
+{
+ if (ConnectNamedPipe(aPipe, aOverlapped)) {
+ fail("Unexpected, overlapped ConnectNamedPipe() always returns 0.\n");
+ return FALSE;
+ }
+
+ switch (GetLastError())
+ {
+ case ERROR_IO_PENDING:
+ return TRUE;
+
+ case ERROR_PIPE_CONNECTED:
+ if (SetEvent(aOverlapped->hEvent))
+ break;
+
+ default: // error
+ fail("ConnectNamedPipe failed [%d]\n", GetLastError());
+ break;
+ }
+
+ return FALSE;
+}
+
+static nsresult
+CreateNamedPipe(LPHANDLE aServer, LPHANDLE aClient)
+{
+ OVERLAPPED overlapped;
+ overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
+ BOOL ret;
+
+ ret = CreateAndConnectInstance(&overlapped, aServer);
+ if (!ret) {
+ fail("pipe server should be pending");
+ return NS_ERROR_FAILURE;
+ }
+
+ *aClient = CreateFileA(PIPE_NAME,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,
+ nullptr);
+
+ if (*aClient == INVALID_HANDLE_VALUE) {
+ fail("Unable to create pipe client");
+ CloseHandle(*aServer);
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD pipeMode = PIPE_READMODE_MESSAGE;
+ if (!SetNamedPipeHandleState(*aClient, &pipeMode, nullptr, nullptr)) {
+ fail("SetNamedPipeHandleState error (%d)", GetLastError());
+ CloseHandle(*aServer);
+ CloseHandle(*aClient);
+ return NS_ERROR_FAILURE;
+ }
+
+ WaitForSingleObjectEx(overlapped.hEvent, INFINITE, TRUE);
+
+ return NS_OK;
+}
+
+int
+main(int32_t argc, char* argv[])
+{
+ ScopedXPCOM xpcom("NamedPipeService");
+ if (xpcom.failed()) {
+ fail("Unable to initalize XPCOM.");
+ return -1;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsINamedPipeService> svc =
+ do_GetService(NS_NAMEDPIPESERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ fail("Unable to create named pipe service");
+ return -1;
+ }
+
+ HANDLE readPipe, writePipe;
+ if (NS_FAILED(rv = CreateNamedPipe(&readPipe, &writePipe))) {
+ fail("Unable to create pipes %d", GetLastError());
+ return -1;
+ }
+
+ RefPtr<nsNamedPipeDataObserver> readObserver =
+ new nsNamedPipeDataObserver(readPipe);
+ RefPtr<nsNamedPipeDataObserver> writeObserver =
+ new nsNamedPipeDataObserver(writePipe);
+
+ if (NS_WARN_IF(NS_FAILED(svc->AddDataObserver(readPipe, readObserver)))) {
+ fail("Unable to add read data observer");
+ return -1;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(svc->AddDataObserver(writePipe, writeObserver)))) {
+ fail("Unable to add read data observer");
+ return -1;
+ }
+
+ if (writeObserver->Write(TEST_STR, sizeof(TEST_STR)) != sizeof(TEST_STR)) {
+ fail("write error");
+ return -1;
+ }
+
+ char buffer[sizeof(TEST_STR)];
+ if (readObserver->Read(buffer, sizeof(buffer)) != sizeof(TEST_STR)) {
+ fail("read error");
+ return -1;
+ }
+
+ if (strcmp(buffer, TEST_STR) != 0) {
+ fail("I/O mismatch");
+ return -1;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(svc->RemoveDataObserver(readPipe, readObserver)))) {
+ fail("Unable to remove read data observer");
+ return -1;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(svc->RemoveDataObserver(writePipe, writeObserver)))) {
+ fail("Unable to remove read data observer");
+ return -1;
+ }
+
+ passed("Finish");
+ return 0;
+} \ No newline at end of file
diff --git a/netwerk/test/TestOpen.cpp b/netwerk/test/TestOpen.cpp
new file mode 100644
index 000000000..43d518b4e
--- /dev/null
+++ b/netwerk/test/TestOpen.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "TestCommon.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Unused.h"
+#include "nsIScriptSecurityManager.h"
+
+#include <stdio.h>
+
+using namespace mozilla;
+
+/*
+ * Test synchronous Open.
+ */
+
+#define RETURN_IF_FAILED(rv, what) \
+ PR_BEGIN_MACRO \
+ if (NS_FAILED(rv)) { \
+ printf(what ": failed - %08x\n", static_cast<uint32_t>(rv)); \
+ return -1; \
+ } \
+ PR_END_MACRO
+
+int
+main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return -1;
+
+ char buf[256];
+
+ if (argc != 3) {
+ printf("Usage: TestOpen url filename\nLoads a URL using ::Open, writing it to a file\n");
+ return -1;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIInputStream> stream;
+
+ rv = NS_NewURI(getter_AddRefs(uri), argv[1]);
+ RETURN_IF_FAILED(rv, "NS_NewURI");
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ RETURN_IF_FAILED(rv, "Couldn't get script security manager!");
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ RETURN_IF_FAILED(rv, "Couldn't get system principal!");
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ RETURN_IF_FAILED(rv, "NS_NewChannel");
+
+ rv = channel->Open2(getter_AddRefs(stream));
+ RETURN_IF_FAILED(rv, "channel->Open2()");
+
+ FILE* outfile = fopen(argv[2], "wb");
+ if (!outfile) {
+ printf("error opening %s\n", argv[2]);
+ return 1;
+ }
+
+ uint32_t read;
+ while (NS_SUCCEEDED(stream->Read(buf, sizeof(buf), &read)) && read) {
+ Unused << fwrite(buf, 1, read, outfile);
+ }
+ printf("Done\n");
+
+ fclose(outfile);
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestOverlappedIO.cpp b/netwerk/test/TestOverlappedIO.cpp
new file mode 100644
index 000000000..db954b4f6
--- /dev/null
+++ b/netwerk/test/TestOverlappedIO.cpp
@@ -0,0 +1,313 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+#include <stdio.h>
+#include <signal.h>
+#include <algorithm>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nspr.h"
+#include "nscore.h"
+#include "nsISocketTransportService.h"
+#include "nsIEventQueueService.h"
+#include "nsIServiceManager.h"
+#include "nsITransport.h"
+#include "nsIRequest.h"
+#include "nsIStreamProvider.h"
+#include "nsIStreamListener.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsIByteArrayInputStream.h"
+
+static PRLogModuleInfo *gTestSocketIOLog;
+#define LOG(args) MOZ_LOG(gTestSocketIOLog, mozilla::LogLevel::Debug, args)
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
+
+static PRTime gElapsedTime;
+static int gKeepRunning = 1;
+static nsIEventQueue* gEventQ = nullptr;
+
+//
+//----------------------------------------------------------------------------
+// Test Listener
+//----------------------------------------------------------------------------
+//
+
+class TestListener : public nsIStreamListener
+{
+public:
+ TestListener() { }
+ virtual ~TestListener() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+NS_IMPL_ISUPPORTS(TestListener,
+ nsIRequestObserver,
+ nsIStreamListener);
+
+NS_IMETHODIMP
+TestListener::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestListener::OnStartRequest\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnDataAvailable(nsIRequest* request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ LOG(("TestListener::OnDataAvailable [offset=%llu length=%u]\n",
+ aSourceOffset, aLength));
+ char buf[1025];
+ uint32_t amt;
+ while (1) {
+ aIStream->Read(buf, 1024, &amt);
+ if (amt == 0)
+ break;
+ buf[amt] = '\0';
+ //puts(buf);
+ printf("read %d bytes\n", amt);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestListener::OnStopRequest [aStatus=%x]\n", aStatus));
+ //gKeepRunning = 0;
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Test Provider
+//----------------------------------------------------------------------------
+//
+
+class TestProvider : public nsIStreamProvider
+{
+public:
+ TestProvider(char *data);
+ virtual ~TestProvider();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMPROVIDER
+
+protected:
+ char *mData;
+ uint32_t mOffset;
+ uint32_t mDataLen;
+ uint32_t mRequestCount;
+};
+
+NS_IMPL_ISUPPORTS(TestProvider,
+ nsIStreamProvider,
+ nsIRequestObserver)
+
+TestProvider::TestProvider(char *data)
+{
+ mData = data;
+ mDataLen = strlen(data);
+ mOffset = 0;
+ mRequestCount = 0;
+ LOG(("Constructing TestProvider [this=%p]\n", this));
+}
+
+TestProvider::~TestProvider()
+{
+ LOG(("Destroying TestProvider [this=%p]\n", this));
+}
+
+NS_IMETHODIMP
+TestProvider::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestProvider::OnStartRequest [this=%p]\n", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestProvider::OnStopRequest [status=%x]\n", aStatus));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnDataWritable(nsIRequest *request, nsISupports *context,
+ nsIOutputStream *output, uint32_t offset, uint32_t count)
+{
+ LOG(("TestProvider::OnDataWritable [offset=%u, count=%u]\n", offset, count));
+
+ // Stop at 5 requests
+ if (mRequestCount == 5)
+ return NS_BASE_STREAM_CLOSED;
+
+ uint32_t writeCount, amount;
+ amount = std::min(count, mDataLen - mOffset);
+ nsresult rv = output->Write(mData + mOffset, amount, &writeCount);
+ if (NS_SUCCEEDED(rv)) {
+ printf("wrote %u bytes\n", writeCount);
+ mOffset += writeCount;
+ if (mOffset == mDataLen) {
+ printf("done sending packet %u\n", mRequestCount);
+ mOffset = 0;
+ mRequestCount++;
+ }
+ }
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Synchronous IO
+//----------------------------------------------------------------------------
+//
+nsresult
+WriteRequest(nsIOutputStream *os, const char *request)
+{
+ LOG(("WriteRequest [request=%s]\n", request));
+ uint32_t n;
+ return os->Write(request, strlen(request), &n);
+}
+
+nsresult
+ReadResponse(nsIInputStream *is)
+{
+ uint32_t bytesRead;
+ char buf[2048];
+ do {
+ is->Read(buf, sizeof(buf), &bytesRead);
+ if (bytesRead > 0)
+ fwrite(buf, 1, bytesRead, stdout);
+ } while (bytesRead > 0);
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Startup...
+//----------------------------------------------------------------------------
+//
+
+void
+sighandler(int sig)
+{
+ LOG(("got signal: %d\n", sig));
+ NS_BREAK();
+}
+
+void
+usage(char **argv)
+{
+ printf("usage: %s <host> <path>\n", argv[0]);
+ exit(1);
+}
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ signal(SIGSEGV, sighandler);
+
+ gTestSocketIOLog = PR_NewLogModule("TestSocketIO");
+
+ if (argc < 3)
+ usage(argv);
+
+ char *hostName = argv[1];
+ char *fileName = argv[2];
+ int port = 80;
+
+ // Create the Event Queue for this thread...
+ nsCOMPtr<nsIEventQueueService> eventQService =
+ do_GetService(kEventQueueServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: event queue service!");
+ return rv;
+ }
+
+ rv = eventQService->CreateMonitoredThreadEventQueue();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: thread event queue!");
+ return rv;
+ }
+
+ eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, &gEventQ);
+
+ // Create the Socket transport service...
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport service!");
+ return rv;
+ }
+
+ char *buffer = PR_smprintf("GET %s HTTP/1.1" CRLF
+ "host: %s" CRLF
+ "user-agent: Mozilla/5.0 (X11; N; Linux 2.2.16-22smp i686; en-US; m18) Gecko/20001220" CRLF
+ "accept: */*" CRLF
+ "accept-language: en" CRLF
+ "accept-encoding: gzip,deflate,compress,identity" CRLF
+ "keep-alive: 300" CRLF
+ "connection: keep-alive" CRLF
+ CRLF,
+ fileName, hostName);
+ LOG(("Request [\n%s]\n", buffer));
+
+ // Create the socket transport...
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateTransport(hostName, port, nullptr, -1, 0, 0, getter_AddRefs(transport));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport!");
+ return rv;
+ }
+
+ gElapsedTime = PR_Now();
+
+ nsCOMPtr<nsIRequest> writeRequest, readRequest;
+
+ rv = transport->AsyncWrite(new TestProvider(buffer), nullptr, 0, 0, 0, getter_AddRefs(writeRequest));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed calling: AsyncWrite!");
+ return rv;
+ }
+ rv = transport->AsyncRead(new TestListener(), nullptr, 0, 0, 0, getter_AddRefs(readRequest));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed calling: AsyncWrite!");
+ return rv;
+ }
+
+ // Enter the message pump
+ while ( gKeepRunning ) {
+ PLEvent *gEvent;
+ gEventQ->WaitForEvent(&gEvent);
+ gEventQ->HandleEvent(gEvent);
+ }
+
+ PRTime endTime;
+ endTime = PR_Now();
+ LOG(("Elapsed time: %d\n", (int32_t)(endTime/1000UL - gElapsedTime/1000UL)));
+
+ sts->Shutdown();
+ return 0;
+}
diff --git a/netwerk/test/TestProtocols.cpp b/netwerk/test/TestProtocols.cpp
new file mode 100644
index 000000000..7bec14f85
--- /dev/null
+++ b/netwerk/test/TestProtocols.cpp
@@ -0,0 +1,890 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et cindent: */
+/* 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/. */
+
+/*
+ The TestProtocols tests the basic protocols architecture and can
+ be used to test individual protocols as well. If this grows too
+ big then we should split it to individual protocols.
+
+ -Gagan Saksena 04/29/99
+*/
+
+#include "TestCommon.h"
+#include <algorithm>
+
+#include <stdio.h>
+#ifdef WIN32
+#include <windows.h>
+#endif
+#ifdef XP_UNIX
+#include <unistd.h>
+#endif
+#include "nspr.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIIOService.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIInputStream.h"
+#include "nsCRT.h"
+#include "nsIChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIURL.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDNSService.h"
+#include "nsIAuthPrompt.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPropertyBag2.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsITimedChannel.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "nsIScriptSecurityManager.h"
+
+#include "nsISimpleEnumerator.h"
+#include "nsStringAPI.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "NetwerkTestLogging.h"
+
+using namespace mozilla;
+
+namespace TestProtocols {
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
+
+//static PRTime gElapsedTime; // enable when we time it...
+static int gKeepRunning = 0;
+static bool gVerbose = false;
+static bool gAskUserForInput = false;
+static bool gResume = false;
+static uint64_t gStartAt = 0;
+
+static const char* gEntityID;
+
+//-----------------------------------------------------------------------------
+// Set proxy preferences for testing
+//-----------------------------------------------------------------------------
+
+static nsresult
+SetHttpProxy(const char *proxy)
+{
+ const char *colon = strchr(proxy, ':');
+ if (!colon)
+ {
+ NS_WARNING("invalid proxy token; use host:port");
+ return NS_ERROR_UNEXPECTED;
+ }
+ int port = atoi(colon + 1);
+ if (port == 0)
+ {
+ NS_WARNING("invalid proxy port; must be an integer");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsAutoCString proxyHost;
+ proxyHost = Substring(proxy, colon);
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs)
+ {
+ prefs->SetCharPref("network.proxy.http", proxyHost.get());
+ prefs->SetIntPref("network.proxy.http_port", port);
+ prefs->SetIntPref("network.proxy.type", 1); // manual proxy config
+ }
+ LOG(("connecting via proxy=%s:%d\n", proxyHost.get(), port));
+ return NS_OK;
+}
+
+static nsresult
+SetPACFile(const char* pacURL)
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs)
+ {
+ prefs->SetCharPref("network.proxy.autoconfig_url", pacURL);
+ prefs->SetIntPref("network.proxy.type", 2); // PAC file
+ }
+ LOG(("connecting using PAC file %s\n", pacURL));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Timing information
+//-----------------------------------------------------------------------------
+
+void PrintTimingInformation(nsITimedChannel* channel) {
+#define PRINT_VALUE(property) \
+ { \
+ PRTime value; \
+ channel->Get##property(&value); \
+ if (value) { \
+ PRExplodedTime exploded; \
+ PR_ExplodeTime(value, PR_LocalTimeParameters, &exploded); \
+ char buf[256]; \
+ PR_FormatTime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &exploded); \
+ LOG((" " #property ":\t%s (%i usec)", buf, exploded.tm_usec)); \
+ } else { \
+ LOG((" " #property ":\t0")); \
+ } \
+ }
+ LOG(("Timing data:"));
+ PRINT_VALUE(ChannelCreationTime)
+ PRINT_VALUE(AsyncOpenTime)
+ PRINT_VALUE(DomainLookupStartTime)
+ PRINT_VALUE(DomainLookupEndTime)
+ PRINT_VALUE(ConnectStartTime)
+ PRINT_VALUE(ConnectEndTime)
+ PRINT_VALUE(RequestStartTime)
+ PRINT_VALUE(ResponseStartTime)
+ PRINT_VALUE(ResponseEndTime)
+ PRINT_VALUE(CacheReadStartTime)
+ PRINT_VALUE(CacheReadEndTime)
+}
+
+//-----------------------------------------------------------------------------
+// HeaderVisitor
+//-----------------------------------------------------------------------------
+
+class HeaderVisitor : public nsIHttpHeaderVisitor
+{
+ virtual ~HeaderVisitor() = default;
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPHEADERVISITOR
+
+ HeaderVisitor() { }
+};
+NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor)
+
+NS_IMETHODIMP
+HeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value)
+{
+ LOG((" %s: %s\n",
+ PromiseFlatCString(header).get(),
+ PromiseFlatCString(value).get()));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// URLLoadInfo
+//-----------------------------------------------------------------------------
+
+class URLLoadInfo : public nsISupports
+{
+ virtual ~URLLoadInfo();
+
+public:
+
+ explicit URLLoadInfo(const char* aUrl);
+
+ // ISupports interface...
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ const char* Name() { return mURLString.get(); }
+ int64_t mBytesRead;
+ PRTime mTotalTime;
+ PRTime mConnectTime;
+ nsCString mURLString;
+};
+
+URLLoadInfo::URLLoadInfo(const char *aUrl) : mURLString(aUrl)
+{
+ mBytesRead = 0;
+ mConnectTime = mTotalTime = PR_Now();
+}
+
+URLLoadInfo::~URLLoadInfo() = default;
+
+
+NS_IMPL_ISUPPORTS0(URLLoadInfo)
+
+//-----------------------------------------------------------------------------
+// TestChannelEventSink
+//-----------------------------------------------------------------------------
+
+class TestChannelEventSink : public nsIChannelEventSink
+{
+ virtual ~TestChannelEventSink();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICHANNELEVENTSINK
+
+ TestChannelEventSink();
+};
+
+TestChannelEventSink::TestChannelEventSink()
+{
+}
+
+TestChannelEventSink::~TestChannelEventSink() = default;
+
+
+NS_IMPL_ISUPPORTS(TestChannelEventSink, nsIChannelEventSink)
+
+NS_IMETHODIMP
+TestChannelEventSink::AsyncOnChannelRedirect(nsIChannel *channel,
+ nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ LOG(("\n+++ TestChannelEventSink::OnChannelRedirect (with flags %x) +++\n",
+ flags));
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TestAuthPrompt
+//-----------------------------------------------------------------------------
+
+class TestAuthPrompt : public nsIAuthPrompt
+{
+ virtual ~TestAuthPrompt();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTHPROMPT
+
+ TestAuthPrompt();
+};
+
+NS_IMPL_ISUPPORTS(TestAuthPrompt, nsIAuthPrompt)
+
+TestAuthPrompt::TestAuthPrompt()
+{
+}
+
+TestAuthPrompt::~TestAuthPrompt() = default;
+
+NS_IMETHODIMP
+TestAuthPrompt::Prompt(const char16_t *dialogTitle,
+ const char16_t *text,
+ const char16_t *passwordRealm,
+ uint32_t savePassword,
+ const char16_t *defaultText,
+ char16_t **result,
+ bool *_retval)
+{
+ *_retval = false;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TestAuthPrompt::PromptUsernameAndPassword(const char16_t *dialogTitle,
+ const char16_t *dialogText,
+ const char16_t *passwordRealm,
+ uint32_t savePassword,
+ char16_t **user,
+ char16_t **pwd,
+ bool *_retval)
+{
+ NS_ConvertUTF16toUTF8 text(passwordRealm);
+ printf("* --------------------------------------------------------------------------- *\n");
+ printf("* Authentication Required [%s]\n", text.get());
+ printf("* --------------------------------------------------------------------------- *\n");
+
+ char buf[256];
+ int n;
+
+ printf("Enter username: ");
+ Unused << fgets(buf, sizeof(buf), stdin);
+ n = strlen(buf);
+ buf[n-1] = '\0'; // trim trailing newline
+ *user = NS_StringCloneData(NS_ConvertUTF8toUTF16(buf));
+
+ const char *p;
+#if defined(XP_UNIX) && !defined(ANDROID)
+ p = getpass("Enter password: ");
+#else
+ printf("Enter password: ");
+ fgets(buf, sizeof(buf), stdin);
+ n = strlen(buf);
+ buf[n-1] = '\0'; // trim trailing newline
+ p = buf;
+#endif
+ *pwd = NS_StringCloneData(NS_ConvertUTF8toUTF16(p));
+
+ // zap buf
+ memset(buf, 0, sizeof(buf));
+
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestAuthPrompt::PromptPassword(const char16_t *dialogTitle,
+ const char16_t *text,
+ const char16_t *passwordRealm,
+ uint32_t savePassword,
+ char16_t **pwd,
+ bool *_retval)
+{
+ *_retval = false;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// InputTestConsumer
+//-----------------------------------------------------------------------------
+
+class InputTestConsumer : public nsIStreamListener
+{
+ virtual ~InputTestConsumer();
+
+public:
+
+ explicit InputTestConsumer(URLLoadInfo* aURLLoadInfo);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+private:
+ URLLoadInfo* mURLLoadInfo;
+};
+
+InputTestConsumer::InputTestConsumer(URLLoadInfo* aURLLoadInfo)
+: mURLLoadInfo(aURLLoadInfo)
+{
+ NS_IF_ADDREF(mURLLoadInfo);
+}
+
+InputTestConsumer::~InputTestConsumer()
+{
+ NS_RELEASE(mURLLoadInfo);
+}
+
+NS_IMPL_ISUPPORTS(InputTestConsumer, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
+{
+ LOG(("InputTestConsumer::OnStartRequest\n"));
+
+ NS_ASSERTION(!context, "context needs to be null when calling asyncOpen2");
+
+ if (mURLLoadInfo)
+ mURLLoadInfo->mConnectTime = PR_Now() - mURLLoadInfo->mConnectTime;
+
+ if (gVerbose) {
+ LOG(("\nStarted loading: %s\n", mURLLoadInfo ? mURLLoadInfo->Name() : "UNKNOWN URL"));
+ }
+
+ nsAutoCString value;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsresult status;
+ channel->GetStatus(&status);
+ LOG(("Channel Status: %08x\n", status));
+ if (NS_SUCCEEDED(status)) {
+ LOG(("Channel Info:\n"));
+
+ channel->GetName(value);
+ LOG(("\tName: %s\n", value.get()));
+
+ channel->GetContentType(value);
+ LOG(("\tContent-Type: %s\n", value.get()));
+
+ channel->GetContentCharset(value);
+ LOG(("\tContent-Charset: %s\n", value.get()));
+
+ int64_t length = -1;
+ if (NS_SUCCEEDED(channel->GetContentLength(&length))) {
+ LOG(("\tContent-Length: %lld\n", length));
+ } else {
+ LOG(("\tContent-Length: Unknown\n"));
+ }
+ }
+
+ nsCOMPtr<nsISupports> owner;
+ channel->GetOwner(getter_AddRefs(owner));
+ LOG(("\tChannel Owner: %x\n", owner.get()));
+ }
+
+ nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(request);
+ if (props) {
+ nsCOMPtr<nsIURI> foo;
+ props->GetPropertyAsInterface(NS_LITERAL_STRING("test.foo"),
+ NS_GET_IID(nsIURI),
+ getter_AddRefs(foo));
+ if (foo) {
+ LOG(("\ttest.foo: %s\n", foo->GetSpecOrDefault().get()));
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInt(do_QueryInterface(request));
+ if (httpChannelInt) {
+ uint32_t majorVer, minorVer;
+ nsresult rv = httpChannelInt->GetResponseVersion(&majorVer, &minorVer);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("HTTP Response version: %u.%u\n", majorVer, minorVer));
+ }
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
+ if (httpChannel) {
+ auto *visitor = new HeaderVisitor();
+ if (!visitor)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(visitor);
+
+ LOG(("HTTP request headers:\n"));
+ httpChannel->VisitRequestHeaders(visitor);
+
+ LOG(("HTTP response headers:\n"));
+ httpChannel->VisitResponseHeaders(visitor);
+
+ NS_RELEASE(visitor);
+ }
+
+ nsCOMPtr<nsIResumableChannel> resChannel = do_QueryInterface(request);
+ if (resChannel) {
+ LOG(("Resumable entity identification:\n"));
+ nsAutoCString entityID;
+ nsresult rv = resChannel->GetEntityID(entityID);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("\t|%s|\n", entityID.get()));
+ }
+ else {
+ LOG(("\t<none>\n"));
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputTestConsumer::OnDataAvailable(nsIRequest *request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ NS_ASSERTION(!context, "context needs to be null when calling asyncOpen2");
+
+ char buf[1025];
+ uint32_t amt, size;
+ nsresult rv;
+
+ while (aLength) {
+ size = std::min<uint32_t>(aLength, sizeof(buf));
+
+ rv = aIStream->Read(buf, size, &amt);
+ if (NS_FAILED(rv)) {
+ NS_ASSERTION((NS_BASE_STREAM_WOULD_BLOCK != rv),
+ "The stream should never block.");
+ return rv;
+ }
+ if (gVerbose) {
+ buf[amt] = '\0';
+ puts(buf);
+ }
+ if (mURLLoadInfo) {
+ mURLLoadInfo->mBytesRead += amt;
+ }
+
+ aLength -= amt;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputTestConsumer::OnStopRequest(nsIRequest *request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("InputTestConsumer::OnStopRequest [status=%x]\n", aStatus));
+
+ if (mURLLoadInfo) {
+ uint32_t httpStatus;
+ bool bHTTPURL = false;
+
+ mURLLoadInfo->mTotalTime = PR_Now() - mURLLoadInfo->mTotalTime;
+
+ double readTime = ((mURLLoadInfo->mTotalTime-mURLLoadInfo->mConnectTime)/1000.0)/1000.0;
+
+ nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(request));
+ if (pHTTPCon) {
+ pHTTPCon->GetResponseStatus(&httpStatus);
+ bHTTPURL = true;
+ }
+
+ LOG(("\nFinished loading: %s Status Code: %x\n", mURLLoadInfo->Name(), aStatus));
+ if (bHTTPURL) {
+ LOG(("\tHTTP Status: %u\n", httpStatus));
+ }
+ if (NS_ERROR_UNKNOWN_HOST == aStatus ||
+ NS_ERROR_UNKNOWN_PROXY_HOST == aStatus) {
+ LOG(("\tDNS lookup failed.\n"));
+ }
+ LOG(("\tTime to connect: %.3f seconds\n", (mURLLoadInfo->mConnectTime/1000.0)/1000.0));
+ LOG(("\tTime to read: %.3f seconds.\n", readTime));
+ LOG(("\tRead: %lld bytes.\n", mURLLoadInfo->mBytesRead));
+ if (mURLLoadInfo->mBytesRead == int64_t(0)) {
+ } else if (readTime > 0.0) {
+ LOG(("\tThroughput: %.0f bps.\n", (double)(mURLLoadInfo->mBytesRead*int64_t(8))/readTime));
+ } else {
+ LOG(("\tThroughput: REAL FAST!!\n"));
+ }
+
+ nsCOMPtr<nsITimedChannel> timed(do_QueryInterface(request));
+ if (timed)
+ PrintTimingInformation(timed);
+ } else {
+ LOG(("\nFinished loading: UNKNOWN URL. Status Code: %x\n", aStatus));
+ }
+
+ if (--gKeepRunning == 0)
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NotificationCallbacks
+//-----------------------------------------------------------------------------
+
+class NotificationCallbacks final : public nsIInterfaceRequestor {
+
+ ~NotificationCallbacks() = default;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ NotificationCallbacks() {
+ }
+
+ NS_IMETHOD GetInterface(const nsIID& iid, void* *result) override {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ TestChannelEventSink *sink;
+
+ sink = new TestChannelEventSink();
+ if (sink == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(sink);
+ rv = sink->QueryInterface(iid, result);
+ NS_RELEASE(sink);
+ }
+
+ if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ TestAuthPrompt *prompt;
+
+ prompt = new TestAuthPrompt();
+ if (prompt == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(prompt);
+ rv = prompt->QueryInterface(iid, result);
+ NS_RELEASE(prompt);
+ }
+ return rv;
+ }
+};
+
+NS_IMPL_ISUPPORTS(NotificationCallbacks, nsIInterfaceRequestor)
+
+//-----------------------------------------------------------------------------
+// helpers...
+//-----------------------------------------------------------------------------
+
+nsresult StartLoadingURL(const char* aUrlString)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> pService(do_GetService(kIOServiceCID, &rv));
+ if (pService) {
+ nsCOMPtr<nsIURI> pURL;
+
+ rv = pService->NewURI(nsDependentCString(aUrlString), nullptr, nullptr, getter_AddRefs(pURL));
+ if (NS_FAILED(rv)) {
+ LOG(("ERROR: NewURI failed for %s [rv=%x]\n", aUrlString));
+ return rv;
+ }
+ nsCOMPtr<nsIChannel> pChannel;
+
+ auto* callbacks = new NotificationCallbacks();
+ if (!callbacks) {
+ LOG(("Failed to create a new consumer!"));
+ return NS_ERROR_OUT_OF_MEMORY;;
+ }
+ NS_ADDREF(callbacks);
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Async reading thru the calls of the event sink interface
+ rv = NS_NewChannel(getter_AddRefs(pChannel),
+ pURL,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // loadGroup
+ callbacks,
+ nsIRequest::LOAD_NORMAL,
+ pService);
+
+ NS_RELEASE(callbacks);
+ if (NS_FAILED(rv)) {
+ LOG(("ERROR: NS_NewChannel failed for %s [rv=%x]\n", aUrlString, rv));
+ return rv;
+ }
+
+ nsCOMPtr<nsITimedChannel> timed(do_QueryInterface(pChannel));
+ if (timed)
+ timed->SetTimingEnabled(true);
+
+ nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(pChannel);
+ if (props) {
+ rv = props->SetPropertyAsInterface(NS_LITERAL_STRING("test.foo"),
+ pURL);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("set prop 'test.foo'\n"));
+ }
+ }
+
+ /*
+ You may optionally add/set other headers on this
+ request object. This is done by QI for the specific
+ protocolConnection.
+ */
+ nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(pChannel));
+
+ if (pHTTPCon) {
+ // Setting a sample header.
+ rv = pHTTPCon->SetRequestHeader(NS_LITERAL_CSTRING("sample-header"),
+ NS_LITERAL_CSTRING("Sample-Value"),
+ false);
+ if (NS_FAILED(rv)) return rv;
+ }
+ auto* info = new URLLoadInfo(aUrlString);
+ if (!info) {
+ NS_ERROR("Failed to create a load info!");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ auto* listener = new InputTestConsumer(info);
+ NS_IF_ADDREF(listener);
+ if (!listener) {
+ NS_ERROR("Failed to create a new stream listener!");
+ return NS_ERROR_OUT_OF_MEMORY;;
+ }
+
+
+ if (gResume) {
+ nsCOMPtr<nsIResumableChannel> res = do_QueryInterface(pChannel);
+ if (!res) {
+ NS_ERROR("Channel is not resumable!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsAutoCString id;
+ if (gEntityID)
+ id = gEntityID;
+ LOG(("* resuming at %llu bytes, with entity id |%s|\n", gStartAt, id.get()));
+ res->ResumeAt(gStartAt, id);
+ }
+ rv = pChannel->AsyncOpen2(listener);
+
+ if (NS_SUCCEEDED(rv)) {
+ gKeepRunning++;
+ }
+ else {
+ LOG(("ERROR: AsyncOpen failed [rv=%x]\n", rv));
+ }
+ NS_RELEASE(listener);
+ }
+
+ return rv;
+}
+
+static int32_t
+FindChar(nsCString& buffer, char c)
+{
+ const char *b;
+ int32_t len = NS_CStringGetData(buffer, &b);
+
+ for (int32_t offset = 0; offset < len; ++offset) {
+ if (b[offset] == c)
+ return offset;
+ }
+
+ return -1;
+}
+
+
+static void
+StripChar(nsCString& buffer, char c)
+{
+ const char *b;
+ uint32_t len = NS_CStringGetData(buffer, &b) - 1;
+
+ for (; len > 0; --len) {
+ if (b[len] == c) {
+ buffer.Cut(len, 1);
+ NS_CStringGetData(buffer, &b);
+ }
+ }
+}
+
+nsresult LoadURLsFromFile(char *aFileName)
+{
+ nsresult rv = NS_OK;
+ int32_t len, offset;
+ PRFileDesc* fd;
+ char buffer[1024];
+ nsCString fileBuffer;
+ nsCString urlString;
+
+ fd = PR_Open(aFileName, PR_RDONLY, 777);
+ if (!fd) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Keep reading the file until EOF (or an error) is reached...
+ do {
+ len = PR_Read(fd, buffer, sizeof(buffer));
+ if (len>0) {
+ fileBuffer.Append(buffer, len);
+ // Treat each line as a URL...
+ while ((offset = FindChar(fileBuffer, '\n')) != -1) {
+ urlString = StringHead(fileBuffer, offset);
+ fileBuffer.Cut(0, offset+1);
+
+ StripChar(urlString, '\r');
+ if (urlString.Length()) {
+ LOG(("\t%s\n", urlString.get()));
+ rv = StartLoadingURL(urlString.get());
+ if (NS_FAILED(rv)) {
+ // No need to log an error -- StartLoadingURL already
+ // did that for us, probably.
+ PR_Close(fd);
+ return rv;
+ }
+ }
+ }
+ }
+ } while (len>0);
+
+ // If anything is left in the fileBuffer, treat it as a URL...
+ StripChar(fileBuffer, '\r');
+ if (fileBuffer.Length()) {
+ LOG(("\t%s\n", fileBuffer.get()));
+ StartLoadingURL(fileBuffer.get());
+ }
+
+ PR_Close(fd);
+ return NS_OK;
+}
+
+
+nsresult LoadURLFromConsole()
+{
+ char buffer[1024];
+ printf(R"(Enter URL ("q" to start): )");
+ Unused << scanf("%s", buffer);
+ if (buffer[0]=='q')
+ gAskUserForInput = false;
+ else
+ StartLoadingURL(buffer);
+ return NS_OK;
+}
+
+} // namespace TestProtocols
+
+using namespace TestProtocols;
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv= (nsresult)-1;
+ if (argc < 2) {
+ printf("usage: %s [-verbose] [-file <name>] [-resume <startoffset>"
+ "[-entityid <entityid>]] [-proxy <proxy>] [-pac <pacURL>]"
+ "[-console] <url> <url> ... \n", argv[0]);
+ return -1;
+ }
+
+ gTestLog = PR_NewLogModule("Test");
+
+ /*
+ The following code only deals with XPCOM registration stuff. and setting
+ up the event queues. Copied from TestSocketIO.cpp
+ */
+
+ rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return -1;
+
+ {
+ int i;
+ LOG(("Trying to load:\n"));
+ for (i=1; i<argc; i++) {
+ // Turn on verbose printing...
+ if (PL_strcasecmp(argv[i], "-verbose") == 0) {
+ gVerbose = true;
+ continue;
+ }
+
+ // Turn on netlib tracing...
+ if (PL_strcasecmp(argv[i], "-file") == 0) {
+ LoadURLsFromFile(argv[++i]);
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-console") == 0) {
+ gAskUserForInput = true;
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-resume") == 0) {
+ gResume = true;
+ PR_sscanf(argv[++i], "%llu", &gStartAt);
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-entityid") == 0) {
+ gEntityID = argv[++i];
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-proxy") == 0) {
+ SetHttpProxy(argv[++i]);
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-pac") == 0) {
+ SetPACFile(argv[++i]);
+ continue;
+ }
+
+ LOG(("\t%s\n", argv[i]));
+ rv = StartLoadingURL(argv[i]);
+ }
+ // Enter the message pump to allow the URL load to proceed.
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return NS_FAILED(rv) ? -1 : 0;
+}
diff --git a/netwerk/test/TestServ.cpp b/netwerk/test/TestServ.cpp
new file mode 100644
index 000000000..7ae41636f
--- /dev/null
+++ b/netwerk/test/TestServ.cpp
@@ -0,0 +1,146 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include <stdlib.h>
+#include "nsIServiceManager.h"
+#include "nsIServerSocket.h"
+#include "nsISocketTransport.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsNetCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringAPI.h"
+#include "nsCOMPtr.h"
+#include "NetwerkTestLogging.h"
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+class MySocketListener : public nsIServerSocketListener
+{
+protected:
+ virtual ~MySocketListener() = default;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ MySocketListener() {}
+};
+
+NS_IMPL_ISUPPORTS(MySocketListener, nsIServerSocketListener)
+
+NS_IMETHODIMP
+MySocketListener::OnSocketAccepted(nsIServerSocket *serv,
+ nsISocketTransport *trans)
+{
+ LOG(("MySocketListener::OnSocketAccepted [serv=%p trans=%p]\n", serv, trans));
+
+ nsAutoCString host;
+ int32_t port;
+
+ trans->GetHost(host);
+ trans->GetPort(&port);
+
+ LOG((" -> %s:%d\n", host.get(), port));
+
+ nsCOMPtr<nsIInputStream> input;
+ nsCOMPtr<nsIOutputStream> output;
+ nsresult rv;
+
+ rv = trans->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(input));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = trans->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(output));
+ if (NS_FAILED(rv))
+ return rv;
+
+ char buf[256];
+ uint32_t n;
+
+ rv = input->Read(buf, sizeof(buf), &n);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const char response[] = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nFooooopy!!\r\n";
+ rv = output->Write(response, sizeof(response) - 1, &n);
+ if (NS_FAILED(rv))
+ return rv;
+
+ input->Close();
+ output->Close();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MySocketListener::OnStopListening(nsIServerSocket *serv, nsresult status)
+{
+ LOG(("MySocketListener::OnStopListening [serv=%p status=%x]\n", serv, status));
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+static nsresult
+MakeServer(int32_t port)
+{
+ nsresult rv;
+ nsCOMPtr<nsIServerSocket> serv = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = serv->Init(port, true, 5);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = serv->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+ LOG((" listening on port %d\n", port));
+
+ rv = serv->AsyncListen(new MySocketListener());
+ return rv;
+}
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv= (nsresult)-1;
+ if (argc < 2) {
+ printf("usage: %s <port>\n", argv[0]);
+ return -1;
+ }
+
+ gTestLog = PR_NewLogModule("Test");
+
+ /*
+ * The following code only deals with XPCOM registration stuff. and setting
+ * up the event queues. Copied from TestSocketIO.cpp
+ */
+
+ rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return -1;
+
+ {
+ rv = MakeServer(atoi(argv[1]));
+ if (NS_FAILED(rv)) {
+ LOG(("MakeServer failed [rv=%x]\n", rv));
+ return -1;
+ }
+
+ // Enter the message pump to allow the URL load to proceed.
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestServ.js b/netwerk/test/TestServ.js
new file mode 100644
index 000000000..7716ac504
--- /dev/null
+++ b/netwerk/test/TestServ.js
@@ -0,0 +1,164 @@
+/* vim:set ts=2 sw=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/. */
+
+/*
+ * To use try out this JS server socket implementation, just copy this file
+ * into the "components" directory of a Mozilla build. Then load the URL
+ * http://localhost:4444/ in the browser. You should see a page get loaded
+ * that was served up by this component :-)
+ *
+ * This code requires Mozilla 1.6 or better.
+ */
+
+const kTESTSERV_CONTRACTID = "@mozilla.org/network/test-serv;1";
+const kTESTSERV_CID = Components.ID("{a741fcd5-9695-42e8-a7f7-14f9a29f8200}");
+const nsISupports = Components.interfaces.nsISupports;
+const nsIObserver = Components.interfaces.nsIObserver;
+const nsIServerSocket = Components.interfaces.nsIServerSocket;
+const nsIServerSocketListener = Components.interfaces.nsIServerSocketListener;
+const nsITransport = Components.interfaces.nsITransport;
+const nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+
+/** we'll listen on this port for HTTP requests **/
+const kPORT = 4444;
+
+function nsTestServ() { dump(">>> creating nsTestServ instance\n"); };
+
+nsTestServ.prototype =
+{
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(nsIObserver) ||
+ iid.equals(nsIServerSocketListener) ||
+ iid.equals(nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data)
+ {
+ dump(">>> observe [" + topic + "]\n");
+ this.startListening();
+ },
+
+ /* this function is called when we receive a new connection */
+ onSocketAccepted: function(serverSocket, clientSocket)
+ {
+ dump(">>> accepted connection on "+clientSocket.host+":"+clientSocket.port+"\n");
+
+ var input = clientSocket.openInputStream(nsITransport.OPEN_BLOCKING, 0, 0);
+ var output = clientSocket.openOutputStream(nsITransport.OPEN_BLOCKING, 0, 0);
+
+ this.consumeInput(input);
+
+ const fixedResponse =
+ "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nFooooopy!!\r\n";
+ var response = fixedResponse + "\r\n" + new Date().toString() + "\r\n";
+ var n = output.write(response, response.length);
+ dump(">>> wrote "+n+" bytes\n");
+
+ input.close();
+ output.close();
+ },
+
+ onStopListening: function(serverSocket, status)
+ {
+ dump(">>> shutting down server socket\n");
+ },
+
+ startListening: function()
+ {
+ const SERVERSOCKET_CONTRACTID = "@mozilla.org/network/server-socket;1";
+ var socket = Components.classes[SERVERSOCKET_CONTRACTID].createInstance(nsIServerSocket);
+ socket.init(kPORT, true /* loopback only */, 5);
+ dump(">>> listening on port "+socket.port+"\n");
+ socket.asyncListen(this);
+ },
+
+ consumeInput: function(input)
+ {
+ /* use nsIScriptableInputStream to consume all of the data on the stream */
+
+ var sin = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(nsIScriptableInputStream);
+ sin.init(input);
+
+ /* discard all data */
+ while (sin.available() > 0)
+ sin.read(512);
+ }
+}
+
+/**
+ * JS XPCOM component registration goop:
+ *
+ * We set ourselves up to observe the xpcom-startup category. This provides
+ * us with a starting point.
+ */
+
+var servModule = new Object();
+
+servModule.registerSelf =
+function (compMgr, fileSpec, location, type)
+{
+ compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ compMgr.registerFactoryLocation(kTESTSERV_CID,
+ "nsTestServ",
+ kTESTSERV_CONTRACTID,
+ fileSpec,
+ location,
+ type);
+
+ const CATMAN_CONTRACTID = "@mozilla.org/categorymanager;1";
+ const nsICategoryManager = Components.interfaces.nsICategoryManager;
+ var catman = Components.classes[CATMAN_CONTRACTID].getService(nsICategoryManager);
+ catman.addCategoryEntry("xpcom-startup",
+ "TestServ",
+ kTESTSERV_CONTRACTID,
+ true,
+ true);
+}
+
+servModule.getClassObject =
+function (compMgr, cid, iid)
+{
+ if (!cid.equals(kTESTSERV_CID))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ if (!iid.equals(Components.interfaces.nsIFactory))
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+
+ return servFactory;
+}
+
+servModule.canUnload =
+function (compMgr)
+{
+ dump(">>> unloading test serv.\n");
+ return true;
+}
+
+var servFactory = new Object();
+
+servFactory.createInstance =
+function (outer, iid)
+{
+ if (outer != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+
+ if (!iid.equals(nsIObserver) &&
+ !iid.equals(nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return TestServ;
+}
+
+function NSGetModule(compMgr, fileSpec)
+{
+ return servModule;
+}
+
+var TestServ = new nsTestServ();
diff --git a/netwerk/test/TestSocketIO.cpp b/netwerk/test/TestSocketIO.cpp
new file mode 100644
index 000000000..319e8e1a4
--- /dev/null
+++ b/netwerk/test/TestSocketIO.cpp
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+#include <stdio.h>
+#include <signal.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nspr.h"
+#include "nscore.h"
+#include "nsISocketTransportService.h"
+#include "nsIEventQueueService.h"
+#include "nsIServiceManager.h"
+#include "nsITransport.h"
+#include "nsIRequest.h"
+#include "nsIStreamProvider.h"
+#include "nsIStreamListener.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsIByteArrayInputStream.h"
+
+static PRLogModuleInfo *gTestSocketIOLog;
+#define LOG(args) MOZ_LOG(gTestSocketIOLog, mozilla::LogLevel::Debug, args)
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
+
+static PRTime gElapsedTime;
+static int gKeepRunning = 1;
+static nsIEventQueue* gEventQ = nullptr;
+
+//
+//----------------------------------------------------------------------------
+// Test Listener
+//----------------------------------------------------------------------------
+//
+
+class TestListener : public nsIStreamListener
+{
+public:
+ TestListener() {}
+ virtual ~TestListener() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+NS_IMPL_ISUPPORTS(TestListener,
+ nsIRequestObserver,
+ nsIStreamListener);
+
+NS_IMETHODIMP
+TestListener::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestListener::OnStartRequest\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnDataAvailable(nsIRequest* request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint32_t aSourceOffset,
+ uint32_t aLength)
+{
+ LOG(("TestListener::OnDataAvailable [offset=%u length=%u]\n",
+ aSourceOffset, aLength));
+ char buf[1025];
+ uint32_t amt;
+ while (1) {
+ aIStream->Read(buf, 1024, &amt);
+ if (amt == 0)
+ break;
+ buf[amt] = '\0';
+ puts(buf);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestListener::OnStopRequest [aStatus=%x]\n", aStatus));
+ gKeepRunning = 0;
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Test Provider
+//----------------------------------------------------------------------------
+//
+
+class TestProvider : public nsIStreamProvider
+{
+public:
+ TestProvider(char *data);
+ virtual ~TestProvider();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMPROVIDER
+
+protected:
+ nsCOMPtr<nsIByteArrayInputStream> mData;
+};
+
+NS_IMPL_ISUPPORTS(TestProvider,
+ nsIStreamProvider,
+ nsIRequestObserver)
+
+TestProvider::TestProvider(char *data)
+{
+ NS_NewByteArrayInputStream(getter_AddRefs(mData), data, strlen(data));
+ LOG(("Constructing TestProvider [this=%p]\n", this));
+}
+
+TestProvider::~TestProvider()
+{
+ LOG(("Destroying TestProvider [this=%p]\n", this));
+}
+
+NS_IMETHODIMP
+TestProvider::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestProvider::OnStartRequest [this=%p]\n", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestProvider::OnStopRequest [status=%x]\n", aStatus));
+
+ nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(new TestListener());
+
+ if (NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsITransportRequest> treq = do_QueryInterface(request);
+ nsCOMPtr<nsITransport> transport;
+ treq->GetTransport(getter_AddRefs(transport));
+ if (transport) {
+ nsCOMPtr<nsIRequest> readRequest;
+ transport->AsyncRead(listener, nullptr, 0, 0, 0, getter_AddRefs(readRequest));
+ }
+ } else
+ gKeepRunning = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnDataWritable(nsIRequest *request, nsISupports *context,
+ nsIOutputStream *output, uint32_t offset, uint32_t count)
+{
+ LOG(("TestProvider::OnDataWritable [offset=%u, count=%u]\n", offset, count));
+ uint32_t writeCount;
+ nsresult rv = output->WriteFrom(mData, count, &writeCount);
+ // Zero bytes written on success indicates EOF
+ if (NS_SUCCEEDED(rv) && (writeCount == 0))
+ return NS_BASE_STREAM_CLOSED;
+ return rv;
+}
+
+//
+//----------------------------------------------------------------------------
+// Synchronous IO
+//----------------------------------------------------------------------------
+//
+nsresult
+WriteRequest(nsIOutputStream *os, const char *request)
+{
+ LOG(("WriteRequest [request=%s]\n", request));
+ uint32_t n;
+ return os->Write(request, strlen(request), &n);
+}
+
+nsresult
+ReadResponse(nsIInputStream *is)
+{
+ uint32_t bytesRead;
+ char buf[2048];
+ do {
+ is->Read(buf, sizeof(buf), &bytesRead);
+ if (bytesRead > 0)
+ fwrite(buf, 1, bytesRead, stdout);
+ } while (bytesRead > 0);
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Startup...
+//----------------------------------------------------------------------------
+//
+
+void
+sighandler(int sig)
+{
+ LOG(("got signal: %d\n", sig));
+ NS_BREAK();
+}
+
+void
+usage(char **argv)
+{
+ printf("usage: %s [-sync] <host> <path>\n", argv[0]);
+ exit(1);
+}
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ signal(SIGSEGV, sighandler);
+
+ gTestSocketIOLog = PR_NewLogModule("TestSocketIO");
+
+ if (argc < 3)
+ usage(argv);
+
+ int i=0;
+ bool sync = false;
+ if (nsCRT::strcasecmp(argv[1], "-sync") == 0) {
+ if (argc < 4)
+ usage(argv);
+ sync = true;
+ i = 1;
+ }
+
+ char *hostName = argv[1+i];
+ char *fileName = argv[2+i];
+ int port = 80;
+
+ // Create the Event Queue for this thread...
+ nsCOMPtr<nsIEventQueueService> eventQService =
+ do_GetService(kEventQueueServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: event queue service!");
+ return rv;
+ }
+
+ rv = eventQService->CreateMonitoredThreadEventQueue();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: thread event queue!");
+ return rv;
+ }
+
+ eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, &gEventQ);
+
+ // Create the Socket transport service...
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport service!");
+ return rv;
+ }
+
+ char *buffer = PR_smprintf("GET %s HTTP/1.1" CRLF
+ "host: %s" CRLF
+ "user-agent: Mozilla/5.0 (X11; N; Linux 2.2.16-22smp i686; en-US; m18) Gecko/20001220" CRLF
+ "accept: */*" CRLF
+ "accept-language: en" CRLF
+ "accept-encoding: gzip,deflate,compress,identity" CRLF
+ "keep-alive: 300" CRLF
+ "connection: keep-alive" CRLF
+ CRLF,
+ fileName, hostName);
+ LOG(("Request [\n%s]\n", buffer));
+
+ // Create the socket transport...
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateTransport(hostName, port, nullptr, 0, 0, getter_AddRefs(transport));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport!");
+ return rv;
+ }
+
+ gElapsedTime = PR_Now();
+
+ if (!sync) {
+ nsCOMPtr<nsIRequest> request;
+ rv = transport->AsyncWrite(new TestProvider(buffer), nullptr, 0, 0, 0, getter_AddRefs(request));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed calling: AsyncWrite!");
+ return rv;
+ }
+
+ // Enter the message pump to allow the URL load to proceed.
+ while ( gKeepRunning ) {
+ PLEvent *gEvent;
+ gEventQ->WaitForEvent(&gEvent);
+ gEventQ->HandleEvent(gEvent);
+ }
+ }
+ else {
+ // synchronous write
+ {
+ nsCOMPtr<nsIOutputStream> os;
+ rv = transport->OpenOutputStream(0, 0, 0, getter_AddRefs(os));
+ if (NS_FAILED(rv)) {
+ LOG(("OpenOutputStream failed [rv=%x]\n", rv));
+ return rv;
+ }
+ rv = WriteRequest(os, buffer);
+ if (NS_FAILED(rv)) {
+ LOG(("WriteRequest failed [rv=%x]\n", rv));
+ return rv;
+ }
+ }
+ // synchronous read
+ {
+ nsCOMPtr<nsIInputStream> is;
+ rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(is));
+ if (NS_FAILED(rv)) {
+ LOG(("OpenInputStream failed [rv=%x]\n", rv));
+ return rv;
+ }
+ rv = ReadResponse(is);
+ if (NS_FAILED(rv)) {
+ LOG(("ReadResponse failed [rv=%x]\n", rv));
+ return rv;
+ }
+ }
+ }
+
+ PRTime endTime;
+ endTime = PR_Now();
+ LOG(("Elapsed time: %d\n", (int32_t)(endTime/1000UL - gElapsedTime/1000UL)));
+
+ sts->Shutdown();
+ return 0;
+}
+
diff --git a/netwerk/test/TestSocketInput.cpp b/netwerk/test/TestSocketInput.cpp
new file mode 100644
index 000000000..c3e58bd1e
--- /dev/null
+++ b/netwerk/test/TestSocketInput.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+#include <stdio.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsISocketTransportService.h"
+#include "nsIEventQueueService.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsITransport.h"
+#include "nsIRequest.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
+
+static int gKeepRunning = 1;
+
+class InputTestConsumer : public nsIStreamListener
+{
+public:
+
+ InputTestConsumer();
+ virtual ~InputTestConsumer();
+
+ // ISupports interface...
+ NS_DECL_ISUPPORTS
+
+ // IStreamListener interface...
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+
+InputTestConsumer::InputTestConsumer()
+{
+}
+
+InputTestConsumer::~InputTestConsumer()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(InputTestConsumer, nsIRequestObserver, nsIStreamListener)
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
+{
+ printf("+++ OnStartRequest +++\n");
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnDataAvailable(nsIRequest *request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ char buf[1025];
+ while (aLength > 0) {
+ uint32_t amt;
+ aIStream->Read(buf, 1024, &amt);
+ if (amt == 0) break;
+ buf[amt] = '\0';
+ printf(buf);
+ aLength -= amt;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnStopRequest(nsIRequest *request, nsISupports* context,
+ nsresult aStatus)
+{
+ gKeepRunning = 0;
+ printf("+++ OnStopRequest status %x +++\n", aStatus);
+ return NS_OK;
+}
+
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <host>\n", argv[0]);
+ return -1;
+ }
+
+ int port;
+ char* hostName = argv[1];
+//nsString portString(argv[2]);
+
+//port = portString.ToInteger(&rv);
+ port = 13;
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ // Create the Event Queue for this thread...
+ nsCOMPtr<nsIEventQueueService> eventQService =
+ do_GetService(kEventQueueServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIEventQueue> eventQ;
+ rv = eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(eventQ));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsITransport* transport;
+
+ rv = sts->CreateTransport(hostName, port, nullptr, 0, 0, &transport);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRequest> request;
+ transport->AsyncRead(new InputTestConsumer, nullptr, 0, -1, 0, getter_AddRefs(request));
+
+ NS_RELEASE(transport);
+ }
+
+ // Enter the message pump to allow the URL load to proceed.
+ while ( gKeepRunning ) {
+ PLEvent *gEvent;
+ eventQ->WaitForEvent(&gEvent);
+ eventQ->HandleEvent(gEvent);
+ }
+
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
+
diff --git a/netwerk/test/TestSocketTransport.cpp b/netwerk/test/TestSocketTransport.cpp
new file mode 100644
index 000000000..58422a38c
--- /dev/null
+++ b/netwerk/test/TestSocketTransport.cpp
@@ -0,0 +1,307 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "nsIComponentRegistrar.h"
+#include "nsPISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIRequest.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsStringAPI.h"
+#include "nsIDNSService.h"
+#include "nsIFileStreams.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsAutoLock.h"
+#include "mozilla/Logging.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class MyHandler : public nsIOutputStreamCallback
+ , public nsIInputStreamCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ MyHandler(const char *path,
+ nsIAsyncInputStream *in,
+ nsIAsyncOutputStream *out)
+ : mInput(in)
+ , mOutput(out)
+ , mWriteOffset(0)
+ {
+ mBuf.AssignLiteral("GET ");
+ mBuf.Append(path);
+ mBuf.AppendLiteral(" HTTP/1.0\r\n\r\n");
+ }
+ virtual ~MyHandler() {}
+
+ // called on any thread
+ NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream *out)
+ {
+ LOG(("OnOutputStreamReady\n"));
+
+ nsresult rv;
+ uint32_t n, count = mBuf.Length() - mWriteOffset;
+
+ rv = out->Write(mBuf.get() + mWriteOffset, count, &n);
+
+ LOG((" write returned [rv=%x count=%u]\n", rv, n));
+
+ if (NS_FAILED(rv) || (n == 0)) {
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG((" done writing; starting to read\n"));
+ mInput->AsyncWait(this, 0, 0, nullptr);
+ return NS_OK;
+ }
+ }
+
+ mWriteOffset += n;
+
+ return out->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ // called on any thread
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream *in)
+ {
+ LOG(("OnInputStreamReady\n"));
+
+ nsresult rv;
+ uint32_t n;
+ char buf[500];
+
+ rv = in->Read(buf, sizeof(buf), &n);
+
+ LOG((" read returned [rv=%x count=%u]\n", rv, n));
+
+ if (NS_FAILED(rv) || (n == 0)) {
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ QuitPumpingEvents();
+ return NS_OK;
+ }
+ }
+
+ return in->AsyncWait(this, 0, 0, nullptr);
+ }
+
+private:
+ nsCOMPtr<nsIAsyncInputStream> mInput;
+ nsCOMPtr<nsIAsyncOutputStream> mOutput;
+ nsCString mBuf;
+ uint32_t mWriteOffset;
+};
+
+NS_IMPL_ISUPPORTS(MyHandler,
+ nsIOutputStreamCallback,
+ nsIInputStreamCallback)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * create transport, open streams, and close
+ */
+static nsresult
+RunCloseTest(nsISocketTransportService *sts,
+ const char *host, int port,
+ uint32_t inFlags, uint32_t outFlags)
+{
+ nsresult rv;
+
+ LOG(("RunCloseTest\n"));
+
+ nsCOMPtr<nsISocketTransport> transport;
+ rv = sts->CreateTransport(nullptr, 0,
+ nsDependentCString(host), port, nullptr,
+ getter_AddRefs(transport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> in;
+ rv = transport->OpenInputStream(inFlags, 0, 0, getter_AddRefs(in));
+ nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(in, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = transport->OpenOutputStream(outFlags, 0, 0, getter_AddRefs(out));
+ nsCOMPtr<nsIAsyncOutputStream> asyncOut = do_QueryInterface(out, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("waiting 1 second before closing transport and streams...\n"));
+ PR_Sleep(PR_SecondsToInterval(1));
+
+ // let nsCOMPtr destructors close everything...
+ return NS_OK;
+}
+
+
+/**
+ * asynchronously read socket stream
+ */
+static nsresult
+RunTest(nsISocketTransportService *sts,
+ const char *host, int port, const char *path,
+ uint32_t inFlags, uint32_t outFlags)
+{
+ nsresult rv;
+
+ LOG(("RunTest\n"));
+
+ nsCOMPtr<nsISocketTransport> transport;
+ rv = sts->CreateTransport(nullptr, 0,
+ nsDependentCString(host), port, nullptr,
+ getter_AddRefs(transport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> in;
+ rv = transport->OpenInputStream(inFlags, 0, 0, getter_AddRefs(in));
+ nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(in, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = transport->OpenOutputStream(outFlags, 0, 0, getter_AddRefs(out));
+ nsCOMPtr<nsIAsyncOutputStream> asyncOut = do_QueryInterface(out, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ MyHandler *handler = new MyHandler(path, asyncIn, asyncOut);
+ if (handler == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(handler);
+
+ rv = asyncOut->AsyncWait(handler, 0, 0, nullptr);
+
+ if (NS_SUCCEEDED(rv))
+ PumpEvents();
+
+ NS_RELEASE(handler);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 4) {
+ printf("usage: TestSocketTransport <host> <port> <path>\n");
+ return -1;
+ }
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ // Make sure the DNS service is initialized on the main thread
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsPISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("phase 1 tests...\n"));
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ nsITransport::OPEN_UNBUFFERED,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ 0 /* nsITransport::OPEN_BUFFERED */,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ nsITransport::OPEN_UNBUFFERED,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ 0 /*nsITransport::OPEN_BUFFERED */,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("calling Shutdown on socket transport service:\n"));
+ sts->Shutdown();
+
+ LOG(("calling Init on socket transport service:\n"));
+ sts->Init();
+
+ LOG(("phase 2 tests...\n"));
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ nsITransport::OPEN_UNBUFFERED,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ 0 /* nsITransport::OPEN_BUFFERED */,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ nsITransport::OPEN_UNBUFFERED,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ 0 /*nsITransport::OPEN_BUFFERED */,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("waiting 1 second before calling Shutdown...\n"));
+ PR_Sleep(PR_SecondsToInterval(1));
+
+ LOG(("calling Shutdown on socket transport service:\n"));
+ sts->Shutdown();
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ LOG(("waiting 1 second before exiting...\n"));
+ PR_Sleep(PR_SecondsToInterval(1));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
diff --git a/netwerk/test/TestStreamLoader.cpp b/netwerk/test/TestStreamLoader.cpp
new file mode 100644
index 000000000..c01148f40
--- /dev/null
+++ b/netwerk/test/TestStreamLoader.cpp
@@ -0,0 +1,101 @@
+#include <stdio.h>
+#include "TestCommon.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "NetwerkTestLogging.h"
+#include "mozilla/Attributes.h"
+#include "nsIScriptSecurityManager.h"
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+class MyStreamLoaderObserver final : public nsIStreamLoaderObserver
+{
+ ~MyStreamLoaderObserver() = default;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+};
+
+NS_IMPL_ISUPPORTS(MyStreamLoaderObserver, nsIStreamLoaderObserver)
+
+NS_IMETHODIMP
+MyStreamLoaderObserver::OnStreamComplete(nsIStreamLoader *loader,
+ nsISupports *ctxt,
+ nsresult status,
+ uint32_t resultLen,
+ const uint8_t *result)
+{
+ LOG(("OnStreamComplete [status=%x resultLen=%u]\n", status, resultLen));
+
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ LOG((" request=%p\n", request.get()));
+
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ if (argc < 2) {
+ printf("usage: %s <url>\n", argv[0]);
+ return -1;
+ }
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return -1;
+
+ {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(argv[1]));
+ if (NS_FAILED(rv))
+ return -1;
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = NS_NewChannel(getter_AddRefs(chan),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv))
+ return -1;
+
+ nsCOMPtr<nsIStreamLoaderObserver> observer = new MyStreamLoaderObserver();
+ if (!observer)
+ return -1;
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), observer);
+ if (NS_FAILED(rv))
+ return -1;
+
+ rv = chan->AsyncOpen2(loader);
+ if (NS_FAILED(rv))
+ return -1;
+
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestStreamPump.cpp b/netwerk/test/TestStreamPump.cpp
new file mode 100644
index 000000000..376b9deb7
--- /dev/null
+++ b/netwerk/test/TestStreamPump.cpp
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIStreamTransportService.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIRequest.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsStringAPI.h"
+#include "nsIFileStreams.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsAutoLock.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include <algorithm>
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class MyListener : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ MyListener() {}
+ virtual ~MyListener() {}
+
+ NS_IMETHOD OnStartRequest(nsIRequest *req, nsISupports *ctx)
+ {
+ LOG(("MyListener::OnStartRequest\n"));
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnDataAvailable(nsIRequest *req, nsISupports *ctx,
+ nsIInputStream *stream,
+ uint64_t offset, uint32_t count)
+ {
+ LOG(("MyListener::OnDataAvailable [offset=%llu count=%u]\n", offset, count));
+
+ char buf[500];
+ nsresult rv;
+
+ while (count) {
+ uint32_t n, amt = std::min<uint32_t>(count, sizeof(buf));
+
+ rv = stream->Read(buf, amt, &n);
+ if (NS_FAILED(rv)) {
+ LOG((" read returned 0x%08x\n", rv));
+ return rv;
+ }
+
+ fwrite(buf, n, 1, stdout);
+ printf("\n");
+
+ LOG((" read %u bytes\n", n));
+ count -= n;
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status)
+ {
+ LOG(("MyListener::OnStopRequest [status=%x]\n", status));
+ QuitPumpingEvents();
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(MyListener,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * asynchronously copy file.
+ */
+static nsresult
+RunTest(nsIFile *file, int64_t offset, int64_t length)
+{
+ nsresult rv;
+
+ LOG(("RunTest\n"));
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream, offset, length);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = pump->AsyncRead(new MyListener(), nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ PumpEvents();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 4) {
+ printf("usage: %s <file-to-read> <start-offset> <read-length>\n", argv[0]);
+ return -1;
+ }
+ char* fileName = argv[1];
+ int64_t offset, length;
+ int err = PR_sscanf(argv[2], "%lld", &offset);
+ if (err == -1) {
+ printf("Start offset must be an integer!\n");
+ return 1;
+ }
+ err = PR_sscanf(argv[3], "%lld", &length);
+ if (err == -1) {
+ printf("Length must be an integer!\n");
+ return 1;
+ }
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsCOMPtr<nsIFile> file;
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileName), false, getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = RunTest(file, offset, length);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ PR_Sleep(PR_SecondsToInterval(1));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return NS_OK;
+}
diff --git a/netwerk/test/TestStreamTransport.cpp b/netwerk/test/TestStreamTransport.cpp
new file mode 100644
index 000000000..840c9578d
--- /dev/null
+++ b/netwerk/test/TestStreamTransport.cpp
@@ -0,0 +1,319 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIStreamTransportService.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIRequest.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsStringAPI.h"
+#include "nsIFileStreams.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsAutoLock.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define CHUNK_SIZE 500
+
+class MyCopier : public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ MyCopier()
+ : mLock(nullptr)
+ , mInputCondition(NS_OK)
+ {
+ }
+
+ virtual ~MyCopier()
+ {
+ if (mLock)
+ nsAutoLock::DestroyLock(mLock);
+ if (mInput)
+ mInput->Close();
+ if (mOutput)
+ mOutput->Close();
+ }
+
+ // called on any thread
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream *inStr)
+ {
+ LOG(("OnInputStreamReady\n"));
+ nsAutoLock lock(mLock);
+ NS_ASSERTION(inStr == mInput, "unexpected stream");
+ Process_Locked();
+ return NS_OK;
+ }
+
+ // called on any thread
+ NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream *outStr)
+ {
+ LOG(("OnOutputStreamReady\n"));
+ nsAutoLock lock(mLock);
+ NS_ASSERTION(outStr == mOutput, "unexpected stream");
+ Process_Locked();
+ return NS_OK;
+ }
+
+ void Close_Locked()
+ {
+ LOG(("Close_Locked\n"));
+
+ mOutput->Close();
+ mOutput = 0;
+ mInput->Close();
+ mInput = 0;
+
+ // post done copying event
+ QuitPumpingEvents();
+ }
+
+ void Process_Locked()
+ {
+ while (1) {
+ mInputCondition = NS_OK; // reset
+
+ uint32_t n;
+ nsresult rv = mOutput->WriteSegments(FillOutputBuffer, this, CHUNK_SIZE, &n);
+ if (NS_FAILED(rv) || (n == 0)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ mOutput->AsyncWait(this, 0, 0, nullptr);
+ else if (mInputCondition == NS_BASE_STREAM_WOULD_BLOCK)
+ mInput->AsyncWait(this, 0, 0, nullptr);
+ else
+ Close_Locked();
+ break;
+ }
+ }
+ }
+
+ nsresult AsyncCopy(nsITransport *srcTrans, nsITransport *destTrans)
+ {
+ mLock = nsAutoLock::NewLock("MyCopier::mLock");
+ if (!mLock)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> inStr;
+ rv = srcTrans->OpenInputStream(0, 0, 0, getter_AddRefs(inStr));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> outStr;
+ rv = destTrans->OpenOutputStream(0, 0, 0, getter_AddRefs(outStr));
+ if (NS_FAILED(rv)) return rv;
+
+ mInput = do_QueryInterface(inStr);
+ mOutput = do_QueryInterface(outStr);
+
+ return mInput->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ static nsresult FillOutputBuffer(nsIOutputStream *outStr,
+ void *closure,
+ char *buffer,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+ {
+ MyCopier *self = (MyCopier *) closure;
+
+ nsresult rv = self->mInput->Read(buffer, count, countRead);
+ if (NS_FAILED(rv))
+ self->mInputCondition = rv;
+ else if (*countRead == 0)
+ self->mInputCondition = NS_BASE_STREAM_CLOSED;
+
+ return self->mInputCondition;
+ }
+
+protected:
+ PRLock *mLock;
+ nsCOMPtr<nsIAsyncInputStream> mInput;
+ nsCOMPtr<nsIAsyncOutputStream> mOutput;
+ nsresult mInputCondition;
+};
+
+NS_IMPL_ISUPPORTS(MyCopier,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * asynchronously copy file.
+ */
+static nsresult
+RunTest(nsIFile *srcFile, nsIFile *destFile)
+{
+ nsresult rv;
+
+ LOG(("RunTest\n"));
+
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> srcStr;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(srcStr), srcFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> destStr;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(destStr), destFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> srcTransport;
+ rv = sts->CreateInputTransport(srcStr, int64_t(-1), int64_t(-1), true,
+ getter_AddRefs(srcTransport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> destTransport;
+ rv = sts->CreateOutputTransport(destStr, int64_t(-1), int64_t(-1), true,
+ getter_AddRefs(destTransport));
+ if (NS_FAILED(rv)) return rv;
+
+ MyCopier *copier = new MyCopier();
+ if (copier == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(copier);
+
+ rv = copier->AsyncCopy(srcTransport, destTransport);
+ if (NS_FAILED(rv)) return rv;
+
+ PumpEvents();
+
+ NS_RELEASE(copier);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static nsresult
+RunBlockingTest(nsIFile *srcFile, nsIFile *destFile)
+{
+ nsresult rv;
+
+ LOG(("RunBlockingTest\n"));
+
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> srcIn;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(srcIn), srcFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> fileOut;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOut), destFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> destTransport;
+ rv = sts->CreateOutputTransport(fileOut, int64_t(-1), int64_t(-1),
+ true, getter_AddRefs(destTransport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> destOut;
+ rv = destTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 100, 10, getter_AddRefs(destOut));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[120];
+ uint32_t n;
+ for (;;) {
+ rv = srcIn->Read(buf, sizeof(buf), &n);
+ if (NS_FAILED(rv) || (n == 0)) return rv;
+
+ rv = destOut->Write(buf, n, &n);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <file-to-read>\n", argv[0]);
+ return -1;
+ }
+ char* fileName = argv[1];
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsCOMPtr<nsIFile> srcFile;
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileName), false, getter_AddRefs(srcFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> destFile;
+ rv = srcFile->Clone(getter_AddRefs(destFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString leafName;
+ rv = destFile->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString newName(leafName);
+ newName.AppendLiteral(".1");
+ rv = destFile->SetNativeLeafName(newName);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = RunTest(srcFile, destFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ newName = leafName;
+ newName.AppendLiteral(".2");
+ rv = destFile->SetNativeLeafName(newName);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = RunBlockingTest(srcFile, destFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunBlockingTest failed");
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ PR_Sleep(PR_SecondsToInterval(1));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return NS_OK;
+}
diff --git a/netwerk/test/TestUDPSocket.cpp b/netwerk/test/TestUDPSocket.cpp
new file mode 100644
index 000000000..9236d2ea3
--- /dev/null
+++ b/netwerk/test/TestUDPSocket.cpp
@@ -0,0 +1,473 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "TestHarness.h"
+#include "nsIUDPSocket.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsINetAddr.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsITimer.h"
+#include "mozilla/net/DNS.h"
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+#include "prerror.h"
+
+#define REQUEST 0x68656c6f
+#define RESPONSE 0x6f6c6568
+#define MULTICAST_TIMEOUT 2000
+
+#define EXPECT_SUCCESS(rv, ...) \
+ PR_BEGIN_MACRO \
+ if (NS_FAILED(rv)) { \
+ fail(__VA_ARGS__); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+
+#define EXPECT_FAILURE(rv, ...) \
+ PR_BEGIN_MACRO \
+ if (NS_SUCCEEDED(rv)) { \
+ fail(__VA_ARGS__); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+#define REQUIRE_EQUAL(a, b, ...) \
+ PR_BEGIN_MACRO \
+ if (a != b) { \
+ fail(__VA_ARGS__); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+enum TestPhase {
+ TEST_OUTPUT_STREAM,
+ TEST_SEND_API,
+ TEST_MULTICAST,
+ TEST_NONE
+};
+
+static TestPhase phase = TEST_NONE;
+
+static bool CheckMessageContent(nsIUDPMessage *aMessage, uint32_t aExpectedContent)
+{
+ nsCString data;
+ aMessage->GetData(data);
+
+ const char* buffer = data.get();
+ uint32_t len = data.Length();
+
+ FallibleTArray<uint8_t>& rawData = aMessage->GetDataAsTArray();
+ uint32_t rawLen = rawData.Length();
+
+ if (len != rawLen) {
+ fail("Raw data length(%d) do not matches String data length(%d).", rawLen, len);
+ return false;
+ }
+
+ for (uint32_t i = 0; i < len; i++) {
+ if (buffer[i] != rawData[i]) {
+ fail("Raw data(%s) do not matches String data(%s)", rawData.Elements() ,buffer);
+ return false;
+ }
+ }
+
+ uint32_t input = 0;
+ for (uint32_t i = 0; i < len; i++) {
+ input += buffer[i] << (8 * i);
+ }
+
+ if (len != sizeof(uint32_t) || input != aExpectedContent)
+ {
+ fail("Request 0x%x received, expected 0x%x", input, aExpectedContent);
+ return false;
+ } else {
+ passed("Request 0x%x received as expected", input);
+ return true;
+ }
+}
+
+/*
+ * UDPClientListener: listens for incomming UDP packets
+ */
+class UDPClientListener : public nsIUDPSocketListener
+{
+protected:
+ virtual ~UDPClientListener();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+ nsresult mResult;
+};
+
+NS_IMPL_ISUPPORTS(UDPClientListener, nsIUDPSocketListener)
+
+UDPClientListener::~UDPClientListener() = default;
+
+NS_IMETHODIMP
+UDPClientListener::OnPacketReceived(nsIUDPSocket* socket, nsIUDPMessage* message)
+{
+ mResult = NS_OK;
+
+ uint16_t port;
+ nsCString ip;
+ nsCOMPtr<nsINetAddr> fromAddr;
+ message->GetFromAddr(getter_AddRefs(fromAddr));
+ fromAddr->GetPort(&port);
+ fromAddr->GetAddress(ip);
+ passed("Packet received on client from %s:%d", ip.get(), port);
+
+ if (TEST_SEND_API == phase && CheckMessageContent(message, REQUEST)) {
+ uint32_t count;
+ const uint32_t data = RESPONSE;
+ printf("*** Attempting to write response 0x%x to server by SendWithAddr...\n", RESPONSE);
+ mResult = socket->SendWithAddr(fromAddr, (const uint8_t*)&data,
+ sizeof(uint32_t), &count);
+ if (mResult == NS_OK && count == sizeof(uint32_t)) {
+ passed("Response written");
+ } else {
+ fail("Response written");
+ }
+ return NS_OK;
+ } else if (TEST_OUTPUT_STREAM != phase || !CheckMessageContent(message, RESPONSE)) {
+ mResult = NS_ERROR_FAILURE;
+ }
+
+ // Notify thread
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPClientListener::OnStopListening(nsIUDPSocket*, nsresult)
+{
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+/*
+ * UDPServerListener: listens for incomming UDP packets
+ */
+class UDPServerListener : public nsIUDPSocketListener
+{
+protected:
+ virtual ~UDPServerListener();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ nsresult mResult;
+};
+
+NS_IMPL_ISUPPORTS(UDPServerListener, nsIUDPSocketListener)
+
+UDPServerListener::~UDPServerListener() = default;
+
+NS_IMETHODIMP
+UDPServerListener::OnPacketReceived(nsIUDPSocket* socket, nsIUDPMessage* message)
+{
+ mResult = NS_OK;
+
+ uint16_t port;
+ nsCString ip;
+ nsCOMPtr<nsINetAddr> fromAddr;
+ message->GetFromAddr(getter_AddRefs(fromAddr));
+ fromAddr->GetPort(&port);
+ fromAddr->GetAddress(ip);
+ passed("Packet received on server from %s:%d", ip.get(), port);
+
+ if (TEST_OUTPUT_STREAM == phase && CheckMessageContent(message, REQUEST))
+ {
+ nsCOMPtr<nsIOutputStream> outstream;
+ message->GetOutputStream(getter_AddRefs(outstream));
+
+ uint32_t count;
+ const uint32_t data = RESPONSE;
+ printf("*** Attempting to write response 0x%x to client by OutputStream...\n", RESPONSE);
+ mResult = outstream->Write((const char*)&data, sizeof(uint32_t), &count);
+
+ if (mResult == NS_OK && count == sizeof(uint32_t)) {
+ passed("Response written");
+ } else {
+ fail("Response written");
+ }
+ return NS_OK;
+ } else if (TEST_MULTICAST == phase && CheckMessageContent(message, REQUEST)) {
+ mResult = NS_OK;
+ } else if (TEST_SEND_API != phase || !CheckMessageContent(message, RESPONSE)) {
+ mResult = NS_ERROR_FAILURE;
+ }
+
+ // Notify thread
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPServerListener::OnStopListening(nsIUDPSocket*, nsresult)
+{
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+/**
+ * Multicast timer callback: detects delivery failure
+ */
+class MulticastTimerCallback : public nsITimerCallback
+{
+protected:
+ virtual ~MulticastTimerCallback();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ nsresult mResult;
+};
+
+NS_IMPL_ISUPPORTS(MulticastTimerCallback, nsITimerCallback)
+
+MulticastTimerCallback::~MulticastTimerCallback() = default;
+
+NS_IMETHODIMP
+MulticastTimerCallback::Notify(nsITimer* timer)
+{
+ if (TEST_MULTICAST != phase) {
+ return NS_OK;
+ }
+ // Multicast ping failed
+ printf("Multicast ping timeout expired\n");
+ mResult = NS_ERROR_FAILURE;
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+/**** Main ****/
+int
+main(int32_t argc, char *argv[])
+{
+ nsresult rv;
+ ScopedXPCOM xpcom("UDP ServerSocket");
+ if (xpcom.failed())
+ return -1;
+
+ // Create UDPSocket
+ nsCOMPtr<nsIUDPSocket> server, client;
+ server = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+ client = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ // Create UDPServerListener to process UDP packets
+ RefPtr<UDPServerListener> serverListener = new UDPServerListener();
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ // Bind server socket to 0.0.0.0
+ rv = server->Init(0, false, systemPrincipal, true, 0);
+ NS_ENSURE_SUCCESS(rv, -1);
+ int32_t serverPort;
+ server->GetPort(&serverPort);
+ server->AsyncListen(serverListener);
+
+ // Bind clinet on arbitrary port
+ RefPtr<UDPClientListener> clientListener = new UDPClientListener();
+ client->Init(0, false, systemPrincipal, true, 0);
+ client->AsyncListen(clientListener);
+
+ // Write data to server
+ uint32_t count;
+ const uint32_t data = REQUEST;
+
+ phase = TEST_OUTPUT_STREAM;
+ rv = client->Send(NS_LITERAL_CSTRING("127.0.0.1"), serverPort, (uint8_t*)&data, sizeof(uint32_t), &count);
+ NS_ENSURE_SUCCESS(rv, -1);
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Request written by Send");
+
+ // Wait for server
+ PumpEvents();
+ NS_ENSURE_SUCCESS(serverListener->mResult, -1);
+
+ // Read response from server
+ NS_ENSURE_SUCCESS(clientListener->mResult, -1);
+
+ mozilla::net::NetAddr clientAddr;
+ rv = client->GetAddress(&clientAddr);
+ NS_ENSURE_SUCCESS(rv, -1);
+ // The client address is 0.0.0.0, but Windows won't receive packets there, so
+ // use 127.0.0.1 explicitly
+ clientAddr.inet.ip = PR_htonl(127 << 24 | 1);
+
+ phase = TEST_SEND_API;
+ rv = server->SendWithAddress(&clientAddr, (uint8_t*)&data, sizeof(uint32_t), &count);
+ NS_ENSURE_SUCCESS(rv, -1);
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Request written by SendWithAddress");
+
+ // Wait for server
+ PumpEvents();
+ NS_ENSURE_SUCCESS(serverListener->mResult, -1);
+
+ // Read response from server
+ NS_ENSURE_SUCCESS(clientListener->mResult, -1);
+
+ // Setup timer to detect multicast failure
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+ if (NS_WARN_IF(!timer)) {
+ return -1;
+ }
+ RefPtr<MulticastTimerCallback> timerCb = new MulticastTimerCallback();
+
+ // The following multicast tests using multiple sockets require a firewall
+ // exception on Windows XP (the earliest version of Windows we now support)
+ // before they pass. For now, we'll skip them here. Later versions of Windows
+ // (Win2003 and onward) don't seem to have this issue.
+#ifdef XP_WIN
+ if (!mozilla::IsWin2003OrLater()) { // i.e. if it is WinXP
+ goto close;
+ }
+#endif
+
+ // Join multicast group
+ printf("Joining multicast group\n");
+ phase = TEST_MULTICAST;
+ mozilla::net::NetAddr multicastAddr;
+ multicastAddr.inet.family = AF_INET;
+ multicastAddr.inet.ip = PR_htonl(224 << 24 | 255);
+ multicastAddr.inet.port = PR_htons(serverPort);
+ rv = server->JoinMulticastAddr(multicastAddr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to receive successfully
+ PumpEvents();
+ if (NS_WARN_IF(NS_FAILED(serverListener->mResult))) {
+ return -1;
+ }
+ if (NS_WARN_IF(NS_FAILED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server received ping successfully");
+
+ // Disable multicast loopback
+ printf("Disable multicast loopback\n");
+ client->SetMulticastLoopback(false);
+ server->SetMulticastLoopback(false);
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to fail to receive
+ PumpEvents();
+ if (NS_WARN_IF(NS_SUCCEEDED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server failed to receive ping correctly");
+
+ // Reset state
+ client->SetMulticastLoopback(true);
+ server->SetMulticastLoopback(true);
+
+ // Change multicast interface
+ printf("Changing multicast interface\n");
+ mozilla::net::NetAddr loopbackAddr;
+ loopbackAddr.inet.family = AF_INET;
+ loopbackAddr.inet.ip = PR_htonl(INADDR_LOOPBACK);
+ client->SetMulticastInterfaceAddr(loopbackAddr);
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to fail to receive
+ PumpEvents();
+ if (NS_WARN_IF(NS_SUCCEEDED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server failed to receive ping correctly");
+
+ // Reset state
+ mozilla::net::NetAddr anyAddr;
+ anyAddr.inet.family = AF_INET;
+ anyAddr.inet.ip = PR_htonl(INADDR_ANY);
+ client->SetMulticastInterfaceAddr(anyAddr);
+
+ // Leave multicast group
+ printf("Leave multicast group\n");
+ rv = server->LeaveMulticastAddr(multicastAddr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to fail to receive
+ PumpEvents();
+ if (NS_WARN_IF(NS_SUCCEEDED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server failed to receive ping correctly");
+ goto close;
+
+close:
+ // Close server
+ printf("*** Attempting to close server ...\n");
+ server->Close();
+ client->Close();
+ PumpEvents();
+ passed("Server closed");
+
+ return 0; // failure is a non-zero return
+}
diff --git a/netwerk/test/TestUDPSocketProvider.cpp b/netwerk/test/TestUDPSocketProvider.cpp
new file mode 100644
index 000000000..921e610ab
--- /dev/null
+++ b/netwerk/test/TestUDPSocketProvider.cpp
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "stdio.h"
+#include "TestCommon.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nspr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+
+#define UDP_PORT 9050
+
+#define UDP_ASSERT(condition, message) \
+ PR_BEGIN_MACRO \
+ NS_ASSERTION(condition, message); \
+ if (!(condition)) { \
+ returnCode = -1; \
+ break; \
+ } \
+ PR_END_MACRO
+
+#define UDP_ASSERT_PRSTATUS(message) \
+ PR_BEGIN_MACRO \
+ NS_ASSERTION(status == PR_SUCCESS, message); \
+ if (status != PR_SUCCESS) { \
+ PRErrorCode err = PR_GetError(); \
+ fprintf(stderr, \
+ "FAIL nspr: %s: (%08x) %s\n", \
+ message, \
+ err, \
+ PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT)); \
+ returnCode = -1; \
+ break; \
+ } \
+ PR_END_MACRO
+
+#define UDP_ASSERT_NSRESULT(message) \
+ PR_BEGIN_MACRO \
+ NS_ASSERTION(NS_SUCCEEDED(rv), message); \
+ if (NS_FAILED(rv)) { \
+ fprintf(stderr, "FAIL UDPSocket: %s: %08x\n", \
+ message, rv); \
+ returnCode = -1; \
+ break; \
+ } \
+ PR_END_MACRO
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ int returnCode = 0;
+ nsresult rv = NS_OK;
+ PRFileDesc *serverFD = nullptr;
+
+ do { // act both as a scope for nsCOMPtrs to be released before XPCOM
+ // shutdown, as well as a easy way to abort the test
+ PRStatus status = PR_SUCCESS;
+
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ UDP_ASSERT(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ // listen for a incoming UDP connection on localhost
+ serverFD = PR_OpenUDPSocket(PR_AF_INET);
+ UDP_ASSERT(serverFD, "Cannot open UDP socket for listening");
+
+ PRSocketOptionData socketOptions;
+ socketOptions.option = PR_SockOpt_Nonblocking;
+ socketOptions.value.non_blocking = false;
+ status = PR_SetSocketOption(serverFD, &socketOptions);
+ UDP_ASSERT_PRSTATUS("Failed to set server socket as blocking");
+
+ PRNetAddr addr;
+ status = PR_InitializeNetAddr(PR_IpAddrLoopback, UDP_PORT, &addr);
+ UDP_ASSERT_PRSTATUS("Failed to initialize loopback address");
+
+ status = PR_Bind(serverFD, &addr);
+ UDP_ASSERT_PRSTATUS("Failed to bind server socket");
+
+ // dummy IOService to get around bug 379890
+ nsCOMPtr<nsISupports> ios =
+ do_GetService("@mozilla.org/network/io-service;1");
+
+ // and have a matching UDP connection for the client
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
+ UDP_ASSERT_NSRESULT("Cannot get socket transport service");
+
+ nsCOMPtr<nsISocketTransport> transport;
+ const char *protocol = "udp";
+ rv = sts->CreateTransport(&protocol, 1, NS_LITERAL_CSTRING("localhost"),
+ UDP_PORT, nullptr, getter_AddRefs(transport));
+ UDP_ASSERT_NSRESULT("Cannot create transport");
+
+ uint32_t count, read;
+ const uint32_t data = 0xFF0056A9;
+
+ // write to the output stream
+ nsCOMPtr<nsIOutputStream> outstream;
+ rv = transport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
+ 0, 0, getter_AddRefs(outstream));
+ UDP_ASSERT_NSRESULT("Cannot open output stream");
+
+ rv = outstream->Write((const char*)&data, sizeof(uint32_t), &count);
+ UDP_ASSERT_NSRESULT("Cannot write to output stream");
+ UDP_ASSERT(count == sizeof(uint32_t),
+ "Did not write enough bytes to output stream");
+
+ // read from NSPR to check it's the same
+ count = PR_RecvFrom(serverFD, &read, sizeof(uint32_t), 0, &addr, 1);
+ UDP_ASSERT(count == sizeof(uint32_t),
+ "Did not read enough bytes from NSPR");
+ status = (read == data ? PR_SUCCESS : PR_FAILURE);
+ UDP_ASSERT_PRSTATUS("Did not read expected data from NSPR");
+
+ // write to NSPR
+ count = PR_SendTo(serverFD, &data, sizeof(uint32_t), 0, &addr, 1);
+ status = (count == sizeof(uint32_t) ? PR_SUCCESS : PR_FAILURE);
+ UDP_ASSERT_PRSTATUS("Did not write enough bytes to NSPR");
+
+ // read from stream
+ nsCOMPtr<nsIInputStream> instream;
+ rv = transport->OpenInputStream(nsITransport::OPEN_BLOCKING,
+ 0, 0, getter_AddRefs(instream));
+ UDP_ASSERT_NSRESULT("Cannot open input stream");
+
+ rv = instream->Read((char*)&read, sizeof(uint32_t), &count);
+ UDP_ASSERT_NSRESULT("Cannot read from input stream");
+ UDP_ASSERT(count == sizeof(uint32_t),
+ "Did not read enough bytes from input stream");
+ UDP_ASSERT(read == data, "Did not read expected data from stream");
+
+ } while (false); // release all XPCOM things
+ if (serverFD) {
+ PRStatus status = PR_Close(serverFD);
+ if (status != PR_SUCCESS) {
+ PRErrorCode err = PR_GetError();
+ fprintf(stderr, "FAIL: Cannot close server: (%08x) %s\n",
+ err, PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT));
+ }
+ }
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+
+ return returnCode;
+}
+
diff --git a/netwerk/test/TestURLManipulation.html b/netwerk/test/TestURLManipulation.html
new file mode 100644
index 000000000..fd58652b7
--- /dev/null
+++ b/netwerk/test/TestURLManipulation.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <title>URL manipulation</title>
+ <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+
+ <script type="text/javascript">
+ var gIOService = null;
+ function getIOService()
+ {
+ if (gIOService)
+ return gIOService;
+
+ try {
+ gIOService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ } catch(e) { dump("problem creating nsIURL for: "+inURLString+"\n"); }
+
+ return gIOService;
+ }
+
+ function getnsIURL(inURLString)
+ {
+ var URL = null;
+ var ioserv = getIOService();
+ try {
+ var URI = ioserv.newURI(inURLString, "", null);
+ URL = URI.QueryInterface(Components.interfaces.nsIURL);
+ } catch(e) { dump("problem creating nsIURL for: "+inURLString+"\n"); }
+ return URL;
+ }
+
+ function getCommonSpec()
+ {
+ var URL1 = getnsIURL(document.foo.baseEdit.value);
+ var URL2 = getnsIURL(document.foo.compareEdit.value);
+ var result = "";
+ try {
+ result = URL1.getCommonBaseSpec(URL2);
+ } catch(e) { dump("problem with getCommonSpec ("+e+")\n"); }
+ document.foo.resultEdit.value = result;
+ }
+
+ function getRelativeSpec()
+ {
+ var URL1 = getnsIURL(document.foo.baseEdit.value);
+ var URL2 = getnsIURL(document.foo.compareEdit.value);
+ var result = "";
+ try {
+ result = URL1.getRelativeSpec(URL2);
+ } catch(e) { dump("problem with getRelativeSpec ("+e+")\n"); }
+ document.foo.resultEdit.value = result;
+ }
+
+ function doResolve()
+ {
+ var URL = getnsIURL(document.foo.baseEdit.value);
+ var result = "";
+ try {
+ result = URL.resolve(document.foo.resultEdit.value);
+ } catch(e) { dump("problem with getRelativeSpec ("+e+")\n"); }
+ document.foo.compareEdit.value = result;
+ }
+ </script>
+</head>
+<body>
+<h1>testing of URL manipulation:</h1>
+<p>
+ <form name="foo">
+ <p>
+ <label for="baseEdit">base url (absolute)</label><br>
+ <input type="input" name="baseEdit" size="80" value="http://www.mozilla.org/">
+
+ <p>
+ <label for="compareEdit">comparison uri (absolute)</label><br>
+ <input type="input" name="compareEdit" size="80">
+
+ <p>
+ <label for="resultEdit">resolved url</label><br>
+ <input type="input" name="resultEdit" size="80">
+
+ <p>
+ <input type="button" onclick="getCommonSpec();" value="Get Common Spec">
+ <input type="button" onclick="getRelativeSpec();" value="Get Relative Spec">
+ <input type="button" onclick="doResolve();" value="Resolve">
+ <h5> note: results from "resolve" are placed in "comparison uri" edit field</h5>
+ </form>
+<p>
+<br>
+
+<h3>notes for testing</h3>
+different types of uris:<br>
+<ul>
+ <li>about:</li>
+ <li>about:blank</li>
+ <li>mailbox://nsmail-2.mcom.com/xxx</li>
+ <li>mailto:brade@netscape.com)</li>
+ <li>junk</li>
+ <li>http://foo/</li>
+ <li>http://foo.com/</li>
+ <li>https://foo.com/</li>
+ <li>ftp://ftp.mozilla.org/</li>
+ <li>http://foo.com:8080/</li>
+ <li>http://brade@foo.com/</li>
+ <li>http://brade:password@foo.com/</li>
+ <li>http://brade:@foo.com:8080/</li>
+ <li>file:///</li>
+ <li>file:///Quest/Desktop%20Folder/test.html</li>
+</ul>
+other variations:<br>
+<ul>
+ <li>sub-directories on above list</li>
+ <li>files on above list</li>
+ <li>sub-directories and files on above list<br>
+ </li>
+ <li>directories which don't end in a '/'</li>
+ <li>files with queries</li>
+ <li>files with no extension</li>
+ <li>files with references</li>
+ <li>files with params</li>
+ <li>other schemes (chrome, ldap, news, finger, etc.)<br>
+ </li>
+</ul>
+<br>
+This should be true:<br>
+&nbsp; resultString = baseURL.getRelativeSpec(URL);<br>
+&lt;==&gt;<br>
+&nbsp; baseURL.resolve(resultString) == URL.spec;<br>
+</body>
+</html>
diff --git a/netwerk/test/TestURLParser.cpp b/netwerk/test/TestURLParser.cpp
new file mode 100644
index 000000000..43f126e72
--- /dev/null
+++ b/netwerk/test/TestURLParser.cpp
@@ -0,0 +1,135 @@
+#include "TestCommon.h"
+#include <stdio.h>
+#include "nsIURLParser.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+static void
+print_field(const char *label, char *str, int32_t len)
+{
+ char c = str[len];
+ str[len] = '\0';
+ printf("[%s=%s]\n", label, str);
+ str[len] = c;
+}
+
+#define PRINT_FIELD(x) \
+ print_field(# x, x, x ## Len)
+
+#define PRINT_SUBFIELD(base, x) \
+ PR_BEGIN_MACRO \
+ if (x ## Len != -1) \
+ print_field(# x, base + x ## Pos, x ## Len); \
+ PR_END_MACRO
+
+static void
+parse_authority(nsIURLParser *urlParser, char *auth, int32_t authLen)
+{
+ PRINT_FIELD(auth);
+
+ uint32_t usernamePos, passwordPos;
+ int32_t usernameLen, passwordLen;
+ uint32_t hostnamePos;
+ int32_t hostnameLen, port;
+
+ urlParser->ParseAuthority(auth, authLen,
+ &usernamePos, &usernameLen,
+ &passwordPos, &passwordLen,
+ &hostnamePos, &hostnameLen,
+ &port);
+
+ PRINT_SUBFIELD(auth, username);
+ PRINT_SUBFIELD(auth, password);
+ PRINT_SUBFIELD(auth, hostname);
+ if (port != -1)
+ printf("[port=%d]\n", port);
+}
+
+static void
+parse_file_path(nsIURLParser *urlParser, char *filepath, int32_t filepathLen)
+{
+ PRINT_FIELD(filepath);
+
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+
+ urlParser->ParseFilePath(filepath, filepathLen,
+ &dirPos, &dirLen,
+ &basePos, &baseLen,
+ &extPos, &extLen);
+
+ PRINT_SUBFIELD(filepath, dir);
+ PRINT_SUBFIELD(filepath, base);
+ PRINT_SUBFIELD(filepath, ext);
+}
+
+static void
+parse_path(nsIURLParser *urlParser, char *path, int32_t pathLen)
+{
+ PRINT_FIELD(path);
+
+ uint32_t filePos, queryPos, refPos;
+ int32_t fileLen, queryLen, refLen;
+
+ urlParser->ParsePath(path, pathLen,
+ &filePos, &fileLen,
+ &queryPos, &queryLen,
+ &refPos, &refLen);
+
+ if (fileLen != -1)
+ parse_file_path(urlParser, path + filePos, fileLen);
+ PRINT_SUBFIELD(path, query);
+ PRINT_SUBFIELD(path, ref);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ if (argc < 2) {
+ printf("usage: TestURLParser [-std|-noauth|-auth] <url>\n");
+ return -1;
+ }
+ nsCOMPtr<nsIURLParser> urlParser;
+ if (strcmp(argv[1], "-noauth") == 0) {
+ urlParser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
+ argv[1] = argv[2];
+ }
+ else if (strcmp(argv[1], "-auth") == 0) {
+ urlParser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
+ argv[1] = argv[2];
+ }
+ else {
+ urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ if (strcmp(argv[1], "-std") == 0)
+ argv[1] = argv[2];
+ else
+ printf("assuming -std\n");
+ }
+ if (urlParser) {
+ printf("have urlParser @%p\n", static_cast<void*>(urlParser.get()));
+
+ char *spec = argv[1];
+ uint32_t schemePos, authPos, pathPos;
+ int32_t schemeLen, authLen, pathLen;
+
+ urlParser->ParseURL(spec, -1,
+ &schemePos, &schemeLen,
+ &authPos, &authLen,
+ &pathPos, &pathLen);
+
+ if (schemeLen != -1)
+ PRINT_SUBFIELD(spec, scheme);
+ if (authLen != -1)
+ parse_authority(urlParser, spec + authPos, authLen);
+ if (pathLen != -1)
+ parse_path(urlParser, spec + pathPos, pathLen);
+ }
+ else
+ printf("no urlParser\n");
+ return 0;
+}
diff --git a/netwerk/test/TestUpload.cpp b/netwerk/test/TestUpload.cpp
new file mode 100644
index 000000000..5818a5ccb
--- /dev/null
+++ b/netwerk/test/TestUpload.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "TestCommon.h"
+#include <algorithm>
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nsIComponentRegistrar.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIServiceManager.h"
+#include "nsNetUtil.h"
+
+#include "nsIUploadChannel.h"
+
+#include "NetwerkTestLogging.h"
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// InputTestConsumer
+//-----------------------------------------------------------------------------
+
+class InputTestConsumer : public nsIStreamListener
+{
+ virtual ~InputTestConsumer();
+
+public:
+
+ InputTestConsumer();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+InputTestConsumer::InputTestConsumer()
+{
+}
+
+InputTestConsumer::~InputTestConsumer() = default;
+
+NS_IMPL_ISUPPORTS(InputTestConsumer,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
+{
+ LOG(("InputTestConsumer::OnStartRequest\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputTestConsumer::OnDataAvailable(nsIRequest *request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ char buf[1025];
+ uint32_t amt, size;
+ nsresult rv;
+
+ while (aLength) {
+ size = std::min<uint32_t>(aLength, sizeof(buf));
+ rv = aIStream->Read(buf, size, &amt);
+ if (NS_FAILED(rv)) {
+ NS_ASSERTION((NS_BASE_STREAM_WOULD_BLOCK != rv),
+ "The stream should never block.");
+ return rv;
+ }
+ aLength -= amt;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnStopRequest(nsIRequest *request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("InputTestConsumer::OnStopRequest [status=%x]\n", aStatus));
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <url> <file-to-upload>\n", argv[0]);
+ return -1;
+ }
+ char* uriSpec = argv[1];
+ char* fileName = argv[2];
+
+ gTestLog = PR_NewLogModule("Test");
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ // first thing to do is create ourselves a stream that
+ // is to be uploaded.
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv = NS_NewPostDataStream(getter_AddRefs(uploadStream),
+ true,
+ nsDependentCString(fileName)); // XXX UTF-8
+ if (NS_FAILED(rv)) return -1;
+
+ // create our url.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv)) return -1;
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return -1;
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ if (NS_FAILED(rv)) return -1;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) return -1;
+
+ // QI and set the upload stream
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(channel));
+ uploadChannel->SetUploadStream(uploadStream, EmptyCString(), -1);
+
+ // create a dummy listener
+ InputTestConsumer* listener;
+
+ listener = new InputTestConsumer;
+ if (!listener) {
+ NS_ERROR("Failed to create a new stream listener!");
+ return -1;
+ }
+ NS_ADDREF(listener);
+
+ channel->AsyncOpen2(listener);
+
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+
+ return 0;
+}
+
diff --git a/netwerk/test/TestWriteSpeed.cpp b/netwerk/test/TestWriteSpeed.cpp
new file mode 100644
index 000000000..0b0260fdd
--- /dev/null
+++ b/netwerk/test/TestWriteSpeed.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "prio.h"
+#include "prinrval.h"
+#include "prmem.h"
+#include <stdio.h>
+#include <math.h>
+
+void
+NS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues,
+ double *meanResult, double *stdDevResult)
+{
+ double mean = 0.0, var = 0.0, stdDev = 0.0;
+ if (n > 0.0 && sumOfValues >= 0) {
+ mean = sumOfValues / n;
+ double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues);
+ if (temp < 0.0 || n <= 1)
+ var = 0.0;
+ else
+ var = temp / (n * (n - 1));
+ // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
+ stdDev = var != 0.0 ? sqrt(var) : 0.0;
+ }
+ *meanResult = mean;
+ *stdDevResult = stdDev;
+}
+
+int
+Test(const char* filename, int32_t minSize, int32_t maxSize,
+ int32_t sizeIncrement, int32_t iterations)
+{
+ fprintf(stdout, " size write: mean stddev iters total: mean stddev iters\n");
+ for (int32_t size = minSize; size <= maxSize; size += sizeIncrement) {
+ // create a buffer of stuff to write
+ char* buf = (char*)PR_Malloc(size);
+ if (buf == nullptr)
+ return -1;
+
+ // initialize it with a pattern
+ int32_t i;
+ char hex[] = "0123456789ABCDEF";
+ for (i = 0; i < size; i++) {
+ buf[i] = hex[i & 0xF];
+ }
+
+ double writeCount = 0, writeRate = 0, writeRateSquared = 0;
+ double totalCount = 0, totalRate = 0, totalRateSquared = 0;
+ for (i = 0; i < iterations; i++) {
+ PRIntervalTime start = PR_IntervalNow();
+
+ char name[1024];
+ sprintf(name, "%s_%d", filename, i);
+ PRFileDesc* fd = PR_Open(name, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0664);
+ if (fd == nullptr)
+ return -1;
+
+ PRIntervalTime writeStart = PR_IntervalNow();
+ int32_t rv = PR_Write(fd, buf, size);
+ if (rv < 0) return rv;
+ if (rv != size) return -1;
+ PRIntervalTime writeStop = PR_IntervalNow();
+
+ PRStatus st = PR_Close(fd);
+ if (st == PR_FAILURE) return -1;
+
+ PRIntervalTime stop = PR_IntervalNow();
+
+ PRIntervalTime writeTime = PR_IntervalToMilliseconds(writeStop - writeStart);
+ if (writeTime > 0) {
+ double wr = size / writeTime;
+ writeRate += wr;
+ writeRateSquared += wr * wr;
+ writeCount++;
+ }
+
+ PRIntervalTime totalTime = PR_IntervalToMilliseconds(stop - start);
+ if (totalTime > 0) {
+ double t = size / totalTime;
+ totalRate += t;
+ totalRateSquared += t * t;
+ totalCount++;
+ }
+ }
+
+ PR_Free(buf);
+
+ double writeMean, writeStddev;
+ double totalMean, totalStddev;
+ NS_MeanAndStdDev(writeCount, writeRate, writeRateSquared,
+ &writeMean, &writeStddev);
+ NS_MeanAndStdDev(totalCount, totalRate, totalRateSquared,
+ &totalMean, &totalStddev);
+ fprintf(stdout, "%10d %10.2f %10.2f %10d %10.2f %10.2f %10d\n",
+ size, writeMean, writeStddev, (int32_t)writeCount,
+ totalMean, totalStddev, (int32_t)totalCount);
+ }
+ return 0;
+}
+
+int
+main(int argc, char* argv[])
+{
+ if (argc != 5) {
+ printf("usage: %s <min buf size (K)> <max buf size (K)> <size increment (K)> <iterations>\n", argv[0]);
+ return -1;
+ }
+ Test("y:\\foo",
+ atoi(argv[1]) * 1024,
+ atoi(argv[2]) * 1024,
+ atoi(argv[3]) * 1024,
+ atoi(argv[4]));
+ return 0;
+}
diff --git a/netwerk/test/browser/browser.ini b/netwerk/test/browser/browser.ini
new file mode 100644
index 000000000..8611891fd
--- /dev/null
+++ b/netwerk/test/browser/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ dummy.html
+
+[browser_about_cache.js]
+[browser_NetUtil.js]
+[browser_child_resource.js]
+skip-if = e10s && debug && os == "linux" && bits == 64
+[browser_post_file.js]
+[browser_nsIFormPOSTActionChannel.js]
+skip-if = e10s # protocol handler and channel does not work in content process
diff --git a/netwerk/test/browser/browser_NetUtil.js b/netwerk/test/browser/browser_NetUtil.js
new file mode 100644
index 000000000..a6c4f2bcd
--- /dev/null
+++ b/netwerk/test/browser/browser_NetUtil.js
@@ -0,0 +1,92 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // We overload this test to include verifying that httpd.js is
+ // importable as a testing-only JS module.
+ Components.utils.import("resource://testing-common/httpd.js", {});
+
+ nextTest();
+}
+
+function nextTest() {
+ if (tests.length)
+ executeSoon(tests.shift());
+ else
+ executeSoon(finish);
+}
+
+var tests = [
+ test_asyncFetchBadCert,
+];
+
+function test_asyncFetchBadCert() {
+ // Try a load from an untrusted cert, with errors supressed
+ NetUtil.asyncFetch({
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true
+ }, function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+
+ // Now try again with a channel whose notificationCallbacks doesn't suprress errors
+ let channel = NetUtil.newChannel({
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true});
+ channel.notificationCallbacks = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink,
+ Ci.nsIInterfaceRequestor]),
+ getInterface: function (aIID) { return this.QueryInterface(aIID); },
+ onProgress: function () {},
+ onStatus: function () {}
+ };
+ NetUtil.asyncFetch(channel, function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+
+ // Now try a valid request
+ NetUtil.asyncFetch({
+ uri: "https://example.com",
+ loadUsingSystemPrincipal: true
+ }, function (aInputStream, aStatusCode, aRequest) {
+ info("aStatusCode for valid request: " + aStatusCode);
+ ok(Components.isSuccessCode(aStatusCode), "request succeeded");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+ ok(aRequest.requestSucceeded, "HTTP request succeeded");
+
+ nextTest();
+ });
+ });
+ });
+}
+
+function WindowListener(aURL, aCallback) {
+ this.callback = aCallback;
+ this.url = aURL;
+}
+WindowListener.prototype = {
+ onOpenWindow: function(aXULWindow) {
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var self = this;
+ domwindow.addEventListener("load", function() {
+ domwindow.removeEventListener("load", arguments.callee, false);
+
+ if (domwindow.document.location.href != self.url)
+ return;
+
+ // Allow other window load listeners to execute before passing to callback
+ executeSoon(function() {
+ self.callback(domwindow);
+ });
+ }, false);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {}
+}
diff --git a/netwerk/test/browser/browser_about_cache.js b/netwerk/test/browser/browser_about_cache.js
new file mode 100644
index 000000000..38cfa3d02
--- /dev/null
+++ b/netwerk/test/browser/browser_about_cache.js
@@ -0,0 +1,71 @@
+"use strict";
+
+/**
+ * Open a dummy page, then open about:cache and verify the opened page shows up in the cache.
+ */
+add_task(function*() {
+ const kRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+ "https://example.com/");
+ const kTestPage = kRoot + "dummy.html";
+ // Open the dummy page to get it cached.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kTestPage, true);
+ yield BrowserTestUtils.removeTab(tab);
+
+ tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:cache", true);
+ let expectedPageCheck = function(uri) {
+ info("Saw load for " + uri);
+ // Can't easily use searchParms and new URL() because it's an about: URI...
+ return uri.startsWith("about:cache?") && uri.includes("storage=disk");
+ };
+ let diskPageLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, expectedPageCheck);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache should not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ let channel = content.document.docShell.currentDocumentChannel;
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let links = [... content.document.querySelectorAll("a[href*=disk]")];
+ is(links.length, 1, "Should have 1 link to the disk entries");
+ links[0].click();
+ });
+ yield diskPageLoaded;
+ info("about:cache disk subpage loaded");
+
+ expectedPageCheck = function(uri) {
+ info("Saw load for " + uri);
+ return uri.startsWith("about:cache-entry") && uri.includes("dummy.html");
+ };
+ let triggeringURISpec = tab.linkedBrowser.currentURI.spec;
+ let entryLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, expectedPageCheck);
+ yield ContentTask.spawn(tab.linkedBrowser, kTestPage, function(kTestPage) {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache with query params should still not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let channel = content.document.docShell.currentDocumentChannel;
+ principalURI = channel.loadInfo.triggeringPrincipal.URI;
+ is(principalURI && principalURI.spec, "about:cache", "Triggering principal matches previous location");
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ let links = [... content.document.querySelectorAll("a[href*='" + kTestPage + "']")];
+ is(links.length, 1, "Should have 1 link to the entry for " + kTestPage);
+ links[0].click();
+ });
+ yield entryLoaded;
+ info("about:cache entry loaded");
+
+
+ yield ContentTask.spawn(tab.linkedBrowser, triggeringURISpec, function(triggeringURISpec) {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache-entry should also not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let channel = content.document.docShell.currentDocumentChannel;
+ principalURI = channel.loadInfo.triggeringPrincipal.URI;
+ is(principalURI && principalURI.spec, triggeringURISpec, "Triggering principal matches previous location");
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ ok(content.document.querySelectorAll("th").length,
+ "Should have several table headers with data.");
+ });
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/test/browser/browser_child_resource.js b/netwerk/test/browser/browser_child_resource.js
new file mode 100644
index 000000000..098e6bd84
--- /dev/null
+++ b/netwerk/test/browser/browser_child_resource.js
@@ -0,0 +1,256 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+// This must be loaded in the remote process for this test to be useful
+const TEST_URL = "http://example.com/browser/netwerk/test/browser/dummy.html";
+
+const expectedRemote = gMultiProcessBrowser ? "true" : "";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+const resProtocol = Cc["@mozilla.org/network/protocol;1?name=resource"]
+ .getService(Ci.nsIResProtocolHandler);
+
+function getMinidumpDirectory() {
+ var dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ dir.append("minidumps");
+ return dir;
+}
+
+// This observer is needed so we can clean up all evidence of the crash so
+// the testrunner thinks things are peachy.
+var CrashObserver = {
+ observe: function(subject, topic, data) {
+ is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+ ok(subject instanceof Ci.nsIPropertyBag2,
+ 'Subject implements nsIPropertyBag2.');
+ // we might see this called as the process terminates due to previous tests.
+ // We are only looking for "abnormal" exits...
+ if (!subject.hasKey("abnormal")) {
+ info("This is a normal termination and isn't the one we are looking for...");
+ return;
+ }
+
+ var dumpID;
+ if ('nsICrashReporter' in Ci) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ ok(dumpID, "dumpID is present and not an empty string");
+ }
+
+ if (dumpID) {
+ var minidumpDirectory = getMinidumpDirectory();
+ let file = minidumpDirectory.clone();
+ file.append(dumpID + '.dmp');
+ file.remove(true);
+ file = minidumpDirectory.clone();
+ file.append(dumpID + '.extra');
+ file.remove(true);
+ }
+ }
+}
+Services.obs.addObserver(CrashObserver, 'ipc:content-shutdown', false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(CrashObserver, 'ipc:content-shutdown');
+});
+
+function frameScript() {
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ let resProtocol = Components.classes["@mozilla.org/network/protocol;1?name=resource"]
+ .getService(Components.interfaces.nsIResProtocolHandler);
+
+ addMessageListener("Test:ResolveURI", function({ data: uri }) {
+ uri = Services.io.newURI(uri, null, null);
+ try {
+ let resolved = resProtocol.resolveURI(uri);
+ sendAsyncMessage("Test:ResolvedURI", resolved);
+ }
+ catch (e) {
+ sendAsyncMessage("Test:ResolvedURI", null);
+ }
+ });
+
+ addMessageListener("Test:Crash", function() {
+ dump("Crashing\n");
+ privateNoteIntentionalCrash();
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+ });
+}
+
+function waitForEvent(obj, name, capturing, chromeEvent) {
+ info("Waiting for " + name);
+ return new Promise((resolve) => {
+ function listener(event) {
+ info("Saw " + name);
+ obj.removeEventListener(name, listener, capturing, chromeEvent);
+ resolve(event);
+ }
+
+ obj.addEventListener(name, listener, capturing, chromeEvent);
+ });
+}
+
+function resolveURI(uri) {
+ uri = Services.io.newURI(uri, null, null);
+ try {
+ return resProtocol.resolveURI(uri);
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function remoteResolveURI(uri) {
+ return new Promise((resolve) => {
+ let manager = gBrowser.selectedBrowser.messageManager;
+
+ function listener({ data: resolved }) {
+ manager.removeMessageListener("Test:ResolvedURI", listener);
+ resolve(resolved);
+ }
+
+ manager.addMessageListener("Test:ResolvedURI", listener);
+ manager.sendAsyncMessage("Test:ResolveURI", uri);
+ });
+}
+
+var loadTestTab = Task.async(function*() {
+ gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ return browser;
+});
+
+// Restarts the child process by crashing it then reloading the tab
+var restart = Task.async(function*() {
+ let browser = gBrowser.selectedBrowser;
+ // If the tab isn't remote this would crash the main process so skip it
+ if (browser.getAttribute("remote") != "true")
+ return browser;
+
+ browser.messageManager.sendAsyncMessage("Test:Crash");
+ yield waitForEvent(browser, "AboutTabCrashedLoad", false, true);
+
+ browser.reload();
+
+ yield BrowserTestUtils.browserLoaded(browser);
+ is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ return browser;
+});
+
+// Sanity check that this test is going to be useful
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ // This must be loaded in the remote process for this test to be useful
+ is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+
+ let local = resolveURI("resource://gre/modules/Services.jsm");
+ let remote = yield remoteResolveURI("resource://gre/modules/Services.jsm");
+ is(local, remote, "Services.jsm should resolve in both processes");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, update it then remove it
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ let local = resolveURI("resource://testing/test.js");
+ let remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ info("Change");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/skin", null, null));
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, restart the child process then check it is still there
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ let local = resolveURI("resource://testing/test.js");
+ let remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ info("Change");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/skin", null, null));
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Adding a mapping to a resource URI should work
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ resProtocol.setSubstitution("testing2", Services.io.newURI("resource://testing", null, null));
+ let local = resolveURI("resource://testing2/test.js");
+ let remote = yield remoteResolveURI("resource://testing2/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = yield remoteResolveURI("resource://testing2/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ resProtocol.setSubstitution("testing2", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = yield remoteResolveURI("resource://testing2/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
new file mode 100644
index 000000000..150c4feca
--- /dev/null
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -0,0 +1,284 @@
+/*
+ * Tests for bug 1241377: A channel with nsIFormPOSTActionChannel interface
+ * should be able to accept form POST.
+ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+const SCHEME = "x-bug1241377";
+
+const FORM_BASE = SCHEME + "://dummy/form/";
+const NORMAL_FORM_URI = FORM_BASE + "normal.html";
+const UPLOAD_FORM_URI = FORM_BASE + "upload.html";
+const POST_FORM_URI = FORM_BASE + "post.html";
+
+const ACTION_BASE = SCHEME + "://dummy/action/";
+const NORMAL_ACTION_URI = ACTION_BASE + "normal.html";
+const UPLOAD_ACTION_URI = ACTION_BASE + "upload.html";
+const POST_ACTION_URI = ACTION_BASE + "post.html";
+
+function CustomProtocolHandler() {
+}
+CustomProtocolHandler.prototype = {
+ /** nsIProtocolHandler */
+ get scheme() {
+ return SCHEME;
+ },
+ get defaultPort() {
+ return -1;
+ },
+ get protocolFlags() {
+ return Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE;
+ },
+ newURI: function(aSpec, aOriginCharset, aBaseURI) {
+ var uri = Cc["@mozilla.org/network/standard-url;1"].
+ createInstance(Ci.nsIURI);
+ uri.spec = aSpec;
+ return uri;
+ },
+ newChannel2: function(aURI, aLoadInfo) {
+ return new CustomChannel(aURI, aLoadInfo);
+ },
+ newChannel: function(aURI) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ allowPort: function(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsIFactory */
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ lockFactory: function() {},
+
+ /** nsISupports */
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
+ Ci.nsIFactory]),
+ classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}")
+};
+
+function CustomChannel(aURI, aLoadInfo) {
+ this.uri = aURI;
+ this.loadInfo = aLoadInfo;
+
+ this._uploadStream = null;
+
+ var interfaces = [Ci.nsIRequest, Ci.nsIChannel];
+ if (this.uri.spec == POST_ACTION_URI) {
+ interfaces.push(Ci.nsIFormPOSTActionChannel);
+ } else if (this.uri.spec == UPLOAD_ACTION_URI) {
+ interfaces.push(Ci.nsIUploadChannel);
+ }
+ this.QueryInterface = XPCOMUtils.generateQI(interfaces);
+}
+CustomChannel.prototype = {
+ /** nsIUploadChannel */
+ get uploadStream() {
+ return this._uploadStream;
+ },
+ set uploadStream(val) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ setUploadStream: function(aStream, aContentType, aContentLength) {
+ this._uploadStream = aStream;
+ },
+
+ /** nsIChannel */
+ get originalURI() {
+ return this.uri;
+ },
+ get URI() {
+ return this.uri;
+ },
+ owner: null,
+ notificationCallbacks: null,
+ get securityInfo() {
+ return null;
+ },
+ get contentType() {
+ return "text/html";
+ },
+ set contentType(val) {
+ },
+ contentCharset: "UTF-8",
+ get contentLength() {
+ return -1;
+ },
+ set contentLength(val) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ open: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ open2: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ asyncOpen: function(aListener, aContext) {
+ var data = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>test bug 1241377</title>
+</head>
+<body>
+`;
+
+ if (this.uri.spec.startsWith(FORM_BASE)) {
+ data += `
+<form id="form" action="${this.uri.spec.replace(FORM_BASE, ACTION_BASE)}"
+ method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+
+<iframe id="frame" name="frame" width="200" height="200"></iframe>
+
+<script type="text/javascript">
+<!--
+document.getElementById('form').submit();
+//-->
+</script>
+`;
+ } else if (this.uri.spec.startsWith(ACTION_BASE)) {
+ var postData = "";
+ if (this._uploadStream) {
+ var bstream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ bstream.setInputStream(this._uploadStream);
+ postData = bstream.readBytes(bstream.available());
+ }
+ data += `
+<input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
+<input id="post_data" value="${btoa(postData)}">
+`;
+ }
+
+ data += `
+</body>
+</html>
+`;
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+
+ var runnable = {
+ run: () => {
+ try {
+ aListener.onStartRequest(this, aContext);
+ } catch(e) {}
+ try {
+ aListener.onDataAvailable(this, aContext, stream, 0, stream.available());
+ } catch(e) {}
+ try {
+ aListener.onStopRequest(this, aContext, Cr.NS_OK);
+ } catch(e) {}
+ }
+ };
+ Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ },
+ asyncOpen2: function(aListener) {
+ this.asyncOpen(aListener, null);
+ },
+
+ /** nsIRequest */
+ get name() {
+ return this.uri.spec;
+ },
+ isPending: function () {
+ return 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,
+};
+
+function frameScript() {
+ addMessageListener("Test:WaitForIFrame", function() {
+ var check = function() {
+ if (content) {
+ var frame = content.document.getElementById("frame");
+ if (frame) {
+ var upload_stream = frame.contentDocument.getElementById("upload_stream");
+ var post_data = frame.contentDocument.getElementById("post_data");
+ if (upload_stream && post_data) {
+ sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value]);
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+}
+
+function loadTestTab(uri) {
+ gBrowser.selectedTab = gBrowser.addTab(uri);
+ var browser = gBrowser.selectedBrowser;
+
+ let manager = browser.messageManager;
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+
+ return new Promise(resolve => {
+ function listener({ data: [hasUploadStream, postData] }) {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve([hasUploadStream, atob(postData)]);
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+}
+
+add_task(function*() {
+ var handler = new CustomProtocolHandler();
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(handler.classID, "",
+ "@mozilla.org/network/protocol;1?name=" + handler.scheme,
+ handler);
+ registerCleanupFunction(function() {
+ registrar.unregisterFactory(handler.classID, handler);
+ });
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(NORMAL_FORM_URI);
+ is(hasUploadStream, "no", "normal action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(UPLOAD_FORM_URI);
+ is(hasUploadStream, "no", "upload action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(POST_FORM_URI);
+ is(hasUploadStream, "yes", "post action should have uploadStream");
+ is(postData,
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: 9\r\n" +
+ "\r\n" +
+ "foo=bar\r\n", "POST data is received correctly");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_post_file.js b/netwerk/test/browser/browser_post_file.js
new file mode 100644
index 000000000..6c9fd7a2f
--- /dev/null
+++ b/netwerk/test/browser/browser_post_file.js
@@ -0,0 +1,101 @@
+/*
+ * Tests for bug 1241100: Post to local file should not overwrite the file.
+ */
+
+Components.utils.import("resource://gre/modules/osfile.jsm");
+
+function* createTestFile(filename, content) {
+ let path = OS.Path.join(OS.Constants.Path.tmpDir, filename);
+ yield OS.File.writeAtomic(path, content);
+ return path;
+}
+
+function* readFile(path) {
+ var array = yield OS.File.read(path);
+ var decoder = new TextDecoder();
+ return decoder.decode(array);
+}
+
+function frameScript() {
+ addMessageListener("Test:WaitForIFrame", function() {
+ var check = function() {
+ if (content) {
+ var frame = content.document.getElementById("frame");
+ if (frame) {
+ var okBox = frame.contentDocument.getElementById("action_file_ok");
+ if (okBox) {
+ sendAsyncMessage("Test:IFrameLoaded");
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+}
+
+add_task(function*() {
+ var postFilename = "post_file.html";
+ var actionFilename = "action_file.html";
+
+ var postFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>post file</title>
+</head>
+<body onload="document.getElementById('form').submit();">
+<form id="form" action="${actionFilename}" method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+<iframe id="frame" name="frame"></iframe>
+</body>
+</html>
+`;
+
+ var actionFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>action file</title>
+</head>
+<body>
+<div id="action_file_ok">ok</div>
+</body>
+</html>
+`;
+
+ var postPath = yield* createTestFile(postFilename, postFileContent);
+ var actionPath = yield* createTestFile(actionFilename, actionFileContent);
+
+ var postURI = OS.Path.toFileURI(postPath);
+
+ gBrowser.selectedTab = gBrowser.addTab(postURI);
+ let browser = gBrowser.selectedBrowser;
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ yield new Promise(resolve => {
+ let manager = browser.messageManager;
+
+ function listener() {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve();
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+
+ var actionFileContentAfter = yield* readFile(actionPath);
+ is(actionFileContentAfter, actionFileContent, "action file is not modified");
+
+ yield OS.File.remove(postPath);
+ yield OS.File.remove(actionPath);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/dummy.html b/netwerk/test/browser/dummy.html
new file mode 100644
index 000000000..6b28a248f
--- /dev/null
+++ b/netwerk/test/browser/dummy.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/crashtests/1274044-1.html b/netwerk/test/crashtests/1274044-1.html
new file mode 100644
index 000000000..cb88e50bc
--- /dev/null
+++ b/netwerk/test/crashtests/1274044-1.html
@@ -0,0 +1,7 @@
+<script>
+
+var u = new URL("http://127.0.0.1:9607/");
+u.protocol = "resource:";
+u.port = "";
+
+</script>
diff --git a/netwerk/test/crashtests/1334468-1.html b/netwerk/test/crashtests/1334468-1.html
new file mode 100644
index 000000000..3d94d6994
--- /dev/null
+++ b/netwerk/test/crashtests/1334468-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<!--
+user_pref("privacy.firstparty.isolate", true);
+-->
+<script>
+
+let RESTRICTED_CHARS = "\001\002\003\004\005\006\007" +
+ "\010\011\012\013\014\015\016\017" +
+ "\020\021\022\023\024\025\026\027" +
+ "\030\031\032\033\034\035\036\037" +
+ "/:*?\"<>|\\";
+
+function boom() {
+ for (let c of RESTRICTED_CHARS) {
+ window.location = 'http://s.s' + c;
+ }
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/netwerk/test/crashtests/785753-1.html b/netwerk/test/crashtests/785753-1.html
new file mode 100644
index 000000000..7ccb82355
--- /dev/null
+++ b/netwerk/test/crashtests/785753-1.html
@@ -0,0 +1,253 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 0.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<title>Test for importing styles via incorrect link element</title>
+<link type="text/css" href="data:text/css;charset=utf-8,p#one%-32519279132875%7Bbackground-color%32769A%340282366920938463463374607431768211436red%1B%7D%0D%0A"/>
+<link rel="stylesheet" href="data:text/css;charset=utf-16,p#two%1%7Bbackground-color%65535A%4294967297lime%3B%7D%0D%0A"/>
+</head>
+<link href="data:text/css;charset=utf-8,p#three%1%7Bbackground-color%3A%20red%3B%7D%0D%0A"/>
+<link type="text/css" rel="stylesheet" href="data:text/css;charset=utf-8,p#four%32767%7Bbackground-color%2147483649A%20lime%257B%7D%0D%0A"/>
+</head>
+<body>
+<p id="one">This line should not have red background</p>
+<p id="two">This line should have lime background</p>
+<p id="three">This line should not have red background</p>
+<p id="four">This line should have lime background</p>
+</body>
+<script type="text/javascript">
+ function alert(msg){}; function confirm(msg){}; function prompt(msg){};
+ try{ document.head.appendChild(document.createElement("style"));}catch(e){}
+ var styleSheet = document.styleSheets[document.styleSheets.length-1];
+try{if(styleSheet.length===undefined){styleSheet.insertRule(":root{}",0); styleSheet.disabled=false}
+
+styleSheet.insertRule("body {counter-reset:c}",0)}catch(e){}
+var styleSheet0 = document.styleSheets[0];
+var styleSheet1 = document.styleSheets[1];
+var styleSheet2 = document.styleSheets[2];
+var test0=document.getElementById("four")
+var test1=document.getElementById("one")
+var test2=document.getElementById("two")
+var test3=document.getElementById("three")
+setTimeout(function(){
+try{test0.style['padding-top']='32px';}catch(e){}
+try{test2.insertBefore(test3);}catch(e){}
+try{test0.style.setProperty('background-image','url(data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7)','important');}catch(e){}
+try{test3.style['line-height']='-324px';}catch(e){}
+try{test0.style.setProperty('border-image-repeat','repeat','important');}catch(e){}
+},3);
+
+setTimeout(function(){
+try{test1.style['line-height']='43px';}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined{clear:right; }",styleSheet0.cssRules.length);}catch(e){}
+try{test2.style.setProperty('bottom','inherit','important');}catch(e){}
+try{test3.parentNode.removeChild(test3)}catch(e){};
+try{test0.style.setProperty('overflow-x','no-content','important');}catch(e){}
+},0);
+
+setTimeout(function(){
+try{styleSheet0.insertRule(".undefined,.undefined{font-size:40px; overflow-x:no-content; -moz-transition-duration:-5.408991568721831s; -moz-column-span:483; }",styleSheet0.cssRules.length);}catch(e){}
+try{test0.parentNode.removeChild(test0)}catch(e){};
+try{test0.insertBefore(test2);}catch(e){}
+try{test3.style.setProperty('bottom','inherit','important');}catch(e){}
+try{test2.style.setProperty('background-origin','border-box','important');}catch(e){}
+},2);
+
+setTimeout(function(){
+window.resizeTo(1018,353)
+try{test0.insertBefore(test1);}catch(e){}
+try{test1.style.setProperty('bottom','auto','important');}catch(e){}
+try{test1.style.setProperty('z-index','inherit','important');}catch(e){}
+window.resizeTo(1018,353)
+},1);
+
+setTimeout(function(){
+try{test0.innerHtml=test3.innerHtml;}catch(e){}
+document.body.style.setProperty('-webkit-filter','blur(18px)','null')
+try{test3.parentNode.removeChild(test3)}catch(e){};
+try{test0.style.setProperty('padding-right','18px','important');}catch(e){}
+try{test3.style.setProperty('border-bottom-color','rgb(96%,328%,106)','important');}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test2.innerHtml=test0.innerHtml;}catch(e){}
+try{styleSheet1.insertRule(".undefined,.undefined{list-style-type:sidama; background-clip:border-box; overflow-x:scroll; border-bottom-left-radius:70px; text-transform:uppercase; empty-cells:inherit; }",styleSheet1.cssRules.length);}catch(e){}
+try{styleSheet1.insertRule(".undefined:active {min-width:759; }",styleSheet1.cssRules.length);}catch(e){}
+try{test1.style.setProperty('top','343','important');}catch(e){}
+try{test2.replaceChild(test0,test2.firstChild)}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined,.undefined{background-attachment:inherit; flood-color:rgba(93%,364%,104,4.471563883125782); }",0);}catch(e){}
+try{test1.style.setProperty('font-style','oblique','important');}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{border-right-style:double; }",styleSheet0.cssRules.length);}catch(e){}
+document.execCommand("SelectAll", true);
+try{test3.parentNode.removeChild(test3)}catch(e){};
+},1);
+
+setTimeout(function(){
+try{test1.parentNode.removeChild(test1)}catch(e){};
+try{test0.innerHtml=test2.innerHtml;}catch(e){}
+try{test1.parentNode.removeChild(test1)}catch(e){};
+try{test0.parentNode.removeChild(test0)}catch(e){};
+try{test2.innerHtml=test2.innerHtml;}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test0.appendChild(test1);}catch(e){}
+try{test1.style['position']='inherit';}catch(e){}
+try{test2.replaceChild(test3,test2.lastChild)}catch(e){}
+try{test1.style['border-left-color']='#6D8997';}catch(e){}
+try{test1.innerHtml=test3.innerHtml;}catch(e){}
+},6);
+
+setTimeout(function(){
+try{test2.insertBefore(test1);}catch(e){}
+try{test2.innerHtml=test3.innerHtml;}catch(e){}
+try{test0.appendChild(test2);}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined{resize:both; background-color:#A22225; position:relative; -moz-column-width:auto; letter-spacing:361px; border-top-width:151%; }",styleSheet0.cssRules.length);}catch(e){}
+document.body.style.zoom=1.8764980849809945
+},6);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined{outline-color:rgba(126,179,46,0.8964905887842178); width:183; }",styleSheet1.cssRules.length);}catch(e){}
+try{test2.insertBefore(test3);}catch(e){}
+try{test1.innerHtml=test3.innerHtml;}catch(e){}
+try{styleSheet0.insertRule("article,footer,article,article{border-bottom-right-radius:7px; }",0);}catch(e){}
+try{test1.insertBefore(test0);}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test0.parentNode.removeChild(test0)}catch(e){};
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined,.undefined{display: table-header-group; content: counter(c, ethiopic); counter-increment:c;}",0);}catch(e){}
+try{test0.style.setProperty('background-color','#8897D3','important');}catch(e){}
+try{test3.appendChild(test0);}catch(e){}
+try{styleSheet0.insertRule("hgroup,hgroup,hgroup{outline-color:rgba(167,242,90%,-0.10827295063063502); }",styleSheet0.cssRules.length);}catch(e){}
+},4);
+
+setTimeout(function(){
+try{test3.style.setProperty('border-bottom-color','#55D7F6','important');}catch(e){}
+try{test1.parentNode.removeChild(test1)}catch(e){};
+try{test2.insertBefore(test1);}catch(e){}
+try{test2.innerHtml=test0.innerHtml;}catch(e){}
+try{styleSheet1.insertRule("#one,#one,#three,#three{background-clip:border-box; border-top-width:85em; }",0);}catch(e){}
+},5);
+
+setTimeout(function(){
+try{test3.appendChild(test0);}catch(e){}
+try{test2.innerHtml=test1.innerHtml;}catch(e){}
+try{test2.style['background-attachment']='inherit';}catch(e){}
+try{test3.style['clip']='inherit';}catch(e){}
+try{test3.parentNode.removeChild(test3)}catch(e){};
+},3);
+
+setTimeout(function(){
+try{test1.parentNode.removeChild(test1)}catch(e){};
+try{styleSheet0.insertRule(".undefined,.undefined{height:424; }",styleSheet0.cssRules.length);}catch(e){}
+try{test2.style.setProperty('border-top-style','solid','important');}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined,.undefined{page-break-inside:left; border-image-slice:fill; border-left-width:184pc; }",styleSheet0.cssRules.length);}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined{margin-left:-105px; text-transform:inherit; box-sizing:border-box; }",styleSheet0.cssRules.length);}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet0.insertRule("param,img,img,param{left:auto; background-clip:padding-box; }",styleSheet0.cssRules.length);}catch(e){}
+try{test2.style.setProperty('min-height','605','important');}catch(e){}
+try{test2.parentNode.removeChild(test2)}catch(e){};
+try{styleSheet1.insertRule(".undefined::first-line, #two::first-line {border-bottom-width:69pt; lighting-color:rgba(75%,166,81,-0.8728196211159229); text-shadow:85px 459px #2F1; }",styleSheet1.cssRules.length);}catch(e){}
+try{test3.appendChild(document.createTextNode(unescape("!F幓[")))}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet0.insertRule("#two,#four{font-style:italic; list-style-position:inside; border-collapse:inherit; word-wrap:break-word; text-transform:uppercase; }",styleSheet0.cssRules.length);}catch(e){}
+try{test1.style.setProperty('border-bottom-right-radius','2px','important');}catch(e){}
+try{test3.style['text-shadow']='58px 64px rgba(61%,60,199,0.03203143551945686)';}catch(e){}
+try{styleSheet1.insertRule("dir,dir,nav{display: inline-table; content: counter(c, upper-greek); counter-increment:c;}",styleSheet1.cssRules.length);}catch(e){}
+try{styleSheet0.insertRule("#one:target, #three:after {color:#D9B; outline-style:hidden; flood-color:rgba(22,59%,99%,-0.008097740123048425); }",0);}catch(e){}
+},6);
+
+setTimeout(function(){
+try{test2.parentNode.removeChild(test2)}catch(e){};
+document.execCommand("Copy", true);
+try{test3.style['letter-spacing']='102px';}catch(e){}
+try{test2.parentNode.removeChild(test2)}catch(e){};
+try{test3.appendChild(test0);}catch(e){}
+},5);
+
+setTimeout(function(){
+try{test0.style['text-transform']='inherit';}catch(e){}
+try{test0.style['word-break']='hyphenate';}catch(e){}
+try{test3.insertBefore(test2);}catch(e){}
+try{test2.style.setProperty('bottom','410','important');}catch(e){}
+try{test1.style['background']='url(data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7)';}catch(e){}
+},5);
+
+setTimeout(function(){
+try{test2.appendChild(test2);}catch(e){}
+try{test1.appendChild(test2);}catch(e){}
+try{test2.appendChild(document.createTextNode(unescape("zn!쎔gw눢fb¤£kꄍ£3wa02fnpå0!äwC䰴頥!!a")))}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{display: inline; content: counter(c, khmer); counter-increment:c;}",0);}catch(e){}
+try{test3.style.setProperty('line-height','42in','important');}catch(e){}
+},7);
+
+setTimeout(function(){
+window.moveBy(133,126)
+try{test0.style['padding-right']='20px';}catch(e){}
+try{test1.replaceChild(test2,test1.firstChild)}catch(e){}
+try{test1.style.setProperty('letter-spacing','120px','important');}catch(e){}
+try{test1.style.setProperty('height','57','important');}catch(e){}
+},4);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined:nth-child(even), #one:nth-last-child(even) {margin-top:-241cm; font-size:23px; }",0);}catch(e){}
+try{styleSheet0.insertRule(".undefined:nth-child(even), #three:default {stop-color:rgba(92%,-201%,31,0.8133529485203326); lighting-color:#E31; }",styleSheet0.cssRules.length);}catch(e){}
+try{test1.style.setProperty('border-top-left-radius','63px','important');}catch(e){}
+try{test0.style['letter-spacing']='36px';}catch(e){}
+try{test1.appendChild(document.createTextNode(unescape("1u£Fⶵ隗(籬fsä⍉㯗cሮ銐k䆴n#蹹圭篺(1w馁")))}catch(e){}
+},7);
+
+setTimeout(function(){
+try{test1.appendChild(test3);}catch(e){}
+try{test2.style.setProperty('lighting-color','rgb(4,36%,95%)','important');}catch(e){}
+try{test0.style.setProperty('border-right-width','71pt','important');}catch(e){}
+try{test2.style['box-shadow']='-228px , 86px , 9px , #F0A134';}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{left:inherit; }",styleSheet0.cssRules.length);}catch(e){}
+},7);
+
+setTimeout(function(){
+try{styleSheet1.insertRule(".undefined,.undefined,.undefined,.undefined{min-height:277; -moz-transition-property:none; }",0);}catch(e){}
+try{test0.style.setProperty('word-wrap','break-word','important');}catch(e){}
+try{test1.style.setProperty('top','inherit','important');}catch(e){}
+try{styleSheet0.insertRule(".undefined,.undefined,.undefined{overflow-x:visible; border-bottom-left-radius:844488.660965659px; }",0);}catch(e){}
+try{test2.style.setProperty('margin-bottom','auto','important');}catch(e){}
+},0);
+
+setTimeout(function(){
+try{styleSheet1.insertRule("hgroup,hgroup,dl,dl{display: list-item; content: counter(c, hebrew); counter-increment:c;}",styleSheet1.cssRules.length);}catch(e){}
+try{test0.style.setProperty('border-image-repeat','repeat','important');}catch(e){}
+try{styleSheet0.insertRule("figure,footer,figure,table{-moz-border-image-repeat:stretch; word-wrap:normal; border-right-color:rgb(4%,19%,6%); caption-side:top; stop-color:rgba(450%,226,14%,1.5385327017866075); }",styleSheet0.cssRules.length);}catch(e){}
+try{test3.innerHtml=test2.innerHtml;}catch(e){}
+try{test2.appendChild(test0);}catch(e){}
+},0);
+
+setTimeout(function(){
+try{test1.replaceChild(test0,test1.lastChild)}catch(e){}
+try{test2.style.setProperty('border-collapse','inherit','important');}catch(e){}
+try{test1.style['overflow-x']='visible';}catch(e){}
+try{test1.style['text-indent']='-30.283706605434418cm';}catch(e){}
+try{styleSheet0.insertRule("tt,hgroup{stroke-width:-439px; box-sizing:border-box; }",styleSheet0.cssRules.length);}catch(e){}
+},1);
+
+setTimeout(function(){
+styleSheet0.disabled=true
+styleSheet1.disabled=false
+styleSheet1.disabled=true
+document.body.style.setProperty('-webkit-filter','invert(338%)','null')
+window.moveBy(302,115)
+},0);
+
+setTimeout(function(){
+window.blur()
+},4);
+
+</script>
+
+</html>
diff --git a/netwerk/test/crashtests/785753-2.html b/netwerk/test/crashtests/785753-2.html
new file mode 100644
index 000000000..15a986538
--- /dev/null
+++ b/netwerk/test/crashtests/785753-2.html
@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="data:text/css;charset=utf-16,a"/>
+
+<link rel="stylesheet" href="data:text/css;charset=utf-16,p#two%1%7Bbackground-color%65535A%4294967297lime%3B%7D%0D%0A"/> \ No newline at end of file
diff --git a/netwerk/test/crashtests/crashtests.list b/netwerk/test/crashtests/crashtests.list
new file mode 100644
index 000000000..564df22b9
--- /dev/null
+++ b/netwerk/test/crashtests/crashtests.list
@@ -0,0 +1,4 @@
+load 785753-1.html
+load 785753-2.html
+load 1274044-1.html
+skip-if(Android) pref(privacy.firstparty.isolate,true) load 1334468-1.html
diff --git a/netwerk/test/gtest/TestProtocolProxyService.cpp b/netwerk/test/gtest/TestProtocolProxyService.cpp
new file mode 100644
index 000000000..a49e9f961
--- /dev/null
+++ b/netwerk/test/gtest/TestProtocolProxyService.cpp
@@ -0,0 +1,128 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "../../base/nsProtocolProxyService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+namespace net {
+
+TEST(TestProtocolProxyService, LoadHostFilters) {
+ nsCOMPtr<nsIProtocolProxyService2> ps = do_GetService(NS_PROTOCOLPROXYSERVICE_CID);
+ ASSERT_TRUE(ps);
+ mozilla::net::nsProtocolProxyService* pps = static_cast<mozilla::net::nsProtocolProxyService*>(ps.get());
+
+ nsCOMPtr<nsIURL> url( do_CreateInstance(NS_STANDARDURL_CONTRACTID) );
+ ASSERT_TRUE(url) << "couldn't create URL";
+
+ nsAutoCString spec;
+
+ auto CheckLoopbackURLs = [&](bool expected)
+ {
+ // loopback IPs are always filtered
+ spec = "http://127.0.0.1";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ spec = "http://[::1]";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckURLs = [&](bool expected)
+ {
+ spec = "http://example.com";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "https://10.2.3.4";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 443), expected);
+
+ spec = "http://1.2.3.4";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://1.2.3.4:8080";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://[2001::1]";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://2.3.4.5:7777";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://[abcd::2]:123";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://bla.test.com";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckPortDomain = [&](bool expected)
+ {
+ spec = "http://blabla.com:10";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckLocalDomain = [&](bool expected)
+ {
+ spec = "http://test";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ // --------------------------------------------------------------------------
+
+ nsAutoCString filter;
+
+ // Anything is allowed when there are no filters set
+ printf("Testing empty filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+
+ CheckLoopbackURLs(true); // only time when loopbacks can be proxied. bug?
+ CheckLocalDomain(true);
+ CheckURLs(true);
+ CheckPortDomain(true);
+
+ // --------------------------------------------------------------------------
+
+ filter = "example.com, 1.2.3.4/16, [2001::1], 10.0.0.0/8, 2.3.0.0/16:7777, [abcd::1]/64:123, *.test.com";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+ // Check URLs can no longer use filtered proxy
+ CheckURLs(false);
+ CheckLoopbackURLs(false);
+ CheckLocalDomain(true);
+ CheckPortDomain(true);
+
+ // --------------------------------------------------------------------------
+
+ // This is space separated. See bug 1346711 comment 4. We check this to keep
+ // backwards compatibility.
+ filter = "<local> blabla.com:10";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+ CheckURLs(true);
+ CheckLoopbackURLs(false);
+ CheckLocalDomain(false);
+ CheckPortDomain(false);
+
+ // Check that we don't crash on weird input
+ filter = "a b c abc:1x2, ,, * ** *.* *:10 :20 :40/12 */12:90";
+ printf("Testing filter: %s\n", filter.get());
+ pps->LoadHostFilters(filter);
+}
+
+} // namespace net
+} // namespace mozila
diff --git a/netwerk/test/gtest/TestStandardURL.cpp b/netwerk/test/gtest/TestStandardURL.cpp
new file mode 100644
index 000000000..ccab556a9
--- /dev/null
+++ b/netwerk/test/gtest/TestStandardURL.cpp
@@ -0,0 +1,69 @@
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+
+TEST(TestStandardURL, Simple) {
+ nsCOMPtr<nsIURL> url( do_CreateInstance(NS_STANDARDURL_CONTRACTID) );
+ ASSERT_TRUE(url);
+ ASSERT_EQ(url->SetSpec(NS_LITERAL_CSTRING("http://example.com")), NS_OK);
+
+ nsAutoCString out;
+
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("http://example.com/"));
+
+ ASSERT_EQ(url->Resolve(NS_LITERAL_CSTRING("foo.html?q=45"), out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("http://example.com/foo.html?q=45"));
+
+ ASSERT_EQ(url->SetScheme(NS_LITERAL_CSTRING("foo")), NS_OK);
+
+ ASSERT_EQ(url->GetScheme(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("foo"));
+
+ ASSERT_EQ(url->GetHost(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("example.com"));
+ ASSERT_EQ(url->SetHost(NS_LITERAL_CSTRING("www.yahoo.com")), NS_OK);
+ ASSERT_EQ(url->GetHost(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("www.yahoo.com"));
+
+ ASSERT_EQ(url->SetPath(NS_LITERAL_CSTRING("/some-path/one-the-net/about.html?with-a-query#for-you")), NS_OK);
+ ASSERT_EQ(url->GetPath(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("/some-path/one-the-net/about.html?with-a-query#for-you"));
+
+ ASSERT_EQ(url->SetQuery(NS_LITERAL_CSTRING("a=b&d=c&what-ever-you-want-to-be-called=45")), NS_OK);
+ ASSERT_EQ(url->GetQuery(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("a=b&d=c&what-ever-you-want-to-be-called=45"));
+
+ ASSERT_EQ(url->SetRef(NS_LITERAL_CSTRING("#some-book-mark")), NS_OK);
+ ASSERT_EQ(url->GetRef(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("some-book-mark"));
+}
+
+#define COUNT 10000
+
+MOZ_GTEST_BENCH(TestStandardURL, Perf, [] {
+ nsCOMPtr<nsIURL> url( do_CreateInstance(NS_STANDARDURL_CONTRACTID) );
+ ASSERT_TRUE(url);
+ nsAutoCString out;
+
+ for (int i = COUNT; i; --i) {
+ ASSERT_EQ(url->SetSpec(NS_LITERAL_CSTRING("http://example.com")), NS_OK);
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ url->Resolve(NS_LITERAL_CSTRING("foo.html?q=45"), out);
+ url->SetScheme(NS_LITERAL_CSTRING("foo"));
+ url->GetScheme(out);
+ url->SetHost(NS_LITERAL_CSTRING("www.yahoo.com"));
+ url->GetHost(out);
+ url->SetPath(NS_LITERAL_CSTRING("/some-path/one-the-net/about.html?with-a-query#for-you"));
+ url->GetPath(out);
+ url->SetQuery(NS_LITERAL_CSTRING("a=b&d=c&what-ever-you-want-to-be-called=45"));
+ url->GetQuery(out);
+ url->SetRef(NS_LITERAL_CSTRING("#some-book-mark"));
+ url->GetRef(out);
+ }
+});
diff --git a/netwerk/test/gtest/moz.build b/netwerk/test/gtest/moz.build
new file mode 100644
index 000000000..6e6c80152
--- /dev/null
+++ b/netwerk/test/gtest/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ 'TestProtocolProxyService.cpp',
+ 'TestStandardURL.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/netwerk/test/httpserver/README b/netwerk/test/httpserver/README
new file mode 100644
index 000000000..e253c6d47
--- /dev/null
+++ b/netwerk/test/httpserver/README
@@ -0,0 +1,101 @@
+httpd.js README
+===============
+
+httpd.js is a small cross-platform implementation of an HTTP/1.1 server in
+JavaScript for the Mozilla platform.
+
+httpd.js may be used as an XPCOM component, as an inline script in a document
+with XPCOM privileges, or from the XPCOM shell (xpcshell). Currently, its most-
+supported method of use is from the XPCOM shell, where you can get all the
+dynamicity of JS in adding request handlers and the like, but component-based
+equivalent functionality is planned.
+
+
+Using httpd.js as an XPCOM Component
+------------------------------------
+
+First, create an XPT file for nsIHttpServer.idl, using the xpidl tool included
+in the Mozilla SDK for the environment in which you wish to run httpd.js. See
+<http://developer.mozilla.org/en/docs/XPIDL:xpidl> for further details on how to
+do this.
+
+Next, register httpd.js and nsIHttpServer.xpt in your Mozilla application. In
+Firefox, these simply need to be added to the /components directory of your XPI.
+Other applications may require use of regxpcom or other techniques; consult the
+applicable documentation for further details.
+
+Finally, create an instance of the server using the following command:
+
+ var server = Components.classes["@mozilla.org/server/jshttp;1"]
+ .createInstance(Components.interfaces.nsIHttpServer);
+
+At this point you'll want to initialize the server, since by default it doesn't
+serve many useful paths. For more information on this, see the IDL docs for the
+nsIHttpServer interface in nsIHttpServer.idl, particularly for
+registerDirectory (useful for mapping the contents of directories onto request
+paths), registerPathHandler (for setting a custom handler for a specific path on
+the server, such as CGI functionality), and registerFile (for mapping a file to
+a specific path).
+
+Finally, you'll want to start (and later stop) the server. Here's some example
+code which does this:
+
+ server.start(8080); // port on which server will operate
+
+ // ...server now runs and serves requests...
+
+ server.stop();
+
+This server will only respond to requests on 127.0.0.1:8080 or localhost:8080.
+If you want it to respond to requests at different hosts (say via a proxy
+mechanism), you must use server.identity.add() or server.identity.setPrimary()
+to add it.
+
+
+Using httpd.js as an Inline Script or from xpcshell
+---------------------------------------------------
+
+Using httpd.js as a script or from xpcshell isn't very different from using it
+as a component; the only real difference lies in how you create an instance of
+the server. To create an instance, do the following:
+
+ var server = new nsHttpServer();
+
+You now can use |server| exactly as you would when |server| was created as an
+XPCOM component. Note, however, that doing so will trample over the global
+namespace, and global values defined in httpd.js will leak into your script.
+This may typically be benign, but since some of the global values defined are
+constants (specifically, Cc/Ci/Cr as abbreviations for the classes, interfaces,
+and results properties of Components), it's possible this trampling could
+break your script. In general you should use httpd.js as an XPCOM component
+whenever possible.
+
+
+Known Issues
+------------
+
+httpd.js makes no effort to time out requests, beyond any the socket itself
+might or might not provide. I don't believe it provides any by default, but
+I haven't verified this.
+
+Every incoming request is processed by the corresponding request handler
+synchronously. In other words, once the first CRLFCRLF of a request is
+received, the entire response is created before any new incoming requests can be
+served. I anticipate adding asynchronous handler functionality in bug 396226,
+but it may be some time before that happens.
+
+There is no way to access the body of an incoming request. This problem is
+merely a symptom of the previous one, and they will probably both be addressed
+at the same time.
+
+
+Other Goodies
+-------------
+
+A special testing function, |server|, is provided for use in xpcshell for quick
+testing of the server; see the source code for details on its use. You don't
+want to use this in a script, however, because doing so will block until the
+server is shut down. It's also a good example of how to use the basic
+functionality of httpd.js, if you need one.
+
+Have fun!
diff --git a/netwerk/test/httpserver/TODO b/netwerk/test/httpserver/TODO
new file mode 100644
index 000000000..3a9546611
--- /dev/null
+++ b/netwerk/test/httpserver/TODO
@@ -0,0 +1,17 @@
+Bugs to fix:
+- make content-length generation not rely on .available() returning the entire
+ size of the body stream's contents -- some sort of wrapper (but how does that
+ work for the unscriptable method WriteSegments, which is good to support from
+ a performance standpoint?)
+
+Ideas for future improvements:
+- add API to disable response buffering which, when called, causes errors when
+ you try to do anything other than write to the body stream (i.e., modify
+ headers or status line) once you've written anything to it -- useful when
+ storing the entire response in memory is unfeasible (e.g., you're testing
+ >4GB download characteristics)
+- add an API which performs asynchronous response processing (instead of
+ nsIHttpRequestHandler.handle, which must construct the response before control
+ returns; |void asyncHandle(request, response)|) -- useful, and can it be done
+ in JS?
+- other awesomeness?
diff --git a/netwerk/test/httpserver/httpd.js b/netwerk/test/httpserver/httpd.js
new file mode 100644
index 000000000..5542adfc2
--- /dev/null
+++ b/netwerk/test/httpserver/httpd.js
@@ -0,0 +1,5376 @@
+/* -*- 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/. */
+
+/*
+ * An implementation of an HTTP server both as a loadable script and as an XPCOM
+ * component. See the accompanying README file for user documentation on
+ * httpd.js.
+ */
+
+this.EXPORTED_SYMBOLS = [
+ "HTTP_400",
+ "HTTP_401",
+ "HTTP_402",
+ "HTTP_403",
+ "HTTP_404",
+ "HTTP_405",
+ "HTTP_406",
+ "HTTP_407",
+ "HTTP_408",
+ "HTTP_409",
+ "HTTP_410",
+ "HTTP_411",
+ "HTTP_412",
+ "HTTP_413",
+ "HTTP_414",
+ "HTTP_415",
+ "HTTP_417",
+ "HTTP_500",
+ "HTTP_501",
+ "HTTP_502",
+ "HTTP_503",
+ "HTTP_504",
+ "HTTP_505",
+ "HttpError",
+ "HttpServer",
+];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+const PR_UINT32_MAX = Math.pow(2, 32) - 1;
+
+/** True if debugging output is enabled, false otherwise. */
+var DEBUG = false; // non-const *only* so tweakable in server tests
+
+/** True if debugging output should be timestamped. */
+var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
+
+var gGlobalObject = this;
+
+/**
+ * Asserts that the given condition holds. If it doesn't, the given message is
+ * dumped, a stack trace is printed, and an exception is thrown to attempt to
+ * stop execution (which unfortunately must rely upon the exception not being
+ * accidentally swallowed by the code that uses it).
+ */
+function NS_ASSERT(cond, msg)
+{
+ if (DEBUG && !cond)
+ {
+ dumpn("###!!!");
+ dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
+ dumpn("###!!! Stack follows:");
+
+ var stack = new Error().stack.split(/\n/);
+ dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n"));
+
+ throw Cr.NS_ERROR_ABORT;
+ }
+}
+
+/** Constructs an HTTP error object. */
+this.HttpError = function HttpError(code, description)
+{
+ this.code = code;
+ this.description = description;
+}
+HttpError.prototype =
+{
+ toString: function()
+ {
+ return this.code + " " + this.description;
+ }
+};
+
+/**
+ * Errors thrown to trigger specific HTTP server responses.
+ */
+this.HTTP_400 = new HttpError(400, "Bad Request");
+this.HTTP_401 = new HttpError(401, "Unauthorized");
+this.HTTP_402 = new HttpError(402, "Payment Required");
+this.HTTP_403 = new HttpError(403, "Forbidden");
+this.HTTP_404 = new HttpError(404, "Not Found");
+this.HTTP_405 = new HttpError(405, "Method Not Allowed");
+this.HTTP_406 = new HttpError(406, "Not Acceptable");
+this.HTTP_407 = new HttpError(407, "Proxy Authentication Required");
+this.HTTP_408 = new HttpError(408, "Request Timeout");
+this.HTTP_409 = new HttpError(409, "Conflict");
+this.HTTP_410 = new HttpError(410, "Gone");
+this.HTTP_411 = new HttpError(411, "Length Required");
+this.HTTP_412 = new HttpError(412, "Precondition Failed");
+this.HTTP_413 = new HttpError(413, "Request Entity Too Large");
+this.HTTP_414 = new HttpError(414, "Request-URI Too Long");
+this.HTTP_415 = new HttpError(415, "Unsupported Media Type");
+this.HTTP_417 = new HttpError(417, "Expectation Failed");
+
+this.HTTP_500 = new HttpError(500, "Internal Server Error");
+this.HTTP_501 = new HttpError(501, "Not Implemented");
+this.HTTP_502 = new HttpError(502, "Bad Gateway");
+this.HTTP_503 = new HttpError(503, "Service Unavailable");
+this.HTTP_504 = new HttpError(504, "Gateway Timeout");
+this.HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
+
+/** Creates a hash with fields corresponding to the values in arr. */
+function array2obj(arr)
+{
+ var obj = {};
+ for (var i = 0; i < arr.length; i++)
+ obj[arr[i]] = arr[i];
+ return obj;
+}
+
+/** Returns an array of the integers x through y, inclusive. */
+function range(x, y)
+{
+ var arr = [];
+ for (var i = x; i <= y; i++)
+ arr.push(i);
+ return arr;
+}
+
+/** An object (hash) whose fields are the numbers of all HTTP error codes. */
+const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));
+
+
+/**
+ * The character used to distinguish hidden files from non-hidden files, a la
+ * the leading dot in Apache. Since that mechanism also hides files from
+ * easy display in LXR, ls output, etc. however, we choose instead to use a
+ * suffix character. If a requested file ends with it, we append another
+ * when getting the file on the server. If it doesn't, we just look up that
+ * file. Therefore, any file whose name ends with exactly one of the character
+ * is "hidden" and available for use by the server.
+ */
+const HIDDEN_CHAR = "^";
+
+/**
+ * The file name suffix indicating the file containing overridden headers for
+ * a requested file.
+ */
+const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;
+
+/** Type used to denote SJS scripts for CGI-like functionality. */
+const SJS_TYPE = "sjs";
+
+/** Base for relative timestamps produced by dumpn(). */
+var firstStamp = 0;
+
+/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */
+function dumpn(str)
+{
+ if (DEBUG)
+ {
+ var prefix = "HTTPD-INFO | ";
+ if (DEBUG_TIMESTAMP)
+ {
+ if (firstStamp === 0)
+ firstStamp = Date.now();
+
+ var elapsed = Date.now() - firstStamp; // milliseconds
+ var min = Math.floor(elapsed / 60000);
+ var sec = (elapsed % 60000) / 1000;
+
+ if (sec < 10)
+ prefix += min + ":0" + sec.toFixed(3) + " | ";
+ else
+ prefix += min + ":" + sec.toFixed(3) + " | ";
+ }
+
+ dump(prefix + str + "\n");
+ }
+}
+
+/** Dumps the current JS stack if DEBUG. */
+function dumpStack()
+{
+ // peel off the frames for dumpStack() and Error()
+ var stack = new Error().stack.split(/\n/).slice(2);
+ stack.forEach(dumpn);
+}
+
+
+/** The XPCOM thread manager. */
+var gThreadManager = null;
+
+/** The XPCOM prefs service. */
+var gRootPrefBranch = null;
+function getRootPrefBranch()
+{
+ if (!gRootPrefBranch)
+ {
+ gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ }
+ return gRootPrefBranch;
+}
+
+/**
+ * JavaScript constructors for commonly-used classes; precreating these is a
+ * speedup over doing the same from base principles. See the docs at
+ * http://developer.mozilla.org/en/docs/Components.Constructor for details.
+ */
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init");
+const Pipe = CC("@mozilla.org/pipe;1",
+ "nsIPipe",
+ "init");
+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
+ "nsIFileInputStream",
+ "init");
+const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",
+ "nsIConverterInputStream",
+ "init");
+const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1",
+ "nsIWritablePropertyBag2");
+const SupportsString = CC("@mozilla.org/supports-string;1",
+ "nsISupportsString");
+
+/* These two are non-const only so a test can overwrite them. */
+var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+
+/**
+ * Returns the RFC 822/1123 representation of a date.
+ *
+ * @param date : Number
+ * the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT
+ * @returns string
+ * the representation of the given date
+ */
+function toDateString(date)
+{
+ //
+ // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
+ // date1 = 2DIGIT SP month SP 4DIGIT
+ // ; day month year (e.g., 02 Jun 1982)
+ // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+ // ; 00:00:00 - 23:59:59
+ // wkday = "Mon" | "Tue" | "Wed"
+ // | "Thu" | "Fri" | "Sat" | "Sun"
+ // month = "Jan" | "Feb" | "Mar" | "Apr"
+ // | "May" | "Jun" | "Jul" | "Aug"
+ // | "Sep" | "Oct" | "Nov" | "Dec"
+ //
+
+ const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+
+ /**
+ * Processes a date and returns the encoded UTC time as a string according to
+ * the format specified in RFC 2616.
+ *
+ * @param date : Date
+ * the date to process
+ * @returns string
+ * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+ */
+ function toTime(date)
+ {
+ var hrs = date.getUTCHours();
+ var rv = (hrs < 10) ? "0" + hrs : hrs;
+
+ var mins = date.getUTCMinutes();
+ rv += ":";
+ rv += (mins < 10) ? "0" + mins : mins;
+
+ var secs = date.getUTCSeconds();
+ rv += ":";
+ rv += (secs < 10) ? "0" + secs : secs;
+
+ return rv;
+ }
+
+ /**
+ * Processes a date and returns the encoded UTC date as a string according to
+ * the date1 format specified in RFC 2616.
+ *
+ * @param date : Date
+ * the date to process
+ * @returns string
+ * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+ */
+ function toDate1(date)
+ {
+ var day = date.getUTCDate();
+ var month = date.getUTCMonth();
+ var year = date.getUTCFullYear();
+
+ var rv = (day < 10) ? "0" + day : day;
+ rv += " " + monthStrings[month];
+ rv += " " + year;
+
+ return rv;
+ }
+
+ date = new Date(date);
+
+ const fmtString = "%wkday%, %date1% %time% GMT";
+ var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);
+ rv = rv.replace("%time%", toTime(date));
+ return rv.replace("%date1%", toDate1(date));
+}
+
+/**
+ * Prints out a human-readable representation of the object o and its fields,
+ * omitting those whose names begin with "_" if showMembers != true (to ignore
+ * "private" properties exposed via getters/setters).
+ */
+function printObj(o, showMembers)
+{
+ var s = "******************************\n";
+ s += "o = {\n";
+ for (var i in o)
+ {
+ if (typeof(i) != "string" ||
+ (showMembers || (i.length > 0 && i[0] != "_")))
+ s+= " " + i + ": " + o[i] + ",\n";
+ }
+ s += " };\n";
+ s += "******************************";
+ dumpn(s);
+}
+
+/**
+ * Instantiates a new HTTP server.
+ */
+function nsHttpServer()
+{
+ if (!gThreadManager)
+ gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ /** The port on which this server listens. */
+ this._port = undefined;
+
+ /** The socket associated with this. */
+ this._socket = null;
+
+ /** The handler used to process requests to this server. */
+ this._handler = new ServerHandler(this);
+
+ /** Naming information for this server. */
+ this._identity = new ServerIdentity();
+
+ /**
+ * Indicates when the server is to be shut down at the end of the request.
+ */
+ this._doQuit = false;
+
+ /**
+ * True if the socket in this is closed (and closure notifications have been
+ * sent and processed if the socket was ever opened), false otherwise.
+ */
+ this._socketClosed = true;
+
+ /**
+ * Used for tracking existing connections and ensuring that all connections
+ * are properly cleaned up before server shutdown; increases by 1 for every
+ * new incoming connection.
+ */
+ this._connectionGen = 0;
+
+ /**
+ * Hash of all open connections, indexed by connection number at time of
+ * creation.
+ */
+ this._connections = {};
+}
+nsHttpServer.prototype =
+{
+ classID: Components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"),
+
+ // NSISERVERSOCKETLISTENER
+
+ /**
+ * Processes an incoming request coming in on the given socket and contained
+ * in the given transport.
+ *
+ * @param socket : nsIServerSocket
+ * the socket through which the request was served
+ * @param trans : nsISocketTransport
+ * the transport for the request/response
+ * @see nsIServerSocketListener.onSocketAccepted
+ */
+ onSocketAccepted: function(socket, trans)
+ {
+ dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")");
+
+ dumpn(">>> new connection on " + trans.host + ":" + trans.port);
+
+ const SEGMENT_SIZE = 8192;
+ const SEGMENT_COUNT = 1024;
+ try
+ {
+ var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ var output = trans.openOutputStream(0, 0, 0);
+ }
+ catch (e)
+ {
+ dumpn("*** error opening transport streams: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ var connectionNumber = ++this._connectionGen;
+
+ try
+ {
+ var conn = new Connection(input, output, this, socket.port, trans.port,
+ connectionNumber);
+ var reader = new RequestReader(conn);
+
+ // XXX add request timeout functionality here!
+
+ // Note: must use main thread here, or we might get a GC that will cause
+ // threadsafety assertions. We really need to fix XPConnect so that
+ // you can actually do things in multi-threaded JS. :-(
+ input.asyncWait(reader, 0, 0, gThreadManager.mainThread);
+ }
+ catch (e)
+ {
+ // Assume this connection can't be salvaged and bail on it completely;
+ // don't attempt to close it so that we can assert that any connection
+ // being closed is in this._connections.
+ dumpn("*** error in initial request-processing stages: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ this._connections[connectionNumber] = conn;
+ dumpn("*** starting connection " + connectionNumber);
+ },
+
+ /**
+ * Called when the socket associated with this is closed.
+ *
+ * @param socket : nsIServerSocket
+ * the socket being closed
+ * @param status : nsresult
+ * the reason the socket stopped listening (NS_BINDING_ABORTED if the server
+ * was stopped using nsIHttpServer.stop)
+ * @see nsIServerSocketListener.onStopListening
+ */
+ onStopListening: function(socket, status)
+ {
+ dumpn(">>> shutting down server on port " + socket.port);
+ for (var n in this._connections) {
+ if (!this._connections[n]._requestStarted) {
+ this._connections[n].close();
+ }
+ }
+ this._socketClosed = true;
+ if (this._hasOpenConnections()) {
+ dumpn("*** open connections!!!");
+ }
+ if (!this._hasOpenConnections())
+ {
+ dumpn("*** no open connections, notifying async from onStopListening");
+
+ // Notify asynchronously so that any pending teardown in stop() has a
+ // chance to run first.
+ var self = this;
+ var stopEvent =
+ {
+ run: function()
+ {
+ dumpn("*** _notifyStopped async callback");
+ self._notifyStopped();
+ }
+ };
+ gThreadManager.currentThread
+ .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ // NSIHTTPSERVER
+
+ //
+ // see nsIHttpServer.start
+ //
+ start: function(port)
+ {
+ this._start(port, "localhost")
+ },
+
+ _start: function(port, host)
+ {
+ if (this._socket)
+ throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+
+ this._port = port;
+ this._doQuit = this._socketClosed = false;
+
+ this._host = host;
+
+ // The listen queue needs to be long enough to handle
+ // network.http.max-persistent-connections-per-server or
+ // network.http.max-persistent-connections-per-proxy concurrent
+ // connections, plus a safety margin in case some other process is
+ // talking to the server as well.
+ var prefs = getRootPrefBranch();
+ var maxConnections = 5 + Math.max(
+ prefs.getIntPref("network.http.max-persistent-connections-per-server"),
+ prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
+
+ try
+ {
+ var loopback = true;
+ if (this._host != "127.0.0.1" && this._host != "localhost") {
+ var loopback = false;
+ }
+
+ // When automatically selecting a port, sometimes the chosen port is
+ // "blocked" from clients. We don't want to use these ports because
+ // tests will intermittently fail. So, we simply keep trying to to
+ // get a server socket until a valid port is obtained. We limit
+ // ourselves to finite attempts just so we don't loop forever.
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ var socket;
+ for (var i = 100; i; i--)
+ {
+ var temp = new ServerSocket(this._port,
+ loopback, // true = localhost, false = everybody
+ maxConnections);
+
+ var allowed = ios.allowPort(temp.port, "http");
+ if (!allowed)
+ {
+ dumpn(">>>Warning: obtained ServerSocket listens on a blocked " +
+ "port: " + temp.port);
+ }
+
+ if (!allowed && this._port == -1)
+ {
+ dumpn(">>>Throwing away ServerSocket with bad port.");
+ temp.close();
+ continue;
+ }
+
+ socket = temp;
+ break;
+ }
+
+ if (!socket) {
+ throw new Error("No socket server available. Are there no available ports?");
+ }
+
+ dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
+ " pending connections");
+ socket.asyncListen(this);
+ this._port = socket.port;
+ this._identity._initialize(socket.port, host, true);
+ this._socket = socket;
+ }
+ catch (e)
+ {
+ dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ }
+ },
+
+ //
+ // see nsIHttpServer.stop
+ //
+ stop: function(callback)
+ {
+ if (!callback)
+ throw Cr.NS_ERROR_NULL_POINTER;
+ if (!this._socket)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ this._stopCallback = typeof callback === "function"
+ ? callback
+ : function() { callback.onStopped(); };
+
+ dumpn(">>> stopping listening on port " + this._socket.port);
+ this._socket.close();
+ this._socket = null;
+
+ // We can't have this identity any more, and the port on which we're running
+ // this server now could be meaningless the next time around.
+ this._identity._teardown();
+
+ this._doQuit = false;
+
+ // socket-close notification and pending request completion happen async
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile: function(path, file)
+ {
+ if (file && (!file.exists() || file.isDirectory()))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handler.registerFile(path, file);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(path, directory)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" ||
+ path.charAt(path.length - 1) != "/" ||
+ (directory &&
+ (!directory.exists() || !directory.isDirectory())))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
+ // exists!
+
+ this._handler.registerDirectory(path, directory);
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler: function(path, handler)
+ {
+ this._handler.registerPathHandler(path, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler: function(prefix, handler)
+ {
+ this._handler.registerPrefixHandler(prefix, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler: function(code, handler)
+ {
+ this._handler.registerErrorHandler(code, handler);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler: function(handler)
+ {
+ this._handler.setIndexHandler(handler);
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(ext, type)
+ {
+ this._handler.registerContentType(ext, type);
+ },
+
+ //
+ // see nsIHttpServer.serverIdentity
+ //
+ get identity()
+ {
+ return this._identity;
+ },
+
+ //
+ // see nsIHttpServer.getState
+ //
+ getState: function(path, k)
+ {
+ return this._handler._getState(path, k);
+ },
+
+ //
+ // see nsIHttpServer.setState
+ //
+ setState: function(path, k, v)
+ {
+ return this._handler._setState(path, k, v);
+ },
+
+ //
+ // see nsIHttpServer.getSharedState
+ //
+ getSharedState: function(k)
+ {
+ return this._handler._getSharedState(k);
+ },
+
+ //
+ // see nsIHttpServer.setSharedState
+ //
+ setSharedState: function(k, v)
+ {
+ return this._handler._setSharedState(k, v);
+ },
+
+ //
+ // see nsIHttpServer.getObjectState
+ //
+ getObjectState: function(k)
+ {
+ return this._handler._getObjectState(k);
+ },
+
+ //
+ // see nsIHttpServer.setObjectState
+ //
+ setObjectState: function(k, v)
+ {
+ return this._handler._setObjectState(k, v);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpServer) ||
+ iid.equals(Ci.nsIServerSocketListener) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // NON-XPCOM PUBLIC API
+
+ /**
+ * Returns true iff this server is not running (and is not in the process of
+ * serving any requests still to be processed when the server was last
+ * stopped after being run).
+ */
+ isStopped: function()
+ {
+ return this._socketClosed && !this._hasOpenConnections();
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /** True if this server has any open connections to it, false otherwise. */
+ _hasOpenConnections: function()
+ {
+ //
+ // If we have any open connections, they're tracked as numeric properties on
+ // |this._connections|. The non-standard __count__ property could be used
+ // to check whether there are any properties, but standard-wise, even
+ // looking forward to ES5, there's no less ugly yet still O(1) way to do
+ // this.
+ //
+ for (var n in this._connections)
+ return true;
+ return false;
+ },
+
+ /** Calls the server-stopped callback provided when stop() was called. */
+ _notifyStopped: function()
+ {
+ NS_ASSERT(this._stopCallback !== null, "double-notifying?");
+ NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now");
+
+ //
+ // NB: We have to grab this now, null out the member, *then* call the
+ // callback here, or otherwise the callback could (indirectly) futz with
+ // this._stopCallback by starting and immediately stopping this, at
+ // which point we'd be nulling out a field we no longer have a right to
+ // modify.
+ //
+ var callback = this._stopCallback;
+ this._stopCallback = null;
+ try
+ {
+ callback();
+ }
+ catch (e)
+ {
+ // not throwing because this is specified as being usually (but not
+ // always) asynchronous
+ dump("!!! error running onStopped callback: " + e + "\n");
+ }
+ },
+
+ /**
+ * Notifies this server that the given connection has been closed.
+ *
+ * @param connection : Connection
+ * the connection that was closed
+ */
+ _connectionClosed: function(connection)
+ {
+ NS_ASSERT(connection.number in this._connections,
+ "closing a connection " + this + " that we never added to the " +
+ "set of open connections?");
+ NS_ASSERT(this._connections[connection.number] === connection,
+ "connection number mismatch? " +
+ this._connections[connection.number]);
+ delete this._connections[connection.number];
+
+ // Fire a pending server-stopped notification if it's our responsibility.
+ if (!this._hasOpenConnections() && this._socketClosed)
+ this._notifyStopped();
+ // Bug 508125: Add a GC here else we'll use gigabytes of memory running
+ // mochitests. We can't rely on xpcshell doing an automated GC, as that
+ // would interfere with testing GC stuff...
+ Components.utils.forceGC();
+ },
+
+ /**
+ * Requests that the server be shut down when possible.
+ */
+ _requestQuit: function()
+ {
+ dumpn(">>> requesting a quit");
+ dumpStack();
+ this._doQuit = true;
+ }
+};
+
+this.HttpServer = nsHttpServer;
+
+//
+// RFC 2396 section 3.2.2:
+//
+// host = hostname | IPv4address
+// hostname = *( domainlabel "." ) toplabel [ "." ]
+// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+//
+
+const HOST_REGEX =
+ new RegExp("^(?:" +
+ // *( domainlabel "." )
+ "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
+ // toplabel
+ "[a-z](?:[a-z0-9-]*[a-z0-9])?" +
+ "|" +
+ // IPv4 address
+ "\\d+\\.\\d+\\.\\d+\\.\\d+" +
+ ")$",
+ "i");
+
+
+/**
+ * Represents the identity of a server. An identity consists of a set of
+ * (scheme, host, port) tuples denoted as locations (allowing a single server to
+ * serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
+ * host/port). Any incoming request must be to one of these locations, or it
+ * will be rejected with an HTTP 400 error. One location, denoted as the
+ * primary location, is the location assigned in contexts where a location
+ * cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
+ *
+ * A single identity may contain at most one location per unique host/port pair;
+ * other than that, no restrictions are placed upon what locations may
+ * constitute an identity.
+ */
+function ServerIdentity()
+{
+ /** The scheme of the primary location. */
+ this._primaryScheme = "http";
+
+ /** The hostname of the primary location. */
+ this._primaryHost = "127.0.0.1"
+
+ /** The port number of the primary location. */
+ this._primaryPort = -1;
+
+ /**
+ * The current port number for the corresponding server, stored so that a new
+ * primary location can always be set if the current one is removed.
+ */
+ this._defaultPort = -1;
+
+ /**
+ * Maps hosts to maps of ports to schemes, e.g. the following would represent
+ * https://example.com:789/ and http://example.org/:
+ *
+ * {
+ * "xexample.com": { 789: "https" },
+ * "xexample.org": { 80: "http" }
+ * }
+ *
+ * Note the "x" prefix on hostnames, which prevents collisions with special
+ * JS names like "prototype".
+ */
+ this._locations = { "xlocalhost": {} };
+}
+ServerIdentity.prototype =
+{
+ // NSIHTTPSERVERIDENTITY
+
+ //
+ // see nsIHttpServerIdentity.primaryScheme
+ //
+ get primaryScheme()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryScheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryHost
+ //
+ get primaryHost()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryHost;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryPort
+ //
+ get primaryPort()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryPort;
+ },
+
+ //
+ // see nsIHttpServerIdentity.add
+ //
+ add: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ this._locations["x" + host] = entry = {};
+
+ entry[port] = scheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.remove
+ //
+ remove: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ return false;
+
+ var present = port in entry;
+ delete entry[port];
+
+ if (this._primaryScheme == scheme &&
+ this._primaryHost == host &&
+ this._primaryPort == port &&
+ this._defaultPort !== -1)
+ {
+ // Always keep at least one identity in existence at any time, unless
+ // we're in the process of shutting down (the last condition above).
+ this._primaryPort = -1;
+ this._initialize(this._defaultPort, host, false);
+ }
+
+ return present;
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ has: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ return "x" + host in this._locations &&
+ scheme === this._locations["x" + host][port];
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ getScheme: function(host, port)
+ {
+ this._validate("http", host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ return "";
+
+ return entry[port] || "";
+ },
+
+ //
+ // see nsIHttpServerIdentity.setPrimary
+ //
+ setPrimary: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ this.add(scheme, host, port);
+
+ this._primaryScheme = scheme;
+ this._primaryHost = host;
+ this._primaryPort = port;
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+ * Initializes the primary name for the corresponding server, based on the
+ * provided port number.
+ */
+ _initialize: function(port, host, addSecondaryDefault)
+ {
+ this._host = host;
+ if (this._primaryPort !== -1)
+ this.add("http", host, port);
+ else
+ this.setPrimary("http", "localhost", port);
+ this._defaultPort = port;
+
+ // Only add this if we're being called at server startup
+ if (addSecondaryDefault && host != "127.0.0.1")
+ this.add("http", "127.0.0.1", port);
+ },
+
+ /**
+ * Called at server shutdown time, unsets the primary location only if it was
+ * the default-assigned location and removes the default location from the
+ * set of locations used.
+ */
+ _teardown: function()
+ {
+ if (this._host != "127.0.0.1") {
+ // Not the default primary location, nothing special to do here
+ this.remove("http", "127.0.0.1", this._defaultPort);
+ }
+
+ // This is a *very* tricky bit of reasoning here; make absolutely sure the
+ // tests for this code pass before you commit changes to it.
+ if (this._primaryScheme == "http" &&
+ this._primaryHost == this._host &&
+ this._primaryPort == this._defaultPort)
+ {
+ // Make sure we don't trigger the readding logic in .remove(), then remove
+ // the default location.
+ var port = this._defaultPort;
+ this._defaultPort = -1;
+ this.remove("http", this._host, port);
+
+ // Ensure a server start triggers the setPrimary() path in ._initialize()
+ this._primaryPort = -1;
+ }
+ else
+ {
+ // No reason not to remove directly as it's not our primary location
+ this.remove("http", this._host, this._defaultPort);
+ }
+ },
+
+ /**
+ * Ensures scheme, host, and port are all valid with respect to RFC 2396.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if any argument doesn't match the corresponding production
+ */
+ _validate: function(scheme, host, port)
+ {
+ if (scheme !== "http" && scheme !== "https")
+ {
+ dumpn("*** server only supports http/https schemes: '" + scheme + "'");
+ dumpStack();
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (!HOST_REGEX.test(host))
+ {
+ dumpn("*** unexpected host: '" + host + "'");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (port < 0 || port > 65535)
+ {
+ dumpn("*** unexpected port: '" + port + "'");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+};
+
+
+/**
+ * Represents a connection to the server (and possibly in the future the thread
+ * on which the connection is processed).
+ *
+ * @param input : nsIInputStream
+ * stream from which incoming data on the connection is read
+ * @param output : nsIOutputStream
+ * stream to write data out the connection
+ * @param server : nsHttpServer
+ * the server handling the connection
+ * @param port : int
+ * the port on which the server is running
+ * @param outgoingPort : int
+ * the outgoing port used by this connection
+ * @param number : uint
+ * a serial number used to uniquely identify this connection
+ */
+function Connection(input, output, server, port, outgoingPort, number)
+{
+ dumpn("*** opening new connection " + number + " on port " + outgoingPort);
+
+ /** Stream of incoming data. */
+ this.input = input;
+
+ /** Stream for outgoing data. */
+ this.output = output;
+
+ /** The server associated with this request. */
+ this.server = server;
+
+ /** The port on which the server is running. */
+ this.port = port;
+
+ /** The outgoing poort used by this connection. */
+ this._outgoingPort = outgoingPort;
+
+ /** The serial number of this connection. */
+ this.number = number;
+
+ /**
+ * The request for which a response is being generated, null if the
+ * incoming request has not been fully received or if it had errors.
+ */
+ this.request = null;
+
+ /** This allows a connection to disambiguate between a peer initiating a
+ * close and the socket being forced closed on shutdown.
+ */
+ this._closed = false;
+
+ /** State variable for debugging. */
+ this._processed = false;
+
+ /** whether or not 1st line of request has been received */
+ this._requestStarted = false;
+}
+Connection.prototype =
+{
+ /** Closes this connection's input/output streams. */
+ close: function()
+ {
+ if (this._closed)
+ return;
+
+ dumpn("*** closing connection " + this.number +
+ " on port " + this._outgoingPort);
+
+ this.input.close();
+ this.output.close();
+ this._closed = true;
+
+ var server = this.server;
+ server._connectionClosed(this);
+
+ // If an error triggered a server shutdown, act on it now
+ if (server._doQuit)
+ server.stop(function() { /* not like we can do anything better */ });
+ },
+
+ /**
+ * Initiates processing of this connection, using the data in the given
+ * request.
+ *
+ * @param request : Request
+ * the request which should be processed
+ */
+ process: function(request)
+ {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+
+ this.request = request;
+ this.server._handler.handleResponse(this);
+ },
+
+ /**
+ * Initiates processing of this connection, generating a response with the
+ * given HTTP error code.
+ *
+ * @param code : uint
+ * an HTTP code, so in the range [0, 1000)
+ * @param request : Request
+ * incomplete data about the incoming request (since there were errors
+ * during its processing
+ */
+ processError: function(code, request)
+ {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+ this.request = request;
+ this.server._handler.handleError(code, this);
+ },
+
+ /** Converts this to a string for debugging purposes. */
+ toString: function()
+ {
+ return "<Connection(" + this.number +
+ (this.request ? ", " + this.request.path : "") +"): " +
+ (this._closed ? "closed" : "open") + ">";
+ },
+
+ requestStarted: function()
+ {
+ this._requestStarted = true;
+ }
+};
+
+
+
+/** Returns an array of count bytes from the given input stream. */
+function readBytes(inputStream, count)
+{
+ return new BinaryInputStream(inputStream).readByteArray(count);
+}
+
+
+
+/** Request reader processing states; see RequestReader for details. */
+const READER_IN_REQUEST_LINE = 0;
+const READER_IN_HEADERS = 1;
+const READER_IN_BODY = 2;
+const READER_FINISHED = 3;
+
+
+/**
+ * Reads incoming request data asynchronously, does any necessary preprocessing,
+ * and forwards it to the request handler. Processing occurs in three states:
+ *
+ * READER_IN_REQUEST_LINE Reading the request's status line
+ * READER_IN_HEADERS Reading headers in the request
+ * READER_IN_BODY Reading the body of the request
+ * READER_FINISHED Entire request has been read and processed
+ *
+ * During the first two stages, initial metadata about the request is gathered
+ * into a Request object. Once the status line and headers have been processed,
+ * we start processing the body of the request into the Request. Finally, when
+ * the entire body has been read, we create a Response and hand it off to the
+ * ServerHandler to be given to the appropriate request handler.
+ *
+ * @param connection : Connection
+ * the connection for the request being read
+ */
+function RequestReader(connection)
+{
+ /** Connection metadata for this request. */
+ this._connection = connection;
+
+ /**
+ * A container providing line-by-line access to the raw bytes that make up the
+ * data which has been read from the connection but has not yet been acted
+ * upon (by passing it to the request handler or by extracting request
+ * metadata from it).
+ */
+ this._data = new LineData();
+
+ /**
+ * The amount of data remaining to be read from the body of this request.
+ * After all headers in the request have been read this is the value in the
+ * Content-Length header, but as the body is read its value decreases to zero.
+ */
+ this._contentLength = 0;
+
+ /** The current state of parsing the incoming request. */
+ this._state = READER_IN_REQUEST_LINE;
+
+ /** Metadata constructed from the incoming request for the request handler. */
+ this._metadata = new Request(connection.port);
+
+ /**
+ * Used to preserve state if we run out of line data midway through a
+ * multi-line header. _lastHeaderName stores the name of the header, while
+ * _lastHeaderValue stores the value we've seen so far for the header.
+ *
+ * These fields are always either both undefined or both strings.
+ */
+ this._lastHeaderName = this._lastHeaderValue = undefined;
+}
+RequestReader.prototype =
+{
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+ * Called when more data from the incoming request is available. This method
+ * then reads the available data from input and deals with that data as
+ * necessary, depending upon the syntax of already-downloaded data.
+ *
+ * @param input : nsIAsyncInputStream
+ * the stream of incoming data from the connection
+ */
+ onInputStreamReady: function(input)
+ {
+ dumpn("*** onInputStreamReady(input=" + input + ") on thread " +
+ gThreadManager.currentThread + " (main is " +
+ gThreadManager.mainThread + ")");
+ dumpn("*** this._state == " + this._state);
+
+ // Handle cases where we get more data after a request error has been
+ // discovered but *before* we can close the connection.
+ var data = this._data;
+ if (!data)
+ return;
+
+ try
+ {
+ data.appendBytes(readBytes(input, input.available()));
+ }
+ catch (e)
+ {
+ if (streamClosed(e))
+ {
+ dumpn("*** WARNING: unexpected error when reading from socket; will " +
+ "be treated as if the input stream had been closed");
+ dumpn("*** WARNING: actual error was: " + e);
+ }
+
+ // We've lost a race -- input has been closed, but we're still expecting
+ // to read more data. available() will throw in this case, and since
+ // we're dead in the water now, destroy the connection.
+ dumpn("*** onInputStreamReady called on a closed input, destroying " +
+ "connection");
+ this._connection.close();
+ return;
+ }
+
+ switch (this._state)
+ {
+ default:
+ NS_ASSERT(false, "invalid state: " + this._state);
+ break;
+
+ case READER_IN_REQUEST_LINE:
+ if (!this._processRequestLine())
+ break;
+ /* fall through */
+
+ case READER_IN_HEADERS:
+ if (!this._processHeaders())
+ break;
+ /* fall through */
+
+ case READER_IN_BODY:
+ this._processBody();
+ }
+
+ if (this._state != READER_FINISHED)
+ input.asyncWait(this, 0, 0, gThreadManager.currentThread);
+ },
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIInputStreamCallback) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE API
+
+ /**
+ * Processes unprocessed, downloaded data as a request line.
+ *
+ * @returns boolean
+ * true iff the request line has been fully processed
+ */
+ _processRequestLine: function()
+ {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ // Servers SHOULD ignore any empty line(s) received where a Request-Line
+ // is expected (section 4.1).
+ var data = this._data;
+ var line = {};
+ var readSuccess;
+ while ((readSuccess = data.readLine(line)) && line.value == "")
+ dumpn("*** ignoring beginning blank line...");
+
+ // if we don't have a full line, wait until we do
+ if (!readSuccess)
+ return false;
+
+ // we have the first non-blank line
+ try
+ {
+ this._parseRequestLine(line.value);
+ this._state = READER_IN_HEADERS;
+ this._connection.requestStarted();
+ return true;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+ * Processes stored data, assuming it is either at the beginning or in
+ * the middle of processing request headers.
+ *
+ * @returns boolean
+ * true iff header data in the request has been fully processed
+ */
+ _processHeaders: function()
+ {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ // XXX things to fix here:
+ //
+ // - need to support RFC 2047-encoded non-US-ASCII characters
+
+ try
+ {
+ var done = this._parseHeaders();
+ if (done)
+ {
+ var request = this._metadata;
+
+ // XXX this is wrong for requests with transfer-encodings applied to
+ // them, particularly chunked (which by its nature can have no
+ // meaningful Content-Length header)!
+ this._contentLength = request.hasHeader("Content-Length")
+ ? parseInt(request.getHeader("Content-Length"), 10)
+ : 0;
+ dumpn("_processHeaders, Content-length=" + this._contentLength);
+
+ this._state = READER_IN_BODY;
+ }
+ return done;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+ * Processes stored data, assuming it is either at the beginning or in
+ * the middle of processing the request body.
+ *
+ * @returns boolean
+ * true iff the request body has been fully processed
+ */
+ _processBody: function()
+ {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ // XXX handle chunked transfer-coding request bodies!
+
+ try
+ {
+ if (this._contentLength > 0)
+ {
+ var data = this._data.purge();
+ var count = Math.min(data.length, this._contentLength);
+ dumpn("*** loading data=" + data + " len=" + data.length +
+ " excess=" + (data.length - count));
+
+ var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
+ bos.writeByteArray(data, count);
+ this._contentLength -= count;
+ }
+
+ dumpn("*** remaining body data len=" + this._contentLength);
+ if (this._contentLength == 0)
+ {
+ this._validateRequest();
+ this._state = READER_FINISHED;
+ this._handleResponse();
+ return true;
+ }
+
+ return false;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+ * Does various post-header checks on the data in this request.
+ *
+ * @throws : HttpError
+ * if the request was malformed in some way
+ */
+ _validateRequest: function()
+ {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ dumpn("*** _validateRequest");
+
+ var metadata = this._metadata;
+ var headers = metadata._headers;
+
+ // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
+ var identity = this._connection.server.identity;
+ if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
+ {
+ if (!headers.hasHeader("Host"))
+ {
+ dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
+ throw HTTP_400;
+ }
+
+ // If the Request-URI wasn't absolute, then we need to determine our host.
+ // We have to determine what scheme was used to access us based on the
+ // server identity data at this point, because the request just doesn't
+ // contain enough data on its own to do this, sadly.
+ if (!metadata._host)
+ {
+ var host, port;
+ var hostPort = headers.getHeader("Host");
+ var colon = hostPort.indexOf(":");
+ if (colon < 0)
+ {
+ host = hostPort;
+ port = "";
+ }
+ else
+ {
+ host = hostPort.substring(0, colon);
+ port = hostPort.substring(colon + 1);
+ }
+
+ // NB: We allow an empty port here because, oddly, a colon may be
+ // present even without a port number, e.g. "example.com:"; in this
+ // case the default port applies.
+ if (!HOST_REGEX.test(host) || !/^\d*$/.test(port))
+ {
+ dumpn("*** malformed hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ // If we're not given a port, we're stuck, because we don't know what
+ // scheme to use to look up the correct port here, in general. Since
+ // the HTTPS case requires a tunnel/proxy and thus requires that the
+ // requested URI be absolute (and thus contain the necessary
+ // information), let's assume HTTP will prevail and use that.
+ port = +port || 80;
+
+ var scheme = identity.getScheme(host, port);
+ if (!scheme)
+ {
+ dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ }
+ }
+ else
+ {
+ NS_ASSERT(metadata._host === undefined,
+ "HTTP/1.0 doesn't allow absolute paths in the request line!");
+
+ metadata._scheme = identity.primaryScheme;
+ metadata._host = identity.primaryHost;
+ metadata._port = identity.primaryPort;
+ }
+
+ NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
+ "must have a location we recognize by now!");
+ },
+
+ /**
+ * Handles responses in case of error, either in the server or in the request.
+ *
+ * @param e
+ * the specific error encountered, which is an HttpError in the case where
+ * the request is in some way invalid or cannot be fulfilled; if this isn't
+ * an HttpError we're going to be paranoid and shut down, because that
+ * shouldn't happen, ever
+ */
+ _handleError: function(e)
+ {
+ // Don't fall back into normal processing!
+ this._state = READER_FINISHED;
+
+ var server = this._connection.server;
+ if (e instanceof HttpError)
+ {
+ var code = e.code;
+ }
+ else
+ {
+ dumpn("!!! UNEXPECTED ERROR: " + e +
+ (e.lineNumber ? ", line " + e.lineNumber : ""));
+
+ // no idea what happened -- be paranoid and shut down
+ code = 500;
+ server._requestQuit();
+ }
+
+ // make attempted reuse of data an error
+ this._data = null;
+
+ this._connection.processError(code, this._metadata);
+ },
+
+ /**
+ * Now that we've read the request line and headers, we can actually hand off
+ * the request to be handled.
+ *
+ * This method is called once per request, after the request line and all
+ * headers and the body, if any, have been received.
+ */
+ _handleResponse: function()
+ {
+ NS_ASSERT(this._state == READER_FINISHED);
+
+ // We don't need the line-based data any more, so make attempted reuse an
+ // error.
+ this._data = null;
+
+ this._connection.process(this._metadata);
+ },
+
+
+ // PARSING
+
+ /**
+ * Parses the request line for the HTTP request associated with this.
+ *
+ * @param line : string
+ * the request line
+ */
+ _parseRequestLine: function(line)
+ {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ dumpn("*** _parseRequestLine('" + line + "')");
+
+ var metadata = this._metadata;
+
+ // clients and servers SHOULD accept any amount of SP or HT characters
+ // between fields, even though only a single SP is required (section 19.3)
+ var request = line.split(/[ \t]+/);
+ if (!request || request.length != 3)
+ {
+ dumpn("*** No request in line");
+ throw HTTP_400;
+ }
+
+ metadata._method = request[0];
+
+ // get the HTTP version
+ var ver = request[2];
+ var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
+ if (!match)
+ {
+ dumpn("*** No HTTP version in line");
+ throw HTTP_400;
+ }
+
+ // determine HTTP version
+ try
+ {
+ metadata._httpVersion = new nsHttpVersion(match[1]);
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0))
+ throw "unsupported HTTP version";
+ }
+ catch (e)
+ {
+ // we support HTTP/1.0 and HTTP/1.1 only
+ throw HTTP_501;
+ }
+
+
+ var fullPath = request[1];
+ var serverIdentity = this._connection.server.identity;
+
+ var scheme, host, port;
+
+ if (fullPath.charAt(0) != "/")
+ {
+ // No absolute paths in the request line in HTTP prior to 1.1
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
+ {
+ dumpn("*** Metadata version too low");
+ throw HTTP_400;
+ }
+
+ try
+ {
+ var uri = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(fullPath, null, null);
+ fullPath = uri.path;
+ scheme = uri.scheme;
+ host = metadata._host = uri.asciiHost;
+ port = uri.port;
+ if (port === -1)
+ {
+ if (scheme === "http")
+ {
+ port = 80;
+ }
+ else if (scheme === "https")
+ {
+ port = 443;
+ }
+ else
+ {
+ dumpn("*** Unknown scheme: " + scheme);
+ throw HTTP_400;
+ }
+ }
+ }
+ catch (e)
+ {
+ // If the host is not a valid host on the server, the response MUST be a
+ // 400 (Bad Request) error message (section 5.2). Alternately, the URI
+ // is malformed.
+ dumpn("*** Threw when dealing with URI: " + e);
+ throw HTTP_400;
+ }
+
+ if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
+ {
+ dumpn("*** serverIdentity unknown or path does not start with '/'");
+ throw HTTP_400;
+ }
+ }
+
+ var splitter = fullPath.indexOf("?");
+ if (splitter < 0)
+ {
+ // _queryString already set in ctor
+ metadata._path = fullPath;
+ }
+ else
+ {
+ metadata._path = fullPath.substring(0, splitter);
+ metadata._queryString = fullPath.substring(splitter + 1);
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ },
+
+ /**
+ * Parses all available HTTP headers in this until the header-ending CRLFCRLF,
+ * adding them to the store of headers in the request.
+ *
+ * @throws
+ * HTTP_400 if the headers are malformed
+ * @returns boolean
+ * true if all headers have now been processed, false otherwise
+ */
+ _parseHeaders: function()
+ {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ dumpn("*** _parseHeaders");
+
+ var data = this._data;
+
+ var headers = this._metadata._headers;
+ var lastName = this._lastHeaderName;
+ var lastVal = this._lastHeaderValue;
+
+ var line = {};
+ while (true)
+ {
+ dumpn("*** Last name: '" + lastName + "'");
+ dumpn("*** Last val: '" + lastVal + "'");
+ NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
+ lastName === undefined ?
+ "lastVal without lastName? lastVal: '" + lastVal + "'" :
+ "lastName without lastVal? lastName: '" + lastName + "'");
+
+ if (!data.readLine(line))
+ {
+ // save any data we have from the header we might still be processing
+ this._lastHeaderName = lastName;
+ this._lastHeaderValue = lastVal;
+ return false;
+ }
+
+ var lineText = line.value;
+ dumpn("*** Line text: '" + lineText + "'");
+ var firstChar = lineText.charAt(0);
+
+ // blank line means end of headers
+ if (lineText == "")
+ {
+ // we're finished with the previous header
+ if (lastName)
+ {
+ try
+ {
+ headers.setHeader(lastName, lastVal, true);
+ }
+ catch (e)
+ {
+ dumpn("*** setHeader threw on last header, e == " + e);
+ throw HTTP_400;
+ }
+ }
+ else
+ {
+ // no headers in request -- valid for HTTP/1.0 requests
+ }
+
+ // either way, we're done processing headers
+ this._state = READER_IN_BODY;
+ return true;
+ }
+ else if (firstChar == " " || firstChar == "\t")
+ {
+ // multi-line header if we've already seen a header line
+ if (!lastName)
+ {
+ dumpn("We don't have a header to continue!");
+ throw HTTP_400;
+ }
+
+ // append this line's text to the value; starts with SP/HT, so no need
+ // for separating whitespace
+ lastVal += lineText;
+ }
+ else
+ {
+ // we have a new header, so set the old one (if one existed)
+ if (lastName)
+ {
+ try
+ {
+ headers.setHeader(lastName, lastVal, true);
+ }
+ catch (e)
+ {
+ dumpn("*** setHeader threw on a header, e == " + e);
+ throw HTTP_400;
+ }
+ }
+
+ var colon = lineText.indexOf(":"); // first colon must be splitter
+ if (colon < 1)
+ {
+ dumpn("*** No colon or missing header field-name");
+ throw HTTP_400;
+ }
+
+ // set header name, value (to be set in the next loop, usually)
+ lastName = lineText.substring(0, colon);
+ lastVal = lineText.substring(colon + 1);
+ } // empty, continuation, start of header
+ } // while (true)
+ }
+};
+
+
+/** The character codes for CR and LF. */
+const CR = 0x0D, LF = 0x0A;
+
+/**
+ * Calculates the number of characters before the first CRLF pair in array, or
+ * -1 if the array contains no CRLF pair.
+ *
+ * @param array : Array
+ * an array of numbers in the range [0, 256), each representing a single
+ * character; the first CRLF is the lowest index i where
+ * |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
+ * if such an |i| exists, and -1 otherwise
+ * @param start : uint
+ * start index from which to begin searching in array
+ * @returns int
+ * the index of the first CRLF if any were present, -1 otherwise
+ */
+function findCRLF(array, start)
+{
+ for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1))
+ {
+ if (array[i + 1] == LF)
+ return i;
+ }
+ return -1;
+}
+
+
+/**
+ * A container which provides line-by-line access to the arrays of bytes with
+ * which it is seeded.
+ */
+function LineData()
+{
+ /** An array of queued bytes from which to get line-based characters. */
+ this._data = [];
+
+ /** Start index from which to search for CRLF. */
+ this._start = 0;
+}
+LineData.prototype =
+{
+ /**
+ * Appends the bytes in the given array to the internal data cache maintained
+ * by this.
+ */
+ appendBytes: function(bytes)
+ {
+ var count = bytes.length;
+ var quantum = 262144; // just above half SpiderMonkey's argument-count limit
+ if (count < quantum)
+ {
+ Array.prototype.push.apply(this._data, bytes);
+ return;
+ }
+
+ // Large numbers of bytes may cause Array.prototype.push to be called with
+ // more arguments than the JavaScript engine supports. In that case append
+ // bytes in fixed-size amounts until all bytes are appended.
+ for (var start = 0; start < count; start += quantum)
+ {
+ var slice = bytes.slice(start, Math.min(start + quantum, count));
+ Array.prototype.push.apply(this._data, slice);
+ }
+ },
+
+ /**
+ * Removes and returns a line of data, delimited by CRLF, from this.
+ *
+ * @param out
+ * an object whose "value" property will be set to the first line of text
+ * present in this, sans CRLF, if this contains a full CRLF-delimited line
+ * of text; if this doesn't contain enough data, the value of the property
+ * is undefined
+ * @returns boolean
+ * true if a full line of data could be read from the data in this, false
+ * otherwise
+ */
+ readLine: function(out)
+ {
+ var data = this._data;
+ var length = findCRLF(data, this._start);
+ if (length < 0)
+ {
+ this._start = data.length;
+
+ // But if our data ends in a CR, we have to back up one, because
+ // the first byte in the next packet might be an LF and if we
+ // start looking at data.length we won't find it.
+ if (data.length > 0 && data[data.length - 1] === CR)
+ --this._start;
+
+ return false;
+ }
+
+ // Reset for future lines.
+ this._start = 0;
+
+ //
+ // We have the index of the CR, so remove all the characters, including
+ // CRLF, from the array with splice, and convert the removed array
+ // (excluding the trailing CRLF characters) into the corresponding string.
+ //
+ var leading = data.splice(0, length + 2);
+ var quantum = 262144;
+ var line = "";
+ for (var start = 0; start < length; start += quantum)
+ {
+ var slice = leading.slice(start, Math.min(start + quantum, length));
+ line += String.fromCharCode.apply(null, slice);
+ }
+
+ out.value = line;
+ return true;
+ },
+
+ /**
+ * Removes the bytes currently within this and returns them in an array.
+ *
+ * @returns Array
+ * the bytes within this when this method is called
+ */
+ purge: function()
+ {
+ var data = this._data;
+ this._data = [];
+ return data;
+ }
+};
+
+
+
+/**
+ * Creates a request-handling function for an nsIHttpRequestHandler object.
+ */
+function createHandlerFunc(handler)
+{
+ return function(metadata, response) { handler.handle(metadata, response); };
+}
+
+
+/**
+ * The default handler for directories; writes an HTML response containing a
+ * slightly-formatted directory listing.
+ */
+function defaultIndexHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var path = htmlEscape(decodeURI(metadata.path));
+
+ //
+ // Just do a very basic bit of directory listings -- no need for too much
+ // fanciness, especially since we don't have a style sheet in which we can
+ // stick rules (don't want to pollute the default path-space).
+ //
+
+ var body = '<html>\
+ <head>\
+ <title>' + path + '</title>\
+ </head>\
+ <body>\
+ <h1>' + path + '</h1>\
+ <ol style="list-style-type: none">';
+
+ var directory = metadata.getProperty("directory");
+ NS_ASSERT(directory && directory.isDirectory());
+
+ var fileList = [];
+ var files = directory.directoryEntries;
+ while (files.hasMoreElements())
+ {
+ var f = files.getNext().QueryInterface(Ci.nsIFile);
+ var name = f.leafName;
+ if (!f.isHidden() &&
+ (name.charAt(name.length - 1) != HIDDEN_CHAR ||
+ name.charAt(name.length - 2) == HIDDEN_CHAR))
+ fileList.push(f);
+ }
+
+ fileList.sort(fileSort);
+
+ for (var i = 0; i < fileList.length; i++)
+ {
+ var file = fileList[i];
+ try
+ {
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR)
+ name = name.substring(0, name.length - 1);
+ var sep = file.isDirectory() ? "/" : "";
+
+ // Note: using " to delimit the attribute here because encodeURIComponent
+ // passes through '.
+ var item = '<li><a href="' + encodeURIComponent(name) + sep + '">' +
+ htmlEscape(name) + sep +
+ '</a></li>';
+
+ body += item;
+ }
+ catch (e) { /* some file system error, ignore the file */ }
+ }
+
+ body += ' </ol>\
+ </body>\
+ </html>';
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+/**
+ * Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
+ */
+function fileSort(a, b)
+{
+ var dira = a.isDirectory(), dirb = b.isDirectory();
+
+ if (dira && !dirb)
+ return -1;
+ if (dirb && !dira)
+ return 1;
+
+ var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase();
+ return nameb > namea ? -1 : 1;
+}
+
+
+/**
+ * Converts an externally-provided path into an internal path for use in
+ * determining file mappings.
+ *
+ * @param path
+ * the path to convert
+ * @param encoded
+ * true if the given path should be passed through decodeURI prior to
+ * conversion
+ * @throws URIError
+ * if path is incorrectly encoded
+ */
+function toInternalPath(path, encoded)
+{
+ if (encoded)
+ path = decodeURI(path);
+
+ var comps = path.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++)
+ {
+ var comp = comps[i];
+ if (comp.charAt(comp.length - 1) == HIDDEN_CHAR)
+ comps[i] = comp + HIDDEN_CHAR;
+ }
+ return comps.join("/");
+}
+
+const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
+
+/**
+ * Adds custom-specified headers for the given file to the given response, if
+ * any such headers are specified.
+ *
+ * @param file
+ * the file on the disk which is to be written
+ * @param metadata
+ * metadata about the incoming request
+ * @param response
+ * the Response to which any specified headers/data should be written
+ * @throws HTTP_500
+ * if an error occurred while processing custom-specified headers
+ */
+function maybeAddHeaders(file, metadata, response)
+{
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR)
+ name = name.substring(0, name.length - 1);
+
+ var headerFile = file.parent;
+ headerFile.append(name + HEADERS_SUFFIX);
+
+ if (!headerFile.exists())
+ return;
+
+ const PR_RDONLY = 0x01;
+ var fis = new FileInputStream(headerFile, PR_RDONLY, PERMS_READONLY,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ try
+ {
+ var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
+ lis.QueryInterface(Ci.nsIUnicharLineInputStream);
+
+ var line = {value: ""};
+ var more = lis.readLine(line);
+
+ if (!more && line.value == "")
+ return;
+
+
+ // request line
+
+ var status = line.value;
+ if (status.indexOf("HTTP ") == 0)
+ {
+ status = status.substring(5);
+ var space = status.indexOf(" ");
+ var code, description;
+ if (space < 0)
+ {
+ code = status;
+ description = "";
+ }
+ else
+ {
+ code = status.substring(0, space);
+ description = status.substring(space + 1, status.length);
+ }
+
+ response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+
+ // headers
+ while (more || line.value != "")
+ {
+ var header = line.value;
+ var colon = header.indexOf(":");
+
+ response.setHeader(header.substring(0, colon),
+ header.substring(colon + 1, header.length),
+ false); // allow overriding server-set headers
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+ }
+ catch (e)
+ {
+ dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
+ throw HTTP_500;
+ }
+ finally
+ {
+ fis.close();
+ }
+}
+
+
+/**
+ * An object which handles requests for a server, executing default and
+ * overridden behaviors as instructed by the code which uses and manipulates it.
+ * Default behavior includes the paths / and /trace (diagnostics), with some
+ * support for HTTP error pages for various codes and fallback to HTTP 500 if
+ * those codes fail for any reason.
+ *
+ * @param server : nsHttpServer
+ * the server in which this handler is being used
+ */
+function ServerHandler(server)
+{
+ // FIELDS
+
+ /**
+ * The nsHttpServer instance associated with this handler.
+ */
+ this._server = server;
+
+ /**
+ * A FileMap object containing the set of path->nsILocalFile mappings for
+ * all directory mappings set in the server (e.g., "/" for /var/www/html/,
+ * "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).
+ *
+ * Note carefully: the leading and trailing "/" in each path (not file) are
+ * removed before insertion to simplify the code which uses this. You have
+ * been warned!
+ */
+ this._pathDirectoryMap = new FileMap();
+
+ /**
+ * Custom request handlers for the server in which this resides. Path-handler
+ * pairs are stored as property-value pairs in this property.
+ *
+ * @see ServerHandler.prototype._defaultPaths
+ */
+ this._overridePaths = {};
+
+ /**
+ * Custom request handlers for the path prefixes on the server in which this
+ * resides. Path-handler pairs are stored as property-value pairs in this
+ * property.
+ *
+ * @see ServerHandler.prototype._defaultPaths
+ */
+ this._overridePrefixes = {};
+
+ /**
+ * Custom request handlers for the error handlers in the server in which this
+ * resides. Path-handler pairs are stored as property-value pairs in this
+ * property.
+ *
+ * @see ServerHandler.prototype._defaultErrors
+ */
+ this._overrideErrors = {};
+
+ /**
+ * Maps file extensions to their MIME types in the server, overriding any
+ * mapping that might or might not exist in the MIME service.
+ */
+ this._mimeMappings = {};
+
+ /**
+ * The default handler for requests for directories, used to serve directories
+ * when no index file is present.
+ */
+ this._indexHandler = defaultIndexHandler;
+
+ /** Per-path state storage for the server. */
+ this._state = {};
+
+ /** Entire-server state storage. */
+ this._sharedState = {};
+
+ /** Entire-server state storage for nsISupports values. */
+ this._objectState = {};
+}
+ServerHandler.prototype =
+{
+ // PUBLIC API
+
+ /**
+ * Handles a request to this server, responding to the request appropriately
+ * and initiating server shutdown if necessary.
+ *
+ * This method never throws an exception.
+ *
+ * @param connection : Connection
+ * the connection for this request
+ */
+ handleResponse: function(connection)
+ {
+ var request = connection.request;
+ var response = new Response(connection);
+
+ var path = request.path;
+ dumpn("*** path == " + path);
+
+ try
+ {
+ try
+ {
+ if (path in this._overridePaths)
+ {
+ // explicit paths first, then files based on existing directory mappings,
+ // then (if the file doesn't exist) built-in server default paths
+ dumpn("calling override for " + path);
+ this._overridePaths[path](request, response);
+ }
+ else
+ {
+ var longestPrefix = "";
+ for (let prefix in this._overridePrefixes) {
+ if (prefix.length > longestPrefix.length &&
+ path.substr(0, prefix.length) == prefix)
+ {
+ longestPrefix = prefix;
+ }
+ }
+ if (longestPrefix.length > 0)
+ {
+ dumpn("calling prefix override for " + longestPrefix);
+ this._overridePrefixes[longestPrefix](request, response);
+ }
+ else
+ {
+ this._handleDefault(request, response);
+ }
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ if (!(e instanceof HttpError))
+ {
+ dumpn("*** unexpected error: e == " + e);
+ throw HTTP_500;
+ }
+ if (e.code !== 404)
+ throw e;
+
+ dumpn("*** default: " + (path in this._defaultPaths));
+
+ response = new Response(connection);
+ if (path in this._defaultPaths)
+ this._defaultPaths[path](request, response);
+ else
+ throw HTTP_404;
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ var errorCode = "internal";
+
+ try
+ {
+ if (!(e instanceof HttpError))
+ throw e;
+
+ errorCode = e.code;
+ dumpn("*** errorCode == " + errorCode);
+
+ response = new Response(connection);
+ if (e.customErrorHandling)
+ e.customErrorHandling(response);
+ this._handleError(errorCode, request, response);
+ return;
+ }
+ catch (e2)
+ {
+ dumpn("*** error handling " + errorCode + " error: " +
+ "e2 == " + e2 + ", shutting down server");
+
+ connection.server._requestQuit();
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile: function(path, file)
+ {
+ if (!file)
+ {
+ dumpn("*** unregistering '" + path + "' mapping");
+ delete this._overridePaths[path];
+ return;
+ }
+
+ dumpn("*** registering '" + path + "' as mapping to " + file.path);
+ file = file.clone();
+
+ var self = this;
+ this._overridePaths[path] =
+ function(request, response)
+ {
+ if (!file.exists())
+ throw HTTP_404;
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ self._writeFileResponse(request, file, response, 0, file.fileSize);
+ };
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler: function(path, handler)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handlerToField(handler, this._overridePaths, path);
+ },
+
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler: function(path, handler)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handlerToField(handler, this._overridePrefixes, path);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(path, directory)
+ {
+ // strip off leading and trailing '/' so that we can use lastIndexOf when
+ // determining exactly how a path maps onto a mapped directory --
+ // conditional is required here to deal with "/".substring(1, 0) being
+ // converted to "/".substring(0, 1) per the JS specification
+ var key = path.length == 1 ? "" : path.substring(1, path.length - 1);
+
+ // the path-to-directory mapping code requires that the first character not
+ // be "/", or it will go into an infinite loop
+ if (key.charAt(0) == "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ key = toInternalPath(key, false);
+
+ if (directory)
+ {
+ dumpn("*** mapping '" + path + "' to the location " + directory.path);
+ this._pathDirectoryMap.put(key, directory);
+ }
+ else
+ {
+ dumpn("*** removing mapping for '" + path + "'");
+ this._pathDirectoryMap.put(key, null);
+ }
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler: function(err, handler)
+ {
+ if (!(err in HTTP_ERROR_CODES))
+ dumpn("*** WARNING: registering non-HTTP/1.1 error code " +
+ "(" + err + ") handler -- was this intentional?");
+
+ this._handlerToField(handler, this._overrideErrors, err);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler: function(handler)
+ {
+ if (!handler)
+ handler = defaultIndexHandler;
+ else if (typeof(handler) != "function")
+ handler = createHandlerFunc(handler);
+
+ this._indexHandler = handler;
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(ext, type)
+ {
+ if (!type)
+ delete this._mimeMappings[ext];
+ else
+ this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
+ },
+
+ // PRIVATE API
+
+ /**
+ * Sets or remove (if handler is null) a handler in an object with a key.
+ *
+ * @param handler
+ * a handler, either function or an nsIHttpRequestHandler
+ * @param dict
+ * The object to attach the handler to.
+ * @param key
+ * The field name of the handler.
+ */
+ _handlerToField: function(handler, dict, key)
+ {
+ // for convenience, handler can be a function if this is run from xpcshell
+ if (typeof(handler) == "function")
+ dict[key] = handler;
+ else if (handler)
+ dict[key] = createHandlerFunc(handler);
+ else
+ delete dict[key];
+ },
+
+ /**
+ * Handles a request which maps to a file in the local filesystem (if a base
+ * path has already been set; otherwise the 404 error is thrown).
+ *
+ * @param metadata : Request
+ * metadata for the incoming request
+ * @param response : Response
+ * an uninitialized Response to the given request, to be initialized by a
+ * request handler
+ * @throws HTTP_###
+ * if an HTTP error occurred (usually HTTP_404); note that in this case the
+ * calling code must handle post-processing of the response
+ */
+ _handleDefault: function(metadata, response)
+ {
+ dumpn("*** _handleDefault()");
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+
+ var path = metadata.path;
+ NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");
+
+ // determine the actual on-disk file; this requires finding the deepest
+ // path-to-directory mapping in the requested URL
+ var file = this._getFileForPath(path);
+
+ // the "file" might be a directory, in which case we either serve the
+ // contained index.html or make the index handler write the response
+ if (file.exists() && file.isDirectory())
+ {
+ file.append("index.html"); // make configurable?
+ if (!file.exists() || file.isDirectory())
+ {
+ metadata._ensurePropertyBag();
+ metadata._bag.setPropertyAsInterface("directory", file.parent);
+ this._indexHandler(metadata, response);
+ return;
+ }
+ }
+
+ // alternately, the file might not exist
+ if (!file.exists())
+ throw HTTP_404;
+
+ var start, end;
+ if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
+ metadata.hasHeader("Range") &&
+ this._getTypeFromFile(file) !== SJS_TYPE)
+ {
+ var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
+ if (!rangeMatch)
+ {
+ dumpn("*** Range header bogosity: '" + metadata.getHeader("Range") + "'");
+ throw HTTP_400;
+ }
+
+ if (rangeMatch[1] !== undefined)
+ start = parseInt(rangeMatch[1], 10);
+
+ if (rangeMatch[2] !== undefined)
+ end = parseInt(rangeMatch[2], 10);
+
+ if (start === undefined && end === undefined)
+ {
+ dumpn("*** More Range header bogosity: '" + metadata.getHeader("Range") + "'");
+ throw HTTP_400;
+ }
+
+ // No start given, so the end is really the count of bytes from the
+ // end of the file.
+ if (start === undefined)
+ {
+ start = Math.max(0, file.fileSize - end);
+ end = file.fileSize - 1;
+ }
+
+ // start and end are inclusive
+ if (end === undefined || end >= file.fileSize)
+ end = file.fileSize - 1;
+
+ if (start !== undefined && start >= file.fileSize) {
+ var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
+ HTTP_416.customErrorHandling = function(errorResponse)
+ {
+ maybeAddHeaders(file, metadata, errorResponse);
+ };
+ throw HTTP_416;
+ }
+
+ if (end < start)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ start = 0;
+ end = file.fileSize - 1;
+ }
+ else
+ {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
+ response.setHeader("Content-Range", contentRange);
+ }
+ }
+ else
+ {
+ start = 0;
+ end = file.fileSize - 1;
+ }
+
+ // finally...
+ dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " +
+ start + " to " + end + " inclusive");
+ this._writeFileResponse(metadata, file, response, start, end - start + 1);
+ },
+
+ /**
+ * Writes an HTTP response for the given file, including setting headers for
+ * file metadata.
+ *
+ * @param metadata : Request
+ * the Request for which a response is being generated
+ * @param file : nsILocalFile
+ * the file which is to be sent in the response
+ * @param response : Response
+ * the response to which the file should be written
+ * @param offset: uint
+ * the byte offset to skip to when writing
+ * @param count: uint
+ * the number of bytes to write
+ */
+ _writeFileResponse: function(metadata, file, response, offset, count)
+ {
+ const PR_RDONLY = 0x01;
+
+ var type = this._getTypeFromFile(file);
+ if (type === SJS_TYPE)
+ {
+ var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ try
+ {
+ var sis = new ScriptableInputStream(fis);
+ var s = Cu.Sandbox(gGlobalObject);
+ s.importFunction(dump, "dump");
+ s.importFunction(atob, "atob");
+ s.importFunction(btoa, "btoa");
+
+ // Define a basic key-value state-preservation API across requests, with
+ // keys initially corresponding to the empty string.
+ var self = this;
+ var path = metadata.path;
+ s.importFunction(function getState(k)
+ {
+ return self._getState(path, k);
+ });
+ s.importFunction(function setState(k, v)
+ {
+ self._setState(path, k, v);
+ });
+ s.importFunction(function getSharedState(k)
+ {
+ return self._getSharedState(k);
+ });
+ s.importFunction(function setSharedState(k, v)
+ {
+ self._setSharedState(k, v);
+ });
+ s.importFunction(function getObjectState(k, callback)
+ {
+ callback(self._getObjectState(k));
+ });
+ s.importFunction(function setObjectState(k, v)
+ {
+ self._setObjectState(k, v);
+ });
+ s.importFunction(function registerPathHandler(p, h)
+ {
+ self.registerPathHandler(p, h);
+ });
+
+ // Make it possible for sjs files to access their location
+ this._setState(path, "__LOCATION__", file.path);
+
+ try
+ {
+ // Alas, the line number in errors dumped to console when calling the
+ // request handler is simply an offset from where we load the SJS file.
+ // Work around this in a reasonably non-fragile way by dynamically
+ // getting the line number where we evaluate the SJS file. Don't
+ // separate these two lines!
+ var line = new Error().lineNumber;
+ Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
+ }
+ catch (e)
+ {
+ dumpn("*** syntax error in SJS at " + file.path + ": " + e);
+ throw HTTP_500;
+ }
+
+ try
+ {
+ s.handleRequest(metadata, response);
+ }
+ catch (e)
+ {
+ dump("*** error running SJS at " + file.path + ": " +
+ e + " on line " +
+ (e instanceof Error
+ ? e.lineNumber + " in httpd.js"
+ : (e.lineNumber - line)) + "\n");
+ throw HTTP_500;
+ }
+ }
+ finally
+ {
+ fis.close();
+ }
+ }
+ else
+ {
+ try
+ {
+ response.setHeader("Last-Modified",
+ toDateString(file.lastModifiedTime),
+ false);
+ }
+ catch (e) { /* lastModifiedTime threw, ignore */ }
+
+ response.setHeader("Content-Type", type, false);
+ maybeAddHeaders(file, metadata, response);
+ response.setHeader("Content-Length", "" + count, false);
+
+ var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ offset = offset || 0;
+ count = count || file.fileSize;
+ NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
+ NS_ASSERT(count >= 0, "bad count");
+ NS_ASSERT(offset + count <= file.fileSize, "bad total data size");
+
+ try
+ {
+ if (offset !== 0)
+ {
+ // Seek (or read, if seeking isn't supported) to the correct offset so
+ // the data sent to the client matches the requested range.
+ if (fis instanceof Ci.nsISeekableStream)
+ fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
+ else
+ new ScriptableInputStream(fis).read(offset);
+ }
+ }
+ catch (e)
+ {
+ fis.close();
+ throw e;
+ }
+
+ let writeMore = function () {
+ gThreadManager.currentThread
+ .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+
+ var input = new BinaryInputStream(fis);
+ var output = new BinaryOutputStream(response.bodyOutputStream);
+ var writeData =
+ {
+ run: function()
+ {
+ var chunkSize = Math.min(65536, count);
+ count -= chunkSize;
+ NS_ASSERT(count >= 0, "underflow");
+
+ try
+ {
+ var data = input.readByteArray(chunkSize);
+ NS_ASSERT(data.length === chunkSize,
+ "incorrect data returned? got " + data.length +
+ ", expected " + chunkSize);
+ output.writeByteArray(data, data.length);
+ if (count === 0)
+ {
+ fis.close();
+ response.finish();
+ }
+ else
+ {
+ writeMore();
+ }
+ }
+ catch (e)
+ {
+ try
+ {
+ fis.close();
+ }
+ finally
+ {
+ response.finish();
+ }
+ throw e;
+ }
+ }
+ };
+
+ writeMore();
+
+ // Now that we know copying will start, flag the response as async.
+ response.processAsync();
+ }
+ },
+
+ /**
+ * Get the value corresponding to a given key for the given path for SJS state
+ * preservation across requests.
+ *
+ * @param path : string
+ * the path from which the given state is to be retrieved
+ * @param k : string
+ * the key whose corresponding value is to be returned
+ * @returns string
+ * the corresponding value, which is initially the empty string
+ */
+ _getState: function(path, k)
+ {
+ var state = this._state;
+ if (path in state && k in state[path])
+ return state[path][k];
+ return "";
+ },
+
+ /**
+ * Set the value corresponding to a given key for the given path for SJS state
+ * preservation across requests.
+ *
+ * @param path : string
+ * the path from which the given state is to be retrieved
+ * @param k : string
+ * the key whose corresponding value is to be set
+ * @param v : string
+ * the value to be set
+ */
+ _setState: function(path, k, v)
+ {
+ if (typeof v !== "string")
+ throw new Error("non-string value passed");
+ var state = this._state;
+ if (!(path in state))
+ state[path] = {};
+ state[path][k] = v;
+ },
+
+ /**
+ * Get the value corresponding to a given key for SJS state preservation
+ * across requests.
+ *
+ * @param k : string
+ * the key whose corresponding value is to be returned
+ * @returns string
+ * the corresponding value, which is initially the empty string
+ */
+ _getSharedState: function(k)
+ {
+ var state = this._sharedState;
+ if (k in state)
+ return state[k];
+ return "";
+ },
+
+ /**
+ * Set the value corresponding to a given key for SJS state preservation
+ * across requests.
+ *
+ * @param k : string
+ * the key whose corresponding value is to be set
+ * @param v : string
+ * the value to be set
+ */
+ _setSharedState: function(k, v)
+ {
+ if (typeof v !== "string")
+ throw new Error("non-string value passed");
+ this._sharedState[k] = v;
+ },
+
+ /**
+ * Returns the object associated with the given key in the server for SJS
+ * state preservation across requests.
+ *
+ * @param k : string
+ * the key whose corresponding object is to be returned
+ * @returns nsISupports
+ * the corresponding object, or null if none was present
+ */
+ _getObjectState: function(k)
+ {
+ if (typeof k !== "string")
+ throw new Error("non-string key passed");
+ return this._objectState[k] || null;
+ },
+
+ /**
+ * Sets the object associated with the given key in the server for SJS
+ * state preservation across requests.
+ *
+ * @param k : string
+ * the key whose corresponding object is to be set
+ * @param v : nsISupports
+ * the object to be associated with the given key; may be null
+ */
+ _setObjectState: function(k, v)
+ {
+ if (typeof k !== "string")
+ throw new Error("non-string key passed");
+ if (typeof v !== "object")
+ throw new Error("non-object value passed");
+ if (v && !("QueryInterface" in v))
+ {
+ throw new Error("must pass an nsISupports; use wrappedJSObject to ease " +
+ "pain when using the server from JS");
+ }
+
+ this._objectState[k] = v;
+ },
+
+ /**
+ * Gets a content-type for the given file, first by checking for any custom
+ * MIME-types registered with this handler for the file's extension, second by
+ * asking the global MIME service for a content-type, and finally by failing
+ * over to application/octet-stream.
+ *
+ * @param file : nsIFile
+ * the nsIFile for which to get a file type
+ * @returns string
+ * the best content-type which can be determined for the file
+ */
+ _getTypeFromFile: function(file)
+ {
+ try
+ {
+ var name = file.leafName;
+ var dot = name.lastIndexOf(".");
+ if (dot > 0)
+ {
+ var ext = name.slice(dot + 1);
+ if (ext in this._mimeMappings)
+ return this._mimeMappings[ext];
+ }
+ return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(file);
+ }
+ catch (e)
+ {
+ return "application/octet-stream";
+ }
+ },
+
+ /**
+ * Returns the nsILocalFile which corresponds to the path, as determined using
+ * all registered path->directory mappings and any paths which are explicitly
+ * overridden.
+ *
+ * @param path : string
+ * the server path for which a file should be retrieved, e.g. "/foo/bar"
+ * @throws HttpError
+ * when the correct action is the corresponding HTTP error (i.e., because no
+ * mapping was found for a directory in path, the referenced file doesn't
+ * exist, etc.)
+ * @returns nsILocalFile
+ * the file to be sent as the response to a request for the path
+ */
+ _getFileForPath: function(path)
+ {
+ // decode and add underscores as necessary
+ try
+ {
+ path = toInternalPath(path, true);
+ }
+ catch (e)
+ {
+ dumpn("*** toInternalPath threw " + e);
+ throw HTTP_400; // malformed path
+ }
+
+ // next, get the directory which contains this path
+ var pathMap = this._pathDirectoryMap;
+
+ // An example progression of tmp for a path "/foo/bar/baz/" might be:
+ // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
+ var tmp = path.substring(1);
+ while (true)
+ {
+ // do we have a match for current head of the path?
+ var file = pathMap.get(tmp);
+ if (file)
+ {
+ // XXX hack; basically disable showing mapping for /foo/bar/ when the
+ // requested path was /foo/bar, because relative links on the page
+ // will all be incorrect -- we really need the ability to easily
+ // redirect here instead
+ if (tmp == path.substring(1) &&
+ tmp.length != 0 &&
+ tmp.charAt(tmp.length - 1) != "/")
+ file = null;
+ else
+ break;
+ }
+
+ // if we've finished trying all prefixes, exit
+ if (tmp == "")
+ break;
+
+ tmp = tmp.substring(0, tmp.lastIndexOf("/"));
+ }
+
+ // no mapping applies, so 404
+ if (!file)
+ throw HTTP_404;
+
+
+ // last, get the file for the path within the determined directory
+ var parentFolder = file.parent;
+ var dirIsRoot = (parentFolder == null);
+
+ // Strategy here is to append components individually, making sure we
+ // never move above the given directory; this allows paths such as
+ // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";
+ // this component-wise approach also means the code works even on platforms
+ // which don't use "/" as the directory separator, such as Windows
+ var leafPath = path.substring(tmp.length + 1);
+ var comps = leafPath.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++)
+ {
+ var comp = comps[i];
+
+ if (comp == "..")
+ file = file.parent;
+ else if (comp == "." || comp == "")
+ continue;
+ else
+ file.append(comp);
+
+ if (!dirIsRoot && file.equals(parentFolder))
+ throw HTTP_403;
+ }
+
+ return file;
+ },
+
+ /**
+ * Writes the error page for the given HTTP error code over the given
+ * connection.
+ *
+ * @param errorCode : uint
+ * the HTTP error code to be used
+ * @param connection : Connection
+ * the connection on which the error occurred
+ */
+ handleError: function(errorCode, connection)
+ {
+ var response = new Response(connection);
+
+ dumpn("*** error in request: " + errorCode);
+
+ this._handleError(errorCode, new Request(connection.port), response);
+ },
+
+ /**
+ * Handles a request which generates the given error code, using the
+ * user-defined error handler if one has been set, gracefully falling back to
+ * the x00 status code if the code has no handler, and failing to status code
+ * 500 if all else fails.
+ *
+ * @param errorCode : uint
+ * the HTTP error which is to be returned
+ * @param metadata : Request
+ * metadata for the request, which will often be incomplete since this is an
+ * error
+ * @param response : Response
+ * an uninitialized Response should be initialized when this method
+ * completes with information which represents the desired error code in the
+ * ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
+ * fallback for 505, per HTTP specs)
+ */
+ _handleError: function(errorCode, metadata, response)
+ {
+ if (!metadata)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ var errorX00 = errorCode - (errorCode % 100);
+
+ try
+ {
+ if (!(errorCode in HTTP_ERROR_CODES))
+ dumpn("*** WARNING: requested invalid error: " + errorCode);
+
+ // RFC 2616 says that we should try to handle an error by its class if we
+ // can't otherwise handle it -- if that fails, we revert to handling it as
+ // a 500 internal server error, and if that fails we throw and shut down
+ // the server
+
+ // actually handle the error
+ try
+ {
+ if (errorCode in this._overrideErrors)
+ this._overrideErrors[errorCode](metadata, response);
+ else
+ this._defaultErrors[errorCode](metadata, response);
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ // don't retry the handler that threw
+ if (errorX00 == errorCode)
+ throw HTTP_500;
+
+ dumpn("*** error in handling for error code " + errorCode + ", " +
+ "falling back to " + errorX00 + "...");
+ response = new Response(response._connection);
+ if (errorX00 in this._overrideErrors)
+ this._overrideErrors[errorX00](metadata, response);
+ else if (errorX00 in this._defaultErrors)
+ this._defaultErrors[errorX00](metadata, response);
+ else
+ throw HTTP_500;
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort();
+ return;
+ }
+
+ // we've tried everything possible for a meaningful error -- now try 500
+ dumpn("*** error in handling for error code " + errorX00 + ", falling " +
+ "back to 500...");
+
+ try
+ {
+ response = new Response(response._connection);
+ if (500 in this._overrideErrors)
+ this._overrideErrors[500](metadata, response);
+ else
+ this._defaultErrors[500](metadata, response);
+ }
+ catch (e2)
+ {
+ dumpn("*** multiple errors in default error handlers!");
+ dumpn("*** e == " + e + ", e2 == " + e2);
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ // FIELDS
+
+ /**
+ * This object contains the default handlers for the various HTTP error codes.
+ */
+ _defaultErrors:
+ {
+ 400: function(metadata, response)
+ {
+ // none of the data in metadata is reliable, so hard-code everything here
+ response.setStatusLine("1.1", 400, "Bad Request");
+ response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
+
+ var body = "Bad request\n";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 403: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body = "<html>\
+ <head><title>403 Forbidden</title></head>\
+ <body>\
+ <h1>403 Forbidden</h1>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 404: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body = "<html>\
+ <head><title>404 Not Found</title></head>\
+ <body>\
+ <h1>404 Not Found</h1>\
+ <p>\
+ <span style='font-family: monospace;'>" +
+ htmlEscape(metadata.path) +
+ "</span> was not found.\
+ </p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 416: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion,
+ 416,
+ "Requested Range Not Satisfiable");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body = "<html>\
+ <head>\
+ <title>416 Requested Range Not Satisfiable</title></head>\
+ <body>\
+ <h1>416 Requested Range Not Satisfiable</h1>\
+ <p>The byte range was not valid for the\
+ requested resource.\
+ </p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 500: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion,
+ 500,
+ "Internal Server Error");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body = "<html>\
+ <head><title>500 Internal Server Error</title></head>\
+ <body>\
+ <h1>500 Internal Server Error</h1>\
+ <p>Something's broken in this server and\
+ needs to be fixed.</p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 501: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body = "<html>\
+ <head><title>501 Not Implemented</title></head>\
+ <body>\
+ <h1>501 Not Implemented</h1>\
+ <p>This server is not (yet) Apache.</p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 505: function(metadata, response)
+ {
+ response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body = "<html>\
+ <head><title>505 HTTP Version Not Supported</title></head>\
+ <body>\
+ <h1>505 HTTP Version Not Supported</h1>\
+ <p>This server only supports HTTP/1.0 and HTTP/1.1\
+ connections.</p>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+ }
+ },
+
+ /**
+ * Contains handlers for the default set of URIs contained in this server.
+ */
+ _defaultPaths:
+ {
+ "/": function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ var body = "<html>\
+ <head><title>httpd.js</title></head>\
+ <body>\
+ <h1>httpd.js</h1>\
+ <p>If you're seeing this page, httpd.js is up and\
+ serving requests! Now set a base path and serve some\
+ files!</p>\
+ </body>\
+ </html>";
+
+ response.bodyOutputStream.write(body, body.length);
+ },
+
+ "/trace": function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
+
+ var body = "Request-URI: " +
+ metadata.scheme + "://" + metadata.host + ":" + metadata.port +
+ metadata.path + "\n\n";
+ body += "Request (semantically equivalent, slightly reformatted):\n\n";
+ body += metadata.method + " " + metadata.path;
+
+ if (metadata.queryString)
+ body += "?" + metadata.queryString;
+
+ body += " HTTP/" + metadata.httpVersion + "\r\n";
+
+ var headEnum = metadata.headers;
+ while (headEnum.hasMoreElements())
+ {
+ var fieldName = headEnum.getNext()
+ .QueryInterface(Ci.nsISupportsString)
+ .data;
+ body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+ }
+ }
+};
+
+
+/**
+ * Maps absolute paths to files on the local file system (as nsILocalFiles).
+ */
+function FileMap()
+{
+ /** Hash which will map paths to nsILocalFiles. */
+ this._map = {};
+}
+FileMap.prototype =
+{
+ // PUBLIC API
+
+ /**
+ * Maps key to a clone of the nsILocalFile value if value is non-null;
+ * otherwise, removes any extant mapping for key.
+ *
+ * @param key : string
+ * string to which a clone of value is mapped
+ * @param value : nsILocalFile
+ * the file to map to key, or null to remove a mapping
+ */
+ put: function(key, value)
+ {
+ if (value)
+ this._map[key] = value.clone();
+ else
+ delete this._map[key];
+ },
+
+ /**
+ * Returns a clone of the nsILocalFile mapped to key, or null if no such
+ * mapping exists.
+ *
+ * @param key : string
+ * key to which the returned file maps
+ * @returns nsILocalFile
+ * a clone of the mapped file, or null if no mapping exists
+ */
+ get: function(key)
+ {
+ var val = this._map[key];
+ return val ? val.clone() : null;
+ }
+};
+
+
+// Response CONSTANTS
+
+// token = *<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (0-127)>
+// CTL = <any US-ASCII control character (0-31) and DEL (127)>
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+const IS_TOKEN_ARRAY =
+ [0, 0, 0, 0, 0, 0, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24
+
+ 0, 1, 0, 1, 1, 1, 1, 1, // 32
+ 0, 0, 1, 1, 0, 1, 1, 0, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, // 48
+ 1, 1, 0, 0, 0, 0, 0, 0, // 56
+
+ 0, 1, 1, 1, 1, 1, 1, 1, // 64
+ 1, 1, 1, 1, 1, 1, 1, 1, // 72
+ 1, 1, 1, 1, 1, 1, 1, 1, // 80
+ 1, 1, 1, 0, 0, 0, 1, 1, // 88
+
+ 1, 1, 1, 1, 1, 1, 1, 1, // 96
+ 1, 1, 1, 1, 1, 1, 1, 1, // 104
+ 1, 1, 1, 1, 1, 1, 1, 1, // 112
+ 1, 1, 1, 0, 1, 0, 1]; // 120
+
+
+/**
+ * Determines whether the given character code is a CTL.
+ *
+ * @param code : uint
+ * the character code
+ * @returns boolean
+ * true if code is a CTL, false otherwise
+ */
+function isCTL(code)
+{
+ return (code >= 0 && code <= 31) || (code == 127);
+}
+
+/**
+ * Represents a response to an HTTP request, encapsulating all details of that
+ * response. This includes all headers, the HTTP version, status code and
+ * explanation, and the entity itself.
+ *
+ * @param connection : Connection
+ * the connection over which this response is to be written
+ */
+function Response(connection)
+{
+ /** The connection over which this response will be written. */
+ this._connection = connection;
+
+ /**
+ * The HTTP version of this response; defaults to 1.1 if not set by the
+ * handler.
+ */
+ this._httpVersion = nsHttpVersion.HTTP_1_1;
+
+ /**
+ * The HTTP code of this response; defaults to 200.
+ */
+ this._httpCode = 200;
+
+ /**
+ * The description of the HTTP code in this response; defaults to "OK".
+ */
+ this._httpDescription = "OK";
+
+ /**
+ * An nsIHttpHeaders object in which the headers in this response should be
+ * stored. This property is null after the status line and headers have been
+ * written to the network, and it may be modified up until it is cleared,
+ * except if this._finished is set first (in which case headers are written
+ * asynchronously in response to a finish() call not preceded by
+ * flushHeaders()).
+ */
+ this._headers = new nsHttpHeaders();
+
+ /**
+ * Set to true when this response is ended (completely constructed if possible
+ * and the connection closed); further actions on this will then fail.
+ */
+ this._ended = false;
+
+ /**
+ * A stream used to hold data written to the body of this response.
+ */
+ this._bodyOutputStream = null;
+
+ /**
+ * A stream containing all data that has been written to the body of this
+ * response so far. (Async handlers make the data contained in this
+ * unreliable as a way of determining content length in general, but auxiliary
+ * saved information can sometimes be used to guarantee reliability.)
+ */
+ this._bodyInputStream = null;
+
+ /**
+ * A stream copier which copies data to the network. It is initially null
+ * until replaced with a copier for response headers; when headers have been
+ * fully sent it is replaced with a copier for the response body, remaining
+ * so for the duration of response processing.
+ */
+ this._asyncCopier = null;
+
+ /**
+ * True if this response has been designated as being processed
+ * asynchronously rather than for the duration of a single call to
+ * nsIHttpRequestHandler.handle.
+ */
+ this._processAsync = false;
+
+ /**
+ * True iff finish() has been called on this, signaling that no more changes
+ * to this may be made.
+ */
+ this._finished = false;
+
+ /**
+ * True iff powerSeized() has been called on this, signaling that this
+ * response is to be handled manually by the response handler (which may then
+ * send arbitrary data in response, even non-HTTP responses).
+ */
+ this._powerSeized = false;
+}
+Response.prototype =
+{
+ // PUBLIC CONSTRUCTION API
+
+ //
+ // see nsIHttpResponse.bodyOutputStream
+ //
+ get bodyOutputStream()
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ if (!this._bodyOutputStream)
+ {
+ var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX,
+ null);
+ this._bodyOutputStream = pipe.outputStream;
+ this._bodyInputStream = pipe.inputStream;
+ if (this._processAsync || this._powerSeized)
+ this._startAsyncProcessor();
+ }
+
+ return this._bodyOutputStream;
+ },
+
+ //
+ // see nsIHttpResponse.write
+ //
+ write: function(data)
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ var dataAsString = String(data);
+ this.bodyOutputStream.write(dataAsString, dataAsString.length);
+ },
+
+ //
+ // see nsIHttpResponse.setStatusLine
+ //
+ setStatusLine: function(httpVersion, code, description)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ if (!(code >= 0 && code < 1000))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ try
+ {
+ var httpVer;
+ // avoid version construction for the most common cases
+ if (!httpVersion || httpVersion == "1.1")
+ httpVer = nsHttpVersion.HTTP_1_1;
+ else if (httpVersion == "1.0")
+ httpVer = nsHttpVersion.HTTP_1_0;
+ else
+ httpVer = new nsHttpVersion(httpVersion);
+ }
+ catch (e)
+ {
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ // Reason-Phrase = *<TEXT, excluding CR, LF>
+ // TEXT = <any OCTET except CTLs, but including LWS>
+ //
+ // XXX this ends up disallowing octets which aren't Unicode, I think -- not
+ // much to do if description is IDL'd as string
+ if (!description)
+ description = "";
+ for (var i = 0; i < description.length; i++)
+ if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // set the values only after validation to preserve atomicity
+ this._httpDescription = description;
+ this._httpCode = code;
+ this._httpVersion = httpVer;
+ },
+
+ //
+ // see nsIHttpResponse.setHeader
+ //
+ setHeader: function(name, value, merge)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ this._headers.setHeader(name, value, merge);
+ },
+
+ setHeaderNoCheck: function(name, value)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ this._headers.setHeaderNoCheck(name, value);
+ },
+
+ //
+ // see nsIHttpResponse.processAsync
+ //
+ processAsync: function()
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._processAsync)
+ return;
+ this._ensureAlive();
+
+ dumpn("*** processing connection " + this._connection.number + " async");
+ this._processAsync = true;
+
+ /*
+ * Either the bodyOutputStream getter or this method is responsible for
+ * starting the asynchronous processor and catching writes of data to the
+ * response body of async responses as they happen, for the purpose of
+ * forwarding those writes to the actual connection's output stream.
+ * If bodyOutputStream is accessed first, calling this method will create
+ * the processor (when it first is clear that body data is to be written
+ * immediately, not buffered). If this method is called first, accessing
+ * bodyOutputStream will create the processor. If only this method is
+ * called, we'll write nothing, neither headers nor the nonexistent body,
+ * until finish() is called. Since that delay is easily avoided by simply
+ * getting bodyOutputStream or calling write(""), we don't worry about it.
+ */
+ if (this._bodyOutputStream && !this._asyncCopier)
+ this._startAsyncProcessor();
+ },
+
+ //
+ // see nsIHttpResponse.seizePower
+ //
+ seizePower: function()
+ {
+ if (this._processAsync)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ return;
+ this._ensureAlive();
+
+ dumpn("*** forcefully seizing power over connection " +
+ this._connection.number + "...");
+
+ // Purge any already-written data without sending it. We could as easily
+ // swap out the streams entirely, but that makes it possible to acquire and
+ // unknowingly use a stale reference, so we require there only be one of
+ // each stream ever for any response to avoid this complication.
+ if (this._asyncCopier)
+ this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
+ this._asyncCopier = null;
+ if (this._bodyOutputStream)
+ {
+ var input = new BinaryInputStream(this._bodyInputStream);
+ var avail;
+ while ((avail = input.available()) > 0)
+ input.readByteArray(avail);
+ }
+
+ this._powerSeized = true;
+ if (this._bodyOutputStream)
+ this._startAsyncProcessor();
+ },
+
+ //
+ // see nsIHttpResponse.finish
+ //
+ finish: function()
+ {
+ if (!this._processAsync && !this._powerSeized)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._finished)
+ return;
+
+ dumpn("*** finishing connection " + this._connection.number);
+ this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+ this._finished = true;
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // POST-CONSTRUCTION API (not exposed externally)
+
+ /**
+ * The HTTP version number of this, as a string (e.g. "1.1").
+ */
+ get httpVersion()
+ {
+ this._ensureAlive();
+ return this._httpVersion.toString();
+ },
+
+ /**
+ * The HTTP status code of this response, as a string of three characters per
+ * RFC 2616.
+ */
+ get httpCode()
+ {
+ this._ensureAlive();
+
+ var codeString = (this._httpCode < 10 ? "0" : "") +
+ (this._httpCode < 100 ? "0" : "") +
+ this._httpCode;
+ return codeString;
+ },
+
+ /**
+ * The description of the HTTP status code of this response, or "" if none is
+ * set.
+ */
+ get httpDescription()
+ {
+ this._ensureAlive();
+
+ return this._httpDescription;
+ },
+
+ /**
+ * The headers in this response, as an nsHttpHeaders object.
+ */
+ get headers()
+ {
+ this._ensureAlive();
+
+ return this._headers;
+ },
+
+ //
+ // see nsHttpHeaders.getHeader
+ //
+ getHeader: function(name)
+ {
+ this._ensureAlive();
+
+ return this._headers.getHeader(name);
+ },
+
+ /**
+ * Determines whether this response may be abandoned in favor of a newly
+ * constructed response. A response may be abandoned only if it is not being
+ * sent asynchronously and if raw control over it has not been taken from the
+ * server.
+ *
+ * @returns boolean
+ * true iff no data has been written to the network
+ */
+ partiallySent: function()
+ {
+ dumpn("*** partiallySent()");
+ return this._processAsync || this._powerSeized;
+ },
+
+ /**
+ * If necessary, kicks off the remaining request processing needed to be done
+ * after a request handler performs its initial work upon this response.
+ */
+ complete: function()
+ {
+ dumpn("*** complete()");
+ if (this._processAsync || this._powerSeized)
+ {
+ NS_ASSERT(this._processAsync ^ this._powerSeized,
+ "can't both send async and relinquish power");
+ return;
+ }
+
+ NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
+
+ this._startAsyncProcessor();
+
+ // Now make sure we finish processing this request!
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+ },
+
+ /**
+ * Abruptly ends processing of this response, usually due to an error in an
+ * incoming request but potentially due to a bad error handler. Since we
+ * cannot handle the error in the usual way (giving an HTTP error page in
+ * response) because data may already have been sent (or because the response
+ * might be expected to have been generated asynchronously or completely from
+ * scratch by the handler), we stop processing this response and abruptly
+ * close the connection.
+ *
+ * @param e : Error
+ * the exception which precipitated this abort, or null if no such exception
+ * was generated
+ */
+ abort: function(e)
+ {
+ dumpn("*** abort(<" + e + ">)");
+
+ // This response will be ended by the processor if one was created.
+ var copier = this._asyncCopier;
+ if (copier)
+ {
+ // We dispatch asynchronously here so that any pending writes of data to
+ // the connection will be deterministically written. This makes it easier
+ // to specify exact behavior, and it makes observable behavior more
+ // predictable for clients. Note that the correctness of this depends on
+ // callbacks in response to _waitToReadData in WriteThroughCopier
+ // happening asynchronously with respect to the actual writing of data to
+ // bodyOutputStream, as they currently do; if they happened synchronously,
+ // an event which ran before this one could write more data to the
+ // response body before we get around to canceling the copier. We have
+ // tests for this in test_seizepower.js, however, and I can't think of a
+ // way to handle both cases without removing bodyOutputStream access and
+ // moving its effective write(data, length) method onto Response, which
+ // would be slower and require more code than this anyway.
+ gThreadManager.currentThread.dispatch({
+ run: function()
+ {
+ dumpn("*** canceling copy asynchronously...");
+ copier.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ else
+ {
+ this.end();
+ }
+ },
+
+ /**
+ * Closes this response's network connection, marks the response as finished,
+ * and notifies the server handler that the request is done being processed.
+ */
+ end: function()
+ {
+ NS_ASSERT(!this._ended, "ending this response twice?!?!");
+
+ this._connection.close();
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+
+ this._finished = true;
+ this._ended = true;
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+ * Sends the status line and headers of this response if they haven't been
+ * sent and initiates the process of copying data written to this response's
+ * body to the network.
+ */
+ _startAsyncProcessor: function()
+ {
+ dumpn("*** _startAsyncProcessor()");
+
+ // Handle cases where we're being called a second time. The former case
+ // happens when this is triggered both by complete() and by processAsync(),
+ // while the latter happens when processAsync() in conjunction with sent
+ // data causes abort() to be called.
+ if (this._asyncCopier || this._ended)
+ {
+ dumpn("*** ignoring second call to _startAsyncProcessor");
+ return;
+ }
+
+ // Send headers if they haven't been sent already and should be sent, then
+ // asynchronously continue to send the body.
+ if (this._headers && !this._powerSeized)
+ {
+ this._sendHeaders();
+ return;
+ }
+
+ this._headers = null;
+ this._sendBody();
+ },
+
+ /**
+ * Signals that all modifications to the response status line and headers are
+ * complete and then sends that data over the network to the client. Once
+ * this method completes, a different response to the request that resulted
+ * in this response cannot be sent -- the only possible action in case of
+ * error is to abort the response and close the connection.
+ */
+ _sendHeaders: function()
+ {
+ dumpn("*** _sendHeaders()");
+
+ NS_ASSERT(this._headers);
+ NS_ASSERT(!this._powerSeized);
+
+ // request-line
+ var statusLine = "HTTP/" + this.httpVersion + " " +
+ this.httpCode + " " +
+ this.httpDescription + "\r\n";
+
+ // header post-processing
+
+ var headers = this._headers;
+ headers.setHeader("Connection", "close", false);
+ headers.setHeader("Server", "httpd.js", false);
+ if (!headers.hasHeader("Date"))
+ headers.setHeader("Date", toDateString(Date.now()), false);
+
+ // Any response not being processed asynchronously must have an associated
+ // Content-Length header for reasons of backwards compatibility with the
+ // initial server, which fully buffered every response before sending it.
+ // Beyond that, however, it's good to do this anyway because otherwise it's
+ // impossible to test behaviors that depend on the presence or absence of a
+ // Content-Length header.
+ if (!this._processAsync)
+ {
+ dumpn("*** non-async response, set Content-Length");
+
+ var bodyStream = this._bodyInputStream;
+ var avail = bodyStream ? bodyStream.available() : 0;
+
+ // XXX assumes stream will always report the full amount of data available
+ headers.setHeader("Content-Length", "" + avail, false);
+ }
+
+
+ // construct and send response
+ dumpn("*** header post-processing completed, sending response head...");
+
+ // request-line
+ var preambleData = [statusLine];
+
+ // headers
+ var headEnum = headers.enumerator;
+ while (headEnum.hasMoreElements())
+ {
+ var fieldName = headEnum.getNext()
+ .QueryInterface(Ci.nsISupportsString)
+ .data;
+ var values = headers.getHeaderValues(fieldName);
+ for (var i = 0, sz = values.length; i < sz; i++)
+ preambleData.push(fieldName + ": " + values[i] + "\r\n");
+ }
+
+ // end request-line/headers
+ preambleData.push("\r\n");
+
+ var preamble = preambleData.join("");
+
+ var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
+ responseHeadPipe.outputStream.write(preamble, preamble.length);
+
+ var response = this;
+ var copyObserver =
+ {
+ onStartRequest: function(request, cx)
+ {
+ dumpn("*** preamble copying started");
+ },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ dumpn("*** preamble copying complete " +
+ "[status=0x" + statusCode.toString(16) + "]");
+
+ if (!Components.isSuccessCode(statusCode))
+ {
+ dumpn("!!! header copying problems: non-success statusCode, " +
+ "ending response");
+
+ response.end();
+ }
+ else
+ {
+ response._sendBody();
+ }
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ var headerCopier = this._asyncCopier =
+ new WriteThroughCopier(responseHeadPipe.inputStream,
+ this._connection.output,
+ copyObserver, null);
+
+ responseHeadPipe.outputStream.close();
+
+ // Forbid setting any more headers or modifying the request line.
+ this._headers = null;
+ },
+
+ /**
+ * Asynchronously writes the body of the response (or the entire response, if
+ * seizePower() has been called) to the network.
+ */
+ _sendBody: function()
+ {
+ dumpn("*** _sendBody");
+
+ NS_ASSERT(!this._headers, "still have headers around but sending body?");
+
+ // If no body data was written, we're done
+ if (!this._bodyInputStream)
+ {
+ dumpn("*** empty body, response finished");
+ this.end();
+ return;
+ }
+
+ var response = this;
+ var copyObserver =
+ {
+ onStartRequest: function(request, context)
+ {
+ dumpn("*** onStartRequest");
+ },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
+
+ if (statusCode === Cr.NS_BINDING_ABORTED)
+ {
+ dumpn("*** terminating copy observer without ending the response");
+ }
+ else
+ {
+ if (!Components.isSuccessCode(statusCode))
+ dumpn("*** WARNING: non-success statusCode in onStopRequest");
+
+ response.end();
+ }
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ dumpn("*** starting async copier of body data...");
+ this._asyncCopier =
+ new WriteThroughCopier(this._bodyInputStream, this._connection.output,
+ copyObserver, null);
+ },
+
+ /** Ensures that this hasn't been ended. */
+ _ensureAlive: function()
+ {
+ NS_ASSERT(!this._ended, "not handling response lifetime correctly");
+ }
+};
+
+/**
+ * Size of the segments in the buffer used in storing response data and writing
+ * it to the socket.
+ */
+Response.SEGMENT_SIZE = 8192;
+
+/** Serves double duty in WriteThroughCopier implementation. */
+function notImplemented()
+{
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/** Returns true iff the given exception represents stream closure. */
+function streamClosed(e)
+{
+ return e === Cr.NS_BASE_STREAM_CLOSED ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED);
+}
+
+/** Returns true iff the given exception represents a blocked stream. */
+function wouldBlock(e)
+{
+ return e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK);
+}
+
+/**
+ * Copies data from source to sink as it becomes available, when that data can
+ * be written to sink without blocking.
+ *
+ * @param source : nsIAsyncInputStream
+ * the stream from which data is to be read
+ * @param sink : nsIAsyncOutputStream
+ * the stream to which data is to be copied
+ * @param observer : nsIRequestObserver
+ * an observer which will be notified when the copy starts and finishes
+ * @param context : nsISupports
+ * context passed to observer when notified of start/stop
+ * @throws NS_ERROR_NULL_POINTER
+ * if source, sink, or observer are null
+ */
+function WriteThroughCopier(source, sink, observer, context)
+{
+ if (!source || !sink || !observer)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ /** Stream from which data is being read. */
+ this._source = source;
+
+ /** Stream to which data is being written. */
+ this._sink = sink;
+
+ /** Observer watching this copy. */
+ this._observer = observer;
+
+ /** Context for the observer watching this. */
+ this._context = context;
+
+ /**
+ * True iff this is currently being canceled (cancel has been called, the
+ * callback may not yet have been made).
+ */
+ this._canceled = false;
+
+ /**
+ * False until all data has been read from input and written to output, at
+ * which point this copy is completed and cancel() is asynchronously called.
+ */
+ this._completed = false;
+
+ /** Required by nsIRequest, meaningless. */
+ this.loadFlags = 0;
+ /** Required by nsIRequest, meaningless. */
+ this.loadGroup = null;
+ /** Required by nsIRequest, meaningless. */
+ this.name = "response-body-copy";
+
+ /** Status of this request. */
+ this.status = Cr.NS_OK;
+
+ /** Arrays of byte strings waiting to be written to output. */
+ this._pendingData = [];
+
+ // start copying
+ try
+ {
+ observer.onStartRequest(this, context);
+ this._waitToReadData();
+ this._waitForSinkClosure();
+ }
+ catch (e)
+ {
+ dumpn("!!! error starting copy: " + e +
+ ("lineNumber" in e ? ", line " + e.lineNumber : ""));
+ dumpn(e.stack);
+ this.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+}
+WriteThroughCopier.prototype =
+{
+ /* nsISupports implementation */
+
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback) ||
+ iid.equals(Ci.nsIRequest) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+ * Receives a more-data-in-input notification and writes the corresponding
+ * data to the output.
+ *
+ * @param input : nsIAsyncInputStream
+ * the input stream on whose data we have been waiting
+ */
+ onInputStreamReady: function(input)
+ {
+ if (this._source === null)
+ return;
+
+ dumpn("*** onInputStreamReady");
+
+ //
+ // Ordinarily we'll read a non-zero amount of data from input, queue it up
+ // to be written and then wait for further callbacks. The complications in
+ // this method are the cases where we deviate from that behavior when errors
+ // occur or when copying is drawing to a finish.
+ //
+ // The edge cases when reading data are:
+ //
+ // Zero data is read
+ // If zero data was read, we're at the end of available data, so we can
+ // should stop reading and move on to writing out what we have (or, if
+ // we've already done that, onto notifying of completion).
+ // A stream-closed exception is thrown
+ // This is effectively a less kind version of zero data being read; the
+ // only difference is that we notify of completion with that result
+ // rather than with NS_OK.
+ // Some other exception is thrown
+ // This is the least kind result. We don't know what happened, so we
+ // act as though the stream closed except that we notify of completion
+ // with the result NS_ERROR_UNEXPECTED.
+ //
+
+ var bytesWanted = 0, bytesConsumed = -1;
+ try
+ {
+ input = new BinaryInputStream(input);
+
+ bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
+ dumpn("*** input wanted: " + bytesWanted);
+
+ if (bytesWanted > 0)
+ {
+ var data = input.readByteArray(bytesWanted);
+ bytesConsumed = data.length;
+ this._pendingData.push(String.fromCharCode.apply(String, data));
+ }
+
+ dumpn("*** " + bytesConsumed + " bytes read");
+
+ // Handle the zero-data edge case in the same place as all other edge
+ // cases are handled.
+ if (bytesWanted === 0)
+ throw Cr.NS_BASE_STREAM_CLOSED;
+ }
+ catch (e)
+ {
+ if (streamClosed(e))
+ {
+ dumpn("*** input stream closed");
+ e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
+ }
+ else
+ {
+ dumpn("!!! unexpected error reading from input, canceling: " + e);
+ e = Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ this._doneReadingSource(e);
+ return;
+ }
+
+ var pendingData = this._pendingData;
+
+ NS_ASSERT(bytesConsumed > 0);
+ NS_ASSERT(pendingData.length > 0, "no pending data somehow?");
+ NS_ASSERT(pendingData[pendingData.length - 1].length > 0,
+ "buffered zero bytes of data?");
+
+ NS_ASSERT(this._source !== null);
+
+ // Reading has gone great, and we've gotten data to write now. What if we
+ // don't have a place to write that data, because output went away just
+ // before this read? Drop everything on the floor, including new data, and
+ // cancel at this point.
+ if (this._sink === null)
+ {
+ pendingData.length = 0;
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we've read the data, and we know we have a place to write it. We
+ // need to queue up the data to be written, but *only* if none is queued
+ // already -- if data's already queued, the code that actually writes the
+ // data will make sure to wait on unconsumed pending data.
+ try
+ {
+ if (pendingData.length === 1)
+ this._waitToWriteData();
+ }
+ catch (e)
+ {
+ dumpn("!!! error waiting to write data just read, swallowing and " +
+ "writing only what we already have: " + e);
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Whee! We successfully read some data, and it's successfully queued up to
+ // be written. All that remains now is to wait for more data to read.
+ try
+ {
+ this._waitToReadData();
+ }
+ catch (e)
+ {
+ dumpn("!!! error waiting to read more data: " + e);
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ }
+ },
+
+
+ // NSIOUTPUTSTREAMCALLBACK
+
+ /**
+ * Callback when data may be written to the output stream without blocking, or
+ * when the output stream has been closed.
+ *
+ * @param output : nsIAsyncOutputStream
+ * the output stream on whose writability we've been waiting, also known as
+ * this._sink
+ */
+ onOutputStreamReady: function(output)
+ {
+ if (this._sink === null)
+ return;
+
+ dumpn("*** onOutputStreamReady");
+
+ var pendingData = this._pendingData;
+ if (pendingData.length === 0)
+ {
+ // There's no pending data to write. The only way this can happen is if
+ // we're waiting on the output stream's closure, so we can respond to a
+ // copying failure as quickly as possible (rather than waiting for data to
+ // be available to read and then fail to be copied). Therefore, we must
+ // be done now -- don't bother to attempt to write anything and wrap
+ // things up.
+ dumpn("!!! output stream closed prematurely, ending copy");
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+
+ NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?");
+
+ //
+ // Write out the first pending quantum of data. The possible errors here
+ // are:
+ //
+ // The write might fail because we can't write that much data
+ // Okay, we've written what we can now, so re-queue what's left and
+ // finish writing it out later.
+ // The write failed because the stream was closed
+ // Discard pending data that we can no longer write, stop reading, and
+ // signal that copying finished.
+ // Some other error occurred.
+ // Same as if the stream were closed, but notify with the status
+ // NS_ERROR_UNEXPECTED so the observer knows something was wonky.
+ //
+
+ try
+ {
+ var quantum = pendingData[0];
+
+ // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
+ // undefined behavior! We're only using this because writeByteArray
+ // is unusably broken for asynchronous output streams; see bug 532834
+ // for details.
+ var bytesWritten = output.write(quantum, quantum.length);
+ if (bytesWritten === quantum.length)
+ pendingData.shift();
+ else
+ pendingData[0] = quantum.substring(bytesWritten);
+
+ dumpn("*** wrote " + bytesWritten + " bytes of data");
+ }
+ catch (e)
+ {
+ if (wouldBlock(e))
+ {
+ NS_ASSERT(pendingData.length > 0,
+ "stream-blocking exception with no data to write?");
+ NS_ASSERT(pendingData[0].length > 0,
+ "stream-blocking exception with empty quantum?");
+ this._waitToWriteData();
+ return;
+ }
+
+ if (streamClosed(e))
+ dumpn("!!! output stream prematurely closed, signaling error...");
+ else
+ dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // The day is ours! Quantum written, now let's see if we have more data
+ // still to write.
+ try
+ {
+ if (pendingData.length > 0)
+ {
+ this._waitToWriteData();
+ return;
+ }
+ }
+ catch (e)
+ {
+ dumpn("!!! unexpected error waiting to write pending data: " + e);
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we have no more pending data to write -- but might we get more in
+ // the future?
+ if (this._source !== null)
+ {
+ /*
+ * If we might, then wait for the output stream to be closed. (We wait
+ * only for closure because we have no data to write -- and if we waited
+ * for a specific amount of data, we would get repeatedly notified for no
+ * reason if over time the output stream permitted more and more data to
+ * be written to it without blocking.)
+ */
+ this._waitForSinkClosure();
+ }
+ else
+ {
+ /*
+ * On the other hand, if we can't have more data because the input
+ * stream's gone away, then it's time to notify of copy completion.
+ * Victory!
+ */
+ this._sink = null;
+ this._cancelOrDispatchCancelCallback(Cr.NS_OK);
+ }
+ },
+
+
+ // NSIREQUEST
+
+ /** Returns true if the cancel observer hasn't been notified yet. */
+ isPending: function()
+ {
+ return !this._completed;
+ },
+
+ /** Not implemented, don't use! */
+ suspend: notImplemented,
+ /** Not implemented, don't use! */
+ resume: notImplemented,
+
+ /**
+ * Cancels data reading from input, asynchronously writes out any pending
+ * data, and causes the observer to be notified with the given error code when
+ * all writing has finished.
+ *
+ * @param status : nsresult
+ * the status to pass to the observer when data copying has been canceled
+ */
+ cancel: function(status)
+ {
+ dumpn("*** cancel(" + status.toString(16) + ")");
+
+ if (this._canceled)
+ {
+ dumpn("*** suppressing a late cancel");
+ return;
+ }
+
+ this._canceled = true;
+ this.status = status;
+
+ // We could be in the middle of absolutely anything at this point. Both
+ // input and output might still be around, we might have pending data to
+ // write, and in general we know nothing about the state of the world. We
+ // therefore must assume everything's in progress and take everything to its
+ // final steady state (or so far as it can go before we need to finish
+ // writing out remaining data).
+
+ this._doneReadingSource(status);
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+ * Stop reading input if we haven't already done so, passing e as the status
+ * when closing the stream, and kick off a copy-completion notice if no more
+ * data remains to be written.
+ *
+ * @param e : nsresult
+ * the status to be used when closing the input stream
+ */
+ _doneReadingSource: function(e)
+ {
+ dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");
+
+ this._finishSource(e);
+ if (this._pendingData.length === 0)
+ this._sink = null;
+ else
+ NS_ASSERT(this._sink !== null, "null output?");
+
+ // If we've written out all data read up to this point, then it's time to
+ // signal completion.
+ if (this._sink === null)
+ {
+ NS_ASSERT(this._pendingData.length === 0, "pending data still?");
+ this._cancelOrDispatchCancelCallback(e);
+ }
+ },
+
+ /**
+ * Stop writing output if we haven't already done so, discard any data that
+ * remained to be sent, close off input if it wasn't already closed, and kick
+ * off a copy-completion notice.
+ *
+ * @param e : nsresult
+ * the status to be used when closing input if it wasn't already closed
+ */
+ _doneWritingToSink: function(e)
+ {
+ dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");
+
+ this._pendingData.length = 0;
+ this._sink = null;
+ this._doneReadingSource(e);
+ },
+
+ /**
+ * Completes processing of this copy: either by canceling the copy if it
+ * hasn't already been canceled using the provided status, or by dispatching
+ * the cancel callback event (with the originally provided status, of course)
+ * if it already has been canceled.
+ *
+ * @param status : nsresult
+ * the status code to use to cancel this, if this hasn't already been
+ * canceled
+ */
+ _cancelOrDispatchCancelCallback: function(status)
+ {
+ dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");
+
+ NS_ASSERT(this._source === null, "should have finished input");
+ NS_ASSERT(this._sink === null, "should have finished output");
+ NS_ASSERT(this._pendingData.length === 0, "should have no pending data");
+
+ if (!this._canceled)
+ {
+ this.cancel(status);
+ return;
+ }
+
+ var self = this;
+ var event =
+ {
+ run: function()
+ {
+ dumpn("*** onStopRequest async callback");
+
+ self._completed = true;
+ try
+ {
+ self._observer.onStopRequest(self, self._context, self.status);
+ }
+ catch (e)
+ {
+ NS_ASSERT(false,
+ "how are we throwing an exception here? we control " +
+ "all the callers! " + e);
+ }
+ }
+ };
+
+ gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ /**
+ * Kicks off another wait for more data to be available from the input stream.
+ */
+ _waitToReadData: function()
+ {
+ dumpn("*** _waitToReadData");
+ this._source.asyncWait(this, 0, Response.SEGMENT_SIZE,
+ gThreadManager.mainThread);
+ },
+
+ /**
+ * Kicks off another wait until data can be written to the output stream.
+ */
+ _waitToWriteData: function()
+ {
+ dumpn("*** _waitToWriteData");
+
+ var pendingData = this._pendingData;
+ NS_ASSERT(pendingData.length > 0, "no pending data to write?");
+ NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?");
+
+ this._sink.asyncWait(this, 0, pendingData[0].length,
+ gThreadManager.mainThread);
+ },
+
+ /**
+ * Kicks off a wait for the sink to which data is being copied to be closed.
+ * We wait for stream closure when we don't have any data to be copied, rather
+ * than waiting to write a specific amount of data. We can't wait to write
+ * data because the sink might be infinitely writable, and if no data appears
+ * in the source for a long time we might have to spin quite a bit waiting to
+ * write, waiting to write again, &c. Waiting on stream closure instead means
+ * we'll get just one notification if the sink dies. Note that when data
+ * starts arriving from the sink we'll resume waiting for data to be written,
+ * dropping this closure-only callback entirely.
+ */
+ _waitForSinkClosure: function()
+ {
+ dumpn("*** _waitForSinkClosure");
+
+ this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0,
+ gThreadManager.mainThread);
+ },
+
+ /**
+ * Closes input with the given status, if it hasn't already been closed;
+ * otherwise a no-op.
+ *
+ * @param status : nsresult
+ * status code use to close the source stream if necessary
+ */
+ _finishSource: function(status)
+ {
+ dumpn("*** _finishSource(" + status.toString(16) + ")");
+
+ if (this._source !== null)
+ {
+ this._source.closeWithStatus(status);
+ this._source = null;
+ }
+ }
+};
+
+
+/**
+ * A container for utility functions used with HTTP headers.
+ */
+const headerUtils =
+{
+ /**
+ * Normalizes fieldName (by converting it to lowercase) and ensures it is a
+ * valid header field name (although not necessarily one specified in RFC
+ * 2616).
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not match the field-name production in RFC 2616
+ * @returns string
+ * fieldName converted to lowercase if it is a valid header, for characters
+ * where case conversion is possible
+ */
+ normalizeFieldName: function(fieldName)
+ {
+ if (fieldName == "")
+ {
+ dumpn("*** Empty fieldName");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ for (var i = 0, sz = fieldName.length; i < sz; i++)
+ {
+ if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])
+ {
+ dumpn(fieldName + " is not a valid header field name!");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return fieldName.toLowerCase();
+ },
+
+ /**
+ * Ensures that fieldValue is a valid header field value (although not
+ * necessarily as specified in RFC 2616 if the corresponding field name is
+ * part of the HTTP protocol), normalizes the value if it is, and
+ * returns the normalized value.
+ *
+ * @param fieldValue : string
+ * a value to be normalized as an HTTP header field value
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldValue does not match the field-value production in RFC 2616
+ * @returns string
+ * fieldValue as a normalized HTTP header field value
+ */
+ normalizeFieldValue: function(fieldValue)
+ {
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ // TEXT = <any OCTET except CTLs,
+ // but including LWS>
+ // LWS = [CRLF] 1*( SP | HT )
+ //
+ // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ // qdtext = <any TEXT except <">>
+ // quoted-pair = "\" CHAR
+ // CHAR = <any US-ASCII character (octets 0 - 127)>
+
+ // Any LWS that occurs between field-content MAY be replaced with a single
+ // SP before interpreting the field value or forwarding the message
+ // downstream (section 4.2); we replace 1*LWS with a single SP
+ var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");
+
+ // remove leading/trailing LWS (which has been converted to SP)
+ val = val.replace(/^ +/, "").replace(/ +$/, "");
+
+ // that should have taken care of all CTLs, so val should contain no CTLs
+ dumpn("*** Normalized value: '" + val + "'");
+ for (var i = 0, len = val.length; i < len; i++)
+ if (isCTL(val.charCodeAt(i)))
+ {
+ dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
+ // normalize, however, so this can be construed as a tightening of the
+ // spec and not entirely as a bug
+ return val;
+ }
+};
+
+
+
+/**
+ * Converts the given string into a string which is safe for use in an HTML
+ * context.
+ *
+ * @param str : string
+ * the string to make HTML-safe
+ * @returns string
+ * an HTML-safe version of str
+ */
+function htmlEscape(str)
+{
+ // this is naive, but it'll work
+ var s = "";
+ for (var i = 0; i < str.length; i++)
+ s += "&#" + str.charCodeAt(i) + ";";
+ return s;
+}
+
+
+/**
+ * Constructs an object representing an HTTP version (see section 3.1).
+ *
+ * @param versionString
+ * a string of the form "#.#", where # is an non-negative decimal integer with
+ * or without leading zeros
+ * @throws
+ * if versionString does not specify a valid HTTP version number
+ */
+function nsHttpVersion(versionString)
+{
+ var matches = /^(\d+)\.(\d+)$/.exec(versionString);
+ if (!matches)
+ throw "Not a valid HTTP version!";
+
+ /** The major version number of this, as a number. */
+ this.major = parseInt(matches[1], 10);
+
+ /** The minor version number of this, as a number. */
+ this.minor = parseInt(matches[2], 10);
+
+ if (isNaN(this.major) || isNaN(this.minor) ||
+ this.major < 0 || this.minor < 0)
+ throw "Not a valid HTTP version!";
+}
+nsHttpVersion.prototype =
+{
+ /**
+ * Returns the standard string representation of the HTTP version represented
+ * by this (e.g., "1.1").
+ */
+ toString: function ()
+ {
+ return this.major + "." + this.minor;
+ },
+
+ /**
+ * Returns true if this represents the same HTTP version as otherVersion,
+ * false otherwise.
+ *
+ * @param otherVersion : nsHttpVersion
+ * the version to compare against this
+ */
+ equals: function (otherVersion)
+ {
+ return this.major == otherVersion.major &&
+ this.minor == otherVersion.minor;
+ },
+
+ /** True if this >= otherVersion, false otherwise. */
+ atLeast: function(otherVersion)
+ {
+ return this.major > otherVersion.major ||
+ (this.major == otherVersion.major &&
+ this.minor >= otherVersion.minor);
+ }
+};
+
+nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
+nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");
+
+
+/**
+ * An object which stores HTTP headers for a request or response.
+ *
+ * Note that since headers are case-insensitive, this object converts headers to
+ * lowercase before storing them. This allows the getHeader and hasHeader
+ * methods to work correctly for any case of a header, but it means that the
+ * values returned by .enumerator may not be equal case-sensitively to the
+ * values passed to setHeader when adding headers to this.
+ */
+function nsHttpHeaders()
+{
+ /**
+ * A hash of headers, with header field names as the keys and header field
+ * values as the values. Header field names are case-insensitive, but upon
+ * insertion here they are converted to lowercase. Header field values are
+ * normalized upon insertion to contain no leading or trailing whitespace.
+ *
+ * Note also that per RFC 2616, section 4.2, two headers with the same name in
+ * a message may be treated as one header with the same field name and a field
+ * value consisting of the separate field values joined together with a "," in
+ * their original order. This hash stores multiple headers with the same name
+ * in this manner.
+ */
+ this._headers = {};
+}
+nsHttpHeaders.prototype =
+{
+ /**
+ * Sets the header represented by name and value in this.
+ *
+ * @param name : string
+ * the header name
+ * @param value : string
+ * the header value
+ * @throws NS_ERROR_INVALID_ARG
+ * if name or value is not a valid header component
+ */
+ setHeader: function(fieldName, fieldValue, merge)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ var value = headerUtils.normalizeFieldValue(fieldValue);
+
+ // The following three headers are stored as arrays because their real-world
+ // syntax prevents joining individual headers into a single header using
+ // ",". See also <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
+ if (merge && name in this._headers)
+ {
+ if (name === "www-authenticate" ||
+ name === "proxy-authenticate" ||
+ name === "set-cookie")
+ {
+ this._headers[name].push(value);
+ }
+ else
+ {
+ this._headers[name][0] += "," + value;
+ NS_ASSERT(this._headers[name].length === 1,
+ "how'd a non-special header have multiple values?")
+ }
+ }
+ else
+ {
+ this._headers[name] = [value];
+ }
+ },
+
+ setHeaderNoCheck: function(fieldName, fieldValue)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ var value = headerUtils.normalizeFieldValue(fieldValue);
+ if (name in this._headers) {
+ this._headers[name].push(fieldValue);
+ } else {
+ this._headers[name] = [fieldValue];
+ }
+ },
+
+ /**
+ * Returns the value for the header specified by this.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the given header does not exist in this
+ * @returns string
+ * the field value for the given header, possibly with non-semantic changes
+ * (i.e., leading/trailing whitespace stripped, whitespace runs replaced
+ * with spaces, etc.) at the option of the implementation; multiple
+ * instances of the header will be combined with a comma, except for
+ * the three headers noted in the description of getHeaderValues
+ */
+ getHeader: function(fieldName)
+ {
+ return this.getHeaderValues(fieldName).join("\n");
+ },
+
+ /**
+ * Returns the value for the header specified by fieldName as an array.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the given header does not exist in this
+ * @returns [string]
+ * an array of all the header values in this for the given
+ * header name. Header values will generally be collapsed
+ * into a single header by joining all header values together
+ * with commas, but certain headers (Proxy-Authenticate,
+ * WWW-Authenticate, and Set-Cookie) violate the HTTP spec
+ * and cannot be collapsed in this manner. For these headers
+ * only, the returned array may contain multiple elements if
+ * that header has been added more than once.
+ */
+ getHeaderValues: function(fieldName)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+
+ if (name in this._headers)
+ return this._headers[name];
+ else
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ },
+
+ /**
+ * Returns true if a header with the given field name exists in this, false
+ * otherwise.
+ *
+ * @param fieldName : string
+ * the field name whose existence is to be determined in this
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @returns boolean
+ * true if the header's present, false otherwise
+ */
+ hasHeader: function(fieldName)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ return (name in this._headers);
+ },
+
+ /**
+ * Returns a new enumerator over the field names of the headers in this, as
+ * nsISupportsStrings. The names returned will be in lowercase, regardless of
+ * how they were input using setHeader (header names are case-insensitive per
+ * RFC 2616).
+ */
+ get enumerator()
+ {
+ var headers = [];
+ for (var i in this._headers)
+ {
+ var supports = new SupportsString();
+ supports.data = i;
+ headers.push(supports);
+ }
+
+ return new nsSimpleEnumerator(headers);
+ }
+};
+
+
+/**
+ * Constructs an nsISimpleEnumerator for the given array of items.
+ *
+ * @param items : Array
+ * the items, which must all implement nsISupports
+ */
+function nsSimpleEnumerator(items)
+{
+ this._items = items;
+ this._nextIndex = 0;
+}
+nsSimpleEnumerator.prototype =
+{
+ hasMoreElements: function()
+ {
+ return this._nextIndex < this._items.length;
+ },
+ getNext: function()
+ {
+ if (!this.hasMoreElements())
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ return this._items[this._nextIndex++];
+ },
+ QueryInterface: function(aIID)
+ {
+ if (Ci.nsISimpleEnumerator.equals(aIID) ||
+ Ci.nsISupports.equals(aIID))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+
+/**
+ * A representation of the data in an HTTP request.
+ *
+ * @param port : uint
+ * the port on which the server receiving this request runs
+ */
+function Request(port)
+{
+ /** Method of this request, e.g. GET or POST. */
+ this._method = "";
+
+ /** Path of the requested resource; empty paths are converted to '/'. */
+ this._path = "";
+
+ /** Query string, if any, associated with this request (not including '?'). */
+ this._queryString = "";
+
+ /** Scheme of requested resource, usually http, always lowercase. */
+ this._scheme = "http";
+
+ /** Hostname on which the requested resource resides. */
+ this._host = undefined;
+
+ /** Port number over which the request was received. */
+ this._port = port;
+
+ var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
+
+ /** Stream from which data in this request's body may be read. */
+ this._bodyInputStream = bodyPipe.inputStream;
+
+ /** Stream to which data in this request's body is written. */
+ this._bodyOutputStream = bodyPipe.outputStream;
+
+ /**
+ * The headers in this request.
+ */
+ this._headers = new nsHttpHeaders();
+
+ /**
+ * For the addition of ad-hoc properties and new functionality without having
+ * to change nsIHttpRequest every time; currently lazily created, as its only
+ * use is in directory listings.
+ */
+ this._bag = null;
+}
+Request.prototype =
+{
+ // SERVER METADATA
+
+ //
+ // see nsIHttpRequest.scheme
+ //
+ get scheme()
+ {
+ return this._scheme;
+ },
+
+ //
+ // see nsIHttpRequest.host
+ //
+ get host()
+ {
+ return this._host;
+ },
+
+ //
+ // see nsIHttpRequest.port
+ //
+ get port()
+ {
+ return this._port;
+ },
+
+ // REQUEST LINE
+
+ //
+ // see nsIHttpRequest.method
+ //
+ get method()
+ {
+ return this._method;
+ },
+
+ //
+ // see nsIHttpRequest.httpVersion
+ //
+ get httpVersion()
+ {
+ return this._httpVersion.toString();
+ },
+
+ //
+ // see nsIHttpRequest.path
+ //
+ get path()
+ {
+ return this._path;
+ },
+
+ //
+ // see nsIHttpRequest.queryString
+ //
+ get queryString()
+ {
+ return this._queryString;
+ },
+
+ // HEADERS
+
+ //
+ // see nsIHttpRequest.getHeader
+ //
+ getHeader: function(name)
+ {
+ return this._headers.getHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.hasHeader
+ //
+ hasHeader: function(name)
+ {
+ return this._headers.hasHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get headers()
+ {
+ return this._headers.enumerator;
+ },
+
+ //
+ // see nsIPropertyBag.enumerator
+ //
+ get enumerator()
+ {
+ this._ensurePropertyBag();
+ return this._bag.enumerator;
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get bodyInputStream()
+ {
+ return this._bodyInputStream;
+ },
+
+ //
+ // see nsIPropertyBag.getProperty
+ //
+ getProperty: function(name)
+ {
+ this._ensurePropertyBag();
+ return this._bag.getProperty(name);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /** Ensures a property bag has been created for ad-hoc behaviors. */
+ _ensurePropertyBag: function()
+ {
+ if (!this._bag)
+ this._bag = new WritablePropertyBag();
+ }
+};
+
+
+// XPCOM trappings
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
+
+/**
+ * Creates a new HTTP server listening for loopback traffic on the given port,
+ * starts it, and runs the server until the server processes a shutdown request,
+ * spinning an event loop so that events posted by the server's socket are
+ * processed.
+ *
+ * This method is primarily intended for use in running this script from within
+ * xpcshell and running a functional HTTP server without having to deal with
+ * non-essential details.
+ *
+ * Note that running multiple servers using variants of this method probably
+ * doesn't work, simply due to how the internal event loop is spun and stopped.
+ *
+ * @note
+ * This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);
+ * you should use this server as a component in Mozilla 1.8.
+ * @param port
+ * the port on which the server will run, or -1 if there exists no preference
+ * for a specific port; note that attempting to use some values for this
+ * parameter (particularly those below 1024) may cause this method to throw or
+ * may result in the server being prematurely shut down
+ * @param basePath
+ * a local directory from which requests will be served (i.e., if this is
+ * "/home/jwalden/" then a request to /index.html will load
+ * /home/jwalden/index.html); if this is omitted, only the default URLs in
+ * this server implementation will be functional
+ */
+function server(port, basePath)
+{
+ if (basePath)
+ {
+ var lp = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ lp.initWithPath(basePath);
+ }
+
+ // if you're running this, you probably want to see debugging info
+ DEBUG = true;
+
+ var srv = new nsHttpServer();
+ if (lp)
+ srv.registerDirectory("/", lp);
+ srv.registerContentType("sjs", SJS_TYPE);
+ srv.identity.setPrimary("http", "localhost", port);
+ srv.start(port);
+
+ var thread = gThreadManager.currentThread;
+ while (!srv.isStopped())
+ thread.processNextEvent(true);
+
+ // get rid of any pending requests
+ while (thread.hasPendingEvents())
+ thread.processNextEvent(true);
+
+ DEBUG = false;
+}
diff --git a/netwerk/test/httpserver/httpd.manifest b/netwerk/test/httpserver/httpd.manifest
new file mode 100644
index 000000000..745e5d367
--- /dev/null
+++ b/netwerk/test/httpserver/httpd.manifest
@@ -0,0 +1,3 @@
+component {54ef6f81-30af-4b1d-ac55-8ba811293e41} httpd.js
+contract @mozilla.org/server/jshttp;1 {54ef6f81-30af-4b1d-ac55-8ba811293e41}
+interfaces test_necko.xpt
diff --git a/netwerk/test/httpserver/moz.build b/netwerk/test/httpserver/moz.build
new file mode 100644
index 000000000..f8199d18e
--- /dev/null
+++ b/netwerk/test/httpserver/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ 'nsIHttpServer.idl',
+]
+
+XPIDL_MODULE = 'test_necko'
+
+# Don't add our test-only .xpt files to the normal manifests
+XPIDL_NO_MANIFEST = True
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
+
+EXTRA_COMPONENTS += [
+ 'httpd.js',
+ 'httpd.manifest',
+]
+
+TESTING_JS_MODULES += [
+ 'httpd.js',
+]
diff --git a/netwerk/test/httpserver/nsIHttpServer.idl b/netwerk/test/httpserver/nsIHttpServer.idl
new file mode 100644
index 000000000..97192a2d6
--- /dev/null
+++ b/netwerk/test/httpserver/nsIHttpServer.idl
@@ -0,0 +1,620 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIFile;
+interface nsIOutputStream;
+interface nsISimpleEnumerator;
+
+interface nsIHttpServer;
+interface nsIHttpServerStoppedCallback;
+interface nsIHttpRequestHandler;
+interface nsIHttpRequest;
+interface nsIHttpResponse;
+interface nsIHttpServerIdentity;
+
+/**
+ * An interface which represents an HTTP server.
+ */
+[scriptable, uuid(cea8812e-faa6-4013-9396-f9936cbb74ec)]
+interface nsIHttpServer : nsISupports
+{
+ /**
+ * Starts up this server, listening upon the given port.
+ *
+ * @param port
+ * the port upon which listening should happen, or -1 if no specific port is
+ * desired
+ * @throws NS_ERROR_ALREADY_INITIALIZED
+ * if this server is already started
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the server is not started and cannot be started on the desired port
+ * (perhaps because the port is already in use or because the process does
+ * not have privileges to do so)
+ * @note
+ * Behavior is undefined if this method is called after stop() has been
+ * called on this but before the provided callback function has been
+ * called.
+ */
+ void start(in long port);
+
+ /**
+ * Shuts down this server if it is running (including the period of time after
+ * stop() has been called but before the provided callback has been called).
+ *
+ * @param callback
+ * an asynchronous callback used to notify the user when this server is
+ * stopped and all pending requests have been fully served
+ * @throws NS_ERROR_NULL_POINTER
+ * if callback is null
+ * @throws NS_ERROR_UNEXPECTED
+ * if this server is not running
+ */
+ void stop(in nsIHttpServerStoppedCallback callback);
+
+ /**
+ * Associates the local file represented by the string file with all requests
+ * which match request.
+ *
+ * @param path
+ * the path which is to be mapped to the given file; must begin with "/" and
+ * be a valid URI path (i.e., no query string, hash reference, etc.)
+ * @param file
+ * the file to serve for the given path, or null to remove any mapping that
+ * might exist; this file must exist for the lifetime of the server
+ */
+ void registerFile(in string path, in nsIFile file);
+
+ /**
+ * Registers a custom path handler.
+ *
+ * @param path
+ * the path on the server (beginning with a "/") which is to be handled by
+ * handler; this path must not include a query string or hash component; it
+ * also should usually be canonicalized, since most browsers will do so
+ * before sending otherwise-matching requests
+ * @param handler
+ * an object which will handle any requests for the given path, or null to
+ * remove any existing handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500 response
+ * will be returned
+ * @throws NS_ERROR_INVALID_ARG
+ * if path does not begin with a "/"
+ */
+ void registerPathHandler(in string path, in nsIHttpRequestHandler handler);
+
+ /**
+ * Registers a custom prefix handler.
+ *
+ * @param prefix
+ * the path on the server (beginning and ending with "/") which is to be
+ * handled by handler; this path must not include a query string or hash
+ * component. All requests that start with this prefix will be directed to
+ * the given handler.
+ * @param handler
+ * an object which will handle any requests for the given path, or null to
+ * remove any existing handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500 response
+ * will be returned
+ * @throws NS_ERROR_INVALID_ARG
+ * if path does not begin with a "/" or does not end with a "/"
+ */
+ void registerPrefixHandler(in string prefix, in nsIHttpRequestHandler handler);
+
+ /**
+ * Registers a custom error page handler.
+ *
+ * @param code
+ * the error code which is to be handled by handler
+ * @param handler
+ * an object which will handle any requests which generate the given status
+ * code, or null to remove any existing handler. If the handler throws an
+ * exception during server operation, fallback is to the genericized error
+ * handler (the x00 version), then to 500, using a user-defined error
+ * handler if one exists or the server default handler otherwise. Fallback
+ * will never occur from a user-provided handler that throws to the same
+ * handler as provided by the server, e.g. a throwing user 404 falls back to
+ * 400, not a server-provided 404 that might not throw.
+ * @note
+ * If the error handler handles HTTP 500 and throws, behavior is undefined.
+ */
+ void registerErrorHandler(in unsigned long code, in nsIHttpRequestHandler handler);
+
+ /**
+ * Maps all requests to paths beneath path to the corresponding file beneath
+ * dir.
+ *
+ * @param path
+ * the absolute path on the server against which requests will be served
+ * from dir (e.g., "/", "/foo/", etc.); must begin and end with a forward
+ * slash
+ * @param dir
+ * the directory to be used to serve all requests for paths underneath path
+ * (except those further overridden by another, deeper path registered with
+ * another directory); if null, any current mapping for the given path is
+ * removed
+ * @throws NS_ERROR_INVALID_ARG
+ * if dir is non-null and does not exist or is not a directory, or if path
+ * does not begin with and end with a forward slash
+ */
+ void registerDirectory(in string path, in nsIFile dir);
+
+ /**
+ * Associates files with the given extension with the given Content-Type when
+ * served by this server, in the absence of any file-specific information
+ * about the desired Content-Type. If type is empty, removes any extant
+ * mapping, if one is present.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if the given type is not a valid header field value, i.e. if it doesn't
+ * match the field-value production in RFC 2616
+ * @note
+ * No syntax checking is done of the given type, beyond ensuring that it is
+ * a valid header field value. Behavior when not given a string matching
+ * the media-type production in RFC 2616 section 3.7 is undefined.
+ * Implementations may choose to define specific behavior for types which do
+ * not match the production, such as for CGI functionality.
+ * @note
+ * Implementations MAY treat type as a trusted argument; users who fail to
+ * generate this string from trusted data risk security vulnerabilities.
+ */
+ void registerContentType(in string extension, in string type);
+
+ /**
+ * Sets the handler used to display the contents of a directory if
+ * the directory contains no index page.
+ *
+ * @param handler
+ * an object which will handle any requests for directories which
+ * do not contain index pages, or null to reset to the default
+ * index handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500
+ * response will be returned. An nsIFile corresponding to the
+ * directory is available from the metadata object passed to the
+ * handler, under the key "directory".
+ */
+ void setIndexHandler(in nsIHttpRequestHandler handler);
+
+ /** Represents the locations at which this server is reachable. */
+ readonly attribute nsIHttpServerIdentity identity;
+
+ /**
+ * Retrieves the string associated with the given key in this, for the given
+ * path's saved state. All keys are initially associated with the empty
+ * string.
+ */
+ AString getState(in AString path, in AString key);
+
+ /**
+ * Sets the string associated with the given key in this, for the given path's
+ * saved state.
+ */
+ void setState(in AString path, in AString key, in AString value);
+
+ /**
+ * Retrieves the string associated with the given key in this, in
+ * entire-server saved state. All keys are initially associated with the
+ * empty string.
+ */
+ AString getSharedState(in AString key);
+
+ /**
+ * Sets the string associated with the given key in this, in entire-server
+ * saved state.
+ */
+ void setSharedState(in AString key, in AString value);
+
+ /**
+ * Retrieves the object associated with the given key in this in
+ * object-valued saved state. All keys are initially associated with null.
+ */
+ nsISupports getObjectState(in AString key);
+
+ /**
+ * Sets the object associated with the given key in this in object-valued
+ * saved state. The value may be null.
+ */
+ void setObjectState(in AString key, in nsISupports value);
+};
+
+/**
+ * An interface through which a notification of the complete stopping (socket
+ * closure, in-flight requests all fully served and responded to) of an HTTP
+ * server may be received.
+ */
+[scriptable, function, uuid(925a6d33-9937-4c63-abe1-a1c56a986455)]
+interface nsIHttpServerStoppedCallback : nsISupports
+{
+ /** Called when the corresponding server has been fully stopped. */
+ void onStopped();
+};
+
+/**
+ * Represents a set of names for a server, one of which is the primary name for
+ * the server and the rest of which are secondary. By default every server will
+ * contain ("http", "localhost", port) and ("http", "127.0.0.1", port) as names,
+ * where port is what was provided to the corresponding server when started;
+ * however, except for their being removed when the corresponding server stops
+ * they have no special importance.
+ */
+[scriptable, uuid(a89de175-ae8e-4c46-91a5-0dba99bbd284)]
+interface nsIHttpServerIdentity : nsISupports
+{
+ /**
+ * The primary scheme at which the corresponding server is located, defaulting
+ * to 'http'. This name will be the value of nsIHttpRequest.scheme for
+ * HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute string primaryScheme;
+
+ /**
+ * The primary name by which the corresponding server is known, defaulting to
+ * 'localhost'. This name will be the value of nsIHttpRequest.host for
+ * HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute string primaryHost;
+
+ /**
+ * The primary port on which the corresponding server runs, defaulting to the
+ * associated server's port. This name will be the value of
+ * nsIHttpRequest.port for HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute long primaryPort;
+
+ /**
+ * Adds a location at which this server may be accessed.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ void add(in string scheme, in string host, in long port);
+
+ /**
+ * Removes this name from the list of names by which the corresponding server
+ * is known. If name is also the primary name for the server, the primary
+ * name reverts to 'http://127.0.0.1' with the associated server's port.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ * @returns
+ * true if the given name was a name for this server, false otherwise
+ */
+ boolean remove(in string scheme, in string host, in long port);
+
+ /**
+ * Returns true if the given name is in this, false otherwise.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ boolean has(in string scheme, in string host, in long port);
+
+ /**
+ * Returns the scheme for the name with the given host and port, if one is
+ * present; otherwise returns the empty string.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if host does not match the host production imported into RFC 2616 from
+ * RFC 2396, or if port is not a valid port number
+ */
+ string getScheme(in string host, in long port);
+
+ /**
+ * Designates the given name as the primary name in this and adds it to this
+ * if it is not already present.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ void setPrimary(in string scheme, in string host, in long port);
+};
+
+/**
+ * A representation of a handler for HTTP requests. The handler is used by
+ * calling its .handle method with data for an incoming request; it is the
+ * handler's job to use that data as it sees fit to make the desired response.
+ *
+ * @note
+ * This interface uses the [function] attribute, so you can pass a
+ * script-defined function with the functionality of handle() to any
+ * method which has a nsIHttpRequestHandler parameter, instead of wrapping
+ * it in an otherwise empty object.
+ */
+[scriptable, function, uuid(2bbb4db7-d285-42b3-a3ce-142b8cc7e139)]
+interface nsIHttpRequestHandler : nsISupports
+{
+ /**
+ * Processes an HTTP request and initializes the passed-in response to reflect
+ * the correct HTTP response.
+ *
+ * If this method throws an exception, externally observable behavior depends
+ * upon whether is being processed asynchronously. If such is the case, the
+ * output is some prefix (perhaps all, perhaps none, perhaps only some) of the
+ * data which would have been sent if, instead, the response had been finished
+ * at that point. If no data has been written, the response has not had
+ * seizePower() called on it, and it is not being asynchronously created, an
+ * error handler will be invoked (usually 500 unless otherwise specified).
+ *
+ * Some uses of nsIHttpRequestHandler may require this method to never throw
+ * an exception; in the general case, however, this method may throw an
+ * exception (causing an HTTP 500 response to occur, if the above conditions
+ * are met).
+ *
+ * @param request
+ * data representing an HTTP request
+ * @param response
+ * an initially-empty response which must be modified to reflect the data
+ * which should be sent as the response to the request described by metadata
+ */
+ void handle(in nsIHttpRequest request, in nsIHttpResponse response);
+};
+
+
+/**
+ * A representation of the data included in an HTTP request.
+ */
+[scriptable, uuid(978cf30e-ad73-42ee-8f22-fe0aaf1bf5d2)]
+interface nsIHttpRequest : nsISupports
+{
+ /**
+ * The request type for this request (see RFC 2616, section 5.1.1).
+ */
+ readonly attribute string method;
+
+ /**
+ * The scheme of the requested path, usually 'http' but might possibly be
+ * 'https' if some form of SSL tunneling is in use. Note that this value
+ * cannot be accurately determined unless the incoming request used the
+ * absolute-path form of the request line; it defaults to 'http', so only
+ * if it is something else can you be entirely certain it's correct.
+ */
+ readonly attribute string scheme;
+
+ /**
+ * The host of the data being requested (e.g. "localhost" for the
+ * http://localhost:8080/file resource). Note that the relevant port on the
+ * host is specified in this.port. This value is in the ASCII character
+ * encoding.
+ */
+ readonly attribute string host;
+
+ /**
+ * The port on the server on which the request was received.
+ */
+ readonly attribute unsigned long port;
+
+ /**
+ * The requested path, without any query string (e.g. "/dir/file.txt"). It is
+ * guaranteed to begin with a "/". The individual components in this string
+ * are URL-encoded.
+ */
+ readonly attribute string path;
+
+ /**
+ * The URL-encoded query string associated with this request, not including
+ * the initial "?", or "" if no query string was present.
+ */
+ readonly attribute string queryString;
+
+ /**
+ * A string containing the HTTP version of the request (i.e., "1.1"). Leading
+ * zeros for either component of the version will be omitted. (In other
+ * words, if the request contains the version "1.01", this attribute will be
+ * "1.1"; see RFC 2616, section 3.1.)
+ */
+ readonly attribute string httpVersion;
+
+ /**
+ * Returns the value for the header in this request specified by fieldName.
+ *
+ * @param fieldName
+ * the name of the field whose value is to be gotten; note that since HTTP
+ * header field names are case-insensitive, this method produces equivalent
+ * results for "HeAdER" and "hEADer" as fieldName
+ * @returns
+ * The result is a string containing the individual values of the header,
+ * usually separated with a comma. The headers WWW-Authenticate,
+ * Proxy-Authenticate, and Set-Cookie violate the HTTP specification,
+ * however, and for these headers only the separator string is '\n'.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the given header does not exist in this
+ */
+ string getHeader(in string fieldName);
+
+ /**
+ * Returns true if a header with the given field name exists in this, false
+ * otherwise.
+ *
+ * @param fieldName
+ * the field name whose existence is to be determined in this; note that
+ * since HTTP header field names are case-insensitive, this method produces
+ * equivalent results for "HeAdER" and "hEADer" as fieldName
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ */
+ boolean hasHeader(in string fieldName);
+
+ /**
+ * An nsISimpleEnumerator of nsISupportsStrings over the names of the headers
+ * in this request. The header field names in the enumerator may not
+ * necessarily have the same case as they do in the request itself.
+ */
+ readonly attribute nsISimpleEnumerator headers;
+
+ /**
+ * A stream from which data appearing in the body of this request can be read.
+ */
+ readonly attribute nsIInputStream bodyInputStream;
+};
+
+
+/**
+ * Represents an HTTP response, as described in RFC 2616, section 6.
+ */
+[scriptable, uuid(1acd16c2-dc59-42fa-9160-4f26c43c1c21)]
+interface nsIHttpResponse : nsISupports
+{
+ /**
+ * Sets the status line for this. If this method is never called on this, the
+ * status line defaults to "HTTP/", followed by the server's default HTTP
+ * version (e.g. "1.1"), followed by " 200 OK".
+ *
+ * @param httpVersion
+ * the HTTP version of this, as a string (e.g. "1.1"); if null, the server
+ * default is used
+ * @param code
+ * the numeric HTTP status code for this
+ * @param description
+ * a human-readable description of code; may be null if no description is
+ * desired
+ * @throws NS_ERROR_INVALID_ARG
+ * if httpVersion is not a valid HTTP version string, statusCode is greater
+ * than 999, or description contains invalid characters
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if this response is being processed asynchronously and data has been
+ * written to this response's body, or if seizePower() has been called on
+ * this
+ */
+ void setStatusLine(in string httpVersion,
+ in unsigned short statusCode,
+ in string description);
+
+ /**
+ * Sets the specified header in this.
+ *
+ * @param name
+ * the name of the header; must match the field-name production per RFC 2616
+ * @param value
+ * the value of the header; must match the field-value production per RFC
+ * 2616
+ * @param merge
+ * when true, if the given header already exists in this, the values passed
+ * to this function will be merged into the existing header, per RFC 2616
+ * header semantics (except for the Set-Cookie, WWW-Authenticate, and
+ * Proxy-Authenticate headers, which will treat each such merged header as
+ * an additional instance of the header, for real-world compatibility
+ * reasons); when false, replaces any existing header of the given name (if
+ * any exists) with a new header with the specified value
+ * @throws NS_ERROR_INVALID_ARG
+ * if name or value is not a valid header component
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if this response is being processed asynchronously and data has been
+ * written to this response's body, or if seizePower() has been called on
+ * this
+ */
+ void setHeader(in string name, in string value, in boolean merge);
+
+ /**
+ * This is used for testing our header handling, so header will be sent out
+ * without transformation. There can be multiple headers.
+ */
+ void setHeaderNoCheck(in string name, in string value);
+
+ /**
+ * A stream to which data appearing in the body of this response (or in the
+ * totality of the response if seizePower() is called) should be written.
+ * After this response has been designated as being processed asynchronously,
+ * or after seizePower() has been called on this, subsequent writes will no
+ * longer be buffered and will be written to the underlying transport without
+ * delaying until the entire response is constructed. Write-through may or
+ * may not be synchronous in the implementation, and in any case particular
+ * behavior may not be observable to the HTTP client as intermediate buffers
+ * both in the server socket and in the client may delay written data; be
+ * prepared for delays at any time.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if accessed after this response is fully constructed
+ */
+ readonly attribute nsIOutputStream bodyOutputStream;
+
+ /**
+ * Writes a string to the response's output stream. This method is merely a
+ * convenient shorthand for writing the same data to bodyOutputStream
+ * directly.
+ *
+ * @note
+ * This method is only guaranteed to work with ASCII data.
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if called after this response has been fully constructed
+ */
+ void write(in string data);
+
+ /**
+ * Signals that this response is being constructed asynchronously. Requests
+ * are typically completely constructed during nsIHttpRequestHandler.handle;
+ * however, responses which require significant resources (time, memory,
+ * processing) to construct can be created and sent incrementally by calling
+ * this method during the call to nsIHttpRequestHandler.handle. This method
+ * only has this effect when called during nsIHttpRequestHandler.handle;
+ * behavior is undefined if it is called at a later time. It may be called
+ * multiple times with no ill effect, so long as each call occurs before
+ * finish() is called.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * if not initially called within a nsIHttpRequestHandler.handle call or if
+ * called after this response has been finished
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if seizePower() has been called on this
+ */
+ void processAsync();
+
+ /**
+ * Seizes complete control of this response (and its connection) from the
+ * server, allowing raw and unfettered access to data being sent in the HTTP
+ * response. Once this method has been called the only property which may be
+ * accessed without an exception being thrown is bodyOutputStream, and the
+ * only methods which may be accessed without an exception being thrown are
+ * write(), finish(), and seizePower() (which may be called multiple times
+ * without ill effect so long as all calls are otherwise allowed).
+ *
+ * After a successful call, all data subsequently written to the body of this
+ * response is written directly to the corresponding connection. (Previously-
+ * written data is silently discarded.) No status line or headers are sent
+ * before doing so; if the response handler wishes to write such data, it must
+ * do so manually. Data generation completes only when finish() is called; it
+ * is not enough to simply call close() on bodyOutputStream.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if processAsync() has been called on this
+ * @throws NS_ERROR_UNEXPECTED
+ * if finish() has been called on this
+ */
+ void seizePower();
+
+ /**
+ * Signals that construction of this response is complete and that it may be
+ * sent over the network to the client, or if seizePower() has been called
+ * signals that all data has been written and that the underlying connection
+ * may be closed. This method may only be called after processAsync() or
+ * seizePower() has been called. This method is idempotent.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * if processAsync() or seizePower() has not already been properly called
+ */
+ void finish();
+};
diff --git a/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^
new file mode 100644
index 000000000..b005a65fd
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^
@@ -0,0 +1 @@
+If this has goofy headers on it, it's a success.
diff --git a/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^
new file mode 100644
index 000000000..66e152231
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^
@@ -0,0 +1,3 @@
+HTTP 500 This Isn't A Server Error
+Foo-RFC: 3092
+Shaving-Cream-Atom: Illudium Phosdex
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_both.html b/netwerk/test/httpserver/test/data/cern_meta/test_both.html
new file mode 100644
index 000000000..db18ea5d7
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html
@@ -0,0 +1,2 @@
+This page is a text file served with status 501. (That's really a lie, tho,
+because this is definitely Implemented.)
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^
new file mode 100644
index 000000000..bb3c16a2e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^
@@ -0,0 +1,2 @@
+HTTP 501 Unimplemented
+Content-Type: text/plain
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt
new file mode 100644
index 000000000..7235fa32a
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>This is really HTML, not text</title>
+</head>
+<body>
+<p>This file is really HTML; the test_ctype_override.txt^headers^ file sets a
+ new header that overwrites the default text/plain header.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^
new file mode 100644
index 000000000..156209f9c
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html
new file mode 100644
index 000000000..fd243c640
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>This is a 404 page</title>
+</head>
+<body>
+<p>This page has a 404 HTTP status associated with it, via
+ <code>test_status_override.html^headers^</code>.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^
new file mode 100644
index 000000000..f438a0574
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^
@@ -0,0 +1 @@
+HTTP 404 Can't Find This
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt
new file mode 100644
index 000000000..4718ec282
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt
@@ -0,0 +1 @@
+This page has an HTTP status override without a description (it defaults to "").
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^
new file mode 100644
index 000000000..32da7632f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^
@@ -0,0 +1 @@
+HTTP 732
diff --git a/netwerk/test/httpserver/test/data/name-scheme/bar.html^^ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^
new file mode 100644
index 000000000..bed1f34c9
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <title>Welcome to bar.html^</title>
+</head>
+<body>
+<p>This file is named with two trailing carets, so the last is stripped
+ away, producing bar.html^ as the final name.</p>
+</body>
+</html>
+
diff --git a/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^
new file mode 100644
index 000000000..04fbaa08f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^
@@ -0,0 +1,2 @@
+HTTP 200 OK
+Content-Type: text/html
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^
new file mode 100644
index 000000000..dccee48e3
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^
@@ -0,0 +1 @@
+This file shouldn't be shown in directory listings.
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^
new file mode 100644
index 000000000..a8ee35a3b
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^
@@ -0,0 +1 @@
+This file should show up in directory listings as SHOULD_SEE_THIS.txt^.
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt b/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt
new file mode 100644
index 000000000..2ceca8ca9
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt
@@ -0,0 +1,2 @@
+File in a directory named with a trailing caret (in the virtual FS; on disk it
+actually ends with two carets).
diff --git a/netwerk/test/httpserver/test/data/name-scheme/foo.html^ b/netwerk/test/httpserver/test/data/name-scheme/foo.html^
new file mode 100644
index 000000000..a3efe8b5c
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/foo.html^
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>ERROR</title>
+</head>
+<body>
+<p>This file should never be served by the web server because its name ends
+ with a caret not followed by another caret.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt b/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt
new file mode 100644
index 000000000..ab71eabaf
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt
@@ -0,0 +1 @@
+This should be seen.
diff --git a/netwerk/test/httpserver/test/data/ranges/empty.txt b/netwerk/test/httpserver/test/data/ranges/empty.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/empty.txt
diff --git a/netwerk/test/httpserver/test/data/ranges/headers.txt b/netwerk/test/httpserver/test/data/ranges/headers.txt
new file mode 100644
index 000000000..6cf83528c
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/headers.txt
@@ -0,0 +1 @@
+Hello Kitty
diff --git a/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^ b/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^
new file mode 100644
index 000000000..d0a633f04
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^
@@ -0,0 +1 @@
+X-SJS-Header: customized
diff --git a/netwerk/test/httpserver/test/data/ranges/range.txt b/netwerk/test/httpserver/test/data/ranges/range.txt
new file mode 100644
index 000000000..ab71eabaf
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/range.txt
@@ -0,0 +1 @@
+This should be seen.
diff --git a/netwerk/test/httpserver/test/data/sjs/cgi.sjs b/netwerk/test/httpserver/test/data/sjs/cgi.sjs
new file mode 100644
index 000000000..b1554f2bc
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/cgi.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response)
+{
+ if (request.queryString == "throw")
+ throw "monkey wrench!";
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("PASS");
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^ b/netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^
new file mode 100644
index 000000000..a83ff774a
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/cgi.sjs^headers^
@@ -0,0 +1,2 @@
+HTTP 500 Error
+This-Header: SHOULD NOT APPEAR IN CGI.JSC RESPONSES!
diff --git a/netwerk/test/httpserver/test/data/sjs/object-state.sjs b/netwerk/test/httpserver/test/data/sjs/object-state.sjs
new file mode 100644
index 000000000..1d9ea8b4e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/object-state.sjs
@@ -0,0 +1,87 @@
+function parseQueryString(str)
+{
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++)
+ {
+ var match = regex.exec(paramArray[i]);
+ if (!match)
+ throw "Bad parameter in queryString! '" + paramArray[i] + "'";
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+/*
+ * We're relying somewhat dubiously on all data being sent as soon as it's
+ * available at numerous levels (in Necko in the server-side part of the
+ * connection, in the OS's outgoing socket buffer, in the OS's incoming socket
+ * buffer, and in Necko in the client-side part of the connection), but to the
+ * best of my knowledge there's no way to force data flow at all those levels,
+ * so this is the best we can do.
+ */
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ /*
+ * NB: A Content-Type header is *necessary* to avoid content-sniffing, which
+ * will delay onStartRequest past the the point where the entire head of
+ * the response has been received.
+ */
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var params = parseQueryString(request.queryString);
+
+ switch (params.state)
+ {
+ case "initial":
+ response.processAsync();
+ response.write("do");
+ var state =
+ {
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ end: function()
+ {
+ response.write("ne");
+ response.finish();
+ }
+ };
+ state.wrappedJSObject = state;
+ setObjectState("object-state-test", state);
+ getObjectState("object-state-test", function(obj)
+ {
+ if (obj !== state)
+ {
+ response.write("FAIL bad state save");
+ response.finish();
+ }
+ });
+ break;
+
+ case "intermediate":
+ response.write("intermediate");
+ break;
+
+ case "trigger":
+ response.write("trigger");
+ getObjectState("object-state-test", function(obj)
+ {
+ obj.wrappedJSObject.end();
+ setObjectState("object-state-test", null);
+ });
+ break;
+
+ default:
+ response.setStatusLine(request.httpVersion, 500, "Unexpected State");
+ response.write("Bad state: " + params.state);
+ break;
+ }
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/qi.sjs b/netwerk/test/httpserver/test/data/sjs/qi.sjs
new file mode 100644
index 000000000..89c7089b5
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/qi.sjs
@@ -0,0 +1,48 @@
+const Ci = Components.interfaces;
+
+function handleRequest(request, response)
+{
+ var exstr, qid;
+
+ response.setStatusLine(request.httpVersion, 500, "FAIL");
+
+ var passed = false;
+ try
+ {
+ qid = request.QueryInterface(Ci.nsIHttpRequest);
+ passed = qid === request;
+ }
+ catch (e)
+ {
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(request.httpVersion, 500,
+ "request doesn't QI: " + exstr);
+ return;
+ }
+ if (!passed)
+ {
+ response.setStatusLine(request.httpVersion, 500, "request QI'd wrongly?");
+ return;
+ }
+
+ passed = false;
+ try
+ {
+ qid = response.QueryInterface(Ci.nsIHttpResponse);
+ passed = qid === response;
+ }
+ catch (e)
+ {
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(request.httpVersion, 500,
+ "response doesn't QI: " + exstr);
+ return;
+ }
+ if (!passed)
+ {
+ response.setStatusLine(request.httpVersion, 500, "response QI'd wrongly?");
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "SJS QI Tests Passed");
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/range-checker.sjs b/netwerk/test/httpserver/test/data/sjs/range-checker.sjs
new file mode 100644
index 000000000..39fcc2b88
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/range-checker.sjs
@@ -0,0 +1,3 @@
+function handleRequest(request, response)
+{
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/sjs b/netwerk/test/httpserver/test/data/sjs/sjs
new file mode 100644
index 000000000..374ca4167
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response)
+{
+ response.write("FAIL");
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/state1.sjs b/netwerk/test/httpserver/test/data/sjs/state1.sjs
new file mode 100644
index 000000000..da2862d1e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/state1.sjs
@@ -0,0 +1,42 @@
+function parseQueryString(str)
+{
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++)
+ {
+ var match = regex.exec(paramArray[i]);
+ if (!match)
+ throw "Bad parameter in queryString! '" + paramArray[i] + "'";
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var params = parseQueryString(request.queryString);
+
+ var oldShared = getSharedState("shared-value");
+ response.setHeader("X-Old-Shared-Value", oldShared, false);
+
+ var newShared = params.newShared;
+ if (newShared !== undefined)
+ {
+ setSharedState("shared-value", newShared);
+ response.setHeader("X-New-Shared-Value", newShared, false);
+ }
+
+ var oldPrivate = getState("private-value");
+ response.setHeader("X-Old-Private-Value", oldPrivate, false);
+
+ var newPrivate = params.newPrivate;
+ if (newPrivate !== undefined)
+ {
+ setState("private-value", newPrivate);
+ response.setHeader("X-New-Private-Value", newPrivate, false);
+ }
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/state2.sjs b/netwerk/test/httpserver/test/data/sjs/state2.sjs
new file mode 100644
index 000000000..da2862d1e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/state2.sjs
@@ -0,0 +1,42 @@
+function parseQueryString(str)
+{
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++)
+ {
+ var match = regex.exec(paramArray[i]);
+ if (!match)
+ throw "Bad parameter in queryString! '" + paramArray[i] + "'";
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var params = parseQueryString(request.queryString);
+
+ var oldShared = getSharedState("shared-value");
+ response.setHeader("X-Old-Shared-Value", oldShared, false);
+
+ var newShared = params.newShared;
+ if (newShared !== undefined)
+ {
+ setSharedState("shared-value", newShared);
+ response.setHeader("X-New-Shared-Value", newShared, false);
+ }
+
+ var oldPrivate = getState("private-value");
+ response.setHeader("X-Old-Private-Value", oldPrivate, false);
+
+ var newPrivate = params.newPrivate;
+ if (newPrivate !== undefined)
+ {
+ setState("private-value", newPrivate);
+ response.setHeader("X-New-Private-Value", newPrivate, false);
+ }
+}
diff --git a/netwerk/test/httpserver/test/data/sjs/thrower.sjs b/netwerk/test/httpserver/test/data/sjs/thrower.sjs
new file mode 100644
index 000000000..1aaf1639a
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/thrower.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response)
+{
+ if (request.queryString == "throw")
+ undefined[5];
+ response.setHeader("X-Test-Status", "PASS", false);
+}
diff --git a/netwerk/test/httpserver/test/head_utils.js b/netwerk/test/httpserver/test/head_utils.js
new file mode 100644
index 000000000..21f615117
--- /dev/null
+++ b/netwerk/test/httpserver/test/head_utils.js
@@ -0,0 +1,600 @@
+/* -*- 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 _HTTPD_JS_PATH = __LOCATION__.parent;
+_HTTPD_JS_PATH.append("httpd.js");
+load(_HTTPD_JS_PATH.path);
+
+// if these tests fail, we'll want the debug output
+DEBUG = true;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+/**
+ * Constructs a new nsHttpServer instance. This function is intended to
+ * encapsulate construction of a server so that at some point in the future it
+ * is possible to run these tests (with at most slight modifications) against
+ * the server when used as an XPCOM component (not as an inline script).
+ */
+function createServer()
+{
+ return new nsHttpServer();
+}
+
+/**
+ * Creates a new HTTP channel.
+ *
+ * @param url
+ * the URL of the channel to create
+ */
+function makeChannel(url)
+{
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+/**
+ * Make a binary input stream wrapper for the given stream.
+ *
+ * @param stream
+ * the nsIInputStream to wrap
+ */
+function makeBIS(stream)
+{
+ return new BinaryInputStream(stream);
+}
+
+
+/**
+ * Returns the contents of the file as a string.
+ *
+ * @param file : nsILocalFile
+ * the file whose contents are to be read
+ * @returns string
+ * the contents of the file
+ */
+function fileContents(file)
+{
+ const PR_RDONLY = 0x01;
+ var fis = new FileInputStream(file, PR_RDONLY, 0o444,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ var sis = new ScriptableInputStream(fis);
+ var contents = sis.read(file.fileSize);
+ sis.close();
+ return contents;
+}
+
+/**
+ * Iterates over the lines, delimited by CRLF, in data, returning each line
+ * without the trailing line separator.
+ *
+ * @param data : string
+ * a string consisting of lines of data separated by CRLFs
+ * @returns Iterator
+ * an Iterator which returns each line from data in turn; note that this
+ * includes a final empty line if data ended with a CRLF
+ */
+function LineIterator(data)
+{
+ var start = 0, index = 0;
+ do
+ {
+ index = data.indexOf("\r\n");
+ if (index >= 0)
+ yield data.substring(0, index);
+ else
+ yield data;
+
+ data = data.substring(index + 2);
+ }
+ while (index >= 0);
+}
+
+/**
+ * Throws if iter does not contain exactly the CRLF-separated lines in the
+ * array expectedLines.
+ *
+ * @param iter : Iterator
+ * an Iterator which returns lines of text
+ * @param expectedLines : [string]
+ * an array of the expected lines of text
+ * @throws string
+ * an error message if iter doesn't agree with expectedLines
+ */
+function expectLines(iter, expectedLines)
+{
+ var index = 0;
+ for (var line in iter)
+ {
+ if (expectedLines.length == index)
+ throw "Error: got more than " + expectedLines.length + " expected lines!";
+
+ var expected = expectedLines[index++];
+ if (expected !== line)
+ throw "Error on line " + index + "!\n" +
+ " actual: '" + line + "',\n" +
+ " expect: '" + expected + "'";
+ }
+
+ if (expectedLines.length !== index)
+ {
+ throw "Expected more lines! Got " + index +
+ ", expected " + expectedLines.length;
+ }
+}
+
+/**
+ * Spew a bunch of HTTP metadata from request into the body of response.
+ *
+ * @param request : nsIHttpRequest
+ * the request whose metadata should be output
+ * @param response : nsIHttpResponse
+ * the response to which the metadata is written
+ */
+function writeDetails(request, response)
+{
+ response.write("Method: " + request.method + "\r\n");
+ response.write("Path: " + request.path + "\r\n");
+ response.write("Query: " + request.queryString + "\r\n");
+ response.write("Version: " + request.httpVersion + "\r\n");
+ response.write("Scheme: " + request.scheme + "\r\n");
+ response.write("Host: " + request.host + "\r\n");
+ response.write("Port: " + request.port);
+}
+
+/**
+ * Advances iter past all non-blank lines and a single blank line, after which
+ * point the body of the response will be returned next from the iterator.
+ *
+ * @param iter : Iterator
+ * an iterator over the CRLF-delimited lines in an HTTP response, currently
+ * just after the Request-Line
+ */
+function skipHeaders(iter)
+{
+ var line = iter.next();
+ while (line !== "")
+ line = iter.next();
+}
+
+/**
+ * Checks that the exception e (which may be an XPConnect-created exception
+ * object or a raw nsresult number) is the given nsresult.
+ *
+ * @param e : Exception or nsresult
+ * the actual exception
+ * @param code : nsresult
+ * the expected exception
+ */
+function isException(e, code)
+{
+ if (e !== code && e.result !== code)
+ do_throw("unexpected error: " + e);
+}
+
+/**
+ * Calls the given function at least the specified number of milliseconds later.
+ * The callback will not undershoot the given time, but it might overshoot --
+ * don't expect precision!
+ *
+ * @param milliseconds : uint
+ * the number of milliseconds to delay
+ * @param callback : function() : void
+ * the function to call
+ */
+function callLater(msecs, callback)
+{
+ do_timeout(msecs, callback);
+}
+
+
+/*******************************************************
+ * SIMPLE SUPPORT FOR LOADING/TESTING A SERIES OF URLS *
+ *******************************************************/
+
+/**
+ * Create a completion callback which will stop the given server and end the
+ * test, assuming nothing else remains to be done at that point.
+ */
+function testComplete(srv)
+{
+ return function complete()
+ {
+ do_test_pending();
+ srv.stop(function quit() { do_test_finished(); });
+ };
+}
+
+/**
+ * Represents a path to load from the tested HTTP server, along with actions to
+ * take before, during, and after loading the associated page.
+ *
+ * @param path
+ * the URL to load from the server
+ * @param initChannel
+ * a function which takes as a single parameter a channel created for path and
+ * initializes its state, or null if no additional initialization is needed
+ * @param onStartRequest
+ * called during onStartRequest for the load of the URL, with the same
+ * parameters; the request parameter has been QI'd to nsIHttpChannel and
+ * nsIHttpChannelInternal for convenience; may be null if nothing needs to be
+ * done
+ * @param onStopRequest
+ * called during onStopRequest for the channel, with the same parameters plus
+ * a trailing parameter containing an array of the bytes of data downloaded in
+ * the body of the channel response; the request parameter has been QI'd to
+ * nsIHttpChannel and nsIHttpChannelInternal for convenience; may be null if
+ * nothing needs to be done
+ */
+function Test(path, initChannel, onStartRequest, onStopRequest)
+{
+ function nil() { }
+
+ this.path = path;
+ this.initChannel = initChannel || nil;
+ this.onStartRequest = onStartRequest || nil;
+ this.onStopRequest = onStopRequest || nil;
+}
+
+/**
+ * Runs all the tests in testArray.
+ *
+ * @param testArray
+ * a non-empty array of Tests to run, in order
+ * @param done
+ * function to call when all tests have run (e.g. to shut down the server)
+ */
+function runHttpTests(testArray, done)
+{
+ /** Kicks off running the next test in the array. */
+ function performNextTest()
+ {
+ if (++testIndex == testArray.length)
+ {
+ try
+ {
+ done();
+ }
+ catch (e)
+ {
+ do_report_unexpected_exception(e, "running test-completion callback");
+ }
+ return;
+ }
+
+ do_test_pending();
+
+ var test = testArray[testIndex];
+ var ch = makeChannel(test.path);
+ try
+ {
+ test.initChannel(ch);
+ }
+ catch (e)
+ {
+ try
+ {
+ do_report_unexpected_exception(e, "testArray[" + testIndex + "].initChannel(ch)");
+ }
+ catch (e)
+ {
+ /* swallow and let tests continue */
+ }
+ }
+
+ listener._channel = ch;
+ ch.asyncOpen2(listener);
+ }
+
+ /** Index of the test being run. */
+ var testIndex = -1;
+
+ /** Stream listener for the channels. */
+ var listener =
+ {
+ /** Current channel being observed by this. */
+ _channel: null,
+ /** Array of bytes of data in body of response. */
+ _data: [],
+
+ onStartRequest: function(request, cx)
+ {
+ do_check_true(request === this._channel);
+ var ch = request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ this._data.length = 0;
+ try
+ {
+ try
+ {
+ testArray[testIndex].onStartRequest(ch, cx);
+ }
+ catch (e)
+ {
+ do_report_unexpected_exception(e, "testArray[" + testIndex + "].onStartRequest");
+ }
+ }
+ catch (e)
+ {
+ do_note_exception(e, "!!! swallowing onStartRequest exception so onStopRequest is " +
+ "called...");
+ }
+ },
+ onDataAvailable: function(request, cx, inputStream, offset, count)
+ {
+ var quantum = 262144; // just above half the argument-count limit
+ var bis = makeBIS(inputStream);
+ for (var start = 0; start < count; start += quantum)
+ {
+ var newData = bis.readByteArray(Math.min(quantum, count - start));
+ Array.prototype.push.apply(this._data, newData);
+ }
+ },
+ onStopRequest: function(request, cx, status)
+ {
+ this._channel = null;
+
+ var ch = request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ // NB: The onStopRequest callback must run before performNextTest here,
+ // because the latter runs the next test's initChannel callback, and
+ // we want one test to be sequentially processed before the next
+ // one.
+ try
+ {
+ testArray[testIndex].onStopRequest(ch, cx, status, this._data);
+ }
+ finally
+ {
+ try
+ {
+ performNextTest();
+ }
+ finally
+ {
+ do_test_finished();
+ }
+ }
+ },
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIStreamListener) ||
+ aIID.equals(Ci.nsIRequestObserver) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ performNextTest();
+}
+
+
+/****************************************
+ * RAW REQUEST FORMAT TESTING FUNCTIONS *
+ ****************************************/
+
+/**
+ * Sends a raw string of bytes to the given host and port and checks that the
+ * response is acceptable.
+ *
+ * @param host : string
+ * the host to which a connection should be made
+ * @param port : PRUint16
+ * the port to use for the connection
+ * @param data : string or [string...]
+ * either:
+ * - the raw data to send, as a string of characters with codes in the
+ * range 0-255, or
+ * - an array of such strings whose concatenation forms the raw data
+ * @param responseCheck : function(string) : void
+ * a function which is provided with the data sent by the remote host which
+ * conducts whatever tests it wants on that data; useful for tweaking the test
+ * environment between tests
+ */
+function RawTest(host, port, data, responseCheck)
+{
+ if (0 > port || 65535 < port || port % 1 !== 0)
+ throw "bad port";
+ if (!(data instanceof Array))
+ data = [data];
+ if (data.length <= 0)
+ throw "bad data length";
+ if (!data.every(function(v) { return /^[\x00-\xff]*$/.test(v); }))
+ throw "bad data contained non-byte-valued character";
+
+ this.host = host;
+ this.port = port;
+ this.data = data;
+ this.responseCheck = responseCheck;
+}
+
+/**
+ * Runs all the tests in testArray, an array of RawTests.
+ *
+ * @param testArray : [RawTest]
+ * an array of RawTests to run, in order
+ * @param done
+ * function to call when all tests have run (e.g. to shut down the server)
+ */
+function runRawTests(testArray, done)
+{
+ do_test_pending();
+
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+ var currentThread = Cc["@mozilla.org/thread-manager;1"]
+ .getService()
+ .currentThread;
+
+ /** Kicks off running the next test in the array. */
+ function performNextTest()
+ {
+ if (++testIndex == testArray.length)
+ {
+ do_test_finished();
+ try
+ {
+ done();
+ }
+ catch (e)
+ {
+ do_report_unexpected_exception(e, "running test-completion callback");
+ }
+ return;
+ }
+
+
+ var rawTest = testArray[testIndex];
+
+ var transport =
+ sts.createTransport(null, 0, rawTest.host, rawTest.port, null);
+
+ var inStream = transport.openInputStream(0, 0, 0);
+ var outStream = transport.openOutputStream(0, 0, 0);
+
+ // reset
+ dataIndex = 0;
+ received = "";
+
+ waitForMoreInput(inStream);
+ waitToWriteOutput(outStream);
+ }
+
+ function waitForMoreInput(stream)
+ {
+ reader.stream = stream;
+ stream = stream.QueryInterface(Ci.nsIAsyncInputStream);
+ stream.asyncWait(reader, 0, 0, currentThread);
+ }
+
+ function waitToWriteOutput(stream)
+ {
+ // Do the QueryInterface here, not earlier, because there is no
+ // guarantee that 'stream' passed in here been QIed to nsIAsyncOutputStream
+ // since the last GC.
+ stream = stream.QueryInterface(Ci.nsIAsyncOutputStream);
+ stream.asyncWait(writer, 0, testArray[testIndex].data[dataIndex].length,
+ currentThread);
+ }
+
+ /** Index of the test being run. */
+ var testIndex = -1;
+
+ /**
+ * Index of remaining data strings to be written to the socket in current
+ * test.
+ */
+ var dataIndex = 0;
+
+ /** Data received so far from the server. */
+ var received = "";
+
+ /** Reads data from the socket. */
+ var reader =
+ {
+ onInputStreamReady: function(stream)
+ {
+ do_check_true(stream === this.stream);
+ try
+ {
+ var bis = new BinaryInputStream(stream);
+
+ var av = 0;
+ try
+ {
+ av = bis.available();
+ }
+ catch (e)
+ {
+ /* default to 0 */
+ do_note_exception(e);
+ }
+
+ if (av > 0)
+ {
+ var quantum = 262144;
+ for (var start = 0; start < av; start += quantum)
+ {
+ var bytes = bis.readByteArray(Math.min(quantum, av - start));
+ received += String.fromCharCode.apply(null, bytes);
+ }
+ waitForMoreInput(stream);
+ return;
+ }
+ }
+ catch(e)
+ {
+ do_report_unexpected_exception(e);
+ }
+
+ var rawTest = testArray[testIndex];
+ try
+ {
+ rawTest.responseCheck(received);
+ }
+ catch (e)
+ {
+ do_report_unexpected_exception(e);
+ }
+ finally
+ {
+ try
+ {
+ stream.close();
+ performNextTest();
+ }
+ catch (e)
+ {
+ do_report_unexpected_exception(e);
+ }
+ }
+ }
+ };
+
+ /** Writes data to the socket. */
+ var writer =
+ {
+ onOutputStreamReady: function(stream)
+ {
+ var str = testArray[testIndex].data[dataIndex];
+
+ var written = 0;
+ try
+ {
+ written = stream.write(str, str.length);
+ if (written == str.length)
+ dataIndex++;
+ else
+ testArray[testIndex].data[dataIndex] = str.substring(written);
+ }
+ catch (e)
+ {
+ do_note_exception(e);
+ /* stream could have been closed, just ignore */
+ }
+
+ try
+ {
+ // Keep writing data while we can write and
+ // until there's no more data to read
+ if (written > 0 && dataIndex < testArray[testIndex].data.length)
+ waitToWriteOutput(stream);
+ else
+ stream.close();
+ }
+ catch (e)
+ {
+ do_report_unexpected_exception(e);
+ }
+ }
+ };
+
+ performNextTest();
+}
diff --git a/netwerk/test/httpserver/test/test_async_response_sending.js b/netwerk/test/httpserver/test/test_async_response_sending.js
new file mode 100644
index 000000000..84ec74daf
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_async_response_sending.js
@@ -0,0 +1,1683 @@
+/* -*- 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/. */
+
+/*
+ * Ensures that data a request handler writes out in response is sent only as
+ * quickly as the client can receive it, without racing ahead and being forced
+ * to block while writing that data.
+ *
+ * NB: These tests are extremely tied to the current implementation, in terms of
+ * when and how stream-ready notifications occur, the amount of data which will
+ * be read or written at each notification, and so on. If the implementation
+ * changes in any way with respect to stream copying, this test will probably
+ * have to change a little at the edges as well.
+ */
+
+gThreadManager = Cc["@mozilla.org/thread-manager;1"].createInstance();
+
+function run_test()
+{
+ do_test_pending();
+ tests.push(function testsComplete(_)
+ {
+ dumpn("******************\n" +
+ "* TESTS COMPLETE *\n" +
+ "******************");
+ do_test_finished();
+ });
+
+ runNextTest();
+}
+
+function runNextTest()
+{
+ testIndex++;
+ dumpn("*** runNextTest(), testIndex: " + testIndex);
+
+ try
+ {
+ var test = tests[testIndex];
+ test(runNextTest);
+ }
+ catch (e)
+ {
+ var msg = "exception running test " + testIndex + ": " + e;
+ if (e && "stack" in e)
+ msg += "\nstack follows:\n" + e.stack;
+ do_throw(msg);
+ }
+}
+
+
+/*************
+ * TEST DATA *
+ *************/
+
+const NOTHING = [];
+
+const FIRST_SEGMENT = [1, 2, 3, 4];
+const SECOND_SEGMENT = [5, 6, 7, 8];
+const THIRD_SEGMENT = [9, 10, 11, 12];
+
+const SEGMENT = FIRST_SEGMENT;
+const TWO_SEGMENTS = [1, 2, 3, 4, 5, 6, 7, 8];
+const THREE_SEGMENTS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+
+const SEGMENT_AND_HALF = [1, 2, 3, 4, 5, 6];
+
+const QUARTER_SEGMENT = [1];
+const HALF_SEGMENT = [1, 2];
+const SECOND_HALF_SEGMENT = [3, 4];
+const THREE_QUARTER_SEGMENT = [1, 2, 3];
+const EXTRA_HALF_SEGMENT = [5, 6];
+const MIDDLE_HALF_SEGMENT = [2, 3];
+const LAST_QUARTER_SEGMENT = [4];
+const FOURTH_HALF_SEGMENT = [7, 8];
+const HALF_THIRD_SEGMENT = [9, 10];
+const LATTER_HALF_THIRD_SEGMENT = [11, 12];
+
+const TWO_HALF_SEGMENTS = [1, 2, 1, 2];
+
+
+/*********
+ * TESTS *
+ *********/
+
+var tests =
+ [
+ sourceClosedWithoutWrite,
+ writeOneSegmentThenClose,
+ simpleWriteThenRead,
+ writeLittleBeforeReading,
+ writeMultipleSegmentsThenRead,
+ writeLotsBeforeReading,
+ writeLotsBeforeReading2,
+ writeThenReadPartial,
+ manyPartialWrites,
+ partialRead,
+ partialWrite,
+ sinkClosedImmediately,
+ sinkClosedWithReadableData,
+ sinkClosedAfterWrite,
+ sourceAndSinkClosed,
+ sinkAndSourceClosed,
+ sourceAndSinkClosedWithPendingData,
+ sinkAndSourceClosedWithPendingData,
+ ];
+var testIndex = -1;
+
+function sourceClosedWithoutWrite(next)
+{
+ var t = new CopyTest("sourceClosedWithoutWrite", next);
+
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [NOTHING]);
+}
+
+function writeOneSegmentThenClose(next)
+{
+ var t = new CopyTest("writeLittleBeforeReading", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT]);
+}
+
+function simpleWriteThenRead(next)
+{
+ var t = new CopyTest("simpleWriteThenRead", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [SEGMENT]);
+}
+
+function writeLittleBeforeReading(next)
+{
+ var t = new CopyTest("writeLittleBeforeReading", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT, SEGMENT]);
+}
+
+function writeMultipleSegmentsThenRead(next)
+{
+ var t = new CopyTest("writeMultipleSegmentsThenRead", next);
+
+ t.addToSource(TWO_SEGMENTS);
+ t.makeSourceReadable(TWO_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(TWO_SEGMENTS.length,
+ [FIRST_SEGMENT, SECOND_SEGMENT]);
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [TWO_SEGMENTS]);
+}
+
+function writeLotsBeforeReading(next)
+{
+ var t = new CopyTest("writeLotsBeforeReading", next);
+
+ t.addToSource(TWO_SEGMENTS);
+ t.makeSourceReadable(TWO_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(FIRST_SEGMENT.length, [FIRST_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SECOND_SEGMENT.length, [SECOND_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(2 * SEGMENT.length, [SEGMENT, SEGMENT]);
+ t.expect(Cr.NS_OK, [TWO_SEGMENTS, SEGMENT, SEGMENT]);
+}
+
+function writeLotsBeforeReading2(next)
+{
+ var t = new CopyTest("writeLotsBeforeReading", next);
+
+ t.addToSource(THREE_SEGMENTS);
+ t.makeSourceReadable(THREE_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(FIRST_SEGMENT.length, [FIRST_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SECOND_SEGMENT.length, [SECOND_SEGMENT]);
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(THIRD_SEGMENT.length, [THIRD_SEGMENT]);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(2 * SEGMENT.length, [SEGMENT, SEGMENT]);
+ t.expect(Cr.NS_OK, [THREE_SEGMENTS, SEGMENT, SEGMENT]);
+}
+
+function writeThenReadPartial(next)
+{
+ var t = new CopyTest("writeThenReadPartial", next);
+
+ t.addToSource(SEGMENT_AND_HALF);
+ t.makeSourceReadable(SEGMENT_AND_HALF.length);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.closeSource(Cr.NS_OK);
+ t.makeSinkWritableAndWaitFor(EXTRA_HALF_SEGMENT.length, [EXTRA_HALF_SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT_AND_HALF]);
+}
+
+function manyPartialWrites(next)
+{
+ var t = new CopyTest("manyPartialWrites", next);
+
+ t.addToSource(HALF_SEGMENT);
+ t.makeSourceReadable(HALF_SEGMENT.length);
+
+ t.addToSource(HALF_SEGMENT);
+ t.makeSourceReadable(HALF_SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(2 * HALF_SEGMENT.length, [TWO_HALF_SEGMENTS]);
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [TWO_HALF_SEGMENTS]);
+}
+
+function partialRead(next)
+{
+ var t = new CopyTest("partialRead", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.addToSource(HALF_SEGMENT);
+ t.makeSourceReadable(HALF_SEGMENT.length);
+ t.makeSinkWritableAndWaitFor(SEGMENT.length, [SEGMENT]);
+ t.closeSourceAndWaitFor(Cr.NS_OK, HALF_SEGMENT.length, [HALF_SEGMENT]);
+ t.expect(Cr.NS_OK, [SEGMENT, HALF_SEGMENT]);
+}
+
+function partialWrite(next)
+{
+ var t = new CopyTest("partialWrite", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableByIncrementsAndWaitFor(SEGMENT.length,
+ [QUARTER_SEGMENT,
+ MIDDLE_HALF_SEGMENT,
+ LAST_QUARTER_SEGMENT]);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.makeSinkWritableByIncrementsAndWaitFor(SEGMENT.length,
+ [HALF_SEGMENT, SECOND_HALF_SEGMENT]);
+
+ t.addToSource(THREE_SEGMENTS);
+ t.makeSourceReadable(THREE_SEGMENTS.length);
+ t.makeSinkWritableByIncrementsAndWaitFor(THREE_SEGMENTS.length,
+ [HALF_SEGMENT, SECOND_HALF_SEGMENT,
+ SECOND_SEGMENT,
+ HALF_THIRD_SEGMENT,
+ LATTER_HALF_THIRD_SEGMENT]);
+
+ t.closeSource(Cr.NS_OK);
+ t.expect(Cr.NS_OK, [SEGMENT, SEGMENT, THREE_SEGMENTS]);
+}
+
+function sinkClosedImmediately(next)
+{
+ var t = new CopyTest("sinkClosedImmediately", next);
+
+ t.closeSink(Cr.NS_OK);
+ t.expect(Cr.NS_ERROR_UNEXPECTED, [NOTHING]);
+}
+
+function sinkClosedWithReadableData(next)
+{
+ var t = new CopyTest("sinkClosedWithReadableData", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+ t.closeSink(Cr.NS_OK);
+ t.expect(Cr.NS_ERROR_UNEXPECTED, [NOTHING]);
+}
+
+function sinkClosedAfterWrite(next)
+{
+ var t = new CopyTest("sinkClosedAfterWrite", next);
+
+ t.addToSource(TWO_SEGMENTS);
+ t.makeSourceReadable(TWO_SEGMENTS.length);
+ t.makeSinkWritableAndWaitFor(FIRST_SEGMENT.length, [FIRST_SEGMENT]);
+ t.closeSink(Cr.NS_OK);
+ t.expect(Cr.NS_ERROR_UNEXPECTED, [FIRST_SEGMENT]);
+}
+
+function sourceAndSinkClosed(next)
+{
+ var t = new CopyTest("sourceAndSinkClosed", next);
+
+ t.closeSourceThenSink(Cr.NS_OK, Cr.NS_OK);
+ t.expect(Cr.NS_OK, []);
+}
+
+function sinkAndSourceClosed(next)
+{
+ var t = new CopyTest("sinkAndSourceClosed", next);
+
+ t.closeSinkThenSource(Cr.NS_OK, Cr.NS_OK);
+
+ // sink notify received first, hence error
+ t.expect(Cr.NS_ERROR_UNEXPECTED, []);
+}
+
+function sourceAndSinkClosedWithPendingData(next)
+{
+ var t = new CopyTest("sourceAndSinkClosedWithPendingData", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+
+ t.closeSourceThenSink(Cr.NS_OK, Cr.NS_OK);
+
+ // not all data from source copied, so error
+ t.expect(Cr.NS_ERROR_UNEXPECTED, []);
+}
+
+function sinkAndSourceClosedWithPendingData(next)
+{
+ var t = new CopyTest("sinkAndSourceClosedWithPendingData", next);
+
+ t.addToSource(SEGMENT);
+ t.makeSourceReadable(SEGMENT.length);
+
+ t.closeSinkThenSource(Cr.NS_OK, Cr.NS_OK);
+
+ // not all data from source copied, plus sink notify received first, so error
+ t.expect(Cr.NS_ERROR_UNEXPECTED, []);
+}
+
+
+/*************
+ * UTILITIES *
+ *************/
+
+/** Returns the sum of the elements in arr. */
+function sum(arr)
+{
+ var sum = 0;
+ for (var i = 0, sz = arr.length; i < sz; i++)
+ sum += arr[i];
+ return sum;
+}
+
+/**
+ * Returns a constructor for an input or output stream callback that will wrap
+ * the one provided to it as an argument.
+ *
+ * @param wrapperCallback : (nsIInputStreamCallback | nsIOutputStreamCallback) : void
+ * the original callback object (not a function!) being wrapped
+ * @param name : string
+ * either "onInputStreamReady" if we're wrapping an input stream callback or
+ * "onOutputStreamReady" if we're wrapping an output stream callback
+ * @returns function(nsIInputStreamCallback | nsIOutputStreamCallback) : (nsIInputStreamCallback | nsIOutputStreamCallback)
+ * a constructor function which constructs a callback object (not function!)
+ * which, when called, first calls the original callback provided to it and
+ * then calls wrapperCallback
+ */
+function createStreamReadyInterceptor(wrapperCallback, name)
+{
+ return function StreamReadyInterceptor(callback)
+ {
+ this.wrappedCallback = callback;
+ this[name] = function streamReadyInterceptor(stream)
+ {
+ dumpn("*** StreamReadyInterceptor." + name);
+
+ try
+ {
+ dumpn("*** calling original " + name + "...");
+ callback[name](stream);
+ }
+ catch (e)
+ {
+ dumpn("!!! error running inner callback: " + e);
+ throw e;
+ }
+ finally
+ {
+ dumpn("*** calling wrapper " + name + "...");
+ wrapperCallback[name](stream);
+ }
+ }
+ };
+}
+
+/**
+ * Print out a banner with the given message, uppercased, for debugging
+ * purposes.
+ */
+function note(m)
+{
+ m = m.toUpperCase();
+ var asterisks = Array(m.length + 1 + 4).join("*");
+ dumpn(asterisks + "\n* " + m + " *\n" + asterisks);
+}
+
+
+/***********
+ * MOCKERY *
+ ***********/
+
+/*
+ * Blatantly violate abstractions in the name of testability. THIS IS NOT
+ * PUBLIC API! If you use any of these I will knowingly break your code by
+ * changing the names of variables and properties.
+ */
+var BinaryInputStream = function BIS(stream) { return stream; };
+var BinaryOutputStream = function BOS(stream) { return stream; };
+Response.SEGMENT_SIZE = SEGMENT.length;
+
+/**
+ * Roughly mocks an nsIPipe, presenting non-blocking input and output streams
+ * that appear to also be binary streams and whose readability and writability
+ * amounts are configurable. Only the methods used in this test have been
+ * implemented -- these aren't exact mocks (can't be, actually, because input
+ * streams have unscriptable methods).
+ *
+ * @param name : string
+ * a name for this pipe, used in debugging output
+ */
+function CustomPipe(name)
+{
+ var self = this;
+
+ /** Data read from input that's buffered until it can be written to output. */
+ this._data = [];
+
+ /**
+ * The status of this pipe, which is to say the error result the ends of this
+ * pipe will return when attempts are made to use them. This value is always
+ * an error result when copying has finished, because success codes are
+ * converted to NS_BASE_STREAM_CLOSED.
+ */
+ this._status = Cr.NS_OK;
+
+ /** The input end of this pipe. */
+ var input = this.inputStream =
+ {
+ /** A name for this stream, used in debugging output. */
+ name: name + " input",
+
+ /**
+ * The number of bytes of data available to be read from this pipe, or
+ * Infinity if any amount of data in this pipe is made readable as soon as
+ * it is written to the pipe output.
+ */
+ _readable: 0,
+
+ /**
+ * Data regarding a pending stream-ready callback on this, or null if no
+ * callback is currently waiting to be called.
+ */
+ _waiter: null,
+
+ /**
+ * The event currently dispatched to make a stream-ready callback, if any
+ * such callback is currently ready to be made and not already in
+ * progress, or null when no callback is waiting to happen.
+ */
+ _event: null,
+
+ /**
+ * A stream-ready constructor to wrap an existing callback to intercept
+ * stream-ready notifications, or null if notifications shouldn't be
+ * wrapped at all.
+ */
+ _streamReadyInterceptCreator: null,
+
+ /**
+ * Registers a stream-ready wrapper creator function so that a
+ * stream-ready callback made in the future can be wrapped.
+ */
+ interceptStreamReadyCallbacks: function(streamReadyInterceptCreator)
+ {
+ dumpn("*** [" + this.name + "].interceptStreamReadyCallbacks");
+
+ do_check_true(this._streamReadyInterceptCreator === null,
+ "intercepting twice");
+ this._streamReadyInterceptCreator = streamReadyInterceptCreator;
+ if (this._waiter)
+ {
+ this._waiter.callback =
+ new streamReadyInterceptCreator(this._waiter.callback);
+ }
+ },
+
+ /**
+ * Removes a previously-registered stream-ready wrapper creator function,
+ * also clearing any current wrapping.
+ */
+ removeStreamReadyInterceptor: function()
+ {
+ dumpn("*** [" + this.name + "].removeStreamReadyInterceptor()");
+
+ do_check_true(this._streamReadyInterceptCreator !== null,
+ "removing interceptor when none present?");
+ this._streamReadyInterceptCreator = null;
+ if (this._waiter)
+ this._waiter.callback = this._waiter.callback.wrappedCallback;
+ },
+
+ //
+ // see nsIAsyncInputStream.asyncWait
+ //
+ asyncWait: function asyncWait(callback, flags, requestedCount, target)
+ {
+ dumpn("*** [" + this.name + "].asyncWait");
+
+ do_check_true(callback && typeof callback !== "function");
+
+ var closureOnly =
+ (flags & Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY) !== 0;
+
+ do_check_true(this._waiter === null ||
+ (this._waiter.closureOnly && !closureOnly),
+ "asyncWait already called with a non-closure-only " +
+ "callback? unexpected!");
+
+ this._waiter =
+ {
+ callback:
+ this._streamReadyInterceptCreator
+ ? new this._streamReadyInterceptCreator(callback)
+ : callback,
+ closureOnly: closureOnly,
+ requestedCount: requestedCount,
+ eventTarget: target
+ };
+
+ if (!Components.isSuccessCode(self._status) ||
+ (!closureOnly && this._readable >= requestedCount &&
+ self._data.length >= requestedCount))
+ {
+ this._notify();
+ }
+ },
+
+ //
+ // see nsIAsyncInputStream.closeWithStatus
+ //
+ closeWithStatus: function closeWithStatus(status)
+ {
+ dumpn("*** [" + this.name + "].closeWithStatus" +
+ "(" + status + ")");
+
+ if (!Components.isSuccessCode(self._status))
+ {
+ dumpn("*** ignoring second closure of [input " + this.name + "] " +
+ "(status " + self._status + ")");
+ return;
+ }
+
+ if (Components.isSuccessCode(status))
+ status = Cr.NS_BASE_STREAM_CLOSED;
+
+ self._status = status;
+
+ if (this._waiter)
+ this._notify();
+ if (output._waiter)
+ output._notify();
+ },
+
+ //
+ // see nsIBinaryInputStream.readByteArray
+ //
+ readByteArray: function readByteArray(count)
+ {
+ dumpn("*** [" + this.name + "].readByteArray(" + count + ")");
+
+ if (self._data.length === 0)
+ {
+ throw Components.isSuccessCode(self._status)
+ ? Cr.NS_BASE_STREAM_WOULD_BLOCK
+ : self._status;
+ }
+
+ do_check_true(this._readable <= self._data.length ||
+ this._readable === Infinity,
+ "consistency check");
+
+ if (this._readable < count || self._data.length < count)
+ throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
+ this._readable -= count;
+ return self._data.splice(0, count);
+ },
+
+ /**
+ * Makes the given number of additional bytes of data previously written
+ * to the pipe's output stream available for reading, triggering future
+ * notifications when required.
+ *
+ * @param count : uint
+ * the number of bytes of additional data to make available; must not be
+ * greater than the number of bytes already buffered but not made
+ * available by previous makeReadable calls
+ */
+ makeReadable: function makeReadable(count)
+ {
+ dumpn("*** [" + this.name + "].makeReadable(" + count + ")");
+
+ do_check_true(Components.isSuccessCode(self._status), "errant call");
+ do_check_true(this._readable + count <= self._data.length ||
+ this._readable === Infinity,
+ "increasing readable beyond written amount");
+
+ this._readable += count;
+
+ dumpn("readable: " + this._readable + ", data: " + self._data);
+
+ var waiter = this._waiter;
+ if (waiter !== null)
+ {
+ if (waiter.requestedCount <= this._readable && !waiter.closureOnly)
+ this._notify();
+ }
+ },
+
+ /**
+ * Disables the readability limit on this stream, meaning that as soon as
+ * *any* amount of data is written to output it becomes available from
+ * this stream and a stream-ready event is dispatched (if any stream-ready
+ * callback is currently set).
+ */
+ disableReadabilityLimit: function disableReadabilityLimit()
+ {
+ dumpn("*** [" + this.name + "].disableReadabilityLimit()");
+
+ this._readable = Infinity;
+ },
+
+ //
+ // see nsIInputStream.available
+ //
+ available: function available()
+ {
+ dumpn("*** [" + this.name + "].available()");
+
+ if (self._data.length === 0 && !Components.isSuccessCode(self._status))
+ throw self._status;
+
+ return Math.min(this._readable, self._data.length);
+ },
+
+ /**
+ * Dispatches a pending stream-ready event ahead of schedule, rather than
+ * waiting for it to be dispatched in response to normal writes. This is
+ * useful when writing to the output has completed, and we need to have
+ * read all data written to this stream. If the output isn't closed and
+ * the reading of data from this races ahead of the last write to output,
+ * we need a notification to know when everything that's been written has
+ * been read. This ordinarily might be supplied by closing output, but
+ * in some cases it's not desirable to close output, so this supplies an
+ * alternative method to get notified when the last write has occurred.
+ */
+ maybeNotifyFinally: function maybeNotifyFinally()
+ {
+ dumpn("*** [" + this.name + "].maybeNotifyFinally()");
+
+ do_check_true(this._waiter !== null, "must be waiting now");
+
+ if (self._data.length > 0)
+ {
+ dumpn("*** data still pending, normal notifications will signal " +
+ "completion");
+ return;
+ }
+
+ // No data waiting to be written, so notify. We could just close the
+ // stream, but that's less faithful to the server's behavior (it doesn't
+ // close the stream, and we're pretending to impersonate the server as
+ // much as we can here), so instead we're going to notify when no data
+ // can be read. The CopyTest has already been flagged as complete, so
+ // the stream listener will detect that this is a wrap-it-up notify and
+ // invoke the next test.
+ this._notify();
+ },
+
+ /**
+ * Dispatches an event to call a previously-registered stream-ready
+ * callback.
+ */
+ _notify: function _notify()
+ {
+ dumpn("*** [" + this.name + "]._notify()");
+
+ var waiter = this._waiter;
+ do_check_true(waiter !== null, "no waiter?");
+
+ if (this._event === null)
+ {
+ var event = this._event =
+ {
+ run: function run()
+ {
+ input._waiter = null;
+ input._event = null;
+ try
+ {
+ do_check_true(!Components.isSuccessCode(self._status) ||
+ input._readable >= waiter.requestedCount);
+ waiter.callback.onInputStreamReady(input);
+ }
+ catch (e)
+ {
+ do_throw("error calling onInputStreamReady: " + e);
+ }
+ }
+ };
+ waiter.eventTarget.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ QueryInterface: function QueryInterface(iid)
+ {
+ if (iid.equals(Ci.nsIAsyncInputStream) ||
+ iid.equals(Ci.nsIInputStream) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ /** The output end of this pipe. */
+ var output = this.outputStream =
+ {
+ /** A name for this stream, used in debugging output. */
+ name: name + " output",
+
+ /**
+ * The number of bytes of data which may be written to this pipe without
+ * blocking.
+ */
+ _writable: 0,
+
+ /**
+ * The increments in which pending data should be written, rather than
+ * simply defaulting to the amount requested (which, given that
+ * input.asyncWait precisely respects the requestedCount argument, will
+ * ordinarily always be writable in that amount), as an array whose
+ * elements from start to finish are the number of bytes to write each
+ * time write() or writeByteArray() is subsequently called. The sum of
+ * the values in this array, if this array is not empty, is always equal
+ * to this._writable.
+ */
+ _writableAmounts: [],
+
+ /**
+ * Data regarding a pending stream-ready callback on this, or null if no
+ * callback is currently waiting to be called.
+ */
+ _waiter: null,
+
+ /**
+ * The event currently dispatched to make a stream-ready callback, if any
+ * such callback is currently ready to be made and not already in
+ * progress, or null when no callback is waiting to happen.
+ */
+ _event: null,
+
+ /**
+ * A stream-ready constructor to wrap an existing callback to intercept
+ * stream-ready notifications, or null if notifications shouldn't be
+ * wrapped at all.
+ */
+ _streamReadyInterceptCreator: null,
+
+ /**
+ * Registers a stream-ready wrapper creator function so that a
+ * stream-ready callback made in the future can be wrapped.
+ */
+ interceptStreamReadyCallbacks: function(streamReadyInterceptCreator)
+ {
+ dumpn("*** [" + this.name + "].interceptStreamReadyCallbacks");
+
+ do_check_true(this._streamReadyInterceptCreator !== null,
+ "intercepting onOutputStreamReady twice");
+ this._streamReadyInterceptCreator = streamReadyInterceptCreator;
+ if (this._waiter)
+ {
+ this._waiter.callback =
+ new streamReadyInterceptCreator(this._waiter.callback);
+ }
+ },
+
+ /**
+ * Removes a previously-registered stream-ready wrapper creator function,
+ * also clearing any current wrapping.
+ */
+ removeStreamReadyInterceptor: function()
+ {
+ dumpn("*** [" + this.name + "].removeStreamReadyInterceptor()");
+
+ do_check_true(this._streamReadyInterceptCreator !== null,
+ "removing interceptor when none present?");
+ this._streamReadyInterceptCreator = null;
+ if (this._waiter)
+ this._waiter.callback = this._waiter.callback.wrappedCallback;
+ },
+
+ //
+ // see nsIAsyncOutputStream.asyncWait
+ //
+ asyncWait: function asyncWait(callback, flags, requestedCount, target)
+ {
+ dumpn("*** [" + this.name + "].asyncWait");
+
+ do_check_true(callback && typeof callback !== "function");
+
+ var closureOnly =
+ (flags & Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY) !== 0;
+
+ do_check_true(this._waiter === null ||
+ (this._waiter.closureOnly && !closureOnly),
+ "asyncWait already called with a non-closure-only " +
+ "callback? unexpected!");
+
+ this._waiter =
+ {
+ callback:
+ this._streamReadyInterceptCreator
+ ? new this._streamReadyInterceptCreator(callback)
+ : callback,
+ closureOnly: closureOnly,
+ requestedCount: requestedCount,
+ eventTarget: target,
+ toString: function toString()
+ {
+ return "waiter(" + (closureOnly ? "closure only, " : "") +
+ "requestedCount: " + requestedCount + ", target: " +
+ target + ")";
+ }
+ };
+
+ if ((!closureOnly && this._writable >= requestedCount) ||
+ !Components.isSuccessCode(this.status))
+ {
+ this._notify();
+ }
+ },
+
+ //
+ // see nsIAsyncOutputStream.closeWithStatus
+ //
+ closeWithStatus: function closeWithStatus(status)
+ {
+ dumpn("*** [" + this.name + "].closeWithStatus(" + status + ")");
+
+ if (!Components.isSuccessCode(self._status))
+ {
+ dumpn("*** ignoring redundant closure of [input " + this.name + "] " +
+ "because it's already closed (status " + self._status + ")");
+ return;
+ }
+
+ if (Components.isSuccessCode(status))
+ status = Cr.NS_BASE_STREAM_CLOSED;
+
+ self._status = status;
+
+ if (input._waiter)
+ input._notify();
+ if (this._waiter)
+ this._notify();
+ },
+
+ //
+ // see nsIBinaryOutputStream.writeByteArray
+ //
+ writeByteArray: function writeByteArray(bytes, length)
+ {
+ dumpn("*** [" + this.name + "].writeByteArray" +
+ "([" + bytes + "], " + length + ")");
+
+ do_check_eq(bytes.length, length, "sanity");
+ if (!Components.isSuccessCode(self._status))
+ throw self._status;
+
+ do_check_eq(this._writableAmounts.length, 0,
+ "writeByteArray can't support specified-length writes");
+
+ if (this._writable < length)
+ throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
+
+ self._data.push.apply(self._data, bytes);
+ this._writable -= length;
+
+ if (input._readable === Infinity && input._waiter &&
+ !input._waiter.closureOnly)
+ {
+ input._notify();
+ }
+ },
+
+ //
+ // see nsIOutputStream.write
+ //
+ write: function write(str, length)
+ {
+ dumpn("*** [" + this.name + "].write");
+
+ do_check_eq(str.length, length, "sanity");
+ if (!Components.isSuccessCode(self._status))
+ throw self._status;
+ if (this._writable === 0)
+ throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
+
+ var actualWritten;
+ if (this._writableAmounts.length === 0)
+ {
+ actualWritten = Math.min(this._writable, length);
+ }
+ else
+ {
+ do_check_true(this._writable >= this._writableAmounts[0],
+ "writable amounts value greater than writable data?");
+ do_check_eq(this._writable, sum(this._writableAmounts),
+ "total writable amount not equal to sum of writable " +
+ "increments");
+ actualWritten = this._writableAmounts.shift();
+ }
+
+ var bytes = str.substring(0, actualWritten)
+ .split("")
+ .map(function(v) { return v.charCodeAt(0); });
+
+ self._data.push.apply(self._data, bytes);
+ this._writable -= actualWritten;
+
+ if (input._readable === Infinity && input._waiter &&
+ !input._waiter.closureOnly)
+ {
+ input._notify();
+ }
+
+ return actualWritten;
+ },
+
+ /**
+ * Increase the amount of data that can be written without blocking by the
+ * given number of bytes, triggering future notifications when required.
+ *
+ * @param count : uint
+ * the number of bytes of additional data to make writable
+ */
+ makeWritable: function makeWritable(count)
+ {
+ dumpn("*** [" + this.name + "].makeWritable(" + count + ")");
+
+ do_check_true(Components.isSuccessCode(self._status));
+
+ this._writable += count;
+
+ var waiter = this._waiter;
+ if (waiter && !waiter.closureOnly &&
+ waiter.requestedCount <= this._writable)
+ {
+ this._notify();
+ }
+ },
+
+ /**
+ * Increase the amount of data that can be written without blocking, but
+ * do so by specifying a number of bytes that will be written each time
+ * a write occurs, even as asyncWait notifications are initially triggered
+ * as usual. Thus, rather than writes eagerly writing everything possible
+ * at each step, attempts to write out data by segment devolve into a
+ * partial segment write, then another, and so on until the amount of data
+ * specified as permitted to be written, has been written.
+ *
+ * Note that the writeByteArray method is incompatible with the previous
+ * calling of this method, in that, until all increments provided to this
+ * method have been consumed, writeByteArray cannot be called. Once all
+ * increments have been consumed, writeByteArray may again be called.
+ *
+ * @param increments : [uint]
+ * an array whose elements are positive numbers of bytes to permit to be
+ * written each time write() is subsequently called on this, ignoring
+ * the total amount of writable space specified by the sum of all
+ * increments
+ */
+ makeWritableByIncrements: function makeWritableByIncrements(increments)
+ {
+ dumpn("*** [" + this.name + "].makeWritableByIncrements" +
+ "([" + increments.join(", ") + "])");
+
+ do_check_true(increments.length > 0, "bad increments");
+ do_check_true(increments.every(function(v) { return v > 0; }),
+ "zero increment?");
+
+ do_check_true(Components.isSuccessCode(self._status));
+
+ this._writable += sum(increments);
+ this._writableAmounts = increments;
+
+ var waiter = this._waiter;
+ if (waiter && !waiter.closureOnly &&
+ waiter.requestedCount <= this._writable)
+ {
+ this._notify();
+ }
+ },
+
+ /**
+ * Dispatches an event to call a previously-registered stream-ready
+ * callback.
+ */
+ _notify: function _notify()
+ {
+ dumpn("*** [" + this.name + "]._notify()");
+
+ var waiter = this._waiter;
+ do_check_true(waiter !== null, "no waiter?");
+
+ if (this._event === null)
+ {
+ var event = this._event =
+ {
+ run: function run()
+ {
+ output._waiter = null;
+ output._event = null;
+
+ try
+ {
+ waiter.callback.onOutputStreamReady(output);
+ }
+ catch (e)
+ {
+ do_throw("error calling onOutputStreamReady: " + e);
+ }
+ }
+ };
+ waiter.eventTarget.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ QueryInterface: function QueryInterface(iid)
+ {
+ if (iid.equals(Ci.nsIAsyncOutputStream) ||
+ iid.equals(Ci.nsIOutputStream) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+}
+
+/**
+ * Represents a sequence of interactions to perform with a copier, in a given
+ * order and at the desired time intervals.
+ *
+ * @param name : string
+ * test name, used in debugging output
+ */
+function CopyTest(name, next)
+{
+ /** Name used in debugging output. */
+ this.name = name;
+
+ /** A function called when the test completes. */
+ this._done = next;
+
+ var sourcePipe = new CustomPipe(name + "-source");
+
+ /** The source of data for the copier to copy. */
+ this._source = sourcePipe.inputStream;
+
+ /**
+ * The sink to which to write data which will appear in the copier's source.
+ */
+ this._copyableDataStream = sourcePipe.outputStream;
+
+ var sinkPipe = new CustomPipe(name + "-sink");
+
+ /** The sink to which the copier copies data. */
+ this._sink = sinkPipe.outputStream;
+
+ /** Input stream from which to read data the copier's written to its sink. */
+ this._copiedDataStream = sinkPipe.inputStream;
+
+ this._copiedDataStream.disableReadabilityLimit();
+
+ /**
+ * True if there's a callback waiting to read data written by the copier to
+ * its output, from the input end of the pipe representing the copier's sink.
+ */
+ this._waitingForData = false;
+
+ /**
+ * An array of the bytes of data expected to be written to output by the
+ * copier when this test runs.
+ */
+ this._expectedData = undefined;
+
+ /** Array of bytes of data received so far. */
+ this._receivedData = [];
+
+ /** The expected final status returned by the copier. */
+ this._expectedStatus = -1;
+
+ /** The actual final status returned by the copier. */
+ this._actualStatus = -1;
+
+ /** The most recent sequence of bytes written to output by the copier. */
+ this._lastQuantum = [];
+
+ /**
+ * True iff we've received the last quantum of data written to the sink by the
+ * copier.
+ */
+ this._allDataWritten = false;
+
+ /**
+ * True iff the copier has notified its associated stream listener of
+ * completion.
+ */
+ this._copyingFinished = false;
+
+ /** Index of the next task to execute while driving the copier. */
+ this._currentTask = 0;
+
+ /** Array containing all tasks to run. */
+ this._tasks = [];
+
+ /** The copier used by this test. */
+ this._copier =
+ new WriteThroughCopier(this._source, this._sink, this, null);
+
+ // Start watching for data written by the copier to the sink.
+ this._waitForWrittenData();
+}
+CopyTest.prototype =
+{
+ /**
+ * Adds the given array of bytes to data in the copier's source.
+ *
+ * @param bytes : [uint]
+ * array of bytes of data to add to the source for the copier
+ */
+ addToSource: function addToSource(bytes)
+ {
+ var self = this;
+ this._addToTasks(function addToSourceTask()
+ {
+ note("addToSourceTask");
+
+ try
+ {
+ self._copyableDataStream.makeWritable(bytes.length);
+ self._copyableDataStream.writeByteArray(bytes, bytes.length);
+ }
+ finally
+ {
+ self._stageNextTask();
+ }
+ });
+ },
+
+ /**
+ * Makes bytes of data previously added to the source available to be read by
+ * the copier.
+ *
+ * @param count : uint
+ * number of bytes to make available for reading
+ */
+ makeSourceReadable: function makeSourceReadable(count)
+ {
+ var self = this;
+ this._addToTasks(function makeSourceReadableTask()
+ {
+ note("makeSourceReadableTask");
+
+ self._source.makeReadable(count);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Increases available space in the sink by the given amount, waits for the
+ * given series of arrays of bytes to be written to sink by the copier, and
+ * causes execution to asynchronously continue to the next task when the last
+ * of those arrays of bytes is received.
+ *
+ * @param bytes : uint
+ * number of bytes of space to make available in the sink
+ * @param dataQuantums : [[uint]]
+ * array of byte arrays to expect to be written in sequence to the sink
+ */
+ makeSinkWritableAndWaitFor:
+ function makeSinkWritableAndWaitFor(bytes, dataQuantums)
+ {
+ var self = this;
+
+ do_check_eq(bytes,
+ dataQuantums.reduce(function(partial, current)
+ {
+ return partial + current.length;
+ }, 0),
+ "bytes/quantums mismatch");
+
+ function increaseSinkSpaceTask()
+ {
+ /* Now do the actual work to trigger the interceptor. */
+ self._sink.makeWritable(bytes);
+ }
+
+ this._waitForHelper("increaseSinkSpaceTask",
+ dataQuantums, increaseSinkSpaceTask);
+ },
+
+ /**
+ * Increases available space in the sink by the given amount, waits for the
+ * given series of arrays of bytes to be written to sink by the copier, and
+ * causes execution to asynchronously continue to the next task when the last
+ * of those arrays of bytes is received.
+ *
+ * @param bytes : uint
+ * number of bytes of space to make available in the sink
+ * @param dataQuantums : [[uint]]
+ * array of byte arrays to expect to be written in sequence to the sink
+ */
+ makeSinkWritableByIncrementsAndWaitFor:
+ function makeSinkWritableByIncrementsAndWaitFor(bytes, dataQuantums)
+ {
+ var self = this;
+
+ var desiredAmounts = dataQuantums.map(function(v) { return v.length; });
+ do_check_eq(bytes, sum(desiredAmounts), "bytes/quantums mismatch");
+
+ function increaseSinkSpaceByIncrementsTask()
+ {
+ /* Now do the actual work to trigger the interceptor incrementally. */
+ self._sink.makeWritableByIncrements(desiredAmounts);
+ }
+
+ this._waitForHelper("increaseSinkSpaceByIncrementsTask",
+ dataQuantums, increaseSinkSpaceByIncrementsTask);
+ },
+
+ /**
+ * Close the copier's source stream, then asynchronously continue to the next
+ * task.
+ *
+ * @param status : nsresult
+ * the status to provide when closing the copier's source stream
+ */
+ closeSource: function closeSource(status)
+ {
+ var self = this;
+
+ this._addToTasks(function closeSourceTask()
+ {
+ note("closeSourceTask");
+
+ self._source.closeWithStatus(status);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Close the copier's source stream, then wait for the given number of bytes
+ * and for the given series of arrays of bytes to be written to the sink, then
+ * asynchronously continue to the next task.
+ *
+ * @param status : nsresult
+ * the status to provide when closing the copier's source stream
+ * @param bytes : uint
+ * number of bytes of space to make available in the sink
+ * @param dataQuantums : [[uint]]
+ * array of byte arrays to expect to be written in sequence to the sink
+ */
+ closeSourceAndWaitFor:
+ function closeSourceAndWaitFor(status, bytes, dataQuantums)
+ {
+ var self = this;
+
+ do_check_eq(bytes, sum(dataQuantums.map(function(v) { return v.length; })),
+ "bytes/quantums mismatch");
+
+ function closeSourceAndWaitForTask()
+ {
+ self._sink.makeWritable(bytes);
+ self._copyableDataStream.closeWithStatus(status);
+ }
+
+ this._waitForHelper("closeSourceAndWaitForTask",
+ dataQuantums, closeSourceAndWaitForTask);
+ },
+
+ /**
+ * Closes the copier's sink stream, providing the given status, then
+ * asynchronously continue to the next task.
+ *
+ * @param status : nsresult
+ * the status to provide when closing the copier's sink stream
+ */
+ closeSink: function closeSink(status)
+ {
+ var self = this;
+ this._addToTasks(function closeSinkTask()
+ {
+ note("closeSinkTask");
+
+ self._sink.closeWithStatus(status);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Closes the copier's source stream, then immediately closes the copier's
+ * sink stream, then asynchronously continues to the next task.
+ *
+ * @param sourceStatus : nsresult
+ * the status to provide when closing the copier's source stream
+ * @param sinkStatus : nsresult
+ * the status to provide when closing the copier's sink stream
+ */
+ closeSourceThenSink: function closeSourceThenSink(sourceStatus, sinkStatus)
+ {
+ var self = this;
+ this._addToTasks(function closeSourceThenSinkTask()
+ {
+ note("closeSourceThenSinkTask");
+
+ self._source.closeWithStatus(sourceStatus);
+ self._sink.closeWithStatus(sinkStatus);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Closes the copier's sink stream, then immediately closes the copier's
+ * source stream, then asynchronously continues to the next task.
+ *
+ * @param sinkStatus : nsresult
+ * the status to provide when closing the copier's sink stream
+ * @param sourceStatus : nsresult
+ * the status to provide when closing the copier's source stream
+ */
+ closeSinkThenSource: function closeSinkThenSource(sinkStatus, sourceStatus)
+ {
+ var self = this;
+ this._addToTasks(function closeSinkThenSourceTask()
+ {
+ note("closeSinkThenSource");
+
+ self._sink.closeWithStatus(sinkStatus);
+ self._source.closeWithStatus(sourceStatus);
+ self._stageNextTask();
+ });
+ },
+
+ /**
+ * Indicates that the given status is expected to be returned when the stream
+ * listener for the copy indicates completion, that the expected data copied
+ * by the copier to sink are the concatenation of the arrays of bytes in
+ * receivedData, and kicks off the tasks in this test.
+ *
+ * @param expectedStatus : nsresult
+ * the status expected to be returned by the copier at completion
+ * @param receivedData : [[uint]]
+ * an array containing arrays of bytes whose concatenation constitutes the
+ * expected copied data
+ */
+ expect: function expect(expectedStatus, receivedData)
+ {
+ this._expectedStatus = expectedStatus;
+ this._expectedData = [];
+ for (var i = 0, sz = receivedData.length; i < sz; i++)
+ this._expectedData.push.apply(this._expectedData, receivedData[i]);
+
+ this._stageNextTask();
+ },
+
+ /**
+ * Sets up a stream interceptor that will verify that each piece of data
+ * written to the sink by the copier corresponds to the currently expected
+ * pieces of data, calls the trigger, then waits for those pieces of data to
+ * be received. Once all have been received, the interceptor is removed and
+ * the next task is asynchronously executed.
+ *
+ * @param name : string
+ * name of the task created by this, used in debugging output
+ * @param dataQuantums : [[uint]]
+ * array of expected arrays of bytes to be written to the sink by the copier
+ * @param trigger : function() : void
+ * function to call after setting up the interceptor to wait for
+ * notifications (which will be generated as a result of this function's
+ * actions)
+ */
+ _waitForHelper: function _waitForHelper(name, dataQuantums, trigger)
+ {
+ var self = this;
+ this._addToTasks(function waitForHelperTask()
+ {
+ note(name);
+
+ var quantumIndex = 0;
+
+ /*
+ * Intercept all data-available notifications so we can continue when all
+ * the ones we expect have been received.
+ */
+ var streamReadyCallback =
+ {
+ onInputStreamReady: function wrapperOnInputStreamReady(input)
+ {
+ dumpn("*** streamReadyCallback.onInputStreamReady" +
+ "(" + input.name + ")");
+
+ do_check_eq(this, streamReadyCallback, "sanity");
+
+ try
+ {
+ if (quantumIndex < dataQuantums.length)
+ {
+ var quantum = dataQuantums[quantumIndex++];
+ var sz = quantum.length;
+ do_check_eq(self._lastQuantum.length, sz,
+ "different quantum lengths");
+ for (var i = 0; i < sz; i++)
+ {
+ do_check_eq(self._lastQuantum[i], quantum[i],
+ "bad data at " + i);
+ }
+
+ dumpn("*** waiting to check remaining " +
+ (dataQuantums.length - quantumIndex) + " quantums...");
+ }
+ }
+ finally
+ {
+ if (quantumIndex === dataQuantums.length)
+ {
+ dumpn("*** data checks completed! next task...");
+ self._copiedDataStream.removeStreamReadyInterceptor();
+ self._stageNextTask();
+ }
+ }
+ }
+ };
+
+ var interceptor =
+ createStreamReadyInterceptor(streamReadyCallback, "onInputStreamReady");
+ self._copiedDataStream.interceptStreamReadyCallbacks(interceptor);
+
+ /* Do the deed. */
+ trigger();
+ });
+ },
+
+ /**
+ * Initiates asynchronous waiting for data written to the copier's sink to be
+ * available for reading from the input end of the sink's pipe. The callback
+ * stores the received data for comparison in the interceptor used in the
+ * callback added by _waitForHelper and signals test completion when it
+ * receives a zero-data-available notification (if the copier has notified
+ * that it is finished; otherwise allows execution to continue until that has
+ * occurred).
+ */
+ _waitForWrittenData: function _waitForWrittenData()
+ {
+ dumpn("*** _waitForWrittenData (" + this.name + ")");
+
+ var self = this;
+ var outputWrittenWatcher =
+ {
+ onInputStreamReady: function onInputStreamReady(input)
+ {
+ dumpn("*** outputWrittenWatcher.onInputStreamReady" +
+ "(" + input.name + ")");
+
+ if (self._allDataWritten)
+ {
+ do_throw("ruh-roh! why are we getting notified of more data " +
+ "after we should have received all of it?");
+ }
+
+ self._waitingForData = false;
+
+ try
+ {
+ var avail = input.available();
+ }
+ catch (e)
+ {
+ dumpn("*** available() threw! error: " + e);
+ if (self._completed)
+ {
+ dumpn("*** NB: this isn't a problem, because we've copied " +
+ "completely now, and this notify may have been expedited " +
+ "by maybeNotifyFinally such that we're being called when " +
+ "we can *guarantee* nothing is available any more");
+ }
+ avail = 0;
+ }
+
+ if (avail > 0)
+ {
+ var data = input.readByteArray(avail);
+ do_check_eq(data.length, avail,
+ "readByteArray returned wrong number of bytes?");
+ self._lastQuantum = data;
+ self._receivedData.push.apply(self._receivedData, data);
+ }
+
+ if (avail === 0)
+ {
+ dumpn("*** all data received!");
+
+ self._allDataWritten = true;
+
+ if (self._copyingFinished)
+ {
+ dumpn("*** copying already finished, continuing to next test");
+ self._testComplete();
+ }
+ else
+ {
+ dumpn("*** copying not finished, waiting for that to happen");
+ }
+
+ return;
+ }
+
+ self._waitForWrittenData();
+ }
+ };
+
+ this._copiedDataStream.asyncWait(outputWrittenWatcher, 0, 1,
+ gThreadManager.currentThread);
+ this._waitingForData = true;
+ },
+
+ /**
+ * Indicates this test is complete, does the final data-received and copy
+ * status comparisons, and calls the test-completion function provided when
+ * this test was first created.
+ */
+ _testComplete: function _testComplete()
+ {
+ dumpn("*** CopyTest(" + this.name + ") complete! " +
+ "On to the next test...");
+
+ try
+ {
+ do_check_true(this._allDataWritten, "expect all data written now!");
+ do_check_true(this._copyingFinished, "expect copying finished now!");
+
+ do_check_eq(this._actualStatus, this._expectedStatus,
+ "wrong final status");
+
+ var expected = this._expectedData, received = this._receivedData;
+ dumpn("received: [" + received + "], expected: [" + expected + "]");
+ do_check_eq(received.length, expected.length, "wrong data");
+ for (var i = 0, sz = expected.length; i < sz; i++)
+ do_check_eq(received[i], expected[i], "bad data at " + i);
+ }
+ catch (e)
+ {
+ dumpn("!!! ERROR PERFORMING FINAL " + this.name + " CHECKS! " + e);
+ throw e;
+ }
+ finally
+ {
+ dumpn("*** CopyTest(" + this.name + ") complete! " +
+ "Invoking test-completion callback...");
+ this._done();
+ }
+ },
+
+ /** Dispatches an event at this thread which will run the next task. */
+ _stageNextTask: function _stageNextTask()
+ {
+ dumpn("*** CopyTest(" + this.name + ")._stageNextTask()");
+
+ if (this._currentTask === this._tasks.length)
+ {
+ dumpn("*** CopyTest(" + this.name + ") tasks complete!");
+ return;
+ }
+
+ var task = this._tasks[this._currentTask++];
+ var self = this;
+ var event =
+ {
+ run: function run()
+ {
+ try
+ {
+ task();
+ }
+ catch (e)
+ {
+ do_throw("exception thrown running task: " + e);
+ }
+ }
+ };
+ gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ /**
+ * Adds the given function as a task to be run at a later time.
+ *
+ * @param task : function() : void
+ * the function to call as a task
+ */
+ _addToTasks: function _addToTasks(task)
+ {
+ this._tasks.push(task);
+ },
+
+ //
+ // see nsIRequestObserver.onStartRequest
+ //
+ onStartRequest: function onStartRequest(self, _)
+ {
+ dumpn("*** CopyTest.onStartRequest (" + self.name + ")");
+
+ do_check_true(_ === null);
+ do_check_eq(this._receivedData.length, 0);
+ do_check_eq(this._lastQuantum.length, 0);
+ },
+
+ //
+ // see nsIRequestObserver.onStopRequest
+ //
+ onStopRequest: function onStopRequest(self, _, status)
+ {
+ dumpn("*** CopyTest.onStopRequest (" + self.name + ", " + status + ")");
+
+ do_check_true(_ === null);
+ this._actualStatus = status;
+
+ this._copyingFinished = true;
+
+ if (this._allDataWritten)
+ {
+ dumpn("*** all data written, continuing with remaining tests...");
+ this._testComplete();
+ }
+ else
+ {
+ /*
+ * Everything's copied as far as the copier is concerned. However, there
+ * may be a backup transferring from the output end of the copy sink to
+ * the input end where we can actually verify that the expected data was
+ * written as expected, because that transfer occurs asynchronously. If
+ * we do final data-received checks now, we'll miss still-pending data.
+ * Therefore, to wrap up this copy test we still need to asynchronously
+ * wait on the input end of the sink until we hit end-of-stream or some
+ * error condition. Then we know we're done and can continue with the
+ * next test.
+ */
+ dumpn("*** not all data copied, waiting for that to happen...");
+
+ if (!this._waitingForData)
+ this._waitForWrittenData();
+
+ this._copiedDataStream.maybeNotifyFinally();
+ }
+ }
+};
diff --git a/netwerk/test/httpserver/test/test_basic_functionality.js b/netwerk/test/httpserver/test/test_basic_functionality.js
new file mode 100644
index 000000000..9151bed4b
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_basic_functionality.js
@@ -0,0 +1,176 @@
+/* -*- 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/. */
+
+/*
+ * Basic functionality test, from the client programmer's POV.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "port", function() {
+ return srv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test("http://localhost:" + port + "/objHandler",
+ null, start_objHandler, null),
+ new Test("http://localhost:" + port + "/functionHandler",
+ null, start_functionHandler, null),
+ new Test("http://localhost:" + port + "/nonexistent-path",
+ null, start_non_existent_path, null),
+ new Test("http://localhost:" + port + "/lotsOfHeaders",
+ null, start_lots_of_headers, null),
+ ];
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ // base path
+ // XXX should actually test this works with a file by comparing streams!
+ var dirServ = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ var path = dirServ.get("CurProcD", Ci.nsILocalFile);
+ srv.registerDirectory("/", path);
+
+ // register a few test paths
+ srv.registerPathHandler("/objHandler", objHandler);
+ srv.registerPathHandler("/functionHandler", functionHandler);
+ srv.registerPathHandler("/lotsOfHeaders", lotsOfHeadersHandler);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+const HEADER_COUNT = 1000;
+
+// TEST DATA
+
+// common properties *always* appended by server
+// or invariants for every URL in paths
+function commonCheck(ch)
+{
+ do_check_true(ch.contentLength > -1);
+ do_check_eq(ch.getResponseHeader("connection"), "close");
+ do_check_false(ch.isNoStoreResponse());
+ do_check_false(ch.isPrivateResponse());
+}
+
+function start_objHandler(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+ do_check_eq(ch.getResponseHeader("content-type"), "text/plain");
+ do_check_eq(ch.responseStatusText, "OK");
+
+ var reqMin = {}, reqMaj = {}, respMin = {}, respMaj = {};
+ ch.getRequestVersion(reqMaj, reqMin);
+ ch.getResponseVersion(respMaj, respMin);
+ do_check_true(reqMaj.value == respMaj.value &&
+ reqMin.value == respMin.value);
+}
+
+function start_functionHandler(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+ do_check_eq(ch.getResponseHeader("foopy"), "quux-baz");
+ do_check_eq(ch.responseStatusText, "Page Not Found");
+
+ var reqMin = {}, reqMaj = {}, respMin = {}, respMaj = {};
+ ch.getRequestVersion(reqMaj, reqMin);
+ ch.getResponseVersion(respMaj, respMin);
+ do_check_true(reqMaj.value == 1 && reqMin.value == 1);
+ do_check_true(respMaj.value == 1 && respMin.value == 1);
+}
+
+function start_non_existent_path(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+}
+
+function start_lots_of_headers(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+
+ for (var i = 0; i < HEADER_COUNT; i++)
+ do_check_eq(ch.getResponseHeader("X-Header-" + i), "value " + i);
+}
+
+// PATH HANDLERS
+
+// /objHandler
+var objHandler =
+ {
+ handle: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = "Request (slightly reformatted):\n\n";
+ body += metadata.method + " " + metadata.path;
+
+ do_check_eq(metadata.port, port);
+
+ if (metadata.queryString)
+ body += "?" + metadata.queryString;
+
+ body += " HTTP/" + metadata.httpVersion + "\n";
+
+ var headEnum = metadata.headers;
+ while (headEnum.hasMoreElements())
+ {
+ var fieldName = headEnum.getNext()
+ .QueryInterface(Ci.nsISupportsString)
+ .data;
+ body += fieldName + ": " + metadata.getHeader(fieldName) + "\n";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+ },
+ QueryInterface: function(id)
+ {
+ if (id.equals(Ci.nsISupports) || id.equals(Ci.nsIHttpRequestHandler))
+ return this;
+ throw Cr.NS_ERROR_NOINTERFACE;
+ }
+ };
+
+// /functionHandler
+function functionHandler(metadata, response)
+{
+ response.setStatusLine("1.1", 404, "Page Not Found");
+ response.setHeader("foopy", "quux-baz", false);
+
+ do_check_eq(metadata.port, port);
+ do_check_eq(metadata.host, "localhost");
+ do_check_eq(metadata.path.charAt(0), "/");
+
+ var body = "this is text\n";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+// /lotsOfHeaders
+function lotsOfHeadersHandler(request, response)
+{
+ response.setHeader("Content-Type", "text/plain", false);
+
+ for (var i = 0; i < HEADER_COUNT; i++)
+ response.setHeader("X-Header-" + i, "value " + i, false);
+}
diff --git a/netwerk/test/httpserver/test/test_body_length.js b/netwerk/test/httpserver/test/test_body_length.js
new file mode 100644
index 000000000..0fd2236d7
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_body_length.js
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+/*
+ * Tests that the Content-Length header in incoming requests is interpreted as
+ * a decimal number, even if it has the form (including leading zero) of an
+ * octal number.
+ */
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+ srv.registerPathHandler("/content-length", contentLength);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+const REQUEST_DATA = "12345678901234567";
+
+function contentLength(request, response)
+{
+ do_check_eq(request.method, "POST");
+ do_check_eq(request.getHeader("Content-Length"), "017");
+
+ var body = new ScriptableInputStream(request.bodyInputStream);
+
+ var avail;
+ var data = "";
+ while ((avail = body.available()) > 0)
+ data += body.read(avail);
+
+ do_check_eq(data, REQUEST_DATA);
+}
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+XPCOMUtils.defineLazyGetter(this, 'tests', function() {
+ return [
+ new Test("http://localhost:" + srv.identity.primaryPort + "/content-length",
+ init_content_length),
+ ];
+});
+
+function init_content_length(ch)
+{
+ var content = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ content.data = REQUEST_DATA;
+
+ ch.QueryInterface(Ci.nsIUploadChannel)
+ .setUploadStream(content, "text/plain", REQUEST_DATA.length);
+
+ // Override the values implicitly set by setUploadStream above.
+ ch.requestMethod = "POST";
+ ch.setRequestHeader("Content-Length", "017", false); // 17 bytes, not 15
+}
diff --git a/netwerk/test/httpserver/test/test_byte_range.js b/netwerk/test/httpserver/test/test_byte_range.js
new file mode 100644
index 000000000..53d23e522
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_byte_range.js
@@ -0,0 +1,278 @@
+/* -*- 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/. */
+
+// checks if a byte range request and non-byte range request retrieve the
+// correct data.
+
+var srv;
+XPCOMUtils.defineLazyGetter(this, "PREFIX", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(PREFIX + "/range.txt",
+ init_byterange, start_byterange, stop_byterange),
+ new Test(PREFIX + "/range.txt",
+ init_byterange2, start_byterange2),
+ new Test(PREFIX + "/range.txt",
+ init_byterange3, start_byterange3, stop_byterange3),
+ new Test(PREFIX + "/range.txt",
+ init_byterange4, start_byterange4),
+ new Test(PREFIX + "/range.txt",
+ init_byterange5, start_byterange5, stop_byterange5),
+ new Test(PREFIX + "/range.txt",
+ init_byterange6, start_byterange6, stop_byterange6),
+ new Test(PREFIX + "/range.txt",
+ init_byterange7, start_byterange7, stop_byterange7),
+ new Test(PREFIX + "/range.txt",
+ init_byterange8, start_byterange8, stop_byterange8),
+ new Test(PREFIX + "/range.txt",
+ init_byterange9, start_byterange9, stop_byterange9),
+ new Test(PREFIX + "/range.txt",
+ init_byterange10, start_byterange10),
+ new Test(PREFIX + "/range.txt",
+ init_byterange11, start_byterange11, stop_byterange11),
+ new Test(PREFIX + "/empty.txt",
+ null, start_byterange12, stop_byterange12),
+ new Test(PREFIX + "/headers.txt",
+ init_byterange13, start_byterange13, null),
+ new Test(PREFIX + "/range.txt",
+ null, start_normal, stop_normal)
+ ];
+});
+
+function run_test()
+{
+ srv = createServer();
+ var dir = do_get_file("data/ranges/");
+ srv.registerDirectory("/", dir);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+function start_normal(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "21");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function stop_normal(ch, cx, status, data)
+{
+ do_check_eq(data.length, 21);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[20], 0x0a);
+}
+
+function init_byterange(ch)
+{
+ ch.setRequestHeader("Range", "bytes=10-", false);
+}
+
+function start_byterange(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "11");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 10-20/21");
+}
+
+function stop_byterange(ch, cx, status, data)
+{
+ do_check_eq(data.length, 11);
+ do_check_eq(data[0], 0x64);
+ do_check_eq(data[10], 0x0a);
+}
+
+function init_byterange2(ch)
+{
+ ch.setRequestHeader("Range", "bytes=21-", false);
+}
+
+function start_byterange2(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 416);
+}
+
+function init_byterange3(ch)
+{
+ ch.setRequestHeader("Range", "bytes=10-15", false);
+}
+
+function start_byterange3(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "6");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 10-15/21");
+}
+
+function stop_byterange3(ch, cx, status, data)
+{
+ do_check_eq(data.length, 6);
+ do_check_eq(data[0], 0x64);
+ do_check_eq(data[1], 0x20);
+ do_check_eq(data[2], 0x62);
+ do_check_eq(data[3], 0x65);
+ do_check_eq(data[4], 0x20);
+ do_check_eq(data[5], 0x73);
+}
+
+function init_byterange4(ch)
+{
+ ch.setRequestHeader("Range", "xbytes=21-", false);
+}
+
+function start_byterange4(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 400);
+}
+
+function init_byterange5(ch)
+{
+ ch.setRequestHeader("Range", "bytes=-5", false);
+}
+
+function start_byterange5(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+}
+
+function stop_byterange5(ch, cx, status, data)
+{
+ do_check_eq(data.length, 5);
+ do_check_eq(data[0], 0x65);
+ do_check_eq(data[1], 0x65);
+ do_check_eq(data[2], 0x6e);
+ do_check_eq(data[3], 0x2e);
+ do_check_eq(data[4], 0x0a);
+}
+
+function init_byterange6(ch)
+{
+ ch.setRequestHeader("Range", "bytes=15-12", false);
+}
+
+function start_byterange6(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+}
+
+function stop_byterange6(ch, cx, status, data)
+{
+ do_check_eq(data.length, 21);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[20], 0x0a);
+}
+
+function init_byterange7(ch)
+{
+ ch.setRequestHeader("Range", "bytes=0-5", false);
+}
+
+function start_byterange7(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "6");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 0-5/21");
+}
+
+function stop_byterange7(ch, cx, status, data)
+{
+ do_check_eq(data.length, 6);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[1], 0x68);
+ do_check_eq(data[2], 0x69);
+ do_check_eq(data[3], 0x73);
+ do_check_eq(data[4], 0x20);
+ do_check_eq(data[5], 0x73);
+}
+
+function init_byterange8(ch)
+{
+ ch.setRequestHeader("Range", "bytes=20-21", false);
+}
+
+function start_byterange8(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 20-20/21");
+}
+
+function stop_byterange8(ch, cx, status, data)
+{
+ do_check_eq(data.length, 1);
+ do_check_eq(data[0], 0x0a);
+}
+
+function init_byterange9(ch)
+{
+ ch.setRequestHeader("Range", "bytes=020-021", false);
+}
+
+function start_byterange9(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+}
+
+function stop_byterange9(ch, cx, status, data)
+{
+ do_check_eq(data.length, 1);
+ do_check_eq(data[0], 0x0a);
+}
+
+function init_byterange10(ch)
+{
+ ch.setRequestHeader("Range", "bytes=-", false);
+}
+
+function start_byterange10(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 400);
+}
+
+function init_byterange11(ch)
+{
+ ch.setRequestHeader("Range", "bytes=-500", false);
+}
+
+function start_byterange11(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+}
+
+function stop_byterange11(ch, cx, status, data)
+{
+ do_check_eq(data.length, 21);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[20], 0x0a);
+}
+
+function start_byterange12(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "0");
+}
+
+function stop_byterange12(ch, cx, status, data)
+{
+ do_check_eq(data.length, 0);
+}
+
+function init_byterange13(ch)
+{
+ ch.setRequestHeader("Range", "bytes=9999999-", false);
+}
+
+function start_byterange13(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 416);
+ do_check_eq(ch.getResponseHeader("X-SJS-Header"), "customized");
+}
diff --git a/netwerk/test/httpserver/test/test_cern_meta.js b/netwerk/test/httpserver/test/test_cern_meta.js
new file mode 100644
index 000000000..54062bc3e
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_cern_meta.js
@@ -0,0 +1,76 @@
+/* -*- 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/. */
+
+// exercises support for mod_cern_meta-style header/status line modification
+var srv;
+
+XPCOMUtils.defineLazyGetter(this, 'PREFIX', function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, 'tests', function() {
+ return [
+ new Test(PREFIX + "/test_both.html",
+ null, start_testBoth, null),
+ new Test(PREFIX + "/test_ctype_override.txt",
+ null, start_test_ctype_override_txt, null),
+ new Test(PREFIX + "/test_status_override.html",
+ null, start_test_status_override_html, null),
+ new Test(PREFIX + "/test_status_override_nodesc.txt",
+ null, start_test_status_override_nodesc_txt, null),
+ new Test(PREFIX + "/caret_test.txt^",
+ null, start_caret_test_txt_, null)
+ ];
+});
+
+function run_test()
+{
+ srv = createServer();
+
+ var cernDir = do_get_file("data/cern_meta/");
+ srv.registerDirectory("/", cernDir);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+// TEST DATA
+
+function start_testBoth(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 501);
+ do_check_eq(ch.responseStatusText, "Unimplemented");
+
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function start_test_ctype_override_txt(ch, cx)
+{
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/html");
+}
+
+function start_test_status_override_html(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 404);
+ do_check_eq(ch.responseStatusText, "Can't Find This");
+}
+
+function start_test_status_override_nodesc_txt(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 732);
+ do_check_eq(ch.responseStatusText, "");
+}
+
+function start_caret_test_txt_(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 500);
+ do_check_eq(ch.responseStatusText, "This Isn't A Server Error");
+
+ do_check_eq(ch.getResponseHeader("Foo-RFC"), "3092");
+ do_check_eq(ch.getResponseHeader("Shaving-Cream-Atom"), "Illudium Phosdex");
+}
diff --git a/netwerk/test/httpserver/test/test_default_index_handler.js b/netwerk/test/httpserver/test/test_default_index_handler.js
new file mode 100644
index 000000000..c2c1b4e73
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_default_index_handler.js
@@ -0,0 +1,290 @@
+/* -*- 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/. */
+
+// checks for correct output with the default index handler, mostly to do
+// escaping checks -- highly dependent on the default index handler output
+// format
+
+var srv, dir, dirEntries;
+
+XPCOMUtils.defineLazyGetter(this, 'BASE_URL', function() {
+ return "http://localhost:" + srv.identity.primaryPort + "/";
+});
+
+function run_test()
+{
+ createTestDirectory();
+
+ srv = createServer();
+ srv.registerDirectory("/", dir);
+
+ var nameDir = do_get_file("data/name-scheme/");
+ srv.registerDirectory("/bar/", nameDir);
+
+ srv.start(-1);
+
+ function done()
+ {
+ do_test_pending();
+ destroyTestDirectory();
+ srv.stop(function() { do_test_finished(); });
+ }
+
+ runHttpTests(tests, done);
+}
+
+function createTestDirectory()
+{
+ dir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ dir.append("index_handler_test_" + Math.random());
+ dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+
+ // populate with test directories, files, etc.
+ // Files must be in expected order of display on the index page!
+
+ var files = [];
+
+ makeFile("aa_directory", true, dir, files);
+ makeFile("Ba_directory", true, dir, files);
+ makeFile("bb_directory", true, dir, files);
+ makeFile("foo", true, dir, files);
+ makeFile("a_file", false, dir, files);
+ makeFile("B_file", false, dir, files);
+ makeFile("za'z", false, dir, files);
+ makeFile("zb&z", false, dir, files);
+ makeFile("zc<q", false, dir, files);
+ makeFile('zd"q', false, dir, files);
+ makeFile("ze%g", false, dir, files);
+ makeFile("zf%200h", false, dir, files);
+ makeFile("zg>m", false, dir, files);
+
+ dirEntries = [files];
+
+ var subdir = dir.clone();
+ subdir.append("foo");
+
+ files = [];
+
+ makeFile("aa_dir", true, subdir, files);
+ makeFile("b_dir", true, subdir, files);
+ makeFile("AA_file.txt", false, subdir, files);
+ makeFile("test.txt", false, subdir, files);
+
+ dirEntries.push(files);
+}
+
+function destroyTestDirectory()
+{
+ dir.remove(true);
+}
+
+
+/*************
+ * UTILITIES *
+ *************/
+
+/** Verifies data in bytes for the trailing-caret path above. */
+function hiddenDataCheck(bytes, uri, path)
+{
+ var data = String.fromCharCode.apply(null, bytes);
+
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+
+ // Note: the index format isn't XML -- it's actually HTML -- but we require
+ // the index format also be valid XML, albeit XML without namespaces,
+ // XML declarations, etc. Doing this simplifies output checking.
+ try
+ {
+ var doc = parser.parseFromString(data, "application/xml");
+ }
+ catch (e)
+ {
+ do_throw("document failed to parse as XML");
+ }
+
+ // See all the .QueryInterface()s and .item()s happening here? That's because
+ // xpcshell sucks and doesn't have classinfo, so no automatic interface
+ // flattening or array-style access to items in NodeLists. Suck.
+
+ var body = doc.documentElement.getElementsByTagName("body");
+ do_check_eq(body.length, 1);
+ body = body.item(0);
+
+ // header
+ var header = body.QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("h1");
+ do_check_eq(header.length, 1);
+
+ do_check_eq(header.item(0).QueryInterface(Ci.nsIDOMNode).textContent, path);
+
+ // files
+ var lst = body.getElementsByTagName("ol");
+ do_check_eq(lst.length, 1);
+ var items = lst.item(0).QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("li");
+
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ var top = ios.newURI(uri, null, null);
+
+ // N.B. No ERROR_IF_SEE_THIS.txt^ file!
+ var dirEntries = [{name: "file.txt", isDirectory: false},
+ {name: "SHOULD_SEE_THIS.txt^", isDirectory: false}];
+
+ for (var i = 0; i < items.length; i++)
+ {
+ var link = items.item(i)
+ .childNodes
+ .item(0)
+ .QueryInterface(Ci.nsIDOMNode)
+ .QueryInterface(Ci.nsIDOMElement);
+ var f = dirEntries[i];
+
+ var sep = f.isDirectory ? "/" : "";
+
+ do_check_eq(link.textContent, f.name + sep);
+
+ uri = ios.newURI(link.getAttribute("href"), null, top);
+ do_check_eq(decodeURIComponent(uri.path), path + f.name + sep);
+ }
+}
+
+/**
+ * Verifies data in bytes (an array of bytes) represents an index page for the
+ * given URI and path, which should be a page listing the given directory
+ * entries, in order.
+ *
+ * @param bytes
+ * array of bytes representing the index page's contents
+ * @param uri
+ * string which is the URI of the index page
+ * @param path
+ * the path portion of uri
+ * @param dirEntries
+ * sorted (in the manner the directory entries should be sorted) array of
+ * objects, each of which has a name property (whose value is the file's name,
+ * without / if it's a directory) and an isDirectory property (with expected
+ * value)
+ */
+function dataCheck(bytes, uri, path, dirEntries)
+{
+ var data = String.fromCharCode.apply(null, bytes);
+
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+
+ // Note: the index format isn't XML -- it's actually HTML -- but we require
+ // the index format also be valid XML, albeit XML without namespaces,
+ // XML declarations, etc. Doing this simplifies output checking.
+ try
+ {
+ var doc = parser.parseFromString(data, "application/xml");
+ }
+ catch (e)
+ {
+ do_throw("document failed to parse as XML");
+ }
+
+ // See all the .QueryInterface()s and .item()s happening here? That's because
+ // xpcshell sucks and doesn't have classinfo, so no automatic interface
+ // flattening or array-style access to items in NodeLists. Suck.
+
+ var body = doc.documentElement.getElementsByTagName("body");
+ do_check_eq(body.length, 1);
+ body = body.item(0);
+
+ // header
+ var header = body.QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("h1");
+ do_check_eq(header.length, 1);
+
+ do_check_eq(header.item(0).QueryInterface(Ci.nsIDOMNode).textContent, path);
+
+ // files
+ var lst = body.getElementsByTagName("ol");
+ do_check_eq(lst.length, 1);
+ var items = lst.item(0).QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("li");
+
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ var dirURI = ios.newURI(uri, null, null);
+
+ for (var i = 0; i < items.length; i++)
+ {
+ var link = items.item(i)
+ .childNodes
+ .item(0)
+ .QueryInterface(Ci.nsIDOMNode)
+ .QueryInterface(Ci.nsIDOMElement);
+ var f = dirEntries[i];
+
+ var sep = f.isDirectory ? "/" : "";
+
+ do_check_eq(link.textContent, f.name + sep);
+
+ uri = ios.newURI(link.getAttribute("href"), null, top);
+ do_check_eq(decodeURIComponent(uri.path), path + f.name + sep);
+ }
+}
+
+/**
+ * Create a file/directory with the given name underneath parentDir, and
+ * append an object with name/isDirectory properties to lst corresponding
+ * to it if the file/directory could be created.
+ */
+function makeFile(name, isDirectory, parentDir, lst)
+{
+ var type = Ci.nsIFile[isDirectory ? "DIRECTORY_TYPE" : "NORMAL_FILE_TYPE"];
+ var file = parentDir.clone();
+
+ try
+ {
+ file.append(name);
+ file.create(type, 0o755);
+ lst.push({name: name, isDirectory: isDirectory});
+ }
+ catch (e) { /* OS probably doesn't like file name, skip */ }
+}
+
+/*********
+ * TESTS *
+ *********/
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(BASE_URL, null, start, stopRootDirectory),
+ new Test(BASE_URL + "foo/", null, start, stopFooDirectory),
+ new Test(BASE_URL + "bar/folder^/", null, start, stopTrailingCaretDirectory),
+ ];
+});
+
+// check top-level directory listing
+function start(ch)
+{
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/html;charset=utf-8");
+}
+function stopRootDirectory(ch, cx, status, data)
+{
+ dataCheck(data, BASE_URL, "/", dirEntries[0]);
+}
+
+// check non-top-level, too
+function stopFooDirectory(ch, cx, status, data)
+{
+ dataCheck(data, BASE_URL + "foo/", "/foo/", dirEntries[1]);
+}
+
+// trailing-caret leaf with hidden files
+function stopTrailingCaretDirectory(ch, cx, status, data)
+{
+ hiddenDataCheck(data, BASE_URL + "bar/folder^/", "/bar/folder^/");
+}
diff --git a/netwerk/test/httpserver/test/test_empty_body.js b/netwerk/test/httpserver/test/test_empty_body.js
new file mode 100644
index 000000000..85cc3d345
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_empty_body.js
@@ -0,0 +1,55 @@
+/* -*- 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/. */
+
+// in its original incarnation, the server didn't like empty response-bodies;
+// see the comment in _end for details
+
+var srv;
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test("http://localhost:" + srv.identity.primaryPort + "/empty-body-unwritten",
+ null, ensureEmpty, null),
+ new Test("http://localhost:" + srv.identity.primaryPort + "/empty-body-written",
+ null, ensureEmpty, null),
+ ];
+});
+
+function run_test()
+{
+ srv = createServer();
+
+ // register a few test paths
+ srv.registerPathHandler("/empty-body-unwritten", emptyBodyUnwritten);
+ srv.registerPathHandler("/empty-body-written", emptyBodyWritten);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// TEST DATA
+
+function ensureEmpty(ch, cx)
+{
+ do_check_true(ch.contentLength == 0);
+}
+
+// PATH HANDLERS
+
+// /empty-body-unwritten
+function emptyBodyUnwritten(metadata, response)
+{
+ response.setStatusLine("1.1", 200, "OK");
+}
+
+// /empty-body-written
+function emptyBodyWritten(metadata, response)
+{
+ response.setStatusLine("1.1", 200, "OK");
+ var body = "";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/httpserver/test/test_errorhandler_exception.js b/netwerk/test/httpserver/test/test_errorhandler_exception.js
new file mode 100644
index 000000000..c70dd1f11
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_errorhandler_exception.js
@@ -0,0 +1,84 @@
+/* -*- 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/. */
+
+// Request handlers may throw exceptions, and those exception should be caught
+// by the server and converted into the proper error codes.
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test("http://localhost:" + srv.identity.primaryPort + "/throws/exception",
+ null, start_throws_exception, succeeded),
+ new Test("http://localhost:" + srv.identity.primaryPort +
+ "/this/file/does/not/exist/and/404s",
+ null, start_nonexistent_404_fails_so_400, succeeded),
+ new Test("http://localhost:" + srv.identity.primaryPort +
+ "/attempts/404/fails/so/400/fails/so/500s",
+ register400Handler, start_multiple_exceptions_500, succeeded),
+ ];
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ srv.registerErrorHandler(404, throwsException);
+ srv.registerPathHandler("/throws/exception", throwsException);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+// TEST DATA
+
+function checkStatusLine(channel, httpMaxVer, httpMinVer, httpCode, statusText)
+{
+ do_check_eq(channel.responseStatus, httpCode);
+ do_check_eq(channel.responseStatusText, statusText);
+
+ var respMaj = {}, respMin = {};
+ channel.getResponseVersion(respMaj, respMin);
+ do_check_eq(respMaj.value, httpMaxVer);
+ do_check_eq(respMin.value, httpMinVer);
+}
+
+function start_throws_exception(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
+}
+
+function start_nonexistent_404_fails_so_400(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 400, "Bad Request");
+}
+
+function start_multiple_exceptions_500(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
+}
+
+function succeeded(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+}
+
+function register400Handler(ch)
+{
+ srv.registerErrorHandler(400, throwsException);
+}
+
+
+// PATH HANDLERS
+
+// /throws/exception (and also a 404 and 400 error handler)
+function throwsException(metadata, response)
+{
+ throw "this shouldn't cause an exit...";
+ do_throw("Not reached!");
+}
diff --git a/netwerk/test/httpserver/test/test_header_array.js b/netwerk/test/httpserver/test/test_header_array.js
new file mode 100644
index 000000000..2933f9aa6
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_header_array.js
@@ -0,0 +1,67 @@
+/* -*- 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/. */
+
+// test that special headers are sent as an array of headers with the same name
+
+var srv;
+
+function run_test()
+{
+ srv;
+
+ srv = createServer();
+ srv.registerPathHandler("/path-handler", pathHandler);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+/************
+ * HANDLERS *
+ ************/
+
+function pathHandler(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ response.setHeader("Proxy-Authenticate", "First line 1", true);
+ response.setHeader("Proxy-Authenticate", "Second line 1", true);
+ response.setHeader("Proxy-Authenticate", "Third line 1", true);
+
+ response.setHeader("WWW-Authenticate", "Not merged line 1", false);
+ response.setHeader("WWW-Authenticate", "Not merged line 2", true);
+
+ response.setHeader("WWW-Authenticate", "First line 2", false);
+ response.setHeader("WWW-Authenticate", "Second line 2", true);
+ response.setHeader("WWW-Authenticate", "Third line 2", true);
+
+ response.setHeader("X-Single-Header-Merge", "Single 1", true);
+ response.setHeader("X-Single-Header-Merge", "Single 2", true);
+}
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test("http://localhost:" + srv.identity.primaryPort + "/path-handler",
+ null, check)
+ ];
+});
+
+function check(ch, cx)
+{
+ var headerValue;
+
+ headerValue = ch.getResponseHeader("Proxy-Authenticate");
+ do_check_eq(headerValue, "First line 1\nSecond line 1\nThird line 1");
+ headerValue = ch.getResponseHeader("WWW-Authenticate");
+ do_check_eq(headerValue, "First line 2\nSecond line 2\nThird line 2");
+ headerValue = ch.getResponseHeader("X-Single-Header-Merge");
+ do_check_eq(headerValue, "Single 1,Single 2");
+}
diff --git a/netwerk/test/httpserver/test/test_headers.js b/netwerk/test/httpserver/test/test_headers.js
new file mode 100644
index 000000000..74314a966
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_headers.js
@@ -0,0 +1,189 @@
+/* -*- 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/. */
+
+// tests for header storage in httpd.js; nsHttpHeaders is an *internal* data
+// structure and is not to be used directly outside of httpd.js itself except
+// for testing purposes
+
+
+/**
+ * Ensures that a fieldname-fieldvalue combination is a valid header.
+ *
+ * @param fieldName
+ * the name of the header
+ * @param fieldValue
+ * the value of the header
+ * @param headers
+ * an nsHttpHeaders object to use to check validity
+ */
+function assertValidHeader(fieldName, fieldValue, headers)
+{
+ try
+ {
+ headers.setHeader(fieldName, fieldValue, false);
+ }
+ catch (e)
+ {
+ do_throw("Unexpected exception thrown: " + e);
+ }
+}
+
+/**
+ * Ensures that a fieldname-fieldvalue combination is not a valid header.
+ *
+ * @param fieldName
+ * the name of the header
+ * @param fieldValue
+ * the value of the header
+ * @param headers
+ * an nsHttpHeaders object to use to check validity
+ */
+function assertInvalidHeader(fieldName, fieldValue, headers)
+{
+ try
+ {
+ headers.setHeader(fieldName, fieldValue, false);
+ throw "Setting (" + fieldName + ", " +
+ fieldValue + ") as header succeeded!";
+ }
+ catch (e)
+ {
+ if (e !== Cr.NS_ERROR_INVALID_ARG)
+ do_throw("Unexpected exception thrown: " + e);
+ }
+}
+
+
+function run_test()
+{
+ testHeaderValidity();
+ testGetHeader();
+ testHeaderEnumerator();
+ testHasHeader();
+}
+
+function testHeaderValidity()
+{
+ var headers = new nsHttpHeaders();
+
+ assertInvalidHeader("f o", "bar", headers);
+ assertInvalidHeader("f\0n", "bar", headers);
+ assertInvalidHeader("foo:", "bar", headers);
+ assertInvalidHeader("f\\o", "bar", headers);
+ assertInvalidHeader("@xml", "bar", headers);
+ assertInvalidHeader("fiz(", "bar", headers);
+ assertInvalidHeader("HTTP/1.1", "bar", headers);
+ assertInvalidHeader("b\"b", "bar", headers);
+ assertInvalidHeader("ascsd\t", "bar", headers);
+ assertInvalidHeader("{fds", "bar", headers);
+ assertInvalidHeader("baz?", "bar", headers);
+ assertInvalidHeader("a\\b\\c", "bar", headers);
+ assertInvalidHeader("\0x7F", "bar", headers);
+ assertInvalidHeader("\0x1F", "bar", headers);
+ assertInvalidHeader("f\n", "bar", headers);
+ assertInvalidHeader("foo", "b\nar", headers);
+ assertInvalidHeader("foo", "b\rar", headers);
+ assertInvalidHeader("foo", "b\0", headers);
+
+ // request splitting, fwiw -- we're actually immune to this type of attack so
+ // long as we don't implement persistent connections
+ assertInvalidHeader("f\r\nGET /badness HTTP/1.1\r\nFoo", "bar", headers);
+
+ assertValidHeader("f'", "baz", headers);
+ assertValidHeader("f`", "baz", headers);
+ assertValidHeader("f.", "baz", headers);
+ assertValidHeader("f---", "baz", headers);
+ assertValidHeader("---", "baz", headers);
+ assertValidHeader("~~~", "baz", headers);
+ assertValidHeader("~~~", "b\r\n bar", headers);
+ assertValidHeader("~~~", "b\r\n\tbar", headers);
+}
+
+function testGetHeader()
+{
+ var headers = new nsHttpHeaders();
+
+ headers.setHeader("Content-Type", "text/html", false);
+ var c = headers.getHeader("content-type");
+ do_check_eq(c, "text/html");
+
+ headers.setHeader("test", "FOO", false);
+ var c = headers.getHeader("test");
+ do_check_eq(c, "FOO");
+
+ try
+ {
+ headers.getHeader(":");
+ throw "Failed to throw for invalid header";
+ }
+ catch (e)
+ {
+ if (e !== Cr.NS_ERROR_INVALID_ARG)
+ do_throw("headers.getHeader(':') must throw invalid arg");
+ }
+
+ try
+ {
+ headers.getHeader("valid");
+ throw 'header doesn\'t exist';
+ }
+ catch (e)
+ {
+ if (e !== Cr.NS_ERROR_NOT_AVAILABLE)
+ do_throw("shouldn't be a header named 'valid' in headers!");
+ }
+}
+
+function testHeaderEnumerator()
+{
+ var headers = new nsHttpHeaders();
+
+ var heads =
+ {
+ "foo": "17",
+ "baz": "two six niner",
+ "decaf": "class Program { int .7; int main(){ .7 = 5; return 7 - .7; } }"
+ };
+
+ for (var i in heads)
+ headers.setHeader(i, heads[i], false);
+
+ var en = headers.enumerator;
+ while (en.hasMoreElements())
+ {
+ var it = en.getNext().QueryInterface(Ci.nsISupportsString).data;
+ do_check_true(it.toLowerCase() in heads);
+ delete heads[it.toLowerCase()];
+ }
+
+ for (var i in heads)
+ do_throw("still have properties in heads!?!?");
+
+}
+
+function testHasHeader()
+{
+ var headers = new nsHttpHeaders();
+
+ headers.setHeader("foo", "bar", false);
+ do_check_true(headers.hasHeader("foo"));
+ do_check_true(headers.hasHeader("fOo"));
+ do_check_false(headers.hasHeader("not-there"));
+
+ headers.setHeader("f`'~", "bar", false);
+ do_check_true(headers.hasHeader("F`'~"));
+
+ try
+ {
+ headers.hasHeader(":");
+ throw "failed to throw";
+ }
+ catch (e)
+ {
+ if (e !== Cr.NS_ERROR_INVALID_ARG)
+ do_throw(".hasHeader for an invalid name should throw");
+ }
+}
diff --git a/netwerk/test/httpserver/test/test_host.js b/netwerk/test/httpserver/test/test_host.js
new file mode 100644
index 000000000..503a04fef
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_host.js
@@ -0,0 +1,666 @@
+/* -*- 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/. */
+
+/**
+ * Tests that the scheme, host, and port of the server are correctly recorded
+ * and used in HTTP requests and responses.
+ */
+
+const PORT = 4444;
+const FAKE_PORT_ONE = 8888;
+const FAKE_PORT_TWO = 8889;
+
+var srv, id;
+
+function run_test()
+{
+ dumpn("*** run_test");
+
+ srv = createServer();
+
+ srv.registerPathHandler("/http/1.0-request", http10Request);
+ srv.registerPathHandler("/http/1.1-good-host", http11goodHost);
+ srv.registerPathHandler("/http/1.1-good-host-wacky-port",
+ http11goodHostWackyPort);
+ srv.registerPathHandler("/http/1.1-ip-host", http11ipHost);
+
+ srv.start(FAKE_PORT_ONE);
+
+ id = srv.identity;
+
+ // The default location is http://localhost:PORT, where PORT is whatever you
+ // provided when you started the server. http://127.0.0.1:PORT is also part
+ // of the default set of locations.
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // This should be a nop.
+ id.add("http", "localhost", FAKE_PORT_ONE);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // Change the primary location and make sure all the getters work correctly.
+ id.setPrimary("http", "127.0.0.1", FAKE_PORT_ONE);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "127.0.0.1");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // Okay, now remove the primary location -- we fall back to the original
+ // location.
+ id.remove("http", "127.0.0.1", FAKE_PORT_ONE);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // You can't remove every location -- try this and the original default
+ // location will be silently readded.
+ id.remove("http", "localhost", FAKE_PORT_ONE);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // Okay, now that we've exercised that behavior, shut down the server and
+ // restart it on the correct port, to exercise port-changing behaviors at
+ // server start and stop.
+ do_test_pending();
+ srv.stop(function()
+ {
+ try
+ {
+ do_test_pending();
+ run_test_2();
+ }
+ finally
+ {
+ do_test_finished();
+ }
+ });
+}
+
+function run_test_2()
+{
+ dumpn("*** run_test_2");
+
+ do_test_finished();
+
+ // Our primary location is gone because it was dependent on the port on which
+ // the server was running.
+ checkPrimariesThrow(id);
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+
+ srv.start(FAKE_PORT_TWO);
+
+ // We should have picked up http://localhost:8889 as our primary location now
+ // that we've restarted.
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost", FAKE_PORT_TWO);
+ do_check_eq(id.primaryPort, FAKE_PORT_TWO);
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+
+ // Now we're going to see what happens when we shut down with a primary
+ // location that wasn't a default. That location should persist, and the
+ // default we remove should still not be present.
+ id.setPrimary("http", "example.com", FAKE_PORT_TWO);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_true(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+
+ id.remove("http", "localhost", FAKE_PORT_TWO);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ id.remove("http", "127.0.0.1", FAKE_PORT_TWO);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ do_test_pending();
+ srv.stop(function()
+ {
+ try
+ {
+ do_test_pending();
+ run_test_3();
+ }
+ finally
+ {
+ do_test_finished();
+ }
+ });
+}
+
+function run_test_3()
+{
+ dumpn("*** run_test_3");
+
+ do_test_finished();
+
+ // Only the default added location disappears; any others stay around,
+ // possibly as the primary location. We may have removed the default primary
+ // location, but the one we set manually should persist here.
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "example.com");
+ do_check_eq(id.primaryPort, FAKE_PORT_TWO);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ srv.start(PORT);
+
+ // Starting always adds HTTP entries for 127.0.0.1:port and localhost:port.
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "localhost", PORT));
+ do_check_true(id.has("http", "127.0.0.1", PORT));
+
+ // Remove the primary location we'd left set from last time.
+ id.remove("http", "example.com", FAKE_PORT_TWO);
+
+ // Default-port behavior testing requires the server responds to requests
+ // claiming to be on one such port.
+ id.add("http", "localhost", 80);
+
+ // Make sure we don't have anything lying around from running on either the
+ // first or the second port -- all we should have is our generated default,
+ // plus the additional port to test "portless" hostport variants.
+ do_check_true(id.has("http", "localhost", 80));
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, PORT);
+ do_check_true(id.has("http", "localhost", PORT));
+ do_check_true(id.has("http", "127.0.0.1", PORT));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+
+ // Okay, finally done with identity testing. Our primary location is the one
+ // we want it to be, so we're off!
+ runRawTests(tests, testComplete(srv));
+}
+
+
+/*********************
+ * UTILITY FUNCTIONS *
+ *********************/
+
+/**
+ * Verifies that all .primary* getters on a server identity correctly throw
+ * NS_ERROR_NOT_INITIALIZED.
+ *
+ * @param id : nsIHttpServerIdentity
+ * the server identity to test
+ */
+function checkPrimariesThrow(id)
+{
+ var threw = false;
+ try
+ {
+ id.primaryScheme;
+ }
+ catch (e)
+ {
+ threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ do_check_true(threw);
+
+ threw = false;
+ try
+ {
+ id.primaryHost;
+ }
+ catch (e)
+ {
+ threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ do_check_true(threw);
+
+ threw = false;
+ try
+ {
+ id.primaryPort;
+ }
+ catch (e)
+ {
+ threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ do_check_true(threw);
+}
+
+/**
+ * Utility function to check for a 400 response.
+ */
+function check400(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ var firstLine = iter.next();
+ do_check_eq(firstLine.substring(0, HTTP_400_LEADER_LENGTH), HTTP_400_LEADER);
+}
+
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+const HTTP_400_LEADER = "HTTP/1.1 400 ";
+const HTTP_400_LEADER_LENGTH = HTTP_400_LEADER.length;
+
+var test, data;
+var tests = [];
+
+// HTTP/1.0 request, to ensure we see our default scheme/host/port
+
+function http10Request(request, response)
+{
+ writeDetails(request, response);
+ response.setStatusLine("1.0", 200, "TEST PASSED");
+}
+data = "GET /http/1.0-request HTTP/1.0\r\n" +
+ "\r\n";
+function check10(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.0 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body =
+ [
+ "Method: GET",
+ "Path: /http/1.0-request",
+ "Query: ",
+ "Version: 1.0",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check10),
+tests.push(test);
+
+
+// HTTP/1.1 request, no Host header, expect a 400 response
+
+data = "GET /http/1.1-request HTTP/1.1\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, wrong host, expect a 400 response
+
+data = "GET /http/1.1-request HTTP/1.1\r\n" +
+ "Host: not-localhost\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, wrong host/right port, expect a 400 response
+
+data = "GET /http/1.1-request HTTP/1.1\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, Host header has host but no port, expect a 400 response
+
+data = "GET /http/1.1-request HTTP/1.1\r\n" +
+ "Host: 127.0.0.1\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, Request-URI has wrong port, expect a 400 response
+
+data = "GET http://127.0.0.1/http/1.1-request HTTP/1.1\r\n" +
+ "Host: 127.0.0.1\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, Request-URI has wrong port, expect a 400 response
+
+data = "GET http://localhost:31337/http/1.1-request HTTP/1.1\r\n" +
+ "Host: localhost:31337\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, Request-URI has wrong scheme, expect a 400 response
+
+data = "GET https://localhost:4444/http/1.1-request HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, correct Host header, expect handler's response
+
+function http11goodHost(request, response)
+{
+ writeDetails(request, response);
+ response.setStatusLine("1.1", 200, "TEST PASSED");
+}
+data = "GET /http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+function check11goodHost(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body =
+ [
+ "Method: GET",
+ "Path: /http/1.1-good-host",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check11goodHost),
+tests.push(test);
+
+
+// HTTP/1.1 request, Host header is secondary identity
+
+function http11ipHost(request, response)
+{
+ writeDetails(request, response);
+ response.setStatusLine("1.1", 200, "TEST PASSED");
+}
+data = "GET /http/1.1-ip-host HTTP/1.1\r\n" +
+ "Host: 127.0.0.1:4444\r\n" +
+ "\r\n";
+function check11ipHost(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body =
+ [
+ "Method: GET",
+ "Path: /http/1.1-ip-host",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: 127.0.0.1",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check11ipHost),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute path, accurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute path, inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: localhost:1234\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute path, different inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute path, yet another inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: yippity-skippity\r\n" +
+ "\r\n";
+function checkInaccurate(data)
+{
+ check11goodHost(data);
+
+ // dynamism setup
+ srv.identity.setPrimary("http", "127.0.0.1", 4444);
+}
+test = new RawTest("localhost", PORT, data, checkInaccurate),
+tests.push(test);
+
+
+// HTTP/1.0 request, absolute path, different inaccurate Host header
+
+// reusing previous request handler so not defining a new one
+
+data = "GET /http/1.0-request HTTP/1.0\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+function check10ip(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.0 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body =
+ [
+ "Method: GET",
+ "Path: /http/1.0-request",
+ "Query: ",
+ "Version: 1.0",
+ "Scheme: http",
+ "Host: 127.0.0.1",
+ "Port: 4444",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check10ip),
+tests.push(test);
+
+
+// HTTP/1.1 request, Host header with implied port
+
+function http11goodHostWackyPort(request, response)
+{
+ writeDetails(request, response);
+ response.setStatusLine("1.1", 200, "TEST PASSED");
+}
+data = "GET /http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+function check11goodHostWackyPort(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body =
+ [
+ "Method: GET",
+ "Path: /http/1.1-good-host-wacky-port",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: 80",
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
+tests.push(test);
+
+
+// HTTP/1.1 request, Host header with wacky implied port
+
+data = "GET /http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost:\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute URI with implied port
+
+data = "GET http://localhost/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute URI with wacky implied port
+
+data = "GET http://localhost:/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute URI with explicit implied port, ignored Host
+
+data = "GET http://localhost:80/http/1.1-good-host-wacky-port HTTP/1.1\r\n" +
+ "Host: who-cares\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHostWackyPort),
+tests.push(test);
+
+
+// HTTP/1.1 request, a malformed Request-URI
+
+data = "GET is-this-the-real-life-is-this-just-fantasy HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, a malformed Host header
+
+data = "GET /http/1.1-request HTTP/1.1\r\n" +
+ "Host: la la la\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, a malformed Host header but absolute URI, 5.2 sez fine
+
+data = "GET http://localhost:4444/http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: la la la\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check11goodHost),
+tests.push(test);
+
+
+// HTTP/1.0 request, absolute URI, but those aren't valid in HTTP/1.0
+
+data = "GET http://localhost:4444/http/1.1-request HTTP/1.0\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute URI with unrecognized host
+
+data = "GET http://not-localhost:4444/http/1.1-request HTTP/1.1\r\n" +
+ "Host: not-localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
+
+
+// HTTP/1.1 request, absolute URI with unrecognized host (but not in Host)
+
+data = "GET http://not-localhost:4444/http/1.1-request HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+test = new RawTest("localhost", PORT, data, check400),
+tests.push(test);
diff --git a/netwerk/test/httpserver/test/test_linedata.js b/netwerk/test/httpserver/test/test_linedata.js
new file mode 100644
index 000000000..49f4f8258
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_linedata.js
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+// test that the LineData internal data structure works correctly
+
+function run_test()
+{
+ var data = new LineData();
+ data.appendBytes(["a".charCodeAt(0), CR]);
+
+ var out = { value: "" };
+ do_check_false(data.readLine(out));
+
+ data.appendBytes([LF]);
+ do_check_true(data.readLine(out));
+ do_check_eq(out.value, "a");
+}
diff --git a/netwerk/test/httpserver/test/test_load_module.js b/netwerk/test/httpserver/test/test_load_module.js
new file mode 100644
index 000000000..8233833fa
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_load_module.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Ensure httpd.js can be imported as a module and that a server starts.
+ */
+function run_test() {
+ Components.utils.import("resource://testing-common/httpd.js");
+
+ let server = new HttpServer();
+ server.start(-1);
+
+ do_test_pending();
+
+ server.stop(do_test_finished);
+}
diff --git a/netwerk/test/httpserver/test/test_name_scheme.js b/netwerk/test/httpserver/test/test_name_scheme.js
new file mode 100644
index 000000000..154f73d25
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_name_scheme.js
@@ -0,0 +1,90 @@
+/* -*- 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/. */
+
+// requests for files ending with a caret (^) are handled specially to enable
+// htaccess-like functionality without the need to explicitly disable display
+// of such files
+
+var srv;
+
+XPCOMUtils.defineLazyGetter(this, "PREFIX", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(PREFIX + "/bar.html^",
+ null, start_bar_html_, null),
+ new Test(PREFIX + "/foo.html^",
+ null, start_foo_html_, null),
+ new Test(PREFIX + "/normal-file.txt",
+ null, start_normal_file_txt, null),
+ new Test(PREFIX + "/folder^/file.txt",
+ null, start_folder__file_txt, null),
+
+ new Test(PREFIX + "/foo/bar.html^",
+ null, start_bar_html_, null),
+ new Test(PREFIX + "/foo/foo.html^",
+ null, start_foo_html_, null),
+ new Test(PREFIX + "/foo/normal-file.txt",
+ null, start_normal_file_txt, null),
+ new Test(PREFIX + "/foo/folder^/file.txt",
+ null, start_folder__file_txt, null),
+
+ new Test(PREFIX + "/end-caret^/bar.html^",
+ null, start_bar_html_, null),
+ new Test(PREFIX + "/end-caret^/foo.html^",
+ null, start_foo_html_, null),
+ new Test(PREFIX + "/end-caret^/normal-file.txt",
+ null, start_normal_file_txt, null),
+ new Test(PREFIX + "/end-caret^/folder^/file.txt",
+ null, start_folder__file_txt, null)
+ ];
+});
+
+
+function run_test()
+{
+ srv = createServer();
+
+ // make sure underscores work in directories "mounted" in directories with
+ // folders starting with _
+ var nameDir = do_get_file("data/name-scheme/");
+ srv.registerDirectory("/", nameDir);
+ srv.registerDirectory("/foo/", nameDir);
+ srv.registerDirectory("/end-caret^/", nameDir);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+// TEST DATA
+
+function start_bar_html_(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/html");
+}
+
+function start_foo_html_(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 404);
+}
+
+function start_normal_file_txt(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function start_folder__file_txt(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+}
diff --git a/netwerk/test/httpserver/test/test_processasync.js b/netwerk/test/httpserver/test/test_processasync.js
new file mode 100644
index 000000000..21ded660d
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_processasync.js
@@ -0,0 +1,304 @@
+/* -*- 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/. */
+
+/*
+ * Tests for correct behavior of asynchronous responses.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "PREPATH", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+ for (var path in handlers)
+ srv.registerPathHandler(path, handlers[path]);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(PREPATH + "/handleSync", null, start_handleSync, null),
+ new Test(PREPATH + "/handleAsync1", null, start_handleAsync1,
+ stop_handleAsync1),
+ new Test(PREPATH + "/handleAsync2", init_handleAsync2, start_handleAsync2,
+ stop_handleAsync2),
+ new Test(PREPATH + "/handleAsyncOrdering", null, null,
+ stop_handleAsyncOrdering)
+ ];
+});
+
+var handlers = {};
+
+function handleSync(request, response)
+{
+ response.setStatusLine(request.httpVersion, 500, "handleSync fail");
+
+ try
+ {
+ response.finish();
+ do_throw("finish called on sync response");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_UNEXPECTED);
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "handleSync pass");
+}
+handlers["/handleSync"] = handleSync;
+
+function start_handleSync(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "handleSync pass");
+}
+
+function handleAsync1(request, response)
+{
+ response.setStatusLine(request.httpVersion, 500, "Old status line!");
+ response.setHeader("X-Foo", "old value", false);
+
+ response.processAsync();
+
+ response.setStatusLine(request.httpVersion, 200, "New status line!");
+ response.setHeader("X-Foo", "new value", false);
+
+ response.finish();
+
+ try
+ {
+ response.setStatusLine(request.httpVersion, 500, "Too late!");
+ do_throw("late setStatusLine didn't throw");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try
+ {
+ response.setHeader("X-Foo", "late value", false);
+ do_throw("late setHeader didn't throw");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try
+ {
+ response.bodyOutputStream;
+ do_throw("late bodyOutputStream get didn't throw");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try
+ {
+ response.write("fugly");
+ do_throw("late write() didn't throw");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+}
+handlers["/handleAsync1"] = handleAsync1;
+
+function start_handleAsync1(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "New status line!");
+ do_check_eq(ch.getResponseHeader("X-Foo"), "new value");
+}
+
+function stop_handleAsync1(ch, cx, status, data)
+{
+ do_check_eq(data.length, 0);
+}
+
+const startToHeaderDelay = 500;
+const startToFinishedDelay = 750;
+
+function handleAsync2(request, response)
+{
+ response.processAsync();
+
+ response.setStatusLine(request.httpVersion, 200, "Status line");
+ response.setHeader("X-Custom-Header", "value", false);
+
+ callLater(startToHeaderDelay, function()
+ {
+ var body = "BO";
+ response.bodyOutputStream.write(body, body.length);
+
+ try
+ {
+ response.setStatusLine(request.httpVersion, 500, "after body write");
+ do_throw("setStatusLine succeeded");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try
+ {
+ response.setHeader("X-Custom-Header", "new 1", false);
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ callLater(startToFinishedDelay - startToHeaderDelay, function()
+ {
+ var body = "DY";
+ response.bodyOutputStream.write(body, body.length);
+
+ response.finish();
+ response.finish(); // idempotency
+
+ try
+ {
+ response.setStatusLine(request.httpVersion, 500, "after finish");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try
+ {
+ response.setHeader("X-Custom-Header", "new 2", false);
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ try
+ {
+ response.write("EVIL");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ });
+ });
+}
+handlers["/handleAsync2"] = handleAsync2;
+
+var startTime_handleAsync2;
+
+function init_handleAsync2(ch)
+{
+ var now = startTime_handleAsync2 = Date.now();
+ dumpn("*** init_HandleAsync2: start time " + now);
+}
+
+function start_handleAsync2(ch, cx)
+{
+ var now = Date.now();
+ dumpn("*** start_handleAsync2: onStartRequest time " + now + ", " +
+ (now - startTime_handleAsync2) + "ms after start time");
+ do_check_true(now >= startTime_handleAsync2 + startToHeaderDelay);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "Status line");
+ do_check_eq(ch.getResponseHeader("X-Custom-Header"), "value");
+}
+
+function stop_handleAsync2(ch, cx, status, data)
+{
+ var now = Date.now();
+ dumpn("*** stop_handleAsync2: onStopRequest time " + now + ", " +
+ (now - startTime_handleAsync2) + "ms after header time");
+ do_check_true(now >= startTime_handleAsync2 + startToFinishedDelay);
+
+ do_check_eq(String.fromCharCode.apply(null, data), "BODY");
+}
+
+/*
+ * Tests that accessing output stream *before* calling processAsync() works
+ * correctly, sending written data immediately as it is written, not buffering
+ * until finish() is called -- which for this much data would mean we would all
+ * but certainly deadlock, since we're trying to read/write all this data in one
+ * process on a single thread.
+ */
+function handleAsyncOrdering(request, response)
+{
+ var out = new BinaryOutputStream(response.bodyOutputStream);
+
+ var data = [];
+ for (var i = 0; i < 65536; i++)
+ data[i] = 0;
+ var count = 20;
+
+ var writeData =
+ {
+ run: function()
+ {
+ if (count-- === 0)
+ {
+ response.finish();
+ return;
+ }
+
+ try
+ {
+ out.writeByteArray(data, data.length);
+ step();
+ }
+ catch (e)
+ {
+ try
+ {
+ do_throw("error writing data: " + e);
+ }
+ finally
+ {
+ response.finish();
+ }
+ }
+ }
+ };
+ function step()
+ {
+ // Use gThreadManager here because it's expedient, *not* because it's
+ // intended for public use! If you do this in client code, expect me to
+ // knowingly break your code by changing the variable name. :-P
+ gThreadManager.currentThread
+ .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ step();
+ response.processAsync();
+}
+handlers["/handleAsyncOrdering"] = handleAsyncOrdering;
+
+function stop_handleAsyncOrdering(ch, cx, status, data)
+{
+ do_check_eq(data.length, 20 * 65536);
+ data.forEach(function(v, index)
+ {
+ if (v !== 0)
+ do_throw("value " + v + " at index " + index + " should be zero");
+ });
+}
diff --git a/netwerk/test/httpserver/test/test_qi.js b/netwerk/test/httpserver/test/test_qi.js
new file mode 100644
index 000000000..aeac79d89
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_qi.js
@@ -0,0 +1,110 @@
+/* -*- 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/. */
+
+/*
+ * Verify the presence of explicit QueryInterface methods on XPCOM objects
+ * exposed by httpd.js, rather than allowing QueryInterface to be implicitly
+ * created by XPConnect.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test("http://localhost:" + srv.identity.primaryPort + "/test",
+ null, start_test, null),
+ new Test("http://localhost:" + srv.identity.primaryPort + "/sjs/qi.sjs",
+ null, start_sjs_qi, null),
+ ];
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ var qi;
+ try
+ {
+ qi = srv.identity.QueryInterface(Ci.nsIHttpServerIdentity);
+ }
+ catch (e)
+ {
+ var exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ do_throw("server identity didn't QI: " + exstr);
+ return;
+ }
+
+ srv.registerPathHandler("/test", testHandler);
+ srv.registerDirectory("/", do_get_file("data/"));
+ srv.registerContentType("sjs", "sjs");
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+// TEST DATA
+
+function start_test(ch, cx)
+{
+ do_check_eq(ch.responseStatusText, "QI Tests Passed");
+ do_check_eq(ch.responseStatus, 200);
+}
+
+function start_sjs_qi(ch, cx)
+{
+ do_check_eq(ch.responseStatusText, "SJS QI Tests Passed");
+ do_check_eq(ch.responseStatus, 200);
+}
+
+
+function testHandler(request, response)
+{
+ var exstr;
+ var qid;
+
+ response.setStatusLine(request.httpVersion, 500, "FAIL");
+
+ var passed = false;
+ try
+ {
+ qid = request.QueryInterface(Ci.nsIHttpRequest);
+ passed = qid === request;
+ }
+ catch (e)
+ {
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(request.httpVersion, 500,
+ "request doesn't QI: " + exstr);
+ return;
+ }
+ if (!passed)
+ {
+ response.setStatusLine(request.httpVersion, 500, "request QI'd wrongly?");
+ return;
+ }
+
+ passed = false;
+ try
+ {
+ qid = response.QueryInterface(Ci.nsIHttpResponse);
+ passed = qid === response;
+ }
+ catch (e)
+ {
+ exstr = ("" + e).split(/[\x09\x20-\x7f\x81-\xff]+/)[0];
+ response.setStatusLine(request.httpVersion, 500,
+ "response doesn't QI: " + exstr);
+ return;
+ }
+ if (!passed)
+ {
+ response.setStatusLine(request.httpVersion, 500, "response QI'd wrongly?");
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "QI Tests Passed");
+}
diff --git a/netwerk/test/httpserver/test/test_registerdirectory.js b/netwerk/test/httpserver/test/test_registerdirectory.js
new file mode 100644
index 000000000..fbb41293e
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerdirectory.js
@@ -0,0 +1,263 @@
+/* -*- 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/. */
+
+// tests the registerDirectory API
+
+XPCOMUtils.defineLazyGetter(this, "BASE", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+
+function nocache(ch)
+{
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // important!
+}
+
+function notFound(ch)
+{
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+}
+
+function checkOverride(ch)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "OK");
+ do_check_true(ch.requestSucceeded);
+ do_check_eq(ch.getResponseHeader("Override-Succeeded"), "yes");
+}
+
+function check200(ch)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "OK");
+}
+
+function checkFile(ch, cx, status, data)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+
+ var actualFile = serverBasePath.clone();
+ actualFile.append("test_registerdirectory.js");
+ do_check_eq(ch.getResponseHeader("Content-Length"),
+ actualFile.fileSize.toString());
+ do_check_eq(data.map(v => String.fromCharCode(v)).join(""),
+ fileContents(actualFile));
+}
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+
+/***********************
+ * without a base path *
+ ***********************/
+ new Test(BASE + "/test_registerdirectory.js",
+ nocache, notFound, null),
+
+/********************
+ * with a base path *
+ ********************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ serverBasePath = testsDirectory.clone();
+ srv.registerDirectory("/", serverBasePath);
+ },
+ null,
+ checkFile),
+
+/*****************************
+ * without a base path again *
+ *****************************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ serverBasePath = null;
+ srv.registerDirectory("/", serverBasePath);
+ },
+ notFound,
+ null),
+
+/***************************
+ * registered path handler *
+ ***************************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ srv.registerPathHandler("/test_registerdirectory.js",
+ override_test_registerdirectory);
+ },
+ checkOverride,
+ null),
+
+/************************
+ * removed path handler *
+ ************************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function init_registerDirectory6(ch)
+ {
+ nocache(ch);
+ srv.registerPathHandler("/test_registerdirectory.js", null);
+ },
+ notFound,
+ null),
+
+/********************
+ * with a base path *
+ ********************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+
+ // set the base path again
+ serverBasePath = testsDirectory.clone();
+ srv.registerDirectory("/", serverBasePath);
+ },
+ null,
+ checkFile),
+
+/*************************
+ * ...and a path handler *
+ *************************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ srv.registerPathHandler("/test_registerdirectory.js",
+ override_test_registerdirectory);
+ },
+ checkOverride,
+ null),
+
+/************************
+ * removed base handler *
+ ************************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ serverBasePath = null;
+ srv.registerDirectory("/", serverBasePath);
+ },
+ checkOverride,
+ null),
+
+/************************
+ * removed path handler *
+ ************************/
+ new Test(BASE + "/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ srv.registerPathHandler("/test_registerdirectory.js", null);
+ },
+ notFound,
+ null),
+
+/*************************
+ * mapping set up, works *
+ *************************/
+ new Test(BASE + "/foo/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ serverBasePath = testsDirectory.clone();
+ srv.registerDirectory("/foo/", serverBasePath);
+ },
+ check200,
+ null),
+
+/*********************
+ * no mapping, fails *
+ *********************/
+ new Test(BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ nocache,
+ notFound,
+ null),
+
+/******************
+ * mapping, works *
+ ******************/
+ new Test(BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ srv.registerDirectory("/foo/test_registerdirectory.js/",
+ serverBasePath);
+ },
+ null,
+ checkFile),
+
+/************************************
+ * two mappings set up, still works *
+ ************************************/
+ new Test(BASE + "/foo/test_registerdirectory.js",
+ nocache, null, checkFile),
+
+/**************************
+ * remove topmost mapping *
+ **************************/
+ new Test(BASE + "/foo/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ srv.registerDirectory("/foo/", null);
+ },
+ notFound,
+ null),
+
+/**************************************
+ * lower mapping still present, works *
+ **************************************/
+ new Test(BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ nocache, null, checkFile),
+
+/*******************
+ * mapping removed *
+ *******************/
+ new Test(BASE + "/foo/test_registerdirectory.js/test_registerdirectory.js",
+ function(ch)
+ {
+ nocache(ch);
+ srv.registerDirectory("/foo/test_registerdirectory.js/", null);
+ },
+ notFound,
+ null)
+ ];
+});
+
+
+var srv;
+var serverBasePath;
+var testsDirectory;
+
+function run_test()
+{
+ testsDirectory = do_get_cwd();
+
+ srv = createServer();
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+// PATH HANDLERS
+
+// override of /test_registerdirectory.js
+function override_test_registerdirectory(metadata, response)
+{
+ response.setStatusLine("1.1", 200, "OK");
+ response.setHeader("Override-Succeeded", "yes", false);
+
+ var body = "success!";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/netwerk/test/httpserver/test/test_registerfile.js b/netwerk/test/httpserver/test/test_registerfile.js
new file mode 100644
index 000000000..16a1270f5
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerfile.js
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+// tests the registerFile API
+
+XPCOMUtils.defineLazyGetter(this, "BASE", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var file = do_get_file("test_registerfile.js");
+
+function onStart(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+}
+
+function onStop(ch, cx, status, data)
+{
+ // not sufficient for equality, but not likely to be wrong!
+ do_check_eq(data.length, file.fileSize);
+}
+
+XPCOMUtils.defineLazyGetter(this, "test", function() {
+ return new Test(BASE + "/foo", null, onStart, onStop);
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ try
+ {
+ srv.registerFile("/foo", do_get_profile());
+ throw "registerFile succeeded!";
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ srv.registerFile("/foo", file);
+ srv.start(-1);
+
+ runHttpTests([test], testComplete(srv));
+}
diff --git a/netwerk/test/httpserver/test/test_registerprefix.js b/netwerk/test/httpserver/test/test_registerprefix.js
new file mode 100644
index 000000000..fa3c3390a
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerprefix.js
@@ -0,0 +1,127 @@
+/* -*- 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/. */
+
+// tests the registerPrefixHandler API
+
+XPCOMUtils.defineLazyGetter(this, "BASE", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+function nocache(ch)
+{
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // important!
+}
+
+function notFound(ch)
+{
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+}
+
+function makeCheckOverride(magic)
+{
+ return (function checkOverride(ch)
+ {
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "OK");
+ do_check_true(ch.requestSucceeded);
+ do_check_eq(ch.getResponseHeader("Override-Succeeded"), magic);
+ });
+}
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(BASE + "/prefix/dummy", prefixHandler, null,
+ makeCheckOverride("prefix")),
+ new Test(BASE + "/prefix/dummy", pathHandler, null,
+ makeCheckOverride("path")),
+ new Test(BASE + "/prefix/subpath/dummy", longerPrefixHandler, null,
+ makeCheckOverride("subpath")),
+ new Test(BASE + "/prefix/dummy", removeHandlers, null, notFound),
+ new Test(BASE + "/prefix/subpath/dummy", newPrefixHandler, null,
+ makeCheckOverride("subpath"))
+ ];
+});
+
+/***************************
+ * registered prefix handler *
+ ***************************/
+
+function prefixHandler(channel)
+{
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/", makeOverride("prefix"));
+}
+
+/********************************
+ * registered path handler on top *
+ ********************************/
+
+function pathHandler(channel)
+{
+ nocache(channel);
+ srv.registerPathHandler("/prefix/dummy", makeOverride("path"));
+}
+
+/**********************************
+ * registered longer prefix handler *
+ **********************************/
+
+function longerPrefixHandler(channel)
+{
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/subpath/", makeOverride("subpath"));
+}
+
+/************************
+ * removed prefix handler *
+ ************************/
+
+function removeHandlers(channel)
+{
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/", null);
+ srv.registerPathHandler("/prefix/dummy", null);
+}
+
+/*****************************
+ * re-register shorter handler *
+ *****************************/
+
+function newPrefixHandler(channel)
+{
+ nocache(channel);
+ srv.registerPrefixHandler("/prefix/", makeOverride("prefix"));
+}
+
+var srv;
+var serverBasePath;
+var testsDirectory;
+
+function run_test()
+{
+ testsDirectory = do_get_profile();
+
+ srv = createServer();
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+// PATH HANDLERS
+
+// generate an override
+function makeOverride(magic)
+{
+ return (function override(metadata, response)
+ {
+ response.setStatusLine("1.1", 200, "OK");
+ response.setHeader("Override-Succeeded", magic, false);
+
+ var body = "success!";
+ response.bodyOutputStream.write(body, body.length);
+ });
+}
diff --git a/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js b/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js
new file mode 100644
index 000000000..b1a863f48
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js
@@ -0,0 +1,135 @@
+/* -*- 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/. */
+
+/**
+ * Tests that even when an incoming request's data for the Request-Line doesn't
+ * all fit in a single onInputStreamReady notification, the request is handled
+ * properly.
+ */
+
+var srv = createServer();
+srv.start(-1);
+const PORT = srv.identity.primaryPort;
+
+function run_test()
+{
+ srv.registerPathHandler("/lots-of-leading-blank-lines",
+ lotsOfLeadingBlankLines);
+ srv.registerPathHandler("/very-long-request-line",
+ veryLongRequestLine);
+
+ runRawTests(tests, testComplete(srv));
+}
+
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+var test, data, str;
+var tests = [];
+
+
+function veryLongRequestLine(request, response)
+{
+ writeDetails(request, response);
+ response.setStatusLine(request.httpVersion, 200, "TEST PASSED");
+}
+
+var path = "/very-long-request-line?";
+var reallyLong = "0123456789ABCDEF0123456789ABCDEF"; // 32
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 128
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 512
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 2048
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 8192
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 32768
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 131072
+reallyLong = reallyLong + reallyLong + reallyLong + reallyLong; // 524288
+if (reallyLong.length !== 524288)
+ throw new TypeError("generated length not as long as expected");
+str = "GET /very-long-request-line?" + reallyLong + " HTTP/1.1\r\n" +
+ "Host: localhost:" + PORT + "\r\n" +
+ "\r\n";
+data = [];
+for (var i = 0; i < str.length; i += 16384)
+ data.push(str.substr(i, 16384));
+
+function checkVeryLongRequestLine(data)
+{
+ var iter = LineIterator(data);
+
+ print("data length: " + data.length);
+ print("iter object: " + iter);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body =
+ [
+ "Method: GET",
+ "Path: /very-long-request-line",
+ "Query: " + reallyLong,
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: " + PORT,
+ ];
+
+ expectLines(iter, body);
+}
+test = new RawTest("localhost", PORT, data, checkVeryLongRequestLine),
+tests.push(test);
+
+
+function lotsOfLeadingBlankLines(request, response)
+{
+ writeDetails(request, response);
+ response.setStatusLine(request.httpVersion, 200, "TEST PASSED");
+}
+
+var blankLines = "\r\n";
+for (var i = 0; i < 14; i++)
+ blankLines += blankLines;
+str = blankLines +
+ "GET /lots-of-leading-blank-lines HTTP/1.1\r\n" +
+ "Host: localhost:" + PORT + "\r\n" +
+ "\r\n";
+data = [];
+for (var i = 0; i < str.length; i += 100)
+ data.push(str.substr(i, 100));
+
+function checkLotsOfLeadingBlankLines(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ print("data length: " + data.length);
+ print("iter object: " + iter);
+
+ do_check_eq(iter.next(), "HTTP/1.1 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var body =
+ [
+ "Method: GET",
+ "Path: /lots-of-leading-blank-lines",
+ "Query: ",
+ "Version: 1.1",
+ "Scheme: http",
+ "Host: localhost",
+ "Port: " + PORT,
+ ];
+
+ expectLines(iter, body);
+}
+
+test = new RawTest("localhost", PORT, data, checkLotsOfLeadingBlankLines),
+tests.push(test);
diff --git a/netwerk/test/httpserver/test/test_response_write.js b/netwerk/test/httpserver/test/test_response_write.js
new file mode 100644
index 000000000..0a37ee44b
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_response_write.js
@@ -0,0 +1,55 @@
+/* -*- 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/. */
+
+// make sure response.write works for strings, and coerces other args to strings
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test("http://localhost:" + srv.identity.primaryPort + "/writeString",
+ null, check_1234, succeeded),
+ new Test("http://localhost:" + srv.identity.primaryPort + "/writeInt",
+ null, check_1234, succeeded),
+ ];
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ srv.registerPathHandler("/writeString", writeString);
+ srv.registerPathHandler("/writeInt", writeInt);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+// TEST DATA
+
+function succeeded(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+ do_check_eq(data.map(v => String.fromCharCode(v)).join(""), "1234");
+}
+
+function check_1234(ch, cx)
+{
+ do_check_eq(ch.getResponseHeader("Content-Length"), "4");
+}
+
+// PATH HANDLERS
+
+function writeString(metadata, response)
+{
+ response.write("1234");
+}
+
+function writeInt(metadata, response)
+{
+ response.write(1234);
+}
diff --git a/netwerk/test/httpserver/test/test_seizepower.js b/netwerk/test/httpserver/test/test_seizepower.js
new file mode 100644
index 000000000..f2d9e32c1
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_seizepower.js
@@ -0,0 +1,182 @@
+/* -*- 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/. */
+
+/*
+ * Tests that the seizePower API works correctly.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ srv.registerPathHandler("/raw-data", handleRawData);
+ srv.registerPathHandler("/called-too-late", handleTooLate);
+ srv.registerPathHandler("/exceptions", handleExceptions);
+ srv.registerPathHandler("/async-seizure", handleAsyncSeizure);
+ srv.registerPathHandler("/seize-after-async", handleSeizeAfterAsync);
+
+ srv.start(-1);
+
+ runRawTests(tests, testComplete(srv));
+}
+
+
+function checkException(fun, err, msg)
+{
+ try
+ {
+ fun();
+ }
+ catch (e)
+ {
+ if (e !== err && e.result !== err)
+ do_throw(msg);
+ return;
+ }
+ do_throw(msg);
+}
+
+function callASAPLater(fun)
+{
+ gThreadManager.currentThread.dispatch({
+ run: function()
+ {
+ fun();
+ }
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+}
+
+
+/*****************
+ * PATH HANDLERS *
+ *****************/
+
+function handleRawData(request, response)
+{
+ response.seizePower();
+ response.write("Raw data!");
+ response.finish();
+}
+
+function handleTooLate(request, response)
+{
+ response.write("DO NOT WANT");
+ var output = response.bodyOutputStream;
+
+ response.seizePower();
+
+ if (response.bodyOutputStream !== output)
+ response.write("bodyOutputStream changed!");
+ else
+ response.write("too-late passed");
+ response.finish();
+}
+
+function handleExceptions(request, response)
+{
+ response.seizePower();
+ checkException(function() { response.setStatusLine("1.0", 500, "ISE"); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "setStatusLine should throw not-available after seizePower");
+ checkException(function() { response.setHeader("X-Fail", "FAIL", false); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "setHeader should throw not-available after seizePower");
+ checkException(function() { response.processAsync(); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "processAsync should throw not-available after seizePower");
+ var out = response.bodyOutputStream;
+ var data = "exceptions test passed";
+ out.write(data, data.length);
+ response.seizePower(); // idempotency test of seizePower
+ response.finish();
+ response.finish(); // idempotency test of finish after seizePower
+ checkException(function() { response.seizePower(); },
+ Cr.NS_ERROR_UNEXPECTED,
+ "seizePower should throw unexpected after finish");
+}
+
+function handleAsyncSeizure(request, response)
+{
+ response.seizePower();
+ callLater(1, function()
+ {
+ response.write("async seizure passed");
+ response.bodyOutputStream.close();
+ callLater(1, function()
+ {
+ response.finish();
+ });
+ });
+}
+
+function handleSeizeAfterAsync(request, response)
+{
+ response.setStatusLine(request.httpVersion, 200, "async seizure pass");
+ response.processAsync();
+ checkException(function() { response.seizePower(); },
+ Cr.NS_ERROR_NOT_AVAILABLE,
+ "seizePower should throw not-available after processAsync");
+ callLater(1, function()
+ {
+ response.finish();
+ });
+}
+
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new RawTest("localhost", PORT, data0, checkRawData),
+ new RawTest("localhost", PORT, data1, checkTooLate),
+ new RawTest("localhost", PORT, data2, checkExceptions),
+ new RawTest("localhost", PORT, data3, checkAsyncSeizure),
+ new RawTest("localhost", PORT, data4, checkSeizeAfterAsync),
+ ];
+});
+
+var data0 = "GET /raw-data HTTP/1.0\r\n" +
+ "\r\n";
+function checkRawData(data)
+{
+ do_check_eq(data, "Raw data!");
+}
+
+var data1 = "GET /called-too-late HTTP/1.0\r\n" +
+ "\r\n";
+function checkTooLate(data)
+{
+ do_check_eq(LineIterator(data).next(), "too-late passed");
+}
+
+var data2 = "GET /exceptions HTTP/1.0\r\n" +
+ "\r\n";
+function checkExceptions(data)
+{
+ do_check_eq("exceptions test passed", data);
+}
+
+var data3 = "GET /async-seizure HTTP/1.0\r\n" +
+ "\r\n";
+function checkAsyncSeizure(data)
+{
+ do_check_eq(data, "async seizure passed");
+}
+
+var data4 = "GET /seize-after-async HTTP/1.0\r\n" +
+ "\r\n";
+function checkSeizeAfterAsync(data)
+{
+ do_check_eq(LineIterator(data).next(), "HTTP/1.0 200 async seizure pass");
+}
diff --git a/netwerk/test/httpserver/test/test_setindexhandler.js b/netwerk/test/httpserver/test/test_setindexhandler.js
new file mode 100644
index 000000000..6e733f4db
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_setindexhandler.js
@@ -0,0 +1,67 @@
+/* -*- 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/. */
+
+// Make sure setIndexHandler works as expected
+
+var srv, serverBasePath;
+
+function run_test()
+{
+ srv = createServer();
+ serverBasePath = do_get_profile();
+ srv.registerDirectory("/", serverBasePath);
+ srv.setIndexHandler(myIndexHandler);
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + srv.identity.primaryPort + "/";
+});
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(URL, init, startCustomIndexHandler, stopCustomIndexHandler),
+ new Test(URL, init, startDefaultIndexHandler, stopDefaultIndexHandler)
+ ];
+});
+
+function init(ch)
+{
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // important!
+}
+function startCustomIndexHandler(ch, cx)
+{
+ do_check_eq(ch.getResponseHeader("Content-Length"), "10");
+ srv.setIndexHandler(null);
+}
+function stopCustomIndexHandler(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+ do_check_eq(String.fromCharCode.apply(null, data), "directory!");
+}
+
+function startDefaultIndexHandler(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+}
+function stopDefaultIndexHandler(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+}
+
+// PATH HANDLERS
+
+function myIndexHandler(metadata, response)
+{
+ var dir = metadata.getProperty("directory");
+ do_check_true(dir != null);
+ do_check_true(dir instanceof Ci.nsIFile);
+ do_check_true(dir.equals(serverBasePath));
+
+ response.write("directory!");
+}
diff --git a/netwerk/test/httpserver/test/test_setstatusline.js b/netwerk/test/httpserver/test/test_setstatusline.js
new file mode 100644
index 000000000..f6f651488
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_setstatusline.js
@@ -0,0 +1,172 @@
+/* -*- 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/. */
+
+// exercise nsIHttpResponse.setStatusLine, ensure its atomicity, and ensure the
+// specified behavior occurs if it's not called
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+
+ srv.registerPathHandler("/no/setstatusline", noSetstatusline);
+ srv.registerPathHandler("/http1_0", http1_0);
+ srv.registerPathHandler("/http1_1", http1_1);
+ srv.registerPathHandler("/invalidVersion", invalidVersion);
+ srv.registerPathHandler("/invalidStatus", invalidStatus);
+ srv.registerPathHandler("/invalidDescription", invalidDescription);
+ srv.registerPathHandler("/crazyCode", crazyCode);
+ srv.registerPathHandler("/nullVersion", nullVersion);
+
+ srv.start(-1);
+
+ runHttpTests(tests, testComplete(srv));
+}
+
+
+/*************
+ * UTILITIES *
+ *************/
+
+function checkStatusLine(channel, httpMaxVer, httpMinVer, httpCode, statusText)
+{
+ do_check_eq(channel.responseStatus, httpCode);
+ do_check_eq(channel.responseStatusText, statusText);
+
+ var respMaj = {}, respMin = {};
+ channel.getResponseVersion(respMaj, respMin);
+ do_check_eq(respMaj.value, httpMaxVer);
+ do_check_eq(respMin.value, httpMinVer);
+}
+
+
+/*********
+ * TESTS *
+ *********/
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(URL + "/no/setstatusline", null, startNoSetStatusLine, stop),
+ new Test(URL + "/http1_0", null, startHttp1_0, stop),
+ new Test(URL + "/http1_1", null, startHttp1_1, stop),
+ new Test(URL + "/invalidVersion", null, startPassedTrue, stop),
+ new Test(URL + "/invalidStatus", null, startPassedTrue, stop),
+ new Test(URL + "/invalidDescription", null, startPassedTrue, stop),
+ new Test(URL + "/crazyCode", null, startCrazy, stop),
+ new Test(URL + "/nullVersion", null, startNullVersion, stop)
+ ];
+});
+
+
+// /no/setstatusline
+function noSetstatusline(metadata, response)
+{
+}
+function startNoSetStatusLine(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 200, "OK");
+}
+function stop(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+}
+
+
+// /http1_0
+function http1_0(metadata, response)
+{
+ response.setStatusLine("1.0", 200, "OK");
+}
+function startHttp1_0(ch, cx)
+{
+ checkStatusLine(ch, 1, 0, 200, "OK");
+}
+
+
+// /http1_1
+function http1_1(metadata, response)
+{
+ response.setStatusLine("1.1", 200, "OK");
+}
+function startHttp1_1(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 200, "OK");
+}
+
+
+// /invalidVersion
+function invalidVersion(metadata, response)
+{
+ try
+ {
+ response.setStatusLine(" 1.0", 200, "FAILED");
+ }
+ catch (e)
+ {
+ response.setHeader("Passed", "true", false);
+ }
+}
+function startPassedTrue(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 200, "OK");
+ do_check_eq(ch.getResponseHeader("Passed"), "true");
+}
+
+
+// /invalidStatus
+function invalidStatus(metadata, response)
+{
+ try
+ {
+ response.setStatusLine("1.0", 1000, "FAILED");
+ }
+ catch (e)
+ {
+ response.setHeader("Passed", "true", false);
+ }
+}
+
+
+// /invalidDescription
+function invalidDescription(metadata, response)
+{
+ try
+ {
+ response.setStatusLine("1.0", 200, "FAILED\x01");
+ }
+ catch (e)
+ {
+ response.setHeader("Passed", "true", false);
+ }
+}
+
+
+// /crazyCode
+function crazyCode(metadata, response)
+{
+ response.setStatusLine("1.1", 617, "Crazy");
+}
+function startCrazy(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 617, "Crazy");
+}
+
+
+// /nullVersion
+function nullVersion(metadata, response)
+{
+ response.setStatusLine(null, 255, "NULL");
+}
+function startNullVersion(ch, cx)
+{
+ // currently, this server implementation defaults to 1.1
+ checkStatusLine(ch, 1, 1, 255, "NULL");
+}
diff --git a/netwerk/test/httpserver/test/test_sjs.js b/netwerk/test/httpserver/test/test_sjs.js
new file mode 100644
index 000000000..2a9814196
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs.js
@@ -0,0 +1,251 @@
+/* -*- 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/. */
+
+// tests support for server JS-generated pages
+
+var srv = createServer();
+
+var sjs = do_get_file("data/sjs/cgi.sjs");
+// NB: The server has no state at this point -- all state is set up and torn
+// down in the tests, because we run the same tests twice with only a
+// different query string on the requests, followed by the oddball
+// test that doesn't care about throwing or not.
+srv.start(-1);
+const PORT = srv.identity.primaryPort;
+
+const BASE = "http://localhost:" + PORT;
+var test;
+var tests = [];
+
+
+/*********************
+ * UTILITY FUNCTIONS *
+ *********************/
+
+function bytesToString(bytes)
+{
+ return bytes.map(function(v) { return String.fromCharCode(v); }).join("");
+}
+
+function skipCache(ch)
+{
+ ch.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+}
+
+
+/********************
+ * DEFINE THE TESTS *
+ ********************/
+
+/**
+ * Adds the set of tests defined in here, differentiating between tests with a
+ * SJS which throws an exception and creates a server error and tests with a
+ * normal, successful SJS.
+ */
+function setupTests(throwing)
+{
+ const TEST_URL = BASE + "/cgi.sjs" + (throwing ? "?throw" : "");
+
+ // registerFile with SJS => raw text
+
+ function setupFile(ch)
+ {
+ srv.registerFile("/cgi.sjs", sjs);
+ skipCache(ch);
+ }
+
+ function verifyRawText(channel, cx, status, bytes)
+ {
+ dumpn(channel.originalURI.spec);
+ do_check_eq(bytesToString(bytes), fileContents(sjs));
+ }
+
+ test = new Test(TEST_URL, setupFile, null, verifyRawText);
+ tests.push(test);
+
+
+ // add mapping, => interpreted
+
+ function addTypeMapping(ch)
+ {
+ srv.registerContentType("sjs", "sjs");
+ skipCache(ch);
+ }
+
+ function checkType(ch, cx)
+ {
+ if (throwing)
+ {
+ do_check_false(ch.requestSucceeded);
+ do_check_eq(ch.responseStatus, 500);
+ }
+ else
+ {
+ do_check_eq(ch.contentType, "text/plain");
+ }
+ }
+
+ function checkContents(ch, cx, status, data)
+ {
+ if (!throwing)
+ do_check_eq("PASS", bytesToString(data));
+ }
+
+ test = new Test(TEST_URL, addTypeMapping, checkType, checkContents);
+ tests.push(test);
+
+
+ // remove file/type mapping, map containing directory => raw text
+
+ function setupDirectoryAndRemoveType(ch)
+ {
+ dumpn("removing type mapping");
+ srv.registerContentType("sjs", null);
+ srv.registerFile("/cgi.sjs", null);
+ srv.registerDirectory("/", sjs.parent);
+ skipCache(ch);
+ }
+
+ test = new Test(TEST_URL, setupDirectoryAndRemoveType, null, verifyRawText);
+ tests.push(test);
+
+
+ // add mapping, => interpreted
+
+ function contentAndCleanup(ch, cx, status, data)
+ {
+ checkContents(ch, cx, status, data);
+
+ // clean up state we've set up
+ srv.registerDirectory("/", null);
+ srv.registerContentType("sjs", null);
+ }
+
+ test = new Test(TEST_URL, addTypeMapping, checkType, contentAndCleanup);
+ tests.push(test);
+
+ // NB: No remaining state in the server right now! If we have any here,
+ // either the second run of tests (without ?throw) or the tests added
+ // after the two sets will almost certainly fail.
+}
+
+
+/*****************
+ * ADD THE TESTS *
+ *****************/
+
+setupTests(true);
+setupTests(false);
+
+// Test that when extension-mappings are used, the entire filename cannot be
+// treated as an extension -- there must be at least one dot for a filename to
+// match an extension.
+
+function init(ch)
+{
+ // clean up state we've set up
+ srv.registerDirectory("/", sjs.parent);
+ srv.registerContentType("sjs", "sjs");
+ skipCache(ch);
+}
+
+function checkNotSJS(ch, cx, status, data)
+{
+ do_check_neq("FAIL", bytesToString(data));
+}
+
+test = new Test(BASE + "/sjs", init, null, checkNotSJS);
+tests.push(test);
+
+// Test that Range requests are passed through to the SJS file without
+// bounds checking.
+
+function rangeInit(expectedRangeHeader)
+{
+ return function setupRangeRequest(ch)
+ {
+ ch.setRequestHeader("Range", expectedRangeHeader, false);
+ };
+}
+
+function checkRangeResult(ch, cx)
+{
+ try
+ {
+ var val = ch.getResponseHeader("Content-Range");
+ }
+ catch (e) { /* IDL doesn't specify a particular exception to require */ }
+ if (val !== undefined)
+ {
+ do_throw("should not have gotten a Content-Range header, but got one " +
+ "with this value: " + val);
+ }
+ do_check_eq(200, ch.responseStatus);
+ do_check_eq("OK", ch.responseStatusText);
+}
+
+test = new Test(BASE + "/range-checker.sjs",
+ rangeInit("not-a-bytes-equals-specifier"),
+ checkRangeResult, null);
+tests.push(test);
+test = new Test(BASE + "/range-checker.sjs",
+ rangeInit("bytes=-"),
+ checkRangeResult, null);
+tests.push(test);
+test = new Test(BASE + "/range-checker.sjs",
+ rangeInit("bytes=1000000-"),
+ checkRangeResult, null);
+tests.push(test);
+test = new Test(BASE + "/range-checker.sjs",
+ rangeInit("bytes=1-4"),
+ checkRangeResult, null);
+tests.push(test);
+test = new Test(BASE + "/range-checker.sjs",
+ rangeInit("bytes=-4"),
+ checkRangeResult, null);
+tests.push(test);
+
+// One last test: for file mappings, the content-type is determined by the
+// extension of the file on the server, not by the extension of the requested
+// path.
+
+function setupFileMapping(ch)
+{
+ srv.registerFile("/script.html", sjs);
+}
+
+function onStart(ch, cx)
+{
+ do_check_eq(ch.contentType, "text/plain");
+}
+
+function onStop(ch, cx, status, data)
+{
+ do_check_eq("PASS", bytesToString(data));
+}
+
+test = new Test(BASE + "/script.html", setupFileMapping, onStart, onStop);
+tests.push(test);
+
+
+/*****************
+ * RUN THE TESTS *
+ *****************/
+
+function run_test()
+{
+ // Test for a content-type which isn't a field-value
+ try
+ {
+ srv.registerContentType("foo", "bar\nbaz");
+ throw "this server throws on content-types which aren't field-values";
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_INVALID_ARG);
+ }
+ runHttpTests(tests, testComplete(srv));
+}
diff --git a/netwerk/test/httpserver/test/test_sjs_object_state.js b/netwerk/test/httpserver/test/test_sjs_object_state.js
new file mode 100644
index 000000000..b0f4e546d
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_object_state.js
@@ -0,0 +1,290 @@
+/* -*- 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/. */
+
+/*
+ * Tests that the object-state-preservation mechanism works correctly.
+ */
+
+
+XPCOMUtils.defineLazyGetter(this, "PATH", function() {
+ return "http://localhost:" + srv.identity.primaryPort + "/object-state.sjs";
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+ var sjsDir = do_get_file("data/sjs/");
+ srv.registerDirectory("/", sjsDir);
+ srv.registerContentType("sjs", "sjs");
+ srv.start(-1);
+
+ do_test_pending();
+
+ new HTTPTestLoader(PATH + "?state=initial", initialStart, initialStop);
+}
+
+/********************
+ * OBSERVER METHODS *
+ ********************/
+
+/*
+ * In practice the current implementation will guarantee an exact ordering of
+ * all start and stop callbacks. However, in the interests of robustness, this
+ * test will pass given any valid ordering of callbacks. Any ordering of calls
+ * which obeys the partial ordering shown by this directed acyclic graph will be
+ * handled correctly:
+ *
+ * initialStart
+ * |
+ * V
+ * intermediateStart
+ * |
+ * V
+ * intermediateStop
+ * | \
+ * | V
+ * | initialStop
+ * V
+ * triggerStart
+ * |
+ * V
+ * triggerStop
+ *
+ */
+
+var initialStarted = false;
+function initialStart(ch, cx)
+{
+ dumpn("*** initialStart");
+
+ if (initialStarted)
+ do_throw("initialStart: initialStarted is true?!?!");
+
+ initialStarted = true;
+
+ new HTTPTestLoader(PATH + "?state=intermediate",
+ intermediateStart, intermediateStop);
+}
+
+var initialStopped = false;
+function initialStop(ch, cx, status, data)
+{
+ dumpn("*** initialStop");
+
+ do_check_eq(data.map(function(v) { return String.fromCharCode(v); }).join(""),
+ "done");
+
+ do_check_eq(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted)
+ do_throw("initialStop: initialStarted is false?!?!");
+ if (initialStopped)
+ do_throw("initialStop: initialStopped is true?!?!");
+ if (!intermediateStarted)
+ do_throw("initialStop: intermediateStarted is false?!?!");
+ if (!intermediateStopped)
+ do_throw("initialStop: intermediateStopped is false?!?!");
+
+ initialStopped = true;
+
+ checkForFinish();
+}
+
+var intermediateStarted = false;
+function intermediateStart(ch, cx)
+{
+ dumpn("*** intermediateStart");
+
+ do_check_neq(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted)
+ do_throw("intermediateStart: initialStarted is false?!?!");
+ if (intermediateStarted)
+ do_throw("intermediateStart: intermediateStarted is true?!?!");
+
+ intermediateStarted = true;
+}
+
+var intermediateStopped = false;
+function intermediateStop(ch, cx, status, data)
+{
+ dumpn("*** intermediateStop");
+
+ do_check_eq(data.map(function(v) { return String.fromCharCode(v); }).join(""),
+ "intermediate");
+
+ do_check_neq(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted)
+ do_throw("intermediateStop: initialStarted is false?!?!");
+ if (!intermediateStarted)
+ do_throw("intermediateStop: intermediateStarted is false?!?!");
+ if (intermediateStopped)
+ do_throw("intermediateStop: intermediateStopped is true?!?!");
+
+ intermediateStopped = true;
+
+ new HTTPTestLoader(PATH + "?state=trigger", triggerStart,
+ triggerStop);
+}
+
+var triggerStarted = false;
+function triggerStart(ch, cx)
+{
+ dumpn("*** triggerStart");
+
+ if (!initialStarted)
+ do_throw("triggerStart: initialStarted is false?!?!");
+ if (!intermediateStarted)
+ do_throw("triggerStart: intermediateStarted is false?!?!");
+ if (!intermediateStopped)
+ do_throw("triggerStart: intermediateStopped is false?!?!");
+ if (triggerStarted)
+ do_throw("triggerStart: triggerStarted is true?!?!");
+
+ triggerStarted = true;
+}
+
+var triggerStopped = false;
+function triggerStop(ch, cx, status, data)
+{
+ dumpn("*** triggerStop");
+
+ do_check_eq(data.map(function(v) { return String.fromCharCode(v); }).join(""),
+ "trigger");
+
+ if (!initialStarted)
+ do_throw("triggerStop: initialStarted is false?!?!");
+ if (!intermediateStarted)
+ do_throw("triggerStop: intermediateStarted is false?!?!");
+ if (!intermediateStopped)
+ do_throw("triggerStop: intermediateStopped is false?!?!");
+ if (!triggerStarted)
+ do_throw("triggerStop: triggerStarted is false?!?!");
+ if (triggerStopped)
+ do_throw("triggerStop: triggerStopped is false?!?!");
+
+ triggerStopped = true;
+
+ checkForFinish();
+}
+
+var finished = false;
+function checkForFinish()
+{
+ if (finished)
+ {
+ try
+ {
+ do_throw("uh-oh, how are we being finished twice?!?!");
+ }
+ finally
+ {
+ quit(1);
+ }
+ }
+
+ if (triggerStopped && initialStopped)
+ {
+ finished = true;
+ try
+ {
+ do_check_eq(srv.getObjectState("object-state-test"), null);
+
+ if (!initialStarted)
+ do_throw("checkForFinish: initialStarted is false?!?!");
+ if (!intermediateStarted)
+ do_throw("checkForFinish: intermediateStarted is false?!?!");
+ if (!intermediateStopped)
+ do_throw("checkForFinish: intermediateStopped is false?!?!");
+ if (!triggerStarted)
+ do_throw("checkForFinish: triggerStarted is false?!?!");
+ }
+ finally
+ {
+ srv.stop(do_test_finished);
+ }
+ }
+}
+
+
+/*********************************
+ * UTILITY OBSERVABLE URL LOADER *
+ *********************************/
+
+/** Stream listener for the channels. */
+function HTTPTestLoader(path, start, stop)
+{
+ /** Path to load. */
+ this._path = path;
+
+ /** Array of bytes of data in body of response. */
+ this._data = [];
+
+ /** onStartRequest callback. */
+ this._start = start;
+
+ /** onStopRequest callback. */
+ this._stop = stop;
+
+ var channel = makeChannel(path);
+ channel.asyncOpen2(this);
+}
+HTTPTestLoader.prototype =
+ {
+ onStartRequest: function(request, cx)
+ {
+ dumpn("*** HTTPTestLoader.onStartRequest for " + this._path);
+
+ var ch = request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ try
+ {
+ try
+ {
+ this._start(ch, cx);
+ }
+ catch (e)
+ {
+ do_throw(this._path + ": error in onStartRequest: " + e);
+ }
+ }
+ catch (e)
+ {
+ dumpn("!!! swallowing onStartRequest exception so onStopRequest is " +
+ "called...");
+ }
+ },
+ onDataAvailable: function(request, cx, inputStream, offset, count)
+ {
+ dumpn("*** HTTPTestLoader.onDataAvailable for " + this._path);
+
+ Array.prototype.push.apply(this._data,
+ makeBIS(inputStream).readByteArray(count));
+ },
+ onStopRequest: function(request, cx, status)
+ {
+ dumpn("*** HTTPTestLoader.onStopRequest for " + this._path);
+
+ var ch = request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ this._stop(ch, cx, status, this._data);
+ },
+ QueryInterface: function(aIID)
+ {
+ dumpn("*** QueryInterface: " + aIID);
+
+ if (aIID.equals(Ci.nsIStreamListener) ||
+ aIID.equals(Ci.nsIRequestObserver) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
diff --git a/netwerk/test/httpserver/test/test_sjs_state.js b/netwerk/test/httpserver/test/test_sjs_state.js
new file mode 100644
index 000000000..ccf5c4b03
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_state.js
@@ -0,0 +1,186 @@
+/* -*- 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/. */
+
+// exercises the server's state-preservation API
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+ var sjsDir = do_get_file("data/sjs/");
+ srv.registerDirectory("/", sjsDir);
+ srv.registerContentType("sjs", "sjs");
+ srv.registerPathHandler("/path-handler", pathHandler);
+ srv.start(-1);
+
+ function done()
+ {
+ do_check_eq(srv.getSharedState("shared-value"), "done!");
+ do_check_eq(srv.getState("/path-handler", "private-value"),
+ "pathHandlerPrivate2");
+ do_check_eq(srv.getState("/state1.sjs", "private-value"),
+ "");
+ do_check_eq(srv.getState("/state2.sjs", "private-value"),
+ "newPrivate5");
+ do_test_pending();
+ srv.stop(function() { do_test_finished(); });
+ }
+
+ runHttpTests(tests, done);
+}
+
+
+/************
+ * HANDLERS *
+ ************/
+
+var firstTime = true;
+
+function pathHandler(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ response.setHeader("X-Old-Shared-Value", srv.getSharedState("shared-value"),
+ false);
+ response.setHeader("X-Old-Private-Value", srv.getState("/path-handler", "private-value"),
+ false);
+
+ var privateValue, sharedValue;
+ if (firstTime)
+ {
+ firstTime = false;
+ privateValue = "pathHandlerPrivate";
+ sharedValue = "pathHandlerShared";
+ }
+ else
+ {
+ privateValue = "pathHandlerPrivate2";
+ sharedValue = "";
+ }
+
+ srv.setState("/path-handler", "private-value", privateValue);
+ srv.setSharedState("shared-value", sharedValue);
+
+ response.setHeader("X-New-Private-Value", privateValue, false);
+ response.setHeader("X-New-Shared-Value", sharedValue, false);
+}
+
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ new Test(URL + "/state1.sjs?" +
+ "newShared=newShared&newPrivate=newPrivate",
+ null, start_initial, null),
+ new Test(URL + "/state1.sjs?" +
+ "newShared=newShared2&newPrivate=newPrivate2",
+ null, start_overwrite, null),
+ new Test(URL + "/state1.sjs?" +
+ "newShared=&newPrivate=newPrivate3",
+ null, start_remove, null),
+ new Test(URL + "/path-handler",
+ null, start_handler, null),
+ new Test(URL + "/path-handler",
+ null, start_handler_again, null),
+ new Test(URL + "/state2.sjs?" +
+ "newShared=newShared4&newPrivate=newPrivate4",
+ null, start_other_initial, null),
+ new Test(URL + "/state2.sjs?" +
+ "newShared=",
+ null, start_other_remove_ignore, null),
+ new Test(URL + "/state2.sjs?" +
+ "newShared=newShared5&newPrivate=newPrivate5",
+ null, start_other_set_new, null),
+ new Test(URL + "/state1.sjs?" +
+ "newShared=done!&newPrivate=",
+ null, start_set_remove_original, null)
+ ];
+});
+
+/* Hack around bug 474845 for now. */
+function getHeaderFunction(ch)
+{
+ function getHeader(name)
+ {
+ try
+ {
+ return ch.getResponseHeader(name);
+ }
+ catch (e)
+ {
+ if (e.result !== Cr.NS_ERROR_NOT_AVAILABLE)
+ throw e;
+ }
+ return "";
+ }
+ return getHeader;
+}
+
+function expectValues(ch, oldShared, newShared, oldPrivate, newPrivate)
+{
+ var getHeader = getHeaderFunction(ch);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(getHeader("X-Old-Shared-Value"), oldShared);
+ do_check_eq(getHeader("X-New-Shared-Value"), newShared);
+ do_check_eq(getHeader("X-Old-Private-Value"), oldPrivate);
+ do_check_eq(getHeader("X-New-Private-Value"), newPrivate);
+}
+
+function start_initial(ch, cx)
+{
+dumpn("XXX start_initial");
+ expectValues(ch, "", "newShared", "", "newPrivate");
+}
+
+function start_overwrite(ch, cx)
+{
+ expectValues(ch, "newShared", "newShared2", "newPrivate", "newPrivate2");
+}
+
+function start_remove(ch, cx)
+{
+ expectValues(ch, "newShared2", "", "newPrivate2", "newPrivate3");
+}
+
+function start_handler(ch, cx)
+{
+ expectValues(ch, "", "pathHandlerShared", "", "pathHandlerPrivate");
+}
+
+function start_handler_again(ch, cx)
+{
+ expectValues(ch, "pathHandlerShared", "",
+ "pathHandlerPrivate", "pathHandlerPrivate2");
+}
+
+function start_other_initial(ch, cx)
+{
+ expectValues(ch, "", "newShared4", "", "newPrivate4");
+}
+
+function start_other_remove_ignore(ch, cx)
+{
+ expectValues(ch, "newShared4", "", "newPrivate4", "");
+}
+
+function start_other_set_new(ch, cx)
+{
+ expectValues(ch, "", "newShared5", "newPrivate4", "newPrivate5");
+}
+
+function start_set_remove_original(ch, cx)
+{
+ expectValues(ch, "newShared5", "done!", "newPrivate3", "");
+}
diff --git a/netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js b/netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js
new file mode 100644
index 000000000..2e4855b01
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+/*
+ * Tests that running an SJS a whole lot of times doesn't have any ill effects
+ * (like exceeding open-file limits due to not closing the SJS file each time,
+ * then preventing any file from being opened).
+ */
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test()
+{
+ srv = createServer();
+ var sjsDir = do_get_file("data/sjs/");
+ srv.registerDirectory("/", sjsDir);
+ srv.registerContentType("sjs", "sjs");
+ srv.start(-1);
+
+ function done()
+ {
+ do_test_pending();
+ srv.stop(function() { do_test_finished(); });
+ do_check_eq(gStartCount, TEST_RUNS);
+ do_check_true(lastPassed);
+ }
+
+ runHttpTests(tests, done);
+}
+
+/***************
+ * BEGIN TESTS *
+ ***************/
+
+var gStartCount = 0;
+var lastPassed = false;
+
+// This hits the open-file limit for me on OS X; your mileage may vary.
+const TEST_RUNS = 250;
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ var _tests = new Array(TEST_RUNS + 1);
+ var _test = new Test(URL + "/thrower.sjs?throw", null, start_thrower);
+ for (var i = 0; i < TEST_RUNS; i++)
+ _tests[i] = _test;
+ // ...and don't forget to stop!
+ _tests[TEST_RUNS] = new Test(URL + "/thrower.sjs", null, start_last);
+ return _tests;
+});
+
+function start_thrower(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 500);
+ do_check_false(ch.requestSucceeded);
+
+ gStartCount++;
+}
+
+function start_last(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+
+ do_check_eq(ch.getResponseHeader("X-Test-Status"), "PASS");
+
+ lastPassed = true;
+}
diff --git a/netwerk/test/httpserver/test/test_start_stop.js b/netwerk/test/httpserver/test/test_start_stop.js
new file mode 100644
index 000000000..e9f42bddd
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_start_stop.js
@@ -0,0 +1,189 @@
+/* -*- 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/. */
+
+/*
+ * Tests for correct behavior of the server start() and stop() methods.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return srv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PREPATH", function() {
+ return "http://localhost:" + PORT;
+});
+
+var srv, srv2;
+
+function run_test()
+{
+ if (mozinfo.os == "win")
+ {
+ dumpn("*** not running test_start_stop.js on Windows for now, because " +
+ "Windows is dumb");
+ return;
+ }
+
+ dumpn("*** run_test");
+
+ srv = createServer();
+ srv.start(-1);
+
+ try
+ {
+ srv.start(PORT);
+ do_throw("starting a started server");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_ALREADY_INITIALIZED);
+ }
+
+ try
+ {
+ srv.stop();
+ do_throw("missing argument to stop");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ try
+ {
+ srv.stop(null);
+ do_throw("null argument to stop");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ do_test_pending();
+ srv.stop(function()
+ {
+ try
+ {
+ do_test_pending();
+ run_test_2();
+ }
+ finally
+ {
+ do_test_finished();
+ }
+ });
+}
+
+function run_test_2()
+{
+ dumpn("*** run_test_2");
+
+ do_test_finished();
+
+ srv.start(PORT);
+ srv2 = createServer();
+
+ try
+ {
+ srv2.start(PORT);
+ do_throw("two servers on one port?");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+
+ do_test_pending();
+ try
+ {
+ srv.stop({onStopped: function()
+ {
+ try
+ {
+ do_test_pending();
+ run_test_3();
+ }
+ finally
+ {
+ do_test_finished();
+ }
+ }
+ });
+ }
+ catch (e)
+ {
+ do_throw("error stopping with an object: " + e);
+ }
+}
+
+function run_test_3()
+{
+ dumpn("*** run_test_3");
+
+ do_test_finished();
+
+ srv.registerPathHandler("/handle", handle);
+ srv.start(PORT);
+
+ // Don't rely on the exact (but implementation-constant) sequence of events
+ // as it currently exists by making either run_test_4 or serverStopped handle
+ // the final shutdown.
+ do_test_pending();
+
+ runHttpTests([new Test(PREPATH + "/handle")], run_test_4);
+}
+
+var testsComplete = false;
+
+function run_test_4()
+{
+ dumpn("*** run_test_4");
+
+ testsComplete = true;
+ if (stopped)
+ do_test_finished();
+}
+
+
+const INTERVAL = 500;
+
+function handle(request, response)
+{
+ response.processAsync();
+
+ dumpn("*** stopping server...");
+ srv.stop(serverStopped);
+
+ callLater(INTERVAL, function()
+ {
+ do_check_false(stopped);
+
+ callLater(INTERVAL, function()
+ {
+ do_check_false(stopped);
+ response.finish();
+
+ try
+ {
+ response.processAsync();
+ do_throw("late processAsync didn't throw?");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_UNEXPECTED);
+ }
+ });
+ });
+}
+
+var stopped = false;
+function serverStopped()
+{
+ dumpn("*** server really, fully shut down now");
+ stopped = true;
+ if (testsComplete)
+ do_test_finished();
+}
diff --git a/netwerk/test/httpserver/test/xpcshell.ini b/netwerk/test/httpserver/test/xpcshell.ini
new file mode 100644
index 000000000..7890917a6
--- /dev/null
+++ b/netwerk/test/httpserver/test/xpcshell.ini
@@ -0,0 +1,37 @@
+[DEFAULT]
+head = head_utils.js
+tail =
+support-files = data/** ../httpd.js
+
+[test_async_response_sending.js]
+[test_basic_functionality.js]
+[test_body_length.js]
+[test_byte_range.js]
+[test_cern_meta.js]
+[test_default_index_handler.js]
+[test_empty_body.js]
+[test_errorhandler_exception.js]
+[test_header_array.js]
+[test_headers.js]
+[test_host.js]
+run-sequentially = Reusing same server on different specific ports.
+[test_linedata.js]
+[test_load_module.js]
+[test_name_scheme.js]
+[test_processasync.js]
+[test_qi.js]
+[test_registerdirectory.js]
+[test_registerfile.js]
+[test_registerprefix.js]
+[test_request_line_split_in_two_packets.js]
+[test_response_write.js]
+[test_seizepower.js]
+[test_setindexhandler.js]
+[test_setstatusline.js]
+[test_sjs.js]
+[test_sjs_object_state.js]
+[test_sjs_state.js]
+[test_sjs_throwing_exceptions.js]
+# Bug 1063533: frequent timeouts on Android 4.3
+skip-if = os == "android"
+[test_start_stop.js]
diff --git a/netwerk/test/mochitests/empty.html b/netwerk/test/mochitests/empty.html
new file mode 100644
index 000000000..e60f5abdf
--- /dev/null
+++ b/netwerk/test/mochitests/empty.html
@@ -0,0 +1,16 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ This page does nothing. If the loading page managed to load this, the test
+ probably succeeded.
+</body>
+</html>
diff --git a/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs b/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs
new file mode 100644
index 000000000..88cacbdb9
--- /dev/null
+++ b/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs
@@ -0,0 +1,106 @@
+/*
+ * Redirect handler specifically for the needs of:
+ * Bug 1194052 - Append Principal to RedirectChain within LoadInfo before the channel is succesfully openend
+ */
+
+function createIframeContent(aQuery) {
+
+ var content =`
+ <!DOCTYPE HTML>
+ <html>
+ <head><meta charset="utf-8">
+ <title>Bug 1194052 - LoadInfo redirect chain subtest</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?` + aQuery + `");
+ myXHR.onload = function() {
+ var loadinfo = SpecialPowers.wrap(myXHR).channel.loadInfo;
+ var redirectChain = loadinfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = loadinfo.redirectChainIncludingInternalRedirects;
+ var resultOBJ = { redirectChain : [], redirectChainIncludingInternalRedirects : [] };
+ for (var i = 0; i < redirectChain.length; i++) {
+ resultOBJ.redirectChain.push(redirectChain[i].URI.spec);
+ }
+ for (var i = 0; i < redirectChainIncludingInternalRedirects.length; i++) {
+ resultOBJ.redirectChainIncludingInternalRedirects.push(redirectChainIncludingInternalRedirects[i].URI.spec);
+ }
+ var loadinfoJSON = JSON.stringify(resultOBJ);
+ window.parent.postMessage({ loadinfo: loadinfoJSON }, "*");
+ }
+ myXHR.onerror = function() {
+ var resultOBJ = { redirectChain : [], redirectChainIncludingInternalRedirects : [] };
+ var loadinfoJSON = JSON.stringify(resultOBJ);
+ window.parent.postMessage({ loadinfo: loadinfoJSON }, "*");
+ }
+ myXHR.send();
+ </script>
+ </body>
+ </html>`;
+
+ return content;
+}
+
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+ var queryString = request.queryString;
+
+ if (queryString == "iframe-redir-https-2" ||
+ queryString == "iframe-redir-err-2") {
+ var query = queryString.replace("iframe-", "");
+ // send upgrade-insecure-requests CSP header
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Security-Policy", "upgrade-insecure-requests", false);
+ response.write(createIframeContent(query));
+ return;
+ }
+
+ // at the end of the redirectchain we return some text
+ // for sanity checking
+ if (queryString == "redir-0" ||
+ queryString == "redir-https-0") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("checking redirectchain");
+ return;
+ }
+
+ // special case redir-err-1 and return an error to trigger the fallback
+ if (queryString == "redir-err-1") {
+ response.setStatusLine("1.1", 404, "Bad request");
+ return;
+ }
+
+ // must be a redirect
+ var newLoaction = "";
+ switch (queryString) {
+ case "redir-err-2":
+ newLocation =
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-1";
+ break;
+
+ case "redir-https-2":
+ newLocation =
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1";
+ break;
+
+ case "redir-https-1":
+ newLocation =
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-0";
+ break;
+
+ case "redir-2":
+ newLocation =
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-1";
+ break;
+
+ case "redir-1":
+ newLocation =
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-0";
+ break;
+ }
+
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+}
diff --git a/netwerk/test/mochitests/method.sjs b/netwerk/test/mochitests/method.sjs
new file mode 100644
index 000000000..386c15d79
--- /dev/null
+++ b/netwerk/test/mochitests/method.sjs
@@ -0,0 +1,12 @@
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ // echo method
+ response.write(request.method);
+}
+
diff --git a/netwerk/test/mochitests/mochitest.ini b/netwerk/test/mochitests/mochitest.ini
new file mode 100644
index 000000000..98d406c80
--- /dev/null
+++ b/netwerk/test/mochitests/mochitest.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+support-files =
+ method.sjs
+ partial_content.sjs
+ rel_preconnect.sjs
+ user_agent.sjs
+ user_agent_update.sjs
+ web_packaged_app.sjs
+ file_loadinfo_redirectchain.sjs
+ redirect_idn.html^headers^
+ redirect_idn.html
+ empty.html
+ redirect.sjs
+
+[test_arraybufferinputstream.html]
+[test_idn_redirect.html]
+[test_loadinfo_redirectchain.html]
+[test_partially_cached_content.html]
+[test_rel_preconnect.html]
+[test_redirect_ref.html]
+[test_uri_scheme.html]
+[test_user_agent_overrides.html]
+[test_user_agent_updates.html]
+[test_user_agent_updates_reset.html]
+[test_viewsource_unlinkable.html]
+[test_xhr_method_case.html]
diff --git a/netwerk/test/mochitests/partial_content.sjs b/netwerk/test/mochitests/partial_content.sjs
new file mode 100644
index 000000000..5fd3443f1
--- /dev/null
+++ b/netwerk/test/mochitests/partial_content.sjs
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; 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/. */
+
+/* Debug and Error wrapper functions for dump().
+ */
+function ERR(response, responseCode, responseCodeStr, msg)
+{
+ // Reset state var.
+ setState("expectedRequestType", "");
+ // Dump to console log and send to client in response.
+ dump("SERVER ERROR: " + msg + "\n");
+ response.write("HTTP/1.1 " + responseCode + " " + responseCodeStr + "\r\n");
+ response.write("Content-Type: text/html; charset=UTF-8\r\n");
+ response.write("Content-Length: " + msg.length + "\r\n");
+ response.write("\r\n");
+ response.write(msg);
+}
+
+function DBG(msg)
+{
+ // Dump to console only.
+ dump("SERVER DEBUG: " + msg + "\n");
+}
+
+/* Delivers content in parts to test partially cached content: requires two
+ * requests for the same resource.
+ *
+ * First call will respond with partial content, but a 200 header and
+ * Content-Length equal to the full content length. No Range or If-Range
+ * headers are allowed in the request.
+ *
+ * Second call will require Range and If-Range in the request headers, and
+ * will respond with the range requested.
+ */
+function handleRequest(request, response)
+{
+ DBG("Trying to seize power");
+ response.seizePower();
+
+ DBG("About to check state vars");
+ // Get state var to determine if this is the first or second request.
+ var expectedRequestType;
+ var lastModified;
+ if (getState("expectedRequestType") === "") {
+ DBG("First call: Should be requesting full content.");
+ expectedRequestType = "fullRequest";
+ // Set state var for second request.
+ setState("expectedRequestType", "partialRequest");
+ // Create lastModified variable for responses.
+ lastModified = (new Date()).toUTCString();
+ setState("lastModified", lastModified);
+ } else if (getState("expectedRequestType") === "partialRequest") {
+ DBG("Second call: Should be requesting undelivered content.");
+ expectedRequestType = "partialRequest";
+ // Reset state var for first request.
+ setState("expectedRequestType", "");
+ // Get last modified date and reset state var.
+ lastModified = getState("lastModified");
+ } else {
+ ERR(response, 500, "Internal Server Error",
+ "Invalid expectedRequestType \"" + expectedRequestType + "\"in " +
+ "server state db.");
+ return;
+ }
+
+ // Look for Range and If-Range
+ var range = request.hasHeader("Range") ? request.getHeader("Range") : "";
+ var ifRange = request.hasHeader("If-Range") ? request.getHeader("If-Range") : "";
+
+ if (expectedRequestType === "fullRequest") {
+ // Should not have Range or If-Range in first request.
+ if (range && range.length > 0) {
+ ERR(response, 400, "Bad Request",
+ "Should not receive \"Range: " + range + "\" for first, full request.");
+ return;
+ }
+ if (ifRange && ifRange.length > 0) {
+ ERR(response, 400, "Bad Request",
+ "Should not receive \"Range: " + range + "\" for first, full request.");
+ return;
+ }
+ } else if (expectedRequestType === "partialRequest") {
+ // Range AND If-Range should both be present in second request.
+ if (!range) {
+ ERR(response, 400, "Bad Request",
+ "Should receive \"Range: \" for second, partial request.");
+ return;
+ }
+ if (!ifRange) {
+ ERR(response, 400, "Bad Request",
+ "Should receive \"If-Range: \" for second, partial request.");
+ return;
+ }
+ } else {
+ // Somewhat redundant, but a check for errors in this test code.
+ ERR(response, 500, "Internal Server Error",
+ "expectedRequestType not set correctly: \"" + expectedRequestType + "\"");
+ return;
+ }
+
+ // Prepare content in two parts for responses.
+ var partialContent = "<html><head></head><body><p id=\"firstResponse\">" +
+ "First response</p>";
+ var remainderContent = "<p id=\"secondResponse\">Second response</p>" +
+ "</body></html>";
+ var totalLength = partialContent.length + remainderContent.length;
+
+ DBG("totalLength: " + totalLength);
+
+ // Prepare common headers for the two responses.
+ date = new Date();
+ DBG("Date: " + date.toUTCString() + ", Last-Modified: " + lastModified);
+ var commonHeaders = "Date: " + date.toUTCString() + "\r\n" +
+ "Last-Modified: " + lastModified + "\r\n" +
+ "Content-Type: text/html; charset=UTF-8\r\n" +
+ "ETag: abcd0123\r\n" +
+ "Accept-Ranges: bytes\r\n";
+
+
+ // Prepare specific headers and content for first and second responses.
+ if (expectedRequestType === "fullRequest") {
+ DBG("First response: Sending partial content with a full header");
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write(commonHeaders);
+ // Set Content-Length to full length of resource.
+ response.write("Content-Length: " + totalLength + "\r\n");
+ response.write("\r\n");
+ response.write(partialContent);
+ } else if (expectedRequestType === "partialRequest") {
+ DBG("Second response: Sending remaining content with a range header");
+ response.write("HTTP/1.1 206 Partial Content\r\n");
+ response.write(commonHeaders);
+ // Set Content-Length to length of bytes transmitted.
+ response.write("Content-Length: " + remainderContent.length + "\r\n");
+ response.write("Content-Range: bytes " + partialContent.length + "-" +
+ (totalLength - 1) + "/" + totalLength + "\r\n");
+ response.write("\r\n");
+ response.write(remainderContent);
+ } else {
+ // Somewhat redundant, but a check for errors in this test code.
+ ERR(response, 500, "Internal Server Error",
+ "Something very bad happened here: expectedRequestType is invalid " +
+ "towards the end of handleRequest! - \"" + expectedRequestType + "\"");
+ return;
+ }
+
+ response.finish();
+}
diff --git a/netwerk/test/mochitests/redirect.sjs b/netwerk/test/mochitests/redirect.sjs
new file mode 100644
index 000000000..e36f7c99a
--- /dev/null
+++ b/netwerk/test/mochitests/redirect.sjs
@@ -0,0 +1,8 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", "empty.html#");
+}
diff --git a/netwerk/test/mochitests/redirect_idn.html b/netwerk/test/mochitests/redirect_idn.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/netwerk/test/mochitests/redirect_idn.html
diff --git a/netwerk/test/mochitests/redirect_idn.html^headers^ b/netwerk/test/mochitests/redirect_idn.html^headers^
new file mode 100644
index 000000000..753f65db8
--- /dev/null
+++ b/netwerk/test/mochitests/redirect_idn.html^headers^
@@ -0,0 +1,3 @@
+HTTP 301 Moved Permanently
+Location: http://exämple.test/tests/netwerk/test/mochitests/empty.html
+X-Comment: Bug 1142083 - This is a redirect to http://exämple.test
diff --git a/netwerk/test/mochitests/rel_preconnect.sjs b/netwerk/test/mochitests/rel_preconnect.sjs
new file mode 100644
index 000000000..9b5f5a69c
--- /dev/null
+++ b/netwerk/test/mochitests/rel_preconnect.sjs
@@ -0,0 +1,15 @@
+// Generate response header "Link: <HREF>; rel=preconnect"
+// HREF is provided by the request header X-Link
+
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Link", "<" +
+ request.queryString +
+ ">; rel=preconnect" + ", " +
+ "<" +
+ request.queryString +
+ ">; rel=preconnect; crossOrigin=anonymous");
+ response.write("check that header");
+}
+
diff --git a/netwerk/test/mochitests/signed_web_packaged_app.sjs b/netwerk/test/mochitests/signed_web_packaged_app.sjs
new file mode 100644
index 000000000..6bcac6ffd
--- /dev/null
+++ b/netwerk/test/mochitests/signed_web_packaged_app.sjs
@@ -0,0 +1,78 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "application/package", false);
+ response.write(signedPackage);
+}
+
+// The package content
+// getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
+var signedPackage = `manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgEEMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MTExOTAzMDEwNVoXDTM1MTExOTAzMDEwNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPback9X7RRxKTc3/5o2vm9Ro6XNiSM9NPsN3djjCIVz50bY0rJkP98zsqpFjnLwqHeJigxyYoqFexRhRLgKrG10CxNl4rxP6CEPENjvj5FfbX/HUZpT/DelNR18F498yD95vSHcSrCc3JrjV3bKA+wgt11E4a0Ba95S1RuwtehZw1+Y4hO8nHpbSGfjD0BpluFY2nDoYAm+aWSrsmLuJsKLO8Xn2I1brZFJUynR3q1ujuDE9EJk1niDLfOeVgXM4AavJS5C0ZBHhAhR2W+K9NN97jpkpmHFqecTwDXB7rEhsyB3185cI7anaaQfHHfH5+4SD+cMDNtYIOSgLO06ZwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAlnVyLz5dPhS0ZhZD6qJOUzSo6nFwMxNX1m0oS37mevtuh0b0o1gmEuMw3mVxiAVkC2vPsoxBL2wLlAkcEdBPxGEqhBmtiBY3F3DgvEkf+/sOY1rnr6O1qLZuBAnPzA1Vnco8Jwf0DYF0PxaRd8yT5XSl5qGpM2DItEldZwuKKaL94UEgIeC2c+Uv/IOyrv+EyftX96vcmRwr8ghPFLQ+36H5nuAKEpDD170EvfWl1zs0dUPiqSI6l+hy5V14gl63Woi34L727+FKx8oatbyZtdvbeeOmenfTLifLomnZdx+3WMLkp3TLlHa5xDLwifvZtBP2d3c6zHp7gdrGU1u2WTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQQwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTEyNTAzMDQzMFowIwYJKoZIhvcNAQkEMRYEFD4ut4oKoYdcGzyfQE6ROeazv+uNMA0GCSqGSIb3DQEBAQUABIIBAFG99dKBSOzQmYVn6lHKWERVDtYXbDTIVF957ID8YH9B5unlX/PdludTNbP5dzn8GWQV08tNRgoXQ5sgxjifHunrpaR1WiR6XqvwOCBeA5NB688jxGNxth6zg6fCGFaynsYMX3FlglfIW+AYwyQUclbv+C4UORJpBjvuknOnK+UDBLVSoP9ivL6KhylYna3oFcs0SMsumc/jf/oQW51LzFHpn61TRUqdDgvGhwcjgphMhKj23KwkjwRspU2oIWNRAuhZgqDD5BJlNniCr9X5Hx1dW6tIVISO91CLAryYkGZKRJYekXctCpIvldUkIDeh2tAw5owr0jtsVd6ovFF3bV4=\r
+--NKWXJUAFXB\r
+Content-Location: manifest.webapp\r
+Content-Type: application/x-web-app-manifest+json\r
+\r
+{
+ "moz-package-origin": "http://mochi.test:8888",
+ "name": "My App",
+ "moz-resources": [
+ {
+ "src": "page2.html",
+ "integrity": "JREF3JbXGvZ+I1KHtoz3f46ZkeIPrvXtG4VyFQrJ7II="
+ },
+ {
+ "src": "index.html",
+ "integrity": "Jkvco7U8WOY9s0YREsPouX+DWK7FWlgZwA0iYYSrb7Q="
+ },
+ {
+ "src": "scripts/script.js",
+ "integrity": "6TqtNArQKrrsXEQWu3D9ZD8xvDRIkhyV6zVdTcmsT5Q="
+ },
+ {
+ "src": "scripts/library.js",
+ "integrity": "TN2ByXZiaBiBCvS4MeZ02UyNi44vED+KjdjLInUl4o8="
+ }
+ ],
+ "moz-permissions": [
+ {
+ "systemXHR": {
+ "description": "Needed to download stuff"
+ }
+ }
+ ],
+ "package-identifier": "09bc9714-7ab6-4320-9d20-fde4c237522c",
+ "description": "A great app!"
+}\r
+--NKWXJUAFXB\r
+Content-Location: page2.html\r
+Content-Type: text/html\r
+\r
+<html>
+ page2.html
+</html>
+\r
+--NKWXJUAFXB\r
+Content-Location: index.html\r
+Content-Type: text/html\r
+\r
+<html>
+ Last updated: 2015/10/28
+ <iframe id="innerFrame" src="page2.html"></iframe>
+</html>
+\r
+--NKWXJUAFXB\r
+Content-Location: scripts/script.js\r
+Content-Type: text/javascript\r
+\r
+// script.js
+\r
+--NKWXJUAFXB\r
+Content-Location: scripts/library.js\r
+Content-Type: text/javascript\r
+\r
+// library.js
+\r
+--NKWXJUAFXB--`;
diff --git a/netwerk/test/mochitests/test_arraybufferinputstream.html b/netwerk/test/mochitests/test_arraybufferinputstream.html
new file mode 100644
index 000000000..eb7796ef2
--- /dev/null
+++ b/netwerk/test/mochitests/test_arraybufferinputstream.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>ArrayBuffer stream test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+function detachArrayBuffer(ab)
+{
+ var w = new Worker("data:application/javascript,");
+ w.postMessage(ab, [ab]);
+}
+
+function test()
+{
+ var ab = new ArrayBuffer(4000);
+ var ta = new Uint8Array(ab);
+ ta[0] = 'a'.charCodeAt(0);
+ ta[1] = 'b'.charCodeAt(0);
+
+ const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci, Cr = SpecialPowers.Cr;
+ var abis = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
+ .createInstance(Ci.nsIArrayBufferInputStream);
+
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(abis);
+
+ is(sis.read(1), "", "should read no data from an uninitialized ABIS");
+
+ abis.setData(ab, 0, 256 * 1024);
+
+ is(sis.read(1), "a", "should read 'a' after init");
+
+ detachArrayBuffer(ab);
+
+ SpecialPowers.forceGC();
+ SpecialPowers.forceGC();
+
+ try
+ {
+ is(sis.read(1), "b", "should read 'b' after detaching buffer");
+ }
+ catch (e)
+ {
+ ok(false, "reading from stream should have worked");
+ }
+
+ // A regression test for bug 1265076. Previously, overflowing
+ // the internal buffer from readSegments would cause incorrect
+ // copying. The constant mirrors the value in
+ // ArrayBufferInputStream::readSegments.
+ var size = 8192;
+ ab = new ArrayBuffer(2 * size);
+ ta = new Uint8Array(ab);
+
+ var i;
+ for (i = 0; i < size; ++i) {
+ ta[i] = 'x'.charCodeAt(0);
+ }
+ for (i = 0; i < size; ++i) {
+ ta[size + i] = 'y'.charCodeAt(0);
+ }
+
+ abis = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
+ .createInstance(Ci.nsIArrayBufferInputStream);
+ abis.setData(ab, 0, 2 * size);
+
+ sis = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(abis);
+
+ var result = sis.read(2 * size);
+ is(result, "x".repeat(size) + "y".repeat(size), "correctly read the data");
+}
+
+test();
+</script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_idn_redirect.html b/netwerk/test/mochitests/test_idn_redirect.html
new file mode 100644
index 000000000..b9a594cc5
--- /dev/null
+++ b/netwerk/test/mochitests/test_idn_redirect.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Bug 1142083 - IDN Unicode domain redirect is broken
+ This test loads redirectme.html which is redirected simple_test.html, on a different IDN domain.
+ A message is posted to that page, with responds with another.
+ Upon receiving that message, we consider that the IDN redirect has functioned properly, since the intended page was loaded.
+-->
+<head>
+ <title>Test for URI Manipulation</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var iframe = document.createElement("iframe");
+iframe.src = "about:blank";
+iframe.addEventListener("load", finishTest);
+document.body.appendChild(iframe);
+iframe.src = "http://mochi.test:8888/tests/netwerk/test/mochitests/redirect_idn.html";
+
+function finishTest(e) {
+ ok(true);
+ SimpleTest.finish();
+}
+
+</script>
+
+</body>
+</html>
+
diff --git a/netwerk/test/mochitests/test_loadinfo_redirectchain.html b/netwerk/test/mochitests/test_loadinfo_redirectchain.html
new file mode 100644
index 000000000..4568db0c2
--- /dev/null
+++ b/netwerk/test/mochitests/test_loadinfo_redirectchain.html
@@ -0,0 +1,213 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1194052 - Append Principal to RedirectChain within LoadInfo before the channel is succesfully openend</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * We perform the following tests on the redirectchain of the loadinfo:
+ * (1) checkLoadInfoWithoutRedirects:
+ * checks the length of the redirectchain and tries to pop an element
+ * which should result in an exception and not a crash.
+ * (2) checkLoadInfoWithTwoRedirects:
+ * perform two redirects and confirm that both redirect chains
+ * contain the redirected URIs.
+ * (3) checkLoadInfoWithInternalRedirects:
+ * perform two redirects including CSPs upgrade-insecure-requests
+ * so that the redirectchain which includes internal redirects differs.
+ * (4) checkLoadInfoWithInternalRedirectsAndFallback
+ * perform two redirects including CSPs upgrade-insecure-requests
+ * including a 404 repsonse and hence a fallback.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+// *************** TEST 1 ***************
+
+function checkLoadInfoWithoutRedirects() {
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-0");
+
+ myXHR.onload = function() {
+ var loadinfo = SpecialPowers.wrap(myXHR).channel.loadInfo;
+ var redirectChain = loadinfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = loadinfo.redirectChainIncludingInternalRedirects;
+
+ is(redirectChain.length, 0, "no redirect, length should be 0");
+ is(redirectChainIncludingInternalRedirects.length, 0, "no redirect, length should be 0");
+ is(myXHR.responseText, "checking redirectchain", "sanity check to make sure redirects succeeded");
+
+ // try to pop an element from redirectChain
+ try {
+ loadInfo.popRedirectedPrincipal(false);
+ ok(false, "should not be possible to pop from redirectChain");
+ }
+ catch(e) {
+ ok(true, "popping element from empty redirectChain should throw");
+ }
+
+ // try to pop an element from redirectChainIncludingInternalRedirects
+ try {
+ loadInfo.popRedirectedPrincipal(true);
+ ok(false, "should not be possible to pop from redirectChainIncludingInternalRedirects");
+ }
+ catch(e) {
+ ok(true, "popping element from empty redirectChainIncludingInternalRedirects should throw");
+ }
+ // move on to the next test
+ checkLoadInfoWithTwoRedirects();
+ }
+ myXHR.onerror = function() {
+ ok(false, "xhr problem within checkLoadInfoWithoutRedirect()");
+ }
+ myXHR.send();
+}
+
+// *************** TEST 2 ***************
+
+function checkLoadInfoWithTwoRedirects() {
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-2");
+
+ const EXPECTED_REDIRECT_CHAIN = [
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-2",
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-1"
+ ];
+
+ myXHR.onload = function() {
+ is(myXHR.responseText, "checking redirectchain", "sanity check to make sure redirects succeeded");
+
+ var loadinfo = SpecialPowers.wrap(myXHR).channel.loadInfo;
+ var redirectChain = loadinfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = loadinfo.redirectChainIncludingInternalRedirects;
+
+ is(redirectChain.length,
+ EXPECTED_REDIRECT_CHAIN.length,
+ "two redirects, chain should have length 2");
+ is(redirectChainIncludingInternalRedirects.length,
+ EXPECTED_REDIRECT_CHAIN.length,
+ "two redirect, chainInternal should have length 2");
+
+ for (var i = 0; i < redirectChain.length; i++) {
+ is(redirectChain[i].URI.spec,
+ EXPECTED_REDIRECT_CHAIN[i],
+ "redirectChain at index [" + i + "] should match");
+ is(redirectChainIncludingInternalRedirects[i].URI.spec,
+ EXPECTED_REDIRECT_CHAIN[i],
+ "redirectChainIncludingInternalRedirects at index [" + i + "] should match");
+ }
+
+ // move on to the next test
+ checkLoadInfoWithInternalRedirects();
+ }
+ myXHR.onerror = function() {
+ ok(false, "xhr problem within checkLoadInfoWithTwoRedirects()");
+ }
+ myXHR.send();
+}
+
+// ************** HELPERS ***************
+
+function compareChains(aLoadInfo, aExpectedRedirectChain, aExpectedRedirectChainIncludingInternalRedirects) {
+
+ var redirectChain = aLoadInfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = aLoadInfo.redirectChainIncludingInternalRedirects;
+
+ is(redirectChain.length,
+ aExpectedRedirectChain.length,
+ "confirming length of redirectChain");
+
+ is(redirectChainIncludingInternalRedirects.length,
+ aExpectedRedirectChainIncludingInternalRedirects.length,
+ "confirming length of redirectChainIncludingInternalRedirects");
+
+ for (var i = 0; i < redirectChain.length; i++) {
+ is(redirectChain[i],
+ aExpectedRedirectChain[i],
+ "redirectChain at index [" + i + "] should match");
+ }
+
+ for (var i = 0; i < redirectChainIncludingInternalRedirects.length; i++) {
+ is(redirectChainIncludingInternalRedirects[i],
+ aExpectedRedirectChainIncludingInternalRedirects[i],
+ "redirectChainIncludingInternalRedirects at index [" + i + "] should match");
+ }
+}
+
+// *************** TEST 3 ***************
+
+function confirmCheckLoadInfoWithInternalRedirects(event) {
+
+ const EXPECTED_REDIRECT_CHAIN = [
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-2",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1"
+ ];
+
+ const EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS = [
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-2",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-2",
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-1",
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-https-0",
+ ];
+
+ var loadinfo = JSON.parse(event.data.loadinfo);
+ compareChains(loadinfo, EXPECTED_REDIRECT_CHAIN, EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS);
+
+ // remove the postMessage listener and move on to the next test
+ window.removeEventListener("message", confirmCheckLoadInfoWithInternalRedirects, false);
+ checkLoadInfoWithInternalRedirectsAndFallback();
+}
+
+function checkLoadInfoWithInternalRedirects() {
+ // load the XHR request into an iframe so we can apply a CSP to the iframe
+ // a postMessage returns the result back to the main page.
+ window.addEventListener("message", confirmCheckLoadInfoWithInternalRedirects, false);
+ document.getElementById("testframe").src =
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?iframe-redir-https-2";
+}
+
+// *************** TEST 4 ***************
+
+function confirmCheckLoadInfoWithInternalRedirectsAndFallback(event) {
+
+ var EXPECTED_REDIRECT_CHAIN = [
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-2",
+ ];
+
+ var EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS = [
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-2",
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-2",
+ "http://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-err-1",
+ ];
+
+ var loadinfo = JSON.parse(event.data.loadinfo);
+ compareChains(loadinfo, EXPECTED_REDIRECT_CHAIN, EXPECTED_REDIRECT_CHAIN_INCLUDING_INTERNAL_REDIRECTS);
+
+ // remove the postMessage listener and finish test
+ window.removeEventListener("message", confirmCheckLoadInfoWithInternalRedirectsAndFallback, false);
+ SimpleTest.finish();
+}
+
+function checkLoadInfoWithInternalRedirectsAndFallback() {
+ // load the XHR request into an iframe so we can apply a CSP to the iframe
+ // a postMessage returns the result back to the main page.
+ window.addEventListener("message", confirmCheckLoadInfoWithInternalRedirectsAndFallback, false);
+ document.getElementById("testframe").src =
+ "https://example.com/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?iframe-redir-err-2";
+}
+
+// *************** START TESTS ***************
+
+checkLoadInfoWithoutRedirects();
+
+</script>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_partially_cached_content.html b/netwerk/test/mochitests/test_partially_cached_content.html
new file mode 100644
index 000000000..65a56dfe5
--- /dev/null
+++ b/netwerk/test/mochitests/test_partially_cached_content.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=497003
+
+ This test verifies that partially cached content is read from the cache first
+ and then from the network. It is written in the mochitest framework to take
+ thread retargeting into consideration of nsIStreamListener callbacks (inc.
+ nsIRequestObserver). E.g. HTML5 Stream Parser requesting retargeting of
+ nsIStreamListener callbacks to the parser thread.
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 497003: support sending OnDataAvailable() to other threads</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=497003">Mozilla Bug 497003: support sending OnDataAvailable() to other threads</a></p>
+ <p><iframe id="contentFrame" src="partial_content.sjs"></iframe></p>
+
+<pre id="test">
+<script>
+
+
+
+/* Check that the iframe has initial content only after the first load.
+ */
+function expectInitialContent(e) {
+ info("expectInitialContent",
+ "First response received: should have partial content");
+ var frameElement = document.getElementById('contentFrame');
+ var frameWindow = frameElement.contentWindow;
+
+ // Expect "First response" in received HTML.
+ var firstResponse = frameWindow.document.getElementById('firstResponse');
+ ok(firstResponse, "First response should exist");
+ if (firstResponse) {
+ is(firstResponse.innerHTML, "First response",
+ "First response should be correct");
+ }
+
+ // Expect NOT to get any second response element.
+ var secondResponse = frameWindow.document.getElementById('secondResponse');
+ ok(!secondResponse, "Should not get text for second response in first.");
+
+ // Set up listener for second load.
+ removeEventListener("load", expectInitialContent, false);
+ frameElement.addEventListener("load", expectFullContent, false);
+
+ // Reload.
+ frameElement.src="partial_content.sjs";
+}
+
+/* Check that the iframe has all the content after the second load.
+ */
+function expectFullContent(e)
+{
+ info("expectFullContent",
+ "Second response received: should complete content from first load");
+ var frameWindow = document.getElementById('contentFrame').contentWindow;
+
+ // Expect "First response" to still be there
+ var firstResponse = frameWindow.document.getElementById('firstResponse');
+ ok(firstResponse, "First response should exist");
+ if (firstResponse) {
+ is(firstResponse.innerHTML, "First response",
+ "First response should be correct");
+ }
+
+ // Expect "Second response" to be there also.
+ var secondResponse = frameWindow.document.getElementById('secondResponse');
+ ok(secondResponse, "Second response should exist");
+ if (secondResponse) {
+ is(secondResponse.innerHTML, "Second response",
+ "Second response should be correct");
+ }
+
+ SimpleTest.finish();
+}
+
+// Set listener for first load to expect partial content.
+// Note: Set listener on the global object/window since 'load' should not fire
+// for partially loaded content in an iframe.
+addEventListener("load", expectInitialContent, false);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_redirect_ref.html b/netwerk/test/mochitests/test_redirect_ref.html
new file mode 100644
index 000000000..40ea16658
--- /dev/null
+++ b/netwerk/test/mochitests/test_redirect_ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title> Bug 1234575 - Test redirect ref</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var iframe = document.createElement("iframe");
+iframe.src = "about:blank";
+iframe.addEventListener("load", finishTest);
+document.body.appendChild(iframe);
+iframe.src = "redirect.sjs#start";
+
+function finishTest(e) {
+ is(iframe.contentWindow.location.href, "http://mochi.test:8888/tests/netwerk/test/mochitests/empty.html#");
+ SimpleTest.finish();
+}
+
+</script>
+
+</body>
+</html>
+
diff --git a/netwerk/test/mochitests/test_rel_preconnect.html b/netwerk/test/mochitests/test_rel_preconnect.html
new file mode 100644
index 000000000..4e94fb4ce
--- /dev/null
+++ b/netwerk/test/mochitests/test_rel_preconnect.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for link rel=preconnect</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci, Cr = SpecialPowers.Cr;
+
+var remainder = 4;
+var observer;
+
+function doTest()
+{
+ SpecialPowers.setBoolPref("network.http.debug-observations", true);
+
+ observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
+ remainder--;
+ ok(true, "observed remainder = " + remainder);
+ if (!remainder) {
+ SpecialPowers.removeObserver(observer, "speculative-connect-request");
+ SpecialPowers.setBoolPref("network.http.debug-observations", false);
+ SimpleTest.finish();
+ }
+ });
+ SpecialPowers.addObserver(observer, "speculative-connect-request", false);
+
+ // test the link rel=preconnect element in the head for both normal
+ // and crossOrigin=anonymous
+ var link = document.createElement("link");
+ link.rel = "preconnect";
+ link.href = "//localhost:8888";
+ document.head.appendChild(link);
+ link = document.createElement("link");
+ link.rel = "preconnect";
+ link.href = "//localhost:8888";
+ link.crossOrigin = "anonymous";
+ document.head.appendChild(link);
+
+ // test the http link response header - the test contains both a
+ // normal and anonymous preconnect link header
+ var iframe = document.createElement('iframe');
+ iframe.src = 'rel_preconnect.sjs?//localhost:8888';
+
+ document.body.appendChild(iframe);
+}
+
+</script>
+</head>
+<body onload="doTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/netwerk/test/mochitests/test_uri_scheme.html b/netwerk/test/mochitests/test_uri_scheme.html
new file mode 100644
index 000000000..1f01a4d0a
--- /dev/null
+++ b/netwerk/test/mochitests/test_uri_scheme.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for URI Manipulation</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+function dotest1()
+{
+ SimpleTest.waitForExplicitFinish();
+ var o = new URL("http://localhost/");
+ try { o.href = "foopy:bar:baz"; } catch(e) { }
+ o.protocol = "http:";
+ o.hostname;
+ try { o.href = "http://localhost/"; } catch(e) { }
+ ok(o.protocol, "http:");
+ dotest2();
+}
+
+function dotest2()
+{
+ var o = new URL("http://www.mozilla.org/");
+ try {
+ o.href ="aaaaaaaaaaa:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ } catch(e) { }
+ o.hash = "#";
+ o.pathname = "/";
+ o.protocol = "http:";
+ try { o.href = "http://localhost/"; } catch(e) { }
+ ok(o.protocol, "http:");
+ dotest3();
+}
+
+function dotest3()
+{
+ is(new URL("resource://123/").href, "resource://123/");
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="dotest1();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/netwerk/test/mochitests/test_user_agent_overrides.html b/netwerk/test/mochitests/test_user_agent_overrides.html
new file mode 100644
index 000000000..7396c35e1
--- /dev/null
+++ b/netwerk/test/mochitests/test_user_agent_overrides.html
@@ -0,0 +1,240 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=782453
+-->
+<head>
+ <title>Test for User Agent Overrides</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782453">Mozilla Bug 782453</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides";
+const PREF_OVERRIDES_BRANCH = "general.useragent.override.";
+
+const DEFAULT_UA = navigator.userAgent;
+
+const UA_WHOLE_OVERRIDE = "DummyUserAgent";
+const UA_WHOLE_EXPECTED = UA_WHOLE_OVERRIDE;
+
+const UA_PARTIAL_FROM = "\\wozilla"; // /\wozilla
+const UA_PARTIAL_SEP = "#";
+const UA_PARTIAL_TO = UA_WHOLE_OVERRIDE;
+const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO;
+const UA_PARTIAL_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
+
+function testUAIFrame(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
+ let url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+ let ifr = document.createElement('IFRAME');
+
+ ifr.src = url;
+
+ document.getElementById('content').appendChild(ifr);
+
+ window.addEventListener("message", function recv(e) {
+ ok(sameQ == (e.data.header.indexOf(expected) != -1), message);
+ if (testNavQ) {
+ ok(navSameQ == (e.data.nav.indexOf(expected) != -1), navMessage);
+ }
+ window.removeEventListener("message", recv, false);
+ callback();
+ }, false);
+
+}
+
+function testUAIFrameNoNav(host, expected, sameQ, message, callback) {
+ testUAIFrame(host, expected, sameQ, message, false, true, '', callback);
+}
+
+function testUA(options, callback) {
+ var [domain, override, test_hosts, expected] =
+ [options.domain, options.override, options.test_hosts, options.expected];
+
+ (function nextTest() {
+ let test_host = test_hosts.shift();
+
+ info("Overriding " + domain + " with " + override + " for " + test_host);
+
+ function is_subdomain(host) {
+ var [test_domain] = host.slice(host.lastIndexOf('/') + 1).split(':', 1);
+ return test_domain === domain || test_domain.endsWith('.' + domain);
+ }
+
+ var localhost = location.origin;
+ var overrideNavigator = is_subdomain(localhost);
+ var navigator_ua, test_ua;
+
+ if (overrideNavigator) {
+ navigator_ua = navigator.userAgent;
+ }
+
+ let url = location.pathname;
+ url = test_host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+ let ifr = document.createElement('IFRAME');
+ ifr.src = url;
+
+ document.getElementById('content').appendChild(ifr);
+
+ window.addEventListener("message", function recv(e) {
+ test_ua = e.data.header;
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_OVERRIDES_BRANCH + domain, override]],
+ }, function () {
+ testUAIFrame(test_host, expected, true, 'Header UA not overridden at step ' + (++step), true,
+ true, 'Navigator UA not overridden at step ' + (++step), function () {
+ // clear the override pref to undo overriding the UA
+ SpecialPowers.pushPrefEnv({
+ clear: [[PREF_OVERRIDES_BRANCH + domain]],
+ }, function () {
+ testUAIFrameNoNav(test_host, test_ua, true, 'Header UA not restored at step ' + (++step), function() {
+ test_hosts.length ? nextTest() : callback();
+ });
+ });
+ });
+ });
+ window.removeEventListener("message", recv, false);
+ }, false);
+ })();
+}
+
+var step = 0; // for logging
+var tests = [
+ // should override both header and navigator.userAgent
+ {
+ domain: location.hostname,
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: [location.origin],
+ expected: UA_WHOLE_EXPECTED
+ },
+
+ // should support partial overrides
+ {
+ domain: location.hostname,
+ override: UA_PARTIAL_OVERRIDE,
+ test_hosts: [location.origin],
+ expected: UA_PARTIAL_EXPECTED
+ },
+
+ // should match domain and subdomains
+ {
+ domain: 'example.org',
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: ['http://example.org',
+ 'http://test1.example.org',
+ 'http://sub1.test1.example.org'],
+ expected: UA_WHOLE_EXPECTED
+ },
+
+ // should not match superdomains
+ {
+ domain: 'sub1.test1.example.org',
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: ['http://example.org',
+ 'http://test1.example.org'],
+ expected: DEFAULT_UA
+ },
+
+ // should work with https
+ {
+ domain: 'example.com',
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: ['https://example.com',
+ 'https://test1.example.com',
+ 'https://sub1.test1.example.com'],
+ expected: UA_WHOLE_EXPECTED
+ },
+];
+
+// test that UA is not overridden when the 'site_specific_overrides' pref is off
+function testInactive(callback) {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_OVERRIDES_ENABLED, false],
+ [PREF_OVERRIDES_BRANCH + location.hostname, UA_WHOLE_OVERRIDE]
+ ]
+ }, function () {
+ testUAIFrame(location.origin, UA_WHOLE_OVERRIDE, false, 'Failed to disabled header UA override at step ' + (++step),
+ true, false, 'Failed to disable navigator UA override at step + ' + (++step), function () {
+ SpecialPowers.pushPrefEnv({
+ clear: [
+ [PREF_OVERRIDES_ENABLED],
+ [PREF_OVERRIDES_BRANCH + location.hostname]
+ ]
+ }, callback);
+ });
+ });
+}
+
+function testPriority(callback) {
+ // foo.bar.com override should have priority over bar.com override
+ var tests = [
+ ['example.org', 'test1.example.org', 'sub1.test1.example.org'],
+ ['example.org', 'test1.example.org', 'sub2.test1.example.org'],
+ ['example.org', 'test2.example.org', 'sub1.test2.example.org'],
+ ['example.org', 'test2.example.org', 'sub2.test2.example.org'],
+ ];
+ (function nextTest() {
+ var [level0, level1, level2] = tests.shift();
+ var host = 'http://' + level2;
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_OVERRIDES_ENABLED, true],
+ [PREF_OVERRIDES_BRANCH + level1, UA_WHOLE_OVERRIDE]
+ ]
+ }, function () {
+ // should use first override at this point
+ testUAIFrameNoNav(host, UA_WHOLE_EXPECTED, true, 'UA not overridden at step ' + (++step), function() {
+ // add a second override that should be used
+ testUA({
+ domain: level2,
+ override: UA_PARTIAL_OVERRIDE,
+ test_hosts: [host],
+ expected: UA_PARTIAL_EXPECTED
+ }, function () {
+ // add a third override that should not be used
+ testUA({
+ domain: level0,
+ override: UA_PARTIAL_OVERRIDE,
+ test_hosts: [host],
+ expected: UA_WHOLE_EXPECTED
+ }, tests.length ? nextTest : callback);
+ });
+ });
+ });
+ })();
+}
+
+function testOverrides(callback) {
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_OVERRIDES_ENABLED, true]]
+ }, function nextTest() {
+ testUA(tests.shift(), function() { tests.length ? nextTest() : callback() });
+ });
+}
+
+SpecialPowers.loadChromeScript(_ => {
+ Components.utils.import("resource://gre/modules/UserAgentOverrides.jsm");
+ UserAgentOverrides.init();
+});
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestCompleteLog();
+SimpleTest.requestLongerTimeout(5);
+
+testOverrides(function() {
+ testInactive(function() {
+ testPriority(SimpleTest.finish)
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_user_agent_updates.html b/netwerk/test/mochitests/test_user_agent_updates.html
new file mode 100644
index 000000000..839f9e000
--- /dev/null
+++ b/netwerk/test/mochitests/test_user_agent_updates.html
@@ -0,0 +1,369 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=897221
+-->
+<head>
+ <title>Test for User Agent Updates</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897221">Mozilla Bug 897221</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
+const PREF_UPDATES = "general.useragent.updates.";
+const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
+const PREF_UPDATES_URL = PREF_UPDATES + "url";
+const PREF_UPDATES_INTERVAL = PREF_UPDATES + "interval";
+const PREF_UPDATES_TIMEOUT = PREF_UPDATES + "timeout";
+
+const DEFAULT_UA = navigator.userAgent;
+const UA_OVERRIDE = "DummyUserAgent";
+const UA_ALT_OVERRIDE = "AltUserAgent";
+
+const UA_PARTIAL_FROM = "\\wozilla"; // /\wozilla
+const UA_PARTIAL_SEP = "#";
+const UA_PARTIAL_TO = UA_OVERRIDE;
+const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO;
+const UA_PARTIAL_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
+
+function getUA(host) {
+ var url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false); // sync request
+ xhr.send();
+ is(xhr.status, 200, 'request failed');
+ is(typeof xhr.response, 'string', 'invalid response');
+ return xhr.response;
+}
+
+function testUAIFrame(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
+ let url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+ let ifr = document.createElement('IFRAME');
+
+ ifr.src = url;
+
+ document.getElementById('content').appendChild(ifr);
+
+ window.addEventListener("message", function recv(e) {
+ ok(sameQ == (e.data.header.indexOf(expected) != -1), message);
+ if (testNavQ) {
+ ok(navSameQ == (e.data.nav.indexOf(expected) != -1), navMessage);
+ }
+ window.removeEventListener("message", recv, false);
+ callback();
+ }, false);
+}
+
+function testUAIFrameNoNav(host, expected, sameQ, message, callback) {
+ testUAIFrame(host, expected, sameQ, message, false, true, '', callback);
+}
+
+const OVERRIDES = [
+ {
+ domain: 'example.org',
+ override: '%DATE%',
+ host: 'http://example.org'
+ },
+ {
+ domain: 'test1.example.org',
+ override: '%PRODUCT%',
+ expected: SpecialPowers.Services.appinfo.name,
+ host: 'http://test1.example.org'
+ },
+ {
+ domain: 'test2.example.org',
+ override: '%APP_ID%',
+ expected: SpecialPowers.Services.appinfo.ID,
+ host: 'http://test2.example.org'
+ },
+ {
+ domain: 'sub1.test1.example.org',
+ override: '%APP_VERSION%',
+ expected: SpecialPowers.Services.appinfo.version,
+ host: 'http://sub1.test1.example.org'
+ },
+ {
+ domain: 'sub2.test1.example.org',
+ override: '%BUILD_ID%',
+ expected: SpecialPowers.Services.appinfo.appBuildID,
+ host: 'http://sub2.test1.example.org'
+ },
+ {
+ domain: 'sub1.test2.example.org',
+ override: '%OS%',
+ expected: SpecialPowers.Services.appinfo.OS,
+ host: 'http://sub1.test2.example.org'
+ },
+ {
+ domain: 'sub2.test2.example.org',
+ override: UA_PARTIAL_OVERRIDE,
+ expected: UA_PARTIAL_EXPECTED,
+ host: 'http://sub2.test2.example.org'
+ },
+];
+
+function getServerURL() {
+ var url = location.pathname;
+ return location.origin + url.slice(0, url.lastIndexOf('/')) + '/user_agent_update.sjs?';
+}
+
+function getUpdateURL() {
+ var url = getServerURL();
+ var overrides = {};
+ overrides[location.hostname] = UA_OVERRIDE;
+ OVERRIDES.forEach(function (val) {
+ overrides[val.domain] = val.override;
+ });
+ url = url + encodeURIComponent(JSON.stringify(overrides)).replace(/%25/g, '%');
+ return url;
+}
+
+function testDownload(callback) {
+ var startTime = Date.now();
+ var url = getUpdateURL();
+ isnot(navigator.userAgent, UA_OVERRIDE, 'UA already overridden');
+ info('Waiting for UA update: ' + url);
+
+ chromeScript.sendAsyncMessage("notify-on-update");
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UPDATES_ENABLED, true],
+ [PREF_UPDATES_URL, url],
+ [PREF_UPDATES_TIMEOUT, 10000],
+ [PREF_UPDATES_INTERVAL, 1] // 1 second interval
+ ]
+ });
+
+ function waitForUpdate() {
+ info("Update Happened");
+ testUAIFrameNoNav(location.origin, UA_OVERRIDE, true, 'Header UA not overridden', function() {
+ var updateTime = parseInt(getUA('http://example.org'));
+ todo(startTime <= updateTime, 'Update was before start time');
+ todo(updateTime <= Date.now(), 'Update was after present time');
+
+ let overs = OVERRIDES;
+ (function nextOverride() {
+ val = overs.shift();
+ if (val.expected) {
+ testUAIFrameNoNav(val.host, val.expected, true, 'Incorrect URL parameter: ' + val.override, function() {
+ overs.length ? nextOverride() : callback();
+ });
+ } else {
+ nextOverride();
+ }
+ })();
+ });
+ }
+
+ chromeScript.addMessageListener("useragent-update-complete", waitForUpdate);
+}
+
+function testBadUpdate(callback) {
+ var url = getServerURL() + 'invalid-json';
+ var prevOverride = navigator.userAgent;
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UPDATES_URL, url],
+ [PREF_UPDATES_INTERVAL, 1] // 1 second interval
+ ]
+ }, function () { setTimeout(function () {
+ var ifr = document.createElement('IFRAME');
+ ifr.src = "about:blank";
+
+ ifr.addEventListener('load', function() {
+ // We want to make sure a bad update doesn't cancel out previous
+ // overrides. We do this by waiting for 5 seconds (assuming the update
+ // occurs within 5 seconds), and check that the previous override hasn't
+ // changed.
+ is(navigator.userAgent, prevOverride,
+ 'Invalid update deleted previous override');
+ callback();
+ }, false);
+ document.getElementById('content').appendChild(ifr);
+ }, 5000); });
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("Test sets timeouts to wait for updates to happen.");
+
+SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_TIMERMINIMUMDELAY, 0]
+ ]
+}, function () {
+ chromeScript.sendSyncMessage("UAO-uninit");
+
+ // Sets the OVERRIDES var in the chrome script.
+ // We do this to avoid code duplication.
+ chromeScript.sendSyncMessage("set-overrides", OVERRIDES);
+
+ // testProfileLoad, testDownload, and testProfileSave must run in this order
+ // because testDownload depends on testProfileLoad to call UAO.init()
+ // and testProfileSave depends on testDownload to save overrides to the profile
+ chromeScript.sendAsyncMessage("testProfileLoad", location.hostname);
+});
+
+
+const chromeScript = SpecialPowers.loadChromeScript(_ => {
+ // Enter update timer manager test mode
+ Components.classes["@mozilla.org/updates/timer-manager;1"].getService(
+ Components.interfaces.nsIObserver).observe(null, "utm-test-init", "");
+
+ Components.utils.import("resource://gre/modules/UserAgentOverrides.jsm");
+
+ var _notifyOnUpdate = false;
+
+ var UAO = UserAgentOverrides;
+ UAO.uninit();
+
+ Components.utils.import("resource://gre/modules/FileUtils.jsm");
+ var FU = FileUtils;
+
+ const { TextDecoder, TextEncoder, OS } = Components.utils.import("resource://gre/modules/osfile.jsm");
+ var OSF = OS.File;
+
+ const KEY_PREFDIR = "PrefD";
+ const KEY_APPDIR = "XCurProcD";
+ const FILE_UPDATES = "ua-update.json";
+
+ const UA_OVERRIDE = "DummyUserAgent";
+ const UA_ALT_OVERRIDE = "AltUserAgent";
+
+ const PREF_UPDATES = "general.useragent.updates.";
+ const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
+ const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.prefs.addObserver(PREF_UPDATES_LASTUPDATED, () => {
+ if (_notifyOnUpdate) {
+ _notifyOnUpdate = false; // Only notify once, for the first update.
+ sendAsyncMessage("useragent-update-complete");
+ }
+ } , false);
+
+ var OVERRIDES = null;
+
+ function is(value, expected, message) {
+ sendAsyncMessage("is-message", {value, expected, message});
+ }
+
+ function info(message) {
+ sendAsyncMessage("info-message", message);
+ }
+
+ function testProfileSave(hostname) {
+ info('Waiting for saving to profile');
+ var file = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
+ (function waitForSave() {
+ OSF.exists(file).then(
+ (exists) => {
+ if (!exists) {
+ setTimeout(waitForSave, 100);
+ return;
+ }
+ return OSF.read(file).then(
+ (bytes) => {
+ info('Saved new overrides');
+ var decoder = new TextDecoder();
+ var overrides = JSON.parse(decoder.decode(bytes));
+ is(overrides[hostname], UA_OVERRIDE, 'Incorrect saved override');
+ OVERRIDES.forEach(function (val) {
+ val.expected && is(overrides[val.domain], val.expected,
+ 'Incorrect saved override: ' + val.override);
+ });
+ sendAsyncMessage("testProfileSaveDone");
+ }
+ );
+ }
+ ).then(null,
+ (reason) => {
+ throw reason
+ }
+ );
+ })();
+ }
+
+ function testProfileLoad(hostname) {
+ var file = FU.getFile(KEY_APPDIR, [FILE_UPDATES]).path;
+ var encoder = new TextEncoder();
+ var overrides = {};
+ overrides[hostname] = UA_ALT_OVERRIDE;
+ var bytes = encoder.encode(JSON.stringify(overrides));
+
+ var badfile = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
+ var badbytes = encoder.encode("null");
+
+ OSF.writeAtomic(file, bytes, {tmpPath: file + ".tmp"}).then(
+ () => OSF.writeAtomic(badfile, badbytes, {tmpPath: badfile + ".tmp"})
+ ).then(
+ () => {
+ sendAsyncMessage("testProfileLoadDone");
+ },
+ (reason) => {
+ throw reason
+ }
+ );
+ }
+
+
+ addMessageListener("testProfileSave", testProfileSave);
+ addMessageListener("testProfileLoad", testProfileLoad);
+ addMessageListener("set-overrides", function(overrides) { OVERRIDES = overrides});
+ addMessageListener("UAO-init", function() { UAO.init(); });
+ addMessageListener("UAO-uninit", function() { UAO.uninit(); });
+ addMessageListener("notify-on-update", () => { _notifyOnUpdate = true });
+});
+
+chromeScript.addMessageListener("testProfileSaveDone", SimpleTest.finish);
+chromeScript.addMessageListener("testProfileLoadDone", function() {
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_UPDATES_ENABLED, true]]
+ }, function () {
+ // initialize UserAgentOverrides.jsm and
+ // UserAgentUpdates.jsm and load saved file
+ chromeScript.sendSyncMessage("UAO-init");
+ (function waitForLoad() {
+ var ifr = document.createElement('IFRAME');
+ ifr.src = "about:blank";
+
+ ifr.addEventListener('load', function() {
+ var nav = ifr.contentWindow.navigator;
+ if (nav.userAgent !== UA_ALT_OVERRIDE) {
+ setTimeout(waitForLoad, 100);
+ return;
+ }
+ testUAIFrameNoNav(location.origin, UA_ALT_OVERRIDE, true, 'Did not apply saved override', function () {
+ testDownload(function() {
+ testBadUpdate(function() {
+ chromeScript.sendAsyncMessage("testProfileSave", location.hostname);
+ })
+ })
+ });
+ }, true);
+
+ document.getElementById('content').appendChild(ifr);
+ })();
+ });
+});
+
+chromeScript.addMessageListener("is-message", function(params) {
+ let {value, expected, message} = params;
+ is(value, expected, message);
+});
+chromeScript.addMessageListener("info-message", function(message) {
+ info(message);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_user_agent_updates_reset.html b/netwerk/test/mochitests/test_user_agent_updates_reset.html
new file mode 100644
index 000000000..5b51fc40d
--- /dev/null
+++ b/netwerk/test/mochitests/test_user_agent_updates_reset.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=942470
+-->
+<head>
+ <title>Test for Bug 942470</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=942470">Mozilla Bug 942470</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 942470 **/
+
+function getUA(host) {
+ var url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false); // sync request
+ xhr.send();
+ is(xhr.status, 200, 'request failed');
+ is(typeof xhr.response, 'string', 'invalid response');
+ return xhr.response;
+}
+
+const UA_OVERRIDE = "DummyUserAgent";
+
+info("User agent is " + navigator.userAgent);
+isnot(navigator.userAgent, UA_OVERRIDE,
+ "navigator.userAgent is not reverted");
+isnot(getUA(location.origin), UA_OVERRIDE,
+ "User-Agent is not reverted");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_viewsource_unlinkable.html b/netwerk/test/mochitests/test_viewsource_unlinkable.html
new file mode 100644
index 000000000..9a1a35d68
--- /dev/null
+++ b/netwerk/test/mochitests/test_viewsource_unlinkable.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for view-source linkability</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+function runTest() {
+ SimpleTest.doesThrow(function() {
+ window.open('view-source:' + location.href, "_blank");
+ }, "Trying to access view-source URL from unprivileged code should throw.");
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
+
diff --git a/netwerk/test/mochitests/test_xhr_method_case.html b/netwerk/test/mochitests/test_xhr_method_case.html
new file mode 100644
index 000000000..236ab210b
--- /dev/null
+++ b/netwerk/test/mochitests/test_xhr_method_case.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+XHR uppercases certain method names, but not others
+-->
+<head>
+ <title>Test for XHR Method casing</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+const testMethods = [
+// these methods should be normalized
+ ["get", "GET"],
+ ["GET", "GET"],
+ ["GeT", "GET"],
+ ["geT", "GET"],
+ ["GEt", "GET"],
+ ["post", "POST"],
+ ["POST", "POST"],
+ ["delete", "DELETE"],
+ ["DELETE", "DELETE"],
+ ["options", "OPTIONS"],
+ ["OPTIONS", "OPTIONS"],
+ ["put", "PUT"],
+ ["PUT", "PUT"],
+// HEAD is not tested because we use the resposne body as part of the test
+// ["head", "HEAD"],
+// ["HEAD", "HEAD"],
+
+// other custom methods should not be normalized
+ ["Foo", "Foo"],
+ ["bAR", "bAR"],
+ ["foobar", "foobar"],
+ ["FOOBAR", "FOOBAR"]
+]
+
+function doIter(index)
+{
+ var xhr = new XMLHttpRequest();
+ xhr.open(testMethods[index][0], 'method.sjs', false); // sync request
+ xhr.send();
+ is(xhr.status, 200, 'transaction failed');
+ is(xhr.response, testMethods[index][1], 'unexpected method');
+}
+
+function dotest()
+{
+ SimpleTest.waitForExplicitFinish();
+ for (var i = 0; i < testMethods.length; i++) {
+ doIter(i);
+ }
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+<body onload="dotest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/netwerk/test/mochitests/user_agent.sjs b/netwerk/test/mochitests/user_agent.sjs
new file mode 100644
index 000000000..dea299a20
--- /dev/null
+++ b/netwerk/test/mochitests/user_agent.sjs
@@ -0,0 +1,21 @@
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ // used by test_user_agent tests
+ response.write(
+ "<html><body>\
+ <script type='text/javascript'>\
+ var msg = {\
+ header: '" + request.getHeader('User-Agent') + "',\
+ nav: navigator.userAgent\
+ };\
+ self.parent.postMessage(msg, '*');\
+ </script>\
+ </body></html>"
+ );
+}
diff --git a/netwerk/test/mochitests/user_agent_update.sjs b/netwerk/test/mochitests/user_agent_update.sjs
new file mode 100644
index 000000000..649ceb903
--- /dev/null
+++ b/netwerk/test/mochitests/user_agent_update.sjs
@@ -0,0 +1,10 @@
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/json", false);
+
+ // used by test_user_agent_updates test
+ response.write(decodeURIComponent(request.queryString));
+}
diff --git a/netwerk/test/mochitests/web_packaged_app.sjs b/netwerk/test/mochitests/web_packaged_app.sjs
new file mode 100644
index 000000000..d5587d8d7
--- /dev/null
+++ b/netwerk/test/mochitests/web_packaged_app.sjs
@@ -0,0 +1,35 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "application/package", false);
+ response.write(octetStreamData.getData());
+ return;
+}
+
+// The package content
+// getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
+var octetStreamData = {
+ content: [
+ { headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n <head>\r\n <script> alert('OK: hello'); alert('DONE'); </script>\r\n</head>\r\n Web Packaged App Index\r\n</html>\r\n", type: "text/html" },
+ { headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
+ { headers: ["Content-Location: /scripts/helpers/math.js", "Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" }
+ ],
+ token : "gc0pJq0M:08jU534c0p",
+ getData: function() {
+ var str = "";
+ for (var i in this.content) {
+ str += "--" + this.token + "\r\n";
+ for (var j in this.content[i].headers) {
+ str += this.content[i].headers[j] + "\r\n";
+ }
+ str += "\r\n";
+ str += this.content[i].data + "\r\n";
+ }
+
+ str += "--" + this.token + "--";
+ return str;
+ }
+}
diff --git a/netwerk/test/moz.build b/netwerk/test/moz.build
new file mode 100644
index 000000000..3df865c3a
--- /dev/null
+++ b/netwerk/test/moz.build
@@ -0,0 +1,70 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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_DIRS += ['httpserver', 'gtest']
+
+BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
+MOCHITEST_MANIFESTS += ['mochitests/mochitest.ini']
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'unit/xpcshell.ini',
+ 'unit_ipc/xpcshell.ini',
+]
+
+GeckoSimplePrograms([
+ 'PropertiesTest',
+ 'ReadNTLM',
+ 'TestBlockingSocket',
+ 'TestDNS',
+ 'TestIncrementalDownload',
+ 'TestOpen',
+ 'TestProtocols',
+ 'TestServ',
+ 'TestStreamLoader',
+ 'TestUpload',
+ 'TestURLParser',
+ 'urltest',
+])
+
+# XXX Make this work in libxul builds.
+#SIMPLE_PROGRAMS += [
+# TestIDN',
+# TestIOThreads',
+# TestSocketTransport',
+# TestStreamPump',
+# TestStreamTransport',
+# TestUDPSocketProvider',
+#]
+
+CppUnitTests([
+ 'TestBind',
+ 'TestCookie',
+ 'TestUDPSocket',
+])
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ CppUnitTests([
+ 'TestNamedPipeService'
+ ])
+
+RESOURCE_FILES += [
+ 'urlparse.dat',
+ 'urlparse_unx.dat',
+]
+
+USE_LIBS += ['static:js']
+
+if CONFIG['ENABLE_INTL_API'] and CONFIG['MOZ_ICU_DATA_ARCHIVE']:
+ # The ICU libraries linked into libmozjs will not include the ICU data,
+ # so link it directly.
+ USE_LIBS += ['icudata']
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-shadow']
diff --git a/netwerk/test/reftest/658949-1-ref.html b/netwerk/test/reftest/658949-1-ref.html
new file mode 100644
index 000000000..6e6d7e25f
--- /dev/null
+++ b/netwerk/test/reftest/658949-1-ref.html
@@ -0,0 +1 @@
+<iframe src="data:text/html,ABC"></iframe>
diff --git a/netwerk/test/reftest/658949-1.html b/netwerk/test/reftest/658949-1.html
new file mode 100644
index 000000000..f61c03a52
--- /dev/null
+++ b/netwerk/test/reftest/658949-1.html
@@ -0,0 +1 @@
+<iframe src="data:text/html,ABC#myRef"></iframe>
diff --git a/netwerk/test/reftest/bug565432-1-ref.html b/netwerk/test/reftest/bug565432-1-ref.html
new file mode 100644
index 000000000..05ea512a3
--- /dev/null
+++ b/netwerk/test/reftest/bug565432-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Test newlines in href</title>
+<ul>
+<li><a href="about:blank">Link</a>
+<li><a href="data:,test">Link</a>
+<li><a href="file:///tmp/test">Link</a>
+<li><a href="ftp://test.invalid/">Link</a>
+<li><a href="gopher://test.invalid/">Link</a>
+<li><a href="http://test.invalid/">Link</a>
+<li><a href="ftp://test.invalid/%0a">Not Link</a>
+</ul>
+
diff --git a/netwerk/test/reftest/bug565432-1.html b/netwerk/test/reftest/bug565432-1.html
new file mode 100644
index 000000000..69980a534
--- /dev/null
+++ b/netwerk/test/reftest/bug565432-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Test newlines in href</title>
+<ul>
+<li><a href="
+about:blank">Link</a>
+<li><a href="
+data:,test">Link</a>
+<li><a href="
+file:///tmp/test">Link</a>
+<li><a href="
+ftp://test.invalid/">Link</a>
+<li><a href="
+gopher://test.invalid/">Link</a>
+<li><a href="
+http://test.invalid/">Link</a>
+<li><a href="ftp://test.invalid/%0a">Not Link</a>
+</ul>
+
diff --git a/netwerk/test/reftest/reftest-stylo.list b/netwerk/test/reftest/reftest-stylo.list
new file mode 100644
index 000000000..d96477f0d
--- /dev/null
+++ b/netwerk/test/reftest/reftest-stylo.list
@@ -0,0 +1,3 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+== bug565432-1.html bug565432-1.html
+== 658949-1.html 658949-1.html
diff --git a/netwerk/test/reftest/reftest.list b/netwerk/test/reftest/reftest.list
new file mode 100644
index 000000000..98b5d4fb9
--- /dev/null
+++ b/netwerk/test/reftest/reftest.list
@@ -0,0 +1,2 @@
+== bug565432-1.html bug565432-1-ref.html
+== 658949-1.html 658949-1-ref.html
diff --git a/netwerk/test/sites.txt b/netwerk/test/sites.txt
new file mode 100644
index 000000000..e3089595a
--- /dev/null
+++ b/netwerk/test/sites.txt
@@ -0,0 +1,257 @@
+http://www.yahoo.com/
+http://www.netscape.com/
+http://www.microsoft.com/
+http://www.excite.com/
+http://www.mckinley.com/
+http://www.city.net/
+http://www.webcrawler.com/
+http://www.mirabilis.com/
+http://www.infoseek.com/
+http://www.pathfinder.com/
+http://www.warnerbros.com/
+http://www.cnn.com/
+http://www.altavista.digital.com/
+http://www.altavista.com/
+http://www.usatoday.com/
+http://www.disney.com/
+http://www.starwave.com/
+http://www.hotwired.com/
+http://www.hotbot.com/
+http://www.lycos.com/
+http://www.pointcom.com/
+http://www.cnet.com/
+http://www.search.com/
+http://www.news.com/
+http://www.download.com/
+http://www.geocities.com/
+http://www.aol.com/
+http://members.aol.com/
+http://www.imdb.com/
+http://uk.imdb.com/
+http://www.macromedia.com/
+http://www.infobeat.com/
+http://www.fxweb.com/
+http://www.whowhere.com/
+http://www.real.com/
+http://www.sportsline.com/
+http://www.dejanews.com/
+http://www.the-park.com/
+http://www.cmpnet.com/
+http://www.go2net.com/
+http://www.metacrawler.com/
+http://www.playsite.com/
+http://www.stocksite.com/
+http://www.sony.com/
+http://www.music.sony.com/
+http://www.station.sony.com/
+http://www.scea.sony.com/
+http://www.infospace.com/
+http://www.zdnet.com/
+http://www.hotfiles.com/
+http://www.chathouse.com/
+http://www.looksmart.com/
+http://www.iamginegames.com/
+http://www.macaddict.com/
+http://www.rsac.org/
+http://www.apple.com/
+http://www.beseen.com/
+http://www.dogpile.com/
+http://www.xoom.com/
+http://www.tucows.com/
+http://www.freethemes.com/
+http://www.winfiles.com/
+http://www.vservers.com/
+http://www.mtv.com/
+http://www.the-xfiles.com/
+http://www.datek.com/
+http://www.cyberthrill.com/
+http://www.surplusdirect.com/
+http://www.tomshardware.com/
+http://www.bigyellow.com/
+http://www.100hot.com/
+http://www.messagemates.com/
+http://www.onelist.com/
+http://www.bluemountain.com/
+http://www.ea.com/
+http://www.bullfrog.co.uk/
+http://www.travelocity.com/
+http://www.ibm.com/
+http://www.bigcharts.com/
+http://www.davesclassics.com/
+http://www.goto.com/
+http://www.weather.com/
+http://www.gamespot.com/
+http://www.bloomberg.com/
+http://www.winzip.com/
+http://www.filez.com/
+http://www.westwood.com/
+http://www.internet.com/
+http://www.cardmaster.com/
+http://www.creaf.com/
+http://netaddress.usa.net/
+http://www.occ.com/
+http://www.as.org/
+http://www.amazon.com/
+http://www.drudgereport.com/
+http://www.hardradio.com/
+http://www.intel.com/
+http://www.mp3.com/
+http://www.ebay.com/
+http://www.msn.com/
+http://www.fifa.com/
+http://www.attitude.com/
+http://www.happypuppy.com/
+http://www.gamesdomain.com/
+http://www.onsale.com/
+http://www.tm.com/
+http://www.xlnc1.com/
+http://www.greatsports.com/
+http://www.discovery.com/
+http://www.nai.com/
+http://www.nasa.gov/
+http://www.ogr.com/
+http://www.warzone.com/
+http://www.gamestats.com/
+http://www.winamp.com/
+http://java.sun.com/
+http://www.hp.com/
+http://www.cdnow.com/
+http://www.nytimes.com/
+http://www.majorleaguebaseball.com/
+http://www.washingtonpost.com/
+http://www.planetquake.com/
+http://www.wsj.com/
+http://www.slashdot.org/
+http://www.adobe.com/
+http://www.quicken.com/
+http://www.talkcity.com/
+http://www.developer.com/
+http://www.mapquest.com/
+http://www.yahoo.com/
+http://www.pathfinder.com/
+http://www.msn.com/
+http://www.fifa.com/
+http://www.attitude.com/
+http://www.happypuppy.com/
+http://www.gamesdomain.com/
+http://www.onsale.com/
+http://www.tm.com/
+http://www.xlnc1.com/
+http://www.greatsports.com/
+http://www.discovery.com/
+http://www.warnerbros.com/
+http://www.nai.com/
+http://www.nasa.gov/
+http://www.ogr.com/
+http://www.warzone.com/
+http://www.gamestats.com/
+http://www.winamp.com/
+http://java.sun.com/
+http://www.hp.com/
+http://www.cdnow.com/
+http://www.nytimes.com/
+http://www.majorleaguebaseball.com/
+http://www.planetquake.com/
+http://www.wsj.com/
+http://www.slashdot.org/
+http://www.adobe.com/
+http://www.quicken.com/
+http://www.talkcity.com/
+http://www.developer.com/
+http://www.mapquest.com/
+http://www.altavista.digital.com/
+http://www.altavista.com/
+http://www.usatoday.com/
+http://www.disney.com/
+http://www.starwave.com/
+http://www.hotwired.com/
+http://www.hotbot.com/
+http://www.netscape.com/
+http://www.lycos.com/
+http://www.pointcom.com/
+http://www.cnet.com/
+http://www.search.com/
+http://www.news.com/
+http://www.download.com/
+http://www.geocities.com/
+http://www.imdb.com/
+http://www.microsoft.com/
+http://uk.imdb.com/
+http://www.macromedia.com/
+http://www.infobeat.com/
+http://www.fxweb.com/
+http://www.whowhere.com/
+http://www.real.com/
+http://www.sportsline.com/
+http://www.dejanews.com/
+http://www.the-park.com/
+http://www.cmpnet.com/
+http://www.excite.com/
+http://www.go2net.com/
+http://www.metacrawler.com/
+http://www.playsite.com/
+http://www.stocksite.com/
+http://www.infospace.com/
+http://www.zdnet.com/
+http://www.mckinley.com/
+http://www.hotfiles.com/
+http://www.chathouse.com/
+http://www.looksmart.com/
+http://www.iamginegames.com/
+http://www.macaddict.com/
+http://www.rsac.org/
+http://www.apple.com/
+http://www.beseen.com/
+http://www.dogpile.com/
+http://www.xoom.com/
+http://www.city.net/
+http://www.tucows.com/
+http://www.freethemes.com/
+http://www.winfiles.com/
+http://www.vservers.com/
+http://www.mtv.com/
+http://www.the-xfiles.com/
+http://www.datek.com/
+http://www.cyberthrill.com/
+http://www.surplusdirect.com/
+http://www.tomshardware.com/
+http://www.webcrawler.com/
+http://www.bigyellow.com/
+http://www.100hot.com/
+http://www.messagemates.com/
+http://www.onelist.com/
+http://www.bluemountain.com/
+http://www.ea.com/
+http://www.bullfrog.co.uk/
+http://www.travelocity.com/
+http://www.ibm.com/
+http://www.bigcharts.com/
+http://www.mirabilis.com/
+http://www.davesclassics.com/
+http://www.goto.com/
+http://www.weather.com/
+http://www.gamespot.com/
+http://www.bloomberg.com/
+http://www.winzip.com/
+http://www.filez.com/
+http://www.westwood.com/
+http://www.internet.com/
+http://www.cardmaster.com/
+http://www.infoseek.com/
+http://www.creaf.com/
+http://netaddress.usa.net/
+http://www.occ.com/
+http://www.as.org/
+http://www.amazon.com/
+http://www.drudgereport.com/
+http://www.hardradio.com/
+http://www.intel.com/
+http://www.mp3.com/
+http://www.ebay.com/
+http://www.aol.com/
+http://www.cnn.com/
+http://www.music.sony.com/
+http://www.scea.sony.com/
+http://www.sony.com/
+http://www.station.sony.com/
+http://www.washingtonpost.com/
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]
diff --git a/netwerk/test/unit_ipc/child_app_offline_notifications.js b/netwerk/test/unit_ipc/child_app_offline_notifications.js
new file mode 100644
index 000000000..870c22b39
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_app_offline_notifications.js
@@ -0,0 +1,43 @@
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function is_app_offline(appId) {
+ let ioservice = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+ return ioservice.isAppOffline(appId);
+}
+
+var events_observed_no = 0;
+
+// Holds the last observed app-offline event
+var info = null;
+function observer(aSubject, aTopic, aData) {
+ events_observed_no++;
+ info = aSubject.QueryInterface(Ci.nsIAppOfflineInfo);
+ dump("ChildObserver - subject: {" + aSubject.appId + ", " + aSubject.mode + "} ");
+}
+
+// Add observer for the app-offline notification
+function run_test() {
+ Services.obs.addObserver(observer, "network:app-offline-status-changed", false);
+}
+
+// Chech that the app has the proper offline status
+function check_status(appId, status)
+{
+ do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE);
+}
+
+// Check that the app has the proper offline status
+// and that the correct notification has been received
+function check_notification_and_status(appId, status) {
+ do_check_eq(info.appId, appId);
+ do_check_eq(info.mode, status);
+ do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE);
+}
+
+// Remove the observer from the child process
+function finished() {
+ Services.obs.removeObserver(observer, "network:app-offline-status-changed");
+ do_check_eq(events_observed_no, 2);
+}
diff --git a/netwerk/test/unit_ipc/child_channel_id.js b/netwerk/test/unit_ipc/child_channel_id.js
new file mode 100644
index 000000000..55cb2fd14
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_channel_id.js
@@ -0,0 +1,45 @@
+/**
+ * Send HTTP requests and notify the parent about their channelId
+ */
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+let shouldQuit = false;
+
+function run_test() {
+ // keep the event loop busy and the test alive until a "finish" command
+ // is issued by parent
+ do_timeout(100, function keepAlive() {
+ if (!shouldQuit) {
+ do_timeout(100, keepAlive);
+ }
+ });
+}
+
+function makeRequest(uri) {
+ let requestChannel = NetUtil.newChannel({uri, loadUsingSystemPrincipal: true});
+ requestChannel.asyncOpen2(new ChannelListener(checkResponse, requestChannel));
+ requestChannel.QueryInterface(Ci.nsIHttpChannel);
+ dump(`Child opened request: ${uri}, channelId=${requestChannel.channelId}\n`);
+}
+
+function checkResponse(request, buffer, requestChannel) {
+ // notify the parent process about the original request channel
+ requestChannel.QueryInterface(Ci.nsIHttpChannel);
+ do_send_remote_message(`request:${requestChannel.channelId}`);
+
+ // the response channel can be different (if it was redirected)
+ let responseChannel = request.QueryInterface(Ci.nsIHttpChannel);
+
+ let uri = responseChannel.URI.spec;
+ let origUri = responseChannel.originalURI.spec;
+ let id = responseChannel.channelId;
+ dump(`Child got response to: ${uri} (orig=${origUri}), channelId=${id}\n`);
+
+ // notify the parent process about this channel's ID
+ do_send_remote_message(`response:${id}`);
+}
+
+function finish() {
+ shouldQuit = true;
+}
diff --git a/netwerk/test/unit_ipc/head_cc.js b/netwerk/test/unit_ipc/head_cc.js
new file mode 100644
index 000000000..2765f95f2
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_cc.js
@@ -0,0 +1,4 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
diff --git a/netwerk/test/unit_ipc/head_channels_clone.js b/netwerk/test/unit_ipc/head_channels_clone.js
new file mode 100644
index 000000000..f5eb45cbb
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_channels_clone.js
@@ -0,0 +1,8 @@
+//
+// Load standard base class for network tests into child process
+//
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+
+load("../unit/head_channels.js");
+
diff --git a/netwerk/test/unit_ipc/test_XHR_redirects.js b/netwerk/test/unit_ipc/test_XHR_redirects.js
new file mode 100644
index 000000000..472817a9e
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_XHR_redirects.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_XHR_redirects.js");
+}
diff --git a/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js b/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js
new file mode 100644
index 000000000..04441c22a
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_alt-data_simple.js");
+}
diff --git a/netwerk/test/unit_ipc/test_alt-data_stream_wrap.js b/netwerk/test/unit_ipc/test_alt-data_stream_wrap.js
new file mode 100644
index 000000000..1eee2f243
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_stream_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_alt-data_stream.js");
+}
diff --git a/netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js b/netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js
new file mode 100644
index 000000000..bb8b28815
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js
@@ -0,0 +1,7 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_bug248970_cookie.js");
+} \ No newline at end of file
diff --git a/netwerk/test/unit_ipc/test_bug528292_wrap.js b/netwerk/test/unit_ipc/test_bug528292_wrap.js
new file mode 100644
index 000000000..117ada748
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_bug528292_wrap.js
@@ -0,0 +1,6 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ run_test_in_child("../unit/test_bug528292.js");
+} \ No newline at end of file
diff --git a/netwerk/test/unit_ipc/test_cache_jar_wrap.js b/netwerk/test/unit_ipc/test_cache_jar_wrap.js
new file mode 100644
index 000000000..fa2bb82a8
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cache_jar_wrap.js
@@ -0,0 +1,4 @@
+function run_test() {
+ run_test_in_child("../unit/head_cache2.js");
+ run_test_in_child("../unit/test_cache_jar.js");
+}
diff --git a/netwerk/test/unit_ipc/test_cacheflags_wrap.js b/netwerk/test/unit_ipc/test_cacheflags_wrap.js
new file mode 100644
index 000000000..e269e1ee5
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cacheflags_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_cacheflags.js");
+}
diff --git a/netwerk/test/unit_ipc/test_channel_close_wrap.js b/netwerk/test/unit_ipc/test_channel_close_wrap.js
new file mode 100644
index 000000000..ead999957
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_close_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_channel_close.js");
+}
diff --git a/netwerk/test/unit_ipc/test_channel_id.js b/netwerk/test/unit_ipc/test_channel_id.js
new file mode 100644
index 000000000..d82e5f5df
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_id.js
@@ -0,0 +1,109 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+/*
+ * Test that when doing HTTP requests, the nsIHttpChannel is detected in
+ * both parent and child and shares the same channelId across processes.
+ */
+
+let httpserver;
+let port;
+
+function startHttpServer() {
+ httpserver = new HttpServer();
+
+ httpserver.registerPathHandler("/resource", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.bodyOutputStream.write("data", 4);
+ });
+
+ httpserver.registerPathHandler("/redirect", (metadata, response) => {
+ response.setStatusLine(metadata.httpVersion, 302, "Redirect");
+ response.setHeader("Location", "/resource", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ });
+
+ httpserver.start(-1);
+ port = httpserver.identity.primaryPort;
+}
+
+function stopHttpServer(next) {
+ httpserver.stop(next);
+}
+
+let expectedParentChannels = [];
+let expectedChildMessages = [];
+
+let maybeFinishWaitForParentChannels;
+let parentChannelsDone = new Promise(resolve => {
+ maybeFinishWaitForParentChannels = () => {
+ if (expectedParentChannels.length == 0) {
+ dump("All expected parent channels were detected\n");
+ resolve();
+ }
+ };
+});
+
+function observer(subject, topic, data) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ let uri = channel.URI.spec;
+ let origUri = channel.originalURI.spec;
+ let id = channel.channelId;
+ dump(`Parent detected channel: ${uri} (orig=${origUri}): channelId=${id}\n`);
+
+ // did we expect a new channel?
+ let expected = expectedParentChannels.shift();
+ do_check_true(!!expected);
+
+ // Start waiting for the messages about request/response from child
+ for (let event of expected) {
+ let message = `${event}:${id}`;
+ dump(`Expecting message from child: ${message}\n`);
+
+ let messagePromise = do_await_remote_message(message).then(() => {
+ dump(`Expected message from child arrived: ${message}\n`);
+ });
+ expectedChildMessages.push(messagePromise);
+ }
+
+ // If we don't expect any further parent channels, finish the parent wait
+ maybeFinishWaitForParentChannels();
+}
+
+function run_test() {
+ startHttpServer();
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+ run_test_in_child("child_channel_id.js", makeRequests);
+}
+
+function makeRequests() {
+ // First, a normal request without any redirect. Expect one channel detected
+ // in parent, used by both request and response.
+ expectedParentChannels.push(["request", "response"]);
+ sendCommand(`makeRequest("http://localhost:${port}/resource");`);
+
+ // Second request will be redirected. Expect two channels, one with the
+ // original request, then the redirected one which gets the final response.
+ expectedParentChannels.push(["request"], ["response"]);
+ sendCommand(`makeRequest("http://localhost:${port}/redirect");`);
+
+ waitForParentChannels();
+}
+
+function waitForParentChannels() {
+ parentChannelsDone.then(waitForChildMessages);
+}
+
+function waitForChildMessages() {
+ dump(`Waiting for ${expectedChildMessages.length} child messages\n`);
+ Promise.all(expectedChildMessages).then(finish);
+}
+
+function finish() {
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ sendCommand("finish();", () => stopHttpServer(do_test_finished));
+}
diff --git a/netwerk/test/unit_ipc/test_chunked_responses_wrap.js b/netwerk/test/unit_ipc/test_chunked_responses_wrap.js
new file mode 100644
index 000000000..72bb40554
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_chunked_responses_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_chunked_responses.js");
+}
diff --git a/netwerk/test/unit_ipc/test_cookie_header_wrap.js b/netwerk/test/unit_ipc/test_cookie_header_wrap.js
new file mode 100644
index 000000000..3a071a6c1
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookie_header_wrap.js
@@ -0,0 +1,11 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_cookie_header.js");
+}
diff --git a/netwerk/test/unit_ipc/test_cookiejars_wrap.js b/netwerk/test/unit_ipc/test_cookiejars_wrap.js
new file mode 100644
index 000000000..dc3ea7d9f
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookiejars_wrap.js
@@ -0,0 +1,7 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_cookiejars.js");
+}
diff --git a/netwerk/test/unit_ipc/test_dns_cancel_wrap.js b/netwerk/test/unit_ipc/test_dns_cancel_wrap.js
new file mode 100644
index 000000000..2f38aa4ec
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_cancel_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_dns_cancel.js");
+}
diff --git a/netwerk/test/unit_ipc/test_dns_per_interface_wrap.js b/netwerk/test/unit_ipc/test_dns_per_interface_wrap.js
new file mode 100644
index 000000000..25e7217b0
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_per_interface_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_dns_per_interface.js");
+}
diff --git a/netwerk/test/unit_ipc/test_dns_service_wrap.js b/netwerk/test/unit_ipc/test_dns_service_wrap.js
new file mode 100644
index 000000000..fdbecf16d
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_service_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_dns_service.js");
+}
diff --git a/netwerk/test/unit_ipc/test_duplicate_headers_wrap.js b/netwerk/test/unit_ipc/test_duplicate_headers_wrap.js
new file mode 100644
index 000000000..6225d593d
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_duplicate_headers_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_duplicate_headers.js");
+}
diff --git a/netwerk/test/unit_ipc/test_event_sink_wrap.js b/netwerk/test/unit_ipc/test_event_sink_wrap.js
new file mode 100644
index 000000000..908c971f8
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_event_sink_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_event_sink.js");
+}
diff --git a/netwerk/test/unit_ipc/test_getHost_wrap.js b/netwerk/test/unit_ipc/test_getHost_wrap.js
new file mode 100644
index 000000000..f74ab7d15
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_getHost_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_getHost.js");
+}
diff --git a/netwerk/test/unit_ipc/test_head_wrap.js b/netwerk/test/unit_ipc/test_head_wrap.js
new file mode 100644
index 000000000..13f0702e5
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_head_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_head.js");
+}
diff --git a/netwerk/test/unit_ipc/test_headers_wrap.js b/netwerk/test/unit_ipc/test_headers_wrap.js
new file mode 100644
index 000000000..e0bae4080
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_headers_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_headers.js");
+}
diff --git a/netwerk/test/unit_ipc/test_httpsuspend_wrap.js b/netwerk/test/unit_ipc/test_httpsuspend_wrap.js
new file mode 100644
index 000000000..348541283
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_httpsuspend_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_httpsuspend.js");
+} \ No newline at end of file
diff --git a/netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js b/netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js
new file mode 100644
index 000000000..91a8a00f0
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_original_sent_received_head.js");
+}
diff --git a/netwerk/test/unit_ipc/test_post_wrap.js b/netwerk/test/unit_ipc/test_post_wrap.js
new file mode 100644
index 000000000..27afae5b4
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_post_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_post.js");
+}
diff --git a/netwerk/test/unit_ipc/test_predictor_wrap.js b/netwerk/test/unit_ipc/test_predictor_wrap.js
new file mode 100644
index 000000000..de4ddc174
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_predictor_wrap.js
@@ -0,0 +1,36 @@
+// This is the bit that runs in the parent process when the test begins. Here's
+// what happens:
+//
+// - Load the entire single-process test in the parent
+// - Setup the test harness within the child process
+// - Send a command to the child to have the single-process test loaded there, as well
+// - Send a command to the child to make the predictor available
+// - run_test_real in the parent
+// - run_test_real does a bunch of pref-setting and other fun things on the parent
+// - once all that's done, it calls run_next_test IN THE PARENT
+// - every time run_next_test is called, it is called IN THE PARENT
+// - the test that gets started then does any parent-side setup that's necessary
+// - once the parent-side setup is done, it calls continue_<testname> IN THE CHILD
+// - when the test is done running on the child, the child calls predictor.reset
+// this causes some code to be run on the parent which, when complete, sends an
+// obserer service notification IN THE PARENT, which causes run_next_test to be
+// called again, bumping us up to the top of the loop outlined here
+// - when the final test is done, cleanup happens IN THE PARENT and we're done
+//
+// This is a little confusing, but it's what we have to do in order to have some
+// things that must run on the parent (the setup - opening cache entries, etc)
+// but with most of the test running on the child (calls to the predictor api,
+// verification, etc).
+//
+function run_test() {
+ var test_path = do_get_file("../unit/test_predictor.js").path.replace(/\\/g, "/");
+ load(test_path);
+ do_load_child_test_harness();
+ do_test_pending();
+ sendCommand("load(\"" + test_path + "\");", function () {
+ sendCommand("predictor = Cc[\"@mozilla.org/network/predictor;1\"].getService(Ci.nsINetworkPredictor);", function() {
+ run_test_real();
+ do_test_finished();
+ });
+ });
+}
diff --git a/netwerk/test/unit_ipc/test_progress_wrap.js b/netwerk/test/unit_ipc/test_progress_wrap.js
new file mode 100644
index 000000000..c4a658c09
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_progress_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_progress.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js b/netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js
new file mode 100644
index 000000000..a1b8adfb9
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_canceled_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect-caching_canceled.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js b/netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js
new file mode 100644
index 000000000..4fea2fdf6
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect-caching_failure.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js b/netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js
new file mode 100644
index 000000000..851950186
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_passing_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect-caching_passing.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_canceled_wrap.js b/netwerk/test/unit_ipc/test_redirect_canceled_wrap.js
new file mode 100644
index 000000000..546e05141
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_canceled_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+//
+function run_test() {
+ run_test_in_child("../unit/test_redirect_canceled.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js b/netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js
new file mode 100644
index 000000000..c45e7810a
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_different-protocol_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_redirect_different-protocol.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_failure_wrap.js b/netwerk/test/unit_ipc/test_redirect_failure_wrap.js
new file mode 100644
index 000000000..fbe27697f
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_failure_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_redirect_failure.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_from_script_wrap.js b/netwerk/test/unit_ipc/test_redirect_from_script_wrap.js
new file mode 100644
index 000000000..74f77a9ea
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_from_script_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_redirect_from_script.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_history_wrap.js b/netwerk/test/unit_ipc/test_redirect_history_wrap.js
new file mode 100644
index 000000000..38cdfa35e
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_history_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_redirect_history.js");
+}
diff --git a/netwerk/test/unit_ipc/test_redirect_passing_wrap.js b/netwerk/test/unit_ipc/test_redirect_passing_wrap.js
new file mode 100644
index 000000000..597ac35fb
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_passing_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_redirect_passing.js");
+}
diff --git a/netwerk/test/unit_ipc/test_reentrancy_wrap.js b/netwerk/test/unit_ipc/test_reentrancy_wrap.js
new file mode 100644
index 000000000..43d72dd05
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_reentrancy_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_reentrancy.js");
+} \ No newline at end of file
diff --git a/netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js b/netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js
new file mode 100644
index 000000000..f2d90c33d
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_reply_without_content_type_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_reply_without_content_type.js");
+}
diff --git a/netwerk/test/unit_ipc/test_resumable_channel_wrap.js b/netwerk/test/unit_ipc/test_resumable_channel_wrap.js
new file mode 100644
index 000000000..573ab25b6
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_resumable_channel_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_resumable_channel.js");
+}
diff --git a/netwerk/test/unit_ipc/test_simple_wrap.js b/netwerk/test/unit_ipc/test_simple_wrap.js
new file mode 100644
index 000000000..8c6957e94
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_simple_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+ run_test_in_child("../unit/test_simple.js");
+}
diff --git a/netwerk/test/unit_ipc/test_synthesized_response_wrap.js b/netwerk/test/unit_ipc/test_synthesized_response_wrap.js
new file mode 100644
index 000000000..337646df5
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_synthesized_response_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_synthesized_response.js");
+}
diff --git a/netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js b/netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js
new file mode 100644
index 000000000..00b4a0b85
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_xmlhttprequest.js");
+}
diff --git a/netwerk/test/unit_ipc/xpcshell.ini b/netwerk/test/unit_ipc/xpcshell.ini
new file mode 100644
index 000000000..ec7a13df9
--- /dev/null
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,99 @@
+[DEFAULT]
+head = head_channels_clone.js head_cc.js
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ child_channel_id.js
+ !/netwerk/test/unit/test_XHR_redirects.js
+ !/netwerk/test/unit/test_bug248970_cookie.js
+ !/netwerk/test/unit/test_bug528292.js
+ !/netwerk/test/unit/test_cache_jar.js
+ !/netwerk/test/unit/test_cacheflags.js
+ !/netwerk/test/unit/test_channel_close.js
+ !/netwerk/test/unit/test_cookie_header.js
+ !/netwerk/test/unit/test_cookiejars.js
+ !/netwerk/test/unit/test_dns_cancel.js
+ !/netwerk/test/unit/test_dns_per_interface.js
+ !/netwerk/test/unit/test_dns_service.js
+ !/netwerk/test/unit/test_duplicate_headers.js
+ !/netwerk/test/unit/test_event_sink.js
+ !/netwerk/test/unit/test_getHost.js
+ !/netwerk/test/unit/test_head.js
+ !/netwerk/test/unit/test_headers.js
+ !/netwerk/test/unit/test_httpsuspend.js
+ !/netwerk/test/unit/test_post.js
+ !/netwerk/test/unit/test_predictor.js
+ !/netwerk/test/unit/test_progress.js
+ !/netwerk/test/unit/test_redirect-caching_canceled.js
+ !/netwerk/test/unit/test_redirect-caching_failure.js
+ !/netwerk/test/unit/test_redirect-caching_passing.js
+ !/netwerk/test/unit/test_redirect_canceled.js
+ !/netwerk/test/unit/test_redirect_different-protocol.js
+ !/netwerk/test/unit/test_redirect_failure.js
+ !/netwerk/test/unit/test_redirect_from_script.js
+ !/netwerk/test/unit/test_redirect_history.js
+ !/netwerk/test/unit/test_redirect_passing.js
+ !/netwerk/test/unit/test_reentrancy.js
+ !/netwerk/test/unit/test_reply_without_content_type.js
+ !/netwerk/test/unit/test_resumable_channel.js
+ !/netwerk/test/unit/test_simple.js
+ !/netwerk/test/unit/test_synthesized_response.js
+ !/netwerk/test/unit/test_xmlhttprequest.js
+ !/netwerk/test/unit/head_channels.js
+ !/netwerk/test/unit/head_cache2.js
+ !/netwerk/test/unit/data/image.png
+ !/netwerk/test/unit/data/system_root.lnk
+ !/netwerk/test/unit/data/test_psl.txt
+ !/netwerk/test/unit/data/test_readline1.txt
+ !/netwerk/test/unit/data/test_readline2.txt
+ !/netwerk/test/unit/data/test_readline3.txt
+ !/netwerk/test/unit/data/test_readline4.txt
+ !/netwerk/test/unit/data/test_readline5.txt
+ !/netwerk/test/unit/data/test_readline6.txt
+ !/netwerk/test/unit/data/test_readline7.txt
+ !/netwerk/test/unit/data/test_readline8.txt
+ !/netwerk/test/unit/data/signed_win.exe
+ !/netwerk/test/unit/test_alt-data_simple.js
+ !/netwerk/test/unit/test_alt-data_stream.js
+
+[test_bug528292_wrap.js]
+[test_bug248970_cookie_wrap.js]
+[test_cacheflags_wrap.js]
+[test_cache_jar_wrap.js]
+[test_channel_close_wrap.js]
+[test_cookie_header_wrap.js]
+[test_cookiejars_wrap.js]
+[test_dns_cancel_wrap.js]
+[test_dns_per_interface_wrap.js]
+[test_dns_service_wrap.js]
+[test_duplicate_headers_wrap.js]
+[test_event_sink_wrap.js]
+[test_head_wrap.js]
+[test_headers_wrap.js]
+[test_httpsuspend_wrap.js]
+[test_post_wrap.js]
+[test_predictor_wrap.js]
+[test_progress_wrap.js]
+[test_redirect-caching_canceled_wrap.js]
+[test_redirect-caching_failure_wrap.js]
+[test_redirect-caching_passing_wrap.js]
+[test_redirect_canceled_wrap.js]
+[test_redirect_failure_wrap.js]
+# Do not test the channel.redirectTo() API under e10s until 827269 is resolved
+[test_redirect_from_script_wrap.js]
+skip-if = true
+[test_redirect_passing_wrap.js]
+[test_redirect_different-protocol_wrap.js]
+[test_reentrancy_wrap.js]
+[test_resumable_channel_wrap.js]
+[test_simple_wrap.js]
+[test_synthesized_response_wrap.js]
+[test_xmlhttprequest_wrap.js]
+[test_XHR_redirects.js]
+[test_redirect_history_wrap.js]
+[test_reply_without_content_type_wrap.js]
+[test_getHost_wrap.js]
+[test_alt-data_simple_wrap.js]
+[test_alt-data_stream_wrap.js]
+[test_original_sent_received_head_wrap.js]
+[test_channel_id.js]
diff --git a/netwerk/test/urlparse.dat b/netwerk/test/urlparse.dat
new file mode 100644
index 000000000..3b607d4e6
--- /dev/null
+++ b/netwerk/test/urlparse.dat
@@ -0,0 +1,103 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+
+http://username:password@hostname.com:80/pathname/./more/stuff/../path
+http,username,password,hostname.com,80,/pathname/more/,path,,,,,http://username:password@hostname.com:80/pathname/more/path
+http,username,password,hostname.com,80,/pathname/more/,path,,,,,http://username:password@hostname.com/pathname/more/path
+
+username@host:8080/path
+,username,,host,8080,/,path,,,,,username@host:8080/path
+Can not create URL
+
+http://gagan/
+http,,,gagan,-1,/,,,,,,http://gagan/
+http,,,gagan,-1,/,,,,,,http://gagan/
+
+scheme:host/netlib
+scheme,,,host,-1,/,netlib,,,,,scheme://host/netlib
+Can not create URL
+
+mailbox:///foo
+mailbox,,,,-1,/,foo,,,,,mailbox:///foo
+mailbox,,,,-1,/,foo,,,,,mailbox:///foo
+
+scheme:user@hostname.edu:80/pathname
+scheme,user,,hostname.edu,80,/,pathname,,,,,scheme://user@hostname.edu:80/pathname
+Can not create URL
+
+http://username:password@hostname:80/pathname
+http,username,password,hostname,80,/,pathname,,,,,http://username:password@hostname:80/pathname
+http,username,password,hostname,80,/,pathname,,,,,http://username:password@hostname/pathname
+
+http://username:password@hostname:8080/path/filebasename.fileextension;param?query#ref
+http,username,password,hostname,8080,/path/,filebasename,fileextension,param,query,ref,http://username:password@hostname:8080/path/filebasename.fileextension;param?query#ref
+http,username,password,hostname,8080,/path/,filebasename,fileextension,param,query,ref,http://username:password@hostname:8080/path/filebasename.fileextension;param?query#ref
+
+resource:/pathname
+resource,,,,-1,/,pathname,,,,,resource:///pathname
+resource,,,,-1,/,pathname,,,,,resource:///pathname
+
+ftp://uname%here.com:pwd@there.com/aPath/a.html
+ftp,uname%here.com,pwd,there.com,-1,/aPath/,a,html,,,,ftp://uname%here.com:pwd@there.com/aPath/a.html
+ftp,uname%here.com,pwd,there.com,-1,/aPath/,a,html,,,,ftp://uname%here.com:pwd@there.com/aPath/a.html
+
+http://www.inf.bme.hu?foo=bar
+http,,,www.inf.bme.hu,-1,/,,,,foo=bar,,http://www.inf.bme.hu/?foo=bar
+http,,,www.inf.bme.hu,-1,/,,,,foo=bar,,http://www.inf.bme.hu/?foo=bar
+
+http://test.com/aPath/a.html#/1/2
+http,,,test.com,-1,/aPath/,a,html,,,/1/2,http://test.com/aPath/a.html#/1/2
+http,,,test.com,-1,/aPath/,a,html,,,/1/2,http://test.com/aPath/a.html#/1/2
+
+http://user:pass@ipaddres:2/get?foo/something
+http,user,pass,ipaddres,2,/,get,,,foo/something,,http://user:pass@ipaddres:2/get?foo/something
+http,user,pass,ipaddres,2,/,get,,,foo/something,,http://user:pass@ipaddres:2/get?foo/something
+
+# testing different versions of http urls
+http:www.mozilla.org
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+
+http:/www.mozilla.org
+http,,,,-1,/,www.mozilla,org,,,,http:///www.mozilla.org
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+
+# testing cap letters (23927)
+HtTp://wWw.mozilLa.org
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+
+# testing spaces (15150)
+go.com.au?mozilla bug reports
+,,,go.com.au,-1,/,,,,mozilla%20bug%20reports,,go.com.au/?mozilla%20bug%20reports
+Can not create URL
+
+http://go.com.au?mozilla bug reports
+http,,,go.com.au,-1,/,,,,mozilla%20bug%20reports,,http://go.com.au/?mozilla%20bug%20reports
+http,,,go.com.au,-1,/,,,,mozilla%20bug%20reports,,http://go.com.au/?mozilla%20bug%20reports
+
+# testing for multiple params (14801)
+http://ad.doubleclick.net/ad/cbsmw.button.com/SIDEBAR_BUTTONS;sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414
+http,,,ad.doubleclick.net,-1,/ad/cbsmw.button.com/,SIDEBAR_BUTTONS,,sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414,,,http://ad.doubleclick.net/ad/cbsmw.button.com/SIDEBAR_BUTTONS;sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414
+http,,,ad.doubleclick.net,-1,/ad/cbsmw.button.com/,SIDEBAR_BUTTONS,,sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414,,,http://ad.doubleclick.net/ad/cbsmw.button.com/SIDEBAR_BUTTONS;sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414
+
+fxqn:/us/va/reston/cnri/ietf/24/asdf%*.fred
+fxqn,,,,-1,/us/va/reston/cnri/ietf/24/,asdf%*,fred,,,,fxqn:///us/va/reston/cnri/ietf/24/asdf%*.fred
+Can not create URL
+
+news:3B5C133C.2080505@foobar.net
+news,3B5C133C.2080505,,foobar.net,-1,/,,,,,,news://3B5C133C.2080505@foobar.net/
+news,3B5C133C.2080505,,foobar.net,-1,/,,,,,,news://3B5C133C.2080505@foobar.net/
+
+http://host/path/%2E%2E/file%2Ehtml
+http,,,host,-1,/,file%2Ehtml,,,,,http://host/file%2Ehtml
+http,,,host,-1,/,file%2Ehtml,,,,,http://host/file%2Ehtml
+
diff --git a/netwerk/test/urlparse_mac.dat b/netwerk/test/urlparse_mac.dat
new file mode 100644
index 000000000..f9209d0a5
--- /dev/null
+++ b/netwerk/test/urlparse_mac.dat
@@ -0,0 +1,14 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+# This version is specifically for the Mac platform.
+#
+
+# testing different versions of file urls
diff --git a/netwerk/test/urlparse_unx.dat b/netwerk/test/urlparse_unx.dat
new file mode 100644
index 000000000..909d1a9a0
--- /dev/null
+++ b/netwerk/test/urlparse_unx.dat
@@ -0,0 +1,34 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+# This version is specifically *not* for PC platforms like Windows or OS/2.
+# It's testcases for the file protocol target a unix-like filesystem.
+
+# testing different versions of file urls
+file:home
+file,,,home,-1,/,,,,,,file://home/
+file,,,,-1,/,home,,,,,file:///home
+
+file:/home
+file,,,,-1,/,home,,,,,file:///home
+file,,,,-1,/,home,,,,,file:///home
+
+file://home
+file,,,home,-1,/,,,,,,file://home/
+file,,,home,-1,/,,,,,,file://home/
+
+file:///home
+file,,,,-1,/,home,,,,,file:///home
+file,,,,-1,/,home,,,,,file:///home
+
+# testing UNC filepaths
+file:////server/path
+file,,,,-1,//server/,path,,,,,file:////server/path
+file,,,,-1,//server/,path,,,,,file:////server/path
diff --git a/netwerk/test/urlparse_win.dat b/netwerk/test/urlparse_win.dat
new file mode 100644
index 000000000..cf418fefb
--- /dev/null
+++ b/netwerk/test/urlparse_win.dat
@@ -0,0 +1,60 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+# This version is specifically for PC platforms like Windows or OS/2.
+# It has testcases for the file protocol targeting typical
+# drive:/path filesystems
+#
+
+# testing different versions of file urls
+file:c:
+file,,,,-1,/,c:,,,,,file:///c%3A
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file:c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file:/c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file://c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+# testing UNC filepaths
+file:server/path
+file,,,server,-1,/,path,,,,,file://server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file:/server/path
+file,,,,-1,/server/,path,,,,,file:///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file://server/path
+file,,,server,-1,/,path,,,,,file://server/path
+file,,,server,-1,///,path,,,,,file://server///path
+
+file:///server/path
+file,,,,-1,/server/,path,,,,,file:///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file:////server/path
+file,,,,-1,//server/,path,,,,,file:////server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file://///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
diff --git a/netwerk/test/urltest.cpp b/netwerk/test/urltest.cpp
new file mode 100644
index 000000000..df6f3f301
--- /dev/null
+++ b/netwerk/test/urltest.cpp
@@ -0,0 +1,461 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+/*
+ A test file to check default URL parsing.
+ -Gagan Saksena 03/25/99
+*/
+
+#include <stdio.h>
+
+#include "TestCommon.h"
+#include "plstr.h"
+#include "nsIServiceManager.h"
+#include "nsIIOService.h"
+#include "nsIURL.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsNetCID.h"
+#include "nsIComponentRegistrar.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXPCOM.h"
+#include "prprf.h"
+#include "mozilla/Sprintf.h"
+
+// Define CIDs...
+static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
+static NS_DEFINE_CID(kStdURLCID, NS_STANDARDURL_CID);
+
+char* gFileIO = 0;
+
+enum {
+ URL_FACTORY_DEFAULT,
+ URL_FACTORY_STDURL
+};
+
+nsresult writeoutto(const char* i_pURL, char** o_Result, int32_t urlFactory = URL_FACTORY_DEFAULT)
+{
+ if (!o_Result || !i_pURL)
+ return NS_ERROR_FAILURE;
+ *o_Result = 0;
+ nsCOMPtr<nsIURI> pURL;
+ nsresult result = NS_OK;
+
+ switch (urlFactory) {
+ case URL_FACTORY_STDURL: {
+ nsIURI* url;
+ result = CallCreateInstance(kStdURLCID, &url);
+ if (NS_FAILED(result))
+ {
+ printf("CreateInstance failed\n");
+ return NS_ERROR_FAILURE;
+ }
+ pURL = url;
+ result = pURL->SetSpec(nsDependentCString(i_pURL));
+ if (NS_FAILED(result))
+ {
+ printf("SetSpec failed\n");
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ case URL_FACTORY_DEFAULT: {
+ nsCOMPtr<nsIIOService> pService =
+ do_GetService(kIOServiceCID, &result);
+ if (NS_FAILED(result))
+ {
+ printf("Service failed!\n");
+ return NS_ERROR_FAILURE;
+ }
+ result = pService->NewURI(nsDependentCString(i_pURL), nullptr, nullptr, getter_AddRefs(pURL));
+ }
+ }
+
+ nsCString output;
+ if (NS_SUCCEEDED(result))
+ {
+ nsCOMPtr<nsIURL> tURL = do_QueryInterface(pURL);
+ nsAutoCString temp;
+ int32_t port;
+ nsresult rv;
+
+#define RESULT() NS_SUCCEEDED(rv) ? temp.get() : ""
+
+ rv = tURL->GetScheme(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetUsername(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetPassword(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetHost(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetPort(&port);
+ char portbuffer[40];
+ SprintfLiteral(portbuffer, "%d", port);
+ output.Append(portbuffer);
+ output += ',';
+ rv = tURL->GetDirectory(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetFileBaseName(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetFileExtension(temp);
+ output += RESULT();
+ output += ',';
+ // removed with https://bugzilla.mozilla.org/show_bug.cgi?id=665706
+ // rv = tURL->GetParam(temp);
+ // output += RESULT();
+ output += ',';
+ rv = tURL->GetQuery(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetRef(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetSpec(temp);
+ output += RESULT();
+ *o_Result = ToNewCString(output);
+ } else {
+ output = "Can not create URL";
+ *o_Result = ToNewCString(output);
+ }
+ return NS_OK;
+}
+
+nsresult writeout(const char* i_pURL, int32_t urlFactory = URL_FACTORY_DEFAULT)
+{
+ if (!i_pURL) return NS_ERROR_FAILURE;
+ nsCString temp;
+ nsresult rv = writeoutto(i_pURL, getter_Copies(temp), urlFactory);
+ printf("%s\n%s\n", i_pURL, temp.get());
+ return rv;
+}
+
+/* construct a url and print out its elements separated by commas and
+ the whole spec */
+nsresult testURL(const char* i_pURL, int32_t urlFactory = URL_FACTORY_DEFAULT)
+{
+
+ if (i_pURL)
+ return writeout(i_pURL, urlFactory);
+
+ if (!gFileIO)
+ return NS_ERROR_FAILURE;
+
+ FILE *testfile = fopen(gFileIO, "rt");
+ if (!testfile)
+ {
+ fprintf(stderr, "Cannot open testfile: %s\n", gFileIO);
+ return NS_ERROR_FAILURE;
+ }
+
+ char temp[512];
+ int count=0;
+ int failed=0;
+ nsCString prevResult;
+ nsCString tempurl;
+
+ while (fgets(temp,512,testfile))
+ {
+ if (*temp == '#' || !*temp)
+ continue;
+
+ if (0 == count%3)
+ {
+ printf("Testing: %s\n", temp);
+ writeoutto(temp, getter_Copies(prevResult), urlFactory);
+ }
+ else if (1 == count%3) {
+ tempurl.Assign(temp);
+ } else {
+ if (prevResult.IsEmpty())
+ printf("no results to compare to!\n");
+ else
+ {
+ int32_t res;
+ printf("Result: %s\n", prevResult.get());
+ if (urlFactory != URL_FACTORY_DEFAULT) {
+ printf("Expected: %s\n", tempurl.get());
+ res = PL_strcmp(tempurl.get(), prevResult.get());
+ } else {
+ printf("Expected: %s\n", temp);
+ res = PL_strcmp(temp, prevResult.get());
+ }
+
+ if (res == 0)
+ printf("\tPASSED\n\n");
+ else
+ {
+ printf("\tFAILED\n\n");
+ failed++;
+ }
+ }
+ }
+ count++;
+ }
+ if (failed>0) {
+ printf("%d tests FAILED out of %d\n", failed, count/3);
+ return NS_ERROR_FAILURE;
+ } else {
+ printf("All %d tests PASSED.\n", count/3);
+ return NS_OK;
+ }
+}
+
+nsresult makeAbsTest(const char* i_BaseURI, const char* relativePortion,
+ const char* expectedResult)
+{
+ if (!i_BaseURI)
+ return NS_ERROR_FAILURE;
+
+ // build up the base URL
+ nsresult status;
+ nsCOMPtr<nsIURI> baseURL = do_CreateInstance(kStdURLCID, &status);
+ if (NS_FAILED(status))
+ {
+ printf("CreateInstance failed\n");
+ return status;
+ }
+ status = baseURL->SetSpec(nsDependentCString(i_BaseURI));
+ if (NS_FAILED(status)) return status;
+
+
+ // get the new spec
+ nsAutoCString newURL;
+ status = baseURL->Resolve(nsDependentCString(relativePortion), newURL);
+ if (NS_FAILED(status)) return status;
+
+ printf("Analyzing %s\n", baseURL->GetSpecOrDefault().get());
+ printf("With %s\n", relativePortion);
+
+ printf("Got %s\n", newURL.get());
+ if (expectedResult) {
+ printf("Expect %s\n", expectedResult);
+ int res = PL_strcmp(newURL.get(), expectedResult);
+ if (res == 0) {
+ printf("\tPASSED\n\n");
+ return NS_OK;
+ } else {
+ printf("\tFAILED\n\n");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult doMakeAbsTest(const char* i_URL = 0, const char* i_relativePortion=0)
+{
+ if (i_URL && i_relativePortion)
+ {
+ return makeAbsTest(i_URL, i_relativePortion, nullptr);
+ }
+
+ // Run standard tests. These tests are based on the ones described in
+ // rfc2396 with the exception of the handling of ?y which is wrong as
+ // notified by on of the RFC authors.
+
+ /* Section C.1. Normal Examples
+
+ g:h = <URL:g:h>
+ g = <URL:http://a/b/c/g>
+ ./g = <URL:http://a/b/c/g>
+ g/ = <URL:http://a/b/c/g/>
+ /g = <URL:http://a/g>
+ //g = <URL:http://g>
+ ?y = <URL:http://a/b/c/d;p?y>
+ g?y = <URL:http://a/b/c/g?y>
+ g?y/./x = <URL:http://a/b/c/g?y/./x>
+ #s = <URL:http://a/b/c/d;p?q#s>
+ g#s = <URL:http://a/b/c/g#s>
+ g#s/./x = <URL:http://a/b/c/g#s/./x>
+ g?y#s = <URL:http://a/b/c/g?y#s>
+ ;x = <URL:http://a/b/c/;x>
+ g;x = <URL:http://a/b/c/g;x>
+ g;x?y#s = <URL:http://a/b/c/g;x?y#s>
+ . = <URL:http://a/b/c/>
+ ./ = <URL:http://a/b/c/>
+ .. = <URL:http://a/b/>
+ ../ = <URL:http://a/b/>
+ ../g = <URL:http://a/b/g>
+ ../.. = <URL:http://a/>
+ ../../ = <URL:http://a/>
+ ../../g = <URL:http://a/g>
+ */
+
+ struct test {
+ const char* baseURL;
+ const char* relativeURL;
+ const char* expectedResult;
+ };
+
+ test tests[] = {
+ // Tests from rfc2396, section C.1 with the exception of the
+ // handling of ?y
+ { "http://a/b/c/d;p?q#f", "g:h", "g:h" },
+ { "http://a/b/c/d;p?q#f", "g", "http://a/b/c/g" },
+ { "http://a/b/c/d;p?q#f", "./g", "http://a/b/c/g" },
+ { "http://a/b/c/d;p?q#f", "g/", "http://a/b/c/g/" },
+ { "http://a/b/c/d;p?q#f", "/g", "http://a/g" },
+ { "http://a/b/c/d;p?q#f", "//g", "http://g" },
+ { "http://a/b/c/d;p?q#f", "?y", "http://a/b/c/d;p?y" },
+ { "http://a/b/c/d;p?q#f", "g?y", "http://a/b/c/g?y" },
+ { "http://a/b/c/d;p?q#f", "g?y/./x", "http://a/b/c/g?y/./x" },
+ { "http://a/b/c/d;p?q#f", "#s", "http://a/b/c/d;p?q#s" },
+ { "http://a/b/c/d;p?q#f", "g#s", "http://a/b/c/g#s" },
+ { "http://a/b/c/d;p?q#f", "g#s/./x", "http://a/b/c/g#s/./x" },
+ { "http://a/b/c/d;p?q#f", "g?y#s", "http://a/b/c/g?y#s" },
+ { "http://a/b/c/d;p?q#f", ";x", "http://a/b/c/;x" },
+ { "http://a/b/c/d;p?q#f", "g;x", "http://a/b/c/g;x" },
+ { "http://a/b/c/d;p?q#f", "g;x?y#s", "http://a/b/c/g;x?y#s" },
+ { "http://a/b/c/d;p?q#f", ".", "http://a/b/c/" },
+ { "http://a/b/c/d;p?q#f", "./", "http://a/b/c/" },
+ { "http://a/b/c/d;p?q#f", "..", "http://a/b/" },
+ { "http://a/b/c/d;p?q#f", "../", "http://a/b/" },
+ { "http://a/b/c/d;p?q#f", "../g", "http://a/b/g" },
+ { "http://a/b/c/d;p?q#f", "../..", "http://a/" },
+ { "http://a/b/c/d;p?q#f", "../../", "http://a/" },
+ { "http://a/b/c/d;p?q#f", "../../g", "http://a/g" },
+
+ // Our additional tests...
+ { "http://a/b/c/d;p?q#f", "#my::anchor", "http://a/b/c/d;p?q#my::anchor" },
+ { "http://a/b/c/d;p?q#f", "get?baseRef=viewcert.jpg", "http://a/b/c/get?baseRef=viewcert.jpg" },
+
+ // Make sure relative query's work right even if the query
+ // string contains absolute urls or other junk.
+ { "http://a/b/c/d;p?q#f", "?http://foo", "http://a/b/c/d;p?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g?http://foo", "http://a/b/c/g?http://foo" },
+ {"http://a/b/c/d;p?q#f", "g/h?http://foo", "http://a/b/c/g/h?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo","http://a/b/c/g/H?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo?baz", "http://a/b/c/g/H?http://foo?baz" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo;baz", "http://a/b/c/g/H?http://foo;baz" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo#bar", "http://a/b/c/g/H?http://foo#bar" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H;baz?http://foo", "http://a/b/c/g/H;baz?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H;baz?http://foo#bar", "http://a/b/c/g/H;baz?http://foo#bar" },
+ { "http://a/b/c/d;p?q#f", R"(g/h/../H;baz?C:\temp)", R"(http://a/b/c/g/H;baz?C:\temp)" },
+ { "http://a/b/c/d;p?q#f", "", "http://a/b/c/d;p?q" },
+ { "http://a/b/c/d;p?q#f", "#", "http://a/b/c/d;p?q#" },
+ { "http://a/b/c;p/d;p?q#f", "../g;p" , "http://a/b/g;p" },
+
+ };
+
+ const int numTests = sizeof(tests) / sizeof(tests[0]);
+ int failed = 0;
+ nsresult rv;
+ for (auto & test : tests)
+ {
+ rv = makeAbsTest(test.baseURL, test.relativeURL,
+ test.expectedResult);
+ if (NS_FAILED(rv))
+ failed++;
+ }
+ if (failed>0) {
+ printf("%d tests FAILED out of %d\n", failed, numTests);
+ return NS_ERROR_FAILURE;
+ } else {
+ printf("All %d tests PASSED.\n", numTests);
+ return NS_OK;
+ }
+}
+
+void printusage(void)
+{
+ printf("urltest [-std] [-file <filename>] <URL> "
+ " [-abs <relative>]\n\n"
+ "\t-std : Generate results using nsStdURL.\n"
+ "\t-file : Read URLs from file.\n"
+ "\t-abs : Make an absolute URL from the base (<URL>) and the\n"
+ "\t\trelative path specified. If -abs is given without\n"
+ "\t\ta base URI standard RFC 2396 relative URL tests\n"
+ "\t\tare performed. Implies -std.\n"
+ "\t<URL> : The string representing the URL.\n");
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ if (argc < 2) {
+ printusage();
+ return 0;
+ }
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ // end of all messages from register components...
+ printf("------------------\n\n");
+
+ int32_t urlFactory = URL_FACTORY_DEFAULT;
+ bool bMakeAbs= false;
+ char* relativePath = 0;
+ char* url = 0;
+ for (int i=1; i<argc; i++) {
+ if (PL_strcasecmp(argv[i], "-std") == 0)
+ {
+ urlFactory = URL_FACTORY_STDURL;
+ if (i+1 >= argc)
+ {
+ printusage();
+ return 0;
+ }
+ }
+ else if (PL_strcasecmp(argv[i], "-abs") == 0)
+ {
+ if (!gFileIO)
+ {
+ relativePath = argv[i+1];
+ i++;
+ }
+ bMakeAbs = true;
+ }
+ else if (PL_strcasecmp(argv[i], "-file") == 0)
+ {
+ if (i+1 >= argc)
+ {
+ printusage();
+ return 0;
+ }
+ gFileIO = argv[i+1];
+ i++;
+ }
+ else
+ {
+ url = argv[i];
+ }
+ }
+ PRTime startTime = PR_Now();
+ if (bMakeAbs)
+ {
+ if (url && relativePath) {
+ doMakeAbsTest(url, relativePath);
+ } else {
+ doMakeAbsTest();
+ }
+ }
+ else
+ {
+ if (gFileIO) {
+ testURL(0, urlFactory);
+ } else {
+ testURL(url, urlFactory);
+ }
+ }
+ if (gFileIO)
+ {
+ PRTime endTime = PR_Now();
+ printf("Elapsed time: %d micros.\n", (int32_t)
+ (endTime - startTime));
+ }
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ return NS_FAILED(NS_ShutdownXPCOM(nullptr)) ? 1 : 0;
+}
diff --git a/netwerk/test/urltests.dat b/netwerk/test/urltests.dat
new file mode 100644
index 000000000..d1f42fc76
--- /dev/null
+++ b/netwerk/test/urltests.dat
@@ -0,0 +1,43 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output. Then run urltest on
+# this file and hopefully the expected output should match the
+# one from the program.
+# - Gagan Saksena 03/28/00
+
+http://username:password@hostname.com:80/pathname/./more/stuff/../path
+http,username:password,hostname.com,80,,/pathname/more/path
+
+username@host:8080/path
+,username,host,8080,,/path
+
+http://gagan/
+http,,gagan,-1,,/
+
+scheme:host/netlib
+scheme,,host,-1,,/netlib
+
+mailbox:///foo
+mailbox,,,-1,,/foo
+
+scheme:user@hostname.edu:80/pathname
+scheme,user,hostname.edu,80,,/pathname
+
+http://username:password@hostname:80/pathname
+http,username:password,hostname,80,,/pathname
+
+resource:/pathname
+resource,,,-1,,/pathname
+
+ftp://uname%here.com:pwd@there.com/aPath/a.html
+ftp,uname%here.com:pwd,there.com,-1,,/aPath/a.html
+
+http://www.inf.bme.hu?foo=bar
+http,,www.inf.bme.hu,-1,foo=bar,/?foo=bar
+
+http://test.com/aPath/a.html#/1/2
+http,,test.com,-1,,/aPath/a.html#/1/2
+
+http://user:pass@ipaddres:2/get?foo/something
+http,user:pass,ipaddres,2,foo/something,/get?foo/something