diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /netwerk/test/gtest | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
32 files changed, 21546 insertions, 0 deletions
diff --git a/netwerk/test/gtest/TestBase64Stream.cpp b/netwerk/test/gtest/TestBase64Stream.cpp new file mode 100644 index 0000000000..47ef9e7bc6 --- /dev/null +++ b/netwerk/test/gtest/TestBase64Stream.cpp @@ -0,0 +1,123 @@ +/* -*- 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 "gtest/gtest.h" +#include "mozilla/Base64.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsStringStream.h" + +namespace mozilla { +namespace net { + +// An input stream whose ReadSegments method calls aWriter with writes of size +// aStep from the provided aInput in order to test edge-cases related to small +// buffers. +class TestStream final : public nsIInputStream { + public: + NS_DECL_ISUPPORTS; + + TestStream(const nsACString& aInput, uint32_t aStep) + : mInput(aInput), mStep(aStep) {} + + NS_IMETHOD Close() override { MOZ_CRASH("This should not be called"); } + + NS_IMETHOD Available(uint64_t* aLength) override { + *aLength = mInput.Length() - mPos; + return NS_OK; + } + + NS_IMETHOD StreamStatus() override { return NS_OK; } + + NS_IMETHOD Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) override { + MOZ_CRASH("This should not be called"); + } + + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) override { + *aResult = 0; + + if (mPos == mInput.Length()) { + return NS_OK; + } + + while (aCount > 0) { + uint32_t amt = std::min(mStep, (uint32_t)(mInput.Length() - mPos)); + + uint32_t read = 0; + nsresult rv = + aWriter(this, aClosure, mInput.get() + mPos, *aResult, amt, &read); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aResult += read; + aCount -= read; + mPos += read; + } + + return NS_OK; + } + + NS_IMETHOD IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return NS_OK; + } + + private: + ~TestStream() = default; + + nsCString mInput; + const uint32_t mStep; + uint32_t mPos = 0; +}; + +NS_IMPL_ISUPPORTS(TestStream, nsIInputStream) + +// Test the base64 encoder with writer buffer sizes between 1 byte and the +// entire length of "Hello World!" in order to exercise various edge cases. +TEST(TestBase64Stream, Run) +{ + nsCString input; + input.AssignLiteral("Hello World!"); + + for (uint32_t step = 1; step <= input.Length(); ++step) { + RefPtr<TestStream> ts = new TestStream(input, step); + + nsAutoString encodedData; + nsresult rv = Base64EncodeInputStream(ts, encodedData, input.Length()); + ASSERT_NS_SUCCEEDED(rv); + + EXPECT_TRUE(encodedData.EqualsLiteral("SGVsbG8gV29ybGQh")); + } +} + +TEST(TestBase64Stream, VaryingCount) +{ + nsCString input; + input.AssignLiteral("Hello World!"); + + std::pair<size_t, nsCString> tests[] = { + {0, "SGVsbG8gV29ybGQh"_ns}, {1, "SA=="_ns}, + {5, "SGVsbG8="_ns}, {11, "SGVsbG8gV29ybGQ="_ns}, + {12, "SGVsbG8gV29ybGQh"_ns}, {13, "SGVsbG8gV29ybGQh"_ns}, + }; + + for (auto& [count, expected] : tests) { + nsCOMPtr<nsIInputStream> is; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(is), input); + ASSERT_NS_SUCCEEDED(rv); + + nsAutoCString encodedData; + rv = Base64EncodeInputStream(is, encodedData, count); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_EQ(encodedData, expected) << "count: " << count; + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/gtest/TestBind.cpp b/netwerk/test/gtest/TestBind.cpp new file mode 100644 index 0000000000..371a09fdab --- /dev/null +++ b/netwerk/test/gtest/TestBind.cpp @@ -0,0 +1,187 @@ +/* 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 "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsISocketTransportService.h" +#include "nsISocketTransport.h" +#include "nsIServerSocket.h" +#include "nsIAsyncInputStream.h" +#include "mozilla/net/DNS.h" +#include "prerror.h" +#include "../../base/nsSocketTransportService2.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +using namespace mozilla::net; +using namespace mozilla; + +class ServerListener : public nsIServerSocketListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISERVERSOCKETLISTENER + + explicit ServerListener(WaitForCondition* waiter); + + // Port that is got from server side will be store here. + uint32_t mClientPort; + bool mFailed; + RefPtr<WaitForCondition> mWaiter; + + private: + virtual ~ServerListener(); +}; + +NS_IMPL_ISUPPORTS(ServerListener, nsIServerSocketListener) + +ServerListener::ServerListener(WaitForCondition* waiter) + : mClientPort(-1), mFailed(false), mWaiter(waiter) {} + +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; + mWaiter->Notify(); + return NS_OK; + } + mClientPort = PR_ntohs(peerAddr.inet.port); + mWaiter->Notify(); + 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 + + explicit ClientInputCallback(WaitForCondition* waiter); + + bool mFailed; + RefPtr<WaitForCondition> mWaiter; + + private: + virtual ~ClientInputCallback(); +}; + +NS_IMPL_ISUPPORTS(ClientInputCallback, nsIInputStreamCallback) + +ClientInputCallback::ClientInputCallback(WaitForCondition* waiter) + : mFailed(false), mWaiter(waiter) {} + +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; + } + mWaiter->Notify(); + return NS_OK; +} + +TEST(TestBind, MainTest) +{ + // + // Server side. + // + nsCOMPtr<nsIServerSocket> server = + do_CreateInstance("@mozilla.org/network/server-socket;1"); + ASSERT_TRUE(server); + + nsresult rv = server->Init(-1, true, -1); + ASSERT_NS_SUCCEEDED(rv); + + int32_t serverPort; + rv = server->GetPort(&serverPort); + ASSERT_NS_SUCCEEDED(rv); + + RefPtr<WaitForCondition> waiter = new WaitForCondition(); + + // Listening. + RefPtr<ServerListener> serverListener = new ServerListener(waiter); + rv = server->AsyncListen(serverListener); + ASSERT_NS_SUCCEEDED(rv); + + // + // Client side + // + uint32_t bindingPort = 20000; + nsCOMPtr<nsISocketTransportService> service = + do_GetService("@mozilla.org/network/socket-transport-service;1", &rv); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> inputStream; + RefPtr<ClientInputCallback> clientCallback; + + auto* sts = gSocketTransportService; + ASSERT_TRUE(sts); + for (int32_t tried = 0; tried < 100; tried++) { + NS_DispatchAndSpinEventLoopUntilComplete( + "test"_ns, sts, NS_NewRunnableFunction("test", [&]() { + nsCOMPtr<nsISocketTransport> client; + rv = service->CreateTransport(nsTArray<nsCString>(), "127.0.0.1"_ns, + serverPort, nullptr, nullptr, + getter_AddRefs(client)); + ASSERT_NS_SUCCEEDED(rv); + + // 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); + ASSERT_NS_SUCCEEDED(rv); + + // Open IO streams, to make client SocketTransport connect to + // server. + clientCallback = new ClientInputCallback(waiter); + rv = client->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, + getter_AddRefs(inputStream)); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(inputStream); + rv = asyncInputStream->AsyncWait(clientCallback, 0, 0, nullptr); + })); + + // Wait for server's response or callback of input stream. + waiter->Wait(1); + 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; + } + } + + ASSERT_FALSE(serverListener->mFailed); + ASSERT_EQ(serverListener->mClientPort, bindingPort); + + inputStream->Close(); + waiter->Wait(1); + ASSERT_TRUE(clientCallback->mFailed); + + server->Close(); +} diff --git a/netwerk/test/gtest/TestBufferedInputStream.cpp b/netwerk/test/gtest/TestBufferedInputStream.cpp new file mode 100644 index 0000000000..7230fe7e5b --- /dev/null +++ b/netwerk/test/gtest/TestBufferedInputStream.cpp @@ -0,0 +1,252 @@ +#include "gtest/gtest.h" + +#include "mozilla/SpinEventLoopUntil.h" +#include "nsBufferedStreams.h" +#include "nsIThread.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "Helpers.h" + +// Helper function for creating a testing::AsyncStringStream +already_AddRefed<nsBufferedInputStream> CreateStream(uint32_t aSize, + nsCString& aBuffer) { + aBuffer.SetLength(aSize); + for (uint32_t i = 0; i < aSize; ++i) { + aBuffer.BeginWriting()[i] = i % 10; + } + + nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(aBuffer); + + RefPtr<nsBufferedInputStream> bis = new nsBufferedInputStream(); + bis->Init(stream, aSize); + return bis.forget(); +} + +// Simple reading. +TEST(TestBufferedInputStream, SimpleRead) +{ + const size_t kBufSize = 10; + + nsCString buf; + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, bis->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + char buf2[kBufSize]; + uint32_t count; + ASSERT_EQ(NS_OK, bis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count))); +} + +// Simple segment reading. +TEST(TestBufferedInputStream, SimpleReadSegments) +{ + const size_t kBufSize = 10; + + nsCString buf; + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + char buf2[kBufSize]; + uint32_t count; + ASSERT_EQ(NS_OK, bis->ReadSegments(NS_CopySegmentToBuffer, buf2, sizeof(buf2), + &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count))); +} + +// AsyncWait - sync +TEST(TestBufferedInputStream, AsyncWait_sync) +{ + const size_t kBufSize = 10; + + nsCString buf; + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, nullptr)); + + // Immediatelly called + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - async +TEST(TestBufferedInputStream, AsyncWait_async) +{ + const size_t kBufSize = 10; + + nsCString buf; + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, thread)); + + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "TEST(TestBufferedInputStream, AsyncWait_async)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - sync - closureOnly +TEST(TestBufferedInputStream, AsyncWait_sync_closureOnly) +{ + const size_t kBufSize = 10; + + nsCString buf; + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, bis->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + nullptr)); + ASSERT_FALSE(cb->Called()); + + bis->CloseWithStatus(NS_ERROR_FAILURE); + + // Immediatelly called + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - async +TEST(TestBufferedInputStream, AsyncWait_async_closureOnly) +{ + const size_t kBufSize = 10; + + nsCString buf; + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, bis->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + thread)); + + ASSERT_FALSE(cb->Called()); + bis->CloseWithStatus(NS_ERROR_FAILURE); + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "TEST(TestBufferedInputStream, AsyncWait_async_closureOnly)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +TEST(TestBufferedInputStream, AsyncWait_after_close) +{ + const size_t kBufSize = 10; + + nsCString buf; + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + nsCOMPtr<nsIThread> eventTarget = do_GetCurrentThread(); + + auto cb = mozilla::MakeRefPtr<testing::InputStreamCallback>(); + ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, eventTarget)); + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "TEST(TestBufferedInputStream, AsyncWait_after_close) 1"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); + + ASSERT_EQ(NS_OK, bis->Close()); + + cb = mozilla::MakeRefPtr<testing::InputStreamCallback>(); + ASSERT_EQ(NS_OK, bis->AsyncWait(cb, 0, 0, eventTarget)); + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "TEST(TestBufferedInputStream, AsyncWait_after_close) 2"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +TEST(TestBufferedInputStream, AsyncLengthWait_after_close) +{ + nsCString buf{"The Quick Brown Fox Jumps over the Lazy Dog"}; + const size_t kBufSize = 44; + + RefPtr<nsBufferedInputStream> bis = CreateStream(kBufSize, buf); + + nsCOMPtr<nsIThread> eventTarget = do_GetCurrentThread(); + + auto cb = mozilla::MakeRefPtr<testing::LengthCallback>(); + ASSERT_EQ(NS_OK, bis->AsyncLengthWait(cb, eventTarget)); + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "TEST(TestBufferedInputStream, AsyncLengthWait_after_close) 1"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); + + uint64_t length; + ASSERT_EQ(NS_OK, bis->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + cb = mozilla::MakeRefPtr<testing::LengthCallback>(); + ASSERT_EQ(NS_OK, bis->AsyncLengthWait(cb, eventTarget)); + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "TEST(TestBufferedInputStream, AsyncLengthWait_after_close) 2"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +// This stream returns a few bytes on the first read, and error on the second. +class BrokenInputStream : public nsIInputStream { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + private: + virtual ~BrokenInputStream() = default; + bool mFirst = true; +}; + +NS_IMPL_ISUPPORTS(BrokenInputStream, nsIInputStream) + +NS_IMETHODIMP BrokenInputStream::Close(void) { return NS_OK; } + +NS_IMETHODIMP BrokenInputStream::Available(uint64_t* _retval) { + *_retval = 100; + return NS_OK; +} + +NS_IMETHODIMP BrokenInputStream::StreamStatus(void) { return NS_OK; } + +NS_IMETHODIMP BrokenInputStream::Read(char* aBuf, uint32_t aCount, + uint32_t* _retval) { + if (mFirst) { + aBuf[0] = 'h'; + aBuf[1] = 'e'; + aBuf[2] = 'l'; + aBuf[3] = 0; + *_retval = 4; + mFirst = false; + return NS_OK; + } + return NS_ERROR_CORRUPTED_CONTENT; +} + +NS_IMETHODIMP BrokenInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BrokenInputStream::IsNonBlocking(bool* _retval) { + *_retval = false; + return NS_OK; +} + +// Check that the error from BrokenInputStream::Read is propagated +// through NS_ReadInputStreamToString +TEST(TestBufferedInputStream, BrokenInputStreamToBuffer) +{ + nsAutoCString out; + RefPtr<BrokenInputStream> stream = new BrokenInputStream(); + + nsresult rv = NS_ReadInputStreamToString(stream, out, -1); + ASSERT_EQ(rv, NS_ERROR_CORRUPTED_CONTENT); +} diff --git a/netwerk/test/gtest/TestCommon.cpp b/netwerk/test/gtest/TestCommon.cpp new file mode 100644 index 0000000000..37c08fbed8 --- /dev/null +++ b/netwerk/test/gtest/TestCommon.cpp @@ -0,0 +1,7 @@ +/* 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" + +NS_IMPL_ISUPPORTS(WaitForCondition, nsIRunnable) diff --git a/netwerk/test/gtest/TestCommon.h b/netwerk/test/gtest/TestCommon.h new file mode 100644 index 0000000000..0d2fd74e5b --- /dev/null +++ b/netwerk/test/gtest/TestCommon.h @@ -0,0 +1,45 @@ +/* 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" +#include "mozilla/SpinEventLoopUntil.h" + +//----------------------------------------------------------------------------- + +class WaitForCondition final : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + void Wait(int pending) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mPending == 0); + + mPending = pending; + mozilla::SpinEventLoopUntil("TestCommon.h:WaitForCondition::Wait"_ns, + [&]() { return !mPending; }); + NS_ProcessPendingEvents(nullptr); + } + + void Notify() { NS_DispatchToMainThread(this); } + + private: + virtual ~WaitForCondition() = default; + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mPending); + + --mPending; + return NS_OK; + } + + uint32_t mPending = 0; +}; + +#endif diff --git a/netwerk/test/gtest/TestCookie.cpp b/netwerk/test/gtest/TestCookie.cpp new file mode 100644 index 0000000000..4812ee47f1 --- /dev/null +++ b/netwerk/test/gtest/TestCookie.cpp @@ -0,0 +1,1126 @@ +/* -*- 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 "gtest/gtest.h" +#include "nsContentUtils.h" +#include "nsICookieService.h" +#include "nsICookieManager.h" +#include "nsICookie.h" +#include <stdio.h> +#include "plstr.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsIPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsServiceManagerUtils.h" +#include "nsNetCID.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "mozilla/dom/Document.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "mozilla/net/CookieJarSettings.h" +#include "Cookie.h" +#include "nsIURI.h" + +using namespace mozilla; +using namespace mozilla::net; + +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 kPrefCookieQuotaPerHost[] = "network.cookie.quotaPerHost"; +static const char kCookiesMaxPerHost[] = "network.cookie.maxPerHost"; + +#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.ReplaceLiteral( + 0, strlen("test=expiry; expires=") + strlen(timeStringPreset) + 1, + "test=expiry; expires="); + cookieString.Append(timeStringPreset); +} + +void SetACookieInternal(nsICookieService* aCookieService, const char* aSpec, + const char* aCookieString, bool aAllowed) { + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), aSpec); + + // We create a dummy channel using the aSpec to simulate same-siteness + nsresult rv0; + nsCOMPtr<nsIScriptSecurityManager> ssm = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv0); + ASSERT_NS_SUCCEEDED(rv0); + nsCOMPtr<nsIPrincipal> specPrincipal; + nsCString tmpString(aSpec); + ssm->CreateContentPrincipalFromOrigin(tmpString, + getter_AddRefs(specPrincipal)); + + nsCOMPtr<nsIChannel> dummyChannel; + NS_NewChannel(getter_AddRefs(dummyChannel), uri, specPrincipal, + nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, + nsIContentPolicy::TYPE_OTHER); + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = + aAllowed + ? CookieJarSettings::Create(CookieJarSettings::eRegular, + /* shouldResistFingerprinting */ false) + : CookieJarSettings::GetBlockingAll( + /* shouldResistFingerprinting */ false); + MOZ_ASSERT(cookieJarSettings); + + nsCOMPtr<nsILoadInfo> loadInfo = dummyChannel->LoadInfo(); + loadInfo->SetCookieJarSettings(cookieJarSettings); + + nsresult rv = aCookieService->SetCookieStringFromHttp( + uri, nsDependentCString(aCookieString), dummyChannel); + EXPECT_NS_SUCCEEDED(rv); +} + +void SetACookieJarBlocked(nsICookieService* aCookieService, const char* aSpec, + const char* aCookieString) { + SetACookieInternal(aCookieService, aSpec, aCookieString, false); +} + +void SetACookie(nsICookieService* aCookieService, const char* aSpec, + const char* aCookieString) { + SetACookieInternal(aCookieService, aSpec, aCookieString, true); +} + +// The cookie string is returned via aCookie. +void GetACookie(nsICookieService* aCookieService, const char* aSpec, + nsACString& aCookie) { + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), aSpec); + + nsCOMPtr<nsIIOService> service = do_GetIOService(); + + nsCOMPtr<nsIChannel> channel; + Unused << service->NewChannelFromURI( + uri, nullptr, nsContentUtils::GetSystemPrincipal(), + nsContentUtils::GetSystemPrincipal(), 0, nsIContentPolicy::TYPE_DOCUMENT, + getter_AddRefs(channel)); + + Unused << aCookieService->GetCookieStringFromHttp(uri, channel, aCookie); +} + +// The cookie string is returned via aCookie. +void GetACookieNoHttp(nsICookieService* aCookieService, const char* aSpec, + nsACString& aCookie) { + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), aSpec); + + RefPtr<BasePrincipal> principal = + BasePrincipal::CreateContentPrincipal(uri, OriginAttributes()); + MOZ_ASSERT(principal); + + nsCOMPtr<mozilla::dom::Document> document; + nsresult rv = NS_NewDOMDocument(getter_AddRefs(document), + u""_ns, // aNamespaceURI + u""_ns, // aQualifiedName + nullptr, // aDoctype + uri, uri, principal, + false, // aLoadedAsData + nullptr, // aEventObject + DocumentFlavorHTML); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + Unused << aCookieService->GetCookieStringFromDocument(document, aCookie); +} + +// 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 strstr(aLhs, aRhs) != nullptr; + + case MUST_NOT_CONTAIN: + return strstr(aLhs, aRhs) == nullptr; + + default: + return false; // failure + } +} + +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 + // Set quotaPerHost to maxPerHost - 1, so there is only one cookie + // will be evicted everytime. + aPrefBranch->SetIntPref(kPrefCookieQuotaPerHost, 49); + // Set the base domain limit to 50 so we have a known value. + aPrefBranch->SetIntPref(kCookiesMaxPerHost, 50); + + // SameSite=None by default. We have other tests for lax-by-default. + // XXX: Bug 1617611 - Fix all the tests broken by "cookies SameSite=Lax by + // default" + Preferences::SetBool("network.cookie.sameSite.laxByDefault", false); + Preferences::SetBool("network.cookieJarSettings.unblocked_for_testing", true); + Preferences::SetBool("dom.securecontext.allowlist_onions", false); + Preferences::SetBool("network.cookie.sameSite.schemeful", false); +} + +TEST(TestCookie, TestCookieMain) +{ + nsresult rv0; + + nsCOMPtr<nsICookieService> cookieService = + do_GetService(kCookieServiceCID, &rv0); + ASSERT_NS_SUCCEEDED(rv0); + + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(kPrefServiceCID, &rv0); + ASSERT_NS_SUCCEEDED(rv0); + + InitPrefs(prefBranch); + + 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. + * + * NOTE: this testsuite is not yet comprehensive or complete, and is + * somewhat contrived - still under development, and needs improving! + */ + + // test some basic variations of the domain & path + SetACookie(cookieService, "http://www.basic.com", "test=basic"); + GetACookie(cookieService, "http://www.basic.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic")); + GetACookie(cookieService, "http://www.basic.com/testPath/testfile.txt", + cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic")); + GetACookie(cookieService, "http://www.basic.com./", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + GetACookie(cookieService, "http://www.basic.com.", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + GetACookie(cookieService, "http://www.basic.com./testPath/testfile.txt", + cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + GetACookie(cookieService, "http://www.basic2.com/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://www.basic.com", "test=basic; max-age=-1"); + GetACookie(cookieService, "http://www.basic.com/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // *** domain tests + + // test some variations of the domain & path, for different domains of + // a domain cookie + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=domain.com"); + GetACookie(cookieService, "http://domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain")); + GetACookie(cookieService, "http://domain.com.", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + GetACookie(cookieService, "http://www.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain")); + GetACookie(cookieService, "http://foo.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain")); + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=domain.com; max-age=-1"); + GetACookie(cookieService, "http://domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=.domain.com"); + GetACookie(cookieService, "http://domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain")); + GetACookie(cookieService, "http://www.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain")); + GetACookie(cookieService, "http://bah.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=domain")); + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=.domain.com; max-age=-1"); + GetACookie(cookieService, "http://domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=.foo.domain.com"); + GetACookie(cookieService, "http://foo.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=moose.com"); + GetACookie(cookieService, "http://foo.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=domain.com."); + GetACookie(cookieService, "http://foo.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=..domain.com"); + GetACookie(cookieService, "http://foo.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://www.domain.com", + "test=domain; domain=..domain.com."); + GetACookie(cookieService, "http://foo.domain.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://path.net/path/file", + R"(test=taco; path="/bogus")"); + GetACookie(cookieService, "http://path.net/path/file", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=taco")); + SetACookie(cookieService, "http://path.net/path/file", + "test=taco; max-age=-1"); + GetACookie(cookieService, "http://path.net/path/file", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // *** path tests + + // test some variations of the domain & path, for different paths of + // a path cookie + SetACookie(cookieService, "http://path.net/path/file", + "test=path; path=/path"); + GetACookie(cookieService, "http://path.net/path", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path")); + GetACookie(cookieService, "http://path.net/path/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path")); + GetACookie(cookieService, "http://path.net/path/hithere.foo", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path")); + GetACookie(cookieService, "http://path.net/path?hithere/foo", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path")); + GetACookie(cookieService, "http://path.net/path2", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + GetACookie(cookieService, "http://path.net/path2/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://path.net/path/file", + "test=path; path=/path; max-age=-1"); + GetACookie(cookieService, "http://path.net/path/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://path.net/path/file", + "test=path; path=/path/"); + GetACookie(cookieService, "http://path.net/path", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + GetACookie(cookieService, "http://path.net/path/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=path")); + SetACookie(cookieService, "http://path.net/path/file", + "test=path; path=/path/; max-age=-1"); + GetACookie(cookieService, "http://path.net/path/", cookie); + EXPECT_TRUE(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 + // CookieService::CheckPath()), so we test this functionality too + SetACookie(cookieService, "http://path.net/path/file", + "test=path; path=/foo/"); + GetACookie(cookieService, "http://path.net/path", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + GetACookie(cookieService, "http://path.net/foo", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://path.net/path/file", + "test=path; path=/foo/; max-age=-1"); + GetACookie(cookieService, "http://path.net/foo/", cookie); + EXPECT_TRUE(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/", + "test=path; " + "path=/" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "9012345678901234567890/"); + GetACookie( + cookieService, + "http://path.net/" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "9012345678901234567890", + cookie); + EXPECT_TRUE(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/" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "9012345678901234567890/", + "test=path"); + GetACookie( + cookieService, + "http://path.net/" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890123456789012" + "345678901234567890123456789012345678901234567890123456789012345678901234" + "567890123456789012345678901234567890123456789012345678901234567890123456" + "789012345678901234567890123456789012345678901234567890123456789012345678" + "9012345678901234567890/", + cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + // the following cookie includes a tab in the path + SetACookie(cookieService, "http://path.net/", "test=path; path=/foo\tbar/"); + GetACookie(cookieService, "http://path.net/foo\tbar/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + // the following cookie includes a tab in the name + SetACookie(cookieService, "http://path.net/", "test\ttabs=tab"); + GetACookie(cookieService, "http://path.net/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + // the following cookie includes a tab in the value - allowed + SetACookie(cookieService, "http://path.net/", "test=tab\ttest"); + GetACookie(cookieService, "http://path.net/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=tab\ttest")); + SetACookie(cookieService, "http://path.net/", "test=tab\ttest; max-age=-1"); + GetACookie(cookieService, "http://path.net/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // *** expiry & deletion tests + // XXX add server time str parsing tests here + + // test some variations of the expiry time, + // and test deletion of previously set cookies + SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=-1"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=0"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://expireme.org/", "test=expiry; expires=bad"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry")); + SetACookie(cookieService, "http://expireme.org/", + "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://expireme.org/", + R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT)"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://expireme.org/", + R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT")"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=60"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry")); + SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=-20"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=60"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry")); + SetACookie(cookieService, "http://expireme.org/", + "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://expireme.org/", "test=expiry; max-age=60"); + SetACookie(cookieService, "http://expireme.org/", + "newtest=expiry; max-age=60"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=expiry")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "newtest=expiry")); + SetACookie(cookieService, "http://expireme.org/", + "test=differentvalue; max-age=0"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "newtest=expiry")); + SetACookie(cookieService, "http://expireme.org/", + "newtest=evendifferentvalue; max-age=0"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://foo.expireme.org/", + "test=expiry; domain=.expireme.org; max-age=60"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=expiry")); + SetACookie(cookieService, "http://bar.expireme.org/", + "test=differentvalue; domain=.expireme.org; max-age=0"); + GetACookie(cookieService, "http://expireme.org/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + nsAutoCString ServerTime; + nsAutoCString CookieString; + + // *** multiple cookie tests + + // 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/", + "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"); + GetACookie(cookieService, "http://multiple.cookies/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=multiple")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=different")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=same")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=ciao")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=foo")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "newtest=reincarnated")); + SetACookie(cookieService, "http://multiple.cookies/", + "test=expiry; domain=.multiple.cookies; max-age=0"); + GetACookie(cookieService, "http://multiple.cookies/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=same")); + SetACookie(cookieService, "http://multiple.cookies/", + "\n test=different; max-age=0 \n"); + GetACookie(cookieService, "http://multiple.cookies/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=different")); + SetACookie(cookieService, "http://multiple.cookies/", + "newtest=dead; max-age=0"); + GetACookie(cookieService, "http://multiple.cookies/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // *** parser tests + + // test the cookie header parser, under various circumstances. + SetACookie(cookieService, "http://parser.test/", + "test=parser; domain=.parser.test; ;; ;=; ,,, ===,abc,=; " + "abracadabra! max-age=20;=;;"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=parser")); + SetACookie(cookieService, "http://parser.test/", + "test=parser; domain=.parser.test; max-age=0"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "http://parser.test/", + "test=\"fubar! = foo;bar\\\";\" parser; domain=.parser.test; " + "max-age=6\nfive; max-age=2.63,"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, R"(test="fubar! = foo)")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "five")); + SetACookie(cookieService, "http://parser.test/", + "test=kill; domain=.parser.test; max-age=0 \n five; max-age=0"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(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/", "six"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "six")); + SetACookie(cookieService, "http://parser.test/", "seven"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "seven")); + SetACookie(cookieService, "http://parser.test/", " =eight"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "eight")); + SetACookie(cookieService, "http://parser.test/", "test=six"); + GetACookie(cookieService, "http://parser.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=six")); + + // *** path ordering tests + + // 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/", + "test1=path; path=/one/two/three"); + SetACookie(cookieService, "http://multi.path.tests/", + "test2=path; path=/one \n test3=path; path=/one/two/three/four \n " + "test4=path; path=/one/two \n test5=path; path=/one/two/"); + SetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/", + "test6=path"); + SetACookie(cookieService, + "http://multi.path.tests/one/two/three/four/five/six/", + "test7=path; path="); + SetACookie(cookieService, "http://multi.path.tests/", "test8=path; path=/"); + GetACookie(cookieService, + "http://multi.path.tests/one/two/three/four/five/six/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, + "test7=path; test6=path; test3=path; test1=path; " + "test5=path; test4=path; test2=path; test8=path")); + + // *** Cookie prefix tests + + // prefixed cookies can't be set from insecure HTTP + SetACookie(cookieService, "http://prefixed.test/", "__Secure-test1=test"); + SetACookie(cookieService, "http://prefixed.test/", + "__Secure-test2=test; secure"); + SetACookie(cookieService, "http://prefixed.test/", "__Host-test1=test"); + SetACookie(cookieService, "http://prefixed.test/", + "__Host-test2=test; secure"); + GetACookie(cookieService, "http://prefixed.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // prefixed cookies won't be set without the secure flag + SetACookie(cookieService, "https://prefixed.test/", "__Secure-test=test"); + SetACookie(cookieService, "https://prefixed.test/", "__Host-test=test"); + GetACookie(cookieService, "https://prefixed.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // prefixed cookies can be set when done correctly + SetACookie(cookieService, "https://prefixed.test/", + "__Secure-test=test; secure"); + SetACookie(cookieService, "https://prefixed.test/", + "__Host-test=test; secure"); + GetACookie(cookieService, "https://prefixed.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "__Secure-test=test")); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "__Host-test=test")); + + // but when set must not be returned to the host insecurely + GetACookie(cookieService, "http://prefixed.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // Host-prefixed cookies cannot specify a domain + SetACookie(cookieService, "https://host.prefixed.test/", + "__Host-a=test; secure; domain=prefixed.test"); + SetACookie(cookieService, "https://host.prefixed.test/", + "__Host-b=test; secure; domain=.prefixed.test"); + SetACookie(cookieService, "https://host.prefixed.test/", + "__Host-c=test; secure; domain=host.prefixed.test"); + SetACookie(cookieService, "https://host.prefixed.test/", + "__Host-d=test; secure; domain=.host.prefixed.test"); + GetACookie(cookieService, "https://host.prefixed.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // Host-prefixed cookies can only have a path of "/" + SetACookie(cookieService, "https://host.prefixed.test/some/path", + "__Host-e=test; secure"); + SetACookie(cookieService, "https://host.prefixed.test/some/path", + "__Host-f=test; secure; path=/"); + SetACookie(cookieService, "https://host.prefixed.test/some/path", + "__Host-g=test; secure; path=/some"); + GetACookie(cookieService, "https://host.prefixed.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "__Host-f=test")); + + // *** leave-secure-alone tests + + // testing items 0 & 1 for 3.1 of spec Deprecate modification of ’secure’ + // cookies from non-secure origins + SetACookie(cookieService, "http://www.security.test/", + "test=non-security; secure"); + GetACookieNoHttp(cookieService, "https://www.security.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + SetACookie(cookieService, "https://www.security.test/path/", + "test=security; secure; path=/path/"); + GetACookieNoHttp(cookieService, "https://www.security.test/path/", cookie); + EXPECT_TRUE(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/", + "test=security2; secure; path=/path/"); + GetACookieNoHttp(cookieService, "https://www.security.test/path/", cookie); + EXPECT_TRUE(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/", + "test=non-security; path=/path/foo"); + GetACookieNoHttp(cookieService, "https://www.security.test/path/foo/", + cookie); + EXPECT_TRUE(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/", + "test=non-security; path=/bar"); + GetACookieNoHttp(cookieService, "http://www.security.test/bar/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=non-security")); + // Modify value and downgrade secure level. + SetACookie( + cookieService, "https://www.security.test/", + "test_modify_cookie=security-cookie; secure; domain=.security.test"); + GetACookieNoHttp(cookieService, "https://www.security.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, + "test_modify_cookie=security-cookie")); + SetACookie(cookieService, "https://www.security.test/", + "test_modify_cookie=non-security-cookie; domain=.security.test"); + GetACookieNoHttp(cookieService, "https://www.security.test/", cookie); + EXPECT_TRUE(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/", "test=security3"); + GetACookieNoHttp(cookieService, "http://www.security.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=security3")); + SetACookie(cookieService, "http://www.security.test/", + "test=non-security2; domain=security.test"); + GetACookieNoHttp(cookieService, "http://www.security.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=non-security2")); + + // *** nsICookieManager interface tests + nsCOMPtr<nsICookieManager> cookieMgr = + do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv0); + ASSERT_NS_SUCCEEDED(rv0); + + const nsCOMPtr<nsICookieManager>& cookieMgr2 = cookieMgr; + ASSERT_TRUE(cookieMgr2); + + mozilla::OriginAttributes attrs; + + // first, ensure a clean slate + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + // add some cookies + EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative("cookiemgr.test"_ns, // domain + "/foo"_ns, // path + "test1"_ns, // name + "yes"_ns, // value + false, // is secure + false, // is httponly + true, // is session + INT64_MAX, // expiry time + &attrs, // originAttributes + nsICookie::SAMESITE_NONE, + nsICookie::SCHEME_HTTPS))); + EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative( + "cookiemgr.test"_ns, // domain + "/foo"_ns, // path + "test2"_ns, // name + "yes"_ns, // value + false, // is secure + true, // is httponly + true, // is session + PR_Now() / PR_USEC_PER_SEC + 2, // expiry time + &attrs, // originAttributes + nsICookie::SAMESITE_NONE, nsICookie::SCHEME_HTTPS))); + EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative("new.domain"_ns, // domain + "/rabbit"_ns, // path + "test3"_ns, // name + "yes"_ns, // value + false, // is secure + false, // is httponly + true, // is session + INT64_MAX, // expiry time + &attrs, // originAttributes + nsICookie::SAMESITE_NONE, + nsICookie::SCHEME_HTTPS))); + // confirm using enumerator + nsTArray<RefPtr<nsICookie>> cookies; + EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies)); + nsCOMPtr<nsICookie> expiredCookie, newDomainCookie; + for (const auto& cookie : cookies) { + nsAutoCString name; + cookie->GetName(name); + if (name.EqualsLiteral("test2")) { + expiredCookie = cookie; + } else if (name.EqualsLiteral("test3")) { + newDomainCookie = cookie; + } + } + EXPECT_EQ(cookies.Length(), 3ul); + // check the httpOnly attribute of the second cookie is honored + GetACookie(cookieService, "http://cookiemgr.test/foo/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test2=yes")); + GetACookieNoHttp(cookieService, "http://cookiemgr.test/foo/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test2=yes")); + // check CountCookiesFromHost() + uint32_t hostCookies = 0; + EXPECT_TRUE(NS_SUCCEEDED( + cookieMgr2->CountCookiesFromHost("cookiemgr.test"_ns, &hostCookies))); + EXPECT_EQ(hostCookies, 2u); + // check CookieExistsNative() using the third cookie + bool found; + EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative( + "new.domain"_ns, "/rabbit"_ns, "test3"_ns, &attrs, &found))); + EXPECT_TRUE(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 + EXPECT_TRUE(NS_SUCCEEDED( + cookieMgr2->CountCookiesFromHost("cookiemgr.test"_ns, &hostCookies))); + EXPECT_EQ(hostCookies, 2u); + EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative( + "cookiemgr.test"_ns, "/foo"_ns, "test2"_ns, &attrs, &found))); + EXPECT_TRUE(found); + // double-check RemoveAll() using the enumerator + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + cookies.SetLength(0); + EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetCookies(cookies)) && + cookies.IsEmpty()); + + // *** eviction and creation ordering tests + + // 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 = "test"_ns; + name.AppendInt(i); + name += "=creation"_ns; + SetACookie(cookieService, "http://creation.ordering.tests/", name.get()); + + if (i >= 10) { + expected += name; + if (i < 59) expected += "; "_ns; + } + } + GetACookie(cookieService, "http://creation.ordering.tests/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, expected.get())); + + cookieMgr->RemoveAll(); + + for (int32_t i = 0; i < 60; ++i) { + name = "test"_ns; + name.AppendInt(i); + name += "=delete_non_security"_ns; + + // Create 50 cookies that include the secure flag. + if (i < 50) { + name += "; secure"_ns; + SetACookie(cookieService, "https://creation.ordering.tests/", name.get()); + } else { + // non-security cookies will be removed beside the latest cookie that be + // created. + SetACookie(cookieService, "http://creation.ordering.tests/", name.get()); + } + } + GetACookie(cookieService, "http://creation.ordering.tests/", cookie); + + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + // *** SameSite attribute - parsing and cookie storage tests + // Clear the cookies + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + + // None of these cookies will be set because using + // CookieJarSettings::GetBlockingAll(). + SetACookieJarBlocked(cookieService, "http://samesite.test", "unset=yes"); + SetACookieJarBlocked(cookieService, "http://samesite.test", + "unspecified=yes; samesite"); + SetACookieJarBlocked(cookieService, "http://samesite.test", + "empty=yes; samesite="); + SetACookieJarBlocked(cookieService, "http://samesite.test", + "bogus=yes; samesite=bogus"); + SetACookieJarBlocked(cookieService, "http://samesite.test", + "strict=yes; samesite=strict"); + SetACookieJarBlocked(cookieService, "http://samesite.test", + "lax=yes; samesite=lax"); + + cookies.SetLength(0); + EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies)); + + EXPECT_TRUE(cookies.IsEmpty()); + + // Set cookies with various incantations of the samesite attribute: + // No same site attribute present + SetACookie(cookieService, "http://samesite.test", "unset=yes"); + // samesite attribute present but with no value + SetACookie(cookieService, "http://samesite.test", + "unspecified=yes; samesite"); + // samesite attribute present but with an empty value + SetACookie(cookieService, "http://samesite.test", "empty=yes; samesite="); + // samesite attribute present but with an invalid value + SetACookie(cookieService, "http://samesite.test", + "bogus=yes; samesite=bogus"); + // samesite=strict + SetACookie(cookieService, "http://samesite.test", + "strict=yes; samesite=strict"); + // samesite=lax + SetACookie(cookieService, "http://samesite.test", "lax=yes; samesite=lax"); + + cookies.SetLength(0); + EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies)); + + // check the cookies for the required samesite value + for (const auto& cookie : cookies) { + nsAutoCString name; + cookie->GetName(name); + int32_t sameSiteAttr; + cookie->GetSameSite(&sameSiteAttr); + if (name.EqualsLiteral("unset")) { + EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE); + } else if (name.EqualsLiteral("unspecified")) { + EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE); + } else if (name.EqualsLiteral("empty")) { + EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE); + } else if (name.EqualsLiteral("bogus")) { + EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_NONE); + } else if (name.EqualsLiteral("strict")) { + EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_STRICT); + } else if (name.EqualsLiteral("lax")) { + EXPECT_TRUE(sameSiteAttr == nsICookie::SAMESITE_LAX); + } + } + + EXPECT_TRUE(cookies.Length() == 6); + + // *** SameSite attribute + // Clear the cookies + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + + // please note that the flag aForeign is always set to true using this test + // setup because no nsIChannel is passed to SetCookieString(). therefore we + // can only test that no cookies are sent for cross origin requests using + // same-site cookies. + SetACookie(cookieService, "http://www.samesite.com", + "test=sameSiteStrictVal; samesite=strict"); + GetACookie(cookieService, "http://www.notsamesite.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://www.samesite.test", + "test=sameSiteLaxVal; samesite=lax"); + GetACookie(cookieService, "http://www.notsamesite.com", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + static const char* secureURIs[] = { + "http://localhost", "http://localhost:1234", "http://127.0.0.1", + "http://127.0.0.2", "http://127.1.0.1", "http://[::1]", + // TODO bug 1220810 "http://xyzzy.localhost" + }; + + uint32_t numSecureURIs = sizeof(secureURIs) / sizeof(const char*); + for (uint32_t i = 0; i < numSecureURIs; ++i) { + SetACookie(cookieService, secureURIs[i], "test=basic; secure"); + GetACookie(cookieService, secureURIs[i], cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic")); + SetACookie(cookieService, secureURIs[i], "test=basic1"); + GetACookie(cookieService, secureURIs[i], cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=basic1")); + } + + // XXX the following are placeholders: add these tests please! + // *** "noncompliant cookie" tests + // *** IP address tests + // *** speed tests +} + +TEST(TestCookie, SameSiteLax) +{ + Preferences::SetBool("network.cookie.sameSite.laxByDefault", true); + + nsresult rv; + + nsCOMPtr<nsICookieService> cookieService = + do_GetService(kCookieServiceCID, &rv); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsICookieManager> cookieMgr = + do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv); + ASSERT_NS_SUCCEEDED(rv); + + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + + SetACookie(cookieService, "http://samesite.test", "unset=yes"); + + nsTArray<RefPtr<nsICookie>> cookies; + EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies)); + EXPECT_EQ(cookies.Length(), (uint64_t)1); + + Cookie* cookie = static_cast<Cookie*>(cookies[0].get()); + EXPECT_EQ(cookie->RawSameSite(), nsICookie::SAMESITE_NONE); + EXPECT_EQ(cookie->SameSite(), nsICookie::SAMESITE_LAX); + + Preferences::SetCString("network.cookie.sameSite.laxByDefault.disabledHosts", + "foo.com,samesite.test,bar.net"); + + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + + cookies.SetLength(0); + EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies)); + EXPECT_EQ(cookies.Length(), (uint64_t)0); + + SetACookie(cookieService, "http://samesite.test", "unset=yes"); + + cookies.SetLength(0); + EXPECT_NS_SUCCEEDED(cookieMgr->GetCookies(cookies)); + EXPECT_EQ(cookies.Length(), (uint64_t)1); + + cookie = static_cast<Cookie*>(cookies[0].get()); + EXPECT_EQ(cookie->RawSameSite(), nsICookie::SAMESITE_NONE); + EXPECT_EQ(cookie->SameSite(), nsICookie::SAMESITE_LAX); +} + +TEST(TestCookie, OnionSite) +{ + Preferences::SetBool("dom.securecontext.allowlist_onions", true); + Preferences::SetBool("network.cookie.sameSite.laxByDefault", false); + + nsresult rv; + nsCString cookie; + + nsCOMPtr<nsICookieService> cookieService = + do_GetService(kCookieServiceCID, &rv); + ASSERT_NS_SUCCEEDED(rv); + + // .onion secure cookie tests + SetACookie(cookieService, "http://123456789abcdef.onion/", + "test=onion-security; secure"); + GetACookieNoHttp(cookieService, "https://123456789abcdef.onion/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security")); + SetACookie(cookieService, "http://123456789abcdef.onion/", + "test=onion-security2; secure"); + GetACookieNoHttp(cookieService, "http://123456789abcdef.onion/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security2")); + SetACookie(cookieService, "https://123456789abcdef.onion/", + "test=onion-security3; secure"); + GetACookieNoHttp(cookieService, "http://123456789abcdef.onion/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security3")); + SetACookie(cookieService, "http://123456789abcdef.onion/", + "test=onion-security4"); + GetACookieNoHttp(cookieService, "http://123456789abcdef.onion/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "test=onion-security4")); +} + +TEST(TestCookie, HiddenPrefix) +{ + nsresult rv; + nsCString cookie; + + nsCOMPtr<nsICookieService> cookieService = + do_GetService(kCookieServiceCID, &rv); + ASSERT_NS_SUCCEEDED(rv); + + SetACookie(cookieService, "http://hiddenprefix.test/", "=__Host-test=a"); + GetACookie(cookieService, "http://hiddenprefix.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://hiddenprefix.test/", "=__Secure-test=a"); + GetACookie(cookieService, "http://hiddenprefix.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://hiddenprefix.test/", "=__Host-check"); + GetACookie(cookieService, "http://hiddenprefix.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://hiddenprefix.test/", "=__Secure-check"); + GetACookie(cookieService, "http://hiddenprefix.test/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); +} + +TEST(TestCookie, BlockUnicode) +{ + Preferences::SetBool("network.cookie.blockUnicode", true); + + nsresult rv; + nsCString cookie; + + nsCOMPtr<nsICookieService> cookieService = + do_GetService(kCookieServiceCID, &rv); + ASSERT_NS_SUCCEEDED(rv); + + SetACookie(cookieService, "http://unicode.com/", "name=🍪"); + GetACookie(cookieService, "http://unicode.com/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + SetACookie(cookieService, "http://unicode.com/", "🍪=value"); + GetACookie(cookieService, "http://unicode.com/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL)); + + Preferences::SetBool("network.cookie.blockUnicode", false); + + SetACookie(cookieService, "http://unicode.com/", "name=🍪"); + GetACookie(cookieService, "http://unicode.com/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "name=🍪")); + + nsCOMPtr<nsICookieManager> cookieMgr = + do_GetService(NS_COOKIEMANAGER_CONTRACTID); + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + + SetACookie(cookieService, "http://unicode.com/", "🍪=value"); + GetACookie(cookieService, "http://unicode.com/", cookie); + EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "🍪=value")); + + EXPECT_NS_SUCCEEDED(cookieMgr->RemoveAll()); + Preferences::ClearUser("network.cookie.blockUnicode"); +} diff --git a/netwerk/test/gtest/TestDNSPacket.cpp b/netwerk/test/gtest/TestDNSPacket.cpp new file mode 100644 index 0000000000..49530b80e0 --- /dev/null +++ b/netwerk/test/gtest/TestDNSPacket.cpp @@ -0,0 +1,69 @@ +#include "gtest/gtest.h" + +#include "mozilla/net/DNSPacket.h" +#include "mozilla/Preferences.h" + +using namespace mozilla; +using namespace mozilla::net; + +void AssertDnsPadding(uint32_t PaddingLength, unsigned int WithPadding, + unsigned int WithoutPadding, bool DisableEcn, + const nsCString& host) { + DNSPacket encoder; + nsCString buf; + + ASSERT_EQ(Preferences::SetUint("network.trr.padding.length", PaddingLength), + NS_OK); + + ASSERT_EQ(Preferences::SetBool("network.trr.padding", true), NS_OK); + ASSERT_EQ(encoder.EncodeRequest(buf, host, 1, DisableEcn), NS_OK); + ASSERT_EQ(buf.Length(), WithPadding); + + ASSERT_EQ(Preferences::SetBool("network.trr.padding", false), NS_OK); + ASSERT_EQ(encoder.EncodeRequest(buf, host, 1, DisableEcn), NS_OK); + ASSERT_EQ(buf.Length(), WithoutPadding); +} + +TEST(TestDNSPacket, PaddingLenEcn) +{ + AssertDnsPadding(16, 48, 41, true, "a.de"_ns); + AssertDnsPadding(16, 48, 42, true, "ab.de"_ns); + AssertDnsPadding(16, 48, 43, true, "abc.de"_ns); + AssertDnsPadding(16, 48, 44, true, "abcd.de"_ns); + AssertDnsPadding(16, 64, 45, true, "abcde.de"_ns); + AssertDnsPadding(16, 64, 46, true, "abcdef.de"_ns); + AssertDnsPadding(16, 64, 47, true, "abcdefg.de"_ns); + AssertDnsPadding(16, 64, 48, true, "abcdefgh.de"_ns); +} + +TEST(TestDNSPacket, PaddingLenDisableEcn) +{ + AssertDnsPadding(16, 48, 22, false, "a.de"_ns); + AssertDnsPadding(16, 48, 23, false, "ab.de"_ns); + AssertDnsPadding(16, 48, 24, false, "abc.de"_ns); + AssertDnsPadding(16, 48, 25, false, "abcd.de"_ns); + AssertDnsPadding(16, 48, 26, false, "abcde.de"_ns); + AssertDnsPadding(16, 48, 27, false, "abcdef.de"_ns); + AssertDnsPadding(16, 48, 32, false, "abcdefghijk.de"_ns); + AssertDnsPadding(16, 48, 33, false, "abcdefghijkl.de"_ns); + AssertDnsPadding(16, 64, 34, false, "abcdefghijklm.de"_ns); + AssertDnsPadding(16, 64, 35, false, "abcdefghijklmn.de"_ns); +} + +TEST(TestDNSPacket, PaddingLengths) +{ + AssertDnsPadding(0, 45, 41, true, "a.de"_ns); + AssertDnsPadding(1, 45, 41, true, "a.de"_ns); + AssertDnsPadding(2, 46, 41, true, "a.de"_ns); + AssertDnsPadding(3, 45, 41, true, "a.de"_ns); + AssertDnsPadding(4, 48, 41, true, "a.de"_ns); + AssertDnsPadding(16, 48, 41, true, "a.de"_ns); + AssertDnsPadding(32, 64, 41, true, "a.de"_ns); + AssertDnsPadding(42, 84, 41, true, "a.de"_ns); + AssertDnsPadding(52, 52, 41, true, "a.de"_ns); + AssertDnsPadding(80, 80, 41, true, "a.de"_ns); + AssertDnsPadding(128, 128, 41, true, "a.de"_ns); + AssertDnsPadding(256, 256, 41, true, "a.de"_ns); + AssertDnsPadding(1024, 1024, 41, true, "a.de"_ns); + AssertDnsPadding(1025, 1024, 41, true, "a.de"_ns); +} diff --git a/netwerk/test/gtest/TestHeaders.cpp b/netwerk/test/gtest/TestHeaders.cpp new file mode 100644 index 0000000000..0da6b06c70 --- /dev/null +++ b/netwerk/test/gtest/TestHeaders.cpp @@ -0,0 +1,29 @@ +#include "gtest/gtest.h" + +#include "nsHttpHeaderArray.h" + +TEST(TestHeaders, DuplicateHSTS) +{ + // When the Strict-Transport-Security header is sent multiple times, its + // effective value is the value of the first item. It is not merged as other + // headers are. + mozilla::net::nsHttpHeaderArray headers; + nsresult rv = headers.SetHeaderFromNet( + mozilla::net::nsHttp::Strict_Transport_Security, + "Strict_Transport_Security"_ns, "max-age=360"_ns, true); + ASSERT_EQ(rv, NS_OK); + + nsAutoCString h; + rv = headers.GetHeader(mozilla::net::nsHttp::Strict_Transport_Security, h); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(h.get(), "max-age=360"); + + rv = headers.SetHeaderFromNet(mozilla::net::nsHttp::Strict_Transport_Security, + "Strict_Transport_Security"_ns, + "max-age=720"_ns, true); + ASSERT_EQ(rv, NS_OK); + + rv = headers.GetHeader(mozilla::net::nsHttp::Strict_Transport_Security, h); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(h.get(), "max-age=360"); +} diff --git a/netwerk/test/gtest/TestHttpAtom.cpp b/netwerk/test/gtest/TestHttpAtom.cpp new file mode 100644 index 0000000000..917210a2c4 --- /dev/null +++ b/netwerk/test/gtest/TestHttpAtom.cpp @@ -0,0 +1,39 @@ +#include "gtest/gtest.h" + +#include "nsHttp.h" + +TEST(TestHttpAtom, AtomComparison) +{ + mozilla::net::nsHttpAtom atom(mozilla::net::nsHttp::Host); + mozilla::net::nsHttpAtom same_atom(mozilla::net::nsHttp::Host); + mozilla::net::nsHttpAtom different_atom(mozilla::net::nsHttp::Accept); + + ASSERT_EQ(atom, atom); + ASSERT_EQ(atom, mozilla::net::nsHttp::Host); + ASSERT_EQ(mozilla::net::nsHttp::Host, atom); + ASSERT_EQ(atom, same_atom); + ASSERT_EQ(atom.get(), same_atom.get()); + ASSERT_EQ(atom.get(), mozilla::net::nsHttp::Host.get()); + + ASSERT_NE(atom, different_atom); + ASSERT_NE(atom.get(), different_atom.get()); +} + +TEST(TestHttpAtom, LiteralComparison) +{ + ASSERT_EQ(mozilla::net::nsHttp::Host, mozilla::net::nsHttp::Host); + ASSERT_NE(mozilla::net::nsHttp::Host, mozilla::net::nsHttp::Accept); + + ASSERT_EQ(mozilla::net::nsHttp::Host.get(), mozilla::net::nsHttp::Host.get()); + ASSERT_NE(mozilla::net::nsHttp::Host.get(), + mozilla::net::nsHttp::Accept.get()); +} + +TEST(TestHttpAtom, Validity) +{ + mozilla::net::nsHttpAtom atom(mozilla::net::nsHttp::Host); + ASSERT_TRUE(atom); + + mozilla::net::nsHttpAtom atom_empty; + ASSERT_FALSE(atom_empty); +} diff --git a/netwerk/test/gtest/TestHttpAuthUtils.cpp b/netwerk/test/gtest/TestHttpAuthUtils.cpp new file mode 100644 index 0000000000..78fb40d4d0 --- /dev/null +++ b/netwerk/test/gtest/TestHttpAuthUtils.cpp @@ -0,0 +1,43 @@ +#include "gtest/gtest.h" + +#include "mozilla/net/HttpAuthUtils.h" +#include "mozilla/Preferences.h" +#include "nsNetUtil.h" + +namespace mozilla { +namespace net { + +#define TEST_PREF "network.http_test.auth_utils" + +TEST(TestHttpAuthUtils, Bug1351301) +{ + nsCOMPtr<nsIURI> url; + nsAutoCString spec; + + ASSERT_EQ(Preferences::SetCString(TEST_PREF, "bar.com"), NS_OK); + spec = "http://bar.com"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), true); + + spec = "http://foo.bar.com"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), true); + + spec = "http://foobar.com"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), false); + + ASSERT_EQ(Preferences::SetCString(TEST_PREF, ".bar.com"), NS_OK); + spec = "http://foo.bar.com"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), true); + + spec = "http://bar.com"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(auth::URIMatchesPrefPattern(url, TEST_PREF), false); + + ASSERT_EQ(Preferences::ClearUser(TEST_PREF), NS_OK); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/gtest/TestHttpChannel.cpp b/netwerk/test/gtest/TestHttpChannel.cpp new file mode 100644 index 0000000000..10ef744bb4 --- /dev/null +++ b/netwerk/test/gtest/TestHttpChannel.cpp @@ -0,0 +1,135 @@ +#include "gtest/gtest.h" + +#include "nsCOMPtr.h" +#include "mozilla/Maybe.h" +#include "mozilla/PreloadHashKey.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsIStreamListener.h" +#include "nsThreadUtils.h" +#include "nsStringStream.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsContentUtils.h" + +using namespace mozilla; + +class FakeListener : public nsIStreamListener, public nsIInterfaceRequestor { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIINTERFACEREQUESTOR + + enum { Never, OnStart, OnData, OnStop } mCancelIn = Never; + + nsresult mOnStartResult = NS_OK; + nsresult mOnDataResult = NS_OK; + nsresult mOnStopResult = NS_OK; + + bool mOnStart = false; + nsCString mOnData; + Maybe<nsresult> mOnStop; + + private: + virtual ~FakeListener() = default; +}; + +NS_IMPL_ISUPPORTS(FakeListener, nsIStreamListener, nsIRequestObserver, + nsIInterfaceRequestor) + +NS_IMETHODIMP +FakeListener::GetInterface(const nsIID& aIID, void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + return NS_NOINTERFACE; +} + +NS_IMETHODIMP FakeListener::OnStartRequest(nsIRequest* request) { + EXPECT_FALSE(mOnStart); + mOnStart = true; + + if (mCancelIn == OnStart) { + request->Cancel(NS_ERROR_ABORT); + } + + return mOnStartResult; +} + +NS_IMETHODIMP FakeListener::OnDataAvailable(nsIRequest* request, + nsIInputStream* input, + uint64_t offset, uint32_t count) { + nsAutoCString data; + data.SetLength(count); + + uint32_t read; + input->Read(data.BeginWriting(), count, &read); + mOnData += data; + + if (mCancelIn == OnData) { + request->Cancel(NS_ERROR_ABORT); + } + + return mOnDataResult; +} + +NS_IMETHODIMP FakeListener::OnStopRequest(nsIRequest* request, + nsresult status) { + EXPECT_FALSE(mOnStop); + mOnStop.emplace(status); + + if (mCancelIn == OnStop) { + request->Cancel(NS_ERROR_ABORT); + } + + return mOnStopResult; +} + +// Test that nsHttpChannel::AsyncOpen properly picks up changes to +// loadInfo.mPrivateBrowsingId that occur after the channel was created. +TEST(TestHttpChannel, PBAsyncOpen) +{ + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), "http://localhost/"_ns); + + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewChannel( + getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + ASSERT_EQ(rv, NS_OK); + + RefPtr<FakeListener> listener = new FakeListener(); + rv = channel->SetNotificationCallbacks(listener); + ASSERT_EQ(rv, NS_OK); + + nsCOMPtr<nsIPrivateBrowsingChannel> pbchannel = do_QueryInterface(channel); + ASSERT_TRUE(pbchannel); + + bool isPrivate = false; + rv = pbchannel->GetIsChannelPrivate(&isPrivate); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(isPrivate, false); + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + OriginAttributes attrs; + attrs.mPrivateBrowsingId = 1; + rv = loadInfo->SetOriginAttributes(attrs); + ASSERT_EQ(rv, NS_OK); + + rv = pbchannel->GetIsChannelPrivate(&isPrivate); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(isPrivate, false); + + rv = channel->AsyncOpen(listener); + ASSERT_EQ(rv, NS_OK); + + rv = pbchannel->GetIsChannelPrivate(&isPrivate); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(isPrivate, true); + + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "TEST(TestHttpChannel, PBAsyncOpen)"_ns, + [&]() -> bool { return listener->mOnStop.isSome(); })); +} diff --git a/netwerk/test/gtest/TestHttpResponseHead.cpp b/netwerk/test/gtest/TestHttpResponseHead.cpp new file mode 100644 index 0000000000..643489d496 --- /dev/null +++ b/netwerk/test/gtest/TestHttpResponseHead.cpp @@ -0,0 +1,183 @@ +#include "gtest/gtest.h" + +#include "chrome/common/ipc_message.h" +#include "mozilla/net/PHttpChannelParams.h" +#include "mozilla/Unused.h" +#include "nsHttp.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsURLHelper.h" + +namespace mozilla { +namespace net { + +void AssertRoundTrips(const nsHttpResponseHead& aHead) { + { + // Assert it round-trips via IPC. + UniquePtr<IPC::Message> msg(new IPC::Message(MSG_ROUTING_NONE, 0)); + IPC::MessageWriter writer(*msg); + IPC::ParamTraits<nsHttpResponseHead>::Write(&writer, aHead); + + nsHttpResponseHead deserializedHead; + IPC::MessageReader reader(*msg); + bool res = IPC::ParamTraits<mozilla::net::nsHttpResponseHead>::Read( + &reader, &deserializedHead); + ASSERT_TRUE(res); + ASSERT_EQ(aHead, deserializedHead); + } + + { + // Assert it round-trips through copy-ctor. + nsHttpResponseHead copied(aHead); + ASSERT_EQ(aHead, copied); + } + + { + // Assert it round-trips through operator= + nsHttpResponseHead copied; + copied = aHead; + // It is important that the below statement cannot be + // ASSERT_EQ(aHead, copied) to avoid potential lock-order inversion problem. + // See Bug 1829445 for more details + ASSERT_EQ(copied, aHead); + } +} + +TEST(TestHttpResponseHead, Bug1636930) +{ + nsHttpResponseHead head; + + Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns); + Unused << head.ParseHeaderLine("content-type: text/plain"_ns); + Unused << head.ParseHeaderLine("etag: Just testing"_ns); + Unused << head.ParseHeaderLine("cache-control: max-age=99999"_ns); + Unused << head.ParseHeaderLine("accept-ranges: bytes"_ns); + Unused << head.ParseHeaderLine("content-length: 1408"_ns); + Unused << head.ParseHeaderLine("connection: close"_ns); + Unused << head.ParseHeaderLine("server: httpd.js"_ns); + Unused << head.ParseHeaderLine("date: Tue, 12 May 2020 09:24:23 GMT"_ns); + + AssertRoundTrips(head); +} + +TEST(TestHttpResponseHead, bug1649807) +{ + nsHttpResponseHead head; + + Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns); + Unused << head.ParseHeaderLine("content-type: text/plain"_ns); + Unused << head.ParseHeaderLine("etag: Just testing"_ns); + Unused << head.ParseHeaderLine("cache-control: age=99999"_ns); + Unused << head.ParseHeaderLine("accept-ranges: bytes"_ns); + Unused << head.ParseHeaderLine("content-length: 1408"_ns); + Unused << head.ParseHeaderLine("connection: close"_ns); + Unused << head.ParseHeaderLine("server: httpd.js"_ns); + Unused << head.ParseHeaderLine("pragma: no-cache"_ns); + Unused << head.ParseHeaderLine("date: Tue, 12 May 2020 09:24:23 GMT"_ns); + + ASSERT_FALSE(head.NoCache()) + << "Cache-Control wins over Pragma: no-cache"; + AssertRoundTrips(head); +} + +TEST(TestHttpResponseHead, bug1660200) +{ + nsHttpResponseHead head; + + Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns); + Unused << head.ParseHeaderLine("content-type: text/plain"_ns); + Unused << head.ParseHeaderLine("etag: Just testing"_ns); + Unused << head.ParseHeaderLine("cache-control: no-cache"_ns); + Unused << head.ParseHeaderLine("accept-ranges: bytes"_ns); + Unused << head.ParseHeaderLine("content-length: 1408"_ns); + Unused << head.ParseHeaderLine("connection: close"_ns); + Unused << head.ParseHeaderLine("server: httpd.js"_ns); + Unused << head.ParseHeaderLine("date: Tue, 12 May 2020 09:24:23 GMT"_ns); + + AssertRoundTrips(head); +} + +TEST(TestHttpResponseHead, bug1687903) +{ + nsHttpResponseHead head; + + bool usingStrictParsing = false; + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + prefs->GetBoolPref("network.http.strict_response_status_line_parsing", + &usingStrictParsing); + } + + nsresult expectation = + usingStrictParsing ? NS_ERROR_PARSING_HTTP_STATUS_LINE : NS_OK; + + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 "_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 BLAH"_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 1000 BOO"_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 0200 BOO"_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 60200 200"_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 131072 HIOK"_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 -200 OK"_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 0x9 OK"_ns)); + ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 C8 OK"_ns)); +} + +TEST(TestHttpResponseHead, atoms) +{ + // Test that the resolving the content-type atom returns the initial static + ASSERT_EQ(nsHttp::Content_Type, nsHttp::ResolveAtom("content-type"_ns)); + // Check that they're case insensitive + ASSERT_EQ(nsHttp::ResolveAtom("Content-Type"_ns), + nsHttp::ResolveAtom("content-type"_ns)); + // This string literal should be the backing of the atom when resolved first + auto header1 = "CustomHeaderXXX1"_ns; + auto atom1 = nsHttp::ResolveAtom(header1); + auto header2 = "customheaderxxx1"_ns; + auto atom2 = nsHttp::ResolveAtom(header2); + ASSERT_EQ(atom1, atom2); + ASSERT_EQ(atom1.get(), atom2.get()); + // Check that we get the expected pointer back. + ASSERT_EQ(atom2.get(), header1.BeginReading()); +} + +TEST(ContentTypeParsing, CommentHandling1) +{ + bool dummy; + const nsAutoCString val("text/html;charset=gbk("); + nsCString contentType; + nsCString contentCharset; + + net_ParseContentType(val, contentType, contentCharset, &dummy); + + ASSERT_TRUE(contentType.EqualsLiteral("text/html")); + ASSERT_TRUE(contentCharset.EqualsLiteral("gbk(")); +} + +TEST(ContentTypeParsing, CommentHandling2) +{ + bool dummy; + const nsAutoCString val("text/html;x=(;charset=gbk"); + nsCString contentType; + nsCString contentCharset; + + net_ParseContentType(val, contentType, contentCharset, &dummy); + + ASSERT_TRUE(contentType.EqualsLiteral("text/html")); + ASSERT_TRUE(contentCharset.EqualsLiteral("gbk")); +} + +TEST(ContentTypeParsing, CommentHandling3) +{ + bool dummy; + const nsAutoCString val("text/html;test=test;(;charset=gbk"); + nsCString contentType; + nsCString contentCharset; + + net_ParseContentType(val, contentType, contentCharset, &dummy); + + ASSERT_TRUE(contentType.EqualsLiteral("text/html")); + ASSERT_TRUE(contentCharset.EqualsLiteral("gbk")); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/gtest/TestInputStreamTransport.cpp b/netwerk/test/gtest/TestInputStreamTransport.cpp new file mode 100644 index 0000000000..43df0e193a --- /dev/null +++ b/netwerk/test/gtest/TestInputStreamTransport.cpp @@ -0,0 +1,204 @@ +#include "gtest/gtest.h" + +#include "nsIStreamTransportService.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "Helpers.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsITransport.h" +#include "nsNetUtil.h" + +static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); + +void CreateStream(already_AddRefed<nsIInputStream> aSource, + nsIAsyncInputStream** aStream) { + nsCOMPtr<nsIInputStream> source = std::move(aSource); + + nsresult rv; + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(kStreamTransportServiceCID, &rv); + ASSERT_EQ(NS_OK, rv); + + nsCOMPtr<nsITransport> transport; + rv = sts->CreateInputTransport(source, true, getter_AddRefs(transport)); + ASSERT_EQ(NS_OK, rv); + + nsCOMPtr<nsIInputStream> wrapper; + rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper)); + ASSERT_EQ(NS_OK, rv); + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(wrapper); + MOZ_ASSERT(asyncStream); + + asyncStream.forget(aStream); +} + +class BlockingSyncStream final : public nsIInputStream { + nsCOMPtr<nsIInputStream> mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit BlockingSyncStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = false; + return NS_OK; + } + + private: + ~BlockingSyncStream() = default; +}; + +NS_IMPL_ISUPPORTS(BlockingSyncStream, nsIInputStream) + +// Testing a simple blocking stream. +TEST(TestInputStreamTransport, BlockingNotAsync) +{ + RefPtr<BlockingSyncStream> stream = new BlockingSyncStream("Hello world"_ns); + + nsCOMPtr<nsIAsyncInputStream> ais; + CreateStream(stream.forget(), getter_AddRefs(ais)); + ASSERT_TRUE(!!ais); + + nsAutoCString data; + nsresult rv = NS_ReadInputStreamToString(ais, data, -1); + ASSERT_EQ(NS_OK, rv); + + ASSERT_TRUE(data.EqualsLiteral("Hello world")); +} + +class BlockingAsyncStream final : public nsIAsyncInputStream { + nsCOMPtr<nsIInputStream> mStream; + bool mPending; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit BlockingAsyncStream(const nsACString& aBuffer) : mPending(false) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { + mStream->Available(aLength); + + // 1 char at the time, just to test the asyncWait+Read loop a bit more. + if (*aLength > 0) { + *aLength = 1; + } + + return NS_OK; + } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + mPending = !mPending; + if (mPending) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + // 1 char at the time, just to test the asyncWait+Read loop a bit more. + aCount = 1; + + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + mPending = !mPending; + if (mPending) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + // 1 char at the time, just to test the asyncWait+Read loop a bit more. + aCount = 1; + + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = false; + return NS_OK; + } + + NS_IMETHOD + CloseWithStatus(nsresult aStatus) override { return Close(); } + + NS_IMETHOD + AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override { + if (!aCallback) { + return NS_OK; + } + + RefPtr<BlockingAsyncStream> self = this; + nsCOMPtr<nsIInputStreamCallback> callback = aCallback; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "gtest-asyncwait", + [self, callback]() { callback->OnInputStreamReady(self); }); + + if (aEventTarget) { + aEventTarget->Dispatch(r.forget()); + } else { + r->Run(); + } + + return NS_OK; + } + + private: + ~BlockingAsyncStream() = default; +}; + +NS_IMPL_ISUPPORTS(BlockingAsyncStream, nsIInputStream, nsIAsyncInputStream) + +// Testing an async blocking stream. +TEST(TestInputStreamTransport, BlockingAsync) +{ + RefPtr<BlockingAsyncStream> stream = + new BlockingAsyncStream("Hello world"_ns); + + nsCOMPtr<nsIAsyncInputStream> ais; + CreateStream(stream.forget(), getter_AddRefs(ais)); + ASSERT_TRUE(!!ais); + + nsAutoCString data; + nsresult rv = NS_ReadInputStreamToString(ais, data, -1); + ASSERT_EQ(NS_OK, rv); + + ASSERT_TRUE(data.EqualsLiteral("Hello world")); +} diff --git a/netwerk/test/gtest/TestIsValidIp.cpp b/netwerk/test/gtest/TestIsValidIp.cpp new file mode 100644 index 0000000000..dfee8c5c0f --- /dev/null +++ b/netwerk/test/gtest/TestIsValidIp.cpp @@ -0,0 +1,178 @@ +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH +#include "gtest/gtest.h" + +#include "nsURLHelper.h" + +TEST(TestIsValidIp, IPV4Localhost) +{ + constexpr auto ip = "127.0.0.1"_ns; + ASSERT_EQ(true, net_IsValidIPv4Addr(ip)); +} + +TEST(TestIsValidIp, IPV4Only0) +{ + constexpr auto ip = "0.0.0.0"_ns; + ASSERT_EQ(true, net_IsValidIPv4Addr(ip)); +} + +TEST(TestIsValidIp, IPV4Max) +{ + constexpr auto ip = "255.255.255.255"_ns; + ASSERT_EQ(true, net_IsValidIPv4Addr(ip)); +} + +TEST(TestIsValidIp, IPV4LeadingZero) +{ + constexpr auto ip = "055.225.255.255"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip)); + + constexpr auto ip2 = "255.055.255.255"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip2)); + + constexpr auto ip3 = "255.255.055.255"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip3)); + + constexpr auto ip4 = "255.255.255.055"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip4)); +} + +TEST(TestIsValidIp, IPV4StartWithADot) +{ + constexpr auto ip = ".192.168.120.197"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip)); +} + +TEST(TestIsValidIp, IPV4StartWith4Digits) +{ + constexpr auto ip = "1927.168.120.197"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip)); +} + +TEST(TestIsValidIp, IPV4OutOfRange) +{ + constexpr auto invalid1 = "421.168.120.124"_ns; + constexpr auto invalid2 = "192.997.120.124"_ns; + constexpr auto invalid3 = "192.168.300.124"_ns; + constexpr auto invalid4 = "192.168.120.256"_ns; + + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid1)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid2)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid3)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid4)); +} + +TEST(TestIsValidIp, IPV4EmptyDigits) +{ + constexpr auto invalid1 = "..0.0.0"_ns; + constexpr auto invalid2 = "127..0.0"_ns; + constexpr auto invalid3 = "127.0..0"_ns; + constexpr auto invalid4 = "127.0.0."_ns; + + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid1)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid2)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid3)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid4)); +} + +TEST(TestIsValidIp, IPV4NonNumeric) +{ + constexpr auto invalid1 = "127.0.0.f"_ns; + constexpr auto invalid2 = "127.0.0.!"_ns; + constexpr auto invalid3 = "127#0.0.1"_ns; + + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid1)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid2)); + ASSERT_EQ(false, net_IsValidIPv4Addr(invalid3)); +} + +TEST(TestIsValidIp, IPV4TooManyDigits) +{ + constexpr auto ip = "127.0.0.1.2"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip)); +} + +TEST(TestIsValidIp, IPV4TooFewDigits) +{ + constexpr auto ip = "127.0.1"_ns; + ASSERT_EQ(false, net_IsValidIPv4Addr(ip)); +} + +TEST(TestIsValidIp, IPV6WithIPV4Inside) +{ + constexpr auto ipv6 = "0123:4567:89ab:cdef:0123:4567:127.0.0.1"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPv6FullForm) +{ + constexpr auto ipv6 = "0123:4567:89ab:cdef:0123:4567:890a:bcde"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPv6TrimLeading0) +{ + constexpr auto ipv6 = "123:4567:0:0:123:4567:890a:bcde"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPv6Collapsed) +{ + constexpr auto ipv6 = "FF01::101"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPV6WithIPV4InsideCollapsed) +{ + constexpr auto ipv6 = "::FFFF:129.144.52.38"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPV6Localhost) +{ + constexpr auto ipv6 = "::1"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPV6LinkLocalPrefix) +{ + constexpr auto ipv6 = "fe80::"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPV6GlobalUnicastPrefix) +{ + constexpr auto ipv6 = "2001::"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPV6Unspecified) +{ + constexpr auto ipv6 = "::"_ns; + ASSERT_EQ(true, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPV6InvalidIPV4Inside) +{ + constexpr auto ipv6 = "0123:4567:89ab:cdef:0123:4567:127.0."_ns; + ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6)); +} + +TEST(TestIsValidIp, IPV6InvalidCharacters) +{ + constexpr auto ipv6 = "012g:4567:89ab:cdef:0123:4567:127.0.0.1"_ns; + ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6)); + + constexpr auto ipv6pound = "0123:456#:89ab:cdef:0123:4567:127.0.0.1"_ns; + ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6pound)); +} + +TEST(TestIsValidIp, IPV6TooManyCharacters) +{ + constexpr auto ipv6 = "0123:45671:89ab:cdef:0123:4567:127.0.0.1"_ns; + ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6)); +} +TEST(TestIsValidIp, IPV6DoubleDoubleDots) +{ + constexpr auto ipv6 = "0123::4567:890a::bcde:0123:4567"_ns; + ASSERT_EQ(false, net_IsValidIPv6Addr(ipv6)); +} diff --git a/netwerk/test/gtest/TestLinkHeader.cpp b/netwerk/test/gtest/TestLinkHeader.cpp new file mode 100644 index 0000000000..4da7002f87 --- /dev/null +++ b/netwerk/test/gtest/TestLinkHeader.cpp @@ -0,0 +1,356 @@ +/* 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 <ostream> + +#include "gtest/gtest-param-test.h" +#include "gtest/gtest.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "nsNetUtil.h" + +using namespace mozilla::net; + +LinkHeader LinkHeaderSetAll(nsAString const& v) { + LinkHeader l; + l.mHref = v; + l.mRel = v; + l.mTitle = v; + l.mIntegrity = v; + l.mSrcset = v; + l.mSizes = v; + l.mType = v; + l.mMedia = v; + l.mAnchor = v; + l.mCrossOrigin = v; + l.mReferrerPolicy = v; + l.mAs = v; + l.mFetchPriority = v; + return l; +} + +LinkHeader LinkHeaderSetTitle(nsAString const& v) { + LinkHeader l; + l.mHref = v; + l.mRel = v; + l.mTitle = v; + return l; +} + +LinkHeader LinkHeaderSetMinimum(nsAString const& v) { + LinkHeader l; + l.mHref = v; + l.mRel = v; + return l; +} + +void PrintTo(const nsTArray<LinkHeader>& aLinkHeaders, std::ostream* aOs) { + bool first = true; + for (const auto& header : aLinkHeaders) { + if (!first) { + *aOs << ", "; + } + first = false; + + *aOs << "(mHref=" << header.mHref << ", " + << "mRel=" << header.mRel << ", " + << "mTitle=" << header.mTitle << ", " + << "mNonce=" << header.mNonce << ", " + << "mIntegrity=" << header.mIntegrity << ", " + << "mSrcset=" << header.mSrcset << ", " + << "mSizes=" << header.mSizes << ", " + << "mType=" << header.mType << ", " + << "mMedia=" << header.mMedia << ", " + << "mAnchor=" << header.mAnchor << ", " + << "mCrossOrigin=" << header.mCrossOrigin << ", " + << "mReferrerPolicy=" << header.mReferrerPolicy << ", " + << "mAs=" << header.mAs << ", " + << "mFetchPriority=" << header.mFetchPriority << ")"; + } +} + +TEST(TestLinkHeader, MultipleLinkHeaders) +{ + nsString link = + u"<a>; rel=a; title=a; integrity=a; imagesrcset=a; imagesizes=a; type=a; media=a; anchor=a; crossorigin=a; referrerpolicy=a; as=a; fetchpriority=a,"_ns + u"<b>; rel=b; title=b; integrity=b; imagesrcset=b; imagesizes=b; type=b; media=b; anchor=b; crossorigin=b; referrerpolicy=b; as=b; fetchpriority=b,"_ns + u"<c>; rel=c"_ns; + + nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(link); + + nsTArray<LinkHeader> expected; + expected.AppendElement(LinkHeaderSetAll(u"a"_ns)); + expected.AppendElement(LinkHeaderSetAll(u"b"_ns)); + expected.AppendElement(LinkHeaderSetMinimum(u"c"_ns)); + + ASSERT_EQ(linkHeaders, expected); +} + +// title* has to be tested separately +TEST(TestLinkHeader, MultipleLinkHeadersTitleStar) +{ + nsString link = + u"<d>; rel=d; title*=UTF-8'de'd,"_ns + u"<e>; rel=e; title*=UTF-8'de'e; title=g,"_ns + u"<f>; rel=f"_ns; + + nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(link); + + nsTArray<LinkHeader> expected; + expected.AppendElement(LinkHeaderSetTitle(u"d"_ns)); + expected.AppendElement(LinkHeaderSetTitle(u"e"_ns)); + expected.AppendElement(LinkHeaderSetMinimum(u"f"_ns)); + + ASSERT_EQ(linkHeaders, expected); +} + +struct SimpleParseTestData { + nsString link; + bool valid; + nsString url; + nsString rel; + nsString as; + nsString fetchpriority; + + friend void PrintTo(const SimpleParseTestData& aData, std::ostream* aOs) { + *aOs << "link=" << aData.link << ", valid=" << aData.valid + << ", url=" << aData.url << ", rel=" << aData.rel + << ", as=" << aData.as << ", fetchpriority=" << aData.fetchpriority + << ")"; + } +}; + +class SimpleParseTest : public ::testing::TestWithParam<SimpleParseTestData> {}; + +TEST_P(SimpleParseTest, Simple) { + const SimpleParseTestData test = GetParam(); + + nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(test.link); + + EXPECT_EQ(test.valid, !linkHeaders.IsEmpty()); + if (test.valid) { + ASSERT_EQ(linkHeaders.Length(), (nsTArray<LinkHeader>::size_type)1); + EXPECT_EQ(test.url, linkHeaders[0].mHref); + EXPECT_EQ(test.rel, linkHeaders[0].mRel); + EXPECT_EQ(test.as, linkHeaders[0].mAs); + EXPECT_EQ(test.fetchpriority, linkHeaders[0].mFetchPriority); + } +} + +// Some test data copied and adapted from +// https://source.chromium.org/chromium/chromium/src/+/main:components/link_header_util/link_header_util_unittest.cc +// the different behavior of the parser is commented above each test case. +const SimpleParseTestData simple_parse_tests[] = { + {u"<s.css>; rel=stylesheet; fetchpriority=\"auto\""_ns, true, u"s.css"_ns, + u"stylesheet"_ns, u""_ns, u"auto"_ns}, + {u"<s.css>; rel=stylesheet; fetchpriority=\"low\""_ns, true, u"s.css"_ns, + u"stylesheet"_ns, u""_ns, u"low"_ns}, + {u"<s.css>; rel=stylesheet; fetchpriority=\"high\""_ns, true, u"s.css"_ns, + u"stylesheet"_ns, u""_ns, u"high"_ns}, + {u"<s.css>; rel=stylesheet; fetchpriority=\"foo\""_ns, true, u"s.css"_ns, + u"stylesheet"_ns, u""_ns, u"foo"_ns}, + {u"<s.css>; rel=stylesheet; fetchpriority=\"\""_ns, true, u"s.css"_ns, + u"stylesheet"_ns, u""_ns, u""_ns}, + {u"<s.css>; rel=stylesheet; fetchpriority=fooWithoutDoubleQuotes"_ns, true, + u"s.css"_ns, u"stylesheet"_ns, u""_ns, u"fooWithoutDoubleQuotes"_ns}, + {u"</images/cat.jpg>; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>;rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg> ;rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg> ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"< /images/cat.jpg> ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg > ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + // TODO(1744051): don't ignore spaces in href + // {u"</images/cat.jpg wutwut> ; rel=prefetch"_ns, true, + // u"/images/cat.jpg wutwut"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg wutwut> ; rel=prefetch"_ns, true, + u"/images/cat.jpgwutwut"_ns, u"prefetch"_ns, u""_ns}, + // TODO(1744051): don't ignore spaces in href + // {u"</images/cat.jpg wutwut \t > ; rel=prefetch"_ns, true, + // u"/images/cat.jpg wutwut"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg wutwut \t > ; rel=prefetch"_ns, true, + u"/images/cat.jpgwutwut"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; rel=prefetch "_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; Rel=prefetch "_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; Rel=PReFetCh "_ns, true, u"/images/cat.jpg"_ns, + u"PReFetCh"_ns, u""_ns}, + {u"</images/cat.jpg>; rel=prefetch; rel=somethingelse"_ns, true, + u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>\t\t ; \trel=prefetch \t "_ns, true, + u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; rel= prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"<../images/cat.jpg?dog>; rel= prefetch"_ns, true, + u"../images/cat.jpg?dog"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; rel =prefetch"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; rel pel=prefetch"_ns, false}, + // different from chromium test case, because we already check for + // existence of "rel" parameter + {u"< /images/cat.jpg>"_ns, false}, + {u"</images/cat.jpg>; wut=sup; rel =prefetch"_ns, true, + u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; wut=sup ; rel =prefetch"_ns, true, + u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; wut=sup ; rel =prefetch \t ;"_ns, true, + u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, + // TODO(1744051): forbid non-whitespace characters between '>' and the first + // semicolon making it conform RFC 8288 Sec 3 + // {u"</images/cat.jpg> wut=sup ; rel =prefetch \t ;"_ns, false}, + {u"</images/cat.jpg> wut=sup ; rel =prefetch \t ;"_ns, true, + u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, + {u"< /images/cat.jpg"_ns, false}, + // TODO(1744051): don't ignore spaces in href + // {u"< http://wut.com/ sdfsdf ?sd>; rel=dns-prefetch"_ns, true, + // u"http://wut.com/ sdfsdf ?sd"_ns, u"dns-prefetch"_ns, u""_ns}, + {u"< http://wut.com/ sdfsdf ?sd>; rel=dns-prefetch"_ns, true, + u"http://wut.com/sdfsdf?sd"_ns, u"dns-prefetch"_ns, u""_ns}, + {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=dns-prefetch"_ns, true, + u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"dns-prefetch"_ns, u""_ns}, + {u"< http://wut.com/dfsdf?sdf=ghj&wer=rty>; rel=prefetch"_ns, true, + u"http://wut.com/dfsdf?sdf=ghj&wer=rty"_ns, u"prefetch"_ns, u""_ns}, + {u"< http://wut.com/dfsdf?sdf=ghj&wer=rty>;;;;; rel=prefetch"_ns, true, + u"http://wut.com/dfsdf?sdf=ghj&wer=rty"_ns, u"prefetch"_ns, u""_ns}, + {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=image"_ns, true, + u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"preload"_ns, u"image"_ns}, + {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=whatever"_ns, + true, u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"preload"_ns, + u"whatever"_ns}, + {u"</images/cat.jpg>; rel=prefetch;"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/cat.jpg>; rel=prefetch ;"_ns, true, u"/images/cat.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"</images/ca,t.jpg>; rel=prefetch ;"_ns, true, u"/images/ca,t.jpg"_ns, + u"prefetch"_ns, u""_ns}, + {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE and " + "backslash\""_ns, + true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, + // TODO(1744051): forbid missing end quote + // {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and " + // "backslash: \\\""_ns, false}, + {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and backslash: \\\""_ns, + true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, + {u"<simple.css>; title=\"title with a DQUOTE \\\" and backslash: \"; " + "rel=stylesheet; "_ns, + true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, + {u"<simple.css>; title=\'title with a DQUOTE \\\' and backslash: \'; " + "rel=stylesheet; "_ns, + true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, + {u"<simple.css>; title=\"title with a DQUOTE \\\" and ;backslash,: \"; " + "rel=stylesheet; "_ns, + true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, + {u"<simple.css>; title=\"title with a DQUOTE \' and ;backslash,: \"; " + "rel=stylesheet; "_ns, + true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, + {u"<simple.css>; title=\"\"; rel=stylesheet; "_ns, true, u"simple.css"_ns, + u"stylesheet"_ns, u""_ns}, + {u"<simple.css>; title=\"\"; rel=\"stylesheet\"; "_ns, true, + u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, + // TODO(1744051): forbid missing end quote + // {u"<simple.css>; rel=stylesheet; title=\""_ns, false}, + {u"<simple.css>; rel=stylesheet; title=\""_ns, true, u"simple.css"_ns, + u"stylesheet"_ns, u""_ns}, + {u"<simple.css>; rel=stylesheet; title=\"\""_ns, true, u"simple.css"_ns, + u"stylesheet"_ns, u""_ns}, + // TODO(1744051): forbid missing end quote + // {u"<simple.css>; rel=\"stylesheet\"; title=\""_ns, false}, + {u"<simple.css>; rel=\"stylesheet\"; title=\""_ns, true, u"simple.css"_ns, + u"stylesheet"_ns, u""_ns}, + // TODO(1744051): forbid missing end quote + // {u"<simple.css>; rel=\";style,sheet\"; title=\""_ns, false}, + {u"<simple.css>; rel=\";style,sheet\"; title=\""_ns, true, u"simple.css"_ns, + u";style,sheet"_ns, u""_ns}, + // TODO(1744051): forbid missing end quote + // {u"<simple.css>; rel=\"bla'sdf\"; title=\""_ns, false} + {u"<simple.css>; rel=\"bla'sdf\"; title=\""_ns, true, u"simple.css"_ns, + u"bla'sdf"_ns, u""_ns}, + // TODO(1744051): allow explicit empty rel + // {u"<simple.css>; rel=\"\"; title=\"\""_ns, true, u"simple.css"_ns, + // u""_ns, u""_ns} + {u"<simple.css>; rel=\"\"; title=\"\""_ns, false}, + {u"<simple.css>; rel=''; title=\"\""_ns, true, u"simple.css"_ns, u"''"_ns, + u""_ns}, + {u"<simple.css>; rel=''; bla"_ns, true, u"simple.css"_ns, u"''"_ns, u""_ns}, + {u"<simple.css>; rel='prefetch"_ns, true, u"simple.css"_ns, u"'prefetch"_ns, + u""_ns}, + // TODO(1744051): forbid missing end quote + // {u"<simple.css>; rel=\"prefetch"_ns, false}, + {u"<simple.css>; rel=\"prefetch"_ns, true, u"simple.css"_ns, + u"\"prefetch"_ns, u""_ns}, + {u"<simple.css>; rel=\""_ns, false}, + {u"simple.css; rel=prefetch"_ns, false}, + {u"<simple.css>; rel=prefetch; rel=foobar"_ns, true, u"simple.css"_ns, + u"prefetch"_ns, u""_ns}, +}; + +INSTANTIATE_TEST_SUITE_P(TestLinkHeader, SimpleParseTest, + testing::ValuesIn(simple_parse_tests)); + +// Test anchor + +struct AnchorTestData { + nsString baseURI; + // building the new anchor in combination with the baseURI + nsString anchor; + nsString href; + const char* resolved; +}; + +class AnchorTest : public ::testing::TestWithParam<AnchorTestData> {}; + +const AnchorTestData anchor_tests[] = { + {u"http://example.com/path/to/index.html"_ns, u""_ns, u"page.html"_ns, + "http://example.com/path/to/page.html"}, + {u"http://example.com/path/to/index.html"_ns, + u"http://example.com/path/"_ns, u"page.html"_ns, + "http://example.com/path/page.html"}, + {u"http://example.com/path/to/index.html"_ns, + u"http://example.com/path/"_ns, u"/page.html"_ns, + "http://example.com/page.html"}, + {u"http://example.com/path/to/index.html"_ns, u".."_ns, u"page.html"_ns, + "http://example.com/path/page.html"}, + {u"http://example.com/path/to/index.html"_ns, u".."_ns, + u"from/page.html"_ns, "http://example.com/path/from/page.html"}, + {u"http://example.com/path/to/index.html"_ns, u"/hello/"_ns, + u"page.html"_ns, "http://example.com/hello/page.html"}, + {u"http://example.com/path/to/index.html"_ns, u"/hello"_ns, u"page.html"_ns, + "http://example.com/page.html"}, + {u"http://example.com/path/to/index.html"_ns, u"#necko"_ns, u"page.html"_ns, + "http://example.com/path/to/page.html"}, + {u"http://example.com/path/to/index.html"_ns, u"https://example.net/"_ns, + u"to/page.html"_ns, "https://example.net/to/page.html"}, +}; + +LinkHeader LinkHeaderFromHrefAndAnchor(nsAString const& aHref, + nsAString const& aAnchor) { + LinkHeader l; + l.mHref = aHref; + l.mAnchor = aAnchor; + return l; +} + +TEST_P(AnchorTest, Anchor) { + const AnchorTestData test = GetParam(); + + LinkHeader linkHeader = LinkHeaderFromHrefAndAnchor(test.href, test.anchor); + + nsCOMPtr<nsIURI> baseURI; + ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(baseURI), test.baseURI)); + + nsCOMPtr<nsIURI> resolved; + ASSERT_TRUE(NS_SUCCEEDED( + linkHeader.NewResolveHref(getter_AddRefs(resolved), baseURI))); + + ASSERT_STREQ(resolved->GetSpecOrDefault().get(), test.resolved); +} + +INSTANTIATE_TEST_SUITE_P(TestLinkHeader, AnchorTest, + testing::ValuesIn(anchor_tests)); diff --git a/netwerk/test/gtest/TestMIMEInputStream.cpp b/netwerk/test/gtest/TestMIMEInputStream.cpp new file mode 100644 index 0000000000..a2f3f7a43d --- /dev/null +++ b/netwerk/test/gtest/TestMIMEInputStream.cpp @@ -0,0 +1,268 @@ +#include "gtest/gtest.h" + +#include "Helpers.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsIMIMEInputStream.h" +#include "nsISeekableStream.h" + +using mozilla::GetCurrentSerialEventTarget; +using mozilla::SpinEventLoopUntil; + +namespace { + +class SeekableLengthInputStream final : public testing::LengthInputStream, + public nsISeekableStream { + public: + SeekableLengthInputStream(const nsACString& aBuffer, + bool aIsInputStreamLength, + bool aIsAsyncInputStreamLength, + nsresult aLengthRv = NS_OK, + bool aNegativeValue = false) + : testing::LengthInputStream(aBuffer, aIsInputStreamLength, + aIsAsyncInputStreamLength, aLengthRv, + aNegativeValue) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD + Seek(int32_t aWhence, int64_t aOffset) override { + MOZ_CRASH("This method should not be called."); + return NS_ERROR_FAILURE; + } + + NS_IMETHOD + Tell(int64_t* aResult) override { + MOZ_CRASH("This method should not be called."); + return NS_ERROR_FAILURE; + } + + NS_IMETHOD + SetEOF() override { + MOZ_CRASH("This method should not be called."); + return NS_ERROR_FAILURE; + } + + private: + ~SeekableLengthInputStream() = default; +}; + +NS_IMPL_ISUPPORTS_INHERITED(SeekableLengthInputStream, + testing::LengthInputStream, nsISeekableStream) + +} // namespace + +// nsIInputStreamLength && nsIAsyncInputStreamLength + +TEST(TestNsMIMEInputStream, QIInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + for (int i = 0; i < 4; i++) { + nsCOMPtr<nsIInputStream> mis; + { + RefPtr<SeekableLengthInputStream> stream = + new SeekableLengthInputStream(buf, i % 2, i > 1); + + nsresult rv; + nsCOMPtr<nsIMIMEInputStream> m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = m; + ASSERT_TRUE(!!mis); + } + + { + nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(mis); + ASSERT_EQ(!!(i % 2), !!qi); + } + + { + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis); + ASSERT_EQ(i > 1, !!qi); + } + } +} + +TEST(TestNsMIMEInputStream, InputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> mis; + { + RefPtr<SeekableLengthInputStream> stream = + new SeekableLengthInputStream(buf, true, false); + + nsresult rv; + nsCOMPtr<nsIMIMEInputStream> m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = m; + ASSERT_TRUE(!!mis); + } + + nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(int64_t(buf.Length()), size); +} + +TEST(TestNsMIMEInputStream, NegativeInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> mis; + { + RefPtr<SeekableLengthInputStream> stream = + new SeekableLengthInputStream(buf, true, false, NS_OK, true); + + nsresult rv; + nsCOMPtr<nsIMIMEInputStream> m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = m; + ASSERT_TRUE(!!mis); + } + + nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(-1, size); +} + +TEST(TestNsMIMEInputStream, AsyncInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> mis; + { + RefPtr<SeekableLengthInputStream> stream = + new SeekableLengthInputStream(buf, false, true); + + nsresult rv; + nsCOMPtr<nsIMIMEInputStream> m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = m; + ASSERT_TRUE(!!mis); + } + + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + RefPtr<testing::LengthCallback> callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "TEST(TestNsMIMEInputStream, AsyncInputStreamLength)"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(int64_t(buf.Length()), callback->Size()); +} + +TEST(TestNsMIMEInputStream, NegativeAsyncInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> mis; + { + RefPtr<SeekableLengthInputStream> stream = + new SeekableLengthInputStream(buf, false, true, NS_OK, true); + + nsresult rv; + nsCOMPtr<nsIMIMEInputStream> m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = m; + ASSERT_TRUE(!!mis); + } + + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + RefPtr<testing::LengthCallback> callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "TEST(TestNsMIMEInputStream, NegativeAsyncInputStreamLength)"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(-1, callback->Size()); +} + +TEST(TestNsMIMEInputStream, AbortLengthCallback) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> mis; + { + RefPtr<SeekableLengthInputStream> stream = + new SeekableLengthInputStream(buf, false, true, NS_OK, true); + + nsresult rv; + nsCOMPtr<nsIMIMEInputStream> m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = m; + ASSERT_TRUE(!!mis); + } + + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + RefPtr<testing::LengthCallback> callback1 = new testing::LengthCallback(); + nsresult rv = qi->AsyncLengthWait(callback1, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + RefPtr<testing::LengthCallback> callback2 = new testing::LengthCallback(); + rv = qi->AsyncLengthWait(callback2, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("TEST(TestNsMIMEInputStream, AbortLengthCallback)"_ns, + [&]() { return callback2->Called(); })); + ASSERT_TRUE(!callback1->Called()); + ASSERT_EQ(-1, callback2->Size()); +} diff --git a/netwerk/test/gtest/TestMozURL.cpp b/netwerk/test/gtest/TestMozURL.cpp new file mode 100644 index 0000000000..826adb241d --- /dev/null +++ b/netwerk/test/gtest/TestMozURL.cpp @@ -0,0 +1,392 @@ +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH + +#include <regex> +#include "json/json.h" +#include "json/reader.h" +#include "mozilla/TextUtils.h" +#include "nsString.h" +#include "mozilla/net/MozURL.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsNetUtil.h" +#include "nsIFile.h" +#include "nsIURI.h" +#include "nsStreamUtils.h" +#include "mozilla/BasePrincipal.h" + +using namespace mozilla; +using namespace mozilla::net; + +TEST(TestMozURL, Getters) +{ + nsAutoCString href("http://user:pass@example.com/path?query#ref"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + + ASSERT_TRUE(url->Scheme().EqualsLiteral("http")); + + ASSERT_TRUE(url->Spec() == href); + + ASSERT_TRUE(url->Username().EqualsLiteral("user")); + + ASSERT_TRUE(url->Password().EqualsLiteral("pass")); + + ASSERT_TRUE(url->Host().EqualsLiteral("example.com")); + + ASSERT_TRUE(url->FilePath().EqualsLiteral("/path")); + + ASSERT_TRUE(url->Query().EqualsLiteral("query")); + + ASSERT_TRUE(url->Ref().EqualsLiteral("ref")); + + url = nullptr; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), ""_ns), NS_ERROR_MALFORMED_URI); + ASSERT_EQ(url, nullptr); +} + +TEST(TestMozURL, MutatorChain) +{ + nsAutoCString href("http://user:pass@example.com/path?query#ref"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + nsAutoCString out; + + RefPtr<MozURL> url2; + ASSERT_EQ(url->Mutate() + .SetScheme("https"_ns) + .SetUsername("newuser"_ns) + .SetPassword("newpass"_ns) + .SetHostname("test"_ns) + .SetFilePath("new/file/path"_ns) + .SetQuery("bla"_ns) + .SetRef("huh"_ns) + .Finalize(getter_AddRefs(url2)), + NS_OK); + + ASSERT_TRUE(url2->Spec().EqualsLiteral( + "https://newuser:newpass@test/new/file/path?bla#huh")); +} + +TEST(TestMozURL, MutatorFinalizeTwice) +{ + nsAutoCString href("http://user:pass@example.com/path?query#ref"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + nsAutoCString out; + + RefPtr<MozURL> url2; + MozURL::Mutator mut = url->Mutate(); + mut.SetScheme("https"_ns); // Change the scheme to https + ASSERT_EQ(mut.Finalize(getter_AddRefs(url2)), NS_OK); + ASSERT_TRUE(url2->Spec().EqualsLiteral( + "https://user:pass@example.com/path?query#ref")); + + // Test that a second call to Finalize will result in an error code + url2 = nullptr; + ASSERT_EQ(mut.Finalize(getter_AddRefs(url2)), NS_ERROR_NOT_AVAILABLE); + ASSERT_EQ(url2, nullptr); +} + +TEST(TestMozURL, MutatorErrorStatus) +{ + nsAutoCString href("http://user:pass@example.com/path?query#ref"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + nsAutoCString out; + + // Test that trying to set the scheme to a bad value will get you an error + MozURL::Mutator mut = url->Mutate(); + mut.SetScheme("!@#$%^&*("_ns); + ASSERT_EQ(mut.GetStatus(), NS_ERROR_MALFORMED_URI); + + // Test that the mutator will not work after one faulty operation + mut.SetScheme("test"_ns); + ASSERT_EQ(mut.GetStatus(), NS_ERROR_MALFORMED_URI); +} + +TEST(TestMozURL, InitWithBase) +{ + nsAutoCString href("https://example.net/a/b.html"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + + ASSERT_TRUE(url->Spec().EqualsLiteral("https://example.net/a/b.html")); + + RefPtr<MozURL> url2; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url2), "c.png"_ns, url), NS_OK); + + ASSERT_TRUE(url2->Spec().EqualsLiteral("https://example.net/a/c.png")); +} + +TEST(TestMozURL, Path) +{ + nsAutoCString href("about:blank"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + + ASSERT_TRUE(url->Spec().EqualsLiteral("about:blank")); + + ASSERT_TRUE(url->Scheme().EqualsLiteral("about")); + + ASSERT_TRUE(url->FilePath().EqualsLiteral("blank")); +} + +TEST(TestMozURL, HostPort) +{ + nsAutoCString href("https://user:pass@example.net:1234/path?query#ref"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + + ASSERT_TRUE(url->HostPort().EqualsLiteral("example.net:1234")); + + RefPtr<MozURL> url2; + url->Mutate().SetHostPort("test:321"_ns).Finalize(getter_AddRefs(url2)); + + ASSERT_TRUE(url2->HostPort().EqualsLiteral("test:321")); + ASSERT_TRUE( + url2->Spec().EqualsLiteral("https://user:pass@test:321/path?query#ref")); + + href.Assign("https://user:pass@example.net:443/path?query#ref"); + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + ASSERT_TRUE(url->HostPort().EqualsLiteral("example.net")); + ASSERT_EQ(url->Port(), -1); +} + +TEST(TestMozURL, Origin) +{ + nsAutoCString href("https://user:pass@example.net:1234/path?query#ref"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + + nsAutoCString out; + url->Origin(out); + ASSERT_TRUE(out.EqualsLiteral("https://example.net:1234")); + + RefPtr<MozURL> url2; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url2), "file:///tmp/foo"_ns), NS_OK); + url2->Origin(out); + ASSERT_TRUE(out.EqualsLiteral("file:///tmp/foo")); + + RefPtr<MozURL> url3; + ASSERT_EQ( + MozURL::Init(getter_AddRefs(url3), + nsLiteralCString( + "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc/" + "foo/bar.html")), + NS_OK); + url3->Origin(out); + ASSERT_TRUE(out.EqualsLiteral( + "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc")); + + RefPtr<MozURL> url4; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url4), "resource://foo/bar.html"_ns), + NS_OK); + url4->Origin(out); + ASSERT_TRUE(out.EqualsLiteral("resource://foo")); + + RefPtr<MozURL> url5; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url5), "about:home"_ns), NS_OK); + url5->Origin(out); + ASSERT_TRUE(out.EqualsLiteral("about:home")); +} + +TEST(TestMozURL, BaseDomain) +{ + nsAutoCString href("https://user:pass@example.net:1234/path?query#ref"); + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), href), NS_OK); + + nsAutoCString out; + ASSERT_EQ(url->BaseDomain(out), NS_OK); + ASSERT_TRUE(out.EqualsLiteral("example.net")); + + RefPtr<MozURL> url2; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url2), "file:///tmp/foo"_ns), NS_OK); + ASSERT_EQ(url2->BaseDomain(out), NS_OK); + ASSERT_TRUE(out.EqualsLiteral("/tmp/foo")); + + RefPtr<MozURL> url3; + ASSERT_EQ( + MozURL::Init(getter_AddRefs(url3), + nsLiteralCString( + "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc/" + "foo/bar.html")), + NS_OK); + ASSERT_EQ(url3->BaseDomain(out), NS_OK); + ASSERT_TRUE(out.EqualsLiteral("53711a8f-65ed-e742-9671-1f02e267c0bc")); + + RefPtr<MozURL> url4; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url4), "resource://foo/bar.html"_ns), + NS_OK); + ASSERT_EQ(url4->BaseDomain(out), NS_OK); + ASSERT_TRUE(out.EqualsLiteral("foo")); + + RefPtr<MozURL> url5; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url5), "about:home"_ns), NS_OK); + ASSERT_EQ(url5->BaseDomain(out), NS_OK); + ASSERT_TRUE(out.EqualsLiteral("about:home")); +} + +namespace { + +bool OriginMatchesExpectedOrigin(const nsACString& aOrigin, + const nsACString& aExpectedOrigin) { + if (aExpectedOrigin.Equals("null") && + StringBeginsWith(aOrigin, "moz-nullprincipal"_ns)) { + return true; + } + return aOrigin == aExpectedOrigin; +} + +bool IsUUID(const nsACString& aString) { + if (!IsAscii(aString)) { + return false; + } + + std::regex pattern( + "^\\{[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab" + "][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\\}$"); + return regex_match(nsCString(aString).get(), pattern); +} + +bool BaseDomainsEqual(const nsACString& aBaseDomain1, + const nsACString& aBaseDomain2) { + if (IsUUID(aBaseDomain1) && IsUUID(aBaseDomain2)) { + return true; + } + return aBaseDomain1 == aBaseDomain2; +} + +void CheckOrigin(const nsACString& aSpec, const nsACString& aBase, + const nsACString& aOrigin) { + nsCOMPtr<nsIURI> baseUri; + nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase); + ASSERT_EQ(rv, NS_OK); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), aSpec, nullptr, baseUri); + ASSERT_EQ(rv, NS_OK); + + OriginAttributes attrs; + + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(principal); + + nsCString origin; + rv = principal->GetOriginNoSuffix(origin); + ASSERT_EQ(rv, NS_OK); + + EXPECT_TRUE(OriginMatchesExpectedOrigin(origin, aOrigin)); + + nsCString baseDomain; + rv = principal->GetBaseDomain(baseDomain); + + bool baseDomainSucceeded = NS_SUCCEEDED(rv); + + RefPtr<MozURL> baseUrl; + ASSERT_EQ(MozURL::Init(getter_AddRefs(baseUrl), aBase), NS_OK); + + RefPtr<MozURL> url; + ASSERT_EQ(MozURL::Init(getter_AddRefs(url), aSpec, baseUrl), NS_OK); + + url->Origin(origin); + + EXPECT_TRUE(OriginMatchesExpectedOrigin(origin, aOrigin)); + + nsCString baseDomain2; + rv = url->BaseDomain(baseDomain2); + + bool baseDomain2Succeeded = NS_SUCCEEDED(rv); + + EXPECT_TRUE(baseDomainSucceeded == baseDomain2Succeeded); + + if (baseDomainSucceeded) { + EXPECT_TRUE(BaseDomainsEqual(baseDomain, baseDomain2)); + } +} + +} // namespace + +TEST(TestMozURL, UrlTestData) +{ + nsCOMPtr<nsIFile> file; + nsresult rv = + NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(file)); + ASSERT_EQ(rv, NS_OK); + + rv = file->Append(u"urltestdata.json"_ns); + ASSERT_EQ(rv, NS_OK); + + bool exists; + rv = file->Exists(&exists); + ASSERT_EQ(rv, NS_OK); + + ASSERT_TRUE(exists); + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); + ASSERT_EQ(rv, NS_OK); + + nsCOMPtr<nsIInputStream> bufferedStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), + stream.forget(), 4096); + ASSERT_EQ(rv, NS_OK); + + nsCString data; + rv = NS_ConsumeStream(bufferedStream, UINT32_MAX, data); + ASSERT_EQ(rv, NS_OK); + + Json::Value root; + Json::CharReaderBuilder builder; + std::unique_ptr<Json::CharReader> const reader(builder.newCharReader()); + ASSERT_TRUE( + reader->parse(data.BeginReading(), data.EndReading(), &root, nullptr)); + ASSERT_TRUE(root.isArray()); + + for (auto& item : root) { + if (!item.isObject()) { + continue; + } + + const Json::Value& skip = item["skip"]; + ASSERT_TRUE(skip.isNull() || skip.isBool()); + if (skip.isBool() && skip.asBool()) { + continue; + } + + const Json::Value& failure = item["failure"]; + ASSERT_TRUE(failure.isNull() || failure.isBool()); + if (failure.isBool() && failure.asBool()) { + continue; + } + + const Json::Value& origin = item["origin"]; + ASSERT_TRUE(origin.isNull() || origin.isString()); + if (origin.isNull()) { + continue; + } + const char* originBegin; + const char* originEnd; + origin.getString(&originBegin, &originEnd); + + auto baseCString = nsDependentCString("about:blank"); + const Json::Value& base = item["base"]; + if (!base.isNull()) { + const char* baseBegin; + const char* baseEnd; + base.getString(&baseBegin, &baseEnd); + baseCString.Assign(nsDependentCSubstring(baseBegin, baseEnd)); + } + + const Json::Value& input = item["input"]; + ASSERT_TRUE(input.isString()); + const char* inputBegin; + const char* inputEnd; + input.getString(&inputBegin, &inputEnd); + + CheckOrigin(nsDependentCString(inputBegin, inputEnd), baseCString, + nsDependentCString(originBegin, originEnd)); + } +} diff --git a/netwerk/test/gtest/TestNamedPipeService.cpp b/netwerk/test/gtest/TestNamedPipeService.cpp new file mode 100644 index 0000000000..b91a17a93e --- /dev/null +++ b/netwerk/test/gtest/TestNamedPipeService.cpp @@ -0,0 +1,281 @@ +/* -*- 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 "gtest/gtest.h" + +#include <windows.h> + +#include "mozilla/Atomics.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Monitor.h" +#include "nsNamedPipeService.h" +#include "nsNetCID.h" + +#define PIPE_NAME L"\\\\.\\pipe\\TestNPS" +#define TEST_STR "Hello World" + +using namespace mozilla; + +/** + * Unlike a monitor, an event allows a thread to wait on another thread + * completing an action without regard to ordering of the wait and the notify. + */ +class Event { + public: + explicit Event(const char* aName) : mMonitor(aName) {} + + ~Event() = default; + + void Set() { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(!mSignaled); + mSignaled = true; + mMonitor.Notify(); + } + void Wait() { + MonitorAutoLock lock(mMonitor); + while (!mSignaled) { + lock.Wait(); + } + mSignaled = false; + } + + private: + Monitor mMonitor MOZ_UNANNOTATED; + bool mSignaled = false; +}; + +class nsNamedPipeDataObserver final : 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; + Event mEvent; +}; + +NS_IMPL_ISUPPORTS(nsNamedPipeDataObserver, nsINamedPipeDataObserver) + +nsNamedPipeDataObserver::nsNamedPipeDataObserver(HANDLE aPipe) + : mPipe(aPipe), mOverlapped(), mBytesTransferred(0), mEvent("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: { + mEvent.Wait(); + } + if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesRead, FALSE)) { + ADD_FAILURE() << "GetOverlappedResult failed"; + return -1; + } + if (mBytesTransferred != bytesRead) { + ADD_FAILURE() << "GetOverlappedResult mismatch"; + return -1; + } + + break; + default: + ADD_FAILURE() << "ReadFile error " << GetLastError(); + return -1; + } + } else { + mEvent.Wait(); + + if (mBytesTransferred != bytesRead) { + ADD_FAILURE() << "GetOverlappedResult mismatch"; + return -1; + } + } + + mBytesTransferred = 0; + 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: { + mEvent.Wait(); + } + if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesWritten, FALSE)) { + ADD_FAILURE() << "GetOverlappedResult failed"; + return -1; + } + if (mBytesTransferred != bytesWritten) { + ADD_FAILURE() << "GetOverlappedResult mismatch"; + return -1; + } + + break; + default: + ADD_FAILURE() << "WriteFile error " << GetLastError(); + return -1; + } + } else { + mEvent.Wait(); + + if (mBytesTransferred != bytesWritten) { + ADD_FAILURE() << "GetOverlappedResult mismatch"; + return -1; + } + } + + mBytesTransferred = 0; + return bytesWritten; +} + +NS_IMETHODIMP +nsNamedPipeDataObserver::OnDataAvailable(uint32_t aBytesTransferred, + void* aOverlapped) { + if (aOverlapped != &mOverlapped) { + ADD_FAILURE() << "invalid overlapped object"; + return NS_ERROR_FAILURE; + } + + DWORD bytesTransferred = 0; + BOOL ret = + GetOverlappedResult(mPipe, reinterpret_cast<LPOVERLAPPED>(aOverlapped), + &bytesTransferred, FALSE); + + if (!ret) { + ADD_FAILURE() << "GetOverlappedResult failed"; + return NS_ERROR_FAILURE; + } + + if (bytesTransferred != aBytesTransferred) { + ADD_FAILURE() << "GetOverlappedResult mismatch"; + return NS_ERROR_FAILURE; + } + + mBytesTransferred += aBytesTransferred; + mEvent.Set(); + + 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) { + // FIXME: adjust parameters + *aPipe = + CreateNamedPipeW(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) { + ADD_FAILURE() << "CreateNamedPipe failed " << GetLastError(); + return FALSE; + } + + return ConnectToNewClient(*aPipe, aOverlapped); +} + +BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped) { + if (ConnectNamedPipe(aPipe, aOverlapped)) { + ADD_FAILURE() + << "Unexpected, overlapped ConnectNamedPipe() always returns 0."; + return FALSE; + } + + switch (GetLastError()) { + case ERROR_IO_PENDING: + return TRUE; + + case ERROR_PIPE_CONNECTED: + if (SetEvent(aOverlapped->hEvent)) break; + + [[fallthrough]]; + default: // error + ADD_FAILURE() << "ConnectNamedPipe failed " << 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) { + ADD_FAILURE() << "pipe server should be pending"; + return NS_ERROR_FAILURE; + } + + *aClient = CreateFileW(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr); + + if (*aClient == INVALID_HANDLE_VALUE) { + ADD_FAILURE() << "Unable to create pipe client"; + CloseHandle(*aServer); + return NS_ERROR_FAILURE; + } + + DWORD pipeMode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState(*aClient, &pipeMode, nullptr, nullptr)) { + ADD_FAILURE() << "SetNamedPipeHandleState error " << GetLastError(); + CloseHandle(*aServer); + CloseHandle(*aClient); + return NS_ERROR_FAILURE; + } + + WaitForSingleObjectEx(overlapped.hEvent, INFINITE, TRUE); + + return NS_OK; +} + +TEST(TestNamedPipeService, Test) +{ + nsCOMPtr<nsINamedPipeService> svc = net::NamedPipeService::GetOrCreate(); + + HANDLE readPipe, writePipe; + nsresult rv = CreateNamedPipe(&readPipe, &writePipe); + ASSERT_NS_SUCCEEDED(rv); + + RefPtr<nsNamedPipeDataObserver> readObserver = + new nsNamedPipeDataObserver(readPipe); + RefPtr<nsNamedPipeDataObserver> writeObserver = + new nsNamedPipeDataObserver(writePipe); + + ASSERT_NS_SUCCEEDED(svc->AddDataObserver(readPipe, readObserver)); + ASSERT_NS_SUCCEEDED(svc->AddDataObserver(writePipe, writeObserver)); + ASSERT_EQ(std::size_t(writeObserver->Write(TEST_STR, sizeof(TEST_STR))), + sizeof(TEST_STR)); + + char buffer[sizeof(TEST_STR)]; + ASSERT_EQ(std::size_t(readObserver->Read(buffer, sizeof(buffer))), + sizeof(TEST_STR)); + ASSERT_STREQ(buffer, TEST_STR) << "I/O mismatch"; + + ASSERT_NS_SUCCEEDED(svc->RemoveDataObserver(readPipe, readObserver)); + ASSERT_NS_SUCCEEDED(svc->RemoveDataObserver(writePipe, writeObserver)); +} diff --git a/netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp b/netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp new file mode 100644 index 0000000000..a07c9438bd --- /dev/null +++ b/netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp @@ -0,0 +1,93 @@ +#include <arpa/inet.h> + +#include "gtest/gtest.h" +#include "mozilla/SHA1.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "mozilla/Logging.h" +#include "nsNetworkLinkService.h" + +using namespace mozilla; + +in6_addr StringToSockAddr(const std::string& str) { + sockaddr_in6 ip; + inet_pton(AF_INET6, str.c_str(), &(ip.sin6_addr)); + return ip.sin6_addr; +} + +TEST(TestNetworkLinkIdHashingDarwin, Single) +{ + // Setup + SHA1Sum expected_sha1; + SHA1Sum::Hash expected_digest; + + in6_addr a1 = StringToSockAddr("2001:db8:8714:3a91::1"); + + // Prefix + expected_sha1.update(&a1, sizeof(in6_addr)); + // Netmask + expected_sha1.update(&a1, sizeof(in6_addr)); + expected_sha1.finish(expected_digest); + + std::vector<prefix_and_netmask> prefixNetmaskStore; + prefixNetmaskStore.push_back(std::make_pair(a1, a1)); + SHA1Sum actual_sha1; + // Run + nsNetworkLinkService::HashSortedPrefixesAndNetmasks(prefixNetmaskStore, + &actual_sha1); + SHA1Sum::Hash actual_digest; + actual_sha1.finish(actual_digest); + + // Assert + ASSERT_EQ(0, memcmp(&expected_digest, &actual_digest, sizeof(SHA1Sum::Hash))); +} + +TEST(TestNetworkLinkIdHashingDarwin, Multiple) +{ + // Setup + SHA1Sum expected_sha1; + SHA1Sum::Hash expected_digest; + + std::vector<in6_addr> addresses; + addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::1")); + addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::2")); + addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::3")); + addresses.push_back(StringToSockAddr("2001:db8:8714:3a91::4")); + + for (const auto& address : addresses) { + // Prefix + expected_sha1.update(&address, sizeof(in6_addr)); + // Netmask + expected_sha1.update(&address, sizeof(in6_addr)); + } + expected_sha1.finish(expected_digest); + + // Ordered + std::vector<prefix_and_netmask> ordered; + for (const auto& address : addresses) { + ordered.push_back(std::make_pair(address, address)); + } + SHA1Sum ordered_sha1; + + // Unordered + std::vector<prefix_and_netmask> reversed; + for (auto it = addresses.rbegin(); it != addresses.rend(); ++it) { + reversed.push_back(std::make_pair(*it, *it)); + } + SHA1Sum reversed_sha1; + + // Run + nsNetworkLinkService::HashSortedPrefixesAndNetmasks(ordered, &ordered_sha1); + SHA1Sum::Hash ordered_digest; + ordered_sha1.finish(ordered_digest); + + nsNetworkLinkService::HashSortedPrefixesAndNetmasks(reversed, &reversed_sha1); + SHA1Sum::Hash reversed_digest; + reversed_sha1.finish(reversed_digest); + + // Assert + ASSERT_EQ(0, + memcmp(&expected_digest, &ordered_digest, sizeof(SHA1Sum::Hash))); + ASSERT_EQ(0, + memcmp(&expected_digest, &reversed_digest, sizeof(SHA1Sum::Hash))); +} diff --git a/netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp b/netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp new file mode 100644 index 0000000000..eb2097d0a4 --- /dev/null +++ b/netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp @@ -0,0 +1,88 @@ +#include <combaseapi.h> + +#include "gtest/gtest.h" +#include "mozilla/SHA1.h" +#include "nsNotifyAddrListener.h" + +using namespace mozilla; + +GUID StringToGuid(const std::string& str) { + GUID guid; + sscanf(str.c_str(), + "%8lx-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", + &guid.Data1, &guid.Data2, &guid.Data3, &guid.Data4[0], &guid.Data4[1], + &guid.Data4[2], &guid.Data4[3], &guid.Data4[4], &guid.Data4[5], + &guid.Data4[6], &guid.Data4[7]); + + return guid; +} + +TEST(TestGuidHashWindows, Single) +{ + // Setup + SHA1Sum expected_sha1; + SHA1Sum::Hash expected_digest; + + GUID g1 = StringToGuid("264555b1-289c-4494-83d1-e158d1d95115"); + + expected_sha1.update(&g1, sizeof(GUID)); + expected_sha1.finish(expected_digest); + + std::vector<GUID> nwGUIDS; + nwGUIDS.push_back(g1); + SHA1Sum actual_sha1; + // Run + nsNotifyAddrListener::HashSortedNetworkIds(nwGUIDS, actual_sha1); + SHA1Sum::Hash actual_digest; + actual_sha1.finish(actual_digest); + + // Assert + ASSERT_EQ(0, memcmp(&expected_digest, &actual_digest, sizeof(SHA1Sum::Hash))); +} + +TEST(TestNetworkLinkIdHashingWindows, Multiple) +{ + // Setup + SHA1Sum expected_sha1; + SHA1Sum::Hash expected_digest; + + std::vector<GUID> nwGUIDS; + nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000001")); + nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000002")); + nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000003")); + nwGUIDS.push_back(StringToGuid("00000000-0000-0000-0000-000000000004")); + + for (const auto& guid : nwGUIDS) { + expected_sha1.update(&guid, sizeof(GUID)); + } + expected_sha1.finish(expected_digest); + + // Ordered + std::vector<GUID> ordered; + for (const auto& guid : nwGUIDS) { + ordered.push_back(guid); + } + SHA1Sum ordered_sha1; + + // Unordered + std::vector<GUID> reversed; + for (auto it = nwGUIDS.rbegin(); it != nwGUIDS.rend(); ++it) { + reversed.push_back(*it); + } + SHA1Sum reversed_sha1; + + // Run + nsNotifyAddrListener::HashSortedNetworkIds(ordered, ordered_sha1); + SHA1Sum::Hash ordered_digest; + ordered_sha1.finish(ordered_digest); + + nsNotifyAddrListener::HashSortedNetworkIds(reversed, reversed_sha1); + SHA1Sum::Hash reversed_digest; + reversed_sha1.finish(reversed_digest); + + // Assert + ASSERT_EQ(0, + memcmp(&expected_digest, &ordered_digest, sizeof(SHA1Sum::Hash))); + ASSERT_EQ(0, + memcmp(&expected_digest, &reversed_digest, sizeof(SHA1Sum::Hash))); +} diff --git a/netwerk/test/gtest/TestPACMan.cpp b/netwerk/test/gtest/TestPACMan.cpp new file mode 100644 index 0000000000..46c57cfc79 --- /dev/null +++ b/netwerk/test/gtest/TestPACMan.cpp @@ -0,0 +1,246 @@ +#include <utility> + +#include "gtest/gtest.h" +#include "nsServiceManagerUtils.h" +#include "../../../xpcom/threads/nsThreadManager.h" +#include "nsIDHCPClient.h" +#include "nsIPrefBranch.h" +#include "nsComponentManager.h" +#include "nsIPrefService.h" +#include "nsNetCID.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/GenericFactory.h" +#include "../../base/nsPACMan.h" + +#define TEST_WPAD_DHCP_OPTION "http://pac/pac.dat" +#define TEST_ASSIGNED_PAC_URL "http://assignedpac/pac.dat" +#define WPAD_PREF 4 +#define NETWORK_PROXY_TYPE_PREF_NAME "network.proxy.type" +#define GETTING_NETWORK_PROXY_TYPE_FAILED (-1) + +nsCString WPADOptionResult; + +namespace mozilla { +namespace net { + +nsresult SetNetworkProxyType(int32_t pref) { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + if (!prefs) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + return prefs->SetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref); +} + +nsresult GetNetworkProxyType(int32_t* pref) { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + if (!prefs) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + return prefs->GetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref); +} + +class nsTestDHCPClient final : public nsIDHCPClient { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDHCPCLIENT + + nsTestDHCPClient() = default; + + nsresult Init() { return NS_OK; }; + + private: + ~nsTestDHCPClient() = default; +}; + +NS_IMETHODIMP +nsTestDHCPClient::GetOption(uint8_t option, nsACString& _retval) { + _retval.Assign(WPADOptionResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsTestDHCPClient, nsIDHCPClient) + +#define NS_TESTDHCPCLIENTSERVICE_CID /* {FEBF1D69-4D7D-4891-9524-045AD18B5593} \ + */ \ + { \ + 0xFEBF1D69, 0x4D7D, 0x4891, { \ + 0x95, 0x24, 0x04, 0x5a, 0xd1, 0x8b, 0x55, 0x93 \ + } \ + } + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsTestDHCPClient, Init) +NS_DEFINE_NAMED_CID(NS_TESTDHCPCLIENTSERVICE_CID); + +void SetOptionResult(const char* result) { WPADOptionResult.Assign(result); } + +class ProcessPendingEventsAction final : public Runnable { + public: + ProcessPendingEventsAction() : Runnable("net::ProcessPendingEventsAction") {} + + NS_IMETHOD + Run() override { + if (NS_HasPendingEvents(nullptr)) { + NS_WARNING("Found pending requests on PAC thread"); + nsresult rv; + rv = NS_ProcessPendingEvents(nullptr); + EXPECT_EQ(NS_OK, rv); + } + NS_WARNING("No pending requests on PAC thread"); + return NS_OK; + } +}; + +class TestPACMan : public ::testing::Test { + protected: + RefPtr<nsPACMan> mPACMan; + + void ProcessAllEvents() { + ProcessPendingEventsOnPACThread(); + nsresult rv; + while (NS_HasPendingEvents(nullptr)) { + NS_WARNING("Pending events on main thread"); + rv = NS_ProcessPendingEvents(nullptr); + ASSERT_EQ(NS_OK, rv); + ProcessPendingEventsOnPACThread(); + } + NS_WARNING("End of pending events on main thread"); + } + + // This method is used to ensure that all pending events on the main thread + // and the Proxy thread are processsed. + // It iterates over ProcessAllEvents because simply calling ProcessAllEvents + // once did not reliably process the events on both threads on all platforms. + void ProcessAllEventsTenTimes() { + for (int i = 0; i < 10; i++) { + ProcessAllEvents(); + } + } + + virtual void SetUp() { + ASSERT_EQ(NS_OK, GetNetworkProxyType(&originalNetworkProxyTypePref)); + nsCOMPtr<nsIFactory> factory; + nsresult rv = nsComponentManagerImpl::gComponentManager->GetClassObject( + kNS_TESTDHCPCLIENTSERVICE_CID, NS_GET_IID(nsIFactory), + getter_AddRefs(factory)); + if (NS_SUCCEEDED(rv) && factory) { + rv = nsComponentManagerImpl::gComponentManager->UnregisterFactory( + kNS_TESTDHCPCLIENTSERVICE_CID, factory); + ASSERT_EQ(NS_OK, rv); + } + factory = new mozilla::GenericFactory(nsTestDHCPClientConstructor); + nsComponentManagerImpl::gComponentManager->RegisterFactory( + kNS_TESTDHCPCLIENTSERVICE_CID, "nsTestDHCPClient", + NS_DHCPCLIENT_CONTRACTID, factory); + + mPACMan = new nsPACMan(nullptr); + mPACMan->SetWPADOverDHCPEnabled(true); + mPACMan->Init(nullptr); + ASSERT_EQ(NS_OK, SetNetworkProxyType(WPAD_PREF)); + } + + virtual void TearDown() { + mPACMan->Shutdown(); + if (originalNetworkProxyTypePref != GETTING_NETWORK_PROXY_TYPE_FAILED) { + ASSERT_EQ(NS_OK, SetNetworkProxyType(originalNetworkProxyTypePref)); + } + } + + nsCOMPtr<nsIDHCPClient> GetPACManDHCPCient() { return mPACMan->mDHCPClient; } + + void SetPACManDHCPCient(nsCOMPtr<nsIDHCPClient> aValue) { + mPACMan->mDHCPClient = std::move(aValue); + } + + void AssertPACSpecEqualTo(const char* aExpected) { + ASSERT_STREQ(aExpected, mPACMan->mPACURISpec.Data()); + } + + private: + int32_t originalNetworkProxyTypePref = GETTING_NETWORK_PROXY_TYPE_FAILED; + + void ProcessPendingEventsOnPACThread() { + RefPtr<ProcessPendingEventsAction> action = + new ProcessPendingEventsAction(); + + mPACMan->DispatchToPAC(action.forget(), /*aSync =*/true); + } +}; + +TEST_F(TestPACMan, TestCreateDHCPClientAndGetOption) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + nsCString spec; + + GetPACManDHCPCient()->GetOption(252, spec); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, spec.Data()); +} + +TEST_F(TestPACMan, TestCreateDHCPClientAndGetEmptyOption) { + SetOptionResult(""); + nsCString spec; + spec.AssignLiteral(TEST_ASSIGNED_PAC_URL); + + GetPACManDHCPCient()->GetOption(252, spec); + + ASSERT_TRUE(spec.IsEmpty()); +} + +TEST_F(TestPACMan, + WhenTheDHCPClientExistsAndDHCPIsNonEmptyDHCPOptionIsUsedAsPACUri) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + + mPACMan->LoadPACFromURI(""_ns); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data()); + AssertPACSpecEqualTo(TEST_WPAD_DHCP_OPTION); +} + +TEST_F(TestPACMan, WhenTheDHCPResponseIsEmptyWPADDefaultsToStandardURL) { + SetOptionResult(""_ns.Data()); + + mPACMan->LoadPACFromURI(""_ns); + ASSERT_TRUE(NS_HasPendingEvents(nullptr)); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ("", WPADOptionResult.Data()); + AssertPACSpecEqualTo("http://wpad/wpad.dat"); +} + +TEST_F(TestPACMan, WhenThereIsNoDHCPClientWPADDefaultsToStandardURL) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + SetPACManDHCPCient(nullptr); + + mPACMan->LoadPACFromURI(""_ns); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data()); + AssertPACSpecEqualTo("http://wpad/wpad.dat"); +} + +TEST_F(TestPACMan, WhenWPADOverDHCPIsPreffedOffWPADDefaultsToStandardURL) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + mPACMan->SetWPADOverDHCPEnabled(false); + + mPACMan->LoadPACFromURI(""_ns); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data()); + AssertPACSpecEqualTo("http://wpad/wpad.dat"); +} + +TEST_F(TestPACMan, WhenPACUriIsSetDirectlyItIsUsedRatherThanWPAD) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + nsCString spec; + spec.AssignLiteral(TEST_ASSIGNED_PAC_URL); + + mPACMan->LoadPACFromURI(spec); + ProcessAllEventsTenTimes(); + + AssertPACSpecEqualTo(TEST_ASSIGNED_PAC_URL); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/gtest/TestProtocolProxyService.cpp b/netwerk/test/gtest/TestProtocolProxyService.cpp new file mode 100644 index 0000000000..a26f5f62a8 --- /dev/null +++ b/netwerk/test/gtest/TestProtocolProxyService.cpp @@ -0,0 +1,164 @@ +#include "gtest/gtest.h" + +#include "nsCOMPtr.h" +#include "nsNetCID.h" +#include "nsString.h" +#include "nsComponentManagerUtils.h" +#include "../../base/nsProtocolProxyService.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Preferences.h" +#include "nsNetUtil.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<nsIURI> url; + nsAutoCString spec; + + auto CheckLoopbackURLs = [&](bool expected) { + // loopback IPs are always filtered + spec = "http://127.0.0.1"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + spec = "http://[::1]"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + spec = "http://localhost"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + }; + + auto CheckURLs = [&](bool expected) { + spec = "http://example.com"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + + spec = "https://10.2.3.4"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 443), expected); + + spec = "http://1.2.3.4"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + + spec = "http://1.2.3.4:8080"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + + spec = "http://[2001::1]"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + + spec = "http://2.3.4.5:7777"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + + spec = "http://[abcd::2]:123"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + + spec = "http://bla.test.com"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + }; + + auto CheckPortDomain = [&](bool expected) { + spec = "http://blabla.com:10"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + ASSERT_EQ(pps->CanUseProxy(url, 80), expected); + }; + + auto CheckLocalDomain = [&](bool expected) { + spec = "http://test"; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), 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(false); + 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); + + CheckLoopbackURLs(false); + // Check URLs can no longer use filtered proxy + CheckURLs(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); + + CheckLoopbackURLs(false); + CheckURLs(true); + 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); + + // Check that filtering works properly when the filter is set to "<local>" + filter = "<local>"; + printf("Testing filter: %s\n", filter.get()); + pps->LoadHostFilters(filter); + + CheckLoopbackURLs(false); + CheckURLs(true); + CheckLocalDomain(false); + CheckPortDomain(true); + + // Check that allow_hijacking_localhost works with empty filter + Preferences::SetBool("network.proxy.allow_hijacking_localhost", true); + + filter = ""; + printf("Testing filter: %s\n", filter.get()); + pps->LoadHostFilters(filter); + + CheckLoopbackURLs(true); + CheckLocalDomain(true); + CheckURLs(true); + CheckPortDomain(true); + + // Check that allow_hijacking_localhost works with non-trivial filter + filter = "127.0.0.1, [::1], localhost, blabla.com:10"; + printf("Testing filter: %s\n", filter.get()); + pps->LoadHostFilters(filter); + + CheckLoopbackURLs(false); + CheckLocalDomain(true); + CheckURLs(true); + CheckPortDomain(false); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/gtest/TestReadStreamToString.cpp b/netwerk/test/gtest/TestReadStreamToString.cpp new file mode 100644 index 0000000000..f5fa0a4979 --- /dev/null +++ b/netwerk/test/gtest/TestReadStreamToString.cpp @@ -0,0 +1,190 @@ +#include "gtest/gtest.h" + +#include "Helpers.h" +#include "nsCOMPtr.h" +#include "nsNetUtil.h" +#include "nsStringStream.h" + +// Here we test the reading a pre-allocated size +TEST(TestReadStreamToString, SyncStreamPreAllocatedSize) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer)); + + uint64_t written; + nsAutoCString result; + result.SetLength(5); + + void* ptr = result.BeginWriting(); + + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, 5, &written)); + ASSERT_EQ((uint64_t)5, written); + ASSERT_TRUE(nsCString(buffer.get(), 5).Equals(result)); + + // The pointer should be equal: no relocation. + ASSERT_EQ(ptr, result.BeginWriting()); +} + +// Here we test the reading the full size of a sync stream +TEST(TestReadStreamToString, SyncStreamFullSize) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer)); + + uint64_t written; + nsAutoCString result; + + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, buffer.Length(), + &written)); + ASSERT_EQ(buffer.Length(), written); + ASSERT_TRUE(buffer.Equals(result)); +} + +// Here we test the reading less than the full size of a sync stream +TEST(TestReadStreamToString, SyncStreamLessThan) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer)); + + uint64_t written; + nsAutoCString result; + + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, 5, &written)); + ASSERT_EQ((uint64_t)5, written); + ASSERT_TRUE(nsCString(buffer.get(), 5).Equals(result)); +} + +// Here we test the reading more than the full size of a sync stream +TEST(TestReadStreamToString, SyncStreamMoreThan) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer)); + + uint64_t written; + nsAutoCString result; + + // Reading more than the buffer size. + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, + buffer.Length() + 5, &written)); + ASSERT_EQ(buffer.Length(), written); + ASSERT_TRUE(buffer.Equals(result)); +} + +// Here we test the reading a sync stream without passing the size +TEST(TestReadStreamToString, SyncStreamUnknownSize) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), buffer)); + + uint64_t written; + nsAutoCString result; + + // Reading all without passing the size + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, -1, &written)); + ASSERT_EQ(buffer.Length(), written); + ASSERT_TRUE(buffer.Equals(result)); +} + +// Here we test the reading the full size of an async stream +TEST(TestReadStreamToString, AsyncStreamFullSize) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer); + + uint64_t written; + nsAutoCString result; + + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, buffer.Length(), + &written)); + ASSERT_EQ(buffer.Length(), written); + ASSERT_TRUE(buffer.Equals(result)); +} + +// Here we test the reading less than the full size of an async stream +TEST(TestReadStreamToString, AsyncStreamLessThan) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer); + + uint64_t written; + nsAutoCString result; + + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, 5, &written)); + ASSERT_EQ((uint64_t)5, written); + ASSERT_TRUE(nsCString(buffer.get(), 5).Equals(result)); +} + +// Here we test the reading more than the full size of an async stream +TEST(TestReadStreamToString, AsyncStreamMoreThan) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer); + + uint64_t written; + nsAutoCString result; + + // Reading more than the buffer size. + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, + buffer.Length() + 5, &written)); + ASSERT_EQ(buffer.Length(), written); + ASSERT_TRUE(buffer.Equals(result)); +} + +// Here we test the reading an async stream without passing the size +TEST(TestReadStreamToString, AsyncStreamUnknownSize) +{ + nsCString buffer; + buffer.AssignLiteral("Hello world!"); + + nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer); + + uint64_t written; + nsAutoCString result; + + // Reading all without passing the size + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, -1, &written)); + ASSERT_EQ(buffer.Length(), written); + ASSERT_TRUE(buffer.Equals(result)); +} + +// Here we test the reading an async big stream without passing the size +TEST(TestReadStreamToString, AsyncStreamUnknownBigSize) +{ + nsCString buffer; + + buffer.SetLength(4096 * 2); + for (uint32_t i = 0; i < 4096 * 2; ++i) { + buffer.BeginWriting()[i] = i % 10; + } + + nsCOMPtr<nsIInputStream> stream = new testing::AsyncStringStream(buffer); + + uint64_t written; + nsAutoCString result; + + // Reading all without passing the size + ASSERT_EQ(NS_OK, NS_ReadInputStreamToString(stream, result, -1, &written)); + ASSERT_EQ(buffer.Length(), written); + ASSERT_TRUE(buffer.Equals(result)); +} diff --git a/netwerk/test/gtest/TestSSLTokensCache.cpp b/netwerk/test/gtest/TestSSLTokensCache.cpp new file mode 100644 index 0000000000..0f67a532eb --- /dev/null +++ b/netwerk/test/gtest/TestSSLTokensCache.cpp @@ -0,0 +1,154 @@ +#include <numeric> + +#include "CertVerifier.h" +#include "CommonSocketControl.h" +#include "SSLTokensCache.h" +#include "TransportSecurityInfo.h" +#include "gtest/gtest.h" +#include "mozilla/Preferences.h" +#include "nsITransportSecurityInfo.h" +#include "nsIWebProgressListener.h" +#include "nsIX509Cert.h" +#include "nsIX509CertDB.h" +#include "nsServiceManagerUtils.h" +#include "sslproto.h" + +static already_AddRefed<CommonSocketControl> createDummySocketControl() { + nsCOMPtr<nsIX509CertDB> certDB(do_GetService(NS_X509CERTDB_CONTRACTID)); + EXPECT_TRUE(certDB); + nsLiteralCString base64( + "MIIBbjCCARWgAwIBAgIUOyCxVVqw03yUxKSfSojsMF8K/" + "ikwCgYIKoZIzj0EAwIwHTEbMBkGA1UEAwwScm9vdF9zZWNwMjU2azFfMjU2MCIYDzIwMjAxM" + "TI3MDAwMDAwWhgPMjAyMzAyMDUwMDAwMDBaMC8xLTArBgNVBAMMJGludF9zZWNwMjU2cjFfM" + "jU2LXJvb3Rfc2VjcDI1NmsxXzI1NjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE+/" + "u7th4Pj5saYKWayHBOLsBQtCPjz3LpI/" + "LE95S0VcKmnSM0VsNsQRnQcG4A7tyNGTkNeZG3stB6ME6qBKpsCjHTAbMAwGA1UdEwQFMAMB" + "Af8wCwYDVR0PBAQDAgEGMAoGCCqGSM49BAMCA0cAMEQCIFuwodUwyOUnIR4KN5ZCSrU7y4iz" + "4/1EWRdHm5kWKi8dAiB6Ixn9sw3uBVbyxnQKYqGnOwM+qLOkJK0W8XkIE3n5sg=="); + nsCOMPtr<nsIX509Cert> cert; + EXPECT_TRUE(NS_SUCCEEDED( + certDB->ConstructX509FromBase64(base64, getter_AddRefs(cert)))); + EXPECT_TRUE(cert); + nsTArray<nsTArray<uint8_t>> succeededCertChain; + for (size_t i = 0; i < 3; i++) { + nsTArray<uint8_t> certDER; + EXPECT_TRUE(NS_SUCCEEDED(cert->GetRawDER(certDER))); + succeededCertChain.AppendElement(std::move(certDER)); + } + RefPtr<CommonSocketControl> socketControl( + new CommonSocketControl(nsLiteralCString("example.com"), 433, 0)); + socketControl->SetServerCert(cert, mozilla::psm::EVStatus::NotEV); + socketControl->SetSucceededCertChain(std::move(succeededCertChain)); + return socketControl.forget(); +} + +static auto MakeTestData(const size_t aDataSize) { + auto data = nsTArray<uint8_t>(); + data.SetLength(aDataSize); + std::iota(data.begin(), data.end(), 0); + return data; +} + +static void putToken(const nsACString& aKey, uint32_t aSize) { + RefPtr<CommonSocketControl> socketControl = createDummySocketControl(); + nsTArray<uint8_t> token = MakeTestData(aSize); + nsresult rv = mozilla::net::SSLTokensCache::Put(aKey, token.Elements(), aSize, + socketControl, aSize); + ASSERT_EQ(rv, NS_OK); +} + +static void getAndCheckResult(const nsACString& aKey, uint32_t aExpectedSize) { + nsTArray<uint8_t> result; + mozilla::net::SessionCacheInfo unused; + nsresult rv = mozilla::net::SSLTokensCache::Get(aKey, result, unused); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(result.Length(), (size_t)aExpectedSize); +} + +TEST(TestTokensCache, SinglePut) +{ + mozilla::net::SSLTokensCache::Clear(); + mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 1); + mozilla::Preferences::SetBool("network.ssl_tokens_cache_use_only_once", true); + + putToken("anon:www.example.com:443"_ns, 100); + nsTArray<uint8_t> result; + mozilla::net::SessionCacheInfo unused; + nsresult rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns, + result, unused); + ASSERT_EQ(rv, NS_OK); + rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns, result, + unused); + ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE); +} + +TEST(TestTokensCache, MultiplePut) +{ + mozilla::net::SSLTokensCache::Clear(); + mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3); + + putToken("anon:www.example1.com:443"_ns, 300); + // This record will be removed because + // "network.ssl_tokens_cache_records_per_entry" is 3. + putToken("anon:www.example1.com:443"_ns, 100); + putToken("anon:www.example1.com:443"_ns, 200); + putToken("anon:www.example1.com:443"_ns, 400); + + // Test if records are ordered by the expiration time + getAndCheckResult("anon:www.example1.com:443"_ns, 200); + getAndCheckResult("anon:www.example1.com:443"_ns, 300); + getAndCheckResult("anon:www.example1.com:443"_ns, 400); +} + +TEST(TestTokensCache, RemoveAll) +{ + mozilla::net::SSLTokensCache::Clear(); + mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3); + + putToken("anon:www.example1.com:443"_ns, 100); + putToken("anon:www.example1.com:443"_ns, 200); + putToken("anon:www.example1.com:443"_ns, 300); + + putToken("anon:www.example2.com:443"_ns, 100); + putToken("anon:www.example2.com:443"_ns, 200); + putToken("anon:www.example2.com:443"_ns, 300); + + nsTArray<uint8_t> result; + mozilla::net::SessionCacheInfo unused; + nsresult rv = mozilla::net::SSLTokensCache::Get( + "anon:www.example1.com:443"_ns, result, unused); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(result.Length(), (size_t)100); + + rv = mozilla::net::SSLTokensCache::RemoveAll("anon:www.example1.com:443"_ns); + ASSERT_EQ(rv, NS_OK); + + rv = mozilla::net::SSLTokensCache::Get("anon:www.example1.com:443"_ns, result, + unused); + ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE); + + rv = mozilla::net::SSLTokensCache::Get("anon:www.example2.com:443"_ns, result, + unused); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(result.Length(), (size_t)100); +} + +TEST(TestTokensCache, Eviction) +{ + mozilla::net::SSLTokensCache::Clear(); + + mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3); + mozilla::Preferences::SetInt("network.ssl_tokens_cache_capacity", 8); + + putToken("anon:www.example2.com:443"_ns, 300); + putToken("anon:www.example2.com:443"_ns, 400); + putToken("anon:www.example2.com:443"_ns, 500); + // The one has expiration time "300" will be removed because we only allow 3 + // records per entry. + putToken("anon:www.example2.com:443"_ns, 600); + + putToken("anon:www.example3.com:443"_ns, 600); + putToken("anon:www.example3.com:443"_ns, 500); + // The one has expiration time "400" was evicted, so we get "500". + getAndCheckResult("anon:www.example2.com:443"_ns, 500); +} diff --git a/netwerk/test/gtest/TestServerTimingHeader.cpp b/netwerk/test/gtest/TestServerTimingHeader.cpp new file mode 100644 index 0000000000..183726a440 --- /dev/null +++ b/netwerk/test/gtest/TestServerTimingHeader.cpp @@ -0,0 +1,238 @@ +#include "gtest/gtest.h" + +#include "mozilla/Unused.h" +#include "mozilla/net/nsServerTiming.h" +#include <string> +#include <vector> + +using namespace mozilla; +using namespace mozilla::net; + +void testServerTimingHeader( + const char* headerValue, + std::vector<std::vector<std::string>> expectedResults) { + nsAutoCString header(headerValue); + ServerTimingParser parser(header); + parser.Parse(); + + nsTArray<nsCOMPtr<nsIServerTiming>> results = + parser.TakeServerTimingHeaders(); + + ASSERT_EQ(results.Length(), expectedResults.size()); + + unsigned i = 0; + for (const auto& header : results) { + std::vector<std::string> expectedResult(expectedResults[i++]); + nsCString name; + mozilla::Unused << header->GetName(name); + ASSERT_TRUE(name.Equals(expectedResult[0].c_str())); + + double duration; + mozilla::Unused << header->GetDuration(&duration); + ASSERT_EQ(duration, atof(expectedResult[1].c_str())); + + nsCString description; + mozilla::Unused << header->GetDescription(description); + ASSERT_TRUE(description.Equals(expectedResult[2].c_str())); + } +} + +TEST(TestServerTimingHeader, HeaderParsing) +{ + // Test cases below are copied from + // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/network/HTTPParsersTest.cpp + + testServerTimingHeader("", {}); + testServerTimingHeader("metric", {{"metric", "0", ""}}); + testServerTimingHeader("metric;dur", {{"metric", "0", ""}}); + testServerTimingHeader("metric;dur=123.4", {{"metric", "123.4", ""}}); + testServerTimingHeader("metric;dur=\"123.4\"", {{"metric", "123.4", ""}}); + + testServerTimingHeader("metric;desc", {{"metric", "0", ""}}); + testServerTimingHeader("metric;desc=description", + {{"metric", "0", "description"}}); + testServerTimingHeader("metric;desc=\"description\"", + {{"metric", "0", "description"}}); + + testServerTimingHeader("metric;dur;desc", {{"metric", "0", ""}}); + testServerTimingHeader("metric;dur=123.4;desc", {{"metric", "123.4", ""}}); + testServerTimingHeader("metric;dur;desc=description", + {{"metric", "0", "description"}}); + testServerTimingHeader("metric;dur=123.4;desc=description", + {{"metric", "123.4", "description"}}); + testServerTimingHeader("metric;desc;dur", {{"metric", "0", ""}}); + testServerTimingHeader("metric;desc;dur=123.4", {{"metric", "123.4", ""}}); + testServerTimingHeader("metric;desc=description;dur", + {{"metric", "0", "description"}}); + testServerTimingHeader("metric;desc=description;dur=123.4", + {{"metric", "123.4", "description"}}); + + // special chars in name + testServerTimingHeader("aB3!#$%&'*+-.^_`|~", + {{"aB3!#$%&'*+-.^_`|~", "0", ""}}); + + // delimiter chars in quoted description + testServerTimingHeader("metric;desc=\"descr;,=iption\";dur=123.4", + {{"metric", "123.4", "descr;,=iption"}}); + + // whitespace + testServerTimingHeader("metric ; ", {{"metric", "0", ""}}); + testServerTimingHeader("metric , ", {{"metric", "0", ""}}); + testServerTimingHeader("metric ; dur = 123.4 ; desc = description", + {{"metric", "123.4", "description"}}); + testServerTimingHeader("metric ; desc = description ; dur = 123.4", + {{"metric", "123.4", "description"}}); + + // multiple entries + testServerTimingHeader( + "metric1;dur=12.3;desc=description1,metric2;dur=45.6;" + "desc=description2,metric3;dur=78.9;desc=description3", + {{"metric1", "12.3", "description1"}, + {"metric2", "45.6", "description2"}, + {"metric3", "78.9", "description3"}}); + testServerTimingHeader("metric1,metric2 ,metric3, metric4 , metric5", + {{"metric1", "0", ""}, + {"metric2", "0", ""}, + {"metric3", "0", ""}, + {"metric4", "0", ""}, + {"metric5", "0", ""}}); + + // quoted-strings + // metric;desc=\ --> '' + testServerTimingHeader("metric;desc=\\", {{"metric", "0", ""}}); + // metric;desc=" --> '' + testServerTimingHeader("metric;desc=\"", {{"metric", "0", ""}}); + // metric;desc=\\ --> '' + testServerTimingHeader("metric;desc=\\\\", {{"metric", "0", ""}}); + // metric;desc=\" --> '' + testServerTimingHeader("metric;desc=\\\"", {{"metric", "0", ""}}); + // metric;desc="\ --> '' + testServerTimingHeader("metric;desc=\"\\", {{"metric", "0", ""}}); + // metric;desc="" --> '' + testServerTimingHeader("metric;desc=\"\"", {{"metric", "0", ""}}); + // metric;desc=\\\ --> '' + testServerTimingHeader(R"(metric;desc=\\\)", {{"metric", "0", ""}}); + // metric;desc=\\" --> '' + testServerTimingHeader(R"(metric;desc=\\")", {{"metric", "0", ""}}); + // metric;desc=\"\ --> '' + testServerTimingHeader(R"(metric;desc=\"\)", {{"metric", "0", ""}}); + // metric;desc=\"" --> '' + testServerTimingHeader(R"(metric;desc=\"")", {{"metric", "0", ""}}); + // metric;desc="\\ --> '' + testServerTimingHeader(R"(metric;desc="\\)", {{"metric", "0", ""}}); + // metric;desc="\" --> '' + testServerTimingHeader(R"(metric;desc="\")", {{"metric", "0", ""}}); + // metric;desc=""\ --> '' + testServerTimingHeader(R"(metric;desc=""\)", {{"metric", "0", ""}}); + // metric;desc=""" --> '' + testServerTimingHeader(R"(metric;desc=""")", {{"metric", "0", ""}}); + // metric;desc=\\\\ --> '' + testServerTimingHeader(R"(metric;desc=\\\\)", {{"metric", "0", ""}}); + // metric;desc=\\\" --> '' + testServerTimingHeader(R"(metric;desc=\\\")", {{"metric", "0", ""}}); + // metric;desc=\\"\ --> '' + testServerTimingHeader(R"(metric;desc=\\"\)", {{"metric", "0", ""}}); + // metric;desc=\\"" --> '' + testServerTimingHeader(R"(metric;desc=\\"")", {{"metric", "0", ""}}); + // metric;desc=\"\\ --> '' + testServerTimingHeader(R"(metric;desc=\"\\)", {{"metric", "0", ""}}); + // metric;desc=\"\" --> '' + testServerTimingHeader(R"(metric;desc=\"\")", {{"metric", "0", ""}}); + // metric;desc=\""\ --> '' + testServerTimingHeader(R"(metric;desc=\""\)", {{"metric", "0", ""}}); + // metric;desc=\""" --> '' + testServerTimingHeader(R"(metric;desc=\""")", {{"metric", "0", ""}}); + // metric;desc="\\\ --> '' + testServerTimingHeader(R"(metric;desc="\\\)", {{"metric", "0", ""}}); + // metric;desc="\\" --> '\' + testServerTimingHeader(R"(metric;desc="\\")", {{"metric", "0", "\\"}}); + // metric;desc="\"\ --> '' + testServerTimingHeader(R"(metric;desc="\"\)", {{"metric", "0", ""}}); + // metric;desc="\"" --> '"' + testServerTimingHeader(R"(metric;desc="\"")", {{"metric", "0", "\""}}); + // metric;desc=""\\ --> '' + testServerTimingHeader(R"(metric;desc=""\\)", {{"metric", "0", ""}}); + // metric;desc=""\" --> '' + testServerTimingHeader(R"(metric;desc=""\")", {{"metric", "0", ""}}); + // metric;desc="""\ --> '' + testServerTimingHeader(R"(metric;desc="""\)", {{"metric", "0", ""}}); + // metric;desc="""" --> '' + testServerTimingHeader(R"(metric;desc="""")", {{"metric", "0", ""}}); + + // duplicate entry names + testServerTimingHeader( + "metric;dur=12.3;desc=description1,metric;dur=45.6;" + "desc=description2", + {{"metric", "12.3", "description1"}, {"metric", "45.6", "description2"}}); + + // non-numeric durations + testServerTimingHeader("metric;dur=foo", {{"metric", "0", ""}}); + testServerTimingHeader("metric;dur=\"foo\"", {{"metric", "0", ""}}); + + // unrecognized param names + testServerTimingHeader( + "metric;foo=bar;desc=description;foo=bar;dur=123.4;foo=bar", + {{"metric", "123.4", "description"}}); + + // duplicate param names + testServerTimingHeader("metric;dur=123.4;dur=567.8", + {{"metric", "123.4", ""}}); + testServerTimingHeader("metric;desc=description1;desc=description2", + {{"metric", "0", "description1"}}); + testServerTimingHeader("metric;dur=foo;dur=567.8", {{"metric", "", ""}}); + + // unspecified param values + testServerTimingHeader("metric;dur;dur=123.4", {{"metric", "0", ""}}); + testServerTimingHeader("metric;desc;desc=description", {{"metric", "0", ""}}); + + // param name case + testServerTimingHeader("metric;DuR=123.4;DeSc=description", + {{"metric", "123.4", "description"}}); + + // nonsense + testServerTimingHeader("metric=foo;dur;dur=123.4,metric2", + {{"metric", "0", ""}, {"metric2", "0", ""}}); + testServerTimingHeader("metric\"foo;dur;dur=123.4,metric2", + {{"metric", "0", ""}}); + + // nonsense - return zero entries + testServerTimingHeader(" ", {}); + testServerTimingHeader("=", {}); + testServerTimingHeader("[", {}); + testServerTimingHeader("]", {}); + testServerTimingHeader(";", {}); + testServerTimingHeader(",", {}); + testServerTimingHeader("=;", {}); + testServerTimingHeader(";=", {}); + testServerTimingHeader("=,", {}); + testServerTimingHeader(",=", {}); + testServerTimingHeader(";,", {}); + testServerTimingHeader(",;", {}); + testServerTimingHeader("=;,", {}); + + // Invalid token + testServerTimingHeader("met=ric", {{"met", "0", ""}}); + testServerTimingHeader("met ric", {{"met", "0", ""}}); + testServerTimingHeader("met[ric", {{"met", "0", ""}}); + testServerTimingHeader("met]ric", {{"met", "0", ""}}); + testServerTimingHeader("metric;desc=desc=123, metric2", + {{"metric", "0", "desc"}, {"metric2", "0", ""}}); + testServerTimingHeader("met ric;desc=de sc , metric2", + {{"met", "0", "de"}, {"metric2", "0", ""}}); + + // test cases from https://w3c.github.io/server-timing/#examples + testServerTimingHeader( + " miss, ,db;dur=53, app;dur=47.2 ", + {{"miss", "0", ""}, {"db", "53", ""}, {"app", "47.2", ""}}); + testServerTimingHeader(" customView, dc;desc=atl ", + {{"customView", "0", ""}, {"dc", "0", "atl"}}); + testServerTimingHeader(" total;dur=123.4 ", {{"total", "123.4", ""}}); + + // test cases for comma in quoted string + testServerTimingHeader(R"( metric ; desc="descr\"\";,=iption";dur=123.4)", + {{"metric", "123.4", "descr\"\";,=iption"}}); + testServerTimingHeader( + " metric2;dur=\"123.4\";;desc=\",;\\\",;,\";;, metric ; desc = \" " + "\\\", ;\\\" \"; dur=123.4,", + {{"metric2", "123.4", ",;\",;,"}, {"metric", "123.4", " \", ;\" "}}); +} diff --git a/netwerk/test/gtest/TestSocketTransportService.cpp b/netwerk/test/gtest/TestSocketTransportService.cpp new file mode 100644 index 0000000000..89adad3740 --- /dev/null +++ b/netwerk/test/gtest/TestSocketTransportService.cpp @@ -0,0 +1,164 @@ +#include "gtest/gtest.h" + +#include "nsCOMPtr.h" +#include "nsISocketTransport.h" +#include "nsString.h" +#include "nsComponentManagerUtils.h" +#include "../../base/nsSocketTransportService2.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +TEST(TestSocketTransportService, PortRemappingPreferenceReading) +{ + nsCOMPtr<nsISocketTransportService> service = + do_GetService("@mozilla.org/network/socket-transport-service;1"); + ASSERT_TRUE(service); + + auto* sts = gSocketTransportService; + ASSERT_TRUE(sts); + + NS_DispatchAndSpinEventLoopUntilComplete( + "test"_ns, sts, NS_NewRunnableFunction("test", [&]() { + auto CheckPortRemap = [&](uint16_t input, uint16_t output) -> bool { + sts->ApplyPortRemap(&input); + return input == output; + }; + + // Ill-formed prefs + ASSERT_FALSE(sts->UpdatePortRemapPreference(";"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference(" ;"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("; "_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("foo"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference(" foo"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference(" foo "_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("10=20;"_ns)); + + ASSERT_FALSE(sts->UpdatePortRemapPreference("1"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1="_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1,="_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-="_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-,="_ns)); + + ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2,"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2-3"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,=3"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4,"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4="_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("10000000=10"_ns)); + + ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2;3"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3="_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-foo=2;3=15"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=foo;3=15"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;foo=15"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3=foo"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2x3=15"_ns)); + ASSERT_FALSE(sts->UpdatePortRemapPreference("1+4=2;3=15"_ns)); + + // Well-formed prefs + ASSERT_TRUE(sts->UpdatePortRemapPreference("1=2"_ns)); + ASSERT_TRUE(CheckPortRemap(1, 2)); + ASSERT_TRUE(CheckPortRemap(2, 2)); + ASSERT_TRUE(CheckPortRemap(3, 3)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference("10=20"_ns)); + ASSERT_TRUE(CheckPortRemap(1, 1)); + ASSERT_TRUE(CheckPortRemap(2, 2)); + ASSERT_TRUE(CheckPortRemap(3, 3)); + ASSERT_TRUE(CheckPortRemap(10, 20)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference("100-200=1000"_ns)); + ASSERT_TRUE(CheckPortRemap(10, 10)); + ASSERT_TRUE(CheckPortRemap(99, 99)); + ASSERT_TRUE(CheckPortRemap(100, 1000)); + ASSERT_TRUE(CheckPortRemap(101, 1000)); + ASSERT_TRUE(CheckPortRemap(200, 1000)); + ASSERT_TRUE(CheckPortRemap(201, 201)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference("100-200,500=1000"_ns)); + ASSERT_TRUE(CheckPortRemap(10, 10)); + ASSERT_TRUE(CheckPortRemap(99, 99)); + ASSERT_TRUE(CheckPortRemap(100, 1000)); + ASSERT_TRUE(CheckPortRemap(101, 1000)); + ASSERT_TRUE(CheckPortRemap(200, 1000)); + ASSERT_TRUE(CheckPortRemap(201, 201)); + ASSERT_TRUE(CheckPortRemap(499, 499)); + ASSERT_TRUE(CheckPortRemap(500, 1000)); + ASSERT_TRUE(CheckPortRemap(501, 501)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference("1-3=10;5-8,12=20"_ns)); + ASSERT_TRUE(CheckPortRemap(1, 10)); + ASSERT_TRUE(CheckPortRemap(2, 10)); + ASSERT_TRUE(CheckPortRemap(3, 10)); + ASSERT_TRUE(CheckPortRemap(4, 4)); + ASSERT_TRUE(CheckPortRemap(5, 20)); + ASSERT_TRUE(CheckPortRemap(8, 20)); + ASSERT_TRUE(CheckPortRemap(11, 11)); + ASSERT_TRUE(CheckPortRemap(12, 20)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference("80=8080;443=8080"_ns)); + ASSERT_TRUE(CheckPortRemap(80, 8080)); + ASSERT_TRUE(CheckPortRemap(443, 8080)); + + // Later rules rewrite earlier rules + ASSERT_TRUE(sts->UpdatePortRemapPreference("10=100;10=200"_ns)); + ASSERT_TRUE(CheckPortRemap(10, 200)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference("10-20=100;10-20=200"_ns)); + ASSERT_TRUE(CheckPortRemap(10, 200)); + ASSERT_TRUE(CheckPortRemap(20, 200)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference("10-20=100;15=200"_ns)); + ASSERT_TRUE(CheckPortRemap(10, 100)); + ASSERT_TRUE(CheckPortRemap(15, 200)); + ASSERT_TRUE(CheckPortRemap(20, 100)); + + ASSERT_TRUE(sts->UpdatePortRemapPreference( + " 100 - 200 = 1000 ; 150 = 2000 "_ns)); + ASSERT_TRUE(CheckPortRemap(100, 1000)); + ASSERT_TRUE(CheckPortRemap(150, 2000)); + ASSERT_TRUE(CheckPortRemap(200, 1000)); + + // Turn off any mapping + ASSERT_TRUE(sts->UpdatePortRemapPreference(""_ns)); + for (uint32_t port = 0; port < 65536; ++port) { + ASSERT_TRUE(CheckPortRemap((uint16_t)port, (uint16_t)port)); + } + })); +} + +TEST(TestSocketTransportService, StatusValues) +{ + static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_RESOLVING) == + NS_NET_STATUS_RESOLVING_HOST); + static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_RESOLVED) == + NS_NET_STATUS_RESOLVED_HOST); + static_assert( + static_cast<nsresult>(nsISocketTransport::STATUS_CONNECTING_TO) == + NS_NET_STATUS_CONNECTING_TO); + static_assert( + static_cast<nsresult>(nsISocketTransport::STATUS_CONNECTED_TO) == + NS_NET_STATUS_CONNECTED_TO); + static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_SENDING_TO) == + NS_NET_STATUS_SENDING_TO); + static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_WAITING_FOR) == + NS_NET_STATUS_WAITING_FOR); + static_assert( + static_cast<nsresult>(nsISocketTransport::STATUS_RECEIVING_FROM) == + NS_NET_STATUS_RECEIVING_FROM); + static_assert(static_cast<nsresult>( + nsISocketTransport::STATUS_TLS_HANDSHAKE_STARTING) == + NS_NET_STATUS_TLS_HANDSHAKE_STARTING); + static_assert( + static_cast<nsresult>(nsISocketTransport::STATUS_TLS_HANDSHAKE_ENDED) == + NS_NET_STATUS_TLS_HANDSHAKE_ENDED); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/gtest/TestStandardURL.cpp b/netwerk/test/gtest/TestStandardURL.cpp new file mode 100644 index 0000000000..035c92fcc2 --- /dev/null +++ b/netwerk/test/gtest/TestStandardURL.cpp @@ -0,0 +1,441 @@ +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH + +#include "nsCOMPtr.h" +#include "nsNetCID.h" +#include "nsIURL.h" +#include "nsIStandardURL.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "nsComponentManagerUtils.h" +#include "nsIURIMutator.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Unused.h" +#include "nsSerializationHelper.h" +#include "mozilla/Base64.h" +#include "nsEscape.h" + +using namespace mozilla; + +// In nsStandardURL.cpp +extern nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result); +extern nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base, + uint32_t& number, uint32_t maxNumber); +extern int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4], + int32_t dotIndex[3], bool& onlyBase10, + int32_t length); +TEST(TestStandardURL, Simple) +{ + nsCOMPtr<nsIURI> url; + ASSERT_EQ(NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec("http://example.com"_ns) + .Finalize(url), + NS_OK); + ASSERT_TRUE(url); + + ASSERT_EQ(NS_MutateURI(url).SetSpec("http://example.com"_ns).Finalize(url), + NS_OK); + + nsAutoCString out; + + ASSERT_EQ(url->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "http://example.com/"_ns); + + ASSERT_EQ(url->Resolve("foo.html?q=45"_ns, out), NS_OK); + ASSERT_TRUE(out == "http://example.com/foo.html?q=45"_ns); + + ASSERT_EQ(NS_MutateURI(url).SetScheme("foo"_ns).Finalize(url), NS_OK); + + ASSERT_EQ(url->GetScheme(out), NS_OK); + ASSERT_TRUE(out == "foo"_ns); + + ASSERT_EQ(url->GetHost(out), NS_OK); + ASSERT_TRUE(out == "example.com"_ns); + ASSERT_EQ(NS_MutateURI(url).SetHost("www.yahoo.com"_ns).Finalize(url), NS_OK); + ASSERT_EQ(url->GetHost(out), NS_OK); + ASSERT_TRUE(out == "www.yahoo.com"_ns); + + ASSERT_EQ(NS_MutateURI(url) + .SetPathQueryRef(nsLiteralCString( + "/some-path/one-the-net/about.html?with-a-query#for-you")) + .Finalize(url), + NS_OK); + ASSERT_EQ(url->GetPathQueryRef(out), NS_OK); + ASSERT_TRUE(out == + nsLiteralCString( + "/some-path/one-the-net/about.html?with-a-query#for-you")); + + ASSERT_EQ(NS_MutateURI(url) + .SetQuery(nsLiteralCString( + "a=b&d=c&what-ever-you-want-to-be-called=45")) + .Finalize(url), + NS_OK); + ASSERT_EQ(url->GetQuery(out), NS_OK); + ASSERT_TRUE(out == "a=b&d=c&what-ever-you-want-to-be-called=45"_ns); + + ASSERT_EQ(NS_MutateURI(url).SetRef("#some-book-mark"_ns).Finalize(url), + NS_OK); + ASSERT_EQ(url->GetRef(out), NS_OK); + ASSERT_TRUE(out == "some-book-mark"_ns); +} + +TEST(TestStandardURL, NormalizeGood) +{ + nsCString result; + const char* manual[] = {"0.0.0.0", + "0.0.0.0", + "0", + "0.0.0.0", + "000", + "0.0.0.0", + "0x00", + "0.0.0.0", + "10.20.100.200", + "10.20.100.200", + "255.255.255.255", + "255.255.255.255", + "0XFF.0xFF.0xff.0xFf", + "255.255.255.255", + "0x000ff.0X00FF.0x0ff.0xff", + "255.255.255.255", + "0x000fA.0X00FB.0x0fC.0xfD", + "250.251.252.253", + "0x000fE.0X00FF.0x0fC.0xfD", + "254.255.252.253", + "0x000fa.0x00fb.0x0fc.0xfd", + "250.251.252.253", + "0x000fe.0x00ff.0x0fc.0xfd", + "254.255.252.253", + "0377.0377.0377.0377", + "255.255.255.255", + "0000377.000377.00377.0377", + "255.255.255.255", + "65535", + "0.0.255.255", + "0xfFFf", + "0.0.255.255", + "0x00000ffff", + "0.0.255.255", + "0177777", + "0.0.255.255", + "000177777", + "0.0.255.255", + "0.13.65535", + "0.13.255.255", + "0.22.0xffff", + "0.22.255.255", + "0.123.0177777", + "0.123.255.255", + "65536", + "0.1.0.0", + "0200000", + "0.1.0.0", + "0x10000", + "0.1.0.0"}; + for (uint32_t i = 0; i < sizeof(manual) / sizeof(manual[0]); i += 2) { + nsCString encHost(manual[i + 0]); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result)); + ASSERT_TRUE(result.Equals(manual[i + 1])); + } + + // Make sure we're getting the numbers correctly interpreted: + for (int i = 0; i < 256; i++) { + nsCString encHost = nsPrintfCString("0x%x", i); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result)); + ASSERT_TRUE(result.Equals(nsPrintfCString("0.0.0.%d", i))); + + encHost = nsPrintfCString("0%o", i); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result)); + ASSERT_TRUE(result.Equals(nsPrintfCString("0.0.0.%d", i))); + } + + // Some random numbers in the range, mixing hex, decimal, octal + for (int i = 0; i < 8; i++) { + int val[4] = {i * 11 + 13, i * 18 + 22, i * 4 + 28, i * 15 + 2}; + + nsCString encHost = + nsPrintfCString("%d.%d.%d.%d", val[0], val[1], val[2], val[3]); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result)); + ASSERT_TRUE(result.Equals(encHost)); + + nsCString encHostM = + nsPrintfCString("0x%x.0x%x.0x%x.0x%x", val[0], val[1], val[2], val[3]); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result)); + ASSERT_TRUE(result.Equals(encHost)); + + encHostM = + nsPrintfCString("0%o.0%o.0%o.0%o", val[0], val[1], val[2], val[3]); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result)); + ASSERT_TRUE(result.Equals(encHost)); + + encHostM = + nsPrintfCString("0x%x.%d.0%o.%d", val[0], val[1], val[2], val[3]); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result)); + ASSERT_TRUE(result.Equals(encHost)); + + encHostM = + nsPrintfCString("%d.0%o.0%o.0x%x", val[0], val[1], val[2], val[3]); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result)); + ASSERT_TRUE(result.Equals(encHost)); + + encHostM = + nsPrintfCString("0%o.0%o.0x%x.0x%x", val[0], val[1], val[2], val[3]); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHostM, result)); + ASSERT_TRUE(result.Equals(encHost)); + } +} + +TEST(TestStandardURL, NormalizeBad) +{ + nsAutoCString result; + const char* manual[] = { + "x22.232.12.32", "122..12.32", "122.12.32.12.32", "122.12.32..", + "122.12.xx.22", "122.12.0xx.22", "0xx.12.01.22", "12.12.02x.22", + "1q.12.2.22", "122.01f.02.22", "12a.01.02.22", "12.01.02.20x1", + "10x2.01.02.20", "0xx.01.02.20", "10.x.02.20", "10.00x2.02.20", + "10.13.02x2.20", "10.x13.02.20", "10.0x134def.02.20", "\0.2.2.2", + "256.2.2.2", "2.256.2.2", "2.2.256.2", "2.2.2.256", + "2.2.-2.3", "+2.2.2.3", "13.0x2x2.2.3", "0x2x2.13.2.3"}; + + for (auto& i : manual) { + nsCString encHost(i); + ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result)); + } +} + +TEST(TestStandardURL, From_test_standardurldotjs) +{ + // These are test (success and failure) cases from test_standardurl.js + nsAutoCString result; + + const char* localIPv4s[] = { + "127.0.0.1", + "127.0.1", + "127.1", + "2130706433", + "0177.00.00.01", + "0177.00.01", + "0177.01", + "00000000000000000000000000177.0000000.0000000.0001", + "000000177.0000001", + "017700000001", + "0x7f.0x00.0x00.0x01", + "0x7f.0x01", + "0x7f000001", + "0x007f.0x0000.0x0000.0x0001", + "000177.0.00000.0x0001", + "127.0.0.1.", + + "0X7F.0X00.0X00.0X01", + "0X7F.0X01", + "0X7F000001", + "0X007F.0X0000.0X0000.0X0001", + "000177.0.00000.0X0001"}; + for (auto& localIPv4 : localIPv4s) { + nsCString encHost(localIPv4); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result)); + ASSERT_TRUE(result.EqualsLiteral("127.0.0.1")); + } + + const char* nonIPv4s[] = {"0xfffffffff", "0x100000000", + "4294967296", "1.2.0x10000", + "1.0x1000000", "256.0.0.1", + "1.256.1", "-1.0.0.0", + "1.2.3.4.5", "010000000000000000", + "2+3", "0.0.0.-1", + "1.2.3.4..", "1..2", + ".1.2.3.4", ".127"}; + for (auto& nonIPv4 : nonIPv4s) { + nsCString encHost(nonIPv4); + ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result)); + } + + const char* oneOrNoDotsIPv4s[] = {"127", "127."}; + for (auto& localIPv4 : oneOrNoDotsIPv4s) { + nsCString encHost(localIPv4); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result)); + ASSERT_TRUE(result.EqualsLiteral("0.0.0.127")); + } +} + +#define TEST_COUNT 10000 + +MOZ_GTEST_BENCH(TestStandardURL, DISABLED_Perf, [] { + nsCOMPtr<nsIURI> url; + ASSERT_EQ(NS_OK, NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec("http://example.com"_ns) + .Finalize(url)); + + nsAutoCString out; + for (int i = TEST_COUNT; i; --i) { + ASSERT_EQ(NS_MutateURI(url).SetSpec("http://example.com"_ns).Finalize(url), + NS_OK); + ASSERT_EQ(url->GetSpec(out), NS_OK); + url->Resolve("foo.html?q=45"_ns, out); + mozilla::Unused << NS_MutateURI(url).SetScheme("foo"_ns).Finalize(url); + url->GetScheme(out); + mozilla::Unused + << NS_MutateURI(url).SetHost("www.yahoo.com"_ns).Finalize(url); + url->GetHost(out); + mozilla::Unused + << NS_MutateURI(url) + .SetPathQueryRef(nsLiteralCString( + "/some-path/one-the-net/about.html?with-a-query#for-you")) + .Finalize(url); + url->GetPathQueryRef(out); + mozilla::Unused << NS_MutateURI(url) + .SetQuery(nsLiteralCString( + "a=b&d=c&what-ever-you-want-to-be-called=45")) + .Finalize(url); + url->GetQuery(out); + mozilla::Unused + << NS_MutateURI(url).SetRef("#some-book-mark"_ns).Finalize(url); + url->GetRef(out); + } +}); + +// Note the five calls in the loop, so divide by 100k +MOZ_GTEST_BENCH(TestStandardURL, DISABLED_NormalizePerf, [] { + nsAutoCString result; + for (int i = 0; i < 20000; i++) { + nsAutoCString encHost("123.232.12.32"); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost, result)); + nsAutoCString encHost2("83.62.12.92"); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost2, result)); + nsAutoCString encHost3("8.7.6.5"); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost3, result)); + nsAutoCString encHost4("111.159.123.220"); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost4, result)); + nsAutoCString encHost5("1.160.204.200"); + ASSERT_EQ(NS_OK, Test_NormalizeIPv4(encHost5, result)); + } +}); + +// Bug 1394785 - ignore unstable test on OSX +#ifndef XP_MACOSX +// Note the five calls in the loop, so divide by 100k +MOZ_GTEST_BENCH(TestStandardURL, DISABLED_NormalizePerfFails, [] { + nsAutoCString result; + for (int i = 0; i < 20000; i++) { + nsAutoCString encHost("123.292.12.32"); + ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result)); + nsAutoCString encHost2("83.62.12.0x13292"); + ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost2, result)); + nsAutoCString encHost3("8.7.6.0xhello"); + ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost3, result)); + nsAutoCString encHost4("111.159.notonmywatch.220"); + ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost4, result)); + nsAutoCString encHost5("1.160.204.20f"); + ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost5, result)); + } +}); +#endif + +TEST(TestStandardURL, Mutator) +{ + nsAutoCString out; + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec("http://example.com"_ns) + .Finalize(uri); + ASSERT_EQ(rv, NS_OK); + + ASSERT_EQ(uri->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "http://example.com/"_ns); + + rv = NS_MutateURI(uri) + .SetScheme("ftp"_ns) + .SetHost("mozilla.org"_ns) + .SetPathQueryRef("/path?query#ref"_ns) + .Finalize(uri); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(uri->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "ftp://mozilla.org/path?query#ref"_ns); + + nsCOMPtr<nsIURL> url; + rv = NS_MutateURI(uri).SetScheme("https"_ns).Finalize(url); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(url->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "https://mozilla.org/path?query#ref"_ns); +} + +TEST(TestStandardURL, Deserialize_Bug1392739) +{ + mozilla::ipc::StandardURLParams standard_params; + standard_params.urlType() = nsIStandardURL::URLTYPE_STANDARD; + standard_params.spec().Truncate(); + standard_params.host() = mozilla::ipc::StandardURLSegment(4294967295, 1); + + mozilla::ipc::URIParams params(standard_params); + + nsCOMPtr<nsIURIMutator> mutator = + do_CreateInstance(NS_STANDARDURLMUTATOR_CID); + ASSERT_EQ(mutator->Deserialize(params), NS_ERROR_FAILURE); +} + +TEST(TestStandardURL, CorruptSerialization) +{ + auto spec = "http://user:pass@example.com/path/to/file.ext?query#hash"_ns; + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec(spec) + .Finalize(uri); + ASSERT_EQ(rv, NS_OK); + + nsAutoCString serialization; + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(uri); + ASSERT_TRUE(serializable); + + // Check that the URL is normally serializable. + ASSERT_EQ(NS_OK, NS_SerializeToString(serializable, serialization)); + nsCOMPtr<nsISupports> deserializedObject; + ASSERT_EQ(NS_OK, NS_DeserializeObject(serialization, + getter_AddRefs(deserializedObject))); + + nsAutoCString canonicalBin; + Unused << Base64Decode(serialization, canonicalBin); + +// The spec serialization begins at byte 49 +// If the implementation of nsStandardURL::Write changes, this test will need +// to be adjusted. +#define SPEC_OFFSET 49 + + ASSERT_EQ(Substring(canonicalBin, SPEC_OFFSET, 7), "http://"_ns); + + nsAutoCString corruptedBin = canonicalBin; + // change mScheme.mPos + corruptedBin.BeginWriting()[SPEC_OFFSET + spec.Length()] = 1; + Unused << Base64Encode(corruptedBin, serialization); + ASSERT_EQ( + NS_ERROR_MALFORMED_URI, + NS_DeserializeObject(serialization, getter_AddRefs(deserializedObject))); + + corruptedBin = canonicalBin; + // change mScheme.mLen + corruptedBin.BeginWriting()[SPEC_OFFSET + spec.Length() + 4] = 127; + Unused << Base64Encode(corruptedBin, serialization); + ASSERT_EQ( + NS_ERROR_MALFORMED_URI, + NS_DeserializeObject(serialization, getter_AddRefs(deserializedObject))); +} + +TEST(TestStandardURL, ParseIPv4Num) +{ + auto host = "0x.0x.0"_ns; + + int32_t bases[4] = {10, 10, 10, 10}; + bool onlyBase10 = true; // Track this as a special case + int32_t dotIndex[3]; // The positions of the dots in the string + int32_t length = static_cast<int32_t>(host.Length()); + + ASSERT_EQ(2, + Test_ValidateIPv4Number(host, bases, dotIndex, onlyBase10, length)); + + nsCString result; + ASSERT_EQ(NS_OK, Test_NormalizeIPv4("0x.0x.0"_ns, result)); + + uint32_t number; + Test_ParseIPv4Number("0x10"_ns, 16, number, 255); + ASSERT_EQ(number, (uint32_t)16); +} diff --git a/netwerk/test/gtest/TestUDPSocket.cpp b/netwerk/test/gtest/TestUDPSocket.cpp new file mode 100644 index 0000000000..fe6e9b223d --- /dev/null +++ b/netwerk/test/gtest/TestUDPSocket.cpp @@ -0,0 +1,410 @@ +/* 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 "gtest/gtest.h" +#include "nsIUDPSocket.h" +#include "nsISocketTransport.h" +#include "nsIOutputStream.h" +#include "nsINetAddr.h" +#include "nsITimer.h" +#include "nsContentUtils.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/net/DNS.h" +#include "prerror.h" +#include "nsComponentManagerUtils.h" + +#define REQUEST 0x68656c6f +#define RESPONSE 0x6f6c6568 +#define MULTICAST_TIMEOUT 2000 + +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) { + ADD_FAILURE() << "Raw data length " << rawLen + << " does not match String data length " << len; + return false; + } + + for (uint32_t i = 0; i < len; i++) { + if (buffer[i] != rawData[i]) { + ADD_FAILURE(); + return false; + } + } + + uint32_t input = 0; + for (uint32_t i = 0; i < len; i++) { + input += buffer[i] << (8 * i); + } + + if (len != sizeof(uint32_t)) { + ADD_FAILURE() << "Message length mismatch, expected " << sizeof(uint32_t) + << " got " << len; + return false; + } + if (input != aExpectedContent) { + ADD_FAILURE() << "Message content mismatch, expected 0x" << std::hex + << aExpectedContent << " got 0x" << input; + return false; + } + + return true; +} + +/* + * UDPClientListener: listens for incomming UDP packets + */ +class UDPClientListener : public nsIUDPSocketListener { + protected: + virtual ~UDPClientListener(); + + public: + explicit UDPClientListener(WaitForCondition* waiter) : mWaiter(waiter) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPSOCKETLISTENER + nsresult mResult = NS_ERROR_FAILURE; + RefPtr<WaitForCondition> mWaiter; +}; + +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); + + if (TEST_SEND_API == phase && CheckMessageContent(message, REQUEST)) { + uint32_t count; + nsTArray<uint8_t> data; + const uint32_t dataBuffer = RESPONSE; + data.AppendElements((const uint8_t*)&dataBuffer, sizeof(uint32_t)); + mResult = socket->SendWithAddr(fromAddr, data, &count); + if (mResult == NS_OK && count == sizeof(uint32_t)) { + SUCCEED(); + } else { + ADD_FAILURE(); + } + return NS_OK; + } + if (TEST_OUTPUT_STREAM != phase || !CheckMessageContent(message, RESPONSE)) { + mResult = NS_ERROR_FAILURE; + } + + // Notify thread + mWaiter->Notify(); + return NS_OK; +} + +NS_IMETHODIMP +UDPClientListener::OnStopListening(nsIUDPSocket*, nsresult) { + mWaiter->Notify(); + return NS_OK; +} + +/* + * UDPServerListener: listens for incomming UDP packets + */ +class UDPServerListener : public nsIUDPSocketListener { + protected: + virtual ~UDPServerListener(); + + public: + explicit UDPServerListener(WaitForCondition* waiter) : mWaiter(waiter) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPSOCKETLISTENER + + nsresult mResult = NS_ERROR_FAILURE; + RefPtr<WaitForCondition> mWaiter; +}; + +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); + SUCCEED(); + + if (TEST_OUTPUT_STREAM == phase && CheckMessageContent(message, REQUEST)) { + nsCOMPtr<nsIOutputStream> outstream; + message->GetOutputStream(getter_AddRefs(outstream)); + + uint32_t count; + const uint32_t data = RESPONSE; + mResult = outstream->Write((const char*)&data, sizeof(uint32_t), &count); + + if (mResult == NS_OK && count == sizeof(uint32_t)) { + SUCCEED(); + } else { + ADD_FAILURE(); + } + return NS_OK; + } + 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 + mWaiter->Notify(); + return NS_OK; +} + +NS_IMETHODIMP +UDPServerListener::OnStopListening(nsIUDPSocket*, nsresult) { + mWaiter->Notify(); + return NS_OK; +} + +/** + * Multicast timer callback: detects delivery failure + */ +class MulticastTimerCallback : public nsITimerCallback, public nsINamed { + protected: + virtual ~MulticastTimerCallback(); + + public: + explicit MulticastTimerCallback(WaitForCondition* waiter) + : mResult(NS_ERROR_NOT_INITIALIZED), mWaiter(waiter) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsresult mResult; + RefPtr<WaitForCondition> mWaiter; +}; + +NS_IMPL_ISUPPORTS(MulticastTimerCallback, nsITimerCallback, nsINamed) + +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; + mWaiter->Notify(); + return NS_OK; +} + +NS_IMETHODIMP +MulticastTimerCallback::GetName(nsACString& aName) { + aName.AssignLiteral("MulticastTimerCallback"); + return NS_OK; +} + +/**** Main ****/ + +TEST(TestUDPSocket, TestUDPSocketMain) +{ + nsresult rv; + + // Create UDPSocket + nsCOMPtr<nsIUDPSocket> server, client; + server = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv); + ASSERT_NS_SUCCEEDED(rv); + + client = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv); + ASSERT_NS_SUCCEEDED(rv); + + RefPtr<WaitForCondition> waiter = new WaitForCondition(); + + // Create UDPServerListener to process UDP packets + RefPtr<UDPServerListener> serverListener = new UDPServerListener(waiter); + + nsCOMPtr<nsIPrincipal> systemPrincipal = nsContentUtils::GetSystemPrincipal(); + + // Bind server socket to 0.0.0.0 + rv = server->Init(0, false, systemPrincipal, true, 0); + ASSERT_NS_SUCCEEDED(rv); + int32_t serverPort; + server->GetPort(&serverPort); + server->AsyncListen(serverListener); + + // Bind clinet on arbitrary port + RefPtr<UDPClientListener> clientListener = new UDPClientListener(waiter); + client->Init(0, false, systemPrincipal, true, 0); + client->AsyncListen(clientListener); + + // Write data to server + uint32_t count; + nsTArray<uint8_t> data; + const uint32_t dataBuffer = REQUEST; + data.AppendElements((const uint8_t*)&dataBuffer, sizeof(uint32_t)); + + phase = TEST_OUTPUT_STREAM; + rv = client->Send("127.0.0.1"_ns, serverPort, data, &count); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_EQ(count, sizeof(uint32_t)); + + // Wait for server + waiter->Wait(1); + ASSERT_NS_SUCCEEDED(serverListener->mResult); + + // Read response from server + ASSERT_NS_SUCCEEDED(clientListener->mResult); + + mozilla::net::NetAddr clientAddr; + rv = client->GetAddress(&clientAddr); + ASSERT_NS_SUCCEEDED(rv); + // 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, data.Elements(), data.Length(), + &count); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_EQ(count, sizeof(uint32_t)); + + // Wait for server + waiter->Wait(1); + ASSERT_NS_SUCCEEDED(serverListener->mResult); + + // Read response from server + ASSERT_NS_SUCCEEDED(clientListener->mResult); + + // Setup timer to detect multicast failure + nsCOMPtr<nsITimer> timer = NS_NewTimer(); + ASSERT_TRUE(timer); + RefPtr<MulticastTimerCallback> timerCb = new MulticastTimerCallback(waiter); + + // 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); + ASSERT_NS_SUCCEEDED(rv); + + // Send multicast ping + timerCb->mResult = NS_OK; + timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT); + rv = client->SendWithAddress(&multicastAddr, data.Elements(), data.Length(), + &count); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_EQ(count, sizeof(uint32_t)); + + // Wait for server to receive successfully + waiter->Wait(1); + ASSERT_NS_SUCCEEDED(serverListener->mResult); + ASSERT_NS_SUCCEEDED(timerCb->mResult); + timer->Cancel(); + + // 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, data.Elements(), data.Length(), + &count); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_EQ(count, sizeof(uint32_t)); + + // Wait for server to fail to receive + waiter->Wait(1); + ASSERT_FALSE(NS_SUCCEEDED(timerCb->mResult)); + timer->Cancel(); + + // Reset state + client->SetMulticastLoopback(true); + server->SetMulticastLoopback(true); + + // Change multicast interface + 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, data.Elements(), data.Length(), + &count); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_EQ(count, sizeof(uint32_t)); + + // Wait for server to fail to receive + waiter->Wait(1); + ASSERT_FALSE(NS_SUCCEEDED(timerCb->mResult)); + timer->Cancel(); + + // Reset state + mozilla::net::NetAddr anyAddr; + anyAddr.inet.family = AF_INET; + anyAddr.inet.ip = PR_htonl(INADDR_ANY); + client->SetMulticastInterfaceAddr(anyAddr); + + // Leave multicast group + rv = server->LeaveMulticastAddr(multicastAddr, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + // Send multicast ping + timerCb->mResult = NS_OK; + timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT); + rv = client->SendWithAddress(&multicastAddr, data.Elements(), data.Length(), + &count); + ASSERT_NS_SUCCEEDED(rv); + EXPECT_EQ(count, sizeof(uint32_t)); + + // Wait for server to fail to receive + waiter->Wait(1); + ASSERT_FALSE(NS_SUCCEEDED(timerCb->mResult)); + timer->Cancel(); + + goto close; // suppress warning about unused label + +close: + // Close server + server->Close(); + client->Close(); + + // Wait for client and server to see closing + waiter->Wait(2); +} diff --git a/netwerk/test/gtest/TestURIMutator.cpp b/netwerk/test/gtest/TestURIMutator.cpp new file mode 100644 index 0000000000..255ed640eb --- /dev/null +++ b/netwerk/test/gtest/TestURIMutator.cpp @@ -0,0 +1,163 @@ +#include "gtest/gtest.h" +#include "nsCOMPtr.h" +#include "nsNetCID.h" +#include "nsIURIMutator.h" +#include "nsIURL.h" +#include "nsThreadPool.h" +#include "nsNetUtil.h" + +TEST(TestURIMutator, Mutator) +{ + nsAutoCString out; + + // This test instantiates a new nsStandardURL::Mutator (via contractID) + // and uses it to create a new URI. + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec("http://example.com"_ns) + .Finalize(uri); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(uri->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "http://example.com/"_ns); + + // This test verifies that we can use NS_MutateURI to change a URI + rv = NS_MutateURI(uri) + .SetScheme("ftp"_ns) + .SetHost("mozilla.org"_ns) + .SetPathQueryRef("/path?query#ref"_ns) + .Finalize(uri); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(uri->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "ftp://mozilla.org/path?query#ref"_ns); + + // This test verifies that we can pass nsIURL to Finalize, and + nsCOMPtr<nsIURL> url; + rv = NS_MutateURI(uri).SetScheme("https"_ns).Finalize(url); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(url->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "https://mozilla.org/path?query#ref"_ns); + + // This test verifies that we can pass nsIURL** to Finalize. + // We need to use the explicit template because it's actually passing + // getter_AddRefs + nsCOMPtr<nsIURL> url2; + rv = NS_MutateURI(url) + .SetRef("newref"_ns) + .Finalize<nsIURL>(getter_AddRefs(url2)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(url2->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "https://mozilla.org/path?query#newref"_ns); + + // This test verifies that we can pass nsIURI** to Finalize. + // No need to be explicit. + auto functionSetRef = [](nsIURI* aURI, nsIURI** aResult) -> nsresult { + return NS_MutateURI(aURI).SetRef("originalRef"_ns).Finalize(aResult); + }; + + nsCOMPtr<nsIURI> newURI; + rv = functionSetRef(url2, getter_AddRefs(newURI)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(newURI->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "https://mozilla.org/path?query#originalRef"_ns); + + // This test verifies that we can pass nsIURI** to Finalize. + nsCOMPtr<nsIURI> uri2; + rv = + NS_MutateURI(url2).SetQuery("newquery"_ns).Finalize(getter_AddRefs(uri2)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(uri2->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "https://mozilla.org/path?newquery#newref"_ns); + + // This test verifies that we can pass nsIURI** to Finalize. + // No need to be explicit. + auto functionSetQuery = [](nsIURI* aURI, nsIURL** aResult) -> nsresult { + return NS_MutateURI(aURI).SetQuery("originalQuery"_ns).Finalize(aResult); + }; + + nsCOMPtr<nsIURL> newURL; + rv = functionSetQuery(uri2, getter_AddRefs(newURL)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(newURL->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "https://mozilla.org/path?originalQuery#newref"_ns); + + // Check that calling Finalize twice will fail. + NS_MutateURI mutator(newURL); + rv = mutator.SetQuery(""_ns).Finalize(uri2); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(uri2->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "https://mozilla.org/path#newref"_ns); + nsCOMPtr<nsIURI> uri3; + rv = mutator.Finalize(uri3); + ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE); + ASSERT_TRUE(uri3 == nullptr); + + // Make sure changing scheme updates the default port + rv = NS_NewURI(getter_AddRefs(uri), + "https://example.org:80/path?query#ref"_ns); + ASSERT_EQ(rv, NS_OK); + rv = NS_MutateURI(uri).SetScheme("http"_ns).Finalize(uri); + ASSERT_EQ(rv, NS_OK); + rv = uri->GetSpec(out); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(out, "http://example.org/path?query#ref"_ns); + int32_t port; + rv = uri->GetPort(&port); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(port, -1); + rv = uri->GetFilePath(out); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(out, "/path"_ns); + rv = uri->GetQuery(out); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(out, "query"_ns); + rv = uri->GetRef(out); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(out, "ref"_ns); + + // Make sure changing scheme does not change non-default port + rv = NS_NewURI(getter_AddRefs(uri), "https://example.org:123"_ns); + ASSERT_EQ(rv, NS_OK); + rv = NS_MutateURI(uri).SetScheme("http"_ns).Finalize(uri); + ASSERT_EQ(rv, NS_OK); + rv = uri->GetSpec(out); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(out, "http://example.org:123/"_ns); + rv = uri->GetPort(&port); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(port, 123); +} + +extern MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount; + +TEST(TestURIMutator, OnAnyThread) +{ + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + pool->SetThreadLimit(60); + + pool = new nsThreadPool(); + for (int i = 0; i < 1000; ++i) { + nsCOMPtr<nsIRunnable> task = + NS_NewRunnableFunction("gtest-OnAnyThread", []() { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), "http://example.com"_ns); + ASSERT_EQ(rv, NS_OK); + nsAutoCString out; + ASSERT_EQ(uri->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "http://example.com/"_ns); + }); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), "http://example.com"_ns); + ASSERT_EQ(rv, NS_OK); + nsAutoCString out; + ASSERT_EQ(uri->GetSpec(out), NS_OK); + ASSERT_TRUE(out == "http://example.com/"_ns); + + pool->Shutdown(); + + ASSERT_EQ(gTlsURLRecursionCount.get(), 0u); +} diff --git a/netwerk/test/gtest/moz.build b/netwerk/test/gtest/moz.build new file mode 100644 index 0000000000..b6d82b41db --- /dev/null +++ b/netwerk/test/gtest/moz.build @@ -0,0 +1,80 @@ +# -*- 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 += [ + "TestBase64Stream.cpp", + "TestBind.cpp", + "TestBufferedInputStream.cpp", + "TestCommon.cpp", + "TestCookie.cpp", + "TestDNSPacket.cpp", + "TestHeaders.cpp", + "TestHttpAtom.cpp", + "TestHttpAuthUtils.cpp", + "TestHttpChannel.cpp", + "TestHttpResponseHead.cpp", + "TestInputStreamTransport.cpp", + "TestIsValidIp.cpp", + "TestLinkHeader.cpp", + "TestMIMEInputStream.cpp", + "TestMozURL.cpp", + "TestProtocolProxyService.cpp", + "TestReadStreamToString.cpp", + "TestServerTimingHeader.cpp", + "TestSocketTransportService.cpp", + "TestSSLTokensCache.cpp", + "TestStandardURL.cpp", + "TestUDPSocket.cpp", +] + +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "TestNamedPipeService.cpp", + ] + +# skip the test on windows10-aarch64 +if not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["TARGET_CPU"] == "aarch64"): + UNIFIED_SOURCES += [ + "TestPACMan.cpp", + "TestURIMutator.cpp", + ] + +# run the test on windows only +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += ["TestNetworkLinkIdHashingWindows.cpp"] + +# run the test on mac only +if CONFIG["OS_TARGET"] == "Darwin": + UNIFIED_SOURCES += ["TestNetworkLinkIdHashingDarwin.cpp"] + +TEST_HARNESS_FILES.gtest += [ + "urltestdata.json", +] + +USE_LIBS += [ + "jsoncpp", +] + +LOCAL_INCLUDES += [ + "/netwerk/base", + "/netwerk/cookie", + "/toolkit/components/jsoncpp/include", + "/xpcom/tests/gtest", +] + +# windows includes only +if CONFIG["OS_TARGET"] == "WINNT": + LOCAL_INCLUDES += ["/netwerk/system/win32"] + +# mac includes only +if CONFIG["OS_TARGET"] == "Darwin": + LOCAL_INCLUDES += ["/netwerk/system/mac"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += ["!/xpcom", "/xpcom/components"] diff --git a/netwerk/test/gtest/urltestdata-orig.json b/netwerk/test/gtest/urltestdata-orig.json new file mode 100644 index 0000000000..5565c938fd --- /dev/null +++ b/netwerk/test/gtest/urltestdata-orig.json @@ -0,0 +1,6148 @@ +[ + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/segments.js", + { + "input": "http://example\t.\norg", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@foo:21/bar;par?b#c", + "base": "http://example.org/foo/bar", + "href": "http://user:pass@foo:21/bar;par?b#c", + "origin": "http://foo:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "foo:21", + "hostname": "foo", + "port": "21", + "pathname": "/bar;par", + "search": "?b", + "hash": "#c" + }, + { + "input": "https://test:@test", + "base": "about:blank", + "href": "https://test@test/", + "origin": "https://test", + "protocol": "https:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://:@test", + "base": "about:blank", + "href": "https://test/", + "origin": "https://test", + "protocol": "https:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://test:@test/x", + "base": "about:blank", + "href": "non-special://test@test/x", + "origin": "null", + "protocol": "non-special:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "non-special://:@test/x", + "base": "about:blank", + "href": "non-special://test/x", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "http:foo.com", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "\t :foo.com \n", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com", + "search": "", + "hash": "" + }, + { + "input": " foo.com ", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "a:\t foo.com", + "base": "http://example.org/foo/bar", + "href": "a: foo.com", + "origin": "null", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": " foo.com", + "search": "", + "hash": "" + }, + { + "input": "http://f:21/ b ? d # e ", + "base": "http://example.org/foo/bar", + "href": "http://f:21/%20b%20?%20d%20# e", + "origin": "http://f:21", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:21", + "hostname": "f", + "port": "21", + "pathname": "/%20b%20", + "search": "?%20d%20", + "hash": "# e" + }, + { + "input": "lolscheme:x x#x x", + "base": "about:blank", + "href": "lolscheme:x x#x x", + "protocol": "lolscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x x", + "search": "", + "hash": "#x x" + }, + { + "input": "http://f:/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:0/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000000000080/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:b/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: /c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:\n/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:fifty-two/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "non-special://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: 21 / b ? d # e ", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": " \t", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": ":foo.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": ":a", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:a", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:a", + "search": "", + "hash": "" + }, + { + "input": ":/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": "#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "#/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#/" + }, + { + "input": "#\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#\\", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#\\" + }, + { + "input": "#;?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#;?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#;?" + }, + { + "input": "?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": ":23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:23", + "search": "", + "hash": "" + }, + { + "input": "/:23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/:23", + "search": "", + "hash": "" + }, + { + "input": "::", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::", + "search": "", + "hash": "" + }, + { + "input": "::23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::23", + "search": "", + "hash": "" + }, + { + "input": "foo://", + "base": "http://example.org/foo/bar", + "href": "foo:///", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@c:29/d", + "base": "http://example.org/foo/bar", + "href": "http://a:b@c:29/d", + "origin": "http://c:29", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "c:29", + "hostname": "c", + "port": "29", + "pathname": "/d", + "search": "", + "hash": "" + }, + { + "input": "http::@c:29", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:@c:29", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:@c:29", + "search": "", + "hash": "" + }, + { + "input": "http://&a:foo(b]c@d:2/", + "base": "http://example.org/foo/bar", + "href": "http://&a:foo(b%5Dc@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "&a", + "password": "foo(b%5Dc", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://::@c@d:2", + "base": "http://example.org/foo/bar", + "href": "http://:%3A%40c@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "", + "password": "%3A%40c", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com:b@d/", + "base": "http://example.org/foo/bar", + "href": "http://foo.com:b@d/", + "origin": "http://d", + "protocol": "http:", + "username": "foo.com", + "password": "b", + "host": "d", + "hostname": "d", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com/\\@", + "base": "http://example.org/foo/bar", + "href": "http://foo.com//@", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "//@", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://foo.com/", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\a\\b:c\\d@foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://a/b:c/d@foo.com/", + "origin": "http://a", + "protocol": "http:", + "username": "", + "password": "", + "host": "a", + "hostname": "a", + "port": "", + "pathname": "/b:c/d@foo.com/", + "search": "", + "hash": "" + }, + { + "input": "foo:/", + "base": "http://example.org/foo/bar", + "href": "foo:/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "foo:/bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo:/bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo://///////", + "base": "http://example.org/foo/bar", + "href": "foo://///////", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////", + "search": "", + "hash": "" + }, + { + "input": "foo://///////bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo://///////bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo:////://///", + "base": "http://example.org/foo/bar", + "href": "foo:////://///", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//://///", + "search": "", + "hash": "" + }, + { + "input": "c:/foo", + "base": "http://example.org/foo/bar", + "href": "c:/foo", + "origin": "null", + "protocol": "c:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "//foo/bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + { + "input": "http://foo/path;a??e#f#g", + "base": "http://example.org/foo/bar", + "href": "http://foo/path;a??e#f#g", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/path;a", + "search": "??e", + "hash": "#f#g" + }, + { + "input": "http://foo/abcd?efgh?ijkl", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd?efgh?ijkl", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "?efgh?ijkl", + "hash": "" + }, + { + "input": "http://foo/abcd#foo?bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd#foo?bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "", + "hash": "#foo?bar" + }, + { + "input": "[61:24:74]:98", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:24:74]:98", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:24:74]:98", + "search": "", + "hash": "" + }, + { + "input": "http:[61:27]/:foo", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:27]/:foo", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:27]/:foo", + "search": "", + "hash": "" + }, + { + "input": "http://[1::2]:3:4", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]:80", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://[2001::1]", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[::127.0.0.1]", + "base": "http://example.org/foo/bar", + "href": "http://[::7f00:1]/", + "origin": "http://[::7f00:1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::7f00:1]", + "hostname": "[::7f00:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[0:0:0:0:0:0:13.1.68.3]", + "base": "http://example.org/foo/bar", + "href": "http://[::d01:4403]/", + "origin": "http://[::d01:4403]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::d01:4403]", + "hostname": "[::d01:4403]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[2001::1]:80", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": "http://example.org/foo/bar", + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file://example:1/", + "base": "about:blank", + "failure": true + }, + { + "input": "file://example:test/", + "base": "about:blank", + "failure": true + }, + { + "input": "file://example%/", + "base": "about:blank", + "failure": true + }, + { + "input": "file://[example]/", + "base": "about:blank", + "failure": true + }, + { + "input": "ftps:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": "http://example.org/foo/bar", + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "http:example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": "http://example.org/foo/bar", + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "/a/b/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/b/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/b/c", + "search": "", + "hash": "" + }, + { + "input": "/a/ /c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%20/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%20/c", + "search": "", + "hash": "" + }, + { + "input": "/a%2fc", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a%2fc", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a%2fc", + "search": "", + "hash": "" + }, + { + "input": "/a/%2f/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%2f/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%2f/c", + "search": "", + "hash": "" + }, + { + "input": "#β", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#%CE%B2", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#%CE%B2" + }, + { + "input": "data:text/html,test#test", + "base": "http://example.org/foo/bar", + "href": "data:text/html,test#test", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "text/html,test", + "search": "", + "hash": "#test" + }, + { + "input": "tel:1234567890", + "base": "http://example.org/foo/bar", + "href": "tel:1234567890", + "origin": "null", + "protocol": "tel:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "1234567890", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html", + { + "input": "file:c:\\foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:/foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": " File:c|////foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:////foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:////foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": "C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/C|\\foo\\bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "\\\\server\\file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "/\\server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "file:///foo/bar.txt", + "base": "file:///tmp/mock/path", + "href": "file:///foo/bar.txt", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo/bar.txt", + "search": "", + "hash": "" + }, + { + "input": "file:///home/me", + "base": "file:///tmp/mock/path", + "href": "file:///home/me", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/home/me", + "search": "", + "hash": "" + }, + { + "input": "//", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "file://test", + "base": "file:///tmp/mock/path", + "href": "file://test/", + "protocol": "file:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + { + "input": "file:test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js", + { + "input": "http://example.com/././foo", + "base": "about:blank", + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/./.foo", + "base": "about:blank", + "href": "http://example.com/.foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/.foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/.", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/./", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/..", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/..bar", + "base": "about:blank", + "href": "http://example.com/foo/..bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/..bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton", + "base": "about:blank", + "href": "http://example.com/foo/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton/../../a", + "base": "about:blank", + "href": "http://example.com/a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../..", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../../ton", + "base": "about:blank", + "href": "http://example.com/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e%2", + "base": "about:blank", + "href": "http://example.com/foo/%2e%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/%2e%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar", + "base": "about:blank", + "href": "http://example.com/%2e.bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%2e.bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com////../..", + "base": "about:blank", + "href": "http://example.com//", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//../..", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//..", + "base": "about:blank", + "href": "http://example.com/foo/bar/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/bar/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo", + "base": "about:blank", + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%20foo", + "base": "about:blank", + "href": "http://example.com/%20foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%20foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%", + "base": "about:blank", + "href": "http://example.com/foo%", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2", + "base": "about:blank", + "href": "http://example.com/foo%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2zbar", + "base": "about:blank", + "href": "http://example.com/foo%2zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2©zbar", + "base": "about:blank", + "href": "http://example.com/foo%2%C3%82%C2%A9zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2%C3%82%C2%A9zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%41%7a", + "base": "about:blank", + "href": "http://example.com/foo%41%7a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%41%7a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\t\u0091%91", + "base": "about:blank", + "href": "http://example.com/foo%C2%91%91", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%C2%91%91", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%00%51", + "base": "about:blank", + "href": "http://example.com/foo%00%51", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%00%51", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/(%28:%3A%29)", + "base": "about:blank", + "href": "http://example.com/(%28:%3A%29)", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/(%28:%3A%29)", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%3A%3a%3C%3c", + "base": "about:blank", + "href": "http://example.com/%3A%3a%3C%3c", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%3A%3a%3C%3c", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\tbar", + "base": "about:blank", + "href": "http://example.com/foobar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foobar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com\\\\foo\\\\bar", + "base": "about:blank", + "href": "http://example.com//foo//bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//foo//bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "base": "about:blank", + "href": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%7Ffp3%3Eju%3Dduvgw%3Dd", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/@asdf%40", + "base": "about:blank", + "href": "http://example.com/@asdf%40", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/@asdf%40", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/你好你好", + "base": "about:blank", + "href": "http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/‥/foo", + "base": "about:blank", + "href": "http://example.com/%E2%80%A5/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%A5/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com//foo", + "base": "about:blank", + "href": "http://example.com/%EF%BB%BF/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%EF%BB%BF/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com//foo//bar", + "base": "about:blank", + "href": "http://example.com/%E2%80%AE/foo/%E2%80%AD/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%AE/foo/%E2%80%AD/bar", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js", + { + "input": "http://www.google.com/foo?bar=baz#", + "base": "about:blank", + "href": "http://www.google.com/foo?bar=baz#", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "" + }, + { + "input": "http://www.google.com/foo?bar=baz# »", + "base": "about:blank", + "href": "http://www.google.com/foo?bar=baz# %C2%BB", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "# %C2%BB" + }, + { + "input": "data:test# »", + "base": "about:blank", + "href": "data:test# %C2%BB", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "# %C2%BB" + }, + { + "input": "http://www.google.com", + "base": "about:blank", + "href": "http://www.google.com/", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.0x00A80001", + "base": "about:blank", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo%2Ehtml", + "base": "about:blank", + "href": "http://www/foo%2Ehtml", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo%2Ehtml", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo/%2E/html", + "base": "about:blank", + "href": "http://www/foo/html", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo/html", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@/", + "base": "about:blank", + "failure": true + }, + { + "input": "http://%25DOMAIN:foobar@foodomain.com/", + "base": "about:blank", + "href": "http://%25DOMAIN:foobar@foodomain.com/", + "origin": "http://foodomain.com", + "protocol": "http:", + "username": "%25DOMAIN", + "password": "foobar", + "host": "foodomain.com", + "hostname": "foodomain.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\www.google.com\\foo", + "base": "about:blank", + "href": "http://www.google.com/foo", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://foo:80/", + "base": "about:blank", + "href": "http://foo/", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:81/", + "base": "about:blank", + "href": "http://foo:81/", + "origin": "http://foo:81", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "httpa://foo:80/", + "base": "about:blank", + "href": "httpa://foo:80/", + "origin": "null", + "protocol": "httpa:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:-80/", + "base": "about:blank", + "failure": true + }, + { + "input": "https://foo:443/", + "base": "about:blank", + "href": "https://foo/", + "origin": "https://foo", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://foo:80/", + "base": "about:blank", + "href": "https://foo:80/", + "origin": "https://foo:80", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:21/", + "base": "about:blank", + "href": "ftp://foo/", + "origin": "ftp://foo", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:80/", + "base": "about:blank", + "href": "ftp://foo:80/", + "origin": "ftp://foo:80", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:70/", + "base": "about:blank", + "href": "gopher://foo/", + "origin": "gopher://foo", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:443/", + "base": "about:blank", + "href": "gopher://foo:443/", + "origin": "gopher://foo:443", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:80/", + "base": "about:blank", + "href": "ws://foo/", + "origin": "ws://foo", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:81/", + "base": "about:blank", + "href": "ws://foo:81/", + "origin": "ws://foo:81", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:443/", + "base": "about:blank", + "href": "ws://foo:443/", + "origin": "ws://foo:443", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:815/", + "base": "about:blank", + "href": "ws://foo:815/", + "origin": "ws://foo:815", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:80/", + "base": "about:blank", + "href": "wss://foo:80/", + "origin": "wss://foo:80", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:81/", + "base": "about:blank", + "href": "wss://foo:81/", + "origin": "wss://foo:81", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:443/", + "base": "about:blank", + "href": "wss://foo/", + "origin": "wss://foo", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:815/", + "base": "about:blank", + "href": "wss://foo:815/", + "origin": "wss://foo:815", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/example.com/", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": "about:blank", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": "about:blank", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": "about:blank", + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": "about:blank", + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:/example.com/", + "base": "about:blank", + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": "about:blank", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": "about:blank", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": "about:blank", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": "about:blank", + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": "about:blank", + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": "about:blank", + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "http:example.com/", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": "about:blank", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": "about:blank", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": "about:blank", + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": "about:blank", + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": "about:blank", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": "about:blank", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": "about:blank", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": "about:blank", + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": "about:blank", + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": "about:blank", + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html", + { + "input": "http:@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:a:b@www.example.com", + "base": "about:blank", + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/a:b@www.example.com", + "base": "about:blank", + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@www.example.com", + "base": "about:blank", + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://@pple.com", + "base": "about:blank", + "href": "http://pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http::b@www.example.com", + "base": "about:blank", + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/:b@www.example.com", + "base": "about:blank", + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://:b@www.example.com", + "base": "about:blank", + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/:@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://user@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:/@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "https:@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:a:b@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:/a:b@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://a:b@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http::@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:a:@www.example.com", + "base": "about:blank", + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/a:@www.example.com", + "base": "about:blank", + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:@www.example.com", + "base": "about:blank", + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www.@pple.com", + "base": "about:blank", + "href": "http://www.@pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "www.", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:@:www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:/@:www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://@:www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://:@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Others", + { + "input": "/", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": ".", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "./test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../aaa/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/aaa/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/aaa/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "中/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/%E4%B8%AD/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/%E4%B8%AD/test.txt", + "search": "", + "hash": "" + }, + { + "input": "http://www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "//www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:...", + "base": "http://www.example.com/test", + "href": "file:///...", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/...", + "search": "", + "hash": "" + }, + { + "input": "file:..", + "base": "http://www.example.com/test", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:a", + "base": "http://www.example.com/test", + "href": "file:///a", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html", + "Basic canonicalization, uppercase should be converted to lowercase", + { + "input": "http://ExAmPlE.CoM", + "base": "http://other.com/", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example example.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://Goo%20 goo%7C|.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[:]", + "base": "http://other.com/", + "failure": true + }, + "U+3000 is mapped to U+0020 (space) which is disallowed", + { + "input": "http://GOO\u00a0\u3000goo.com", + "base": "http://other.com/", + "failure": true + }, + "Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored", + { + "input": "http://GOO\u200b\u2060\ufeffgoo.com", + "base": "http://other.com/", + "href": "http://googoo.com/", + "origin": "http://googoo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "googoo.com", + "hostname": "googoo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Leading and trailing C0 control or space", + { + "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)", + { + "input": "http://www.foo。bar.com", + "base": "http://other.com/", + "href": "http://www.foo.bar.com/", + "origin": "http://www.foo.bar.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.foo.bar.com", + "hostname": "www.foo.bar.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0", + { + "input": "http://\ufdd0zyx.com", + "base": "http://other.com/", + "failure": true + }, + "This is the same as previous but escaped", + { + "input": "http://%ef%b7%90zyx.com", + "base": "http://other.com/", + "failure": true + }, + "U+FFFD", + { + "input": "https://\ufffd", + "base": "about:blank", + "failure": true + }, + { + "input": "https://%EF%BF%BD", + "base": "about:blank", + "failure": true + }, + { + "input": "https://x/\ufffd?\ufffd#\ufffd", + "base": "about:blank", + "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD", + "origin": "https://x", + "protocol": "https:", + "username": "", + "password": "", + "host": "x", + "hostname": "x", + "port": "", + "pathname": "/%EF%BF%BD", + "search": "?%EF%BF%BD", + "hash": "#%EF%BF%BD" + }, + "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.", + { + "input": "http://Go.com", + "base": "http://other.com/", + "href": "http://go.com/", + "origin": "http://go.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "go.com", + "hostname": "go.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257", + { + "input": "http://%41.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com", + "base": "http://other.com/", + "failure": true + }, + "...%00 in fullwidth should fail (also as escaped UTF-8 input)", + { + "input": "http://%00.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com", + "base": "http://other.com/", + "failure": true + }, + "Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN", + { + "input": "http://你好你好", + "base": "http://other.com/", + "href": "http://xn--6qqa088eba/", + "origin": "http://xn--6qqa088eba", + "protocol": "http:", + "username": "", + "password": "", + "host": "xn--6qqa088eba", + "hostname": "xn--6qqa088eba", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://faß.ExAmPlE/", + "base": "about:blank", + "href": "https://xn--fa-hia.example/", + "origin": "https://xn--fa-hia.example", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--fa-hia.example", + "hostname": "xn--fa-hia.example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://faß.ExAmPlE/", + "base": "about:blank", + "href": "sc://fa%C3%9F.ExAmPlE/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "fa%C3%9F.ExAmPlE", + "hostname": "fa%C3%9F.ExAmPlE", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191", + { + "input": "http://%zz%66%a.com", + "base": "http://other.com/", + "failure": true + }, + "If we get an invalid character that has been escaped.", + { + "input": "http://%25", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://hello%00", + "base": "http://other.com/", + "failure": true + }, + "Escaped numbers should be treated like IP addresses if they are.", + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.0.257", + "base": "http://other.com/", + "failure": true + }, + "Invalid escaping in hosts causes failure", + { + "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01", + "base": "http://other.com/", + "failure": true + }, + "A space in a host causes failure", + { + "input": "http://192.168.0.1 hello", + "base": "http://other.com/", + "failure": true + }, + { + "input": "https://x x:12", + "base": "about:blank", + "failure": true + }, + "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP", + { + "input": "http://0Xc0.0250.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Domains with empty labels", + { + "input": "http://./", + "base": "about:blank", + "href": "http://./", + "origin": "http://.", + "protocol": "http:", + "username": "", + "password": "", + "host": ".", + "hostname": ".", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://../", + "base": "about:blank", + "href": "http://../", + "origin": "http://..", + "protocol": "http:", + "username": "", + "password": "", + "host": "..", + "hostname": "..", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0..0x300/", + "base": "about:blank", + "href": "http://0..0x300/", + "origin": "http://0..0x300", + "protocol": "http:", + "username": "", + "password": "", + "host": "0..0x300", + "hostname": "0..0x300", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Broken IPv6", + { + "input": "http://[www.google.com]/", + "base": "about:blank", + "failure": true + }, + { + "input": "http://[google.com]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.4x]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.]", + "base": "http://other.com/", + "failure": true + }, + "Misc Unicode", + { + "input": "http://foo:💩@example.com/bar", + "base": "http://other.com/", + "href": "http://foo:%F0%9F%92%A9@example.com/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "foo", + "password": "%F0%9F%92%A9", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + "# resolving a fragment against any scheme succeeds", + { + "input": "#", + "base": "test:test", + "href": "test:test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "" + }, + { + "input": "#x", + "base": "mailto:x@x.com", + "href": "mailto:x@x.com#x", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x@x.com", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "data:,", + "href": "data:,#x", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ",", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "about:blank", + "href": "about:blank#x", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blank", + "search": "", + "hash": "#x" + }, + { + "input": "#", + "base": "test:test?test", + "href": "test:test?test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "?test", + "hash": "" + }, + "# multiple @ in authority state", + { + "input": "https://@test@test@example:800/", + "base": "http://doesnotmatter/", + "href": "https://%40test%40test@example:800/", + "origin": "https://example:800", + "protocol": "https:", + "username": "%40test%40test", + "password": "", + "host": "example:800", + "hostname": "example", + "port": "800", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://@@@example", + "base": "http://doesnotmatter/", + "href": "https://%40%40@example/", + "origin": "https://example", + "protocol": "https:", + "username": "%40%40", + "password": "", + "host": "example", + "hostname": "example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "non-az-09 characters", + { + "input": "http://`{}:`{}@h/`{}?`{}", + "base": "http://doesnotmatter/", + "href": "http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}", + "origin": "http://h", + "protocol": "http:", + "username": "%60%7B%7D", + "password": "%60%7B%7D", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/%60%7B%7D", + "search": "?`{}", + "hash": "" + }, + "# Credentials in base", + { + "input": "/some/path", + "base": "http://user@example.org/smth", + "href": "http://user@example.org/some/path", + "origin": "http://example.org", + "protocol": "http:", + "username": "user", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/smth", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/smth", + "search": "", + "hash": "" + }, + { + "input": "/some/path", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/some/path", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + "# a set of tests designed by zcorpan for relative URLs with unknown schemes", + { + "input": "i", + "base": "sc:sd", + "failure": true + }, + { + "input": "i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "i", + "base": "sc:/pa/pa", + "href": "sc:/pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc:///pa/pa", + "href": "sc:///pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "?i", + "base": "sc:sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc://ho/pa", + "href": "sc://ho/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "#i", + "base": "sc:sd", + "href": "sc:sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:sd/sd", + "href": "sc:sd/sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd/sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc://ho/pa", + "href": "sc://ho/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + "# make sure that relative URL logic works on known typically non-relative schemes too", + { + "input": "about:/../", + "base": "about:blank", + "href": "about:/", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/../", + "base": "about:blank", + "href": "data:/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/../", + "base": "about:blank", + "href": "javascript:/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/../", + "base": "about:blank", + "href": "mailto:/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# unknown schemes and their hosts", + { + "input": "sc://ñ.test/", + "base": "about:blank", + "href": "sc://%C3%B1.test/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1.test", + "hostname": "%C3%B1.test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://\u001F!\"$&'()*+,-.;<=>^_`{|}~/", + "base": "about:blank", + "href": "sc://%1F!\"$&'()*+,-.;<=>^_`{|}~/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%1F!\"$&'()*+,-.;<=>^_`{|}~", + "hostname": "%1F!\"$&'()*+,-.;<=>^_`{|}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://\u0000/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc:// /", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://%/", + "base": "about:blank", + "href": "sc://%/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%", + "hostname": "%", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://[/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://\\/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://]/", + "base": "about:blank", + "failure": true + }, + { + "input": "x", + "base": "sc://ñ", + "href": "sc://%C3%B1/x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + "# unknown schemes and backslashes", + { + "input": "sc:\\../", + "base": "about:blank", + "href": "sc:\\../", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\../", + "search": "", + "hash": "" + }, + "# unknown scheme with path looking like a password", + { + "input": "sc::a@example.net", + "base": "about:blank", + "href": "sc::a@example.net", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ":a@example.net", + "search": "", + "hash": "" + }, + "# unknown scheme with bogus percent-encoding", + { + "input": "wow:%NBD", + "base": "about:blank", + "href": "wow:%NBD", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%NBD", + "search": "", + "hash": "" + }, + { + "input": "wow:%1G", + "base": "about:blank", + "href": "wow:%1G", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%1G", + "search": "", + "hash": "" + }, + "# Hosts and percent-encoding", + { + "input": "ftp://example.com%80/", + "base": "about:blank", + "failure": true + }, + { + "input": "ftp://example.com%A0/", + "base": "about:blank", + "failure": true + }, + { + "input": "https://example.com%80/", + "base": "about:blank", + "failure": true + }, + { + "input": "https://example.com%A0/", + "base": "about:blank", + "failure": true + }, + { + "input": "ftp://%e2%98%83", + "base": "about:blank", + "href": "ftp://xn--n3h/", + "origin": "ftp://xn--n3h", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://%e2%98%83", + "base": "about:blank", + "href": "https://xn--n3h/", + "origin": "https://xn--n3h", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# tests from jsdom/whatwg-url designed for code coverage", + { + "input": "http://127.0.0.1:10100/relative_import.html", + "base": "about:blank", + "href": "http://127.0.0.1:10100/relative_import.html", + "origin": "http://127.0.0.1:10100", + "protocol": "http:", + "username": "", + "password": "", + "host": "127.0.0.1:10100", + "hostname": "127.0.0.1", + "port": "10100", + "pathname": "/relative_import.html", + "search": "", + "hash": "" + }, + { + "input": "http://facebook.com/?foo=%7B%22abc%22", + "base": "about:blank", + "href": "http://facebook.com/?foo=%7B%22abc%22", + "origin": "http://facebook.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "facebook.com", + "hostname": "facebook.com", + "port": "", + "pathname": "/", + "search": "?foo=%7B%22abc%22", + "hash": "" + }, + { + "input": "https://localhost:3000/jqueryui@1.2.3", + "base": "about:blank", + "href": "https://localhost:3000/jqueryui@1.2.3", + "origin": "https://localhost:3000", + "protocol": "https:", + "username": "", + "password": "", + "host": "localhost:3000", + "hostname": "localhost", + "port": "3000", + "pathname": "/jqueryui@1.2.3", + "search": "", + "hash": "" + }, + "# tab/LF/CR", + { + "input": "h\tt\nt\rp://h\to\ns\rt:9\t0\n0\r0/p\ta\nt\rh?q\tu\ne\rry#f\tr\na\rg", + "base": "about:blank", + "href": "http://host:9000/path?query#frag", + "origin": "http://host:9000", + "protocol": "http:", + "username": "", + "password": "", + "host": "host:9000", + "hostname": "host", + "port": "9000", + "pathname": "/path", + "search": "?query", + "hash": "#frag" + }, + "# Stringification of URL.searchParams", + { + "input": "?a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "?a=b&c=d", + "searchParams": "a=b&c=d", + "hash": "" + }, + { + "input": "??a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar??a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "??a=b&c=d", + "searchParams": "%3Fa=b&c=d", + "hash": "" + }, + "# Scheme only", + { + "input": "http:", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "searchParams": "", + "hash": "" + }, + { + "input": "http:", + "base": "https://example.org/foo/bar", + "failure": true + }, + { + "input": "sc:", + "base": "https://example.org/foo/bar", + "href": "sc:", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "searchParams": "", + "hash": "" + }, + "# Percent encoding of fragments", + { + "input": "http://foo.bar/baz?qux#foo\bbar", + "base": "about:blank", + "href": "http://foo.bar/baz?qux#foo%08bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%08bar" + }, + "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)", + { + "input": "http://192.168.257", + "base": "http://other.com/", + "href": "http://192.168.1.1/", + "origin": "http://192.168.1.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.1.1", + "hostname": "192.168.1.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257.com", + "base": "http://other.com/", + "href": "http://192.168.257.com/", + "origin": "http://192.168.257.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.257.com", + "hostname": "192.168.257.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256", + "base": "http://other.com/", + "href": "http://0.0.1.0/", + "origin": "http://0.0.1.0", + "protocol": "http:", + "username": "", + "password": "", + "host": "0.0.1.0", + "hostname": "0.0.1.0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256.com", + "base": "http://other.com/", + "href": "http://256.com/", + "origin": "http://256.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "256.com", + "hostname": "256.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999", + "base": "http://other.com/", + "href": "http://59.154.201.255/", + "origin": "http://59.154.201.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "59.154.201.255", + "hostname": "59.154.201.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999.com", + "base": "http://other.com/", + "href": "http://999999999.com/", + "origin": "http://999999999.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "999999999.com", + "hostname": "999999999.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://10000000000", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://10000000000.com", + "base": "http://other.com/", + "href": "http://10000000000.com/", + "origin": "http://10000000000.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "10000000000.com", + "hostname": "10000000000.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967295", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967296", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://0xffffffff", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0xffffffff1", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256.256", + "base": "http://other.com/", + "href": "http://256.256.256.256.256/", + "origin": "http://256.256.256.256.256", + "protocol": "http:", + "username": "", + "password": "", + "host": "256.256.256.256.256", + "hostname": "256.256.256.256.256", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://0x.0x.0", + "base": "about:blank", + "href": "https://0.0.0.0/", + "origin": "https://0.0.0.0", + "protocol": "https:", + "username": "", + "password": "", + "host": "0.0.0.0", + "hostname": "0.0.0.0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)", + { + "input": "https://256.0.0.1/test", + "base": "about:blank", + "failure": true + }, + "# file URLs containing percent-encoded Windows drive letters (shouldn't work)", + { + "input": "file:///C%3A/", + "base": "about:blank", + "href": "file:///C%3A/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%3A/", + "search": "", + "hash": "" + }, + { + "input": "file:///C%7C/", + "base": "about:blank", + "href": "file:///C%7C/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%7C/", + "search": "", + "hash": "" + }, + "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)", + { + "input": "pix/submit.gif", + "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html", + "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///C:/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# More file URL tests by zcorpan and annevk", + { + "input": "/", + "base": "file:///C:/a/b", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "//d:", + "base": "file:///C:/a/b", + "href": "file:///d:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:", + "search": "", + "hash": "" + }, + { + "input": "//d:/..", + "base": "file:///C:/a/b", + "href": "file:///d:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///ab:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///1:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "file:", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "file:?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + { + "input": "file:#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + "# File URLs and many (back)slashes", + { + "input": "file:///localhost//cat", + "base": "about:blank", + "href": "file:///localhost//cat", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/localhost//cat", + "search": "", + "hash": "" + }, + { + "input": "\\//pig", + "base": "file://lion/", + "href": "file:///pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pig", + "search": "", + "hash": "" + }, + { + "input": "file://", + "base": "file://ape/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Windows drive letter handling with the 'file:' base URL", + { + "input": "C|#", + "base": "file://host/dir/file", + "href": "file:///C:#", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|?", + "base": "file://host/dir/file", + "href": "file:///C:?", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|/", + "base": "file://host/dir/file", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\n/", + "base": "file://host/dir/file", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\\", + "base": "file://host/dir/file", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C", + "base": "file://host/dir/file", + "href": "file://host/dir/C", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C", + "search": "", + "hash": "" + }, + { + "input": "C|a", + "base": "file://host/dir/file", + "href": "file://host/dir/C|a", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C|a", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk in the file slash state", + { + "input": "/c:/foo/bar", + "base": "file://host/path", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk (no host)", + { + "input": "file:/C|/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://C|/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk with not empty host", + { + "input": "file://example.net/C:/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://1.2.3.4/C:/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://[1::8]/C:/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# file URLs without base URL by Rimas Misevičius", + { + "input": "file:", + "base": "about:blank", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:?q=v", + "base": "about:blank", + "href": "file:///?q=v", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "?q=v", + "hash": "" + }, + { + "input": "file:#frag", + "base": "about:blank", + "href": "file:///#frag", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "#frag" + }, + "# IPv6 tests", + { + "input": "http://[1:0::]", + "base": "http://example.net/", + "href": "http://[1::]/", + "origin": "http://[1::]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1::]", + "hostname": "[1::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[0:1:2:3:4:5:6:7:8]", + "base": "http://example.net/", + "failure": true + }, + { + "input": "https://[0::0::0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:.0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:0:]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1.00.0.0.0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1.290.0.0.0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1.23.23]", + "base": "about:blank", + "failure": true + }, + "# Empty host", + { + "input": "http://?", + "base": "about:blank", + "failure": true + }, + { + "input": "http://#", + "base": "about:blank", + "failure": true + }, + "Port overflow (2^32 + 81)", + { + "input": "http://f:4294967377/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^64 + 81)", + { + "input": "http://f:18446744073709551697/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^128 + 81)", + { + "input": "http://f:340282366920938463463374607431768211537/c", + "base": "http://example.org/", + "failure": true + }, + "# Non-special-URL path tests", + { + "input": "///", + "base": "sc://x/", + "href": "sc:///", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "tftp://foobar.com/someconfig;mode=netascii", + "base": "about:blank", + "href": "tftp://foobar.com/someconfig;mode=netascii", + "origin": "null", + "protocol": "tftp:", + "username": "", + "password": "", + "host": "foobar.com", + "hostname": "foobar.com", + "port": "", + "pathname": "/someconfig;mode=netascii", + "search": "", + "hash": "" + }, + { + "input": "telnet://user:pass@foobar.com:23/", + "base": "about:blank", + "href": "telnet://user:pass@foobar.com:23/", + "origin": "null", + "protocol": "telnet:", + "username": "user", + "password": "pass", + "host": "foobar.com:23", + "hostname": "foobar.com", + "port": "23", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ut2004://10.10.10.10:7777/Index.ut2", + "base": "about:blank", + "href": "ut2004://10.10.10.10:7777/Index.ut2", + "origin": "null", + "protocol": "ut2004:", + "username": "", + "password": "", + "host": "10.10.10.10:7777", + "hostname": "10.10.10.10", + "port": "7777", + "pathname": "/Index.ut2", + "search": "", + "hash": "" + }, + { + "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "base": "about:blank", + "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "origin": "null", + "protocol": "redis:", + "username": "foo", + "password": "bar", + "host": "somehost:6379", + "hostname": "somehost", + "port": "6379", + "pathname": "/0", + "search": "?baz=bam&qux=baz", + "hash": "" + }, + { + "input": "rsync://foo@host:911/sup", + "base": "about:blank", + "href": "rsync://foo@host:911/sup", + "origin": "null", + "protocol": "rsync:", + "username": "foo", + "password": "", + "host": "host:911", + "hostname": "host", + "port": "911", + "pathname": "/sup", + "search": "", + "hash": "" + }, + { + "input": "git://github.com/foo/bar.git", + "base": "about:blank", + "href": "git://github.com/foo/bar.git", + "origin": "null", + "protocol": "git:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar.git", + "search": "", + "hash": "" + }, + { + "input": "irc://myserver.com:6999/channel?passwd", + "base": "about:blank", + "href": "irc://myserver.com:6999/channel?passwd", + "origin": "null", + "protocol": "irc:", + "username": "", + "password": "", + "host": "myserver.com:6999", + "hostname": "myserver.com", + "port": "6999", + "pathname": "/channel", + "search": "?passwd", + "hash": "" + }, + { + "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "base": "about:blank", + "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "origin": "null", + "protocol": "dns:", + "username": "", + "password": "", + "host": "fw.example.org:9999", + "hostname": "fw.example.org", + "port": "9999", + "pathname": "/foo.bar.org", + "search": "?type=TXT", + "hash": "" + }, + { + "input": "ldap://localhost:389/ou=People,o=JNDITutorial", + "base": "about:blank", + "href": "ldap://localhost:389/ou=People,o=JNDITutorial", + "origin": "null", + "protocol": "ldap:", + "username": "", + "password": "", + "host": "localhost:389", + "hostname": "localhost", + "port": "389", + "pathname": "/ou=People,o=JNDITutorial", + "search": "", + "hash": "" + }, + { + "input": "git+https://github.com/foo/bar", + "base": "about:blank", + "href": "git+https://github.com/foo/bar", + "origin": "null", + "protocol": "git+https:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "urn:ietf:rfc:2648", + "base": "about:blank", + "href": "urn:ietf:rfc:2648", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "ietf:rfc:2648", + "search": "", + "hash": "" + }, + { + "input": "tag:joe@example.org,2001:foo/bar", + "base": "about:blank", + "href": "tag:joe@example.org,2001:foo/bar", + "origin": "null", + "protocol": "tag:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "joe@example.org,2001:foo/bar", + "search": "", + "hash": "" + }, + "# percent encoded hosts in non-special-URLs", + { + "input": "non-special://%E2%80%A0/", + "base": "about:blank", + "href": "non-special://%E2%80%A0/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "%E2%80%A0", + "hostname": "%E2%80%A0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://H%4fSt/path", + "base": "about:blank", + "href": "non-special://H%4fSt/path", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "H%4fSt", + "hostname": "H%4fSt", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + "# IPv6 in non-special-URLs", + { + "input": "non-special://[1:2:0:0:5:0:0:0]/", + "base": "about:blank", + "href": "non-special://[1:2:0:0:5::]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2:0:0:5::]", + "hostname": "[1:2:0:0:5::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2:0:0:0:0:0:3]/", + "base": "about:blank", + "href": "non-special://[1:2::3]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]", + "hostname": "[1:2::3]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2::3]:80/", + "base": "about:blank", + "href": "non-special://[1:2::3]:80/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]:80", + "hostname": "[1:2::3]", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[:80/", + "base": "about:blank", + "failure": true + }, + { + "input": "blob:https://example.com:443/", + "base": "about:blank", + "href": "blob:https://example.com:443/", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "https://example.com:443/", + "search": "", + "hash": "" + }, + { + "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "base": "about:blank", + "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf", + "search": "", + "hash": "" + }, + "Invalid IPv4 radix digits", + { + "input": "http://0177.0.0.0189", + "base": "about:blank", + "href": "http://0177.0.0.0189/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0177.0.0.0189", + "hostname": "0177.0.0.0189", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0x7f.0.0.0x7g", + "base": "about:blank", + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0X7F.0.0.0X7G", + "base": "about:blank", + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid IPv4 portion of IPv6 address", + { + "input": "http://[::127.0.0.0.1]", + "base": "about:blank", + "failure": true + }, + "Uncompressed IPv6 addresses with 0", + { + "input": "http://[0:1:0:1:0:1:0:1]", + "base": "about:blank", + "href": "http://[0:1:0:1:0:1:0:1]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[0:1:0:1:0:1:0:1]", + "hostname": "[0:1:0:1:0:1:0:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[1:0:1:0:1:0:1:0]", + "base": "about:blank", + "href": "http://[1:0:1:0:1:0:1:0]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1:0:1:0:1:0:1:0]", + "hostname": "[1:0:1:0:1:0:1:0]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Percent-encoded query and fragment", + { + "input": "http://example.org/test?\u0022", + "base": "about:blank", + "href": "http://example.org/test?%22", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%22", + "hash": "" + }, + { + "input": "http://example.org/test?\u0023", + "base": "about:blank", + "href": "http://example.org/test?#", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "http://example.org/test?\u003C", + "base": "about:blank", + "href": "http://example.org/test?%3C", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3C", + "hash": "" + }, + { + "input": "http://example.org/test?\u003E", + "base": "about:blank", + "href": "http://example.org/test?%3E", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3E", + "hash": "" + }, + { + "input": "http://example.org/test?\u2323", + "base": "about:blank", + "href": "http://example.org/test?%E2%8C%A3", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%E2%8C%A3", + "hash": "" + }, + { + "input": "http://example.org/test?%23%23", + "base": "about:blank", + "href": "http://example.org/test?%23%23", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%23%23", + "hash": "" + }, + { + "input": "http://example.org/test?%GH", + "base": "about:blank", + "href": "http://example.org/test?%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%GH", + "hash": "" + }, + { + "input": "http://example.org/test?a#%EF", + "base": "about:blank", + "href": "http://example.org/test?a#%EF", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%EF" + }, + { + "input": "http://example.org/test?a#%GH", + "base": "about:blank", + "href": "http://example.org/test?a#%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%GH" + }, + "Bad bases", + { + "input": "test-a.html", + "base": "a", + "failure": true + }, + { + "input": "test-a-slash.html", + "base": "a/", + "failure": true + }, + { + "input": "test-a-slash-slash.html", + "base": "a//", + "failure": true + }, + { + "input": "test-a-colon.html", + "base": "a:", + "failure": true + }, + { + "input": "test-a-colon-slash.html", + "base": "a:/", + "href": "a:/test-a-colon-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash.html", + "base": "a://", + "href": "a:///test-a-colon-slash-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-b.html", + "base": "a:b", + "failure": true + }, + { + "input": "test-a-colon-slash-b.html", + "base": "a:/b", + "href": "a:/test-a-colon-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-b.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash-b.html", + "base": "a://b", + "href": "a://b/test-a-colon-slash-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "b", + "hostname": "b", + "port": "", + "pathname": "/test-a-colon-slash-slash-b.html", + "search": "", + "hash": "" + }, + "Null code point in fragment", + { + "input": "http://example.org/test?a#b\u0000c", + "base": "about:blank", + "href": "http://example.org/test?a#bc", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#bc" + } +] diff --git a/netwerk/test/gtest/urltestdata.json b/netwerk/test/gtest/urltestdata.json new file mode 100644 index 0000000000..41cf6725b3 --- /dev/null +++ b/netwerk/test/gtest/urltestdata.json @@ -0,0 +1,9050 @@ +[ + "This file is based on testing/web-platform/tests/url/resources/urltestdata.json (with failing tests removed)", + { + "input": "http://example\t.\norg", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@foo:21/bar;par?b#c", + "base": "http://example.org/foo/bar", + "href": "http://user:pass@foo:21/bar;par?b#c", + "origin": "http://foo:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "foo:21", + "hostname": "foo", + "port": "21", + "pathname": "/bar;par", + "search": "?b", + "hash": "#c" + }, + { + "input": "https://test:@test", + "base": null, + "href": "https://test@test/", + "origin": "https://test", + "protocol": "https:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://:@test", + "base": null, + "href": "https://test/", + "origin": "https://test", + "protocol": "https:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://test:@test/x", + "base": null, + "href": "non-special://test@test/x", + "origin": "null", + "protocol": "non-special:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "non-special://:@test/x", + "base": null, + "href": "non-special://test/x", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "http:foo.com", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "\t :foo.com \n", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com", + "search": "", + "hash": "" + }, + { + "input": " foo.com ", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "a:\t foo.com", + "base": "http://example.org/foo/bar", + "href": "a: foo.com", + "origin": "null", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": " foo.com", + "search": "", + "hash": "" + }, + { + "input": "http://f:21/ b ? d # e ", + "base": "http://example.org/foo/bar", + "href": "http://f:21/%20b%20?%20d%20#%20e", + "origin": "http://f:21", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:21", + "hostname": "f", + "port": "21", + "pathname": "/%20b%20", + "search": "?%20d%20", + "hash": "#%20e" + }, + { + "input": "lolscheme:x x#x x", + "base": null, + "href": "lolscheme:x x#x%20x", + "protocol": "lolscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x x", + "search": "", + "hash": "#x%20x" + }, + { + "input": "http://f:/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:0/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000000000080/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:b/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: /c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:\n/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:fifty-two/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "non-special://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: 21 / b ? d # e ", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": " \t", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": ":foo.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": ":a", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:a", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:a", + "search": "", + "hash": "" + }, + { + "input": ":/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": "#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "#/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#/" + }, + { + "input": "#\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#\\", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#\\" + }, + { + "input": "#;?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#;?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#;?" + }, + { + "input": "?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": ":23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:23", + "search": "", + "hash": "" + }, + { + "input": "/:23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/:23", + "search": "", + "hash": "" + }, + { + "input": "\\x", + "base": "http://example.org/foo/bar", + "href": "http://example.org/x", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "\\\\x\\hello", + "base": "http://example.org/foo/bar", + "href": "http://x/hello", + "origin": "http://x", + "protocol": "http:", + "username": "", + "password": "", + "host": "x", + "hostname": "x", + "port": "", + "pathname": "/hello", + "search": "", + "hash": "" + }, + { + "input": "::", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::", + "search": "", + "hash": "" + }, + { + "input": "::23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::23", + "search": "", + "hash": "" + }, + { + "input": "foo://", + "base": "http://example.org/foo/bar", + "href": "foo://", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@c:29/d", + "base": "http://example.org/foo/bar", + "href": "http://a:b@c:29/d", + "origin": "http://c:29", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "c:29", + "hostname": "c", + "port": "29", + "pathname": "/d", + "search": "", + "hash": "" + }, + { + "input": "http::@c:29", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:@c:29", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:@c:29", + "search": "", + "hash": "" + }, + { + "input": "http://&a:foo(b]c@d:2/", + "base": "http://example.org/foo/bar", + "href": "http://&a:foo(b%5Dc@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "&a", + "password": "foo(b%5Dc", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://::@c@d:2", + "base": "http://example.org/foo/bar", + "href": "http://:%3A%40c@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "", + "password": "%3A%40c", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com:b@d/", + "base": "http://example.org/foo/bar", + "href": "http://foo.com:b@d/", + "origin": "http://d", + "protocol": "http:", + "username": "foo.com", + "password": "b", + "host": "d", + "hostname": "d", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com/\\@", + "base": "http://example.org/foo/bar", + "href": "http://foo.com//@", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "//@", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://foo.com/", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\a\\b:c\\d@foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://a/b:c/d@foo.com/", + "origin": "http://a", + "protocol": "http:", + "username": "", + "password": "", + "host": "a", + "hostname": "a", + "port": "", + "pathname": "/b:c/d@foo.com/", + "search": "", + "hash": "" + }, + { + "input": "foo:/", + "base": "http://example.org/foo/bar", + "href": "foo:/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "foo:/bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo:/bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo://///////", + "base": "http://example.org/foo/bar", + "href": "foo://///////", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////", + "search": "", + "hash": "" + }, + { + "input": "foo://///////bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo://///////bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo:////://///", + "base": "http://example.org/foo/bar", + "href": "foo:////://///", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//://///", + "search": "", + "hash": "" + }, + { + "input": "c:/foo", + "base": "http://example.org/foo/bar", + "href": "c:/foo", + "origin": "null", + "protocol": "c:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "//foo/bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + { + "input": "http://foo/path;a??e#f#g", + "base": "http://example.org/foo/bar", + "href": "http://foo/path;a??e#f#g", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/path;a", + "search": "??e", + "hash": "#f#g" + }, + { + "input": "http://foo/abcd?efgh?ijkl", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd?efgh?ijkl", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "?efgh?ijkl", + "hash": "" + }, + { + "input": "http://foo/abcd#foo?bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd#foo?bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "", + "hash": "#foo?bar" + }, + { + "input": "[61:24:74]:98", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:24:74]:98", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:24:74]:98", + "search": "", + "hash": "" + }, + { + "input": "http:[61:27]/:foo", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:27]/:foo", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:27]/:foo", + "search": "", + "hash": "" + }, + { + "input": "http://[1::2]:3:4", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]:80", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://[2001::1]", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[::127.0.0.1]", + "base": "http://example.org/foo/bar", + "href": "http://[::7f00:1]/", + "origin": "http://[::7f00:1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::7f00:1]", + "hostname": "[::7f00:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[::127.0.0.1.]", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://[0:0:0:0:0:0:13.1.68.3]", + "base": "http://example.org/foo/bar", + "href": "http://[::d01:4403]/", + "origin": "http://[::d01:4403]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::d01:4403]", + "hostname": "[::d01:4403]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[2001::1]:80", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": "http://example.org/foo/bar", + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file://example:1/", + "base": null, + "failure": true + }, + { + "input": "file://example:test/", + "base": null, + "failure": true + }, + { + "input": "file://example%/", + "base": null, + "failure": true + }, + { + "input": "file://[example]/", + "base": null, + "failure": true + }, + { + "input": "ftps:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher:/example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": "http://example.org/foo/bar", + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "http:example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher:example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": "http://example.org/foo/bar", + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "/a/b/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/b/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/b/c", + "search": "", + "hash": "" + }, + { + "input": "/a/ /c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%20/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%20/c", + "search": "", + "hash": "" + }, + { + "input": "/a%2fc", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a%2fc", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a%2fc", + "search": "", + "hash": "" + }, + { + "input": "/a/%2f/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%2f/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%2f/c", + "search": "", + "hash": "" + }, + { + "input": "#β", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#%CE%B2", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#%CE%B2" + }, + { + "input": "data:text/html,test#test", + "base": "http://example.org/foo/bar", + "href": "data:text/html,test#test", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "text/html,test", + "search": "", + "hash": "#test" + }, + { + "input": "tel:1234567890", + "base": "http://example.org/foo/bar", + "href": "tel:1234567890", + "origin": "null", + "protocol": "tel:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "1234567890", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html", + { + "input": "file:c:\\foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:/foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": " File:c|////foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:////foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:////foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": "C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/C|\\foo\\bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "\\\\server\\file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "/\\server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "file:///foo/bar.txt", + "base": "file:///tmp/mock/path", + "href": "file:///foo/bar.txt", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo/bar.txt", + "search": "", + "hash": "" + }, + { + "input": "file:///home/me", + "base": "file:///tmp/mock/path", + "href": "file:///home/me", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/home/me", + "search": "", + "hash": "" + }, + { + "input": "//", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "file://test", + "base": "file:///tmp/mock/path", + "href": "file://test/", + "protocol": "file:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + { + "input": "file:test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js", + { + "input": "http://example.com/././foo", + "base": null, + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/./.foo", + "base": null, + "href": "http://example.com/.foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/.foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/.", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/./", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/..", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/..bar", + "base": null, + "href": "http://example.com/foo/..bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/..bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton", + "base": null, + "href": "http://example.com/foo/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton/../../a", + "base": null, + "href": "http://example.com/a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../..", + "base": null, + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../../ton", + "base": null, + "href": "http://example.com/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e%2", + "base": null, + "href": "http://example.com/foo/%2e%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/%2e%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar", + "base": null, + "href": "http://example.com/%2e.bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%2e.bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com////../..", + "base": null, + "href": "http://example.com//", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//../..", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//..", + "base": null, + "href": "http://example.com/foo/bar/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/bar/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo", + "base": null, + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%20foo", + "base": null, + "href": "http://example.com/%20foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%20foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%", + "base": null, + "href": "http://example.com/foo%", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2", + "base": null, + "href": "http://example.com/foo%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2zbar", + "base": null, + "href": "http://example.com/foo%2zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2©zbar", + "base": null, + "href": "http://example.com/foo%2%C3%82%C2%A9zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2%C3%82%C2%A9zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%41%7a", + "base": null, + "href": "http://example.com/foo%41%7a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%41%7a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\t\u0091%91", + "base": null, + "href": "http://example.com/foo%C2%91%91", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%C2%91%91", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%00%51", + "base": null, + "href": "http://example.com/foo%00%51", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%00%51", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/(%28:%3A%29)", + "base": null, + "href": "http://example.com/(%28:%3A%29)", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/(%28:%3A%29)", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%3A%3a%3C%3c", + "base": null, + "href": "http://example.com/%3A%3a%3C%3c", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%3A%3a%3C%3c", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\tbar", + "base": null, + "href": "http://example.com/foobar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foobar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com\\\\foo\\\\bar", + "base": null, + "href": "http://example.com//foo//bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//foo//bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "base": null, + "href": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%7Ffp3%3Eju%3Dduvgw%3Dd", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/@asdf%40", + "base": null, + "href": "http://example.com/@asdf%40", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/@asdf%40", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/你好你好", + "base": null, + "href": "http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/‥/foo", + "base": null, + "href": "http://example.com/%E2%80%A5/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%A5/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com//foo", + "base": null, + "href": "http://example.com/%EF%BB%BF/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%EF%BB%BF/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com//foo//bar", + "base": null, + "href": "http://example.com/%E2%80%AE/foo/%E2%80%AD/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%AE/foo/%E2%80%AD/bar", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js", + { + "input": "http://www.google.com/foo?bar=baz#", + "base": null, + "href": "http://www.google.com/foo?bar=baz#", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "" + }, + { + "input": "http://www.google.com/foo?bar=baz# »", + "base": null, + "href": "http://www.google.com/foo?bar=baz#%20%C2%BB", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "#%20%C2%BB" + }, + { + "input": "data:test# »", + "base": null, + "href": "data:test#%20%C2%BB", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "#%20%C2%BB" + }, + { + "input": "http://www.google.com", + "base": null, + "href": "http://www.google.com/", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.0x00A80001", + "base": null, + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo%2Ehtml", + "base": null, + "href": "http://www/foo%2Ehtml", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo%2Ehtml", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo/%2E/html", + "base": null, + "href": "http://www/foo/html", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo/html", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@/", + "base": null, + "failure": true + }, + { + "input": "http://%25DOMAIN:foobar@foodomain.com/", + "base": null, + "href": "http://%25DOMAIN:foobar@foodomain.com/", + "origin": "http://foodomain.com", + "protocol": "http:", + "username": "%25DOMAIN", + "password": "foobar", + "host": "foodomain.com", + "hostname": "foodomain.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\www.google.com\\foo", + "base": null, + "href": "http://www.google.com/foo", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://foo:80/", + "base": null, + "href": "http://foo/", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:81/", + "base": null, + "href": "http://foo:81/", + "origin": "http://foo:81", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "httpa://foo:80/", + "base": null, + "href": "httpa://foo:80/", + "origin": "null", + "protocol": "httpa:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:-80/", + "base": null, + "failure": true + }, + { + "input": "https://foo:443/", + "base": null, + "href": "https://foo/", + "origin": "https://foo", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://foo:80/", + "base": null, + "href": "https://foo:80/", + "origin": "https://foo:80", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:21/", + "base": null, + "href": "ftp://foo/", + "origin": "ftp://foo", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:80/", + "base": null, + "href": "ftp://foo:80/", + "origin": "ftp://foo:80", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:70/", + "base": null, + "href": "gopher://foo:70/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo:70", + "hostname": "foo", + "port": "70", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:443/", + "base": null, + "href": "gopher://foo:443/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:80/", + "base": null, + "href": "ws://foo/", + "origin": "ws://foo", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:81/", + "base": null, + "href": "ws://foo:81/", + "origin": "ws://foo:81", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:443/", + "base": null, + "href": "ws://foo:443/", + "origin": "ws://foo:443", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:815/", + "base": null, + "href": "ws://foo:815/", + "origin": "ws://foo:815", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:80/", + "base": null, + "href": "wss://foo:80/", + "origin": "wss://foo:80", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:81/", + "base": null, + "href": "wss://foo:81/", + "origin": "wss://foo:81", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:443/", + "base": null, + "href": "wss://foo/", + "origin": "wss://foo", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:815/", + "base": null, + "href": "wss://foo:815/", + "origin": "wss://foo:815", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": null, + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": null, + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": null, + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": null, + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:/example.com/", + "base": null, + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": null, + "href": "gopher:/example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": null, + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": null, + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": null, + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": null, + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": null, + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": null, + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": null, + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": null, + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": null, + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": null, + "href": "gopher:example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": null, + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": null, + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": null, + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": null, + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": null, + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "http://@www.example.com", + "base": null, + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@www.example.com", + "base": null, + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://@pple.com", + "base": null, + "href": "http://pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://:b@www.example.com", + "base": null, + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/:@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://user@/www.example.com", + "base": null, + "failure": true + }, + { + "input": "http:@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:/@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://@/www.example.com", + "base": null, + "failure": true + }, + { + "input": "https:@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:a:b@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:/a:b@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://a:b@/www.example.com", + "base": null, + "failure": true + }, + { + "input": "http::@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://a:@www.example.com", + "base": null, + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www.@pple.com", + "base": null, + "href": "http://www.@pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "www.", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:@:www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:/@:www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://@:www.example.com", + "base": null, + "failure": true + }, + { + "input": "http://:@www.example.com", + "base": null, + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Others", + { + "input": "/", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": ".", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "./test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../aaa/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/aaa/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/aaa/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "中/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/%E4%B8%AD/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/%E4%B8%AD/test.txt", + "search": "", + "hash": "" + }, + { + "input": "http://www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "//www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:...", + "base": "http://www.example.com/test", + "href": "file:///...", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/...", + "search": "", + "hash": "" + }, + { + "input": "file:..", + "base": "http://www.example.com/test", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:a", + "base": "http://www.example.com/test", + "href": "file:///a", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html", + "Basic canonicalization, uppercase should be converted to lowercase", + { + "input": "http://ExAmPlE.CoM", + "base": "http://other.com/", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example example.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://Goo%20 goo%7C|.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[:]", + "base": "http://other.com/", + "failure": true + }, + "U+3000 is mapped to U+0020 (space) which is disallowed", + { + "input": "http://GOO\u00a0\u3000goo.com", + "base": "http://other.com/", + "failure": true + }, + "Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored", + { + "input": "http://GOO\u200b\u2060\ufeffgoo.com", + "base": "http://other.com/", + "href": "http://googoo.com/", + "origin": "http://googoo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "googoo.com", + "hostname": "googoo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Leading and trailing C0 control or space", + { + "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ", + "base": null, + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)", + { + "input": "http://www.foo。bar.com", + "base": "http://other.com/", + "href": "http://www.foo.bar.com/", + "origin": "http://www.foo.bar.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.foo.bar.com", + "hostname": "www.foo.bar.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0", + { + "input": "http://\ufdd0zyx.com", + "base": "http://other.com/", + "failure": true + }, + "This is the same as previous but escaped", + { + "input": "http://%ef%b7%90zyx.com", + "base": "http://other.com/", + "failure": true + }, + "U+FFFD", + { + "input": "https://\ufffd", + "base": null, + "failure": true + }, + { + "input": "https://%EF%BF%BD", + "base": null, + "failure": true + }, + { + "input": "https://x/\ufffd?\ufffd#\ufffd", + "base": null, + "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD", + "origin": "https://x", + "protocol": "https:", + "username": "", + "password": "", + "host": "x", + "hostname": "x", + "port": "", + "pathname": "/%EF%BF%BD", + "search": "?%EF%BF%BD", + "hash": "#%EF%BF%BD" + }, + "Domain is ASCII, but a label is invalid IDNA", + { + "input": "http://a.b.c.xn--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://10.0.0.xn--pokxncvks", + "base": null, + "failure": true + }, + "IDNA labels should be matched case-insensitively", + { + "input": "http://a.b.c.XN--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://a.b.c.Xn--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://10.0.0.XN--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://10.0.0.xN--pokxncvks", + "base": null, + "failure": true + }, + "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.", + { + "input": "http://Go.com", + "base": "http://other.com/", + "href": "http://go.com/", + "origin": "http://go.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "go.com", + "hostname": "go.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257", + { + "input": "http://%41.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com", + "base": "http://other.com/", + "failure": true + }, + "...%00 in fullwidth should fail (also as escaped UTF-8 input)", + { + "input": "http://%00.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com", + "base": "http://other.com/", + "failure": true + }, + "Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN", + { + "input": "http://你好你好", + "base": "http://other.com/", + "href": "http://xn--6qqa088eba/", + "origin": "http://xn--6qqa088eba", + "protocol": "http:", + "username": "", + "password": "", + "host": "xn--6qqa088eba", + "hostname": "xn--6qqa088eba", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://faß.ExAmPlE/", + "base": null, + "href": "https://xn--fa-hia.example/", + "origin": "https://xn--fa-hia.example", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--fa-hia.example", + "hostname": "xn--fa-hia.example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://faß.ExAmPlE/", + "base": null, + "href": "sc://fa%C3%9F.ExAmPlE/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "fa%C3%9F.ExAmPlE", + "hostname": "fa%C3%9F.ExAmPlE", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191", + { + "input": "http://%zz%66%a.com", + "base": "http://other.com/", + "failure": true + }, + "If we get an invalid character that has been escaped.", + { + "input": "http://%25", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://hello%00", + "base": "http://other.com/", + "failure": true + }, + "Escaped numbers should be treated like IP addresses if they are.", + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.0.257", + "base": "http://other.com/", + "failure": true + }, + "Invalid escaping in hosts causes failure", + { + "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01", + "base": "http://other.com/", + "failure": true + }, + "A space in a host causes failure", + { + "input": "http://192.168.0.1 hello", + "base": "http://other.com/", + "failure": true + }, + { + "input": "https://x x:12", + "base": null, + "failure": true + }, + "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP", + { + "input": "http://0Xc0.0250.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Domains with empty labels", + { + "input": "http://./", + "base": null, + "href": "http://./", + "origin": "http://.", + "protocol": "http:", + "username": "", + "password": "", + "host": ".", + "hostname": ".", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://../", + "base": null, + "href": "http://../", + "origin": "http://..", + "protocol": "http:", + "username": "", + "password": "", + "host": "..", + "hostname": "..", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Non-special domains with empty labels", + { + "input": "h://.", + "base": null, + "href": "h://.", + "origin": "null", + "protocol": "h:", + "username": "", + "password": "", + "host": ".", + "hostname": ".", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + "Broken IPv6", + { + "input": "http://[www.google.com]/", + "base": null, + "failure": true + }, + { + "input": "http://[google.com]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.4x]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::.1.2]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::.1]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::%31]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%5B::1]", + "base": "http://other.com/", + "failure": true + }, + "Misc Unicode", + { + "input": "http://foo:💩@example.com/bar", + "base": "http://other.com/", + "href": "http://foo:%F0%9F%92%A9@example.com/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "foo", + "password": "%F0%9F%92%A9", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + "# resolving a fragment against any scheme succeeds", + { + "input": "#", + "base": "test:test", + "href": "test:test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "" + }, + { + "input": "#x", + "base": "mailto:x@x.com", + "href": "mailto:x@x.com#x", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x@x.com", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "data:,", + "href": "data:,#x", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ",", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "about:blank", + "href": "about:blank#x", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blank", + "search": "", + "hash": "#x" + }, + { + "input": "#x:y", + "base": "about:blank", + "href": "about:blank#x:y", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blank", + "search": "", + "hash": "#x:y" + }, + { + "input": "#", + "base": "test:test?test", + "href": "test:test?test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "?test", + "hash": "" + }, + "# multiple @ in authority state", + { + "input": "https://@test@test@example:800/", + "base": "http://doesnotmatter/", + "href": "https://%40test%40test@example:800/", + "origin": "https://example:800", + "protocol": "https:", + "username": "%40test%40test", + "password": "", + "host": "example:800", + "hostname": "example", + "port": "800", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://@@@example", + "base": "http://doesnotmatter/", + "href": "https://%40%40@example/", + "origin": "https://example", + "protocol": "https:", + "username": "%40%40", + "password": "", + "host": "example", + "hostname": "example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "non-az-09 characters", + { + "input": "http://`{}:`{}@h/`{}?`{}", + "base": "http://doesnotmatter/", + "href": "http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}", + "origin": "http://h", + "protocol": "http:", + "username": "%60%7B%7D", + "password": "%60%7B%7D", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/%60%7B%7D", + "search": "?`{}", + "hash": "" + }, + "byte is ' and url is special", + { + "input": "http://host/?'", + "base": null, + "href": "http://host/?%27", + "origin": "http://host", + "protocol": "http:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/", + "search": "?%27", + "hash": "" + }, + { + "input": "notspecial://host/?'", + "base": null, + "href": "notspecial://host/?'", + "origin": "null", + "protocol": "notspecial:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/", + "search": "?'", + "hash": "" + }, + "# Credentials in base", + { + "input": "/some/path", + "base": "http://user@example.org/smth", + "href": "http://user@example.org/some/path", + "origin": "http://example.org", + "protocol": "http:", + "username": "user", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/smth", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/smth", + "search": "", + "hash": "" + }, + { + "input": "/some/path", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/some/path", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + "# a set of tests designed by zcorpan for relative URLs with unknown schemes", + { + "input": "i", + "base": "sc:sd", + "failure": true + }, + { + "input": "i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "i", + "base": "sc:/pa/pa", + "href": "sc:/pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc:///pa/pa", + "href": "sc:///pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "?i", + "base": "sc:sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc://ho/pa", + "href": "sc://ho/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "#i", + "base": "sc:sd", + "href": "sc:sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:sd/sd", + "href": "sc:sd/sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd/sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc://ho/pa", + "href": "sc://ho/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + "# make sure that relative URL logic works on known typically non-relative schemes too", + { + "input": "data:/../", + "base": null, + "href": "data:/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/../", + "base": null, + "href": "javascript:/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/../", + "base": null, + "href": "mailto:/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# unknown schemes and their hosts", + { + "input": "sc://ñ.test/", + "base": null, + "href": "sc://%C3%B1.test/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1.test", + "hostname": "%C3%B1.test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://%/", + "base": null, + "href": "sc://%/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%", + "hostname": "%", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://@/", + "base": null, + "failure": true + }, + { + "input": "sc://te@s:t@/", + "base": null, + "failure": true + }, + { + "input": "sc://:/", + "base": null, + "failure": true + }, + { + "input": "sc://:12/", + "base": null, + "failure": true + }, + { + "input": "x", + "base": "sc://ñ", + "href": "sc://%C3%B1/x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + "# unknown schemes and backslashes", + { + "input": "sc:\\../", + "base": null, + "href": "sc:\\../", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\../", + "search": "", + "hash": "" + }, + "# unknown scheme with path looking like a password", + { + "input": "sc::a@example.net", + "base": null, + "href": "sc::a@example.net", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ":a@example.net", + "search": "", + "hash": "" + }, + "# unknown scheme with bogus percent-encoding", + { + "input": "wow:%NBD", + "base": null, + "href": "wow:%NBD", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%NBD", + "search": "", + "hash": "" + }, + { + "input": "wow:%1G", + "base": null, + "href": "wow:%1G", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%1G", + "search": "", + "hash": "" + }, + "# unknown scheme with non-URL characters", + { + "input": "wow:\uFFFF", + "base": null, + "href": "wow:%EF%BF%BF", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%EF%BF%BF", + "search": "", + "hash": "" + }, + "Forbidden host code points", + { + "input": "sc://a\u0000b/", + "base": null, + "failure": true + }, + { + "input": "sc://a b/", + "base": null, + "failure": true + }, + { + "input": "sc://a<b", + "base": null, + "failure": true + }, + { + "input": "sc://a>b", + "base": null, + "failure": true + }, + { + "input": "sc://a[b/", + "base": null, + "failure": true + }, + { + "input": "sc://a\\b/", + "base": null, + "failure": true + }, + { + "input": "sc://a]b/", + "base": null, + "failure": true + }, + { + "input": "sc://a^b", + "base": null, + "failure": true + }, + { + "input": "sc://a|b/", + "base": null, + "failure": true + }, + "Forbidden host codepoints: tabs and newlines are removed during preprocessing", + { + "input": "foo://ho\u0009st/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://host/", + "password": "", + "pathname": "/", + "port": "", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "foo://ho\u000Ast/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://host/", + "password": "", + "pathname": "/", + "port": "", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "foo://ho\u000Dst/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://host/", + "password": "", + "pathname": "/", + "port": "", + "protocol": "foo:", + "search": "", + "username": "" + }, + "Forbidden domain code-points", + { + "input": "http://a\u0000b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0001b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0002b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0003b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0004b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0005b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0006b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0007b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0008b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Bb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Cb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Eb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Fb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0010b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0011b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0012b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0013b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0014b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0015b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0016b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0017b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0018b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0019b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Ab/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Bb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Cb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Db/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Eb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Fb/", + "base": null, + "failure": true + }, + { + "input": "http://a b/", + "base": null, + "failure": true + }, + { + "input": "http://a%b/", + "base": null, + "failure": true + }, + { + "input": "http://a<b", + "base": null, + "failure": true + }, + { + "input": "http://a>b", + "base": null, + "failure": true + }, + { + "input": "http://a[b/", + "base": null, + "failure": true + }, + { + "input": "http://a]b/", + "base": null, + "failure": true + }, + { + "input": "http://a^b", + "base": null, + "failure": true + }, + { + "input": "http://a|b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u007Fb/", + "base": null, + "failure": true + }, + "Forbidden domain codepoints: tabs and newlines are removed during preprocessing", + { + "input": "http://ho\u0009st/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "http://host/", + "password": "", + "pathname": "/", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + }, + { + "input": "http://ho\u000Ast/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "http://host/", + "password": "", + "pathname": "/", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + }, + { + "input": "http://ho\u000Dst/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "http://host/", + "password": "", + "pathname": "/", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + }, + "Encoded forbidden domain codepoints in special URLs", + { + "input": "http://ho%00st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%01st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%02st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%03st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%04st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%05st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%06st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%07st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%08st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%09st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Ast/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Bst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Dst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Est/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%10st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%11st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%12st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%13st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%14st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%15st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%16st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%17st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%18st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%19st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Ast/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Bst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Dst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Est/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%20st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%23st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%25st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%2Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Ast/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Est/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%40st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%5Bst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%5Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%5Dst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%7Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%7Fst/", + "base": null, + "failure": true + }, + "Allowed host/domain code points", + { + "input": "sc://\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000B\u000C\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u007F!\"$%&'()*+,-.;=_`{}~/", + "base": null, + "href": "sc://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~", + "hostname": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Hosts and percent-encoding", + { + "input": "ftp://example.com%80/", + "base": null, + "failure": true + }, + { + "input": "ftp://example.com%A0/", + "base": null, + "failure": true + }, + { + "input": "https://example.com%80/", + "base": null, + "failure": true + }, + { + "input": "https://example.com%A0/", + "base": null, + "failure": true + }, + { + "input": "ftp://%e2%98%83", + "base": null, + "href": "ftp://xn--n3h/", + "origin": "ftp://xn--n3h", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://%e2%98%83", + "base": null, + "href": "https://xn--n3h/", + "origin": "https://xn--n3h", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# tests from jsdom/whatwg-url designed for code coverage", + { + "input": "http://127.0.0.1:10100/relative_import.html", + "base": null, + "href": "http://127.0.0.1:10100/relative_import.html", + "origin": "http://127.0.0.1:10100", + "protocol": "http:", + "username": "", + "password": "", + "host": "127.0.0.1:10100", + "hostname": "127.0.0.1", + "port": "10100", + "pathname": "/relative_import.html", + "search": "", + "hash": "" + }, + { + "input": "http://facebook.com/?foo=%7B%22abc%22", + "base": null, + "href": "http://facebook.com/?foo=%7B%22abc%22", + "origin": "http://facebook.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "facebook.com", + "hostname": "facebook.com", + "port": "", + "pathname": "/", + "search": "?foo=%7B%22abc%22", + "hash": "" + }, + { + "input": "https://localhost:3000/jqueryui@1.2.3", + "base": null, + "href": "https://localhost:3000/jqueryui@1.2.3", + "origin": "https://localhost:3000", + "protocol": "https:", + "username": "", + "password": "", + "host": "localhost:3000", + "hostname": "localhost", + "port": "3000", + "pathname": "/jqueryui@1.2.3", + "search": "", + "hash": "" + }, + "# tab/LF/CR", + { + "input": "h\tt\nt\rp://h\to\ns\rt:9\t0\n0\r0/p\ta\nt\rh?q\tu\ne\rry#f\tr\na\rg", + "base": null, + "href": "http://host:9000/path?query#frag", + "origin": "http://host:9000", + "protocol": "http:", + "username": "", + "password": "", + "host": "host:9000", + "hostname": "host", + "port": "9000", + "pathname": "/path", + "search": "?query", + "hash": "#frag" + }, + "# Stringification of URL.searchParams", + { + "input": "?a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "?a=b&c=d", + "searchParams": "a=b&c=d", + "hash": "" + }, + { + "input": "??a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar??a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "??a=b&c=d", + "searchParams": "%3Fa=b&c=d", + "hash": "" + }, + "# Scheme only", + { + "input": "http:", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "searchParams": "", + "hash": "" + }, + { + "input": "http:", + "base": "https://example.org/foo/bar", + "failure": true + }, + { + "input": "sc:", + "base": "https://example.org/foo/bar", + "href": "sc:", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "searchParams": "", + "hash": "" + }, + "# Percent encoding of fragments", + { + "input": "http://foo.bar/baz?qux#foo\bbar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%08bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%08bar" + }, + { + "input": "http://foo.bar/baz?qux#foo\"bar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%22bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%22bar" + }, + { + "input": "http://foo.bar/baz?qux#foo<bar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%3Cbar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%3Cbar" + }, + { + "input": "http://foo.bar/baz?qux#foo>bar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%3Ebar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%3Ebar" + }, + { + "input": "http://foo.bar/baz?qux#foo`bar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%60bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%60bar" + }, + "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)", + { + "input": "http://1.2.3.4/", + "base": "http://other.com/", + "href": "http://1.2.3.4/", + "origin": "http://1.2.3.4", + "protocol": "http:", + "username": "", + "password": "", + "host": "1.2.3.4", + "hostname": "1.2.3.4", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://1.2.3.4./", + "base": "http://other.com/", + "href": "http://1.2.3.4/", + "origin": "http://1.2.3.4", + "protocol": "http:", + "username": "", + "password": "", + "host": "1.2.3.4", + "hostname": "1.2.3.4", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257", + "base": "http://other.com/", + "href": "http://192.168.1.1/", + "origin": "http://192.168.1.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.1.1", + "hostname": "192.168.1.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257.", + "base": "http://other.com/", + "href": "http://192.168.1.1/", + "origin": "http://192.168.1.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.1.1", + "hostname": "192.168.1.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257.com", + "base": "http://other.com/", + "href": "http://192.168.257.com/", + "origin": "http://192.168.257.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.257.com", + "hostname": "192.168.257.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256", + "base": "http://other.com/", + "href": "http://0.0.1.0/", + "origin": "http://0.0.1.0", + "protocol": "http:", + "username": "", + "password": "", + "host": "0.0.1.0", + "hostname": "0.0.1.0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256.com", + "base": "http://other.com/", + "href": "http://256.com/", + "origin": "http://256.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "256.com", + "hostname": "256.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999", + "base": "http://other.com/", + "href": "http://59.154.201.255/", + "origin": "http://59.154.201.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "59.154.201.255", + "hostname": "59.154.201.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999.", + "base": "http://other.com/", + "href": "http://59.154.201.255/", + "origin": "http://59.154.201.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "59.154.201.255", + "hostname": "59.154.201.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "", + "failure": true + }, + { + "input": "http://999999999.com", + "base": "http://other.com/", + "href": "http://999999999.com/", + "origin": "http://999999999.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "999999999.com", + "hostname": "999999999.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://10000000000", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://10000000000.com", + "base": "http://other.com/", + "href": "http://10000000000.com/", + "origin": "http://10000000000.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "10000000000.com", + "hostname": "10000000000.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967295", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967296", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://0xffffffff", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0xffffffff1", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256", + "base": "http://other.com/", + "failure": true + }, + "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)", + { + "input": "https://0x100000000/test", + "base": null, + "failure": true + }, + { + "input": "https://256.0.0.1/test", + "base": null, + "failure": true + }, + "# file URLs containing percent-encoded Windows drive letters (shouldn't work)", + { + "input": "file:///C%3A/", + "base": null, + "href": "file:///C%3A/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%3A/", + "search": "", + "hash": "" + }, + { + "input": "file:///C%7C/", + "base": null, + "href": "file:///C%7C/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%7C/", + "search": "", + "hash": "" + }, + { + "input": "file://%43%3A", + "base": null, + "failure": true + }, + { + "input": "file://%43%7C", + "base": null, + "failure": true + }, + { + "input": "file://%43|", + "base": null, + "failure": true + }, + { + "input": "file://C%7C", + "base": null, + "failure": true + }, + { + "input": "file://%43%7C/", + "base": null, + "failure": true + }, + { + "input": "https://%43%7C/", + "base": null, + "failure": true + }, + { + "input": "asdf://%43|/", + "base": null, + "failure": true + }, + { + "input": "asdf://%43%7C/", + "base": null, + "href": "asdf://%43%7C/", + "origin": "null", + "protocol": "asdf:", + "username": "", + "password": "", + "host": "%43%7C", + "hostname": "%43%7C", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)", + { + "input": "pix/submit.gif", + "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html", + "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///C:/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# More file URL tests by zcorpan and annevk", + { + "input": "/", + "base": "file:///C:/a/b", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "file://h/C:/a/b", + "href": "file://h/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "file://h/a/b", + "href": "file://h/", + "protocol": "file:", + "username": "", + "password": "", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "//d:", + "base": "file:///C:/a/b", + "href": "file:///d:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:", + "search": "", + "hash": "" + }, + { + "input": "//d:/..", + "base": "file:///C:/a/b", + "href": "file:///d:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///ab:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///1:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "file:", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "file:?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + { + "input": "file:#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + "# File URLs and many (back)slashes", + { + "input": "file:\\\\//", + "base": null, + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\\\\\", + "base": null, + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\\\\\?fox", + "base": null, + "href": "file:////?fox", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "?fox", + "hash": "" + }, + { + "input": "file:\\\\\\\\#guppy", + "base": null, + "href": "file:////#guppy", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "#guppy" + }, + { + "input": "file://spider///", + "base": null, + "href": "file://spider///", + "protocol": "file:", + "username": "", + "password": "", + "host": "spider", + "hostname": "spider", + "port": "", + "pathname": "///", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\localhost//", + "base": null, + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "file:///localhost//cat", + "base": null, + "href": "file:///localhost//cat", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/localhost//cat", + "search": "", + "hash": "" + }, + { + "input": "file://\\/localhost//cat", + "base": null, + "href": "file:////localhost//cat", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//localhost//cat", + "search": "", + "hash": "" + }, + { + "input": "file://localhost//a//../..//", + "base": null, + "href": "file://///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///", + "search": "", + "hash": "" + }, + { + "input": "/////mouse", + "base": "file:///elephant", + "href": "file://///mouse", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///mouse", + "search": "", + "hash": "" + }, + { + "input": "\\//pig", + "base": "file://lion/", + "href": "file:///pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pig", + "search": "", + "hash": "" + }, + { + "input": "\\/localhost//pig", + "base": "file://lion/", + "href": "file:////pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//pig", + "search": "", + "hash": "" + }, + { + "input": "//localhost//pig", + "base": "file://lion/", + "href": "file:////pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//pig", + "search": "", + "hash": "" + }, + { + "input": "/..//localhost//pig", + "base": "file://lion/", + "href": "file://lion//localhost//pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "lion", + "hostname": "lion", + "port": "", + "pathname": "//localhost//pig", + "search": "", + "hash": "" + }, + { + "input": "file://", + "base": "file://ape/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# File URLs with non-empty hosts", + { + "input": "/rooibos", + "base": "file://tea/", + "href": "file://tea/rooibos", + "protocol": "file:", + "username": "", + "password": "", + "host": "tea", + "hostname": "tea", + "port": "", + "pathname": "/rooibos", + "search": "", + "hash": "" + }, + { + "input": "/?chai", + "base": "file://tea/", + "href": "file://tea/?chai", + "protocol": "file:", + "username": "", + "password": "", + "host": "tea", + "hostname": "tea", + "port": "", + "pathname": "/", + "search": "?chai", + "hash": "" + }, + "# Windows drive letter handling with the 'file:' base URL", + { + "input": "C|", + "base": "file://host/dir/file", + "href": "file://host/C:", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|", + "base": "file://host/D:/dir1/dir2/file", + "href": "file://host/C:", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|#", + "base": "file://host/dir/file", + "href": "file://host/C:#", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|?", + "base": "file://host/dir/file", + "href": "file://host/C:?", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|/", + "base": "file://host/dir/file", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\n/", + "base": "file://host/dir/file", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\\", + "base": "file://host/dir/file", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C", + "base": "file://host/dir/file", + "href": "file://host/dir/C", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C", + "search": "", + "hash": "" + }, + { + "input": "C|a", + "base": "file://host/dir/file", + "href": "file://host/dir/C|a", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C|a", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk in the file slash state", + { + "input": "/c:/foo/bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/c|/foo/bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "file:\\c:\\foo\\bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/c:/foo/bar", + "base": "file://host/path", + "href": "file://host/c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + "# Do not drop the host in the presence of a drive letter", + { + "input": "file://example.net/C:/", + "base": null, + "href": "file://example.net/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "example.net", + "hostname": "example.net", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://1.2.3.4/C:/", + "base": null, + "href": "file://1.2.3.4/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "1.2.3.4", + "hostname": "1.2.3.4", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://[1::8]/C:/", + "base": null, + "href": "file://[1::8]/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "[1::8]", + "hostname": "[1::8]", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Copy the host from the base URL in the following cases", + { + "input": "C|/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "/C:/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file:C:/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file:/C:/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Copy the empty host from the input in the following cases", + { + "input": "//C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "///C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file:///C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk (no host)", + { + "input": "file:/C|/", + "base": null, + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://C|/", + "base": null, + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# file URLs without base URL by Rimas Misevičius", + { + "input": "file:", + "base": null, + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:?q=v", + "base": null, + "href": "file:///?q=v", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "?q=v", + "hash": "" + }, + { + "input": "file:#frag", + "base": null, + "href": "file:///#frag", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "#frag" + }, + "# file: drive letter cases from https://crbug.com/1078698", + { + "input": "file:///Y:", + "base": null, + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "file:///Y:/", + "base": null, + "href": "file:///Y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y", + "base": null, + "href": "file:///Y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y:", + "base": null, + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\Y:", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "# file: drive letter cases from https://crbug.com/1078698 but lowercased", + { + "input": "file:///y:", + "base": null, + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "file:///y:/", + "base": null, + "href": "file:///y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./y", + "base": null, + "href": "file:///y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y", + "search": "", + "hash": "" + }, + { + "input": "file:///./y:", + "base": null, + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\y:", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "# Additional file URL tests for (https://github.com/whatwg/url/issues/405)", + { + "input": "file://localhost//a//../..//foo", + "base": null, + "href": "file://///foo", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///foo", + "search": "", + "hash": "" + }, + { + "input": "file://localhost////foo", + "base": null, + "href": "file://////foo", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "////foo", + "search": "", + "hash": "" + }, + { + "input": "file:////foo", + "base": null, + "href": "file:////foo", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//foo", + "search": "", + "hash": "" + }, + { + "input": "file:///one/two", + "base": "file:///", + "href": "file:///one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/one/two", + "search": "", + "hash": "" + }, + { + "input": "file:////one/two", + "base": "file:///", + "href": "file:////one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//one/two", + "search": "", + "hash": "" + }, + { + "input": "//one/two", + "base": "file:///", + "href": "file://one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "one", + "hostname": "one", + "port": "", + "pathname": "/two", + "search": "", + "hash": "" + }, + { + "input": "///one/two", + "base": "file:///", + "href": "file:///one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/one/two", + "search": "", + "hash": "" + }, + { + "input": "////one/two", + "base": "file:///", + "href": "file:////one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//one/two", + "search": "", + "hash": "" + }, + { + "input": "file:///.//", + "base": "file:////", + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + "File URL tests for https://github.com/whatwg/url/issues/549", + { + "input": "file:.//p", + "base": null, + "href": "file:////p", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//p", + "search": "", + "hash": "" + }, + { + "input": "file:/.//p", + "base": null, + "href": "file:////p", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//p", + "search": "", + "hash": "" + }, + "# IPv6 tests", + { + "input": "http://[1:0::]", + "base": "http://example.net/", + "href": "http://[1::]/", + "origin": "http://[1::]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1::]", + "hostname": "[1::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[0:1:2:3:4:5:6:7:8]", + "base": "http://example.net/", + "failure": true + }, + { + "input": "https://[0::0::0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:.0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:0:]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1.00.0.0.0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1.290.0.0.0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1.23.23]", + "base": null, + "failure": true + }, + "# Empty host", + { + "input": "http://?", + "base": null, + "failure": true + }, + { + "input": "http://#", + "base": null, + "failure": true + }, + "Port overflow (2^32 + 81)", + { + "input": "http://f:4294967377/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^64 + 81)", + { + "input": "http://f:18446744073709551697/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^128 + 81)", + { + "input": "http://f:340282366920938463463374607431768211537/c", + "base": "http://example.org/", + "failure": true + }, + "# Non-special-URL path tests", + { + "input": "sc://ñ", + "base": null, + "href": "sc://%C3%B1", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "sc://ñ?x", + "base": null, + "href": "sc://%C3%B1?x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "?x", + "hash": "" + }, + { + "input": "sc://ñ#x", + "base": null, + "href": "sc://%C3%B1#x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "sc://ñ", + "href": "sc://%C3%B1#x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "#x" + }, + { + "input": "?x", + "base": "sc://ñ", + "href": "sc://%C3%B1?x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "?x", + "hash": "" + }, + { + "input": "sc://?", + "base": null, + "href": "sc://?", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "sc://#", + "base": null, + "href": "sc://#", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "///", + "base": "sc://x/", + "href": "sc:///", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "////", + "base": "sc://x/", + "href": "sc:////", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "////x/", + "base": "sc://x/", + "href": "sc:////x/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//x/", + "search": "", + "hash": "" + }, + { + "input": "tftp://foobar.com/someconfig;mode=netascii", + "base": null, + "href": "tftp://foobar.com/someconfig;mode=netascii", + "origin": "null", + "protocol": "tftp:", + "username": "", + "password": "", + "host": "foobar.com", + "hostname": "foobar.com", + "port": "", + "pathname": "/someconfig;mode=netascii", + "search": "", + "hash": "" + }, + { + "input": "telnet://user:pass@foobar.com:23/", + "base": null, + "href": "telnet://user:pass@foobar.com:23/", + "origin": "null", + "protocol": "telnet:", + "username": "user", + "password": "pass", + "host": "foobar.com:23", + "hostname": "foobar.com", + "port": "23", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ut2004://10.10.10.10:7777/Index.ut2", + "base": null, + "href": "ut2004://10.10.10.10:7777/Index.ut2", + "origin": "null", + "protocol": "ut2004:", + "username": "", + "password": "", + "host": "10.10.10.10:7777", + "hostname": "10.10.10.10", + "port": "7777", + "pathname": "/Index.ut2", + "search": "", + "hash": "" + }, + { + "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "base": null, + "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "origin": "null", + "protocol": "redis:", + "username": "foo", + "password": "bar", + "host": "somehost:6379", + "hostname": "somehost", + "port": "6379", + "pathname": "/0", + "search": "?baz=bam&qux=baz", + "hash": "" + }, + { + "input": "rsync://foo@host:911/sup", + "base": null, + "href": "rsync://foo@host:911/sup", + "origin": "null", + "protocol": "rsync:", + "username": "foo", + "password": "", + "host": "host:911", + "hostname": "host", + "port": "911", + "pathname": "/sup", + "search": "", + "hash": "" + }, + { + "input": "git://github.com/foo/bar.git", + "base": null, + "href": "git://github.com/foo/bar.git", + "origin": "null", + "protocol": "git:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar.git", + "search": "", + "hash": "" + }, + { + "input": "irc://myserver.com:6999/channel?passwd", + "base": null, + "href": "irc://myserver.com:6999/channel?passwd", + "origin": "null", + "protocol": "irc:", + "username": "", + "password": "", + "host": "myserver.com:6999", + "hostname": "myserver.com", + "port": "6999", + "pathname": "/channel", + "search": "?passwd", + "hash": "" + }, + { + "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "base": null, + "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "origin": "null", + "protocol": "dns:", + "username": "", + "password": "", + "host": "fw.example.org:9999", + "hostname": "fw.example.org", + "port": "9999", + "pathname": "/foo.bar.org", + "search": "?type=TXT", + "hash": "" + }, + { + "input": "ldap://localhost:389/ou=People,o=JNDITutorial", + "base": null, + "href": "ldap://localhost:389/ou=People,o=JNDITutorial", + "origin": "null", + "protocol": "ldap:", + "username": "", + "password": "", + "host": "localhost:389", + "hostname": "localhost", + "port": "389", + "pathname": "/ou=People,o=JNDITutorial", + "search": "", + "hash": "" + }, + { + "input": "git+https://github.com/foo/bar", + "base": null, + "href": "git+https://github.com/foo/bar", + "origin": "null", + "protocol": "git+https:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "urn:ietf:rfc:2648", + "base": null, + "href": "urn:ietf:rfc:2648", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "ietf:rfc:2648", + "search": "", + "hash": "" + }, + { + "input": "tag:joe@example.org,2001:foo/bar", + "base": null, + "href": "tag:joe@example.org,2001:foo/bar", + "origin": "null", + "protocol": "tag:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "joe@example.org,2001:foo/bar", + "search": "", + "hash": "" + }, + "Serialize /. in path", + { + "input": "non-spec:/.//", + "base": null, + "href": "non-spec:/.//", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/..//", + "base": null, + "href": "non-spec:/.//", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/a/..//", + "base": null, + "href": "non-spec:/.//", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/.//path", + "base": null, + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/..//path", + "base": null, + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/a/..//path", + "base": null, + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "/.//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "/..//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "..//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "a/..//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "non-spec:/..//p", + "href": "non-spec:/.//p", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//p", + "search": "", + "hash": "" + }, + { + "input": "path", + "base": "non-spec:/..//p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + "Do not serialize /. in path", + { + "input": "../path", + "base": "non-spec:/.//p", + "href": "non-spec:/path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + "# percent encoded hosts in non-special-URLs", + { + "input": "non-special://%E2%80%A0/", + "base": null, + "href": "non-special://%E2%80%A0/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "%E2%80%A0", + "hostname": "%E2%80%A0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://H%4fSt/path", + "base": null, + "href": "non-special://H%4fSt/path", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "H%4fSt", + "hostname": "H%4fSt", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + "# IPv6 in non-special-URLs", + { + "input": "non-special://[1:2:0:0:5:0:0:0]/", + "base": null, + "href": "non-special://[1:2:0:0:5::]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2:0:0:5::]", + "hostname": "[1:2:0:0:5::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2:0:0:0:0:0:3]/", + "base": null, + "href": "non-special://[1:2::3]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]", + "hostname": "[1:2::3]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2::3]:80/", + "base": null, + "href": "non-special://[1:2::3]:80/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]:80", + "hostname": "[1:2::3]", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[:80/", + "base": null, + "failure": true + }, + { + "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "base": null, + "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf", + "search": "", + "hash": "" + }, + { + "input": "blob:", + "base": null, + "href": "blob:", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + "Invalid IPv4 radix digits", + { + "input": "http://0x7f.0.0.0x7g", + "base": null, + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0X7F.0.0.0X7G", + "base": null, + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid IPv4 portion of IPv6 address", + { + "input": "http://[::127.0.0.0.1]", + "base": null, + "failure": true + }, + "Uncompressed IPv6 addresses with 0", + { + "input": "http://[0:1:0:1:0:1:0:1]", + "base": null, + "href": "http://[0:1:0:1:0:1:0:1]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[0:1:0:1:0:1:0:1]", + "hostname": "[0:1:0:1:0:1:0:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[1:0:1:0:1:0:1:0]", + "base": null, + "href": "http://[1:0:1:0:1:0:1:0]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1:0:1:0:1:0:1:0]", + "hostname": "[1:0:1:0:1:0:1:0]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Percent-encoded query and fragment", + { + "input": "http://example.org/test?\u0022", + "base": null, + "href": "http://example.org/test?%22", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%22", + "hash": "" + }, + { + "input": "http://example.org/test?\u0023", + "base": null, + "href": "http://example.org/test?#", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "http://example.org/test?\u003C", + "base": null, + "href": "http://example.org/test?%3C", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3C", + "hash": "" + }, + { + "input": "http://example.org/test?\u003E", + "base": null, + "href": "http://example.org/test?%3E", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3E", + "hash": "" + }, + { + "input": "http://example.org/test?\u2323", + "base": null, + "href": "http://example.org/test?%E2%8C%A3", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%E2%8C%A3", + "hash": "" + }, + { + "input": "http://example.org/test?%23%23", + "base": null, + "href": "http://example.org/test?%23%23", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%23%23", + "hash": "" + }, + { + "input": "http://example.org/test?%GH", + "base": null, + "href": "http://example.org/test?%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%GH", + "hash": "" + }, + { + "input": "http://example.org/test?a#%EF", + "base": null, + "href": "http://example.org/test?a#%EF", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%EF" + }, + { + "input": "http://example.org/test?a#%GH", + "base": null, + "href": "http://example.org/test?a#%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%GH" + }, + "URLs that require a non-about:blank base. (Also serve as invalid base tests.)", + { + "input": "a", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "a/", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "a//", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "Bases that don't fail to parse but fail to be bases", + { + "input": "test-a-colon.html", + "base": "a:", + "failure": true + }, + { + "input": "test-a-colon-b.html", + "base": "a:b", + "failure": true + }, + "Other base URL tests, that must succeed", + { + "input": "test-a-colon-slash.html", + "base": "a:/", + "href": "a:/test-a-colon-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash.html", + "base": "a://", + "href": "a:///test-a-colon-slash-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-b.html", + "base": "a:/b", + "href": "a:/test-a-colon-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-b.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash-b.html", + "base": "a://b", + "href": "a://b/test-a-colon-slash-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "b", + "hostname": "b", + "port": "", + "pathname": "/test-a-colon-slash-slash-b.html", + "search": "", + "hash": "" + }, + "Null code point in fragment", + { + "input": "http://example.org/test?a#b\u0000c", + "base": null, + "href": "http://example.org/test?a#b%00c", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + { + "input": "non-spec://example.org/test?a#b\u0000c", + "base": null, + "href": "non-spec://example.org/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + { + "input": "non-spec:/test?a#b\u0000c", + "base": null, + "href": "non-spec:/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + "First scheme char - not allowed: https://github.com/whatwg/url/issues/464", + { + "input": "10.0.0.7:8080/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/10.0.0.7:8080/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/10.0.0.7:8080/foo.html", + "search": "", + "hash": "" + }, + "Subsequent scheme chars - not allowed", + { + "input": "a!@$*=/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/a!@$*=/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/a!@$*=/foo.html", + "search": "", + "hash": "" + }, + "First and subsequent scheme chars - allowed", + { + "input": "a1234567890-+.:foo/bar", + "base": "http://example.com/dir/file", + "href": "a1234567890-+.:foo/bar", + "protocol": "a1234567890-+.:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "foo/bar", + "search": "", + "hash": "" + }, + "IDNA ignored code points in file URLs hosts", + { + "input": "file://a\u00ADb/p", + "base": null, + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + { + "input": "file://a%C2%ADb/p", + "base": null, + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + "IDNA hostnames which get mapped to 'localhost'", + { + "input": "file://loC𝐀𝐋𝐇𝐨𝐬𝐭/usr/bin", + "base": null, + "href": "file:///usr/bin", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/usr/bin", + "search": "", + "hash": "" + }, + "Empty host after the domain to ASCII", + { + "input": "file://\u00ad/p", + "base": null, + "failure": true + }, + { + "input": "file://%C2%AD/p", + "base": null, + "failure": true + }, + { + "input": "file://xn--/p", + "base": null, + "failure": true + }, + "https://bugzilla.mozilla.org/show_bug.cgi?id=1647058", + { + "input": "#link", + "base": "https://example.org/##link", + "href": "https://example.org/#link", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "#link" + }, + "UTF-8 percent-encode of C0 control percent-encode set and supersets", + { + "input": "non-special:cannot-be-a-base-url-\u0000\u0001\u001F\u001E\u007E\u007F\u0080", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80", + "origin": "null", + "password": "", + "pathname": "cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "https://www.example.com/path{\u007Fpath.html?query'\u007F=query#fragment<\u007Ffragment", + "base": null, + "hash": "#fragment%3C%7Ffragment", + "host": "www.example.com", + "hostname": "www.example.com", + "href": "https://www.example.com/path%7B%7Fpath.html?query%27%7F=query#fragment%3C%7Ffragment", + "origin": "https://www.example.com", + "password": "", + "pathname": "/path%7B%7Fpath.html", + "port": "", + "protocol": "https:", + "search": "?query%27%7F=query", + "username": "" + }, + { + "input": "https://user:pass[\u007F@foo/bar", + "base": "http://example.org", + "hash": "", + "host": "foo", + "hostname": "foo", + "href": "https://user:pass%5B%7F@foo/bar", + "origin": "https://foo", + "password": "pass%5B%7F", + "pathname": "/bar", + "port": "", + "protocol": "https:", + "search": "", + "username": "user" + }, + "Tests for the distinct percent-encode sets", + { + "input": "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/", + "origin": "null", + "password": "", + "pathname": "/", + "port": "", + "protocol": "foo:", + "search": "", + "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~" + }, + { + "input": "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/", + "origin": "null", + "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~", + "pathname": "/", + "port": "", + "protocol": "foo:", + "search": "", + "username": "joe" + }, + { + "input": "foo://!\"$%&'()*+,-.;=_`{}~/", + "base": null, + "hash": "", + "host": "!\"$%&'()*+,-.;=_`{}~", + "hostname": "!\"$%&'()*+,-.;=_`{}~", + "href": "foo://!\"$%&'()*+,-.;=_`{}~/", + "origin": "null", + "password": "", + "pathname": "/", + "port": "", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~", + "origin": "null", + "password": "", + "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~", + "port": "", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "foo://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "origin": "null", + "password": "", + "pathname": "/dir/", + "port": "", + "protocol": "foo:", + "search": "?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "username": "" + }, + { + "input": "wss://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "wss://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "origin": "wss://host", + "password": "", + "pathname": "/dir/", + "port": "", + "protocol": "wss:", + "search": "?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "username": "" + }, + { + "input": "foo://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", + "base": null, + "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~", + "host": "host", + "hostname": "host", + "href": "foo://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~", + "origin": "null", + "password": "", + "pathname": "/dir/", + "port": "", + "protocol": "foo:", + "search": "", + "username": "" + }, + "Ensure that input schemes are not ignored when resolving non-special URLs", + { + "input": "abc:rootless", + "base": "abc://host/path", + "hash": "", + "host": "", + "hostname": "", + "href": "abc:rootless", + "password": "", + "pathname": "rootless", + "port": "", + "protocol": "abc:", + "search": "", + "username": "" + }, + { + "input": "abc:rootless", + "base": "abc:/path", + "hash": "", + "host": "", + "hostname": "", + "href": "abc:rootless", + "password": "", + "pathname": "rootless", + "port": "", + "protocol": "abc:", + "search": "", + "username": "" + }, + { + "input": "abc:rootless", + "base": "abc:path", + "hash": "", + "host": "", + "hostname": "", + "href": "abc:rootless", + "password": "", + "pathname": "rootless", + "port": "", + "protocol": "abc:", + "search": "", + "username": "" + }, + { + "input": "abc:/rooted", + "base": "abc://host/path", + "hash": "", + "host": "", + "hostname": "", + "href": "abc:/rooted", + "password": "", + "pathname": "/rooted", + "port": "", + "protocol": "abc:", + "search": "", + "username": "" + }, + "Empty query and fragment with blank should throw an error", + { + "input": "#", + "base": null, + "failure": true, + "relativeTo": "any-base" + }, + { + "input": "?", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "Last component looks like a number, but not valid IPv4", + { + "input": "http://1.2.3.4.5", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://1.2.3.4.5.", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://0..0x300/", + "base": null, + "failure": true + }, + { + "input": "http://0..0x300./", + "base": null, + "failure": true + }, + { + "input": "http://256.256.256.256.256", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256.256.", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://1.2.3.08", + "base": null, + "failure": true + }, + { + "input": "http://1.2.3.08.", + "base": null, + "failure": true + }, + { + "input": "http://1.2.3.09", + "base": null, + "failure": true + }, + { + "input": "http://09.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://09.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://01.2.3.4.5", + "base": null, + "failure": true + }, + { + "input": "http://01.2.3.4.5.", + "base": null, + "failure": true + }, + { + "input": "http://0x100.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://0x100.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://0x1.2.3.4.5", + "base": null, + "failure": true + }, + { + "input": "http://0x1.2.3.4.5.", + "base": null, + "failure": true + }, + { + "input": "http://foo.1.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://foo.1.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://foo.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://foo.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://foo.09", + "base": null, + "failure": true + }, + { + "input": "http://foo.09.", + "base": null, + "failure": true + }, + { + "input": "http://foo.0x4", + "base": null, + "failure": true + }, + { + "input": "http://foo.0x4.", + "base": null, + "failure": true + }, + { + "input": "http://foo.09..", + "base": null, + "hash": "", + "host": "foo.09..", + "hostname": "foo.09..", + "href": "http://foo.09../", + "password": "", + "pathname": "/", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + }, + { + "input": "http://0999999999999999999/", + "base": null, + "failure": true + }, + { + "input": "http://foo.0x", + "base": null, + "failure": true + }, + { + "input": "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123", + "base": null, + "failure": true + }, + { + "input": "http://💩.123/", + "base": null, + "failure": true + }, + "U+0000 and U+FFFF in various places", + { + "input": "https://\u0000y", + "base": null, + "failure": true + }, + { + "input": "https://x/\u0000y", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/%00y", + "password": "", + "pathname": "/%00y", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://x/?\u0000y", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/?%00y", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "?%00y", + "username": "" + }, + { + "input": "https://x/?#\u0000y", + "base": null, + "hash": "#%00y", + "host": "x", + "hostname": "x", + "href": "https://x/?#%00y", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://\uFFFFy", + "base": null, + "failure": true + }, + { + "input": "https://x/\uFFFFy", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/%EF%BF%BFy", + "password": "", + "pathname": "/%EF%BF%BFy", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://x/?\uFFFFy", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/?%EF%BF%BFy", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "?%EF%BF%BFy", + "username": "" + }, + { + "input": "https://x/?#\uFFFFy", + "base": null, + "hash": "#%EF%BF%BFy", + "host": "x", + "hostname": "x", + "href": "https://x/?#%EF%BF%BFy", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "non-special:\u0000y", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:%00y", + "password": "", + "pathname": "%00y", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/\u0000y", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/%00y", + "password": "", + "pathname": "x/%00y", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/?\u0000y", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/?%00y", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "?%00y", + "username": "" + }, + { + "input": "non-special:x/?#\u0000y", + "base": null, + "hash": "#%00y", + "host": "", + "hostname": "", + "href": "non-special:x/?#%00y", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:\uFFFFy", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:%EF%BF%BFy", + "password": "", + "pathname": "%EF%BF%BFy", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/\uFFFFy", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/%EF%BF%BFy", + "password": "", + "pathname": "x/%EF%BF%BFy", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/?\uFFFFy", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/?%EF%BF%BFy", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "?%EF%BF%BFy", + "username": "" + }, + { + "input": "non-special:x/?#\uFFFFy", + "base": null, + "hash": "#%EF%BF%BFy", + "host": "", + "hostname": "", + "href": "non-special:x/?#%EF%BF%BFy", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "https://example.com/\"quoted\"", + "base": null, + "hash": "", + "host": "example.com", + "hostname": "example.com", + "href": "https://example.com/%22quoted%22", + "origin": "https://example.com", + "password": "", + "pathname": "/%22quoted%22", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://a%C2%ADb/", + "base": null, + "hash": "", + "host": "ab", + "hostname": "ab", + "href": "https://ab/", + "origin": "https://ab", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "comment": "Empty host after domain to ASCII", + "input": "https://\u00AD/", + "base": null, + "failure": true + }, + { + "input": "https://%C2%AD/", + "base": null, + "failure": true + }, + { + "input": "https://xn--/", + "base": null, + "failure": true + }, + "Non-special schemes that some implementations might incorrectly treat as special", + { + "input": "data://example.com:8080/pathname?search#hash", + "base": null, + "href": "data://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "data:///test", + "base": null, + "href": "data:///test", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "data://test/a/../b", + "base": null, + "href": "data://test/b", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "data://:443", + "base": null, + "failure": true + }, + { + "input": "data://test:test", + "base": null, + "failure": true + }, + { + "input": "data://[:1]", + "base": null, + "failure": true + }, + { + "input": "javascript://example.com:8080/pathname?search#hash", + "base": null, + "href": "javascript://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "javascript:///test", + "base": null, + "href": "javascript:///test", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "javascript://test/a/../b", + "base": null, + "href": "javascript://test/b", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "javascript://:443", + "base": null, + "failure": true + }, + { + "input": "javascript://test:test", + "base": null, + "failure": true + }, + { + "input": "javascript://[:1]", + "base": null, + "failure": true + }, + { + "input": "mailto://example.com:8080/pathname?search#hash", + "base": null, + "href": "mailto://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "mailto:///test", + "base": null, + "href": "mailto:///test", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "mailto://test/a/../b", + "base": null, + "href": "mailto://test/b", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "mailto://:443", + "base": null, + "failure": true + }, + { + "input": "mailto://test:test", + "base": null, + "failure": true + }, + { + "input": "mailto://[:1]", + "base": null, + "failure": true + }, + { + "input": "intent://example.com:8080/pathname?search#hash", + "base": null, + "href": "intent://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "intent:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "intent:///test", + "base": null, + "href": "intent:///test", + "origin": "null", + "protocol": "intent:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "intent://test/a/../b", + "base": null, + "href": "intent://test/b", + "origin": "null", + "protocol": "intent:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "intent://:443", + "base": null, + "failure": true + }, + { + "input": "intent://test:test", + "base": null, + "failure": true + }, + { + "input": "intent://[:1]", + "base": null, + "failure": true + }, + { + "input": "urn://example.com:8080/pathname?search#hash", + "base": null, + "href": "urn://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "urn:///test", + "base": null, + "href": "urn:///test", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "urn://test/a/../b", + "base": null, + "href": "urn://test/b", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "urn://:443", + "base": null, + "failure": true + }, + { + "input": "urn://test:test", + "base": null, + "failure": true + }, + { + "input": "urn://[:1]", + "base": null, + "failure": true + }, + { + "input": "turn://example.com:8080/pathname?search#hash", + "base": null, + "href": "turn://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "turn:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "turn:///test", + "base": null, + "href": "turn:///test", + "origin": "null", + "protocol": "turn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "turn://test/a/../b", + "base": null, + "href": "turn://test/b", + "origin": "null", + "protocol": "turn:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "turn://:443", + "base": null, + "failure": true + }, + { + "input": "turn://test:test", + "base": null, + "failure": true + }, + { + "input": "turn://[:1]", + "base": null, + "failure": true + }, + { + "input": "stun://example.com:8080/pathname?search#hash", + "base": null, + "href": "stun://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "stun:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "stun:///test", + "base": null, + "href": "stun:///test", + "origin": "null", + "protocol": "stun:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "stun://test/a/../b", + "base": null, + "href": "stun://test/b", + "origin": "null", + "protocol": "stun:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "stun://:443", + "base": null, + "failure": true + }, + { + "input": "stun://test:test", + "base": null, + "failure": true + }, + { + "input": "stun://[:1]", + "base": null, + "failure": true + } +] |