Bug 1394906 - Implement immutable, threadsafe MozURL r=mayhemer
Also adds the necessary methods to rust-url-capi and fixes a bug in rusturl_set_username
MozReview-Commit-ID: 9rsPIQAbWBJ
new file mode 100644
--- /dev/null
+++ b/netwerk/base/MozURL.cpp
@@ -0,0 +1,173 @@
+/* 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 "MozURL.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(MozURL)
+NS_IMPL_RELEASE(MozURL)
+
+/* static */ nsresult
+MozURL::Init(const nsACString& aSpec, MozURL** aURL)
+{
+ rusturl* ptr = rusturl_new(&aSpec);
+ if (!ptr) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<MozURL> url = new MozURL(ptr);
+ url.forget(aURL);
+ return NS_OK;
+}
+
+nsresult
+MozURL::GetScheme(nsACString& aScheme)
+{
+ return rusturl_get_scheme(mURL.get(), &aScheme);
+}
+
+nsresult
+MozURL::GetSpec(nsACString& aSpec)
+{
+ return rusturl_get_spec(mURL.get(), &aSpec);
+}
+
+nsresult
+MozURL::GetUsername(nsACString& aUser)
+{
+ return rusturl_get_username(mURL.get(), &aUser);
+}
+
+nsresult
+MozURL::GetPassword(nsACString& aPassword)
+{
+ return rusturl_get_password(mURL.get(), &aPassword);
+}
+
+nsresult
+MozURL::GetHostname(nsACString& aHost)
+{
+ return rusturl_get_host(mURL.get(), &aHost);
+}
+
+nsresult
+MozURL::GetPort(int32_t* aPort)
+{
+ return rusturl_get_port(mURL.get(), aPort);
+}
+
+nsresult
+MozURL::GetFilePath(nsACString& aPath)
+{
+ return rusturl_get_filepath(mURL.get(), &aPath);
+}
+
+nsresult
+MozURL::GetQuery(nsACString& aQuery)
+{
+ return rusturl_get_query(mURL.get(), &aQuery);
+}
+
+nsresult
+MozURL::GetRef(nsACString& aRef)
+{
+ return rusturl_get_fragment(mURL.get(), &aRef);
+}
+
+// MozURL::Mutator
+
+// This macro ensures that the mutator is still valid, meaning it hasn't been
+// finalized, and none of the setters have returned an error code.
+#define ENSURE_VALID() \
+ PR_BEGIN_MACRO \
+ if (mFinalized) { \
+ mStatus = NS_ERROR_NOT_AVAILABLE; \
+ } \
+ if (NS_FAILED(mStatus)) { \
+ return *this; \
+ } \
+ PR_END_MACRO
+
+nsresult
+MozURL::Mutator::Finalize(MozURL** aURL)
+{
+ if (mFinalized) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mFinalized = true;
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+ RefPtr<MozURL> result = new MozURL(mURL.release());
+ result.forget(aURL);
+ return NS_OK;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetScheme(const nsACString& aScheme)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_scheme(mURL.get(), &aScheme);
+ return *this;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetUsername(const nsACString& aUser)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_username(mURL.get(), &aUser);
+ return *this;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetPassword(const nsACString& aPassword)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_password(mURL.get(), &aPassword);
+ return *this;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetHostname(const nsACString& aHost)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_host(mURL.get(), &aHost);
+ return *this;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetFilePath(const nsACString& aPath)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_path(mURL.get(), &aPath);
+ return *this;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetQuery(const nsACString& aQuery)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_query(mURL.get(), &aQuery);
+ return *this;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetRef(const nsACString& aRef)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_fragment(mURL.get(), &aRef);
+ return *this;
+}
+
+MozURL::Mutator&
+MozURL::Mutator::SetPort(int32_t aPort)
+{
+ ENSURE_VALID();
+ mStatus = rusturl_set_port_no(mURL.get(), aPort);
+ return *this;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/MozURL.h
@@ -0,0 +1,134 @@
+/* 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 mozURL_h__
+#define mozURL_h__
+
+#include "rust-url-capi/src/rust-url-capi.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace net {
+
+// This class provides a thread-safe, immutable URL parser.
+// As long as there is RefPtr to the object, you may use it on any thread.
+// The constructor is private. One can instantiate the object by
+// calling the Init() method as such:
+//
+// RefPtr<MozURL> url;
+// nsAutoCString href("http://example.com/path?query#ref");
+// nsresult rv = MozURL::Init(href, getter_AddRefs(url));
+// if (NS_SUCCEEDED(rv)) { /* use url */ }
+//
+// When changing the URL is needed, you need to call the Mutate() method.
+// This gives you a Mutator object, on which you can perform setter operations.
+// Calling Finalize() on the Mutator will result in a new MozURL and a status
+// code. If any of the setter operations failed, it will be reflected in the
+// status code, and a null MozURL.
+//
+// Note: In the case of a domain name containing non-ascii characters,
+// GetSpec and GetHostname will return the IDNA(punycode) version of the host.
+// Also note that for now, MozURL only supports the UTF-8 charset.
+class MozURL final
+{
+public:
+ static nsresult Init(const nsACString& aSpec, MozURL** aURL);
+
+ nsresult GetScheme(nsACString& aScheme);
+ nsresult GetSpec(nsACString& aSpec);
+ nsresult GetUsername(nsACString& aUser);
+ nsresult GetPassword(nsACString& aPassword);
+ // Will return the hostname of URL. If the hostname is an IPv6 address,
+ // it will be enclosed in square brackets, such as `[::1]`
+ nsresult GetHostname(nsACString& aHost);
+ // Will return the port number, if specified, or -1
+ nsresult GetPort(int32_t* aPort);
+ nsresult GetFilePath(nsACString& aPath);
+ nsresult GetQuery(nsACString& aQuery);
+ nsresult GetRef(nsACString& aRef);
+
+private:
+ MozURL(rusturl* rawPtr)
+ : mURL(rawPtr)
+ {
+ }
+ virtual ~MozURL() {}
+ struct FreeRustURL
+ {
+ void operator()(rusturl* aPtr) { rusturl_free(aPtr); }
+ };
+ mozilla::UniquePtr<rusturl, FreeRustURL> mURL;
+
+public:
+ class MOZ_STACK_CLASS Mutator
+ {
+ public:
+ // Calling this method will result in the creation of a new MozURL that
+ // adopts the mutator's mURL.
+ // If any of the setters failed with an error code, that error code will be
+ // returned here. It will also return an error code if Finalize is called
+ // more than once on the Mutator.
+ nsresult Finalize(MozURL** aURL);
+
+ // These setter methods will return a reference to `this` so that you may
+ // chain setter operations as such:
+ //
+ // RefPtr<MozURL> url2;
+ // nsresult rv = url->Mutate().SetHostname(NS_LITERAL_CSTRING("newhost"))
+ // .SetFilePath(NS_LITERAL_CSTRING("new/file/path"))
+ // .Finalize(getter_AddRefs(url2));
+ // if (NS_SUCCEEDED(rv)) { /* use url2 */ }
+ Mutator& SetScheme(const nsACString& aScheme);
+ Mutator& SetUsername(const nsACString& aUser);
+ Mutator& SetPassword(const nsACString& aPassword);
+ Mutator& SetHostname(const nsACString& aHost);
+ Mutator& SetFilePath(const nsACString& aPath);
+ Mutator& SetQuery(const nsACString& aQuery);
+ Mutator& SetRef(const nsACString& aRef);
+ Mutator& SetPort(int32_t aPort);
+
+ // This method returns the status code of the setter operations.
+ // If any of the setters failed, it will return the code of the first error
+ // that occured. If none of the setters failed, it will return NS_OK.
+ // This method is useful to avoid doing expensive operations when the result
+ // would not be used because an error occurred. For example:
+ //
+ // RefPtr<MozURL> url2;
+ // MozURL::Mutator mut = url->Mutate();
+ // mut.SetScheme("!@#$"); // this would fail
+ // if (NS_SUCCEDED(mut.GetStatus())) {
+ // nsAutoCString host(ExpensiveComputing());
+ // rv = mut.SetHostname(host).Finalize(getter_AddRefs(url2));
+ // }
+ // if (NS_SUCCEEDED(rv)) { /* use url2 */ }
+ nsresult GetStatus() { return mStatus; }
+ private:
+ Mutator(MozURL* url)
+ : mURL(rusturl_clone(url->mURL.get()))
+ , mFinalized(false)
+ , mStatus(NS_OK)
+ {
+ }
+ mozilla::UniquePtr<rusturl, FreeRustURL> mURL;
+ bool mFinalized;
+ nsresult mStatus;
+ friend class MozURL;
+ };
+
+ Mutator Mutate() { return Mutator(this); }
+
+// These are used to avoid inheriting from nsISupports
+public:
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
+ NS_IMETHOD_(MozExternalRefCountType) Release(void);
+ typedef mozilla::TrueType HasThreadSafeRefCnt;
+protected:
+ ::mozilla::ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozURL_h__
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -170,16 +170,17 @@ EXPORTS.mozilla += [
EXPORTS.mozilla.net += [
'CaptivePortalService.h',
'ChannelDiverterChild.h',
'ChannelDiverterParent.h',
'Dashboard.h',
'DashboardTypes.h',
'MemoryDownloader.h',
+ 'MozURL.h',
'PartiallySeekableInputStream.h',
'Predictor.h',
'ReferrerPolicy.h',
'SimpleChannelParent.h',
'TCPFastOpen.h',
]
UNIFIED_SOURCES += [
@@ -188,16 +189,17 @@ UNIFIED_SOURCES += [
'CaptivePortalService.cpp',
'ChannelDiverterChild.cpp',
'ChannelDiverterParent.cpp',
'Dashboard.cpp',
'EventTokenBucket.cpp',
'LoadContextInfo.cpp',
'LoadInfo.cpp',
'MemoryDownloader.cpp',
+ 'MozURL.cpp',
'NetworkActivityMonitor.cpp',
'nsAsyncRedirectVerifyHelper.cpp',
'nsAsyncStreamCopier.cpp',
'nsAuthInformationHolder.cpp',
'nsBase64Encoder.cpp',
'nsBaseChannel.cpp',
'nsBaseContentStream.cpp',
'nsBufferedStreams.cpp',
--- a/netwerk/base/rust-url-capi/src/lib.rs
+++ b/netwerk/base/rust-url-capi/src/lib.rs
@@ -48,16 +48,27 @@ pub extern "C" fn rusturl_new(spec: &nsA
match parser().parse(url_spec) {
Ok(url) => Box::into_raw(Box::new(url)),
Err(_) => return ptr::null_mut(),
}
}
#[no_mangle]
+pub extern "C" fn rusturl_clone(urlptr: Option<&Url>) -> *mut Url {
+ let url = if let Some(url) = urlptr {
+ url
+ } else {
+ return ptr::null_mut();
+ };
+
+ return Box::into_raw(Box::new(url.clone()));
+}
+
+#[no_mangle]
pub unsafe extern "C" fn rusturl_free(urlptr: *mut Url) {
if urlptr.is_null() {
return;
}
Box::from_raw(urlptr);
}
#[no_mangle]
@@ -140,16 +151,32 @@ pub extern "C" fn rusturl_get_port(urlpt
// NOTE: Gecko uses -1 to represent the default port
*port = -1;
}
}
NS_OK
}
#[no_mangle]
+pub extern "C" fn rusturl_get_filepath(urlptr: Option<&Url>, cont: &mut nsACString) -> nsresult {
+ let url = if let Some(url) = urlptr {
+ url
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ };
+
+ if url.cannot_be_a_base() {
+ cont.assign("");
+ } else {
+ cont.assign(&url[Position::BeforePath..Position::AfterPath]);
+ }
+ NS_OK
+}
+
+#[no_mangle]
pub extern "C" fn rusturl_get_path(urlptr: Option<&Url>, cont: &mut nsACString) -> nsresult {
let url = if let Some(url) = urlptr {
url
} else {
return NS_ERROR_INVALID_ARG;
};
if url.cannot_be_a_base() {
@@ -224,17 +251,17 @@ pub extern "C" fn rusturl_set_username(u
return NS_ERROR_INVALID_ARG;
};
let username_ = match str::from_utf8(username) {
Ok(p) => p,
Err(_) => return NS_ERROR_MALFORMED_URI, // utf-8 failed
};
- match quirks::set_protocol(url, username_) {
+ match quirks::set_username(url, username_) {
Ok(()) => NS_OK,
Err(()) => NS_ERROR_MALFORMED_URI,
}
}
#[no_mangle]
pub extern "C" fn rusturl_set_password(urlptr: Option<&mut Url>, password: &nsACString) -> nsresult {
let url = if let Some(url) = urlptr {
--- a/netwerk/base/rust-url-capi/src/rust-url-capi.h
+++ b/netwerk/base/rust-url-capi/src/rust-url-capi.h
@@ -15,24 +15,26 @@ extern "C" {
// be non-null.
// * All rusturl* pointers must refer to pointers which are returned
// by rusturl_new, and must be freed with rusturl_free.
// The `rusturl` opaque type is equivalent to the rust type `::url::Url`
struct rusturl;
rusturl* rusturl_new(const nsACString* spec);
+rusturl* rusturl_clone(const rusturl* url);
/* unsafe */ void rusturl_free(rusturl* url);
nsresult rusturl_get_spec(const rusturl* url, nsACString* cont);
nsresult rusturl_get_scheme(const rusturl* url, nsACString* cont);
nsresult rusturl_get_username(const rusturl* url, nsACString* cont);
nsresult rusturl_get_password(const rusturl* url, nsACString* cont);
nsresult rusturl_get_host(const rusturl* url, nsACString* cont);
nsresult rusturl_get_port(const rusturl* url, int32_t* port);
+nsresult rusturl_get_filepath(const rusturl* url, nsACString* cont);
nsresult rusturl_get_path(const rusturl* url, nsACString* cont);
nsresult rusturl_get_query(const rusturl* url, nsACString* cont);
nsresult rusturl_get_fragment(const rusturl* url, nsACString* cont);
nsresult rusturl_has_fragment(const rusturl* url, bool* has_fragment);
nsresult rusturl_set_scheme(rusturl* url, const nsACString* scheme);
nsresult rusturl_set_username(rusturl* url, const nsACString* user);
nsresult rusturl_set_password(rusturl* url, const nsACString* password);
new file mode 100644
--- /dev/null
+++ b/netwerk/test/gtest/TestMozURL.cpp
@@ -0,0 +1,103 @@
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+
+#include "nsCOMPtr.h"
+#include "../../base/MozURL.h"
+
+using namespace mozilla::net;
+
+TEST(TestMozURL, Getters)
+{
+ nsAutoCString href("http://user:pass@example.com/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(href, getter_AddRefs(url)), NS_OK);
+
+ nsAutoCString out;
+
+ ASSERT_EQ(url->GetScheme(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("http"));
+
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == href);
+
+ ASSERT_EQ(url->GetUsername(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("user"));
+
+ ASSERT_EQ(url->GetPassword(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("pass"));
+
+ ASSERT_EQ(url->GetHostname(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("example.com"));
+
+ ASSERT_EQ(url->GetFilePath(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("/path"));
+
+ ASSERT_EQ(url->GetQuery(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("query"));
+
+ ASSERT_EQ(url->GetRef(out), NS_OK);
+ ASSERT_TRUE(out.EqualsLiteral("ref"));
+
+ url = nullptr;
+ ASSERT_EQ(MozURL::Init(NS_LITERAL_CSTRING(""), getter_AddRefs(url)),
+ NS_ERROR_FAILURE);
+ ASSERT_EQ(url, nullptr);
+}
+
+TEST(TestMozURL, MutatorChain)
+{
+ nsAutoCString href("http://user:pass@example.com/path?query#ref");
+ RefPtr<MozURL> url;
+ ASSERT_EQ(MozURL::Init(href, getter_AddRefs(url)), NS_OK);
+ nsAutoCString out;
+
+ RefPtr<MozURL> url2;
+ ASSERT_EQ(url->Mutate().SetScheme(NS_LITERAL_CSTRING("https"))
+ .SetUsername(NS_LITERAL_CSTRING("newuser"))
+ .SetPassword(NS_LITERAL_CSTRING("newpass"))
+ .SetHostname(NS_LITERAL_CSTRING("test"))
+ .SetFilePath(NS_LITERAL_CSTRING("new/file/path"))
+ .SetQuery(NS_LITERAL_CSTRING("bla"))
+ .SetRef(NS_LITERAL_CSTRING("huh"))
+ .Finalize(getter_AddRefs(url2)), NS_OK);
+
+ ASSERT_EQ(url2->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out.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(href, getter_AddRefs(url)), NS_OK);
+ nsAutoCString out;
+
+ RefPtr<MozURL> url2;
+ MozURL::Mutator mut = url->Mutate();
+ mut.SetScheme(NS_LITERAL_CSTRING("https")); // Change the scheme to https
+ ASSERT_EQ(mut.Finalize(getter_AddRefs(url2)), NS_OK);
+ ASSERT_EQ(url2->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out.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(href, getter_AddRefs(url)), 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_LITERAL_CSTRING("!@#$%^&*("));
+ ASSERT_EQ(mut.GetStatus(), NS_ERROR_MALFORMED_URI);
+
+ // Test that the mutator will not work after one faulty operation
+ mut.SetScheme(NS_LITERAL_CSTRING("test"));
+ ASSERT_EQ(mut.GetStatus(), NS_ERROR_MALFORMED_URI);
+}
--- a/netwerk/test/gtest/moz.build
+++ b/netwerk/test/gtest/moz.build
@@ -2,16 +2,17 @@
# 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 += [
'TestHeaders.cpp',
'TestHttpAuthUtils.cpp',
+ 'TestMozURL.cpp',
'TestPartiallySeekableInputStream.cpp',
'TestProtocolProxyService.cpp',
'TestStandardURL.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul-gtest'