#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" #include "nsURLHelper.h" #include "IPv4Parser.h" using namespace mozilla; nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result) { return net::IPv4Parser::NormalizeIPv4(host, result); } TEST(TestStandardURL, Simple) { nsCOMPtr 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 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 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 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 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 uri; nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) .SetSpec(spec) .Finalize(uri); ASSERT_EQ(rv, NS_OK); nsAutoCString serialization; nsCOMPtr serializable = do_QueryInterface(uri); ASSERT_TRUE(serializable); // Check that the URL is normally serializable. ASSERT_EQ(NS_OK, NS_SerializeToString(serializable, serialization)); nsCOMPtr 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(host.Length()); ASSERT_EQ(2, mozilla::net::IPv4Parser::ValidateIPv4Number( host, bases, dotIndex, onlyBase10, length, false)); nsCString result; ASSERT_EQ(NS_OK, Test_NormalizeIPv4("0x.0x.0"_ns, result)); uint32_t number; mozilla::net::IPv4Parser::ParseIPv4Number("0x10"_ns, 16, number, 255); ASSERT_EQ(number, (uint32_t)16); } TEST(TestStandardURL, CoalescePath) { auto testCoalescing = [](const char* input, const char* expected, uint32_t lastSlash, uint32_t endOfBasename) { nsAutoCString buf(input); auto resultCoalesceDirs = net_CoalesceDirs(buf.BeginWriting()); ASSERT_EQ(nsCString(buf.get()), nsCString(expected)); // If the path does not begin with '/', it should return Nothing(). if (input[0] == '/') { ASSERT_EQ(resultCoalesceDirs->first(), lastSlash); ASSERT_EQ(resultCoalesceDirs->second(), endOfBasename); } else { ASSERT_TRUE(resultCoalesceDirs == Nothing()); } }; #ifndef DEBUG // invalid input testCoalescing("", "", -1, -1); #endif // end of basename is the null character's index testCoalescing("/", "/", 0, 1); testCoalescing("/.", "/", 0, 1); testCoalescing("/..", "/", 0, 1); testCoalescing("/foo/foo1/.", "/foo/foo1/", 9, 10); testCoalescing("/foo/../foo1", "/foo1", 0, 5); testCoalescing("/foo/./foo1", "/foo/foo1", 4, 9); testCoalescing("/foo/foo1/..", "/foo/", 4, 5); // Bug 1890346 testCoalescing("/..?/..", "/?/..", 0, 1); testCoalescing("/.?/..", "/?/..", 0, 1); testCoalescing("/./../?", "/?", 0, 1); testCoalescing("/.abc", "/.abc", 0, 5); testCoalescing("//", "//", 1, 2); testCoalescing("/../", "/", 0, 1); testCoalescing("/./", "/", 0, 1); testCoalescing("/.../", "/.../", 4, 5); // Bug 1873915 testCoalescing("/hello/%2e%2e/foo", "/foo", 0, 4); testCoalescing("/hello/%2e%2E/foo", "/foo", 0, 4); testCoalescing("/hello/%2E%2e/foo", "/foo", 0, 4); testCoalescing("/hello/%2E%2E/foo", "/foo", 0, 4); testCoalescing("/hello/%2e./foo", "/foo", 0, 4); testCoalescing("/hello/.%2e/foo", "/foo", 0, 4); testCoalescing("/hello/%2E./foo", "/foo", 0, 4); testCoalescing("/hello/.%2E/foo", "/foo", 0, 4); testCoalescing("/hello/%2e%2E/%2e", "/", 0, 1); testCoalescing("/hello/%2e/%2e%2e", "/", 0, 1); testCoalescing("/hello/%2e%2E/%#", "/%#", 0, 2); testCoalescing("/hello/%2e%2E/%?", "/%?", 0, 2); testCoalescing("/hello/%2e", "/hello/", 6, 7); testCoalescing("/hello/%2E", "/hello/", 6, 7); testCoalescing("/hello/%2e/", "/hello/", 6, 7); testCoalescing("/hello/%2E/", "/hello/", 6, 7); testCoalescing("/hello/%2e.", "/", 0, 1); testCoalescing("/hello/.%2e", "/", 0, 1); testCoalescing("/hello/%2E.", "/", 0, 1); testCoalescing("/hello/.%2E", "/", 0, 1); testCoalescing("/hello/%2E%2e/%2e/./../.", "/", 0, 1); testCoalescing("/test/%2e%2e/dir/%2E%2e/file", "/file", 0, 5); // should not convert as a character is present testCoalescing("/hello/%2E%2ea", "/hello/%2E%2ea", 6, 14); testCoalescing("/hello/a%2E%2e", "/hello/a%2E%2e", 6, 14); testCoalescing("/hello/a%2E%2ea", "/hello/a%2E%2ea", 6, 15); // Encoded sequences followed by special characters testCoalescing("/foo/%2e%2e/%2e%23/file.txt", "/%2e%23/file.txt", 7, 16); testCoalescing("/foo/%2e%2e/%2e%3f/file.txt", "/%2e%3f/file.txt", 7, 16); // Query strings and fragments with encoded sequences testCoalescing("/query/%2e%2e/path?p=%2e", "/path?p=%2e", 0, 5); testCoalescing("/frag/%2e%2e/path#q=%2e", "/path#q=%2e", 0, 5); // 3 or more "%2e" repeating should not convert to "." testCoalescing("/hello/%2E%2e%2E", "/hello/%2E%2e%2E", 6, 16); testCoalescing("/hello/%2E%2e%2E%2e", "/hello/%2E%2e%2E%2e", 6, 19); testCoalescing("/hello/%2E%2e%2E%2e.", "/hello/%2E%2e%2E%2e.", 6, 20); // Relatively long inputs testCoalescing("/foo/bar/foo/bar/foo/bar/foo/bar/foo?query#frag", "/foo/bar/foo/bar/foo/bar/foo/bar/foo?query#frag", 32, 36); testCoalescing("/coder/coder/edit/main/docs/./enterprise.md", "/coder/coder/edit/main/docs/enterprise.md", 27, 41); // bug 1942820 testCoalescing("/foo/bar/.%2e", "/foo/", 4, 5); testCoalescing("/foo/bar/..", "/foo/", 4, 5); testCoalescing("/foo/bar/%2e%2e", "/foo/", 4, 5); testCoalescing("/foo/bar/%2e%2e#frag", "/foo/#frag", 4, 5); testCoalescing("/foo/bar/%2e%2e?query", "/foo/?query", 4, 5); } TEST(TestStandardURL, bug1904582) { auto spec = "x:///%2e%2e"_ns; nsCOMPtr uri; nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) .SetSpec(spec) .Finalize(uri); ASSERT_TRUE(NS_SUCCEEDED(rv)); } TEST(TestStandardURL, bug1911529) { nsCOMPtr uri; nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) .SetSpec( "https://github.com/coder/coder/edit/main/docs/./enterprise.md"_ns) .Finalize(uri); ASSERT_TRUE(NS_SUCCEEDED(rv)); nsAutoCString out; ASSERT_EQ(uri->GetSpec(out), NS_OK); ASSERT_TRUE(out == "https://github.com/coder/coder/edit/main/docs/enterprise.md"_ns); nsCOMPtr uri2; rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) .SetSpec( "https://github.com/coder/coder/edit/main/docs/enterprise.md"_ns) .Finalize(uri2); ASSERT_TRUE(NS_SUCCEEDED(rv)); bool equals = false; ASSERT_EQ(uri->Equals(uri2, &equals), NS_OK); ASSERT_TRUE(equals); rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) .SetSpec("https://domain.com/."_ns) .Finalize(uri); ASSERT_TRUE(NS_SUCCEEDED(rv)); rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) .SetSpec("https://domain.com/"_ns) .Finalize(uri2); ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_EQ(uri->Equals(uri2, &equals), NS_OK); ASSERT_TRUE(equals); }