Bug 1885993 - Enable the BackupService initializer on Nightly by default. r=backup...
[gecko.git] / netwerk / base / nsStandardURL.cpp
blobfac8e4ca7fb88f864aca2177a5a3de2fc7d24bce
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"
11 #include "nsCRT.h"
12 #include "nsEscape.h"
13 #include "nsIFile.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"
20 #include "nsNetCID.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"
26 #include <algorithm>
27 #include "nsContentUtils.h"
28 #include "prprf.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"
34 #include <string.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
42 #undef LOG
43 #define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
44 #undef LOG_ENABLED
45 #define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
47 using namespace mozilla::ipc;
49 namespace mozilla {
50 namespace net {
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) {
68 // Testing for these:
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 == '<' ||
74 c == '^' ||
75 #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
76 // Mailnews %-escapes file paths into URLs.
77 c == '>' || c == '|' || c == '"';
78 #else
79 c == '>' || c == '|' || c == '"' || c == '%';
80 #endif
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) {
92 mEncoding = nullptr;
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
101 // a username).
102 if (!aStr || aSeg.mLen <= 0) {
103 // Empty segment, so aExtraLen not added per above.
104 aAppended = false;
105 return 0;
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.
115 if (mEncoding) {
116 size_t upTo;
117 if (MOZ_UNLIKELY(mEncoding == ISO_2022_JP_ENCODING)) {
118 upTo = Encoding::ISO2022JPASCIIValidUpTo(AsBytes(span));
119 } else {
120 upTo = Encoding::ASCIIValidUpTo(AsBytes(span));
122 if (upTo != span.Length()) {
123 // we have to encode this segment
124 char bufferArr[512];
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
135 // CHARACTER.
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.
142 span = valid;
145 size_t totalRead = 0;
146 for (;;) {
147 auto [encoderResult, read, written] =
148 encoder->EncodeFromUTF8WithoutReplacement(
149 AsBytes(span.From(totalRead)), AsWritableBytes(buffer), true);
150 totalRead += read;
151 auto bufferWritten = buffer.To(written);
152 if (!NS_EscapeURLSpan(bufferWritten, aMask, aOut)) {
153 aOut.Append(bufferWritten);
155 if (encoderResult == kInputEmpty) {
156 aAppended = true;
157 // Difference between original and current output
158 // string lengths plus extra length
159 return aOut.Length() - origLen + aExtraLen;
161 if (encoderResult == kOutputFull) {
162 continue;
164 aOut.AppendLiteral("%26%23");
165 aOut.AppendInt(encoderResult);
166 aOut.AppendLiteral("%3B");
168 MOZ_RELEASE_ASSERT(
169 false,
170 "There's supposed to be no way out of the above loop except return.");
174 if (NS_EscapeURLSpan(span, aMask, aOut)) {
175 aAppended = true;
176 // Difference between original and current output
177 // string lengths plus extra length
178 return aOut.Length() - origLen + aExtraLen;
180 aAppended = false;
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) {
187 const char* text;
188 bool encoded;
189 EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask,
190 result, encoded);
191 if (encoded) {
192 return result;
194 return str;
197 //----------------------------------------------------------------------------
198 // nsStandardURL <public>
199 //----------------------------------------------------------------------------
201 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
202 static StaticMutex gAllURLsMutex MOZ_UNANNOTATED;
203 static LinkedList<nsStandardURL> gAllURLs;
204 #endif
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
221 if (aTrackURL) {
222 StaticMutexAutoLock lock(gAllURLsMutex);
223 gAllURLs.insertBack(this);
225 #endif
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())) {
235 MOZ_ASSERT(false);
236 return true;
238 #endif
239 // Bad value
240 if (NS_WARN_IF(aSeg.mLen < -1)) {
241 return false;
243 if (aSeg.mLen == -1) {
244 return true;
247 // Points out of string
248 if (NS_WARN_IF(aSeg.mPos + aSeg.mLen > mSpec.Length())) {
249 return false;
252 // Overflow
253 if (NS_WARN_IF(aSeg.mPos + aSeg.mLen < aSeg.mPos)) {
254 return false;
257 return true;
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) {
267 return false;
270 if (mScheme.mPos != 0) {
271 return false;
274 return true;
277 void nsStandardURL::SanityCheck() {
278 if (!IsValid()) {
279 nsPrintfCString msg(
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,
294 (int32_t)mRef.mLen);
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);
308 if (isInList()) {
309 remove();
312 #endif
315 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
316 struct DumpLeakedURLs {
317 DumpLeakedURLs() = default;
318 ~DumpLeakedURLs();
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) {
327 url->PrintSpec();
329 gAllURLs.clear();
332 #endif
334 void nsStandardURL::InitGlobalObjects() {
335 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
337 if (gInitialized) {
338 return;
341 gInitialized = true;
343 nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
344 if (serv) {
345 gIDN = serv;
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);
352 Unused << parser;
355 void nsStandardURL::ShutdownGlobalObjects() {
356 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
357 gIDN = nullptr;
359 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
360 if (gInitialized) {
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;
367 #endif
370 //----------------------------------------------------------------------------
371 // nsStandardURL <private>
372 //----------------------------------------------------------------------------
374 void nsStandardURL::Clear() {
375 mSpec.Truncate();
377 mPort = -1;
379 mScheme.Reset();
380 mAuthority.Reset();
381 mUsername.Reset();
382 mPassword.Reset();
383 mHost.Reset();
385 mPath.Reset();
386 mFilepath.Reset();
387 mDirectory.Reset();
388 mBasename.Reset();
390 mExtension.Reset();
391 mQuery.Reset();
392 mRef.Reset();
394 InvalidateCache();
397 void nsStandardURL::InvalidateCache(bool invalidateCachedFile) {
398 if (invalidateCachedFile) {
399 mFile = nullptr;
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
405 // dots + 1.
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());
414 if (length <= 0) {
415 return -1;
418 bool lastWasNumber = false; // We count on this being false for i == 0
419 int32_t dotCount = 0;
420 onlyBase10 = true;
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)) {
430 return -1;
433 if (dotCount > 2) {
434 return -1;
436 lastWasNumber = false;
437 dotIndex[dotCount] = i;
438 dotCount++;
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
442 (dotCount == 0 &&
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
446 (dotCount > 0 &&
447 host[i - 2] != '.')) { // And that zero follows a dot if it exists
448 return -1;
450 lastWasNumber = false;
451 bases[dotCount] = 16;
452 onlyBase10 = false;
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
460 onlyBase10 = false;
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) {
469 return -1;
471 lastWasNumber = true;
473 } else if ((current >= 'a' && current <= 'f') ||
474 (current >= 'A' && current <= 'F')) {
475 if (bases[dotCount] != 16) {
476 return -1;
478 lastWasNumber = true;
480 } else {
481 return -1;
485 return dotCount;
488 inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
489 uint32_t maxNumber) {
490 uint64_t value = 0;
491 const char* current = input.BeginReading();
492 const char* end = input.EndReading();
493 for (; current < end; ++current) {
494 char c = *current;
495 MOZ_ASSERT(c >= '0' && c <= '9');
496 value *= 10;
497 value += c - '0';
499 if (value <= maxNumber) {
500 number = value;
501 return NS_OK;
504 // The error case
505 number = 0;
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
512 uint64_t value = 0;
513 const char* current = input.BeginReading();
514 const char* end = input.EndReading();
515 switch (base) {
516 case 16:
517 ++current;
518 [[fallthrough]];
519 case 8:
520 ++current;
521 break;
522 case 10:
523 default:
524 break;
526 for (; current < end; ++current) {
527 value *= base;
528 char c = *current;
529 MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
530 (base == 8 && c >= '0' && c <= '7') ||
531 (base == 16 && IsAsciiHexDigit(c)));
532 if (IsAsciiDigit(c)) {
533 value += c - '0';
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) {
542 number = value;
543 return NS_OK;
546 // The error case
547 number = 0;
548 return NS_ERROR_FAILURE;
551 // IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
552 /* static */
553 nsresult nsStandardURL::NormalizeIPv4(const nsACString& host,
554 nsCString& result) {
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() == '.') {
564 trailingDot = true;
565 filteredHost.Rebind(host.BeginReading(), host.Length() - 1);
566 } else {
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,
579 0xffu};
580 uint32_t ipv4;
581 int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
583 // parse the last part first
584 nsresult res;
585 // Doing a special case for all items being base 10 gives ~35% speedup
586 res = (onlyBase10
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++) {
598 uint32_t number;
599 start = lastUsed + 1;
600 lastUsed = dotIndex[i];
601 res =
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
613 // "127".
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]);
622 return NS_OK;
625 nsIIDNService* nsStandardURL::GetIDNService() { return gIDN.get(); }
627 nsresult nsStandardURL::NormalizeIDN(const nsCString& host, nsCString& result) {
628 result.Truncate();
629 mDisplayHost.Truncate();
630 nsresult rv;
632 if (!gIDN) {
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);
639 if (NS_FAILED(rv)) {
640 return rv;
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;
648 return NS_OK;
651 bool isAscii = true;
652 nsAutoCString displayHost;
653 rv = gIDN->ConvertToDisplayIDN(result, &isAscii, displayHost);
654 if (NS_FAILED(rv)) {
655 return rv;
658 mCheckedIfHostA = true;
659 if (!isAscii) {
660 mDisplayHost = displayHost;
662 return NS_OK;
665 bool nsStandardURL::ValidIPv6orHostname(const char* host, uint32_t length) {
666 if (!host || !*host) {
667 // Should not be NULL or empty string
668 return false;
671 if (length != strlen(host)) {
672 // Embedded null
673 return false;
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
685 return false;
688 const char* end = host + length;
689 const char* iter = host;
690 for (; iter != end && *iter; ++iter) {
691 if (ASCIIMask::IsMasked(sInvalidHostChars, *iter)) {
692 return false;
695 return true;
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;
703 mPath.mLen = newLen;
704 mDirectory.mLen += diff;
705 mFilepath.mLen += diff;
706 ShiftFromBasename(diff);
710 uint32_t nsStandardURL::AppendSegmentToBuf(char* buf, uint32_t i,
711 const char* str,
712 const URLSegment& segInput,
713 URLSegment& segOutput,
714 const nsCString* escapedStr,
715 bool useEscaped, int32_t* diff) {
716 MOZ_ASSERT(segInput.mLen == segOutput.mLen);
718 if (diff) {
719 *diff = 0;
722 if (segInput.mLen > 0) {
723 if (useEscaped) {
724 MOZ_ASSERT(diff);
725 segOutput.mLen = escapedStr->Length();
726 *diff = segOutput.mLen - segInput.mLen;
727 memcpy(buf + i, escapedStr->get(), segOutput.mLen);
728 } else {
729 memcpy(buf + i, str + segInput.mPos, segInput.mLen);
731 segOutput.mPos = i;
732 i += segOutput.mLen;
733 } else {
734 segOutput.mPos = i;
736 return i;
739 uint32_t nsStandardURL::AppendToBuf(char* buf, uint32_t i, const char* str,
740 uint32_t len) {
741 memcpy(buf + i, str, len);
742 return i + len;
745 static bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input) {
746 for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
747 if (!IsAsciiDigit(*c)) {
748 return false;
752 return true;
755 static bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input) {
756 for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
757 if (!IsAsciiHexDigit(*c)) {
758 return false;
761 return true;
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) {
773 return false;
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) {
781 return false;
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
791 // stage.
792 if (!last.IsEmpty()) {
793 if (ContainsOnlyAsciiDigits(last)) {
794 return true;
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))) {
803 return true;
807 return false;
810 // basic algorithm:
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
831 // spec length.
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) {
839 approxLen +=
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);
849 // Username@
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) {
862 // :port
863 portbuf.AppendInt(mPort);
864 approxLen += portbuf.Length() + 1;
867 approxLen +=
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://#"
879 // ?query
880 if (mQuery.mLen >= 0) {
881 approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query,
882 encQuery, useEncQuery);
884 // #ref
886 if (mRef.mLen >= 0) {
887 approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, encRef,
888 useEncRef);
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,
899 tempHost);
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);
907 if (NS_FAILED(rv)) {
908 return rv;
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);
917 if (NS_FAILED(rv)) {
918 return rv;
920 encHost = ipString;
921 } else {
922 if (EndsInANumber(encHost)) {
923 rv = NormalizeIPv4(encHost, ipString);
924 if (NS_FAILED(rv)) {
925 return rv;
927 encHost = ipString;
932 // NormalizeIDN always copies, if the call was successful.
933 useEncHost = true;
934 approxLen += encHost.Length();
936 if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
937 return NS_ERROR_MALFORMED_URI;
939 } else {
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
947 // encoded strings.
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();
973 uint32_t i = 0;
974 int32_t diff = 0;
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
983 mAuthority.mPos = i;
985 // append authority
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);
991 } else {
992 mUsername.mLen = -1;
994 if (password.mLen > 0) {
995 buf[i++] = ':';
996 i = AppendSegmentToBuf(buf, i, spec, password, mPassword, &encPassword,
997 useEncPassword, &diff);
998 ShiftFromHost(diff);
999 } else {
1000 mPassword.mLen = -1;
1002 buf[i++] = '@';
1003 } else {
1004 mUsername.mLen = -1;
1005 mPassword.mLen = -1;
1007 if (host.mLen > 0) {
1008 i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
1009 &diff);
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) {
1015 buf[i++] = ':';
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;
1031 mBasename.mLen = 0;
1032 buf[i++] = '/';
1033 } else {
1034 uint32_t leadingSlash = 0;
1035 if (spec[path.mPos] != '/') {
1036 LOG(("adding leading slash to path\n"));
1037 leadingSlash = 1;
1038 buf[i++] = '/';
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] != '/') {
1055 buf[i++] = '/';
1056 mDirectory.mLen++;
1059 i = AppendSegmentToBuf(buf, i, spec, basename, mBasename, &encBasename,
1060 useEncBasename, &diff);
1061 ShiftFromExtension(diff);
1063 // make corrections to directory segment if leadingSlash
1064 if (leadingSlash) {
1065 mDirectory.mPos = mPath.mPos;
1066 if (mDirectory.mLen >= 0) {
1067 mDirectory.mLen += leadingSlash;
1068 } else {
1069 mDirectory.mLen = 1;
1073 if (mExtension.mLen >= 0) {
1074 buf[i++] = '.';
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) {
1083 buf[i++] = '?';
1084 i = AppendSegmentToBuf(buf, i, spec, query, mQuery, &encQuery,
1085 useEncQuery, &diff);
1086 ShiftFromRef(diff);
1088 if (mRef.mLen >= 0) {
1089 buf[i++] = '#';
1090 i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
1091 &diff);
1093 // calculate corrected path length
1094 mPath.mLen = i - mPath.mPos;
1097 buf[i] = '\0';
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]) &&
1104 path[2] == '|') {
1105 buf[mPath.mPos + 2] = ':';
1109 if (mDirectory.mLen > 0) {
1110 netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
1111 if (SegmentIs(buf, mScheme, "ftp")) {
1112 coalesceFlag =
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);
1125 return NS_OK;
1128 bool nsStandardURL::SegmentIs(const URLSegment& seg, const char* val,
1129 bool ignoreCase) {
1130 // one or both may be null
1131 if (!val || mSpec.IsEmpty()) {
1132 return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
1134 if (seg.mLen < 0) {
1135 return false;
1137 // if the first |seg.mLen| chars of |val| match, then |val| must
1138 // also be null terminated at |seg.mLen|.
1139 if (ignoreCase) {
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));
1154 if (seg.mLen < 0) {
1155 return false;
1157 // if the first |seg.mLen| chars of |val| match, then |val| must
1158 // also be null terminated at |seg.mLen|.
1159 if (ignoreCase) {
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) {
1170 return false;
1172 if (seg1.mLen == -1 || (!val && mSpec.IsEmpty())) {
1173 return true; // both are empty
1175 if (!val) {
1176 return false;
1178 if (ignoreCase) {
1179 return !nsCRT::strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos,
1180 seg1.mLen);
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) {
1189 if (len == 0) {
1190 mSpec.Insert(val, pos, valLen);
1191 } else {
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) {
1204 if (len == 0) {
1205 mSpec.Insert(val, pos);
1206 } else {
1207 mSpec.Replace(pos, len, val);
1209 return val.Length() - len;
1212 nsresult nsStandardURL::ParseURL(const char* spec, int32_t specLen) {
1213 nsresult rv;
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)) {
1231 return 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;
1240 #ifdef DEBUG
1241 if (mScheme.mLen <= 0) {
1242 printf("spec=%s\n", spec);
1243 NS_WARNING("malformed url: no scheme");
1245 #endif
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)) {
1258 return 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) {
1270 mPort = -1;
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);
1282 return rv;
1285 nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos,
1286 int32_t pathLen) {
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;
1299 nsresult rv =
1300 mParser->ParsePath(spec + pathPos, pathLen, &filePathPos, &filePathLen,
1301 &queryPos, &queryLen, &refPos, &refLen);
1302 if (NS_FAILED(rv)) {
1303 return rv;
1306 mFilepath.mPos = filePathPos;
1307 mFilepath.mLen = filePathLen;
1308 mQuery.mPos = queryPos;
1309 mQuery.mLen = queryLen;
1310 mRef.mPos = refPos;
1311 mRef.mLen = refLen;
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)) {
1328 return 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;
1342 return NS_OK;
1345 char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len,
1346 const char* tail) {
1347 // Verify pos and length are within boundaries
1348 if (pos > mSpec.Length()) {
1349 return nullptr;
1351 if (len < 0) {
1352 return nullptr;
1354 if ((uint32_t)len > (mSpec.Length() - pos)) {
1355 return nullptr;
1357 if (!tail) {
1358 return nullptr;
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) {
1365 return nullptr;
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';
1372 return result;
1375 nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream,
1376 URLSegment& seg) {
1377 nsresult rv;
1379 uint32_t pos = seg.mPos;
1380 rv = stream->Read32(&pos);
1381 if (NS_FAILED(rv)) {
1382 return rv;
1385 seg.mPos = pos;
1387 uint32_t len = seg.mLen;
1388 rv = stream->Read32(&len);
1389 if (NS_FAILED(rv)) {
1390 return rv;
1393 CheckedInt<int32_t> checkedLen(len);
1394 if (!checkedLen.isValid()) {
1395 seg.mLen = -1;
1396 } else {
1397 seg.mLen = len;
1400 return NS_OK;
1403 nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream,
1404 const URLSegment& seg) {
1405 nsresult rv;
1407 rv = stream->Write32(seg.mPos);
1408 if (NS_FAILED(rv)) {
1409 return rv;
1412 rv = stream->Write32(uint32_t(seg.mLen));
1413 if (NS_FAILED(rv)) {
1414 return rv;
1417 return NS_OK;
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; \
1425 pos += diff; \
1426 MOZ_ASSERT(pos.isValid()); \
1427 (what).mPos = pos.value(); \
1428 } else { \
1429 MOZ_RELEASE_ASSERT((what).mLen == -1); \
1432 #define SHIFT_FROM_NEXT(name, what, next) \
1433 SHIFT_FROM(name, what) \
1434 next(diff); \
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,
1458 NS_STANDARDURL_CID)
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);
1481 } else
1482 NS_INTERFACE_MAP_ENTRY(nsISizeOf)
1483 NS_INTERFACE_MAP_END
1485 //----------------------------------------------------------------------------
1486 // nsStandardURL::nsIURI
1487 //----------------------------------------------------------------------------
1489 // result may contain unescaped UTF-8 characters
1490 NS_IMETHODIMP
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.");
1494 result = mSpec;
1495 return NS_OK;
1498 // result may contain unescaped UTF-8 characters
1499 NS_IMETHODIMP
1500 nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString& result) {
1501 nsresult rv = GetSpec(result);
1502 if (NS_FAILED(rv)) {
1503 return rv;
1505 if (mPassword.mLen > 0) {
1506 result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****");
1508 return NS_OK;
1511 // result may contain unescaped UTF-8 characters
1512 NS_IMETHODIMP
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);
1522 return NS_OK;
1525 nsresult nsStandardURL::CheckIfHostIsAscii() {
1526 nsresult rv;
1527 if (mCheckedIfHostA) {
1528 return NS_OK;
1531 mCheckedIfHostA = true;
1533 if (!gIDN) {
1534 return NS_ERROR_NOT_INITIALIZED;
1537 nsAutoCString displayHost;
1538 bool isAscii;
1539 rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost);
1540 if (NS_FAILED(rv)) {
1541 mDisplayHost.Truncate();
1542 mCheckedIfHostA = false;
1543 return rv;
1546 if (!isAscii) {
1547 mDisplayHost = displayHost;
1550 return NS_OK;
1553 NS_IMETHODIMP
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);
1561 return NS_OK;
1564 NS_IMETHODIMP
1565 nsStandardURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
1566 nsAutoCString unicodeHostPort;
1568 nsresult rv = GetDisplayHost(unicodeHostPort);
1569 if (NS_FAILED(rv)) {
1570 return rv;
1573 if (StringBeginsWith(Hostport(), "["_ns)) {
1574 aUnicodeHostPort.AssignLiteral("[");
1575 aUnicodeHostPort.Append(unicodeHostPort);
1576 aUnicodeHostPort.AppendLiteral("]");
1577 } else {
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);
1586 return NS_OK;
1589 NS_IMETHODIMP
1590 nsStandardURL::GetDisplayHost(nsACString& aUnicodeHost) {
1591 MOZ_ASSERT(mCheckedIfHostA);
1592 if (mDisplayHost.IsEmpty()) {
1593 return GetAsciiHost(aUnicodeHost);
1596 aUnicodeHost = mDisplayHost;
1597 return NS_OK;
1600 // result may contain unescaped UTF-8 characters
1601 NS_IMETHODIMP
1602 nsStandardURL::GetPrePath(nsACString& result) {
1603 result = Prepath();
1604 MOZ_ASSERT(mCheckedIfHostA);
1605 return NS_OK;
1608 // result may contain unescaped UTF-8 characters
1609 NS_IMETHODIMP
1610 nsStandardURL::GetDisplayPrePath(nsACString& result) {
1611 result = Prepath();
1612 MOZ_ASSERT(mCheckedIfHostA);
1613 if (!mDisplayHost.IsEmpty()) {
1614 result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
1616 return NS_OK;
1619 // result is strictly US-ASCII
1620 NS_IMETHODIMP
1621 nsStandardURL::GetScheme(nsACString& result) {
1622 result = Scheme();
1623 return NS_OK;
1626 // result may contain unescaped UTF-8 characters
1627 NS_IMETHODIMP
1628 nsStandardURL::GetUserPass(nsACString& result) {
1629 result = Userpass();
1630 return NS_OK;
1633 // result may contain unescaped UTF-8 characters
1634 NS_IMETHODIMP
1635 nsStandardURL::GetUsername(nsACString& result) {
1636 result = Username();
1637 return NS_OK;
1640 // result may contain unescaped UTF-8 characters
1641 NS_IMETHODIMP
1642 nsStandardURL::GetPassword(nsACString& result) {
1643 result = Password();
1644 return NS_OK;
1647 NS_IMETHODIMP
1648 nsStandardURL::GetHostPort(nsACString& result) {
1649 return GetAsciiHostPort(result);
1652 NS_IMETHODIMP
1653 nsStandardURL::GetHost(nsACString& result) { return GetAsciiHost(result); }
1655 NS_IMETHODIMP
1656 nsStandardURL::GetPort(int32_t* result) {
1657 // should never be more than 16 bit
1658 MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
1659 *result = mPort;
1660 return NS_OK;
1663 // result may contain unescaped UTF-8 characters
1664 NS_IMETHODIMP
1665 nsStandardURL::GetPathQueryRef(nsACString& result) {
1666 result = Path();
1667 return NS_OK;
1670 // result is ASCII
1671 NS_IMETHODIMP
1672 nsStandardURL::GetAsciiSpec(nsACString& result) {
1673 result = mSpec;
1674 return NS_OK;
1677 // result is ASCII
1678 NS_IMETHODIMP
1679 nsStandardURL::GetAsciiHostPort(nsACString& result) {
1680 result = Hostport();
1681 return NS_OK;
1684 // result is ASCII
1685 NS_IMETHODIMP
1686 nsStandardURL::GetAsciiHost(nsACString& result) {
1687 result = Host();
1688 return NS_OK;
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 != ':') {
1698 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);
1736 Clear();
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 == '#') {
1745 break;
1747 if (*start == '\\') {
1748 *start = '/';
1750 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
1764 // copy of |spec|
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)) {
1774 Clear();
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);
1778 return rv;
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));
1807 SanityCheck();
1808 return rv;
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(); });
1839 InvalidateCache();
1841 int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
1843 if (shift) {
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) {
1856 mDefaultPort = 80;
1857 } else if (Scheme() == "https"_ns || Scheme() == "wss"_ns) {
1858 mDefaultPort = 443;
1860 if (mPort == mDefaultPort) {
1861 MOZ_ALWAYS_SUCCEEDS(SetPort(-1));
1864 return NS_OK;
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()) {
1874 return NS_OK;
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(); });
1890 InvalidateCache();
1892 NS_ASSERTION(mHost.mLen >= 0, "uninitialized");
1894 nsresult rv;
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)) {
1901 return rv;
1904 // build new user:pass in |buf|
1905 nsAutoCString buf;
1906 if (usernameLen > 0 || passwordLen > 0) {
1907 nsSegmentEncoder encoder;
1908 bool ignoredOut;
1909 usernameLen = encoder.EncodeSegmentCount(
1910 userpass.get(), URLSegment(usernamePos, usernameLen),
1911 esc_Username | esc_AlwaysCopy, buf, ignoredOut);
1912 if (passwordLen > 0) {
1913 buf.Append(':');
1914 passwordLen = encoder.EncodeSegmentCount(
1915 userpass.get(), URLSegment(passwordPos, passwordLen),
1916 esc_Password | esc_AlwaysCopy, buf, ignoredOut);
1917 } else {
1918 passwordLen = -1;
1920 if (mUsername.mLen < 0 && mPassword.mLen < 0) {
1921 buf.Append('@');
1925 int32_t shift = 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();
1934 } else {
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
1945 userpassLen++;
1947 mSpec.Replace(mAuthority.mPos, userpassLen, buf);
1948 shift = buf.Length() - userpassLen;
1950 if (shift) {
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;
1962 } else {
1963 mPassword.mPos = mAuthority.mPos + 1;
1967 MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
1968 return NS_OK;
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()) {
1978 return NS_OK;
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(); });
1991 InvalidateCache();
1993 // escape username if necessary
1994 nsAutoCString buf;
1995 nsSegmentEncoder encoder;
1996 const nsACString& escUsername =
1997 encoder.EncodeSegment(username, esc_Username, buf);
1999 int32_t shift = 0;
2001 if (mUsername.mLen < 0 && escUsername.IsEmpty()) {
2002 return NS_OK;
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;
2011 } else {
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;
2023 if (shift) {
2024 mAuthority.mLen += shift;
2025 ShiftFromPassword(shift);
2028 MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
2029 return NS_OK;
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()) {
2050 return NS_OK;
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;
2061 InvalidateCache();
2063 if (password.IsEmpty()) {
2064 if (mPassword.mLen > 0) {
2065 // cut(":password")
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);
2077 return NS_OK;
2080 // escape password if necessary
2081 nsAutoCString buf;
2082 nsSegmentEncoder encoder;
2083 const nsACString& escPassword =
2084 encoder.EncodeSegment(password, esc_Password, buf);
2086 int32_t shift;
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;
2093 } else {
2094 mPassword.mPos = mAuthority.mPos + 1;
2095 mSpec.Insert(":"_ns + escPassword + "@"_ns, mPassword.mPos - 1);
2096 shift = escPassword.Length() + 2;
2098 } else {
2099 shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);
2102 if (shift) {
2103 mPassword.mLen = escPassword.Length();
2104 mAuthority.mLen += shift;
2105 ShiftFromHost(shift);
2108 MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
2109 return NS_OK;
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)) {
2117 aEnd = c;
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
2143 isIPv6 = true;
2144 } else {
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);
2169 if (iter == end) {
2170 // does not end in colon
2171 return NS_OK;
2174 iter++; // advance over the colon
2175 if (iter == end) {
2176 // port number is missing
2177 return NS_OK;
2180 nsCString portStr(Substring(iter, end));
2181 int32_t port = portStr.ToInteger(&rv);
2182 if (NS_FAILED(rv)) {
2183 // Failure parsing the port number
2184 return NS_OK;
2187 Unused << SetPort(port);
2188 return NS_OK;
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.
2202 nsAutoCString flat;
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()) {
2211 return NS_OK;
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
2227 // in the hostname.
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(); });
2238 InvalidateCache();
2240 uint32_t len;
2241 nsAutoCString hostBuf;
2242 nsresult rv = NormalizeIDN(flat, hostBuf);
2243 if (NS_FAILED(rv)) {
2244 return 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)) {
2254 return rv;
2256 hostBuf = ipString;
2257 } else {
2258 if (EndsInANumber(hostBuf)) {
2259 rv = NormalizeIPv4(hostBuf, ipString);
2260 if (NS_FAILED(rv)) {
2261 return rv;
2263 hostBuf = ipString;
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;
2278 if (mPort != -1) {
2279 nsAutoCString buf;
2280 buf.Assign(':');
2281 buf.AppendInt(mPort);
2282 port_length = buf.Length();
2284 if (mAuthority.mLen > 0) {
2285 mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
2286 mHost.mLen = 0;
2287 } else if (mScheme.mLen > 0) {
2288 mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
2289 mHost.mLen = 0;
2293 int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);
2295 if (shift) {
2296 mHost.mLen = len;
2297 mAuthority.mLen += shift;
2298 ShiftFromPath(shift);
2301 // Now canonicalize the host to lowercase
2302 net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
2303 return NS_OK;
2306 nsresult nsStandardURL::SetPort(int32_t port) {
2307 LOG(("nsStandardURL::SetPort [port=%d]\n", port));
2309 if ((port == mPort) || (mPort == -1 && port == mDefaultPort)) {
2310 return NS_OK;
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(); });
2326 InvalidateCache();
2327 if (port == mDefaultPort) {
2328 port = -1;
2331 ReplacePortInSpec(port);
2333 mPort = port;
2334 return NS_OK;
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:
2353 nsAutoCString buf;
2354 if (mPort != -1) {
2355 buf.Assign(':');
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) {
2365 buf.Truncate();
2366 } else {
2367 buf.Assign(':');
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(); });
2384 InvalidateCache();
2386 if (!path.IsEmpty()) {
2387 nsAutoCString spec;
2389 spec.Assign(mSpec.get(), mPath.mPos);
2390 if (path.First() != '/') {
2391 spec.Append('/');
2393 spec.Append(path);
2395 return SetSpecInternal(spec);
2397 if (mPath.mLen >= 1) {
2398 mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1);
2399 // these contain only a '/'
2400 mPath.mLen = 1;
2401 mDirectory.mLen = 1;
2402 mFilepath.mLen = 1;
2403 // these are no longer defined
2404 mBasename.mLen = -1;
2405 mExtension.mLen = -1;
2406 mQuery.mLen = -1;
2407 mRef.mLen = -1;
2409 return NS_OK;
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,
2417 nsISerializable)
2419 NS_IMETHODIMP
2420 nsStandardURL::Mutate(nsIURIMutator** aMutator) {
2421 RefPtr<nsStandardURL::Mutator> mutator = new nsStandardURL::Mutator();
2422 nsresult rv = mutator->InitFromURI(this);
2423 if (NS_FAILED(rv)) {
2424 return rv;
2426 mutator.forget(aMutator);
2427 return NS_OK;
2430 NS_IMETHODIMP
2431 nsStandardURL::Equals(nsIURI* unknownOther, bool* result) {
2432 return EqualsInternal(unknownOther, eHonorRef, result);
2435 NS_IMETHODIMP
2436 nsStandardURL::EqualsExceptRef(nsIURI* unknownOther, bool* result) {
2437 return EqualsInternal(unknownOther, eIgnoreRef, result);
2440 nsresult nsStandardURL::EqualsInternal(
2441 nsIURI* unknownOther, nsStandardURL::RefHandlingEnum refHandlingMode,
2442 bool* result) {
2443 NS_ENSURE_ARG_POINTER(unknownOther);
2444 MOZ_ASSERT(result, "null pointer");
2446 RefPtr<nsStandardURL> other;
2447 nsresult rv =
2448 unknownOther->QueryInterface(kThisImplCID, getter_AddRefs(other));
2449 if (NS_FAILED(rv)) {
2450 *result = false;
2451 return NS_OK;
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) {
2457 *result = false;
2458 return NS_OK;
2461 // Next check parts of a URI that, if different, automatically make the
2462 // URIs different
2463 if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
2464 // Check for host manually, since conversion to file will
2465 // ignore the host!
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
2472 // beasties
2473 *result = false;
2474 return NS_OK;
2477 if (refHandlingMode == eHonorRef &&
2478 !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
2479 *result = false;
2480 return NS_OK;
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)) {
2487 *result = true;
2488 return NS_OK;
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.
2498 *result = false;
2500 rv = EnsureFile();
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) {
2504 return NS_OK;
2507 if (NS_FAILED(rv)) {
2508 LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
2509 this, mSpec.get()));
2510 return rv;
2512 NS_ASSERTION(mFile, "EnsureFile() lied!");
2513 rv = rv2;
2514 if (NS_FAILED(rv)) {
2515 LOG(
2516 ("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure "
2517 "file",
2518 other.get(), other->mSpec.get()));
2519 return rv;
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.
2527 *result = false;
2529 return NS_OK;
2532 NS_IMETHODIMP
2533 nsStandardURL::SchemeIs(const char* scheme, bool* result) {
2534 MOZ_ASSERT(result, "null pointer");
2535 if (!scheme) {
2536 *result = false;
2537 return NS_OK;
2540 *result = SegmentIs(mScheme, scheme);
2541 return NS_OK;
2544 /* virtual */ nsStandardURL* nsStandardURL::StartClone() {
2545 nsStandardURL* clone = new nsStandardURL();
2546 return clone;
2549 nsresult nsStandardURL::Clone(nsIURI** aURI) {
2550 return CloneInternal(eHonorRef, ""_ns, aURI);
2553 nsresult nsStandardURL::CloneInternal(
2554 nsStandardURL::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef,
2555 nsIURI** aClone)
2558 RefPtr<nsStandardURL> clone = StartClone();
2559 if (!clone) {
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);
2568 return NS_OK;
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;
2597 if (copyCached) {
2598 mFile = source->mFile;
2599 } else {
2600 InvalidateCache(true);
2603 if (refHandlingMode == eIgnoreRef) {
2604 SetRef(""_ns);
2605 } else if (refHandlingMode == eReplaceRef) {
2606 SetRef(newRef);
2609 return NS_OK;
2612 NS_IMETHODIMP
2613 nsStandardURL::Resolve(const nsACString& in, nsACString& out) {
2614 const nsPromiseFlatCString& flat = PromiseFlatCString(in);
2615 // filter out unexpected chars "\r\n\t" if necessary
2616 nsAutoCString buf;
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;
2638 nsresult rv;
2639 URLSegment scheme;
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) {
2655 out = buf;
2656 return NS_OK;
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)) {
2670 scheme.Reset();
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
2681 // scheme
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 == '#') {
2688 break;
2690 if (*start == '\\') {
2691 *start = '/';
2693 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)) {
2701 coalesceFlag =
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);
2714 } else {
2715 // This is a deprecated form of relative urls like
2716 // http:file or http:/path/file
2717 // we will support it for now ...
2718 relative = true;
2719 offset = scheme.mLen + 1;
2721 } else {
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);
2726 } else {
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")) {
2730 coalesceFlag =
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);
2737 } else {
2738 // then it must be relative
2739 relative = true;
2742 if (relative) {
2743 uint32_t len = 0;
2744 const char* realrelpath = relpath + offset;
2745 switch (*realrelpath) {
2746 case '/':
2747 // overwrite everything after the authority
2748 len = mAuthority.mPos + mAuthority.mLen;
2749 break;
2750 case '?':
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;
2756 } else {
2757 len = mPath.mPos + mPath.mLen;
2759 break;
2760 case '#':
2761 case '\0':
2762 // overwrite the existing #ref
2763 if (mRef.mLen < 0) {
2764 len = mPath.mPos + mPath.mLen;
2765 } else {
2766 len = mRef.mPos - 1;
2768 break;
2769 default:
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;
2776 } else {
2777 // overwrite everything after the directory
2778 len = mDirectory.mPos + mDirectory.mLen;
2780 } else {
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;
2789 if (!result) {
2790 return NS_ERROR_OUT_OF_MEMORY;
2793 if (resultPath) {
2794 net_CoalesceDirs(coalesceFlag, resultPath);
2795 } else {
2796 // locate result path
2797 resultPath = strstr(result, "://");
2798 if (resultPath) {
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.
2801 resultPath += 3;
2802 if (protocol.IsEmpty() && Scheme() != "file") {
2803 while (*resultPath == '/') {
2804 resultPath++;
2807 resultPath = strchr(resultPath, '/');
2808 if (resultPath) {
2809 net_CoalesceDirs(coalesceFlag, resultPath);
2813 out.Adopt(result);
2814 return NS_OK;
2817 // result may contain unescaped UTF-8 characters
2818 NS_IMETHODIMP
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);
2828 aResult.Truncate();
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());
2839 if (!isEquals) {
2840 return NS_OK;
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) {
2849 thisIndex++;
2850 thatIndex++;
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) != '/')) {
2857 thisIndex--;
2860 // grab spec from beginning to thisIndex
2861 aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get());
2863 return rv;
2866 NS_IMETHODIMP
2867 nsStandardURL::GetRelativeSpec(nsIURI* uri2, nsACString& aResult) {
2868 NS_ENSURE_ARG_POINTER(uri2);
2870 aResult.Truncate();
2872 // if uri's are equal, then return empty string
2873 bool isEquals = false;
2874 if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
2875 return NS_OK;
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());
2886 if (!isEquals) {
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;
2896 #ifdef XP_WIN
2897 bool isFileScheme = SegmentIs(mScheme, "file");
2898 if (isFileScheme) {
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 == '/')) {
2903 thisIndex++;
2904 thatIndex++;
2906 // look for end of first segment
2907 while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/')) {
2908 thisIndex++;
2909 thatIndex++;
2912 // if we didn't match through the first segment, return absolute path
2913 if ((*thisIndex != '/') || (*thatIndex != '/')) {
2914 return uri2->GetSpec(aResult);
2917 #endif
2919 while ((*thisIndex == *thatIndex) && *thisIndex) {
2920 thisIndex++;
2921 thatIndex++;
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)) {
2928 thatIndex--;
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();
2942 aResult.Append(
2943 Substring(stdurl2->mSpec, startPos, stdurl2->mSpec.Length() - startPos));
2945 return rv;
2948 //----------------------------------------------------------------------------
2949 // nsStandardURL::nsIURL
2950 //----------------------------------------------------------------------------
2952 // result may contain unescaped UTF-8 characters
2953 NS_IMETHODIMP
2954 nsStandardURL::GetFilePath(nsACString& result) {
2955 result = Filepath();
2956 return NS_OK;
2959 // result may contain unescaped UTF-8 characters
2960 NS_IMETHODIMP
2961 nsStandardURL::GetQuery(nsACString& result) {
2962 result = Query();
2963 return NS_OK;
2966 NS_IMETHODIMP
2967 nsStandardURL::GetHasQuery(bool* result) {
2968 *result = (mQuery.mLen >= 0);
2969 return NS_OK;
2972 // result may contain unescaped UTF-8 characters
2973 NS_IMETHODIMP
2974 nsStandardURL::GetRef(nsACString& result) {
2975 result = Ref();
2976 return NS_OK;
2979 NS_IMETHODIMP
2980 nsStandardURL::GetHasRef(bool* result) {
2981 *result = (mRef.mLen >= 0);
2982 return NS_OK;
2985 NS_IMETHODIMP
2986 nsStandardURL::GetHasUserPass(bool* result) {
2987 *result = (mUsername.mLen >= 0) || (mPassword.mLen >= 0);
2988 return NS_OK;
2991 // result may contain unescaped UTF-8 characters
2992 NS_IMETHODIMP
2993 nsStandardURL::GetDirectory(nsACString& result) {
2994 result = Directory();
2995 return NS_OK;
2998 // result may contain unescaped UTF-8 characters
2999 NS_IMETHODIMP
3000 nsStandardURL::GetFileName(nsACString& result) {
3001 result = Filename();
3002 return NS_OK;
3005 // result may contain unescaped UTF-8 characters
3006 NS_IMETHODIMP
3007 nsStandardURL::GetFileBaseName(nsACString& result) {
3008 result = Basename();
3009 return NS_OK;
3012 // result may contain unescaped UTF-8 characters
3013 NS_IMETHODIMP
3014 nsStandardURL::GetFileExtension(nsACString& result) {
3015 result = Extension();
3016 return NS_OK;
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()) {
3034 nsAutoCString spec;
3035 uint32_t dirPos, basePos, extPos;
3036 int32_t dirLen, baseLen, extLen;
3037 nsresult rv;
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 == '#') {
3046 break;
3048 if (*start == '\\') {
3049 *start = '/';
3051 start++;
3055 rv = mParser->ParseFilePath(filepath, str.Length(), &dirPos, &dirLen,
3056 &basePos, &baseLen, &extPos, &extLen);
3057 if (NS_FAILED(rv)) {
3058 return rv;
3061 // build up new candidate spec
3062 spec.Assign(mSpec.get(), mPath.mPos);
3064 // ensure leading '/'
3065 if (filepath[dirPos] != '/') {
3066 spec.Append('/');
3069 nsSegmentEncoder encoder;
3071 // append encoded filepath components
3072 if (dirLen > 0) {
3073 encoder.EncodeSegment(
3074 Substring(filepath + dirPos, filepath + dirPos + dirLen),
3075 esc_Directory | esc_AlwaysCopy, spec);
3077 if (baseLen > 0) {
3078 encoder.EncodeSegment(
3079 Substring(filepath + basePos, filepath + basePos + baseLen),
3080 esc_FileBaseName | esc_AlwaysCopy, spec);
3082 if (extLen >= 0) {
3083 spec.Append('.');
3084 if (extLen > 0) {
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;
3111 mFilepath.mLen = 1;
3112 // these are no longer defined
3113 mBasename.mLen = -1;
3114 mExtension.mLen = -1;
3116 return NS_OK;
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)) {
3137 encoding = nullptr;
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;
3149 InvalidateCache();
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);
3158 mQuery.mPos = 0;
3159 mQuery.mLen = -1;
3161 return NS_OK;
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] == '?') {
3171 query++;
3172 queryLen--;
3175 if (mQuery.mLen < 0) {
3176 if (mRef.mLen < 0) {
3177 mQuery.mPos = mSpec.Length();
3178 } else {
3179 mQuery.mPos = mRef.mPos - 1;
3181 mSpec.Insert('?', mQuery.mPos);
3182 mQuery.mPos++;
3183 mQuery.mLen = 0;
3184 // the insertion pushes these out by 1
3185 mPath.mLen++;
3186 mRef.mPos++;
3189 // encode query if necessary
3190 nsAutoCString buf;
3191 bool encoded;
3192 nsSegmentEncoder encoder(encoding);
3193 encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query, buf,
3194 encoded);
3195 if (encoded) {
3196 query = buf.get();
3197 queryLen = buf.Length();
3200 int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen);
3202 if (shift) {
3203 mQuery.mLen = queryLen;
3204 mPath.mLen += shift;
3205 ShiftFromRef(shift);
3207 return NS_OK;
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;
3226 InvalidateCache();
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);
3234 mRef.mPos = 0;
3235 mRef.mLen = -1;
3237 return NS_OK;
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] == '#') {
3247 ref++;
3248 refLen--;
3251 if (mRef.mLen < 0) {
3252 mSpec.Append('#');
3253 ++mPath.mLen; // Include the # in the path.
3254 mRef.mPos = mSpec.Length();
3255 mRef.mLen = 0;
3258 // If precent encoding is necessary, `ref` will point to `buf`'s content.
3259 // `buf` needs to outlive any use of the `ref` pointer.
3260 nsAutoCString buf;
3261 // encode ref if necessary
3262 bool encoded;
3263 nsSegmentEncoder encoder;
3264 encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref, buf, encoded);
3265 if (encoded) {
3266 ref = buf.get();
3267 refLen = buf.Length();
3270 int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen);
3271 mPath.mLen += shift;
3272 mRef.mLen = refLen;
3273 return NS_OK;
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;
3292 int32_t shift = 0;
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;
3302 mBasename.mLen = 0;
3303 mExtension.mLen = -1;
3305 } else {
3306 nsresult rv;
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)) {
3315 return 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);
3330 mBasename.mLen = 0;
3331 mExtension.mLen = -1;
3333 } else {
3334 nsAutoCString newFilename;
3335 bool ignoredOut;
3336 nsSegmentEncoder encoder;
3337 basename.mLen = encoder.EncodeSegmentCount(
3338 filename, basename, esc_FileBaseName | esc_AlwaysCopy, newFilename,
3339 ignoredOut);
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();
3352 } else {
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;
3369 if (shift) {
3370 ShiftFromQuery(shift);
3371 mFilepath.mLen += shift;
3372 mPath.mLen += shift;
3374 return NS_OK;
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!");
3415 if (mFile) {
3416 // Nothing to do
3417 return NS_OK;
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));
3434 NS_IMETHODIMP
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)) {
3441 return 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);
3461 nsresult rv;
3462 nsAutoCString url;
3464 rv = net_GetURLSpecFromFile(file, url);
3465 if (NS_FAILED(rv)) {
3466 return 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;
3477 return rv;
3480 // must clone |file| since its value is not guaranteed to remain constant
3481 InvalidateCache();
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)
3485 mFile = nullptr;
3488 return NS_OK;
3491 //----------------------------------------------------------------------------
3492 // nsStandardURL::nsIStandardURL
3493 //----------------------------------------------------------------------------
3495 nsresult nsStandardURL::Init(uint32_t urlType, int32_t defaultPort,
3496 const nsACString& spec, const char* charset,
3497 nsIURI* baseURI) {
3498 if (spec.Length() > StaticPrefs::network_standard_url_max_length() ||
3499 defaultPort > std::numeric_limits<uint16_t>::max()) {
3500 return NS_ERROR_MALFORMED_URI;
3503 InvalidateCache();
3505 switch (urlType) {
3506 case URLTYPE_STANDARD:
3507 mParser = net_GetStdURLParser();
3508 break;
3509 case URLTYPE_AUTHORITY:
3510 mParser = net_GetAuthURLParser();
3511 break;
3512 case URLTYPE_NO_AUTHORITY:
3513 mParser = net_GetNoAuthURLParser();
3514 break;
3515 default:
3516 MOZ_ASSERT_UNREACHABLE("bad urlType");
3517 return NS_ERROR_INVALID_ARG;
3519 mDefaultPort = defaultPort;
3520 mURLType = urlType;
3522 const auto* encoding =
3523 charset ? Encoding::ForLabelNoReplacement(MakeStringSpan(charset))
3524 : nullptr;
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)) {
3529 encoding = nullptr;
3532 if (baseURI && net_IsAbsoluteURL(spec)) {
3533 baseURI = nullptr;
3536 if (!baseURI) {
3537 return SetSpecWithEncoding(spec, encoding);
3540 nsAutoCString buf;
3541 nsresult rv = baseURI->Resolve(spec, buf);
3542 if (NS_FAILED(rv)) {
3543 return rv;
3546 return SetSpecWithEncoding(buf, encoding);
3549 nsresult nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort) {
3550 InvalidateCache();
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);
3562 mPort = -1;
3564 mDefaultPort = aNewDefaultPort;
3566 return NS_OK;
3569 //----------------------------------------------------------------------------
3570 // nsStandardURL::nsISerializable
3571 //----------------------------------------------------------------------------
3573 NS_IMETHODIMP
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(); });
3586 nsresult rv;
3588 uint32_t urlType;
3589 rv = stream->Read32(&urlType);
3590 if (NS_FAILED(rv)) {
3591 return rv;
3593 mURLType = urlType;
3594 switch (mURLType) {
3595 case URLTYPE_STANDARD:
3596 mParser = net_GetStdURLParser();
3597 break;
3598 case URLTYPE_AUTHORITY:
3599 mParser = net_GetAuthURLParser();
3600 break;
3601 case URLTYPE_NO_AUTHORITY:
3602 mParser = net_GetNoAuthURLParser();
3603 break;
3604 default:
3605 MOZ_ASSERT_UNREACHABLE("bad urlType");
3606 return NS_ERROR_FAILURE;
3609 rv = stream->Read32((uint32_t*)&mPort);
3610 if (NS_FAILED(rv)) {
3611 return rv;
3614 rv = stream->Read32((uint32_t*)&mDefaultPort);
3615 if (NS_FAILED(rv)) {
3616 return rv;
3619 rv = NS_ReadOptionalCString(stream, mSpec);
3620 if (NS_FAILED(rv)) {
3621 return rv;
3624 rv = ReadSegment(stream, mScheme);
3625 if (NS_FAILED(rv)) {
3626 return rv;
3629 rv = ReadSegment(stream, mAuthority);
3630 if (NS_FAILED(rv)) {
3631 return rv;
3634 rv = ReadSegment(stream, mUsername);
3635 if (NS_FAILED(rv)) {
3636 return rv;
3639 rv = ReadSegment(stream, mPassword);
3640 if (NS_FAILED(rv)) {
3641 return rv;
3644 rv = ReadSegment(stream, mHost);
3645 if (NS_FAILED(rv)) {
3646 return rv;
3649 rv = ReadSegment(stream, mPath);
3650 if (NS_FAILED(rv)) {
3651 return rv;
3654 rv = ReadSegment(stream, mFilepath);
3655 if (NS_FAILED(rv)) {
3656 return rv;
3659 rv = ReadSegment(stream, mDirectory);
3660 if (NS_FAILED(rv)) {
3661 return rv;
3664 rv = ReadSegment(stream, mBasename);
3665 if (NS_FAILED(rv)) {
3666 return rv;
3669 rv = ReadSegment(stream, mExtension);
3670 if (NS_FAILED(rv)) {
3671 return 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)) {
3678 return rv;
3681 rv = ReadSegment(stream, mQuery);
3682 if (NS_FAILED(rv)) {
3683 return rv;
3686 rv = ReadSegment(stream, mRef);
3687 if (NS_FAILED(rv)) {
3688 return rv;
3691 nsAutoCString oldOriginCharset;
3692 rv = NS_ReadOptionalCString(stream, oldOriginCharset);
3693 if (NS_FAILED(rv)) {
3694 return rv;
3697 bool isMutable;
3698 rv = stream->ReadBoolean(&isMutable);
3699 if (NS_FAILED(rv)) {
3700 return rv;
3702 Unused << isMutable;
3704 bool supportsFileURL;
3705 rv = stream->ReadBoolean(&supportsFileURL);
3706 if (NS_FAILED(rv)) {
3707 return 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)) {
3725 return rv;
3728 if (!IsValid()) {
3729 return NS_ERROR_MALFORMED_URI;
3732 clearOnExit.release();
3734 return NS_OK;
3737 NS_IMETHODIMP
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.");
3741 nsresult rv;
3743 rv = stream->Write32(mURLType);
3744 if (NS_FAILED(rv)) {
3745 return rv;
3748 rv = stream->Write32(uint32_t(mPort));
3749 if (NS_FAILED(rv)) {
3750 return rv;
3753 rv = stream->Write32(uint32_t(mDefaultPort));
3754 if (NS_FAILED(rv)) {
3755 return rv;
3758 rv = NS_WriteOptionalStringZ(stream, mSpec.get());
3759 if (NS_FAILED(rv)) {
3760 return rv;
3763 rv = WriteSegment(stream, mScheme);
3764 if (NS_FAILED(rv)) {
3765 return rv;
3768 rv = WriteSegment(stream, mAuthority);
3769 if (NS_FAILED(rv)) {
3770 return rv;
3773 rv = WriteSegment(stream, mUsername);
3774 if (NS_FAILED(rv)) {
3775 return rv;
3778 rv = WriteSegment(stream, mPassword);
3779 if (NS_FAILED(rv)) {
3780 return rv;
3783 rv = WriteSegment(stream, mHost);
3784 if (NS_FAILED(rv)) {
3785 return rv;
3788 rv = WriteSegment(stream, mPath);
3789 if (NS_FAILED(rv)) {
3790 return rv;
3793 rv = WriteSegment(stream, mFilepath);
3794 if (NS_FAILED(rv)) {
3795 return rv;
3798 rv = WriteSegment(stream, mDirectory);
3799 if (NS_FAILED(rv)) {
3800 return rv;
3803 rv = WriteSegment(stream, mBasename);
3804 if (NS_FAILED(rv)) {
3805 return rv;
3808 rv = WriteSegment(stream, mExtension);
3809 if (NS_FAILED(rv)) {
3810 return 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.
3817 URLSegment empty;
3818 rv = WriteSegment(stream, empty);
3819 if (NS_FAILED(rv)) {
3820 return rv;
3823 rv = WriteSegment(stream, mQuery);
3824 if (NS_FAILED(rv)) {
3825 return rv;
3828 rv = WriteSegment(stream, mRef);
3829 if (NS_FAILED(rv)) {
3830 return rv;
3833 // former origin charset
3834 rv = NS_WriteOptionalStringZ(stream, "");
3835 if (NS_FAILED(rv)) {
3836 return rv;
3839 // former mMutable
3840 rv = stream->WriteBoolean(false);
3841 if (NS_FAILED(rv)) {
3842 return rv;
3845 rv = stream->WriteBoolean(mSupportsFileURL);
3846 if (NS_FAILED(rv)) {
3847 return rv;
3850 // mDisplayHost is just a cache that can be recovered as needed.
3852 return NS_OK;
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();
3866 return true;
3869 // A value of -1 means an empty segment, but < -1 is undefined.
3870 if (NS_WARN_IF(aSegment.length() < -1)) {
3871 return false;
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())) {
3879 return false;
3882 aTarget.mPos = aSegment.position();
3883 aTarget.mLen = aSegment.length();
3885 return true;
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.
3913 aParams = params;
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!");
3922 return false;
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();
3932 switch (mURLType) {
3933 case URLTYPE_STANDARD:
3934 mParser = net_GetStdURLParser();
3935 break;
3936 case URLTYPE_AUTHORITY:
3937 mParser = net_GetAuthURLParser();
3938 break;
3939 case URLTYPE_NO_AUTHORITY:
3940 mParser = net_GetNoAuthURLParser();
3941 break;
3942 default:
3943 MOZ_ASSERT_UNREACHABLE("bad urlType");
3944 return false;
3947 mPort = params.port();
3948 mDefaultPort = params.defaultPort();
3949 mSpec = params.spec();
3950 NS_ENSURE_TRUE(
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)) {
3969 return false;
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);
3978 NS_ENSURE_TRUE(
3979 nsDependentCSubstring(mSpec, mScheme.mLen, 3).EqualsLiteral("://"),
3980 false);
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) == '?'),
3985 false);
3986 NS_ENSURE_TRUE(
3987 mRef.mLen == -1 || (mRef.mPos > 0 && mSpec.CharAt(mRef.mPos - 1) == '#'),
3988 false);
3990 if (!IsValid()) {
3991 return false;
3994 clearOnExit.release();
3996 return true;
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
4008 // worthwhile:
4009 // - mParser
4010 // - mFile
4013 size_t nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
4014 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
4017 } // namespace net
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,
4033 int32_t length) {
4034 return mozilla::net::ValidateIPv4Number(host, bases, dotIndex, onlyBase10,
4035 length, false);