1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/DebugOnly.h"
9 #include "ipc/IPCMessageUtils.h"
11 #include "nsSimpleURI.h"
15 #include "nsURLHelper.h"
17 #include "nsIObjectInputStream.h"
18 #include "nsIObjectOutputStream.h"
21 #include "mozilla/MemoryReporting.h"
22 #include "mozilla/TextUtils.h"
23 #include "mozilla/ipc/URIUtils.h"
24 #include "nsIClassInfoImpl.h"
25 #include "nsIURIMutator.h"
26 #include "mozilla/net/MozURL.h"
27 #include "mozilla/StaticPrefs_network.h"
29 using namespace mozilla::ipc
;
34 static NS_DEFINE_CID(kThisSimpleURIImplementationCID
,
35 NS_THIS_SIMPLEURI_IMPLEMENTATION_CID
);
38 already_AddRefed
<nsSimpleURI
> nsSimpleURI::From(nsIURI
* aURI
) {
39 RefPtr
<nsSimpleURI
> uri
;
40 nsresult rv
= aURI
->QueryInterface(kThisSimpleURIImplementationCID
,
49 NS_IMPL_CLASSINFO(nsSimpleURI
, nullptr, nsIClassInfo::THREADSAFE
,
51 // Empty CI getter. We only need nsIClassInfo for Serialization
52 NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleURI
)
54 ////////////////////////////////////////////////////////////////////////////////
55 // nsSimpleURI methods:
57 NS_IMPL_ADDREF(nsSimpleURI
)
58 NS_IMPL_RELEASE(nsSimpleURI
)
59 NS_INTERFACE_TABLE_HEAD(nsSimpleURI
)
60 NS_INTERFACE_TABLE(nsSimpleURI
, nsIURI
, nsISerializable
)
61 NS_INTERFACE_TABLE_TO_MAP_SEGUE
62 NS_IMPL_QUERY_CLASSINFO(nsSimpleURI
)
63 if (aIID
.Equals(kThisSimpleURIImplementationCID
)) {
64 foundInterface
= static_cast<nsIURI
*>(this);
66 NS_INTERFACE_MAP_ENTRY(nsISizeOf
)
69 ////////////////////////////////////////////////////////////////////////////////
70 // nsISerializable methods:
73 nsSimpleURI::Read(nsIObjectInputStream
* aStream
) {
74 MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
75 return NS_ERROR_NOT_IMPLEMENTED
;
78 nsresult
nsSimpleURI::ReadPrivate(nsIObjectInputStream
* aStream
) {
82 rv
= aStream
->ReadBoolean(&isMutable
);
83 if (NS_FAILED(rv
)) return rv
;
86 rv
= aStream
->ReadCString(mScheme
);
87 if (NS_FAILED(rv
)) return rv
;
89 rv
= aStream
->ReadCString(mPath
);
90 if (NS_FAILED(rv
)) return rv
;
93 rv
= aStream
->ReadBoolean(&isRefValid
);
94 if (NS_FAILED(rv
)) return rv
;
95 mIsRefValid
= isRefValid
;
98 rv
= aStream
->ReadCString(mRef
);
99 if (NS_FAILED(rv
)) return rv
;
101 mRef
.Truncate(); // invariant: mRef should be empty when it's not valid
105 rv
= aStream
->ReadBoolean(&isQueryValid
);
106 if (NS_FAILED(rv
)) return rv
;
107 mIsQueryValid
= isQueryValid
;
110 rv
= aStream
->ReadCString(mQuery
);
111 if (NS_FAILED(rv
)) return rv
;
113 mQuery
.Truncate(); // invariant: mQuery should be empty when it's not valid
120 nsSimpleURI::Write(nsIObjectOutputStream
* aStream
) {
123 rv
= aStream
->WriteBoolean(false); // former mMutable
124 if (NS_FAILED(rv
)) return rv
;
126 rv
= aStream
->WriteStringZ(mScheme
.get());
127 if (NS_FAILED(rv
)) return rv
;
129 rv
= aStream
->WriteStringZ(mPath
.get());
130 if (NS_FAILED(rv
)) return rv
;
132 rv
= aStream
->WriteBoolean(mIsRefValid
);
133 if (NS_FAILED(rv
)) return rv
;
136 rv
= aStream
->WriteStringZ(mRef
.get());
137 if (NS_FAILED(rv
)) return rv
;
140 rv
= aStream
->WriteBoolean(mIsQueryValid
);
141 if (NS_FAILED(rv
)) return rv
;
144 rv
= aStream
->WriteStringZ(mQuery
.get());
145 if (NS_FAILED(rv
)) return rv
;
151 void nsSimpleURI::Serialize(URIParams
& aParams
) {
152 SimpleURIParams params
;
154 params
.scheme() = mScheme
;
155 params
.path() = mPath
;
160 params
.ref().SetIsVoid(true);
164 params
.query() = mQuery
;
166 params
.query().SetIsVoid(true);
172 bool nsSimpleURI::Deserialize(const URIParams
& aParams
) {
173 if (aParams
.type() != URIParams::TSimpleURIParams
) {
174 NS_ERROR("Received unknown parameters from the other process!");
178 const SimpleURIParams
& params
= aParams
.get_SimpleURIParams();
180 mScheme
= params
.scheme();
181 mPath
= params
.path();
183 if (params
.ref().IsVoid()) {
191 if (params
.query().IsVoid()) {
193 mIsQueryValid
= false;
195 mQuery
= params
.query();
196 mIsQueryValid
= true;
202 ////////////////////////////////////////////////////////////////////////////////
206 nsSimpleURI::GetSpec(nsACString
& result
) {
207 if (!result
.Assign(mScheme
, fallible
) || !result
.Append(":"_ns
, fallible
) ||
208 !result
.Append(mPath
, fallible
)) {
209 return NS_ERROR_OUT_OF_MEMORY
;
213 if (!result
.Append("?"_ns
, fallible
) || !result
.Append(mQuery
, fallible
)) {
214 return NS_ERROR_OUT_OF_MEMORY
;
217 MOZ_ASSERT(mQuery
.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
221 if (!result
.Append("#"_ns
, fallible
) || !result
.Append(mRef
, fallible
)) {
222 return NS_ERROR_OUT_OF_MEMORY
;
225 MOZ_ASSERT(mRef
.IsEmpty(), "mIsRefValid/mRef invariant broken");
231 // result may contain unescaped UTF-8 characters
233 nsSimpleURI::GetSpecIgnoringRef(nsACString
& result
) {
234 result
= mScheme
+ ":"_ns
+ mPath
;
236 result
+= "?"_ns
+ mQuery
;
242 nsSimpleURI::GetDisplaySpec(nsACString
& aUnicodeSpec
) {
243 return GetSpec(aUnicodeSpec
);
247 nsSimpleURI::GetDisplayHostPort(nsACString
& aUnicodeHostPort
) {
248 return GetHostPort(aUnicodeHostPort
);
252 nsSimpleURI::GetDisplayHost(nsACString
& aUnicodeHost
) {
253 return GetHost(aUnicodeHost
);
257 nsSimpleURI::GetDisplayPrePath(nsACString
& aPrePath
) {
258 return GetPrePath(aPrePath
);
262 nsSimpleURI::GetHasRef(bool* result
) {
263 *result
= mIsRefValid
;
268 nsSimpleURI::GetHasUserPass(bool* result
) {
273 nsresult
nsSimpleURI::SetSpecInternal(const nsACString
& aSpec
,
274 bool aStripWhitespace
) {
275 if (StaticPrefs::network_url_max_length() &&
276 aSpec
.Length() > StaticPrefs::network_url_max_length()) {
277 return NS_ERROR_MALFORMED_URI
;
280 nsresult rv
= net_ExtractURLScheme(aSpec
, mScheme
);
286 rv
= net_FilterAndEscapeURI(
287 aSpec
, esc_OnlyNonASCII
,
288 aStripWhitespace
? ASCIIMask::MaskWhitespace() : ASCIIMask::MaskCRLFTab(),
294 int32_t colonPos
= spec
.FindChar(':');
295 MOZ_ASSERT(colonPos
!= kNotFound
, "A colon should be in this string");
296 // This sets mPath, mQuery and mRef.
297 return SetPathQueryRefInternal(Substring(spec
, colonPos
+ 1));
301 nsSimpleURI::GetScheme(nsACString
& result
) {
306 nsresult
nsSimpleURI::SetScheme(const nsACString
& input
) {
307 // Strip tabs, newlines, carriage returns from input
308 nsAutoCString
scheme(input
);
309 scheme
.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
311 if (!net_IsValidScheme(scheme
)) {
312 NS_WARNING("the given url scheme contains invalid characters");
313 return NS_ERROR_MALFORMED_URI
;
317 ToLowerCase(mScheme
);
322 nsSimpleURI::GetPrePath(nsACString
& result
) {
323 result
= mScheme
+ ":"_ns
;
328 nsSimpleURI::GetUserPass(nsACString
& result
) { return NS_ERROR_FAILURE
; }
330 nsresult
nsSimpleURI::SetUserPass(const nsACString
& userPass
) {
331 return NS_ERROR_FAILURE
;
335 nsSimpleURI::GetUsername(nsACString
& result
) { return NS_ERROR_FAILURE
; }
337 nsresult
nsSimpleURI::SetUsername(const nsACString
& userName
) {
338 return NS_ERROR_FAILURE
;
342 nsSimpleURI::GetPassword(nsACString
& result
) { return NS_ERROR_FAILURE
; }
344 nsresult
nsSimpleURI::SetPassword(const nsACString
& password
) {
345 return NS_ERROR_FAILURE
;
349 nsSimpleURI::GetHostPort(nsACString
& result
) {
350 // Note: Audit all callers before changing this to return an empty
351 // string -- CAPS and UI code may depend on this throwing.
352 // Note: If this is changed, change GetAsciiHostPort as well.
353 return NS_ERROR_FAILURE
;
356 nsresult
nsSimpleURI::SetHostPort(const nsACString
& aValue
) {
357 return NS_ERROR_FAILURE
;
361 nsSimpleURI::GetHost(nsACString
& result
) {
362 // Note: Audit all callers before changing this to return an empty
363 // string -- CAPS and UI code depend on this throwing.
364 return NS_ERROR_FAILURE
;
367 nsresult
nsSimpleURI::SetHost(const nsACString
& host
) {
368 return NS_ERROR_FAILURE
;
372 nsSimpleURI::GetPort(int32_t* result
) {
373 // Note: Audit all callers before changing this to return an empty
374 // string -- CAPS and UI code may depend on this throwing.
375 return NS_ERROR_FAILURE
;
378 nsresult
nsSimpleURI::SetPort(int32_t port
) { return NS_ERROR_FAILURE
; }
381 nsSimpleURI::GetPathQueryRef(nsACString
& result
) {
384 result
+= "?"_ns
+ mQuery
;
387 result
+= "#"_ns
+ mRef
;
393 nsresult
nsSimpleURI::SetPathQueryRef(const nsACString
& aPath
) {
394 if (StaticPrefs::network_url_max_length() &&
395 aPath
.Length() > StaticPrefs::network_url_max_length()) {
396 return NS_ERROR_MALFORMED_URI
;
400 nsresult rv
= NS_EscapeURL(aPath
, esc_OnlyNonASCII
, path
, fallible
);
404 return SetPathQueryRefInternal(path
);
407 nsresult
nsSimpleURI::SetPathQueryRefInternal(const nsACString
& aPath
) {
409 const auto* start
= aPath
.BeginReading();
410 const auto* end
= aPath
.EndReading();
412 // Find the first instance of ? or # that marks the end of the path.
413 auto hashOrQueryFilter
= [](char c
) { return c
== '?' || c
== '#'; };
414 const auto* pathEnd
= std::find_if(start
, end
, hashOrQueryFilter
);
416 mIsQueryValid
= false;
423 if (!mPath
.Assign(Substring(start
, pathEnd
), fallible
)) {
424 return NS_ERROR_OUT_OF_MEMORY
;
427 if (pathEnd
== end
) {
431 const auto* queryEnd
=
432 std::find_if(pathEnd
, end
, [](char c
) { return c
== '#'; });
434 // Bug 1874118: If the URL does not contain a '?' or '?' appears after '#',
435 // SetQuery will not execute, preventing TrimTrailingCharactersFromPath
436 if (pathEnd
!= end
&& *pathEnd
== '?') {
437 rv
= SetQuery(Substring(pathEnd
, queryEnd
));
443 if (queryEnd
== end
) {
447 return SetRef(Substring(queryEnd
, end
));
451 nsSimpleURI::GetRef(nsACString
& result
) {
453 MOZ_ASSERT(mRef
.IsEmpty(), "mIsRefValid/mRef invariant broken");
462 // NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
463 // string (and will result in .spec and .path having a terminal #).
464 nsresult
nsSimpleURI::SetRef(const nsACString
& aRef
) {
465 if (StaticPrefs::network_url_max_length() &&
466 aRef
.Length() > StaticPrefs::network_url_max_length()) {
467 return NS_ERROR_MALFORMED_URI
;
472 NS_EscapeURL(aRef
, esc_OnlyNonASCII
| esc_Spaces
, ref
, fallible
);
478 // Empty string means to remove ref completely.
480 mRef
.Truncate(); // invariant: mRef should be empty when it's not valid
482 // Trim trailing invalid chars when ref and query are removed
483 if (mScheme
!= "javascript" && mRef
.IsEmpty() && mQuery
.IsEmpty()) {
484 TrimTrailingCharactersFromPath();
492 // Gracefully skip initial hash
494 mRef
= Substring(ref
, 1);
503 nsSimpleURI::Equals(nsIURI
* other
, bool* result
) {
504 return EqualsInternal(other
, eHonorRef
, result
);
508 nsSimpleURI::EqualsExceptRef(nsIURI
* other
, bool* result
) {
509 return EqualsInternal(other
, eIgnoreRef
, result
);
513 nsresult
nsSimpleURI::EqualsInternal(
514 nsIURI
* other
, nsSimpleURI::RefHandlingEnum refHandlingMode
, bool* result
) {
515 NS_ENSURE_ARG_POINTER(other
);
516 MOZ_ASSERT(result
, "null pointer");
518 RefPtr
<nsSimpleURI
> otherUri
;
519 nsresult rv
= other
->QueryInterface(kThisSimpleURIImplementationCID
,
520 getter_AddRefs(otherUri
));
526 *result
= EqualsInternal(otherUri
, refHandlingMode
);
530 bool nsSimpleURI::EqualsInternal(nsSimpleURI
* otherUri
,
531 RefHandlingEnum refHandlingMode
) {
532 bool result
= (mScheme
== otherUri
->mScheme
&& mPath
== otherUri
->mPath
);
535 result
= (mIsQueryValid
== otherUri
->mIsQueryValid
&&
536 (!mIsQueryValid
|| mQuery
== otherUri
->mQuery
));
539 if (result
&& refHandlingMode
== eHonorRef
) {
540 result
= (mIsRefValid
== otherUri
->mIsRefValid
&&
541 (!mIsRefValid
|| mRef
== otherUri
->mRef
));
548 nsSimpleURI::SchemeIs(const char* i_Scheme
, bool* o_Equals
) {
549 MOZ_ASSERT(o_Equals
, "null pointer");
555 const char* this_scheme
= mScheme
.get();
557 // mScheme is guaranteed to be lower case.
558 if (*i_Scheme
== *this_scheme
|| *i_Scheme
== (*this_scheme
- ('a' - 'A'))) {
559 *o_Equals
= nsCRT::strcasecmp(this_scheme
, i_Scheme
) == 0;
567 /* virtual */ nsSimpleURI
* nsSimpleURI::StartClone(
568 nsSimpleURI::RefHandlingEnum refHandlingMode
, const nsACString
& newRef
) {
569 nsSimpleURI
* url
= new nsSimpleURI();
570 SetRefOnClone(url
, refHandlingMode
, newRef
);
575 void nsSimpleURI::SetRefOnClone(nsSimpleURI
* url
,
576 nsSimpleURI::RefHandlingEnum refHandlingMode
,
577 const nsACString
& newRef
) {
578 if (refHandlingMode
== eHonorRef
) {
580 url
->mIsRefValid
= mIsRefValid
;
581 } else if (refHandlingMode
== eReplaceRef
) {
586 nsresult
nsSimpleURI::Clone(nsIURI
** result
) {
587 return CloneInternal(eHonorRef
, ""_ns
, result
);
590 nsresult
nsSimpleURI::CloneInternal(
591 nsSimpleURI::RefHandlingEnum refHandlingMode
, const nsACString
& newRef
,
593 RefPtr
<nsSimpleURI
> url
= StartClone(refHandlingMode
, newRef
);
594 if (!url
) return NS_ERROR_OUT_OF_MEMORY
;
596 url
->mScheme
= mScheme
;
599 url
->mIsQueryValid
= mIsQueryValid
;
600 if (url
->mIsQueryValid
) {
601 url
->mQuery
= mQuery
;
609 nsSimpleURI::Resolve(const nsACString
& relativePath
, nsACString
& result
) {
610 nsAutoCString scheme
;
611 nsresult rv
= net_ExtractURLScheme(relativePath
, scheme
);
612 if (NS_SUCCEEDED(rv
)) {
613 result
= relativePath
;
618 rv
= GetAsciiSpec(spec
);
619 if (NS_WARN_IF(NS_FAILED(rv
))) {
620 // If getting the spec fails for some reason, preserve behaviour and just
621 // return the relative path.
622 result
= relativePath
;
627 rv
= MozURL::Init(getter_AddRefs(url
), spec
);
628 if (NS_WARN_IF(NS_FAILED(rv
))) {
629 // If parsing the current url fails, we revert to the previous behaviour
630 // and just return the relative path.
631 result
= relativePath
;
636 rv
= MozURL::Init(getter_AddRefs(url2
), relativePath
, url
);
637 if (NS_WARN_IF(NS_FAILED(rv
))) {
638 // If parsing the relative url fails, we revert to the previous behaviour
639 // and just return the relative path.
640 result
= relativePath
;
644 result
= url2
->Spec();
649 nsSimpleURI::GetAsciiSpec(nsACString
& aResult
) {
650 nsresult rv
= GetSpec(aResult
);
651 if (NS_FAILED(rv
)) return rv
;
652 MOZ_ASSERT(IsAscii(aResult
), "The spec should be ASCII");
657 nsSimpleURI::GetAsciiHostPort(nsACString
& result
) {
658 // XXX This behavior mimics GetHostPort.
659 return NS_ERROR_FAILURE
;
663 nsSimpleURI::GetAsciiHost(nsACString
& result
) {
668 //----------------------------------------------------------------------------
669 // nsSimpleURI::nsISizeOf
670 //----------------------------------------------------------------------------
672 size_t nsSimpleURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
673 return mScheme
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
) +
674 mPath
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
) +
675 mQuery
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
) +
676 mRef
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
679 size_t nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
680 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
684 nsSimpleURI::GetFilePath(nsACString
& aFilePath
) {
689 nsresult
nsSimpleURI::SetFilePath(const nsACString
& aFilePath
) {
690 if (mPath
.IsEmpty() || mPath
.First() != '/') {
692 return NS_ERROR_MALFORMED_URI
;
694 const char* current
= aFilePath
.BeginReading();
695 const char* end
= aFilePath
.EndReading();
697 // Only go up to the first ? or # symbol
698 for (; current
< end
; ++current
) {
699 if (*current
== '?' || *current
== '#') {
703 return SetPathQueryRef(
704 nsDependentCSubstring(aFilePath
.BeginReading(), current
));
708 nsSimpleURI::GetQuery(nsACString
& aQuery
) {
709 if (!mIsQueryValid
) {
710 MOZ_ASSERT(mQuery
.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
719 nsSimpleURI::GetHasQuery(bool* result
) {
720 *result
= mIsQueryValid
;
724 nsresult
nsSimpleURI::SetQuery(const nsACString
& aQuery
) {
725 if (StaticPrefs::network_url_max_length() &&
726 aQuery
.Length() > StaticPrefs::network_url_max_length()) {
727 return NS_ERROR_MALFORMED_URI
;
730 nsresult rv
= NS_EscapeURL(aQuery
, esc_OnlyNonASCII
, query
, fallible
);
735 if (query
.IsEmpty()) {
736 // Empty string means to remove query completely.
737 mIsQueryValid
= false;
738 mQuery
.Truncate(); // invariant: mQuery should be empty when it's not valid
740 // Trim trailing invalid chars when ref and query are removed
741 if (mScheme
!= "javascript" && mRef
.IsEmpty() && mQuery
.IsEmpty()) {
742 TrimTrailingCharactersFromPath();
748 mIsQueryValid
= true;
750 // Gracefully skip initial question mark
751 if (query
[0] == '?') {
752 mQuery
= Substring(query
, 1);
760 nsresult
nsSimpleURI::SetQueryWithEncoding(const nsACString
& aQuery
,
761 const Encoding
* aEncoding
) {
762 return SetQuery(aQuery
);
765 void nsSimpleURI::TrimTrailingCharactersFromPath() {
766 const auto* start
= mPath
.BeginReading();
767 const auto* end
= mPath
.EndReading();
769 auto charFilter
= [](char c
) { return static_cast<uint8_t>(c
) > 0x20; };
771 std::find_if(std::reverse_iterator
<decltype(end
)>(end
),
772 std::reverse_iterator
<decltype(start
)>(start
), charFilter
)
775 auto trailCount
= std::distance(newEnd
, end
);
777 mPath
.Truncate(mPath
.Length() - trailCount
);
781 // Queries this list of interfaces. If none match, it queries mURI.
782 NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleURI::Mutator
, nsIURISetters
,
783 nsIURIMutator
, nsISerializable
,
787 nsSimpleURI::Mutate(nsIURIMutator
** aMutator
) {
788 RefPtr
<nsSimpleURI::Mutator
> mutator
= new nsSimpleURI::Mutator();
789 nsresult rv
= mutator
->InitFromURI(this);
793 mutator
.forget(aMutator
);
798 } // namespace mozilla