1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=4 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ipc/IPCMessageUtils.h"
9 #include "nsASCIIMask.h"
10 #include "nsStandardURL.h"
14 #include "nsIObjectInputStream.h"
15 #include "nsIObjectOutputStream.h"
16 #include "nsIIDNService.h"
17 #include "mozilla/Logging.h"
18 #include "nsIURLParser.h"
19 #include "nsPrintfCString.h"
21 #include "mozilla/MemoryReporting.h"
22 #include "mozilla/ipc/URIUtils.h"
23 #include "mozilla/ScopeExit.h"
24 #include "mozilla/StaticPrefs_network.h"
25 #include "mozilla/TextUtils.h"
27 #include "nsContentUtils.h"
29 #include "nsReadableUtils.h"
30 #include "mozilla/net/MozURL_ffi.h"
31 #include "mozilla/TextUtils.h"
32 #include "mozilla/Utf8.h"
33 #include "nsIClassInfoImpl.h"
37 // setenv MOZ_LOG nsStandardURL:5
39 static mozilla::LazyLogModule
gStandardURLLog("nsStandardURL");
41 // The Chromium code defines its own LOG macro which we don't want
43 #define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
45 #define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
47 using namespace mozilla::ipc
;
52 static NS_DEFINE_CID(kThisImplCID
, NS_THIS_STANDARDURL_IMPL_CID
);
54 // This will always be initialized and destroyed on the main thread, but
55 // can be safely used on other threads.
56 StaticRefPtr
<nsIIDNService
> nsStandardURL::gIDN
;
58 // This value will only be updated on the main thread once.
59 static Atomic
<bool, Relaxed
> gInitialized
{false};
61 const char nsStandardURL::gHostLimitDigits
[] = {'/', '\\', '?', '#', 0};
63 // Invalid host characters
64 // Note that the array below will be initialized at compile time,
65 // so we do not need to "optimize" TestForInvalidHostCharacters.
67 constexpr bool TestForInvalidHostCharacters(char c
) {
69 // CONTROL_CHARACTERS " #/:?@[\\]*<>|\"";
70 return (c
> 0 && c
< 32) || // The control characters are [1, 31]
71 c
== 0x7F || // // DEL (delete)
72 c
== ' ' || c
== '#' || c
== '/' || c
== ':' || c
== '?' || c
== '@' ||
73 c
== '[' || c
== '\\' || c
== ']' || c
== '*' || c
== '<' ||
75 #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
76 // Mailnews %-escapes file paths into URLs.
77 c
== '>' || c
== '|' || c
== '"';
79 c
== '>' || c
== '|' || c
== '"' || c
== '%';
82 constexpr ASCIIMaskArray sInvalidHostChars
=
83 CreateASCIIMask(TestForInvalidHostCharacters
);
85 //----------------------------------------------------------------------------
86 // nsStandardURL::nsSegmentEncoder
87 //----------------------------------------------------------------------------
89 nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding
* encoding
)
90 : mEncoding(encoding
) {
91 if (mEncoding
== UTF_8_ENCODING
) {
96 int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount(
97 const char* aStr
, const URLSegment
& aSeg
, int16_t aMask
, nsCString
& aOut
,
98 bool& aAppended
, uint32_t aExtraLen
) {
99 // aExtraLen is characters outside the segment that will be
100 // added when the segment is not empty (like the @ following
102 if (!aStr
|| aSeg
.mLen
<= 0) {
103 // Empty segment, so aExtraLen not added per above.
108 uint32_t origLen
= aOut
.Length();
110 Span
<const char> span
= Span(aStr
+ aSeg
.mPos
, aSeg
.mLen
);
112 // first honor the origin charset if appropriate. as an optimization,
113 // only do this if the segment is non-ASCII. Further, if mEncoding is
114 // null, then the origin charset is UTF-8 and there is nothing to do.
117 if (MOZ_UNLIKELY(mEncoding
== ISO_2022_JP_ENCODING
)) {
118 upTo
= Encoding::ISO2022JPASCIIValidUpTo(AsBytes(span
));
120 upTo
= Encoding::ASCIIValidUpTo(AsBytes(span
));
122 if (upTo
!= span
.Length()) {
123 // we have to encode this segment
125 Span
<char> buffer
= Span(bufferArr
);
127 auto encoder
= mEncoding
->NewEncoder();
129 nsAutoCString valid
; // has to be declared in this scope
130 if (MOZ_UNLIKELY(!IsUtf8(span
.From(upTo
)))) {
131 MOZ_ASSERT_UNREACHABLE("Invalid UTF-8 passed to nsStandardURL.");
132 // It's UB to pass invalid UTF-8 to
133 // EncodeFromUTF8WithoutReplacement(), so let's make our input valid
134 // UTF-8 by replacing invalid sequences with the REPLACEMENT
136 UTF_8_ENCODING
->Decode(
137 nsDependentCSubstring(span
.Elements(), span
.Length()), valid
);
138 // This assigment is OK. `span` can't be used after `valid` has
139 // been destroyed because the only way out of the scope that `valid`
140 // was declared in is via return inside the loop below. Specifically,
141 // the return is the only way out of the loop.
145 size_t totalRead
= 0;
147 auto [encoderResult
, read
, written
] =
148 encoder
->EncodeFromUTF8WithoutReplacement(
149 AsBytes(span
.From(totalRead
)), AsWritableBytes(buffer
), true);
151 auto bufferWritten
= buffer
.To(written
);
152 if (!NS_EscapeURLSpan(bufferWritten
, aMask
, aOut
)) {
153 aOut
.Append(bufferWritten
);
155 if (encoderResult
== kInputEmpty
) {
157 // Difference between original and current output
158 // string lengths plus extra length
159 return aOut
.Length() - origLen
+ aExtraLen
;
161 if (encoderResult
== kOutputFull
) {
164 aOut
.AppendLiteral("%26%23");
165 aOut
.AppendInt(encoderResult
);
166 aOut
.AppendLiteral("%3B");
170 "There's supposed to be no way out of the above loop except return.");
174 if (NS_EscapeURLSpan(span
, aMask
, aOut
)) {
176 // Difference between original and current output
177 // string lengths plus extra length
178 return aOut
.Length() - origLen
+ aExtraLen
;
181 // Original segment length plus extra length
182 return span
.Length() + aExtraLen
;
185 const nsACString
& nsStandardURL::nsSegmentEncoder::EncodeSegment(
186 const nsACString
& str
, int16_t mask
, nsCString
& result
) {
189 EncodeSegmentCount(str
.BeginReading(text
), URLSegment(0, str
.Length()), mask
,
197 //----------------------------------------------------------------------------
198 // nsStandardURL <public>
199 //----------------------------------------------------------------------------
201 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
202 static StaticMutex gAllURLsMutex MOZ_UNANNOTATED
;
203 static LinkedList
<nsStandardURL
> gAllURLs
;
206 nsStandardURL::nsStandardURL(bool aSupportsFileURL
, bool aTrackURL
)
207 : mURLType(URLTYPE_STANDARD
),
208 mSupportsFileURL(aSupportsFileURL
),
209 mCheckedIfHostA(false) {
210 LOG(("Creating nsStandardURL @%p\n", this));
212 // gInitialized changes value only once (false->true) on the main thread.
213 // It's OK to race here because in the worst case we'll just
214 // dispatch a noop runnable to the main thread.
215 MOZ_ASSERT(gInitialized
);
217 // default parser in case nsIStandardURL::Init is never called
218 mParser
= net_GetStdURLParser();
220 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
222 StaticMutexAutoLock
lock(gAllURLsMutex
);
223 gAllURLs
.insertBack(this);
228 bool nsStandardURL::IsValid() {
229 auto checkSegment
= [&](const nsStandardURL::URLSegment
& aSeg
) {
230 #ifdef EARLY_BETA_OR_EARLIER
231 // If the parity is not the same, we assume that this is caused by a memory
232 // error. In this case, we think this URLSegment is valid.
233 if ((aSeg
.mPos
.Parity() != aSeg
.mPos
.CalculateParity()) ||
234 (aSeg
.mLen
.Parity() != aSeg
.mLen
.CalculateParity())) {
240 if (NS_WARN_IF(aSeg
.mLen
< -1)) {
243 if (aSeg
.mLen
== -1) {
247 // Points out of string
248 if (NS_WARN_IF(aSeg
.mPos
+ aSeg
.mLen
> mSpec
.Length())) {
253 if (NS_WARN_IF(aSeg
.mPos
+ aSeg
.mLen
< aSeg
.mPos
)) {
260 bool allSegmentsValid
= checkSegment(mScheme
) && checkSegment(mAuthority
) &&
261 checkSegment(mUsername
) && checkSegment(mPassword
) &&
262 checkSegment(mHost
) && checkSegment(mPath
) &&
263 checkSegment(mFilepath
) && checkSegment(mDirectory
) &&
264 checkSegment(mBasename
) && checkSegment(mExtension
) &&
265 checkSegment(mQuery
) && checkSegment(mRef
);
266 if (!allSegmentsValid
) {
270 if (mScheme
.mPos
!= 0) {
277 void nsStandardURL::SanityCheck() {
280 "mLen:%zX, mScheme (%X,%X), mAuthority (%X,%X), mUsername (%X,%X), "
281 "mPassword (%X,%X), mHost (%X,%X), mPath (%X,%X), mFilepath (%X,%X), "
282 "mDirectory (%X,%X), mBasename (%X,%X), mExtension (%X,%X), mQuery "
283 "(%X,%X), mRef (%X,%X)",
284 mSpec
.Length(), (uint32_t)mScheme
.mPos
, (int32_t)mScheme
.mLen
,
285 (uint32_t)mAuthority
.mPos
, (int32_t)mAuthority
.mLen
,
286 (uint32_t)mUsername
.mPos
, (int32_t)mUsername
.mLen
,
287 (uint32_t)mPassword
.mPos
, (int32_t)mPassword
.mLen
, (uint32_t)mHost
.mPos
,
288 (int32_t)mHost
.mLen
, (uint32_t)mPath
.mPos
, (int32_t)mPath
.mLen
,
289 (uint32_t)mFilepath
.mPos
, (int32_t)mFilepath
.mLen
,
290 (uint32_t)mDirectory
.mPos
, (int32_t)mDirectory
.mLen
,
291 (uint32_t)mBasename
.mPos
, (int32_t)mBasename
.mLen
,
292 (uint32_t)mExtension
.mPos
, (int32_t)mExtension
.mLen
,
293 (uint32_t)mQuery
.mPos
, (int32_t)mQuery
.mLen
, (uint32_t)mRef
.mPos
,
295 CrashReporter::RecordAnnotationNSCString(
296 CrashReporter::Annotation::URLSegments
, msg
);
298 MOZ_CRASH("nsStandardURL::SanityCheck failed");
302 nsStandardURL::~nsStandardURL() {
303 LOG(("Destroying nsStandardURL @%p\n", this));
305 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
307 StaticMutexAutoLock
lock(gAllURLsMutex
);
315 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
316 struct DumpLeakedURLs
{
317 DumpLeakedURLs() = default;
321 DumpLeakedURLs::~DumpLeakedURLs() {
322 MOZ_ASSERT(NS_IsMainThread());
323 StaticMutexAutoLock
lock(gAllURLsMutex
);
324 if (!gAllURLs
.isEmpty()) {
325 printf("Leaked URLs:\n");
326 for (auto* url
: gAllURLs
) {
334 void nsStandardURL::InitGlobalObjects() {
335 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
343 nsCOMPtr
<nsIIDNService
> serv(do_GetService(NS_IDNSERVICE_CONTRACTID
));
347 MOZ_DIAGNOSTIC_ASSERT(gIDN
);
349 // Make sure nsURLHelper::InitGlobals() gets called on the main thread
350 nsCOMPtr
<nsIURLParser
> parser
= net_GetStdURLParser();
351 MOZ_DIAGNOSTIC_ASSERT(parser
);
355 void nsStandardURL::ShutdownGlobalObjects() {
356 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
359 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
361 // This instanciates a dummy class, and will trigger the class
362 // destructor when libxul is unloaded. This is equivalent to atexit(),
363 // but gracefully handles dlclose().
364 StaticMutexAutoLock
lock(gAllURLsMutex
);
365 static DumpLeakedURLs d
;
370 //----------------------------------------------------------------------------
371 // nsStandardURL <private>
372 //----------------------------------------------------------------------------
374 void nsStandardURL::Clear() {
397 void nsStandardURL::InvalidateCache(bool invalidateCachedFile
) {
398 if (invalidateCachedFile
) {
403 // Return the number of "dots" in the string, or -1 if invalid. Note that the
404 // number of relevant entries in the bases/starts/ends arrays is number of
407 // length is assumed to be <= host.Length(); the caller is responsible for that
409 // Note that the value returned is guaranteed to be in [-1, 3] range.
410 inline int32_t ValidateIPv4Number(const nsACString
& host
, int32_t bases
[4],
411 int32_t dotIndex
[3], bool& onlyBase10
,
412 int32_t length
, bool trailingDot
) {
413 MOZ_ASSERT(length
<= (int32_t)host
.Length());
418 bool lastWasNumber
= false; // We count on this being false for i == 0
419 int32_t dotCount
= 0;
422 for (int32_t i
= 0; i
< length
; i
++) {
423 char current
= host
[i
];
424 if (current
== '.') {
425 // A dot should not follow a dot, or be first - it can follow an x though.
426 if (!(lastWasNumber
||
427 (i
>= 2 && (host
[i
- 1] == 'X' || host
[i
- 1] == 'x') &&
428 host
[i
- 2] == '0')) ||
429 (i
== (length
- 1) && trailingDot
)) {
436 lastWasNumber
= false;
437 dotIndex
[dotCount
] = i
;
439 } else if (current
== 'X' || current
== 'x') {
440 if (!lastWasNumber
|| // An X should not follow an X or a dot or be first
441 i
== (length
- 1) || // No trailing Xs allowed
443 i
!= 1) || // If we had no dots, an X should be second
444 host
[i
- 1] != '0' || // X should always follow a 0. Guaranteed i >
445 // 0 as lastWasNumber is true
447 host
[i
- 2] != '.')) { // And that zero follows a dot if it exists
450 lastWasNumber
= false;
451 bases
[dotCount
] = 16;
454 } else if (current
== '0') {
455 if (i
< length
- 1 && // Trailing zero doesn't signal octal
456 host
[i
+ 1] != '.' && // Lone zero is not octal
457 (i
== 0 || host
[i
- 1] == '.')) { // Zero at start or following a dot
458 // is a candidate for octal
459 bases
[dotCount
] = 8; // This will turn to 16 above if X shows up
462 lastWasNumber
= true;
464 } else if (current
>= '1' && current
<= '7') {
465 lastWasNumber
= true;
467 } else if (current
>= '8' && current
<= '9') {
468 if (bases
[dotCount
] == 8) {
471 lastWasNumber
= true;
473 } else if ((current
>= 'a' && current
<= 'f') ||
474 (current
>= 'A' && current
<= 'F')) {
475 if (bases
[dotCount
] != 16) {
478 lastWasNumber
= true;
488 inline nsresult
ParseIPv4Number10(const nsACString
& input
, uint32_t& number
,
489 uint32_t maxNumber
) {
491 const char* current
= input
.BeginReading();
492 const char* end
= input
.EndReading();
493 for (; current
< end
; ++current
) {
495 MOZ_ASSERT(c
>= '0' && c
<= '9');
499 if (value
<= maxNumber
) {
506 return NS_ERROR_FAILURE
;
509 inline nsresult
ParseIPv4Number(const nsACString
& input
, int32_t base
,
510 uint32_t& number
, uint32_t maxNumber
) {
511 // Accumulate in the 64-bit value
513 const char* current
= input
.BeginReading();
514 const char* end
= input
.EndReading();
526 for (; current
< end
; ++current
) {
529 MOZ_ASSERT((base
== 10 && IsAsciiDigit(c
)) ||
530 (base
== 8 && c
>= '0' && c
<= '7') ||
531 (base
== 16 && IsAsciiHexDigit(c
)));
532 if (IsAsciiDigit(c
)) {
534 } else if (c
>= 'a' && c
<= 'f') {
535 value
+= c
- 'a' + 10;
536 } else if (c
>= 'A' && c
<= 'F') {
537 value
+= c
- 'A' + 10;
541 if (value
<= maxNumber
) {
548 return NS_ERROR_FAILURE
;
551 // IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
553 nsresult
nsStandardURL::NormalizeIPv4(const nsACString
& host
,
555 int32_t bases
[4] = {10, 10, 10, 10};
556 bool onlyBase10
= true; // Track this as a special case
557 int32_t dotIndex
[3]; // The positions of the dots in the string
559 // Use "length" rather than host.Length() after call to
560 // ValidateIPv4Number because of potential trailing period.
561 nsDependentCSubstring filteredHost
;
562 bool trailingDot
= false;
563 if (host
.Length() > 0 && host
.Last() == '.') {
565 filteredHost
.Rebind(host
.BeginReading(), host
.Length() - 1);
567 filteredHost
.Rebind(host
.BeginReading(), host
.Length());
570 int32_t length
= static_cast<int32_t>(filteredHost
.Length());
571 int32_t dotCount
= ValidateIPv4Number(filteredHost
, bases
, dotIndex
,
572 onlyBase10
, length
, trailingDot
);
573 if (dotCount
< 0 || length
<= 0) {
574 return NS_ERROR_FAILURE
;
577 // Max values specified by the spec
578 static const uint32_t upperBounds
[] = {0xffffffffu
, 0xffffffu
, 0xffffu
,
581 int32_t start
= (dotCount
> 0 ? dotIndex
[dotCount
- 1] + 1 : 0);
583 // parse the last part first
585 // Doing a special case for all items being base 10 gives ~35% speedup
587 ? ParseIPv4Number10(Substring(host
, start
, length
- start
), ipv4
,
588 upperBounds
[dotCount
])
589 : ParseIPv4Number(Substring(host
, start
, length
- start
),
590 bases
[dotCount
], ipv4
, upperBounds
[dotCount
]));
591 if (NS_FAILED(res
)) {
592 return NS_ERROR_FAILURE
;
595 // parse remaining parts starting from first part
596 int32_t lastUsed
= -1;
597 for (int32_t i
= 0; i
< dotCount
; i
++) {
599 start
= lastUsed
+ 1;
600 lastUsed
= dotIndex
[i
];
602 (onlyBase10
? ParseIPv4Number10(
603 Substring(host
, start
, lastUsed
- start
), number
, 255)
604 : ParseIPv4Number(Substring(host
, start
, lastUsed
- start
),
605 bases
[i
], number
, 255));
606 if (NS_FAILED(res
)) {
607 return NS_ERROR_FAILURE
;
609 ipv4
+= number
<< (8 * (3 - i
));
612 // A special case for ipv4 URL like "127." should have the same result as
614 if (dotCount
== 1 && dotIndex
[0] == length
- 1) {
615 ipv4
= (ipv4
& 0xff000000) >> 24;
618 uint8_t ipSegments
[4];
619 NetworkEndian::writeUint32(ipSegments
, ipv4
);
620 result
= nsPrintfCString("%d.%d.%d.%d", ipSegments
[0], ipSegments
[1],
621 ipSegments
[2], ipSegments
[3]);
625 nsIIDNService
* nsStandardURL::GetIDNService() { return gIDN
.get(); }
627 nsresult
nsStandardURL::NormalizeIDN(const nsCString
& host
, nsCString
& result
) {
629 mDisplayHost
.Truncate();
633 return NS_ERROR_UNEXPECTED
;
636 // Even if it's already ACE, we must still call ConvertUTF8toACE in order
637 // for the input normalization to take place.
638 rv
= gIDN
->ConvertUTF8toACE(host
, result
);
643 // If the ASCII representation doesn't contain the xn-- token then we don't
644 // need to call ConvertToDisplayIDN as that would not change anything.
645 if (!StringBeginsWith(result
, "xn--"_ns
) &&
646 result
.Find(".xn--"_ns
) == kNotFound
) {
647 mCheckedIfHostA
= true;
652 nsAutoCString displayHost
;
653 rv
= gIDN
->ConvertToDisplayIDN(result
, &isAscii
, displayHost
);
658 mCheckedIfHostA
= true;
660 mDisplayHost
= displayHost
;
665 bool nsStandardURL::ValidIPv6orHostname(const char* host
, uint32_t length
) {
666 if (!host
|| !*host
) {
667 // Should not be NULL or empty string
671 if (length
!= strlen(host
)) {
676 bool openBracket
= host
[0] == '[';
677 bool closeBracket
= host
[length
- 1] == ']';
679 if (openBracket
&& closeBracket
) {
680 return net_IsValidIPv6Addr(Substring(host
+ 1, length
- 2));
683 if (openBracket
|| closeBracket
) {
684 // Fail if only one of the brackets is present
688 const char* end
= host
+ length
;
689 const char* iter
= host
;
690 for (; iter
!= end
&& *iter
; ++iter
) {
691 if (ASCIIMask::IsMasked(sInvalidHostChars
, *iter
)) {
698 void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag
, char* path
) {
699 net_CoalesceDirs(coalesceFlag
, path
);
700 int32_t newLen
= strlen(path
);
701 if (newLen
< mPath
.mLen
) {
702 int32_t diff
= newLen
- mPath
.mLen
;
704 mDirectory
.mLen
+= diff
;
705 mFilepath
.mLen
+= diff
;
706 ShiftFromBasename(diff
);
710 uint32_t nsStandardURL::AppendSegmentToBuf(char* buf
, uint32_t i
,
712 const URLSegment
& segInput
,
713 URLSegment
& segOutput
,
714 const nsCString
* escapedStr
,
715 bool useEscaped
, int32_t* diff
) {
716 MOZ_ASSERT(segInput
.mLen
== segOutput
.mLen
);
722 if (segInput
.mLen
> 0) {
725 segOutput
.mLen
= escapedStr
->Length();
726 *diff
= segOutput
.mLen
- segInput
.mLen
;
727 memcpy(buf
+ i
, escapedStr
->get(), segOutput
.mLen
);
729 memcpy(buf
+ i
, str
+ segInput
.mPos
, segInput
.mLen
);
739 uint32_t nsStandardURL::AppendToBuf(char* buf
, uint32_t i
, const char* str
,
741 memcpy(buf
+ i
, str
, len
);
745 static bool ContainsOnlyAsciiDigits(const nsDependentCSubstring
& input
) {
746 for (const auto* c
= input
.BeginReading(); c
< input
.EndReading(); c
++) {
747 if (!IsAsciiDigit(*c
)) {
755 static bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring
& input
) {
756 for (const auto* c
= input
.BeginReading(); c
< input
.EndReading(); c
++) {
757 if (!IsAsciiHexDigit(*c
)) {
764 // https://url.spec.whatwg.org/#ends-in-a-number-checker
765 static bool EndsInANumber(const nsCString
& input
) {
766 // 1. Let parts be the result of strictly splitting input on U+002E (.).
767 nsTArray
<nsDependentCSubstring
> parts
;
768 for (const nsDependentCSubstring
& part
: input
.Split('.')) {
769 parts
.AppendElement(part
);
772 if (parts
.Length() == 0) {
776 // 2.If the last item in parts is the empty string, then:
777 // 1. If parts’s size is 1, then return false.
778 // 2. Remove the last item from parts.
779 if (parts
.LastElement().IsEmpty()) {
780 if (parts
.Length() == 1) {
783 Unused
<< parts
.PopLastElement();
786 // 3. Let last be the last item in parts.
787 const nsDependentCSubstring
& last
= parts
.LastElement();
789 // 4. If last is non-empty and contains only ASCII digits, then return true.
790 // The erroneous input "09" will be caught by the IPv4 parser at a later
792 if (!last
.IsEmpty()) {
793 if (ContainsOnlyAsciiDigits(last
)) {
798 // 5. If parsing last as an IPv4 number does not return failure, then return
799 // true. This is equivalent to checking that last is "0X" or "0x", followed by
800 // zero or more ASCII hex digits.
801 if (StringBeginsWith(last
, "0x"_ns
) || StringBeginsWith(last
, "0X"_ns
)) {
802 if (ContainsOnlyAsciiHexDigits(Substring(last
, 2))) {
811 // 1- escape url segments (for improved GetSpec efficiency)
812 // 2- allocate spec buffer
813 // 3- write url segments
814 // 4- update url segment positions and lengths
815 nsresult
nsStandardURL::BuildNormalizedSpec(const char* spec
,
816 const Encoding
* encoding
) {
817 // Assumptions: all member URLSegments must be relative the |spec| argument
818 // passed to this function.
820 // buffers for holding escaped url segments (these will remain empty unless
821 // escaping is required).
822 nsAutoCString encUsername
, encPassword
, encHost
, encDirectory
, encBasename
,
823 encExtension
, encQuery
, encRef
;
824 bool useEncUsername
, useEncPassword
, useEncHost
= false, useEncDirectory
,
825 useEncBasename
, useEncExtension
,
826 useEncQuery
, useEncRef
;
827 nsAutoCString portbuf
;
830 // escape each URL segment, if necessary, and calculate approximate normalized
833 // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]
835 uint32_t approxLen
= 0;
837 // the scheme is already ASCII
838 if (mScheme
.mLen
> 0) {
840 mScheme
.mLen
+ 3; // includes room for "://", which we insert always
843 // encode URL segments; convert UTF-8 to origin charset and possibly escape.
844 // results written to encXXX variables only if |spec| is not already in the
845 // appropriate encoding.
847 nsSegmentEncoder encoder
;
848 nsSegmentEncoder
queryEncoder(encoding
);
850 approxLen
+= encoder
.EncodeSegmentCount(spec
, mUsername
, esc_Username
,
851 encUsername
, useEncUsername
, 0);
852 approxLen
+= 1; // reserve length for @
853 // :password - we insert the ':' even if there's no actual password if
854 // "user:@" was in the spec
855 if (mPassword
.mLen
> 0) {
856 approxLen
+= 1 + encoder
.EncodeSegmentCount(spec
, mPassword
, esc_Password
,
857 encPassword
, useEncPassword
);
859 // mHost is handled differently below due to encoding differences
860 MOZ_ASSERT(mPort
>= -1, "Invalid negative mPort");
861 if (mPort
!= -1 && mPort
!= mDefaultPort
) {
863 portbuf
.AppendInt(mPort
);
864 approxLen
+= portbuf
.Length() + 1;
868 1; // reserve space for possible leading '/' - may not be needed
869 // Should just use mPath? These are pessimistic, and thus waste space
870 approxLen
+= encoder
.EncodeSegmentCount(spec
, mDirectory
, esc_Directory
,
871 encDirectory
, useEncDirectory
, 1);
872 approxLen
+= encoder
.EncodeSegmentCount(spec
, mBasename
, esc_FileBaseName
,
873 encBasename
, useEncBasename
);
874 approxLen
+= encoder
.EncodeSegmentCount(spec
, mExtension
, esc_FileExtension
,
875 encExtension
, useEncExtension
, 1);
877 // These next ones *always* add their leading character even if length is 0
878 // Handles items like "http://#"
880 if (mQuery
.mLen
>= 0) {
881 approxLen
+= 1 + queryEncoder
.EncodeSegmentCount(spec
, mQuery
, esc_Query
,
882 encQuery
, useEncQuery
);
886 if (mRef
.mLen
>= 0) {
887 approxLen
+= 1 + encoder
.EncodeSegmentCount(spec
, mRef
, esc_Ref
, encRef
,
892 // do not escape the hostname, if IPv6 address literal, mHost will
893 // already point to a [ ] delimited IPv6 address literal.
894 // However, perform Unicode normalization on it, as IDN does.
895 // Note that we don't disallow URLs without a host - file:, etc
896 if (mHost
.mLen
> 0) {
897 nsAutoCString tempHost
;
898 NS_UnescapeURL(spec
+ mHost
.mPos
, mHost
.mLen
, esc_AlwaysCopy
| esc_Host
,
900 if (tempHost
.Contains('\0')) {
901 return NS_ERROR_MALFORMED_URI
; // null embedded in hostname
903 if (tempHost
.Contains(' ')) {
904 return NS_ERROR_MALFORMED_URI
; // don't allow spaces in the hostname
906 nsresult rv
= NormalizeIDN(tempHost
, encHost
);
910 if (!SegmentIs(spec
, mScheme
, "resource") &&
911 !SegmentIs(spec
, mScheme
, "chrome")) {
912 nsAutoCString ipString
;
913 if (encHost
.Length() > 0 && encHost
.First() == '[' &&
914 encHost
.Last() == ']' &&
915 ValidIPv6orHostname(encHost
.get(), encHost
.Length())) {
916 rv
= (nsresult
)rusturl_parse_ipv6addr(&encHost
, &ipString
);
922 if (EndsInANumber(encHost
)) {
923 rv
= NormalizeIPv4(encHost
, ipString
);
932 // NormalizeIDN always copies, if the call was successful.
934 approxLen
+= encHost
.Length();
936 if (!ValidIPv6orHostname(encHost
.BeginReading(), encHost
.Length())) {
937 return NS_ERROR_MALFORMED_URI
;
940 // empty host means empty mDisplayHost
941 mDisplayHost
.Truncate();
942 mCheckedIfHostA
= true;
945 // We must take a copy of every single segment because they are pointing to
946 // the |spec| while we are changing their value, in case we must use
948 URLSegment
username(mUsername
);
949 URLSegment
password(mPassword
);
950 URLSegment
host(mHost
);
951 URLSegment
path(mPath
);
952 URLSegment
directory(mDirectory
);
953 URLSegment
basename(mBasename
);
954 URLSegment
extension(mExtension
);
955 URLSegment
query(mQuery
);
956 URLSegment
ref(mRef
);
958 // The encoded string could be longer than the original input, so we need
959 // to check the final URI isn't longer than the max length.
960 if (approxLen
+ 1 > StaticPrefs::network_standard_url_max_length()) {
961 return NS_ERROR_MALFORMED_URI
;
965 // generate the normalized URL string
967 // approxLen should be correct or 1 high
968 if (!mSpec
.SetLength(approxLen
+ 1,
969 fallible
)) { // buf needs a trailing '\0' below
970 return NS_ERROR_OUT_OF_MEMORY
;
972 char* buf
= mSpec
.BeginWriting();
976 if (mScheme
.mLen
> 0) {
977 i
= AppendSegmentToBuf(buf
, i
, spec
, mScheme
, mScheme
);
978 net_ToLowerCase(buf
+ mScheme
.mPos
, mScheme
.mLen
);
979 i
= AppendToBuf(buf
, i
, "://", 3);
982 // record authority starting position
986 if (mUsername
.mLen
> 0 || mPassword
.mLen
> 0) {
987 if (mUsername
.mLen
> 0) {
988 i
= AppendSegmentToBuf(buf
, i
, spec
, username
, mUsername
, &encUsername
,
989 useEncUsername
, &diff
);
990 ShiftFromPassword(diff
);
994 if (password
.mLen
> 0) {
996 i
= AppendSegmentToBuf(buf
, i
, spec
, password
, mPassword
, &encPassword
,
997 useEncPassword
, &diff
);
1000 mPassword
.mLen
= -1;
1004 mUsername
.mLen
= -1;
1005 mPassword
.mLen
= -1;
1007 if (host
.mLen
> 0) {
1008 i
= AppendSegmentToBuf(buf
, i
, spec
, host
, mHost
, &encHost
, useEncHost
,
1010 ShiftFromPath(diff
);
1012 net_ToLowerCase(buf
+ mHost
.mPos
, mHost
.mLen
);
1013 MOZ_ASSERT(mPort
>= -1, "Invalid negative mPort");
1014 if (mPort
!= -1 && mPort
!= mDefaultPort
) {
1016 // Already formatted while building approxLen
1017 i
= AppendToBuf(buf
, i
, portbuf
.get(), portbuf
.Length());
1021 // record authority length
1022 mAuthority
.mLen
= i
- mAuthority
.mPos
;
1024 // path must always start with a "/"
1025 if (mPath
.mLen
<= 0) {
1026 LOG(("setting path=/"));
1027 mDirectory
.mPos
= mFilepath
.mPos
= mPath
.mPos
= i
;
1028 mDirectory
.mLen
= mFilepath
.mLen
= mPath
.mLen
= 1;
1029 // basename must exist, even if empty (bug 113508)
1030 mBasename
.mPos
= i
+ 1;
1034 uint32_t leadingSlash
= 0;
1035 if (spec
[path
.mPos
] != '/') {
1036 LOG(("adding leading slash to path\n"));
1039 // basename must exist, even if empty (bugs 113508, 429347)
1040 if (mBasename
.mLen
== -1) {
1041 mBasename
.mPos
= basename
.mPos
= i
;
1042 mBasename
.mLen
= basename
.mLen
= 0;
1046 // record corrected (file)path starting position
1047 mPath
.mPos
= mFilepath
.mPos
= i
- leadingSlash
;
1049 i
= AppendSegmentToBuf(buf
, i
, spec
, directory
, mDirectory
, &encDirectory
,
1050 useEncDirectory
, &diff
);
1051 ShiftFromBasename(diff
);
1053 // the directory must end with a '/'
1054 if (buf
[i
- 1] != '/') {
1059 i
= AppendSegmentToBuf(buf
, i
, spec
, basename
, mBasename
, &encBasename
,
1060 useEncBasename
, &diff
);
1061 ShiftFromExtension(diff
);
1063 // make corrections to directory segment if leadingSlash
1065 mDirectory
.mPos
= mPath
.mPos
;
1066 if (mDirectory
.mLen
>= 0) {
1067 mDirectory
.mLen
+= leadingSlash
;
1069 mDirectory
.mLen
= 1;
1073 if (mExtension
.mLen
>= 0) {
1075 i
= AppendSegmentToBuf(buf
, i
, spec
, extension
, mExtension
, &encExtension
,
1076 useEncExtension
, &diff
);
1077 ShiftFromQuery(diff
);
1079 // calculate corrected filepath length
1080 mFilepath
.mLen
= i
- mFilepath
.mPos
;
1082 if (mQuery
.mLen
>= 0) {
1084 i
= AppendSegmentToBuf(buf
, i
, spec
, query
, mQuery
, &encQuery
,
1085 useEncQuery
, &diff
);
1088 if (mRef
.mLen
>= 0) {
1090 i
= AppendSegmentToBuf(buf
, i
, spec
, ref
, mRef
, &encRef
, useEncRef
,
1093 // calculate corrected path length
1094 mPath
.mLen
= i
- mPath
.mPos
;
1099 // https://url.spec.whatwg.org/#path-state (1.4.1.2)
1100 // https://url.spec.whatwg.org/#windows-drive-letter
1101 if (SegmentIs(buf
, mScheme
, "file")) {
1102 char* path
= &buf
[mPath
.mPos
];
1103 if (mPath
.mLen
>= 3 && path
[0] == '/' && IsAsciiAlpha(path
[1]) &&
1105 buf
[mPath
.mPos
+ 2] = ':';
1109 if (mDirectory
.mLen
> 0) {
1110 netCoalesceFlags coalesceFlag
= NET_COALESCE_NORMAL
;
1111 if (SegmentIs(buf
, mScheme
, "ftp")) {
1113 (netCoalesceFlags
)(coalesceFlag
| NET_COALESCE_ALLOW_RELATIVE_ROOT
|
1114 NET_COALESCE_DOUBLE_SLASH_IS_ROOT
);
1116 CoalescePath(coalesceFlag
, buf
+ mDirectory
.mPos
);
1118 mSpec
.Truncate(strlen(buf
));
1119 NS_ASSERTION(mSpec
.Length() <= approxLen
,
1120 "We've overflowed the mSpec buffer!");
1121 MOZ_ASSERT(mSpec
.Length() <= StaticPrefs::network_standard_url_max_length(),
1122 "The spec should never be this long, we missed a check.");
1124 MOZ_ASSERT(mUsername
.mLen
!= 0 && mPassword
.mLen
!= 0);
1128 bool nsStandardURL::SegmentIs(const URLSegment
& seg
, const char* val
,
1130 // one or both may be null
1131 if (!val
|| mSpec
.IsEmpty()) {
1132 return (!val
&& (mSpec
.IsEmpty() || seg
.mLen
< 0));
1137 // if the first |seg.mLen| chars of |val| match, then |val| must
1138 // also be null terminated at |seg.mLen|.
1140 return !nsCRT::strncasecmp(mSpec
.get() + seg
.mPos
, val
, seg
.mLen
) &&
1141 (val
[seg
.mLen
] == '\0');
1144 return !strncmp(mSpec
.get() + seg
.mPos
, val
, seg
.mLen
) &&
1145 (val
[seg
.mLen
] == '\0');
1148 bool nsStandardURL::SegmentIs(const char* spec
, const URLSegment
& seg
,
1149 const char* val
, bool ignoreCase
) {
1150 // one or both may be null
1151 if (!val
|| !spec
) {
1152 return (!val
&& (!spec
|| seg
.mLen
< 0));
1157 // if the first |seg.mLen| chars of |val| match, then |val| must
1158 // also be null terminated at |seg.mLen|.
1160 return !nsCRT::strncasecmp(spec
+ seg
.mPos
, val
, seg
.mLen
) &&
1161 (val
[seg
.mLen
] == '\0');
1164 return !strncmp(spec
+ seg
.mPos
, val
, seg
.mLen
) && (val
[seg
.mLen
] == '\0');
1167 bool nsStandardURL::SegmentIs(const URLSegment
& seg1
, const char* val
,
1168 const URLSegment
& seg2
, bool ignoreCase
) {
1169 if (seg1
.mLen
!= seg2
.mLen
) {
1172 if (seg1
.mLen
== -1 || (!val
&& mSpec
.IsEmpty())) {
1173 return true; // both are empty
1179 return !nsCRT::strncasecmp(mSpec
.get() + seg1
.mPos
, val
+ seg2
.mPos
,
1183 return !strncmp(mSpec
.get() + seg1
.mPos
, val
+ seg2
.mPos
, seg1
.mLen
);
1186 int32_t nsStandardURL::ReplaceSegment(uint32_t pos
, uint32_t len
,
1187 const char* val
, uint32_t valLen
) {
1188 if (val
&& valLen
) {
1190 mSpec
.Insert(val
, pos
, valLen
);
1192 mSpec
.Replace(pos
, len
, nsDependentCString(val
, valLen
));
1194 return valLen
- len
;
1197 // else remove the specified segment
1198 mSpec
.Cut(pos
, len
);
1199 return -int32_t(len
);
1202 int32_t nsStandardURL::ReplaceSegment(uint32_t pos
, uint32_t len
,
1203 const nsACString
& val
) {
1205 mSpec
.Insert(val
, pos
);
1207 mSpec
.Replace(pos
, len
, val
);
1209 return val
.Length() - len
;
1212 nsresult
nsStandardURL::ParseURL(const char* spec
, int32_t specLen
) {
1215 if (specLen
> (int32_t)StaticPrefs::network_standard_url_max_length()) {
1216 return NS_ERROR_MALFORMED_URI
;
1220 // parse given URL string
1222 uint32_t schemePos
= mScheme
.mPos
;
1223 int32_t schemeLen
= mScheme
.mLen
;
1224 uint32_t authorityPos
= mAuthority
.mPos
;
1225 int32_t authorityLen
= mAuthority
.mLen
;
1226 uint32_t pathPos
= mPath
.mPos
;
1227 int32_t pathLen
= mPath
.mLen
;
1228 rv
= mParser
->ParseURL(spec
, specLen
, &schemePos
, &schemeLen
, &authorityPos
,
1229 &authorityLen
, &pathPos
, &pathLen
);
1230 if (NS_FAILED(rv
)) {
1233 mScheme
.mPos
= schemePos
;
1234 mScheme
.mLen
= schemeLen
;
1235 mAuthority
.mPos
= authorityPos
;
1236 mAuthority
.mLen
= authorityLen
;
1237 mPath
.mPos
= pathPos
;
1238 mPath
.mLen
= pathLen
;
1241 if (mScheme
.mLen
<= 0) {
1242 printf("spec=%s\n", spec
);
1243 NS_WARNING("malformed url: no scheme");
1247 if (mAuthority
.mLen
> 0) {
1248 uint32_t usernamePos
= mUsername
.mPos
;
1249 int32_t usernameLen
= mUsername
.mLen
;
1250 uint32_t passwordPos
= mPassword
.mPos
;
1251 int32_t passwordLen
= mPassword
.mLen
;
1252 uint32_t hostPos
= mHost
.mPos
;
1253 int32_t hostLen
= mHost
.mLen
;
1254 rv
= mParser
->ParseAuthority(spec
+ mAuthority
.mPos
, mAuthority
.mLen
,
1255 &usernamePos
, &usernameLen
, &passwordPos
,
1256 &passwordLen
, &hostPos
, &hostLen
, &mPort
);
1257 if (NS_FAILED(rv
)) {
1261 mUsername
.mPos
= usernamePos
;
1262 mUsername
.mLen
= usernameLen
;
1263 mPassword
.mPos
= passwordPos
;
1264 mPassword
.mLen
= passwordLen
;
1265 mHost
.mPos
= hostPos
;
1266 mHost
.mLen
= hostLen
;
1268 // Don't allow mPort to be set to this URI's default port
1269 if (mPort
== mDefaultPort
) {
1273 mUsername
.mPos
+= mAuthority
.mPos
;
1274 mPassword
.mPos
+= mAuthority
.mPos
;
1275 mHost
.mPos
+= mAuthority
.mPos
;
1278 if (mPath
.mLen
> 0) {
1279 rv
= ParsePath(spec
, mPath
.mPos
, mPath
.mLen
);
1285 nsresult
nsStandardURL::ParsePath(const char* spec
, uint32_t pathPos
,
1287 LOG(("ParsePath: %s pathpos %d len %d\n", spec
, pathPos
, pathLen
));
1289 if (pathLen
> (int32_t)StaticPrefs::network_standard_url_max_length()) {
1290 return NS_ERROR_MALFORMED_URI
;
1293 uint32_t filePathPos
= mFilepath
.mPos
;
1294 int32_t filePathLen
= mFilepath
.mLen
;
1295 uint32_t queryPos
= mQuery
.mPos
;
1296 int32_t queryLen
= mQuery
.mLen
;
1297 uint32_t refPos
= mRef
.mPos
;
1298 int32_t refLen
= mRef
.mLen
;
1300 mParser
->ParsePath(spec
+ pathPos
, pathLen
, &filePathPos
, &filePathLen
,
1301 &queryPos
, &queryLen
, &refPos
, &refLen
);
1302 if (NS_FAILED(rv
)) {
1306 mFilepath
.mPos
= filePathPos
;
1307 mFilepath
.mLen
= filePathLen
;
1308 mQuery
.mPos
= queryPos
;
1309 mQuery
.mLen
= queryLen
;
1313 mFilepath
.mPos
+= pathPos
;
1314 mQuery
.mPos
+= pathPos
;
1315 mRef
.mPos
+= pathPos
;
1317 if (mFilepath
.mLen
> 0) {
1318 uint32_t directoryPos
= mDirectory
.mPos
;
1319 int32_t directoryLen
= mDirectory
.mLen
;
1320 uint32_t basenamePos
= mBasename
.mPos
;
1321 int32_t basenameLen
= mBasename
.mLen
;
1322 uint32_t extensionPos
= mExtension
.mPos
;
1323 int32_t extensionLen
= mExtension
.mLen
;
1324 rv
= mParser
->ParseFilePath(spec
+ mFilepath
.mPos
, mFilepath
.mLen
,
1325 &directoryPos
, &directoryLen
, &basenamePos
,
1326 &basenameLen
, &extensionPos
, &extensionLen
);
1327 if (NS_FAILED(rv
)) {
1331 mDirectory
.mPos
= directoryPos
;
1332 mDirectory
.mLen
= directoryLen
;
1333 mBasename
.mPos
= basenamePos
;
1334 mBasename
.mLen
= basenameLen
;
1335 mExtension
.mPos
= extensionPos
;
1336 mExtension
.mLen
= extensionLen
;
1338 mDirectory
.mPos
+= mFilepath
.mPos
;
1339 mBasename
.mPos
+= mFilepath
.mPos
;
1340 mExtension
.mPos
+= mFilepath
.mPos
;
1345 char* nsStandardURL::AppendToSubstring(uint32_t pos
, int32_t len
,
1347 // Verify pos and length are within boundaries
1348 if (pos
> mSpec
.Length()) {
1354 if ((uint32_t)len
> (mSpec
.Length() - pos
)) {
1361 uint32_t tailLen
= strlen(tail
);
1363 // Check for int overflow for proposed length of combined string
1364 if (UINT32_MAX
- ((uint32_t)len
+ 1) < tailLen
) {
1368 char* result
= (char*)moz_xmalloc(len
+ tailLen
+ 1);
1369 memcpy(result
, mSpec
.get() + pos
, len
);
1370 memcpy(result
+ len
, tail
, tailLen
);
1371 result
[len
+ tailLen
] = '\0';
1375 nsresult
nsStandardURL::ReadSegment(nsIBinaryInputStream
* stream
,
1379 uint32_t pos
= seg
.mPos
;
1380 rv
= stream
->Read32(&pos
);
1381 if (NS_FAILED(rv
)) {
1387 uint32_t len
= seg
.mLen
;
1388 rv
= stream
->Read32(&len
);
1389 if (NS_FAILED(rv
)) {
1393 CheckedInt
<int32_t> checkedLen(len
);
1394 if (!checkedLen
.isValid()) {
1403 nsresult
nsStandardURL::WriteSegment(nsIBinaryOutputStream
* stream
,
1404 const URLSegment
& seg
) {
1407 rv
= stream
->Write32(seg
.mPos
);
1408 if (NS_FAILED(rv
)) {
1412 rv
= stream
->Write32(uint32_t(seg
.mLen
));
1413 if (NS_FAILED(rv
)) {
1420 #define SHIFT_FROM(name, what) \
1421 void nsStandardURL::name(int32_t diff) { \
1422 if (!diff) return; \
1423 if ((what).mLen >= 0) { \
1424 CheckedInt<int32_t> pos = (uint32_t)(what).mPos; \
1426 MOZ_ASSERT(pos.isValid()); \
1427 (what).mPos = pos.value(); \
1429 MOZ_RELEASE_ASSERT((what).mLen == -1); \
1432 #define SHIFT_FROM_NEXT(name, what, next) \
1433 SHIFT_FROM(name, what) \
1437 #define SHIFT_FROM_LAST(name, what) \
1438 SHIFT_FROM(name, what) \
1441 SHIFT_FROM_NEXT(ShiftFromAuthority
, mAuthority
, ShiftFromUsername
)
1442 SHIFT_FROM_NEXT(ShiftFromUsername
, mUsername
, ShiftFromPassword
)
1443 SHIFT_FROM_NEXT(ShiftFromPassword
, mPassword
, ShiftFromHost
)
1444 SHIFT_FROM_NEXT(ShiftFromHost
, mHost
, ShiftFromPath
)
1445 SHIFT_FROM_NEXT(ShiftFromPath
, mPath
, ShiftFromFilepath
)
1446 SHIFT_FROM_NEXT(ShiftFromFilepath
, mFilepath
, ShiftFromDirectory
)
1447 SHIFT_FROM_NEXT(ShiftFromDirectory
, mDirectory
, ShiftFromBasename
)
1448 SHIFT_FROM_NEXT(ShiftFromBasename
, mBasename
, ShiftFromExtension
)
1449 SHIFT_FROM_NEXT(ShiftFromExtension
, mExtension
, ShiftFromQuery
)
1450 SHIFT_FROM_NEXT(ShiftFromQuery
, mQuery
, ShiftFromRef
)
1451 SHIFT_FROM_LAST(ShiftFromRef
, mRef
)
1453 //----------------------------------------------------------------------------
1454 // nsStandardURL::nsIClassInfo
1455 //----------------------------------------------------------------------------
1457 NS_IMPL_CLASSINFO(nsStandardURL
, nullptr, nsIClassInfo::THREADSAFE
,
1459 // Empty CI getter. We only need nsIClassInfo for Serialization
1460 NS_IMPL_CI_INTERFACE_GETTER0(nsStandardURL
)
1462 //----------------------------------------------------------------------------
1463 // nsStandardURL::nsISupports
1464 //----------------------------------------------------------------------------
1466 NS_IMPL_ADDREF(nsStandardURL
)
1467 NS_IMPL_RELEASE(nsStandardURL
)
1469 NS_INTERFACE_MAP_BEGIN(nsStandardURL
)
1470 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIStandardURL
)
1471 NS_INTERFACE_MAP_ENTRY(nsIURI
)
1472 NS_INTERFACE_MAP_ENTRY(nsIURL
)
1473 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL
, mSupportsFileURL
)
1474 NS_INTERFACE_MAP_ENTRY(nsIStandardURL
)
1475 NS_INTERFACE_MAP_ENTRY(nsISerializable
)
1476 NS_IMPL_QUERY_CLASSINFO(nsStandardURL
)
1477 NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI
)
1478 // see nsStandardURL::Equals
1479 if (aIID
.Equals(kThisImplCID
)) {
1480 foundInterface
= static_cast<nsIURI
*>(this);
1482 NS_INTERFACE_MAP_ENTRY(nsISizeOf
)
1483 NS_INTERFACE_MAP_END
1485 //----------------------------------------------------------------------------
1486 // nsStandardURL::nsIURI
1487 //----------------------------------------------------------------------------
1489 // result may contain unescaped UTF-8 characters
1491 nsStandardURL::GetSpec(nsACString
& result
) {
1492 MOZ_ASSERT(mSpec
.Length() <= StaticPrefs::network_standard_url_max_length(),
1493 "The spec should never be this long, we missed a check.");
1498 // result may contain unescaped UTF-8 characters
1500 nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString
& result
) {
1501 nsresult rv
= GetSpec(result
);
1502 if (NS_FAILED(rv
)) {
1505 if (mPassword
.mLen
> 0) {
1506 result
.ReplaceLiteral(mPassword
.mPos
, mPassword
.mLen
, "****");
1511 // result may contain unescaped UTF-8 characters
1513 nsStandardURL::GetSpecIgnoringRef(nsACString
& result
) {
1514 // URI without ref is 0 to one char before ref
1515 if (mRef
.mLen
< 0) {
1516 return GetSpec(result
);
1519 URLSegment
noRef(0, mRef
.mPos
- 1);
1520 result
= Segment(noRef
);
1521 MOZ_ASSERT(mCheckedIfHostA
);
1525 nsresult
nsStandardURL::CheckIfHostIsAscii() {
1527 if (mCheckedIfHostA
) {
1531 mCheckedIfHostA
= true;
1534 return NS_ERROR_NOT_INITIALIZED
;
1537 nsAutoCString displayHost
;
1539 rv
= gIDN
->ConvertToDisplayIDN(Host(), &isAscii
, displayHost
);
1540 if (NS_FAILED(rv
)) {
1541 mDisplayHost
.Truncate();
1542 mCheckedIfHostA
= false;
1547 mDisplayHost
= displayHost
;
1554 nsStandardURL::GetDisplaySpec(nsACString
& aUnicodeSpec
) {
1555 aUnicodeSpec
.Assign(mSpec
);
1556 MOZ_ASSERT(mCheckedIfHostA
);
1557 if (!mDisplayHost
.IsEmpty()) {
1558 aUnicodeSpec
.Replace(mHost
.mPos
, mHost
.mLen
, mDisplayHost
);
1565 nsStandardURL::GetDisplayHostPort(nsACString
& aUnicodeHostPort
) {
1566 nsAutoCString unicodeHostPort
;
1568 nsresult rv
= GetDisplayHost(unicodeHostPort
);
1569 if (NS_FAILED(rv
)) {
1573 if (StringBeginsWith(Hostport(), "["_ns
)) {
1574 aUnicodeHostPort
.AssignLiteral("[");
1575 aUnicodeHostPort
.Append(unicodeHostPort
);
1576 aUnicodeHostPort
.AppendLiteral("]");
1578 aUnicodeHostPort
.Assign(unicodeHostPort
);
1581 uint32_t pos
= mHost
.mPos
+ mHost
.mLen
;
1582 if (pos
< mPath
.mPos
) {
1583 aUnicodeHostPort
+= Substring(mSpec
, pos
, mPath
.mPos
- pos
);
1590 nsStandardURL::GetDisplayHost(nsACString
& aUnicodeHost
) {
1591 MOZ_ASSERT(mCheckedIfHostA
);
1592 if (mDisplayHost
.IsEmpty()) {
1593 return GetAsciiHost(aUnicodeHost
);
1596 aUnicodeHost
= mDisplayHost
;
1600 // result may contain unescaped UTF-8 characters
1602 nsStandardURL::GetPrePath(nsACString
& result
) {
1604 MOZ_ASSERT(mCheckedIfHostA
);
1608 // result may contain unescaped UTF-8 characters
1610 nsStandardURL::GetDisplayPrePath(nsACString
& result
) {
1612 MOZ_ASSERT(mCheckedIfHostA
);
1613 if (!mDisplayHost
.IsEmpty()) {
1614 result
.Replace(mHost
.mPos
, mHost
.mLen
, mDisplayHost
);
1619 // result is strictly US-ASCII
1621 nsStandardURL::GetScheme(nsACString
& result
) {
1626 // result may contain unescaped UTF-8 characters
1628 nsStandardURL::GetUserPass(nsACString
& result
) {
1629 result
= Userpass();
1633 // result may contain unescaped UTF-8 characters
1635 nsStandardURL::GetUsername(nsACString
& result
) {
1636 result
= Username();
1640 // result may contain unescaped UTF-8 characters
1642 nsStandardURL::GetPassword(nsACString
& result
) {
1643 result
= Password();
1648 nsStandardURL::GetHostPort(nsACString
& result
) {
1649 return GetAsciiHostPort(result
);
1653 nsStandardURL::GetHost(nsACString
& result
) { return GetAsciiHost(result
); }
1656 nsStandardURL::GetPort(int32_t* result
) {
1657 // should never be more than 16 bit
1658 MOZ_ASSERT(mPort
<= std::numeric_limits
<uint16_t>::max());
1663 // result may contain unescaped UTF-8 characters
1665 nsStandardURL::GetPathQueryRef(nsACString
& result
) {
1672 nsStandardURL::GetAsciiSpec(nsACString
& result
) {
1679 nsStandardURL::GetAsciiHostPort(nsACString
& result
) {
1680 result
= Hostport();
1686 nsStandardURL::GetAsciiHost(nsACString
& result
) {
1691 static bool IsSpecialProtocol(const nsACString
& input
) {
1692 nsACString::const_iterator start
, end
;
1693 input
.BeginReading(start
);
1694 nsACString::const_iterator
iterator(start
);
1695 input
.EndReading(end
);
1697 while (iterator
!= end
&& *iterator
!= ':') {
1701 nsAutoCString
protocol(nsDependentCSubstring(start
.get(), iterator
.get()));
1703 return protocol
.LowerCaseEqualsLiteral("http") ||
1704 protocol
.LowerCaseEqualsLiteral("https") ||
1705 protocol
.LowerCaseEqualsLiteral("ftp") ||
1706 protocol
.LowerCaseEqualsLiteral("ws") ||
1707 protocol
.LowerCaseEqualsLiteral("wss") ||
1708 protocol
.LowerCaseEqualsLiteral("file") ||
1709 protocol
.LowerCaseEqualsLiteral("gopher");
1712 nsresult
nsStandardURL::SetSpecInternal(const nsACString
& input
) {
1713 return SetSpecWithEncoding(input
, nullptr);
1716 nsresult
nsStandardURL::SetSpecWithEncoding(const nsACString
& input
,
1717 const Encoding
* encoding
) {
1718 const nsPromiseFlatCString
& flat
= PromiseFlatCString(input
);
1719 LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat
.get()));
1721 if (input
.Length() > StaticPrefs::network_standard_url_max_length()) {
1722 return NS_ERROR_MALFORMED_URI
;
1725 // filter out unexpected chars "\r\n\t" if necessary
1726 nsAutoCString filteredURI
;
1727 net_FilterURIString(flat
, filteredURI
);
1729 if (filteredURI
.Length() == 0) {
1730 return NS_ERROR_MALFORMED_URI
;
1733 // Make a backup of the current URL
1734 nsStandardURL
prevURL(false, false);
1735 prevURL
.CopyMembers(this, eHonorRef
, ""_ns
);
1738 if (IsSpecialProtocol(filteredURI
)) {
1739 // Bug 652186: Replace all backslashes with slashes when parsing paths
1740 // Stop when we reach the query or the hash.
1741 auto* start
= filteredURI
.BeginWriting();
1742 auto* end
= filteredURI
.EndWriting();
1743 while (start
!= end
) {
1744 if (*start
== '?' || *start
== '#') {
1747 if (*start
== '\\') {
1754 const char* spec
= filteredURI
.get();
1755 int32_t specLength
= filteredURI
.Length();
1757 // parse the given URL...
1758 nsresult rv
= ParseURL(spec
, specLength
);
1759 if (mScheme
.mLen
<= 0) {
1760 rv
= NS_ERROR_MALFORMED_URI
;
1762 if (NS_SUCCEEDED(rv
)) {
1763 // finally, use the URLSegment member variables to build a normalized
1765 rv
= BuildNormalizedSpec(spec
, encoding
);
1768 // Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
1769 if (mURLType
== URLTYPE_AUTHORITY
&& mHost
.mLen
== -1) {
1770 rv
= NS_ERROR_MALFORMED_URI
;
1773 if (NS_FAILED(rv
)) {
1775 // If parsing the spec has failed, restore the old URL
1776 // so we don't end up with an empty URL.
1777 CopyMembers(&prevURL
, eHonorRef
, ""_ns
);
1781 if (LOG_ENABLED()) {
1782 LOG((" spec = %s\n", mSpec
.get()));
1783 LOG((" port = %d\n", mPort
));
1784 LOG((" scheme = (%u,%d)\n", (uint32_t)mScheme
.mPos
,
1785 (int32_t)mScheme
.mLen
));
1786 LOG((" authority = (%u,%d)\n", (uint32_t)mAuthority
.mPos
,
1787 (int32_t)mAuthority
.mLen
));
1788 LOG((" username = (%u,%d)\n", (uint32_t)mUsername
.mPos
,
1789 (int32_t)mUsername
.mLen
));
1790 LOG((" password = (%u,%d)\n", (uint32_t)mPassword
.mPos
,
1791 (int32_t)mPassword
.mLen
));
1792 LOG((" hostname = (%u,%d)\n", (uint32_t)mHost
.mPos
, (int32_t)mHost
.mLen
));
1793 LOG((" path = (%u,%d)\n", (uint32_t)mPath
.mPos
, (int32_t)mPath
.mLen
));
1794 LOG((" filepath = (%u,%d)\n", (uint32_t)mFilepath
.mPos
,
1795 (int32_t)mFilepath
.mLen
));
1796 LOG((" directory = (%u,%d)\n", (uint32_t)mDirectory
.mPos
,
1797 (int32_t)mDirectory
.mLen
));
1798 LOG((" basename = (%u,%d)\n", (uint32_t)mBasename
.mPos
,
1799 (int32_t)mBasename
.mLen
));
1800 LOG((" extension = (%u,%d)\n", (uint32_t)mExtension
.mPos
,
1801 (int32_t)mExtension
.mLen
));
1802 LOG((" query = (%u,%d)\n", (uint32_t)mQuery
.mPos
,
1803 (int32_t)mQuery
.mLen
));
1804 LOG((" ref = (%u,%d)\n", (uint32_t)mRef
.mPos
, (int32_t)mRef
.mLen
));
1811 nsresult
nsStandardURL::SetScheme(const nsACString
& input
) {
1812 // Strip tabs, newlines, carriage returns from input
1813 nsAutoCString
scheme(input
);
1814 scheme
.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
1816 LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme
.get()));
1818 if (scheme
.IsEmpty()) {
1819 NS_WARNING("cannot remove the scheme from an url");
1820 return NS_ERROR_UNEXPECTED
;
1822 if (mScheme
.mLen
< 0) {
1823 NS_WARNING("uninitialized");
1824 return NS_ERROR_NOT_INITIALIZED
;
1827 if (!net_IsValidScheme(scheme
)) {
1828 NS_WARNING("the given url scheme contains invalid characters");
1829 return NS_ERROR_UNEXPECTED
;
1832 if (mSpec
.Length() + input
.Length() - Scheme().Length() >
1833 StaticPrefs::network_standard_url_max_length()) {
1834 return NS_ERROR_MALFORMED_URI
;
1837 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
1841 int32_t shift
= ReplaceSegment(mScheme
.mPos
, mScheme
.mLen
, scheme
);
1844 mScheme
.mLen
= scheme
.Length();
1845 ShiftFromAuthority(shift
);
1848 // ensure new scheme is lowercase
1850 // XXX the string code unfortunately doesn't provide a ToLowerCase
1851 // that operates on a substring.
1852 net_ToLowerCase((char*)mSpec
.get(), mScheme
.mLen
);
1854 // If the scheme changes the default port also changes.
1855 if (Scheme() == "http"_ns
|| Scheme() == "ws"_ns
) {
1857 } else if (Scheme() == "https"_ns
|| Scheme() == "wss"_ns
) {
1860 if (mPort
== mDefaultPort
) {
1861 MOZ_ALWAYS_SUCCEEDS(SetPort(-1));
1867 nsresult
nsStandardURL::SetUserPass(const nsACString
& input
) {
1868 const nsPromiseFlatCString
& userpass
= PromiseFlatCString(input
);
1870 LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass
.get()));
1872 if (mURLType
== URLTYPE_NO_AUTHORITY
) {
1873 if (userpass
.IsEmpty()) {
1876 NS_WARNING("cannot set user:pass on no-auth url");
1877 return NS_ERROR_UNEXPECTED
;
1879 if (mAuthority
.mLen
< 0) {
1880 NS_WARNING("uninitialized");
1881 return NS_ERROR_NOT_INITIALIZED
;
1884 if (mSpec
.Length() + input
.Length() - Userpass(true).Length() >
1885 StaticPrefs::network_standard_url_max_length()) {
1886 return NS_ERROR_MALFORMED_URI
;
1889 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
1892 NS_ASSERTION(mHost
.mLen
>= 0, "uninitialized");
1895 uint32_t usernamePos
, passwordPos
;
1896 int32_t usernameLen
, passwordLen
;
1898 rv
= mParser
->ParseUserInfo(userpass
.get(), userpass
.Length(), &usernamePos
,
1899 &usernameLen
, &passwordPos
, &passwordLen
);
1900 if (NS_FAILED(rv
)) {
1904 // build new user:pass in |buf|
1906 if (usernameLen
> 0 || passwordLen
> 0) {
1907 nsSegmentEncoder encoder
;
1909 usernameLen
= encoder
.EncodeSegmentCount(
1910 userpass
.get(), URLSegment(usernamePos
, usernameLen
),
1911 esc_Username
| esc_AlwaysCopy
, buf
, ignoredOut
);
1912 if (passwordLen
> 0) {
1914 passwordLen
= encoder
.EncodeSegmentCount(
1915 userpass
.get(), URLSegment(passwordPos
, passwordLen
),
1916 esc_Password
| esc_AlwaysCopy
, buf
, ignoredOut
);
1920 if (mUsername
.mLen
< 0 && mPassword
.mLen
< 0) {
1927 if (mUsername
.mLen
< 0 && mPassword
.mLen
< 0) {
1928 // no existing user:pass
1929 if (!buf
.IsEmpty()) {
1930 mSpec
.Insert(buf
, mHost
.mPos
);
1931 mUsername
.mPos
= mHost
.mPos
;
1932 shift
= buf
.Length();
1935 // replace existing user:pass
1936 uint32_t userpassLen
= 0;
1937 if (mUsername
.mLen
> 0) {
1938 userpassLen
+= mUsername
.mLen
;
1940 if (mPassword
.mLen
> 0) {
1941 userpassLen
+= (mPassword
.mLen
+ 1);
1943 if (buf
.IsEmpty()) {
1944 // remove `@` character too
1947 mSpec
.Replace(mAuthority
.mPos
, userpassLen
, buf
);
1948 shift
= buf
.Length() - userpassLen
;
1951 ShiftFromHost(shift
);
1952 MOZ_DIAGNOSTIC_ASSERT(mAuthority
.mLen
>= -shift
);
1953 mAuthority
.mLen
+= shift
;
1955 // update positions and lengths
1956 mUsername
.mLen
= usernameLen
> 0 ? usernameLen
: -1;
1957 mUsername
.mPos
= mAuthority
.mPos
;
1958 mPassword
.mLen
= passwordLen
> 0 ? passwordLen
: -1;
1959 if (passwordLen
> 0) {
1960 if (mUsername
.mLen
> 0) {
1961 mPassword
.mPos
= mUsername
.mPos
+ mUsername
.mLen
+ 1;
1963 mPassword
.mPos
= mAuthority
.mPos
+ 1;
1967 MOZ_ASSERT(mUsername
.mLen
!= 0 && mPassword
.mLen
!= 0);
1971 nsresult
nsStandardURL::SetUsername(const nsACString
& input
) {
1972 const nsPromiseFlatCString
& username
= PromiseFlatCString(input
);
1974 LOG(("nsStandardURL::SetUsername [username=%s]\n", username
.get()));
1976 if (mURLType
== URLTYPE_NO_AUTHORITY
) {
1977 if (username
.IsEmpty()) {
1980 NS_WARNING("cannot set username on no-auth url");
1981 return NS_ERROR_UNEXPECTED
;
1984 if (mSpec
.Length() + input
.Length() - Username().Length() >
1985 StaticPrefs::network_standard_url_max_length()) {
1986 return NS_ERROR_MALFORMED_URI
;
1989 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
1993 // escape username if necessary
1995 nsSegmentEncoder encoder
;
1996 const nsACString
& escUsername
=
1997 encoder
.EncodeSegment(username
, esc_Username
, buf
);
2001 if (mUsername
.mLen
< 0 && escUsername
.IsEmpty()) {
2005 if (mUsername
.mLen
< 0 && mPassword
.mLen
< 0) {
2006 MOZ_ASSERT(!escUsername
.IsEmpty(), "Should not be empty at this point");
2007 mUsername
.mPos
= mAuthority
.mPos
;
2008 mSpec
.Insert(escUsername
+ "@"_ns
, mUsername
.mPos
);
2009 shift
= escUsername
.Length() + 1;
2010 mUsername
.mLen
= escUsername
.Length() > 0 ? escUsername
.Length() : -1;
2012 uint32_t pos
= mUsername
.mLen
< 0 ? mAuthority
.mPos
: mUsername
.mPos
;
2013 int32_t len
= mUsername
.mLen
< 0 ? 0 : mUsername
.mLen
;
2015 if (mPassword
.mLen
< 0 && escUsername
.IsEmpty()) {
2016 len
++; // remove the @ character too
2018 shift
= ReplaceSegment(pos
, len
, escUsername
);
2019 mUsername
.mLen
= escUsername
.Length() > 0 ? escUsername
.Length() : -1;
2020 mUsername
.mPos
= pos
;
2024 mAuthority
.mLen
+= shift
;
2025 ShiftFromPassword(shift
);
2028 MOZ_ASSERT(mUsername
.mLen
!= 0 && mPassword
.mLen
!= 0);
2032 nsresult
nsStandardURL::SetPassword(const nsACString
& input
) {
2033 const nsPromiseFlatCString
& password
= PromiseFlatCString(input
);
2035 auto clearedPassword
= MakeScopeExit([&password
, this]() {
2036 // Check that if this method is called with the empty string then the
2037 // password is definitely cleared when exiting this method.
2038 if (password
.IsEmpty()) {
2039 MOZ_DIAGNOSTIC_ASSERT(this->Password().IsEmpty());
2041 Unused
<< this; // silence compiler -Wunused-lambda-capture
2044 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
2046 LOG(("nsStandardURL::SetPassword [password=%s]\n", password
.get()));
2048 if (mURLType
== URLTYPE_NO_AUTHORITY
) {
2049 if (password
.IsEmpty()) {
2052 NS_WARNING("cannot set password on no-auth url");
2053 return NS_ERROR_UNEXPECTED
;
2056 if (mSpec
.Length() + input
.Length() - Password().Length() >
2057 StaticPrefs::network_standard_url_max_length()) {
2058 return NS_ERROR_MALFORMED_URI
;
2063 if (password
.IsEmpty()) {
2064 if (mPassword
.mLen
> 0) {
2066 int32_t len
= mPassword
.mLen
;
2067 if (mUsername
.mLen
< 0) {
2068 len
++; // also cut the @ character
2070 len
++; // for the : character
2071 mSpec
.Cut(mPassword
.mPos
- 1, len
);
2072 ShiftFromHost(-len
);
2073 mAuthority
.mLen
-= len
;
2074 mPassword
.mLen
= -1;
2076 MOZ_ASSERT(mUsername
.mLen
!= 0 && mPassword
.mLen
!= 0);
2080 // escape password if necessary
2082 nsSegmentEncoder encoder
;
2083 const nsACString
& escPassword
=
2084 encoder
.EncodeSegment(password
, esc_Password
, buf
);
2088 if (mPassword
.mLen
< 0) {
2089 if (mUsername
.mLen
> 0) {
2090 mPassword
.mPos
= mUsername
.mPos
+ mUsername
.mLen
+ 1;
2091 mSpec
.Insert(":"_ns
+ escPassword
, mPassword
.mPos
- 1);
2092 shift
= escPassword
.Length() + 1;
2094 mPassword
.mPos
= mAuthority
.mPos
+ 1;
2095 mSpec
.Insert(":"_ns
+ escPassword
+ "@"_ns
, mPassword
.mPos
- 1);
2096 shift
= escPassword
.Length() + 2;
2099 shift
= ReplaceSegment(mPassword
.mPos
, mPassword
.mLen
, escPassword
);
2103 mPassword
.mLen
= escPassword
.Length();
2104 mAuthority
.mLen
+= shift
;
2105 ShiftFromHost(shift
);
2108 MOZ_ASSERT(mUsername
.mLen
!= 0 && mPassword
.mLen
!= 0);
2112 void nsStandardURL::FindHostLimit(nsACString::const_iterator
& aStart
,
2113 nsACString::const_iterator
& aEnd
) {
2114 for (int32_t i
= 0; gHostLimitDigits
[i
]; ++i
) {
2115 nsACString::const_iterator
c(aStart
);
2116 if (FindCharInReadable(gHostLimitDigits
[i
], c
, aEnd
)) {
2122 // If aValue only has a host part and no port number, the port
2123 // will not be reset!!!
2124 nsresult
nsStandardURL::SetHostPort(const nsACString
& aValue
) {
2125 // We cannot simply call nsIURI::SetHost because that would treat the name as
2126 // an IPv6 address (like http:://[server:443]/). We also cannot call
2127 // nsIURI::SetHostPort because that isn't implemented. Sadfaces.
2129 nsACString::const_iterator start
, end
;
2130 aValue
.BeginReading(start
);
2131 aValue
.EndReading(end
);
2132 nsACString::const_iterator
iter(start
);
2133 bool isIPv6
= false;
2135 FindHostLimit(start
, end
);
2137 if (*start
== '[') { // IPv6 address
2138 if (!FindCharInReadable(']', iter
, end
)) {
2139 // the ] character is missing
2140 return NS_ERROR_MALFORMED_URI
;
2142 // iter now at the ']' character
2145 nsACString::const_iterator
iter2(start
);
2146 if (FindCharInReadable(']', iter2
, end
)) {
2147 // if the first char isn't [ then there should be no ] character
2148 return NS_ERROR_MALFORMED_URI
;
2152 FindCharInReadable(':', iter
, end
);
2154 if (!isIPv6
&& iter
!= end
) {
2155 nsACString::const_iterator
iter2(iter
);
2156 iter2
++; // Skip over the first ':' character
2157 if (FindCharInReadable(':', iter2
, end
)) {
2158 // If there is more than one ':' character it suggests an IPv6
2159 // The format should be [2001::1]:80 where the port is optional
2160 return NS_ERROR_MALFORMED_URI
;
2164 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
2166 nsresult rv
= SetHost(Substring(start
, iter
));
2167 NS_ENSURE_SUCCESS(rv
, rv
);
2170 // does not end in colon
2174 iter
++; // advance over the colon
2176 // port number is missing
2180 nsCString
portStr(Substring(iter
, end
));
2181 int32_t port
= portStr
.ToInteger(&rv
);
2182 if (NS_FAILED(rv
)) {
2183 // Failure parsing the port number
2187 Unused
<< SetPort(port
);
2191 nsresult
nsStandardURL::SetHost(const nsACString
& input
) {
2192 nsAutoCString
hostname(input
);
2193 hostname
.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
2195 nsACString::const_iterator start
, end
;
2196 hostname
.BeginReading(start
);
2197 hostname
.EndReading(end
);
2199 FindHostLimit(start
, end
);
2201 // Do percent decoding on the the input.
2203 NS_UnescapeURL(hostname
.BeginReading(), end
- start
,
2204 esc_AlwaysCopy
| esc_Host
, flat
);
2205 const char* host
= flat
.get();
2207 LOG(("nsStandardURL::SetHost [host=%s]\n", host
));
2209 if (mURLType
== URLTYPE_NO_AUTHORITY
) {
2210 if (flat
.IsEmpty()) {
2213 NS_WARNING("cannot set host on no-auth url");
2214 return NS_ERROR_UNEXPECTED
;
2216 if (flat
.IsEmpty()) {
2217 // Setting an empty hostname is not allowed for
2218 // URLTYPE_STANDARD and URLTYPE_AUTHORITY.
2219 return NS_ERROR_UNEXPECTED
;
2222 if (strlen(host
) < flat
.Length()) {
2223 return NS_ERROR_MALFORMED_URI
; // found embedded null
2226 // For consistency with SetSpec/nsURLParsers, don't allow spaces
2228 if (strchr(host
, ' ')) {
2229 return NS_ERROR_MALFORMED_URI
;
2232 if (mSpec
.Length() + strlen(host
) - Host().Length() >
2233 StaticPrefs::network_standard_url_max_length()) {
2234 return NS_ERROR_MALFORMED_URI
;
2237 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
2241 nsAutoCString hostBuf
;
2242 nsresult rv
= NormalizeIDN(flat
, hostBuf
);
2243 if (NS_FAILED(rv
)) {
2247 if (!SegmentIs(mScheme
, "resource") && !SegmentIs(mScheme
, "chrome")) {
2248 nsAutoCString ipString
;
2249 if (hostBuf
.Length() > 0 && hostBuf
.First() == '[' &&
2250 hostBuf
.Last() == ']' &&
2251 ValidIPv6orHostname(hostBuf
.get(), hostBuf
.Length())) {
2252 rv
= (nsresult
)rusturl_parse_ipv6addr(&hostBuf
, &ipString
);
2253 if (NS_FAILED(rv
)) {
2258 if (EndsInANumber(hostBuf
)) {
2259 rv
= NormalizeIPv4(hostBuf
, ipString
);
2260 if (NS_FAILED(rv
)) {
2268 // NormalizeIDN always copies if the call was successful
2269 host
= hostBuf
.get();
2270 len
= hostBuf
.Length();
2272 if (!ValidIPv6orHostname(host
, len
)) {
2273 return NS_ERROR_MALFORMED_URI
;
2276 if (mHost
.mLen
< 0) {
2277 int port_length
= 0;
2281 buf
.AppendInt(mPort
);
2282 port_length
= buf
.Length();
2284 if (mAuthority
.mLen
> 0) {
2285 mHost
.mPos
= mAuthority
.mPos
+ mAuthority
.mLen
- port_length
;
2287 } else if (mScheme
.mLen
> 0) {
2288 mHost
.mPos
= mScheme
.mPos
+ mScheme
.mLen
+ 3;
2293 int32_t shift
= ReplaceSegment(mHost
.mPos
, mHost
.mLen
, host
, len
);
2297 mAuthority
.mLen
+= shift
;
2298 ShiftFromPath(shift
);
2301 // Now canonicalize the host to lowercase
2302 net_ToLowerCase(mSpec
.BeginWriting() + mHost
.mPos
, mHost
.mLen
);
2306 nsresult
nsStandardURL::SetPort(int32_t port
) {
2307 LOG(("nsStandardURL::SetPort [port=%d]\n", port
));
2309 if ((port
== mPort
) || (mPort
== -1 && port
== mDefaultPort
)) {
2313 // ports must be >= 0 and 16 bit
2314 // -1 == use default
2315 if (port
< -1 || port
> std::numeric_limits
<uint16_t>::max()) {
2316 return NS_ERROR_MALFORMED_URI
;
2319 if (mURLType
== URLTYPE_NO_AUTHORITY
) {
2320 NS_WARNING("cannot set port on no-auth url");
2321 return NS_ERROR_UNEXPECTED
;
2324 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
2327 if (port
== mDefaultPort
) {
2331 ReplacePortInSpec(port
);
2338 * Replaces the existing port in mSpec with aNewPort.
2340 * The caller is responsible for:
2341 * - Calling InvalidateCache (since our mSpec is changing).
2342 * - Checking whether aNewPort is mDefaultPort (in which case the
2343 * caller should pass aNewPort=-1).
2345 void nsStandardURL::ReplacePortInSpec(int32_t aNewPort
) {
2346 NS_ASSERTION(aNewPort
!= mDefaultPort
|| mDefaultPort
== -1,
2347 "Caller should check its passed-in value and pass -1 instead of "
2348 "mDefaultPort, to avoid encoding default port into mSpec");
2350 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
2352 // Create the (possibly empty) string that we're planning to replace:
2356 buf
.AppendInt(mPort
);
2358 // Find the position & length of that string:
2359 const uint32_t replacedLen
= buf
.Length();
2360 const uint32_t replacedStart
=
2361 mAuthority
.mPos
+ mAuthority
.mLen
- replacedLen
;
2363 // Create the (possibly empty) replacement string:
2364 if (aNewPort
== -1) {
2368 buf
.AppendInt(aNewPort
);
2370 // Perform the replacement:
2371 mSpec
.Replace(replacedStart
, replacedLen
, buf
);
2373 // Bookkeeping to reflect the new length:
2374 int32_t shift
= buf
.Length() - replacedLen
;
2375 mAuthority
.mLen
+= shift
;
2376 ShiftFromPath(shift
);
2379 nsresult
nsStandardURL::SetPathQueryRef(const nsACString
& input
) {
2380 const nsPromiseFlatCString
& path
= PromiseFlatCString(input
);
2381 LOG(("nsStandardURL::SetPathQueryRef [path=%s]\n", path
.get()));
2382 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
2386 if (!path
.IsEmpty()) {
2389 spec
.Assign(mSpec
.get(), mPath
.mPos
);
2390 if (path
.First() != '/') {
2395 return SetSpecInternal(spec
);
2397 if (mPath
.mLen
>= 1) {
2398 mSpec
.Cut(mPath
.mPos
+ 1, mPath
.mLen
- 1);
2399 // these contain only a '/'
2401 mDirectory
.mLen
= 1;
2403 // these are no longer defined
2404 mBasename
.mLen
= -1;
2405 mExtension
.mLen
= -1;
2412 // When updating this also update SubstitutingURL::Mutator
2413 // Queries this list of interfaces. If none match, it queries mURI.
2414 NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsStandardURL::Mutator
, nsIURISetters
,
2415 nsIURIMutator
, nsIStandardURLMutator
,
2416 nsIURLMutator
, nsIFileURLMutator
,
2420 nsStandardURL::Mutate(nsIURIMutator
** aMutator
) {
2421 RefPtr
<nsStandardURL::Mutator
> mutator
= new nsStandardURL::Mutator();
2422 nsresult rv
= mutator
->InitFromURI(this);
2423 if (NS_FAILED(rv
)) {
2426 mutator
.forget(aMutator
);
2431 nsStandardURL::Equals(nsIURI
* unknownOther
, bool* result
) {
2432 return EqualsInternal(unknownOther
, eHonorRef
, result
);
2436 nsStandardURL::EqualsExceptRef(nsIURI
* unknownOther
, bool* result
) {
2437 return EqualsInternal(unknownOther
, eIgnoreRef
, result
);
2440 nsresult
nsStandardURL::EqualsInternal(
2441 nsIURI
* unknownOther
, nsStandardURL::RefHandlingEnum refHandlingMode
,
2443 NS_ENSURE_ARG_POINTER(unknownOther
);
2444 MOZ_ASSERT(result
, "null pointer");
2446 RefPtr
<nsStandardURL
> other
;
2448 unknownOther
->QueryInterface(kThisImplCID
, getter_AddRefs(other
));
2449 if (NS_FAILED(rv
)) {
2454 // First, check whether one URIs is an nsIFileURL while the other
2455 // is not. If that's the case, they're different.
2456 if (mSupportsFileURL
!= other
->mSupportsFileURL
) {
2461 // Next check parts of a URI that, if different, automatically make the
2463 if (!SegmentIs(mScheme
, other
->mSpec
.get(), other
->mScheme
) ||
2464 // Check for host manually, since conversion to file will
2466 !SegmentIs(mHost
, other
->mSpec
.get(), other
->mHost
) ||
2467 !SegmentIs(mQuery
, other
->mSpec
.get(), other
->mQuery
) ||
2468 !SegmentIs(mUsername
, other
->mSpec
.get(), other
->mUsername
) ||
2469 !SegmentIs(mPassword
, other
->mSpec
.get(), other
->mPassword
) ||
2470 Port() != other
->Port()) {
2471 // No need to compare files or other URI parts -- these are different
2477 if (refHandlingMode
== eHonorRef
&&
2478 !SegmentIs(mRef
, other
->mSpec
.get(), other
->mRef
)) {
2483 // Then check for exact identity of URIs. If we have it, they're equal
2484 if (SegmentIs(mDirectory
, other
->mSpec
.get(), other
->mDirectory
) &&
2485 SegmentIs(mBasename
, other
->mSpec
.get(), other
->mBasename
) &&
2486 SegmentIs(mExtension
, other
->mSpec
.get(), other
->mExtension
)) {
2491 // At this point, the URIs are not identical, but they only differ in the
2492 // directory/filename/extension. If these are file URLs, then get the
2493 // corresponding file objects and compare those, since two filenames that
2494 // differ, eg, only in case could still be equal.
2495 if (mSupportsFileURL
) {
2496 // Assume not equal for failure cases... but failures in GetFile are
2497 // really failures, more or less, so propagate them to caller.
2501 nsresult rv2
= other
->EnsureFile();
2502 // special case for resource:// urls that don't resolve to files
2503 if (rv
== NS_ERROR_NO_INTERFACE
&& rv
== rv2
) {
2507 if (NS_FAILED(rv
)) {
2508 LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
2509 this, mSpec
.get()));
2512 NS_ASSERTION(mFile
, "EnsureFile() lied!");
2514 if (NS_FAILED(rv
)) {
2516 ("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure "
2518 other
.get(), other
->mSpec
.get()));
2521 NS_ASSERTION(other
->mFile
, "EnsureFile() lied!");
2522 return mFile
->Equals(other
->mFile
, result
);
2525 // The URLs are not identical, and they do not correspond to the
2526 // same file, so they are different.
2533 nsStandardURL::SchemeIs(const char* scheme
, bool* result
) {
2534 MOZ_ASSERT(result
, "null pointer");
2540 *result
= SegmentIs(mScheme
, scheme
);
2544 /* virtual */ nsStandardURL
* nsStandardURL::StartClone() {
2545 nsStandardURL
* clone
= new nsStandardURL();
2549 nsresult
nsStandardURL::Clone(nsIURI
** aURI
) {
2550 return CloneInternal(eHonorRef
, ""_ns
, aURI
);
2553 nsresult
nsStandardURL::CloneInternal(
2554 nsStandardURL::RefHandlingEnum aRefHandlingMode
, const nsACString
& aNewRef
,
2558 RefPtr
<nsStandardURL
> clone
= StartClone();
2560 return NS_ERROR_OUT_OF_MEMORY
;
2563 // Copy local members into clone.
2564 // Also copies the cached members mFile, mDisplayHost
2565 clone
->CopyMembers(this, aRefHandlingMode
, aNewRef
, true);
2567 clone
.forget(aClone
);
2571 nsresult
nsStandardURL::CopyMembers(
2572 nsStandardURL
* source
, nsStandardURL::RefHandlingEnum refHandlingMode
,
2573 const nsACString
& newRef
, bool copyCached
) {
2574 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
2576 mSpec
= source
->mSpec
;
2577 mDefaultPort
= source
->mDefaultPort
;
2578 mPort
= source
->mPort
;
2579 mScheme
= source
->mScheme
;
2580 mAuthority
= source
->mAuthority
;
2581 mUsername
= source
->mUsername
;
2582 mPassword
= source
->mPassword
;
2583 mHost
= source
->mHost
;
2584 mPath
= source
->mPath
;
2585 mFilepath
= source
->mFilepath
;
2586 mDirectory
= source
->mDirectory
;
2587 mBasename
= source
->mBasename
;
2588 mExtension
= source
->mExtension
;
2589 mQuery
= source
->mQuery
;
2590 mRef
= source
->mRef
;
2591 mURLType
= source
->mURLType
;
2592 mParser
= source
->mParser
;
2593 mSupportsFileURL
= source
->mSupportsFileURL
;
2594 mCheckedIfHostA
= source
->mCheckedIfHostA
;
2595 mDisplayHost
= source
->mDisplayHost
;
2598 mFile
= source
->mFile
;
2600 InvalidateCache(true);
2603 if (refHandlingMode
== eIgnoreRef
) {
2605 } else if (refHandlingMode
== eReplaceRef
) {
2613 nsStandardURL::Resolve(const nsACString
& in
, nsACString
& out
) {
2614 const nsPromiseFlatCString
& flat
= PromiseFlatCString(in
);
2615 // filter out unexpected chars "\r\n\t" if necessary
2617 net_FilterURIString(flat
, buf
);
2619 const char* relpath
= buf
.get();
2620 int32_t relpathLen
= buf
.Length();
2622 char* result
= nullptr;
2624 LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n", this,
2625 mSpec
.get(), relpath
));
2627 NS_ASSERTION(mParser
, "no parser: unitialized");
2629 // NOTE: there is no need for this function to produce normalized
2630 // output. normalization will occur when the result is used to
2631 // initialize a nsStandardURL object.
2633 if (mScheme
.mLen
< 0) {
2634 NS_WARNING("unable to Resolve URL: this URL not initialized");
2635 return NS_ERROR_NOT_INITIALIZED
;
2640 char* resultPath
= nullptr;
2641 bool relative
= false;
2642 uint32_t offset
= 0;
2643 netCoalesceFlags coalesceFlag
= NET_COALESCE_NORMAL
;
2645 nsAutoCString
baseProtocol(Scheme());
2646 nsAutoCString protocol
;
2647 rv
= net_ExtractURLScheme(buf
, protocol
);
2649 // Normally, if we parse a scheme, then it's an absolute URI. But because
2650 // we still support a deprecated form of relative URIs such as: http:file or
2651 // http:/path/file we can't do that for all protocols.
2652 // So we just make sure that if there a protocol, it's the same as the
2653 // current one, otherwise we treat it as an absolute URI.
2654 if (NS_SUCCEEDED(rv
) && protocol
!= baseProtocol
) {
2659 // relative urls should never contain a host, so we always want to use
2660 // the noauth url parser.
2661 // use it to extract a possible scheme
2662 uint32_t schemePos
= scheme
.mPos
;
2663 int32_t schemeLen
= scheme
.mLen
;
2664 rv
= mParser
->ParseURL(relpath
, relpathLen
, &schemePos
, &schemeLen
, nullptr,
2665 nullptr, nullptr, nullptr);
2667 // if the parser fails (for example because there is no valid scheme)
2668 // reset the scheme and assume a relative url
2669 if (NS_FAILED(rv
)) {
2673 scheme
.mPos
= schemePos
;
2674 scheme
.mLen
= schemeLen
;
2676 protocol
.Assign(Segment(scheme
));
2678 // We need to do backslash replacement for the following cases:
2679 // 1. The input is an absolute path with a http/https/ftp scheme
2680 // 2. The input is a relative path, and the base URL has a http/https/ftp
2682 if ((protocol
.IsEmpty() && IsSpecialProtocol(baseProtocol
)) ||
2683 IsSpecialProtocol(protocol
)) {
2684 auto* start
= buf
.BeginWriting();
2685 auto* end
= buf
.EndWriting();
2686 while (start
!= end
) {
2687 if (*start
== '?' || *start
== '#') {
2690 if (*start
== '\\') {
2697 if (scheme
.mLen
>= 0) {
2698 // add some flags to coalesceFlag if it is an ftp-url
2699 // need this later on when coalescing the resulting URL
2700 if (SegmentIs(relpath
, scheme
, "ftp", true)) {
2702 (netCoalesceFlags
)(coalesceFlag
| NET_COALESCE_ALLOW_RELATIVE_ROOT
|
2703 NET_COALESCE_DOUBLE_SLASH_IS_ROOT
);
2705 // this URL appears to be absolute
2706 // but try to find out more
2707 if (SegmentIs(mScheme
, relpath
, scheme
, true)) {
2708 // mScheme and Scheme are the same
2709 // but this can still be relative
2710 if (strncmp(relpath
+ scheme
.mPos
+ scheme
.mLen
, "://", 3) == 0) {
2711 // now this is really absolute
2712 // because a :// follows the scheme
2713 result
= NS_xstrdup(relpath
);
2715 // This is a deprecated form of relative urls like
2716 // http:file or http:/path/file
2717 // we will support it for now ...
2719 offset
= scheme
.mLen
+ 1;
2722 // the schemes are not the same, we are also done
2723 // because we have to assume this is absolute
2724 result
= NS_xstrdup(relpath
);
2727 // add some flags to coalesceFlag if it is an ftp-url
2728 // need this later on when coalescing the resulting URL
2729 if (SegmentIs(mScheme
, "ftp")) {
2731 (netCoalesceFlags
)(coalesceFlag
| NET_COALESCE_ALLOW_RELATIVE_ROOT
|
2732 NET_COALESCE_DOUBLE_SLASH_IS_ROOT
);
2734 if (relpath
[0] == '/' && relpath
[1] == '/') {
2735 // this URL //host/path is almost absolute
2736 result
= AppendToSubstring(mScheme
.mPos
, mScheme
.mLen
+ 1, relpath
);
2738 // then it must be relative
2744 const char* realrelpath
= relpath
+ offset
;
2745 switch (*realrelpath
) {
2747 // overwrite everything after the authority
2748 len
= mAuthority
.mPos
+ mAuthority
.mLen
;
2751 // overwrite the existing ?query and #ref
2752 if (mQuery
.mLen
>= 0) {
2753 len
= mQuery
.mPos
- 1;
2754 } else if (mRef
.mLen
>= 0) {
2755 len
= mRef
.mPos
- 1;
2757 len
= mPath
.mPos
+ mPath
.mLen
;
2762 // overwrite the existing #ref
2763 if (mRef
.mLen
< 0) {
2764 len
= mPath
.mPos
+ mPath
.mLen
;
2766 len
= mRef
.mPos
- 1;
2770 if (coalesceFlag
& NET_COALESCE_DOUBLE_SLASH_IS_ROOT
) {
2771 if (Filename().Equals("%2F"_ns
, nsCaseInsensitiveCStringComparator
)) {
2772 // if ftp URL ends with %2F then simply
2773 // append relative part because %2F also
2774 // marks the root directory with ftp-urls
2775 len
= mFilepath
.mPos
+ mFilepath
.mLen
;
2777 // overwrite everything after the directory
2778 len
= mDirectory
.mPos
+ mDirectory
.mLen
;
2781 // overwrite everything after the directory
2782 len
= mDirectory
.mPos
+ mDirectory
.mLen
;
2785 result
= AppendToSubstring(0, len
, realrelpath
);
2786 // locate result path
2787 resultPath
= result
+ mPath
.mPos
;
2790 return NS_ERROR_OUT_OF_MEMORY
;
2794 net_CoalesceDirs(coalesceFlag
, resultPath
);
2796 // locate result path
2797 resultPath
= strstr(result
, "://");
2799 // If there are multiple slashes after :// we must ignore them
2800 // otherwise net_CoalesceDirs may think the host is a part of the path.
2802 if (protocol
.IsEmpty() && Scheme() != "file") {
2803 while (*resultPath
== '/') {
2807 resultPath
= strchr(resultPath
, '/');
2809 net_CoalesceDirs(coalesceFlag
, resultPath
);
2817 // result may contain unescaped UTF-8 characters
2819 nsStandardURL::GetCommonBaseSpec(nsIURI
* uri2
, nsACString
& aResult
) {
2820 NS_ENSURE_ARG_POINTER(uri2
);
2822 // if uri's are equal, then return uri as is
2823 bool isEquals
= false;
2824 if (NS_SUCCEEDED(Equals(uri2
, &isEquals
)) && isEquals
) {
2825 return GetSpec(aResult
);
2830 // check pre-path; if they don't match, then return empty string
2831 RefPtr
<nsStandardURL
> stdurl2
;
2832 nsresult rv
= uri2
->QueryInterface(kThisImplCID
, getter_AddRefs(stdurl2
));
2833 isEquals
= NS_SUCCEEDED(rv
) &&
2834 SegmentIs(mScheme
, stdurl2
->mSpec
.get(), stdurl2
->mScheme
) &&
2835 SegmentIs(mHost
, stdurl2
->mSpec
.get(), stdurl2
->mHost
) &&
2836 SegmentIs(mUsername
, stdurl2
->mSpec
.get(), stdurl2
->mUsername
) &&
2837 SegmentIs(mPassword
, stdurl2
->mSpec
.get(), stdurl2
->mPassword
) &&
2838 (Port() == stdurl2
->Port());
2843 // scan for first mismatched character
2844 const char *thisIndex
, *thatIndex
, *startCharPos
;
2845 startCharPos
= mSpec
.get() + mDirectory
.mPos
;
2846 thisIndex
= startCharPos
;
2847 thatIndex
= stdurl2
->mSpec
.get() + mDirectory
.mPos
;
2848 while ((*thisIndex
== *thatIndex
) && *thisIndex
) {
2853 // backup to just after previous slash so we grab an appropriate path
2854 // segment such as a directory (not partial segments)
2855 // todo: also check for file matches which include '?' and '#'
2856 while ((thisIndex
!= startCharPos
) && (*(thisIndex
- 1) != '/')) {
2860 // grab spec from beginning to thisIndex
2861 aResult
= Substring(mSpec
, mScheme
.mPos
, thisIndex
- mSpec
.get());
2867 nsStandardURL::GetRelativeSpec(nsIURI
* uri2
, nsACString
& aResult
) {
2868 NS_ENSURE_ARG_POINTER(uri2
);
2872 // if uri's are equal, then return empty string
2873 bool isEquals
= false;
2874 if (NS_SUCCEEDED(Equals(uri2
, &isEquals
)) && isEquals
) {
2878 RefPtr
<nsStandardURL
> stdurl2
;
2879 nsresult rv
= uri2
->QueryInterface(kThisImplCID
, getter_AddRefs(stdurl2
));
2880 isEquals
= NS_SUCCEEDED(rv
) &&
2881 SegmentIs(mScheme
, stdurl2
->mSpec
.get(), stdurl2
->mScheme
) &&
2882 SegmentIs(mHost
, stdurl2
->mSpec
.get(), stdurl2
->mHost
) &&
2883 SegmentIs(mUsername
, stdurl2
->mSpec
.get(), stdurl2
->mUsername
) &&
2884 SegmentIs(mPassword
, stdurl2
->mSpec
.get(), stdurl2
->mPassword
) &&
2885 (Port() == stdurl2
->Port());
2887 return uri2
->GetSpec(aResult
);
2890 // scan for first mismatched character
2891 const char *thisIndex
, *thatIndex
, *startCharPos
;
2892 startCharPos
= mSpec
.get() + mDirectory
.mPos
;
2893 thisIndex
= startCharPos
;
2894 thatIndex
= stdurl2
->mSpec
.get() + mDirectory
.mPos
;
2897 bool isFileScheme
= SegmentIs(mScheme
, "file");
2899 // on windows, we need to match the first segment of the path
2900 // if these don't match then we need to return an absolute path
2901 // skip over any leading '/' in path
2902 while ((*thisIndex
== *thatIndex
) && (*thisIndex
== '/')) {
2906 // look for end of first segment
2907 while ((*thisIndex
== *thatIndex
) && *thisIndex
&& (*thisIndex
!= '/')) {
2912 // if we didn't match through the first segment, return absolute path
2913 if ((*thisIndex
!= '/') || (*thatIndex
!= '/')) {
2914 return uri2
->GetSpec(aResult
);
2919 while ((*thisIndex
== *thatIndex
) && *thisIndex
) {
2924 // backup to just after previous slash so we grab an appropriate path
2925 // segment such as a directory (not partial segments)
2926 // todo: also check for file matches with '#' and '?'
2927 while ((*(thatIndex
- 1) != '/') && (thatIndex
!= startCharPos
)) {
2931 const char* limit
= mSpec
.get() + mFilepath
.mPos
+ mFilepath
.mLen
;
2933 // need to account for slashes and add corresponding "../"
2934 for (; thisIndex
<= limit
&& *thisIndex
; ++thisIndex
) {
2935 if (*thisIndex
== '/') {
2936 aResult
.AppendLiteral("../");
2940 // grab spec from thisIndex to end
2941 uint32_t startPos
= stdurl2
->mScheme
.mPos
+ thatIndex
- stdurl2
->mSpec
.get();
2943 Substring(stdurl2
->mSpec
, startPos
, stdurl2
->mSpec
.Length() - startPos
));
2948 //----------------------------------------------------------------------------
2949 // nsStandardURL::nsIURL
2950 //----------------------------------------------------------------------------
2952 // result may contain unescaped UTF-8 characters
2954 nsStandardURL::GetFilePath(nsACString
& result
) {
2955 result
= Filepath();
2959 // result may contain unescaped UTF-8 characters
2961 nsStandardURL::GetQuery(nsACString
& result
) {
2967 nsStandardURL::GetHasQuery(bool* result
) {
2968 *result
= (mQuery
.mLen
>= 0);
2972 // result may contain unescaped UTF-8 characters
2974 nsStandardURL::GetRef(nsACString
& result
) {
2980 nsStandardURL::GetHasRef(bool* result
) {
2981 *result
= (mRef
.mLen
>= 0);
2986 nsStandardURL::GetHasUserPass(bool* result
) {
2987 *result
= (mUsername
.mLen
>= 0) || (mPassword
.mLen
>= 0);
2991 // result may contain unescaped UTF-8 characters
2993 nsStandardURL::GetDirectory(nsACString
& result
) {
2994 result
= Directory();
2998 // result may contain unescaped UTF-8 characters
3000 nsStandardURL::GetFileName(nsACString
& result
) {
3001 result
= Filename();
3005 // result may contain unescaped UTF-8 characters
3007 nsStandardURL::GetFileBaseName(nsACString
& result
) {
3008 result
= Basename();
3012 // result may contain unescaped UTF-8 characters
3014 nsStandardURL::GetFileExtension(nsACString
& result
) {
3015 result
= Extension();
3019 nsresult
nsStandardURL::SetFilePath(const nsACString
& input
) {
3020 nsAutoCString
str(input
);
3021 str
.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
3022 const char* filepath
= str
.get();
3024 LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath
));
3025 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
3027 // if there isn't a filepath, then there can't be anything
3028 // after the path either. this url is likely uninitialized.
3029 if (mFilepath
.mLen
< 0) {
3030 return SetPathQueryRef(str
);
3033 if (!str
.IsEmpty()) {
3035 uint32_t dirPos
, basePos
, extPos
;
3036 int32_t dirLen
, baseLen
, extLen
;
3039 if (IsSpecialProtocol(mSpec
)) {
3040 // Bug 1873955: Replace all backslashes with slashes when parsing paths
3041 // Stop when we reach the query or the hash.
3042 auto* start
= str
.BeginWriting();
3043 auto* end
= str
.EndWriting();
3044 while (start
!= end
) {
3045 if (*start
== '?' || *start
== '#') {
3048 if (*start
== '\\') {
3055 rv
= mParser
->ParseFilePath(filepath
, str
.Length(), &dirPos
, &dirLen
,
3056 &basePos
, &baseLen
, &extPos
, &extLen
);
3057 if (NS_FAILED(rv
)) {
3061 // build up new candidate spec
3062 spec
.Assign(mSpec
.get(), mPath
.mPos
);
3064 // ensure leading '/'
3065 if (filepath
[dirPos
] != '/') {
3069 nsSegmentEncoder encoder
;
3071 // append encoded filepath components
3073 encoder
.EncodeSegment(
3074 Substring(filepath
+ dirPos
, filepath
+ dirPos
+ dirLen
),
3075 esc_Directory
| esc_AlwaysCopy
, spec
);
3078 encoder
.EncodeSegment(
3079 Substring(filepath
+ basePos
, filepath
+ basePos
+ baseLen
),
3080 esc_FileBaseName
| esc_AlwaysCopy
, spec
);
3085 encoder
.EncodeSegment(
3086 Substring(filepath
+ extPos
, filepath
+ extPos
+ extLen
),
3087 esc_FileExtension
| esc_AlwaysCopy
, spec
);
3091 // compute the ending position of the current filepath
3092 if (mFilepath
.mLen
>= 0) {
3093 uint32_t end
= mFilepath
.mPos
+ mFilepath
.mLen
;
3094 if (mSpec
.Length() > end
) {
3095 spec
.Append(mSpec
.get() + end
, mSpec
.Length() - end
);
3099 return SetSpecInternal(spec
);
3101 if (mPath
.mLen
> 1) {
3102 mSpec
.Cut(mPath
.mPos
+ 1, mFilepath
.mLen
- 1);
3103 // left shift query, and ref
3104 ShiftFromQuery(1 - mFilepath
.mLen
);
3105 // One character for '/', and if we have a query or ref we add their
3106 // length and one extra for each '?' or '#' characters
3107 mPath
.mLen
= 1 + (mQuery
.mLen
>= 0 ? (mQuery
.mLen
+ 1) : 0) +
3108 (mRef
.mLen
>= 0 ? (mRef
.mLen
+ 1) : 0);
3109 // these contain only a '/'
3110 mDirectory
.mLen
= 1;
3112 // these are no longer defined
3113 mBasename
.mLen
= -1;
3114 mExtension
.mLen
= -1;
3119 inline bool IsUTFEncoding(const Encoding
* aEncoding
) {
3120 return aEncoding
== UTF_8_ENCODING
|| aEncoding
== UTF_16BE_ENCODING
||
3121 aEncoding
== UTF_16LE_ENCODING
;
3124 nsresult
nsStandardURL::SetQuery(const nsACString
& input
) {
3125 return SetQueryWithEncoding(input
, nullptr);
3128 nsresult
nsStandardURL::SetQueryWithEncoding(const nsACString
& input
,
3129 const Encoding
* encoding
) {
3130 const nsPromiseFlatCString
& flat
= PromiseFlatCString(input
);
3131 const char* query
= flat
.get();
3133 LOG(("nsStandardURL::SetQuery [query=%s]\n", query
));
3134 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
3136 if (IsUTFEncoding(encoding
)) {
3140 if (mPath
.mLen
< 0) {
3141 return SetPathQueryRef(flat
);
3144 if (mSpec
.Length() + input
.Length() - Query().Length() >
3145 StaticPrefs::network_standard_url_max_length()) {
3146 return NS_ERROR_MALFORMED_URI
;
3151 if (flat
.IsEmpty()) {
3152 // remove existing query
3153 if (mQuery
.mLen
>= 0) {
3154 // remove query and leading '?'
3155 mSpec
.Cut(mQuery
.mPos
- 1, mQuery
.mLen
+ 1);
3156 ShiftFromRef(-(mQuery
.mLen
+ 1));
3157 mPath
.mLen
-= (mQuery
.mLen
+ 1);
3164 // filter out unexpected chars "\r\n\t" if necessary
3165 nsAutoCString
filteredURI(flat
);
3166 filteredURI
.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
3168 query
= filteredURI
.get();
3169 int32_t queryLen
= filteredURI
.Length();
3170 if (query
[0] == '?') {
3175 if (mQuery
.mLen
< 0) {
3176 if (mRef
.mLen
< 0) {
3177 mQuery
.mPos
= mSpec
.Length();
3179 mQuery
.mPos
= mRef
.mPos
- 1;
3181 mSpec
.Insert('?', mQuery
.mPos
);
3184 // the insertion pushes these out by 1
3189 // encode query if necessary
3192 nsSegmentEncoder
encoder(encoding
);
3193 encoder
.EncodeSegmentCount(query
, URLSegment(0, queryLen
), esc_Query
, buf
,
3197 queryLen
= buf
.Length();
3200 int32_t shift
= ReplaceSegment(mQuery
.mPos
, mQuery
.mLen
, query
, queryLen
);
3203 mQuery
.mLen
= queryLen
;
3204 mPath
.mLen
+= shift
;
3205 ShiftFromRef(shift
);
3210 nsresult
nsStandardURL::SetRef(const nsACString
& input
) {
3211 const nsPromiseFlatCString
& flat
= PromiseFlatCString(input
);
3212 const char* ref
= flat
.get();
3214 LOG(("nsStandardURL::SetRef [ref=%s]\n", ref
));
3215 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
3217 if (mPath
.mLen
< 0) {
3218 return SetPathQueryRef(flat
);
3221 if (mSpec
.Length() + input
.Length() - Ref().Length() >
3222 StaticPrefs::network_standard_url_max_length()) {
3223 return NS_ERROR_MALFORMED_URI
;
3228 if (input
.IsEmpty()) {
3229 // remove existing ref
3230 if (mRef
.mLen
>= 0) {
3231 // remove ref and leading '#'
3232 mSpec
.Cut(mRef
.mPos
- 1, mRef
.mLen
+ 1);
3233 mPath
.mLen
-= (mRef
.mLen
+ 1);
3240 // filter out unexpected chars "\r\n\t" if necessary
3241 nsAutoCString
filteredURI(flat
);
3242 filteredURI
.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
3244 ref
= filteredURI
.get();
3245 int32_t refLen
= filteredURI
.Length();
3246 if (ref
[0] == '#') {
3251 if (mRef
.mLen
< 0) {
3253 ++mPath
.mLen
; // Include the # in the path.
3254 mRef
.mPos
= mSpec
.Length();
3258 // If precent encoding is necessary, `ref` will point to `buf`'s content.
3259 // `buf` needs to outlive any use of the `ref` pointer.
3261 // encode ref if necessary
3263 nsSegmentEncoder encoder
;
3264 encoder
.EncodeSegmentCount(ref
, URLSegment(0, refLen
), esc_Ref
, buf
, encoded
);
3267 refLen
= buf
.Length();
3270 int32_t shift
= ReplaceSegment(mRef
.mPos
, mRef
.mLen
, ref
, refLen
);
3271 mPath
.mLen
+= shift
;
3276 nsresult
nsStandardURL::SetFileNameInternal(const nsACString
& input
) {
3277 const nsPromiseFlatCString
& flat
= PromiseFlatCString(input
);
3278 const char* filename
= flat
.get();
3280 LOG(("nsStandardURL::SetFileNameInternal [filename=%s]\n", filename
));
3281 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
3283 if (mPath
.mLen
< 0) {
3284 return SetPathQueryRef(flat
);
3287 if (mSpec
.Length() + input
.Length() - Filename().Length() >
3288 StaticPrefs::network_standard_url_max_length()) {
3289 return NS_ERROR_MALFORMED_URI
;
3294 if (!(filename
&& *filename
)) {
3295 // remove the filename
3296 if (mBasename
.mLen
> 0) {
3297 if (mExtension
.mLen
>= 0) {
3298 mBasename
.mLen
+= (mExtension
.mLen
+ 1);
3300 mSpec
.Cut(mBasename
.mPos
, mBasename
.mLen
);
3301 shift
= -mBasename
.mLen
;
3303 mExtension
.mLen
= -1;
3307 uint32_t basenamePos
= 0;
3308 int32_t basenameLen
= -1;
3309 uint32_t extensionPos
= 0;
3310 int32_t extensionLen
= -1;
3311 // let the parser locate the basename and extension
3312 rv
= mParser
->ParseFileName(filename
, flat
.Length(), &basenamePos
,
3313 &basenameLen
, &extensionPos
, &extensionLen
);
3314 if (NS_FAILED(rv
)) {
3318 URLSegment
basename(basenamePos
, basenameLen
);
3319 URLSegment
extension(extensionPos
, extensionLen
);
3321 if (basename
.mLen
< 0) {
3322 // remove existing filename
3323 if (mBasename
.mLen
>= 0) {
3324 uint32_t len
= mBasename
.mLen
;
3325 if (mExtension
.mLen
>= 0) {
3326 len
+= (mExtension
.mLen
+ 1);
3328 mSpec
.Cut(mBasename
.mPos
, len
);
3329 shift
= -int32_t(len
);
3331 mExtension
.mLen
= -1;
3334 nsAutoCString newFilename
;
3336 nsSegmentEncoder encoder
;
3337 basename
.mLen
= encoder
.EncodeSegmentCount(
3338 filename
, basename
, esc_FileBaseName
| esc_AlwaysCopy
, newFilename
,
3340 if (extension
.mLen
>= 0) {
3341 newFilename
.Append('.');
3342 extension
.mLen
= encoder
.EncodeSegmentCount(
3343 filename
, extension
, esc_FileExtension
| esc_AlwaysCopy
,
3344 newFilename
, ignoredOut
);
3347 if (mBasename
.mLen
< 0) {
3348 // insert new filename
3349 mBasename
.mPos
= mDirectory
.mPos
+ mDirectory
.mLen
;
3350 mSpec
.Insert(newFilename
, mBasename
.mPos
);
3351 shift
= newFilename
.Length();
3353 // replace existing filename
3354 uint32_t oldLen
= uint32_t(mBasename
.mLen
);
3355 if (mExtension
.mLen
>= 0) {
3356 oldLen
+= (mExtension
.mLen
+ 1);
3358 mSpec
.Replace(mBasename
.mPos
, oldLen
, newFilename
);
3359 shift
= newFilename
.Length() - oldLen
;
3362 mBasename
.mLen
= basename
.mLen
;
3363 mExtension
.mLen
= extension
.mLen
;
3364 if (mExtension
.mLen
>= 0) {
3365 mExtension
.mPos
= mBasename
.mPos
+ mBasename
.mLen
+ 1;
3370 ShiftFromQuery(shift
);
3371 mFilepath
.mLen
+= shift
;
3372 mPath
.mLen
+= shift
;
3377 nsresult
nsStandardURL::SetFileBaseNameInternal(const nsACString
& input
) {
3378 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
3379 nsAutoCString extension
;
3380 nsresult rv
= GetFileExtension(extension
);
3381 NS_ENSURE_SUCCESS(rv
, rv
);
3383 nsAutoCString
newFileName(input
);
3385 if (!extension
.IsEmpty()) {
3386 newFileName
.Append('.');
3387 newFileName
.Append(extension
);
3390 return SetFileNameInternal(newFileName
);
3393 nsresult
nsStandardURL::SetFileExtensionInternal(const nsACString
& input
) {
3394 auto onExitGuard
= MakeScopeExit([&] { SanityCheck(); });
3395 nsAutoCString newFileName
;
3396 nsresult rv
= GetFileBaseName(newFileName
);
3397 NS_ENSURE_SUCCESS(rv
, rv
);
3399 if (!input
.IsEmpty()) {
3400 newFileName
.Append('.');
3401 newFileName
.Append(input
);
3404 return SetFileNameInternal(newFileName
);
3407 //----------------------------------------------------------------------------
3408 // nsStandardURL::nsIFileURL
3409 //----------------------------------------------------------------------------
3411 nsresult
nsStandardURL::EnsureFile() {
3412 MOZ_ASSERT(mSupportsFileURL
,
3413 "EnsureFile() called on a URL that doesn't support files!");
3420 // Parse the spec if we don't have a cached result
3421 if (mSpec
.IsEmpty()) {
3422 NS_WARNING("url not initialized");
3423 return NS_ERROR_NOT_INITIALIZED
;
3426 if (!SegmentIs(mScheme
, "file")) {
3427 NS_WARNING("not a file URL");
3428 return NS_ERROR_FAILURE
;
3431 return net_GetFileFromURLSpec(mSpec
, getter_AddRefs(mFile
));
3435 nsStandardURL::GetFile(nsIFile
** result
) {
3436 MOZ_ASSERT(mSupportsFileURL
,
3437 "GetFile() called on a URL that doesn't support files!");
3439 nsresult rv
= EnsureFile();
3440 if (NS_FAILED(rv
)) {
3444 if (LOG_ENABLED()) {
3445 LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n", this,
3446 mSpec
.get(), mFile
->HumanReadablePath().get()));
3449 // clone the file, so the caller can modify it.
3450 // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the
3451 // nsIFile returned from this method; but it seems that some folks do
3452 // (see bug 161921). until we can be sure that all the consumers are
3453 // behaving themselves, we'll stay on the safe side and clone the file.
3454 // see bug 212724 about fixing the consumers.
3455 return mFile
->Clone(result
);
3458 nsresult
nsStandardURL::SetFile(nsIFile
* file
) {
3459 NS_ENSURE_ARG_POINTER(file
);
3464 rv
= net_GetURLSpecFromFile(file
, url
);
3465 if (NS_FAILED(rv
)) {
3469 uint32_t oldURLType
= mURLType
;
3470 uint32_t oldDefaultPort
= mDefaultPort
;
3471 rv
= Init(nsIStandardURL::URLTYPE_NO_AUTHORITY
, -1, url
, nullptr, nullptr);
3473 if (NS_FAILED(rv
)) {
3474 // Restore the old url type and default port if the call to Init fails.
3475 mURLType
= oldURLType
;
3476 mDefaultPort
= oldDefaultPort
;
3480 // must clone |file| since its value is not guaranteed to remain constant
3482 if (NS_FAILED(file
->Clone(getter_AddRefs(mFile
)))) {
3483 NS_WARNING("nsIFile::Clone failed");
3484 // failure to clone is not fatal (GetFile will generate mFile)
3491 //----------------------------------------------------------------------------
3492 // nsStandardURL::nsIStandardURL
3493 //----------------------------------------------------------------------------
3495 nsresult
nsStandardURL::Init(uint32_t urlType
, int32_t defaultPort
,
3496 const nsACString
& spec
, const char* charset
,
3498 if (spec
.Length() > StaticPrefs::network_standard_url_max_length() ||
3499 defaultPort
> std::numeric_limits
<uint16_t>::max()) {
3500 return NS_ERROR_MALFORMED_URI
;
3506 case URLTYPE_STANDARD
:
3507 mParser
= net_GetStdURLParser();
3509 case URLTYPE_AUTHORITY
:
3510 mParser
= net_GetAuthURLParser();
3512 case URLTYPE_NO_AUTHORITY
:
3513 mParser
= net_GetNoAuthURLParser();
3516 MOZ_ASSERT_UNREACHABLE("bad urlType");
3517 return NS_ERROR_INVALID_ARG
;
3519 mDefaultPort
= defaultPort
;
3522 const auto* encoding
=
3523 charset
? Encoding::ForLabelNoReplacement(MakeStringSpan(charset
))
3525 // URI can't be encoded in UTF-16BE or UTF-16LE. Truncate encoding
3526 // if it is one of utf encodings (since a null encoding implies
3527 // UTF-8, this is safe even if encoding is UTF-8).
3528 if (IsUTFEncoding(encoding
)) {
3532 if (baseURI
&& net_IsAbsoluteURL(spec
)) {
3537 return SetSpecWithEncoding(spec
, encoding
);
3541 nsresult rv
= baseURI
->Resolve(spec
, buf
);
3542 if (NS_FAILED(rv
)) {
3546 return SetSpecWithEncoding(buf
, encoding
);
3549 nsresult
nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort
) {
3552 // should never be more than 16 bit
3553 if (aNewDefaultPort
>= std::numeric_limits
<uint16_t>::max()) {
3554 return NS_ERROR_MALFORMED_URI
;
3557 // If we're already using the new default-port as a custom port, then clear
3558 // it off of our mSpec & set mPort to -1, to indicate that we'll be using
3559 // the default from now on (which happens to match what we already had).
3560 if (mPort
== aNewDefaultPort
) {
3561 ReplacePortInSpec(-1);
3564 mDefaultPort
= aNewDefaultPort
;
3569 //----------------------------------------------------------------------------
3570 // nsStandardURL::nsISerializable
3571 //----------------------------------------------------------------------------
3574 nsStandardURL::Read(nsIObjectInputStream
* stream
) {
3575 MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
3576 return NS_ERROR_NOT_IMPLEMENTED
;
3579 nsresult
nsStandardURL::ReadPrivate(nsIObjectInputStream
* stream
) {
3580 MOZ_ASSERT(mDisplayHost
.IsEmpty(), "Shouldn't have cached unicode host");
3582 // If we exit early, make sure to clear the URL so we don't fail the sanity
3583 // check in the destructor
3584 auto clearOnExit
= MakeScopeExit([&] { Clear(); });
3589 rv
= stream
->Read32(&urlType
);
3590 if (NS_FAILED(rv
)) {
3595 case URLTYPE_STANDARD
:
3596 mParser
= net_GetStdURLParser();
3598 case URLTYPE_AUTHORITY
:
3599 mParser
= net_GetAuthURLParser();
3601 case URLTYPE_NO_AUTHORITY
:
3602 mParser
= net_GetNoAuthURLParser();
3605 MOZ_ASSERT_UNREACHABLE("bad urlType");
3606 return NS_ERROR_FAILURE
;
3609 rv
= stream
->Read32((uint32_t*)&mPort
);
3610 if (NS_FAILED(rv
)) {
3614 rv
= stream
->Read32((uint32_t*)&mDefaultPort
);
3615 if (NS_FAILED(rv
)) {
3619 rv
= NS_ReadOptionalCString(stream
, mSpec
);
3620 if (NS_FAILED(rv
)) {
3624 rv
= ReadSegment(stream
, mScheme
);
3625 if (NS_FAILED(rv
)) {
3629 rv
= ReadSegment(stream
, mAuthority
);
3630 if (NS_FAILED(rv
)) {
3634 rv
= ReadSegment(stream
, mUsername
);
3635 if (NS_FAILED(rv
)) {
3639 rv
= ReadSegment(stream
, mPassword
);
3640 if (NS_FAILED(rv
)) {
3644 rv
= ReadSegment(stream
, mHost
);
3645 if (NS_FAILED(rv
)) {
3649 rv
= ReadSegment(stream
, mPath
);
3650 if (NS_FAILED(rv
)) {
3654 rv
= ReadSegment(stream
, mFilepath
);
3655 if (NS_FAILED(rv
)) {
3659 rv
= ReadSegment(stream
, mDirectory
);
3660 if (NS_FAILED(rv
)) {
3664 rv
= ReadSegment(stream
, mBasename
);
3665 if (NS_FAILED(rv
)) {
3669 rv
= ReadSegment(stream
, mExtension
);
3670 if (NS_FAILED(rv
)) {
3674 // handle forward compatibility from older serializations that included mParam
3675 URLSegment old_param
;
3676 rv
= ReadSegment(stream
, old_param
);
3677 if (NS_FAILED(rv
)) {
3681 rv
= ReadSegment(stream
, mQuery
);
3682 if (NS_FAILED(rv
)) {
3686 rv
= ReadSegment(stream
, mRef
);
3687 if (NS_FAILED(rv
)) {
3691 nsAutoCString oldOriginCharset
;
3692 rv
= NS_ReadOptionalCString(stream
, oldOriginCharset
);
3693 if (NS_FAILED(rv
)) {
3698 rv
= stream
->ReadBoolean(&isMutable
);
3699 if (NS_FAILED(rv
)) {
3702 Unused
<< isMutable
;
3704 bool supportsFileURL
;
3705 rv
= stream
->ReadBoolean(&supportsFileURL
);
3706 if (NS_FAILED(rv
)) {
3709 mSupportsFileURL
= supportsFileURL
;
3711 // wait until object is set up, then modify path to include the param
3712 if (old_param
.mLen
>= 0) { // note that mLen=0 is ";"
3713 // If this wasn't empty, it marks characters between the end of the
3714 // file and start of the query - mPath should include the param,
3715 // query and ref already. Bump the mFilePath and
3716 // directory/basename/extension components to include this.
3717 mFilepath
.Merge(mSpec
, ';', old_param
);
3718 mDirectory
.Merge(mSpec
, ';', old_param
);
3719 mBasename
.Merge(mSpec
, ';', old_param
);
3720 mExtension
.Merge(mSpec
, ';', old_param
);
3723 rv
= CheckIfHostIsAscii();
3724 if (NS_FAILED(rv
)) {
3729 return NS_ERROR_MALFORMED_URI
;
3732 clearOnExit
.release();
3738 nsStandardURL::Write(nsIObjectOutputStream
* stream
) {
3739 MOZ_ASSERT(mSpec
.Length() <= StaticPrefs::network_standard_url_max_length(),
3740 "The spec should never be this long, we missed a check.");
3743 rv
= stream
->Write32(mURLType
);
3744 if (NS_FAILED(rv
)) {
3748 rv
= stream
->Write32(uint32_t(mPort
));
3749 if (NS_FAILED(rv
)) {
3753 rv
= stream
->Write32(uint32_t(mDefaultPort
));
3754 if (NS_FAILED(rv
)) {
3758 rv
= NS_WriteOptionalStringZ(stream
, mSpec
.get());
3759 if (NS_FAILED(rv
)) {
3763 rv
= WriteSegment(stream
, mScheme
);
3764 if (NS_FAILED(rv
)) {
3768 rv
= WriteSegment(stream
, mAuthority
);
3769 if (NS_FAILED(rv
)) {
3773 rv
= WriteSegment(stream
, mUsername
);
3774 if (NS_FAILED(rv
)) {
3778 rv
= WriteSegment(stream
, mPassword
);
3779 if (NS_FAILED(rv
)) {
3783 rv
= WriteSegment(stream
, mHost
);
3784 if (NS_FAILED(rv
)) {
3788 rv
= WriteSegment(stream
, mPath
);
3789 if (NS_FAILED(rv
)) {
3793 rv
= WriteSegment(stream
, mFilepath
);
3794 if (NS_FAILED(rv
)) {
3798 rv
= WriteSegment(stream
, mDirectory
);
3799 if (NS_FAILED(rv
)) {
3803 rv
= WriteSegment(stream
, mBasename
);
3804 if (NS_FAILED(rv
)) {
3808 rv
= WriteSegment(stream
, mExtension
);
3809 if (NS_FAILED(rv
)) {
3813 // for backwards compatibility since we removed mParam. Note that this will
3814 // mean that an older browser will read "" for mParam, and the param(s) will
3815 // be part of mPath (as they after the removal of special handling). It only
3816 // matters if you downgrade a browser to before the patch.
3818 rv
= WriteSegment(stream
, empty
);
3819 if (NS_FAILED(rv
)) {
3823 rv
= WriteSegment(stream
, mQuery
);
3824 if (NS_FAILED(rv
)) {
3828 rv
= WriteSegment(stream
, mRef
);
3829 if (NS_FAILED(rv
)) {
3833 // former origin charset
3834 rv
= NS_WriteOptionalStringZ(stream
, "");
3835 if (NS_FAILED(rv
)) {
3840 rv
= stream
->WriteBoolean(false);
3841 if (NS_FAILED(rv
)) {
3845 rv
= stream
->WriteBoolean(mSupportsFileURL
);
3846 if (NS_FAILED(rv
)) {
3850 // mDisplayHost is just a cache that can be recovered as needed.
3855 inline ipc::StandardURLSegment
ToIPCSegment(
3856 const nsStandardURL::URLSegment
& aSegment
) {
3857 return ipc::StandardURLSegment(aSegment
.mPos
, aSegment
.mLen
);
3860 [[nodiscard
]] inline bool FromIPCSegment(
3861 const nsACString
& aSpec
, const ipc::StandardURLSegment
& aSegment
,
3862 nsStandardURL::URLSegment
& aTarget
) {
3863 // This seems to be just an empty segment.
3864 if (aSegment
.length() == -1) {
3865 aTarget
= nsStandardURL::URLSegment();
3869 // A value of -1 means an empty segment, but < -1 is undefined.
3870 if (NS_WARN_IF(aSegment
.length() < -1)) {
3874 CheckedInt
<uint32_t> segmentLen
= aSegment
.position();
3875 segmentLen
+= aSegment
.length();
3876 // Make sure the segment does not extend beyond the spec.
3877 if (NS_WARN_IF(!segmentLen
.isValid() ||
3878 segmentLen
.value() > aSpec
.Length())) {
3882 aTarget
.mPos
= aSegment
.position();
3883 aTarget
.mLen
= aSegment
.length();
3888 void nsStandardURL::Serialize(URIParams
& aParams
) {
3889 MOZ_ASSERT(mSpec
.Length() <= StaticPrefs::network_standard_url_max_length(),
3890 "The spec should never be this long, we missed a check.");
3891 StandardURLParams params
;
3893 params
.urlType() = mURLType
;
3894 params
.port() = mPort
;
3895 params
.defaultPort() = mDefaultPort
;
3896 params
.spec() = mSpec
;
3897 params
.scheme() = ToIPCSegment(mScheme
);
3898 params
.authority() = ToIPCSegment(mAuthority
);
3899 params
.username() = ToIPCSegment(mUsername
);
3900 params
.password() = ToIPCSegment(mPassword
);
3901 params
.host() = ToIPCSegment(mHost
);
3902 params
.path() = ToIPCSegment(mPath
);
3903 params
.filePath() = ToIPCSegment(mFilepath
);
3904 params
.directory() = ToIPCSegment(mDirectory
);
3905 params
.baseName() = ToIPCSegment(mBasename
);
3906 params
.extension() = ToIPCSegment(mExtension
);
3907 params
.query() = ToIPCSegment(mQuery
);
3908 params
.ref() = ToIPCSegment(mRef
);
3909 params
.supportsFileURL() = !!mSupportsFileURL
;
3910 params
.isSubstituting() = false;
3911 // mDisplayHost is just a cache that can be recovered as needed.
3916 bool nsStandardURL::Deserialize(const URIParams
& aParams
) {
3917 MOZ_ASSERT(mDisplayHost
.IsEmpty(), "Shouldn't have cached unicode host");
3918 MOZ_ASSERT(!mFile
, "Shouldn't have cached file");
3920 if (aParams
.type() != URIParams::TStandardURLParams
) {
3921 NS_ERROR("Received unknown parameters from the other process!");
3925 // If we exit early, make sure to clear the URL so we don't fail the sanity
3926 // check in the destructor
3927 auto clearOnExit
= MakeScopeExit([&] { Clear(); });
3929 const StandardURLParams
& params
= aParams
.get_StandardURLParams();
3931 mURLType
= params
.urlType();
3933 case URLTYPE_STANDARD
:
3934 mParser
= net_GetStdURLParser();
3936 case URLTYPE_AUTHORITY
:
3937 mParser
= net_GetAuthURLParser();
3939 case URLTYPE_NO_AUTHORITY
:
3940 mParser
= net_GetNoAuthURLParser();
3943 MOZ_ASSERT_UNREACHABLE("bad urlType");
3947 mPort
= params
.port();
3948 mDefaultPort
= params
.defaultPort();
3949 mSpec
= params
.spec();
3951 mSpec
.Length() <= StaticPrefs::network_standard_url_max_length(), false);
3952 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.scheme(), mScheme
), false);
3953 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.authority(), mAuthority
), false);
3954 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.username(), mUsername
), false);
3955 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.password(), mPassword
), false);
3956 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.host(), mHost
), false);
3957 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.path(), mPath
), false);
3958 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.filePath(), mFilepath
), false);
3959 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.directory(), mDirectory
), false);
3960 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.baseName(), mBasename
), false);
3961 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.extension(), mExtension
), false);
3962 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.query(), mQuery
), false);
3963 NS_ENSURE_TRUE(FromIPCSegment(mSpec
, params
.ref(), mRef
), false);
3965 mSupportsFileURL
= params
.supportsFileURL();
3967 nsresult rv
= CheckIfHostIsAscii();
3968 if (NS_FAILED(rv
)) {
3972 // Some sanity checks
3973 NS_ENSURE_TRUE(mScheme
.mPos
== 0, false);
3974 NS_ENSURE_TRUE(mScheme
.mLen
> 0, false);
3975 // Make sure scheme is followed by :// (3 characters)
3976 NS_ENSURE_TRUE(mScheme
.mLen
< INT32_MAX
- 3, false); // avoid overflow
3977 NS_ENSURE_TRUE(mSpec
.Length() >= (uint32_t)mScheme
.mLen
+ 3, false);
3979 nsDependentCSubstring(mSpec
, mScheme
.mLen
, 3).EqualsLiteral("://"),
3981 NS_ENSURE_TRUE(mPath
.mLen
!= -1 && mSpec
.CharAt(mPath
.mPos
) == '/', false);
3982 NS_ENSURE_TRUE(mPath
.mPos
== mFilepath
.mPos
, false);
3983 NS_ENSURE_TRUE(mQuery
.mLen
== -1 ||
3984 (mQuery
.mPos
> 0 && mSpec
.CharAt(mQuery
.mPos
- 1) == '?'),
3987 mRef
.mLen
== -1 || (mRef
.mPos
> 0 && mSpec
.CharAt(mRef
.mPos
- 1) == '#'),
3994 clearOnExit
.release();
3999 //----------------------------------------------------------------------------
4000 // nsStandardURL::nsISizeOf
4001 //----------------------------------------------------------------------------
4003 size_t nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
4004 return mSpec
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
) +
4005 mDisplayHost
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
4007 // Measurement of the following members may be added later if DMD finds it is
4013 size_t nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
4014 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
4018 } // namespace mozilla
4020 // For unit tests. Including nsStandardURL.h seems to cause problems
4021 nsresult
Test_NormalizeIPv4(const nsACString
& host
, nsCString
& result
) {
4022 return mozilla::net::nsStandardURL::NormalizeIPv4(host
, result
);
4025 // For unit tests. Including nsStandardURL.h seems to cause problems
4026 nsresult
Test_ParseIPv4Number(const nsACString
& input
, int32_t base
,
4027 uint32_t& number
, uint32_t maxNumber
) {
4028 return mozilla::net::ParseIPv4Number(input
, base
, number
, maxNumber
);
4031 int32_t Test_ValidateIPv4Number(const nsACString
& host
, int32_t bases
[4],
4032 int32_t dotIndex
[3], bool& onlyBase10
,
4034 return mozilla::net::ValidateIPv4Number(host
, bases
, dotIndex
, onlyBase10
,