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/. */
6 
7 #include "ipc/IPCMessageUtils.h"
8 
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 "nsNetCID.h"
20 #include "mozilla/MemoryReporting.h"
21 #include "mozilla/ipc/URIUtils.h"
22 #include "mozilla/ScopeExit.h"
23 #include "mozilla/StaticPrefs_network.h"
24 #include "mozilla/TextUtils.h"
25 #include <algorithm>
26 #include "nsContentUtils.h"
27 #include "prprf.h"
28 #include "nsReadableUtils.h"
29 #include "mozilla/net/MozURL_ffi.h"
30 #include "mozilla/TextUtils.h"
31 #include "mozilla/Utf8.h"
32 #include "nsIClassInfoImpl.h"
33 
34 //
35 // setenv MOZ_LOG nsStandardURL:5
36 //
37 static LazyLogModule gStandardURLLog("nsStandardURL");
38 
39 // The Chromium code defines its own LOG macro which we don't want
40 #undef LOG
41 #define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
42 #undef LOG_ENABLED
43 #define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
44 
45 using namespace mozilla::ipc;
46 
47 namespace mozilla {
48 namespace net {
49 
50 static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
51 static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID);
52 
53 // This will always be initialized and destroyed on the main thread, but
54 // can be safely used on other threads.
55 StaticRefPtr<nsIIDNService> nsStandardURL::gIDN;
56 
57 // This value will only be updated on the main thread once.
58 static Atomic<bool, Relaxed> gInitialized{false};
59 
60 const char nsStandardURL::gHostLimitDigits[] = {'/', '\\', '?', '#', 0};
61 
62 // Invalid host characters
63 // Note that the array below will be initialized at compile time,
64 // so we do not need to "optimize" TestForInvalidHostCharacters.
65 //
TestForInvalidHostCharacters(char c)66 constexpr bool TestForInvalidHostCharacters(char c) {
67   // Testing for these:
68   // CONTROL_CHARACTERS " #/:?@[\\]*<>|\"";
69   return (c > 0 && c < 32) ||  // The control characters are [1, 31]
70          c == ' ' || c == '#' || c == '/' || c == ':' || c == '?' || c == '@' ||
71          c == '[' || c == '\\' || c == ']' || c == '*' || c == '<' ||
72          c == '^' ||
73 #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
74          // Mailnews %-escapes file paths into URLs.
75          c == '>' || c == '|' || c == '"';
76 #else
77          c == '>' || c == '|' || c == '"' || c == '%';
78 #endif
79 }
80 constexpr ASCIIMaskArray sInvalidHostChars =
81     CreateASCIIMask(TestForInvalidHostCharacters);
82 
83 //----------------------------------------------------------------------------
84 // nsStandardURL::nsSegmentEncoder
85 //----------------------------------------------------------------------------
86 
nsSegmentEncoder(const Encoding * encoding)87 nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding* encoding)
88     : mEncoding(encoding) {
89   if (mEncoding == UTF_8_ENCODING) {
90     mEncoding = nullptr;
91   }
92 }
93 
EncodeSegmentCount(const char * aStr,const URLSegment & aSeg,int16_t aMask,nsCString & aOut,bool & aAppended,uint32_t aExtraLen)94 int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount(
95     const char* aStr, const URLSegment& aSeg, int16_t aMask, nsCString& aOut,
96     bool& aAppended, uint32_t aExtraLen) {
97   // aExtraLen is characters outside the segment that will be
98   // added when the segment is not empty (like the @ following
99   // a username).
100   if (!aStr || aSeg.mLen <= 0) {
101     // Empty segment, so aExtraLen not added per above.
102     aAppended = false;
103     return 0;
104   }
105 
106   uint32_t origLen = aOut.Length();
107 
108   Span<const char> span = Span(aStr + aSeg.mPos, aSeg.mLen);
109 
110   // first honor the origin charset if appropriate. as an optimization,
111   // only do this if the segment is non-ASCII.  Further, if mEncoding is
112   // null, then the origin charset is UTF-8 and there is nothing to do.
113   if (mEncoding) {
114     size_t upTo;
115     if (MOZ_UNLIKELY(mEncoding == ISO_2022_JP_ENCODING)) {
116       upTo = Encoding::ISO2022JPASCIIValidUpTo(AsBytes(span));
117     } else {
118       upTo = Encoding::ASCIIValidUpTo(AsBytes(span));
119     }
120     if (upTo != span.Length()) {
121       // we have to encode this segment
122       char bufferArr[512];
123       Span<char> buffer = Span(bufferArr);
124 
125       auto encoder = mEncoding->NewEncoder();
126 
127       nsAutoCString valid;  // has to be declared in this scope
128       if (MOZ_UNLIKELY(!IsUtf8(span.From(upTo)))) {
129         MOZ_ASSERT_UNREACHABLE("Invalid UTF-8 passed to nsStandardURL.");
130         // It's UB to pass invalid UTF-8 to
131         // EncodeFromUTF8WithoutReplacement(), so let's make our input valid
132         // UTF-8 by replacing invalid sequences with the REPLACEMENT
133         // CHARACTER.
134         UTF_8_ENCODING->Decode(
135             nsDependentCSubstring(span.Elements(), span.Length()), valid);
136         // This assigment is OK. `span` can't be used after `valid` has
137         // been destroyed because the only way out of the scope that `valid`
138         // was declared in is via return inside the loop below. Specifically,
139         // the return is the only way out of the loop.
140         span = valid;
141       }
142 
143       size_t totalRead = 0;
144       for (;;) {
145         uint32_t encoderResult;
146         size_t read;
147         size_t written;
148         Tie(encoderResult, read, written) =
149             encoder->EncodeFromUTF8WithoutReplacement(
150                 AsBytes(span.From(totalRead)), AsWritableBytes(buffer), true);
151         totalRead += read;
152         auto bufferWritten = buffer.To(written);
153         if (!NS_EscapeURLSpan(bufferWritten, aMask, aOut)) {
154           aOut.Append(bufferWritten);
155         }
156         if (encoderResult == kInputEmpty) {
157           aAppended = true;
158           // Difference between original and current output
159           // string lengths plus extra length
160           return aOut.Length() - origLen + aExtraLen;
161         }
162         if (encoderResult == kOutputFull) {
163           continue;
164         }
165         aOut.AppendLiteral("%26%23");
166         aOut.AppendInt(encoderResult);
167         aOut.AppendLiteral("%3B");
168       }
169       MOZ_RELEASE_ASSERT(
170           false,
171           "There's supposed to be no way out of the above loop except return.");
172     }
173   }
174 
175   if (NS_EscapeURLSpan(span, aMask, aOut)) {
176     aAppended = true;
177     // Difference between original and current output
178     // string lengths plus extra length
179     return aOut.Length() - origLen + aExtraLen;
180   }
181   aAppended = false;
182   // Original segment length plus extra length
183   return span.Length() + aExtraLen;
184 }
185 
EncodeSegment(const nsACString & str,int16_t mask,nsCString & result)186 const nsACString& nsStandardURL::nsSegmentEncoder::EncodeSegment(
187     const nsACString& str, int16_t mask, nsCString& result) {
188   const char* text;
189   bool encoded;
190   EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask,
191                      result, encoded);
192   if (encoded) {
193     return result;
194   }
195   return str;
196 }
197 
198 //----------------------------------------------------------------------------
199 // nsStandardURL <public>
200 //----------------------------------------------------------------------------
201 
202 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
203 static StaticMutex gAllURLsMutex;
204 static LinkedList<nsStandardURL> gAllURLs;
205 #endif
206 
nsStandardURL(bool aSupportsFileURL,bool aTrackURL)207 nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
208     : mURLType(URLTYPE_STANDARD),
209       mSupportsFileURL(aSupportsFileURL),
210       mCheckedIfHostA(false) {
211   LOG(("Creating nsStandardURL @%p\n", this));
212 
213   // gInitialized changes value only once (false->true) on the main thread.
214   // It's OK to race here because in the worst case we'll just
215   // dispatch a noop runnable to the main thread.
216   MOZ_ASSERT(gInitialized);
217 
218   // default parser in case nsIStandardURL::Init is never called
219   mParser = net_GetStdURLParser();
220 
221 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
222   if (aTrackURL) {
223     StaticMutexAutoLock lock(gAllURLsMutex);
224     gAllURLs.insertBack(this);
225   }
226 #endif
227 }
228 
IsValid()229 bool nsStandardURL::IsValid() {
230   auto checkSegment = [&](const nsStandardURL::URLSegment& aSeg) {
231     // Bad value
232     if (NS_WARN_IF(aSeg.mLen < -1)) {
233       return false;
234     }
235     if (aSeg.mLen == -1) {
236       return true;
237     }
238 
239     // Points out of string
240     if (NS_WARN_IF(aSeg.mPos + aSeg.mLen > mSpec.Length())) {
241       return false;
242     }
243 
244     // Overflow
245     if (NS_WARN_IF(aSeg.mPos + aSeg.mLen < aSeg.mPos)) {
246       return false;
247     }
248 
249     return true;
250   };
251 
252   bool allSegmentsValid = checkSegment(mScheme) && checkSegment(mAuthority) &&
253                           checkSegment(mUsername) && checkSegment(mPassword) &&
254                           checkSegment(mHost) && checkSegment(mPath) &&
255                           checkSegment(mFilepath) && checkSegment(mDirectory) &&
256                           checkSegment(mBasename) && checkSegment(mExtension) &&
257                           checkSegment(mQuery) && checkSegment(mRef);
258   if (!allSegmentsValid) {
259     return false;
260   }
261 
262   if (mScheme.mPos != 0) {
263     return false;
264   }
265 
266   return true;
267 }
268 
SanityCheck()269 void nsStandardURL::SanityCheck() {
270   if (!IsValid()) {
271     nsPrintfCString msg(
272         "mLen:%zX, mScheme (%X,%X), mAuthority (%X,%X), mUsername (%X,%X), "
273         "mPassword (%X,%X), mHost (%X,%X), mPath (%X,%X), mFilepath (%X,%X), "
274         "mDirectory (%X,%X), mBasename (%X,%X), mExtension (%X,%X), mQuery "
275         "(%X,%X), mRef (%X,%X)",
276         mSpec.Length(), mScheme.mPos, mScheme.mLen, mAuthority.mPos,
277         mAuthority.mLen, mUsername.mPos, mUsername.mLen, mPassword.mPos,
278         mPassword.mLen, mHost.mPos, mHost.mLen, mPath.mPos, mPath.mLen,
279         mFilepath.mPos, mFilepath.mLen, mDirectory.mPos, mDirectory.mLen,
280         mBasename.mPos, mBasename.mLen, mExtension.mPos, mExtension.mLen,
281         mQuery.mPos, mQuery.mLen, mRef.mPos, mRef.mLen);
282     CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URLSegments,
283                                        msg);
284 
285     MOZ_CRASH("nsStandardURL::SanityCheck failed");
286   }
287 }
288 
~nsStandardURL()289 nsStandardURL::~nsStandardURL() {
290   LOG(("Destroying nsStandardURL @%p\n", this));
291 
292 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
293   {
294     StaticMutexAutoLock lock(gAllURLsMutex);
295     if (isInList()) {
296       remove();
297     }
298   }
299 #endif
300 
301   SanityCheck();
302 }
303 
304 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
305 struct DumpLeakedURLs {
306   DumpLeakedURLs() = default;
307   ~DumpLeakedURLs();
308 };
309 
~DumpLeakedURLs()310 DumpLeakedURLs::~DumpLeakedURLs() {
311   MOZ_ASSERT(NS_IsMainThread());
312   StaticMutexAutoLock lock(gAllURLsMutex);
313   if (!gAllURLs.isEmpty()) {
314     printf("Leaked URLs:\n");
315     for (auto* url : gAllURLs) {
316       url->PrintSpec();
317     }
318     gAllURLs.clear();
319   }
320 }
321 #endif
322 
InitGlobalObjects()323 void nsStandardURL::InitGlobalObjects() {
324   MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
325 
326   if (gInitialized) {
327     return;
328   }
329 
330   gInitialized = true;
331 
332   nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
333   if (serv) {
334     gIDN = serv;
335   }
336   MOZ_DIAGNOSTIC_ASSERT(gIDN);
337 
338   // Make sure nsURLHelper::InitGlobals() gets called on the main thread
339   nsCOMPtr<nsIURLParser> parser = net_GetStdURLParser();
340   MOZ_DIAGNOSTIC_ASSERT(parser);
341   Unused << parser;
342 }
343 
ShutdownGlobalObjects()344 void nsStandardURL::ShutdownGlobalObjects() {
345   MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
346   gIDN = nullptr;
347 
348 #ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
349   if (gInitialized) {
350     // This instanciates a dummy class, and will trigger the class
351     // destructor when libxul is unloaded. This is equivalent to atexit(),
352     // but gracefully handles dlclose().
353     StaticMutexAutoLock lock(gAllURLsMutex);
354     static DumpLeakedURLs d;
355   }
356 #endif
357 }
358 
359 //----------------------------------------------------------------------------
360 // nsStandardURL <private>
361 //----------------------------------------------------------------------------
362 
Clear()363 void nsStandardURL::Clear() {
364   mSpec.Truncate();
365 
366   mPort = -1;
367 
368   mScheme.Reset();
369   mAuthority.Reset();
370   mUsername.Reset();
371   mPassword.Reset();
372   mHost.Reset();
373 
374   mPath.Reset();
375   mFilepath.Reset();
376   mDirectory.Reset();
377   mBasename.Reset();
378 
379   mExtension.Reset();
380   mQuery.Reset();
381   mRef.Reset();
382 
383   InvalidateCache();
384 }
385 
InvalidateCache(bool invalidateCachedFile)386 void nsStandardURL::InvalidateCache(bool invalidateCachedFile) {
387   if (invalidateCachedFile) {
388     mFile = nullptr;
389   }
390 }
391 
392 // Return the number of "dots" in the string, or -1 if invalid.  Note that the
393 // number of relevant entries in the bases/starts/ends arrays is number of
394 // dots + 1.
395 // Since the trailing dot is allowed, we pass and adjust "length".
396 //
397 // length is assumed to be <= host.Length(); the callers is responsible for that
398 //
399 // Note that the value returned is guaranteed to be in [-1, 3] range.
ValidateIPv4Number(const nsACString & host,int32_t bases[4],int32_t dotIndex[3],bool & onlyBase10,int32_t & length)400 inline int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
401                                   int32_t dotIndex[3], bool& onlyBase10,
402                                   int32_t& length) {
403   MOZ_ASSERT(length <= (int32_t)host.Length());
404   if (length <= 0) {
405     return -1;
406   }
407 
408   bool lastWasNumber = false;  // We count on this being false for i == 0
409   int32_t dotCount = 0;
410   onlyBase10 = true;
411 
412   for (int32_t i = 0; i < length; i++) {
413     char current = host[i];
414     if (current == '.') {
415       if (!lastWasNumber) {  // A dot should not follow an X or a dot, or be
416                              // first
417         return -1;
418       }
419 
420       if (dotCount > 0 &&
421           i == (length - 1)) {  // Trailing dot is OK; shorten and return
422         length--;
423         return dotCount;
424       }
425 
426       if (dotCount > 2) {
427         return -1;
428       }
429       lastWasNumber = false;
430       dotIndex[dotCount] = i;
431       dotCount++;
432     } else if (current == 'X' || current == 'x') {
433       if (!lastWasNumber ||  // An X should not follow an X or a dot or be first
434           i == (length - 1) ||  // No trailing Xs allowed
435           (dotCount == 0 &&
436            i != 1) ||            // If we had no dots, an X should be second
437           host[i - 1] != '0' ||  // X should always follow a 0.  Guaranteed i >
438                                  // 0 as lastWasNumber is true
439           (dotCount > 0 &&
440            host[i - 2] != '.')) {  // And that zero follows a dot if it exists
441         return -1;
442       }
443       lastWasNumber = false;
444       bases[dotCount] = 16;
445       onlyBase10 = false;
446 
447     } else if (current == '0') {
448       if (i < length - 1 &&      // Trailing zero doesn't signal octal
449           host[i + 1] != '.' &&  // Lone zero is not octal
450           (i == 0 || host[i - 1] == '.')) {  // Zero at start or following a dot
451                                              // is a candidate for octal
452         bases[dotCount] = 8;  // This will turn to 16 above if X shows up
453         onlyBase10 = false;
454       }
455       lastWasNumber = true;
456 
457     } else if (current >= '1' && current <= '7') {
458       lastWasNumber = true;
459 
460     } else if (current >= '8' && current <= '9') {
461       if (bases[dotCount] == 8) {
462         return -1;
463       }
464       lastWasNumber = true;
465 
466     } else if ((current >= 'a' && current <= 'f') ||
467                (current >= 'A' && current <= 'F')) {
468       if (bases[dotCount] != 16) {
469         return -1;
470       }
471       lastWasNumber = true;
472 
473     } else {
474       return -1;
475     }
476   }
477 
478   return dotCount;
479 }
480 
ParseIPv4Number10(const nsACString & input,uint32_t & number,uint32_t maxNumber)481 inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
482                                   uint32_t maxNumber) {
483   uint64_t value = 0;
484   const char* current = input.BeginReading();
485   const char* end = input.EndReading();
486   for (; current < end; ++current) {
487     char c = *current;
488     MOZ_ASSERT(c >= '0' && c <= '9');
489     value *= 10;
490     value += c - '0';
491   }
492   if (value <= maxNumber) {
493     number = value;
494     return NS_OK;
495   }
496 
497   // The error case
498   number = 0;
499   return NS_ERROR_FAILURE;
500 }
501 
ParseIPv4Number(const nsACString & input,int32_t base,uint32_t & number,uint32_t maxNumber)502 inline nsresult ParseIPv4Number(const nsACString& input, int32_t base,
503                                 uint32_t& number, uint32_t maxNumber) {
504   // Accumulate in the 64-bit value
505   uint64_t value = 0;
506   const char* current = input.BeginReading();
507   const char* end = input.EndReading();
508   switch (base) {
509     case 16:
510       ++current;
511       [[fallthrough]];
512     case 8:
513       ++current;
514       break;
515     case 10:
516     default:
517       break;
518   }
519   for (; current < end; ++current) {
520     value *= base;
521     char c = *current;
522     MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
523                (base == 8 && c >= '0' && c <= '7') ||
524                (base == 16 && IsAsciiHexDigit(c)));
525     if (IsAsciiDigit(c)) {
526       value += c - '0';
527     } else if (c >= 'a' && c <= 'f') {
528       value += c - 'a' + 10;
529     } else if (c >= 'A' && c <= 'F') {
530       value += c - 'A' + 10;
531     }
532   }
533 
534   if (value <= maxNumber) {
535     number = value;
536     return NS_OK;
537   }
538 
539   // The error case
540   number = 0;
541   return NS_ERROR_FAILURE;
542 }
543 
544 // IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
545 /* static */
NormalizeIPv4(const nsACString & host,nsCString & result)546 nsresult nsStandardURL::NormalizeIPv4(const nsACString& host,
547                                       nsCString& result) {
548   int32_t bases[4] = {10, 10, 10, 10};
549   bool onlyBase10 = true;  // Track this as a special case
550   int32_t dotIndex[3];     // The positions of the dots in the string
551 
552   // The length may be adjusted by ValidateIPv4Number (ignoring the trailing
553   // period) so use "length", rather than host.Length() after that call.
554   int32_t length = static_cast<int32_t>(host.Length());
555   int32_t dotCount =
556       ValidateIPv4Number(host, bases, dotIndex, onlyBase10, length);
557   if (dotCount < 0 || length <= 0) {
558     return NS_ERROR_FAILURE;
559   }
560 
561   // Max values specified by the spec
562   static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu,
563                                          0xffu};
564   uint32_t ipv4;
565   int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
566 
567   nsresult res;
568   // Doing a special case for all items being base 10 gives ~35% speedup
569   res = (onlyBase10
570              ? ParseIPv4Number10(Substring(host, start, length - start), ipv4,
571                                  upperBounds[dotCount])
572              : ParseIPv4Number(Substring(host, start, length - start),
573                                bases[dotCount], ipv4, upperBounds[dotCount]));
574   if (NS_FAILED(res)) {
575     return NS_ERROR_FAILURE;
576   }
577 
578   int32_t lastUsed = -1;
579   for (int32_t i = 0; i < dotCount; i++) {
580     uint32_t number;
581     start = lastUsed + 1;
582     lastUsed = dotIndex[i];
583     res =
584         (onlyBase10 ? ParseIPv4Number10(
585                           Substring(host, start, lastUsed - start), number, 255)
586                     : ParseIPv4Number(Substring(host, start, lastUsed - start),
587                                       bases[i], number, 255));
588     if (NS_FAILED(res)) {
589       return NS_ERROR_FAILURE;
590     }
591     ipv4 += number << (8 * (3 - i));
592   }
593 
594   uint8_t ipSegments[4];
595   NetworkEndian::writeUint32(ipSegments, ipv4);
596   result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
597                            ipSegments[2], ipSegments[3]);
598   return NS_OK;
599 }
600 
NormalizeIDN(const nsCString & host,nsCString & result)601 nsresult nsStandardURL::NormalizeIDN(const nsCString& host, nsCString& result) {
602   result.Truncate();
603   mDisplayHost.Truncate();
604   nsresult rv;
605 
606   if (!gIDN) {
607     return NS_ERROR_UNEXPECTED;
608   }
609 
610   // If the input is ASCII, and not ACE encoded, then there's no processing
611   // needed. This is needed because we want to allow ascii labels longer than
612   // 64 characters for some schemes.
613   bool isACE = false;
614   if (IsAscii(host) && NS_SUCCEEDED(gIDN->IsACE(host, &isACE)) && !isACE) {
615     mCheckedIfHostA = true;
616     result = host;
617     return NS_OK;
618   }
619 
620   // Even if it's already ACE, we must still call ConvertUTF8toACE in order
621   // for the input normalization to take place.
622   rv = gIDN->ConvertUTF8toACE(host, result);
623   if (NS_FAILED(rv)) {
624     return rv;
625   }
626 
627   // If the ASCII representation doesn't contain the xn-- token then we don't
628   // need to call ConvertToDisplayIDN as that would not change anything.
629   if (!StringBeginsWith(result, "xn--"_ns) &&
630       result.Find(".xn--"_ns) == kNotFound) {
631     mCheckedIfHostA = true;
632     return NS_OK;
633   }
634 
635   bool isAscii = true;
636   nsAutoCString displayHost;
637   rv = gIDN->ConvertToDisplayIDN(result, &isAscii, displayHost);
638   if (NS_FAILED(rv)) {
639     return rv;
640   }
641 
642   mCheckedIfHostA = true;
643   if (!isAscii) {
644     mDisplayHost = displayHost;
645   }
646   return NS_OK;
647 }
648 
ValidIPv6orHostname(const char * host,uint32_t length)649 bool nsStandardURL::ValidIPv6orHostname(const char* host, uint32_t length) {
650   if (!host || !*host) {
651     // Should not be NULL or empty string
652     return false;
653   }
654 
655   if (length != strlen(host)) {
656     // Embedded null
657     return false;
658   }
659 
660   bool openBracket = host[0] == '[';
661   bool closeBracket = host[length - 1] == ']';
662 
663   if (openBracket && closeBracket) {
664     return net_IsValidIPv6Addr(Substring(host + 1, length - 2));
665   }
666 
667   if (openBracket || closeBracket) {
668     // Fail if only one of the brackets is present
669     return false;
670   }
671 
672   const char* end = host + length;
673   const char* iter = host;
674   for (; iter != end && *iter; ++iter) {
675     if (ASCIIMask::IsMasked(sInvalidHostChars, *iter)) {
676       return false;
677     }
678   }
679   return true;
680 }
681 
CoalescePath(netCoalesceFlags coalesceFlag,char * path)682 void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char* path) {
683   net_CoalesceDirs(coalesceFlag, path);
684   int32_t newLen = strlen(path);
685   if (newLen < mPath.mLen) {
686     int32_t diff = newLen - mPath.mLen;
687     mPath.mLen = newLen;
688     mDirectory.mLen += diff;
689     mFilepath.mLen += diff;
690     ShiftFromBasename(diff);
691   }
692 }
693 
AppendSegmentToBuf(char * buf,uint32_t i,const char * str,const URLSegment & segInput,URLSegment & segOutput,const nsCString * escapedStr,bool useEscaped,int32_t * diff)694 uint32_t nsStandardURL::AppendSegmentToBuf(char* buf, uint32_t i,
695                                            const char* str,
696                                            const URLSegment& segInput,
697                                            URLSegment& segOutput,
698                                            const nsCString* escapedStr,
699                                            bool useEscaped, int32_t* diff) {
700   MOZ_ASSERT(segInput.mLen == segOutput.mLen);
701 
702   if (diff) {
703     *diff = 0;
704   }
705 
706   if (segInput.mLen > 0) {
707     if (useEscaped) {
708       MOZ_ASSERT(diff);
709       segOutput.mLen = escapedStr->Length();
710       *diff = segOutput.mLen - segInput.mLen;
711       memcpy(buf + i, escapedStr->get(), segOutput.mLen);
712     } else {
713       memcpy(buf + i, str + segInput.mPos, segInput.mLen);
714     }
715     segOutput.mPos = i;
716     i += segOutput.mLen;
717   } else {
718     segOutput.mPos = i;
719   }
720   return i;
721 }
722 
AppendToBuf(char * buf,uint32_t i,const char * str,uint32_t len)723 uint32_t nsStandardURL::AppendToBuf(char* buf, uint32_t i, const char* str,
724                                     uint32_t len) {
725   memcpy(buf + i, str, len);
726   return i + len;
727 }
728 
729 // basic algorithm:
730 //  1- escape url segments (for improved GetSpec efficiency)
731 //  2- allocate spec buffer
732 //  3- write url segments
733 //  4- update url segment positions and lengths
BuildNormalizedSpec(const char * spec,const Encoding * encoding)734 nsresult nsStandardURL::BuildNormalizedSpec(const char* spec,
735                                             const Encoding* encoding) {
736   // Assumptions: all member URLSegments must be relative the |spec| argument
737   // passed to this function.
738 
739   // buffers for holding escaped url segments (these will remain empty unless
740   // escaping is required).
741   nsAutoCString encUsername, encPassword, encHost, encDirectory, encBasename,
742       encExtension, encQuery, encRef;
743   bool useEncUsername, useEncPassword, useEncHost = false, useEncDirectory,
744                                        useEncBasename, useEncExtension,
745                                        useEncQuery, useEncRef;
746   nsAutoCString portbuf;
747 
748   //
749   // escape each URL segment, if necessary, and calculate approximate normalized
750   // spec length.
751   //
752   // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]
753 
754   uint32_t approxLen = 0;
755 
756   // the scheme is already ASCII
757   if (mScheme.mLen > 0) {
758     approxLen +=
759         mScheme.mLen + 3;  // includes room for "://", which we insert always
760   }
761 
762   // encode URL segments; convert UTF-8 to origin charset and possibly escape.
763   // results written to encXXX variables only if |spec| is not already in the
764   // appropriate encoding.
765   {
766     nsSegmentEncoder encoder;
767     nsSegmentEncoder queryEncoder(encoding);
768     // Username@
769     approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username,
770                                             encUsername, useEncUsername, 0);
771     approxLen += 1;  // reserve length for @
772     // :password - we insert the ':' even if there's no actual password if
773     // "user:@" was in the spec
774     if (mPassword.mLen > 0) {
775       approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password,
776                                                   encPassword, useEncPassword);
777     }
778     // mHost is handled differently below due to encoding differences
779     MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
780     if (mPort != -1 && mPort != mDefaultPort) {
781       // :port
782       portbuf.AppendInt(mPort);
783       approxLen += portbuf.Length() + 1;
784     }
785 
786     approxLen +=
787         1;  // reserve space for possible leading '/' - may not be needed
788     // Should just use mPath?  These are pessimistic, and thus waste space
789     approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory,
790                                             encDirectory, useEncDirectory, 1);
791     approxLen += encoder.EncodeSegmentCount(spec, mBasename, esc_FileBaseName,
792                                             encBasename, useEncBasename);
793     approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension,
794                                             encExtension, useEncExtension, 1);
795 
796     // These next ones *always* add their leading character even if length is 0
797     // Handles items like "http://#"
798     // ?query
799     if (mQuery.mLen >= 0) {
800       approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query,
801                                                        encQuery, useEncQuery);
802     }
803     // #ref
804 
805     if (mRef.mLen >= 0) {
806       approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, encRef,
807                                                   useEncRef);
808     }
809   }
810 
811   // do not escape the hostname, if IPv6 address literal, mHost will
812   // already point to a [ ] delimited IPv6 address literal.
813   // However, perform Unicode normalization on it, as IDN does.
814   // Note that we don't disallow URLs without a host - file:, etc
815   if (mHost.mLen > 0) {
816     nsAutoCString tempHost;
817     NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host,
818                    tempHost);
819     if (tempHost.Contains('\0')) {
820       return NS_ERROR_MALFORMED_URI;  // null embedded in hostname
821     }
822     if (tempHost.Contains(' ')) {
823       return NS_ERROR_MALFORMED_URI;  // don't allow spaces in the hostname
824     }
825     nsresult rv = NormalizeIDN(tempHost, encHost);
826     if (NS_FAILED(rv)) {
827       return rv;
828     }
829     if (!SegmentIs(spec, mScheme, "resource") &&
830         !SegmentIs(spec, mScheme, "chrome")) {
831       nsAutoCString ipString;
832       if (encHost.Length() > 0 && encHost.First() == '[' &&
833           encHost.Last() == ']' &&
834           ValidIPv6orHostname(encHost.get(), encHost.Length())) {
835         rv = (nsresult)rusturl_parse_ipv6addr(&encHost, &ipString);
836         if (NS_FAILED(rv)) {
837           return rv;
838         }
839         encHost = ipString;
840       } else if (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) {
841         encHost = ipString;
842       }
843     }
844 
845     // NormalizeIDN always copies, if the call was successful.
846     useEncHost = true;
847     approxLen += encHost.Length();
848 
849     if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
850       return NS_ERROR_MALFORMED_URI;
851     }
852   } else {
853     // empty host means empty mDisplayHost
854     mDisplayHost.Truncate();
855     mCheckedIfHostA = true;
856   }
857 
858   // We must take a copy of every single segment because they are pointing to
859   // the |spec| while we are changing their value, in case we must use
860   // encoded strings.
861   URLSegment username(mUsername);
862   URLSegment password(mPassword);
863   URLSegment host(mHost);
864   URLSegment path(mPath);
865   URLSegment directory(mDirectory);
866   URLSegment basename(mBasename);
867   URLSegment extension(mExtension);
868   URLSegment query(mQuery);
869   URLSegment ref(mRef);
870 
871   // The encoded string could be longer than the original input, so we need
872   // to check the final URI isn't longer than the max length.
873   if (approxLen + 1 > StaticPrefs::network_standard_url_max_length()) {
874     return NS_ERROR_MALFORMED_URI;
875   }
876 
877   //
878   // generate the normalized URL string
879   //
880   // approxLen should be correct or 1 high
881   if (!mSpec.SetLength(approxLen + 1,
882                        fallible)) {  // buf needs a trailing '\0' below
883     return NS_ERROR_OUT_OF_MEMORY;
884   }
885   char* buf = mSpec.BeginWriting();
886   uint32_t i = 0;
887   int32_t diff = 0;
888 
889   if (mScheme.mLen > 0) {
890     i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme);
891     net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen);
892     i = AppendToBuf(buf, i, "://", 3);
893   }
894 
895   // record authority starting position
896   mAuthority.mPos = i;
897 
898   // append authority
899   if (mUsername.mLen > 0 || mPassword.mLen > 0) {
900     if (mUsername.mLen > 0) {
901       i = AppendSegmentToBuf(buf, i, spec, username, mUsername, &encUsername,
902                              useEncUsername, &diff);
903       ShiftFromPassword(diff);
904     } else {
905       mUsername.mLen = -1;
906     }
907     if (password.mLen > 0) {
908       buf[i++] = ':';
909       i = AppendSegmentToBuf(buf, i, spec, password, mPassword, &encPassword,
910                              useEncPassword, &diff);
911       ShiftFromHost(diff);
912     } else {
913       mPassword.mLen = -1;
914     }
915     buf[i++] = '@';
916   } else {
917     mUsername.mLen = -1;
918     mPassword.mLen = -1;
919   }
920   if (host.mLen > 0) {
921     i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
922                            &diff);
923     ShiftFromPath(diff);
924 
925     net_ToLowerCase(buf + mHost.mPos, mHost.mLen);
926     MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
927     if (mPort != -1 && mPort != mDefaultPort) {
928       buf[i++] = ':';
929       // Already formatted while building approxLen
930       i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length());
931     }
932   }
933 
934   // record authority length
935   mAuthority.mLen = i - mAuthority.mPos;
936 
937   // path must always start with a "/"
938   if (mPath.mLen <= 0) {
939     LOG(("setting path=/"));
940     mDirectory.mPos = mFilepath.mPos = mPath.mPos = i;
941     mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1;
942     // basename must exist, even if empty (bug 113508)
943     mBasename.mPos = i + 1;
944     mBasename.mLen = 0;
945     buf[i++] = '/';
946   } else {
947     uint32_t leadingSlash = 0;
948     if (spec[path.mPos] != '/') {
949       LOG(("adding leading slash to path\n"));
950       leadingSlash = 1;
951       buf[i++] = '/';
952       // basename must exist, even if empty (bugs 113508, 429347)
953       if (mBasename.mLen == -1) {
954         mBasename.mPos = basename.mPos = i;
955         mBasename.mLen = basename.mLen = 0;
956       }
957     }
958 
959     // record corrected (file)path starting position
960     mPath.mPos = mFilepath.mPos = i - leadingSlash;
961 
962     i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory, &encDirectory,
963                            useEncDirectory, &diff);
964     ShiftFromBasename(diff);
965 
966     // the directory must end with a '/'
967     if (buf[i - 1] != '/') {
968       buf[i++] = '/';
969       mDirectory.mLen++;
970     }
971 
972     i = AppendSegmentToBuf(buf, i, spec, basename, mBasename, &encBasename,
973                            useEncBasename, &diff);
974     ShiftFromExtension(diff);
975 
976     // make corrections to directory segment if leadingSlash
977     if (leadingSlash) {
978       mDirectory.mPos = mPath.mPos;
979       if (mDirectory.mLen >= 0) {
980         mDirectory.mLen += leadingSlash;
981       } else {
982         mDirectory.mLen = 1;
983       }
984     }
985 
986     if (mExtension.mLen >= 0) {
987       buf[i++] = '.';
988       i = AppendSegmentToBuf(buf, i, spec, extension, mExtension, &encExtension,
989                              useEncExtension, &diff);
990       ShiftFromQuery(diff);
991     }
992     // calculate corrected filepath length
993     mFilepath.mLen = i - mFilepath.mPos;
994 
995     if (mQuery.mLen >= 0) {
996       buf[i++] = '?';
997       i = AppendSegmentToBuf(buf, i, spec, query, mQuery, &encQuery,
998                              useEncQuery, &diff);
999       ShiftFromRef(diff);
1000     }
1001     if (mRef.mLen >= 0) {
1002       buf[i++] = '#';
1003       i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
1004                              &diff);
1005     }
1006     // calculate corrected path length
1007     mPath.mLen = i - mPath.mPos;
1008   }
1009 
1010   buf[i] = '\0';
1011 
1012   // https://url.spec.whatwg.org/#path-state (1.4.1.2)
1013   // https://url.spec.whatwg.org/#windows-drive-letter
1014   if (SegmentIs(buf, mScheme, "file")) {
1015     char* path = &buf[mPath.mPos];
1016     if (mPath.mLen >= 3 && path[0] == '/' && IsAsciiAlpha(path[1]) &&
1017         path[2] == '|') {
1018       buf[mPath.mPos + 2] = ':';
1019     }
1020   }
1021 
1022   if (mDirectory.mLen > 1) {
1023     netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
1024     if (SegmentIs(buf, mScheme, "ftp")) {
1025       coalesceFlag =
1026           (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
1027                              NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
1028     }
1029     CoalescePath(coalesceFlag, buf + mDirectory.mPos);
1030   }
1031   mSpec.Truncate(strlen(buf));
1032   NS_ASSERTION(mSpec.Length() <= approxLen,
1033                "We've overflowed the mSpec buffer!");
1034   MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
1035              "The spec should never be this long, we missed a check.");
1036 
1037   MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
1038   return NS_OK;
1039 }
1040 
SegmentIs(const URLSegment & seg,const char * val,bool ignoreCase)1041 bool nsStandardURL::SegmentIs(const URLSegment& seg, const char* val,
1042                               bool ignoreCase) {
1043   // one or both may be null
1044   if (!val || mSpec.IsEmpty()) {
1045     return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
1046   }
1047   if (seg.mLen < 0) {
1048     return false;
1049   }
1050   // if the first |seg.mLen| chars of |val| match, then |val| must
1051   // also be null terminated at |seg.mLen|.
1052   if (ignoreCase) {
1053     return !nsCRT::strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
1054            (val[seg.mLen] == '\0');
1055   }
1056 
1057   return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
1058          (val[seg.mLen] == '\0');
1059 }
1060 
SegmentIs(const char * spec,const URLSegment & seg,const char * val,bool ignoreCase)1061 bool nsStandardURL::SegmentIs(const char* spec, const URLSegment& seg,
1062                               const char* val, bool ignoreCase) {
1063   // one or both may be null
1064   if (!val || !spec) {
1065     return (!val && (!spec || seg.mLen < 0));
1066   }
1067   if (seg.mLen < 0) {
1068     return false;
1069   }
1070   // if the first |seg.mLen| chars of |val| match, then |val| must
1071   // also be null terminated at |seg.mLen|.
1072   if (ignoreCase) {
1073     return !nsCRT::strncasecmp(spec + seg.mPos, val, seg.mLen) &&
1074            (val[seg.mLen] == '\0');
1075   }
1076 
1077   return !strncmp(spec + seg.mPos, val, seg.mLen) && (val[seg.mLen] == '\0');
1078 }
1079 
SegmentIs(const URLSegment & seg1,const char * val,const URLSegment & seg2,bool ignoreCase)1080 bool nsStandardURL::SegmentIs(const URLSegment& seg1, const char* val,
1081                               const URLSegment& seg2, bool ignoreCase) {
1082   if (seg1.mLen != seg2.mLen) {
1083     return false;
1084   }
1085   if (seg1.mLen == -1 || (!val && mSpec.IsEmpty())) {
1086     return true;  // both are empty
1087   }
1088   if (!val) {
1089     return false;
1090   }
1091   if (ignoreCase) {
1092     return !nsCRT::strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos,
1093                                seg1.mLen);
1094   }
1095 
1096   return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
1097 }
1098 
ReplaceSegment(uint32_t pos,uint32_t len,const char * val,uint32_t valLen)1099 int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
1100                                       const char* val, uint32_t valLen) {
1101   if (val && valLen) {
1102     if (len == 0) {
1103       mSpec.Insert(val, pos, valLen);
1104     } else {
1105       mSpec.Replace(pos, len, nsDependentCString(val, valLen));
1106     }
1107     return valLen - len;
1108   }
1109 
1110   // else remove the specified segment
1111   mSpec.Cut(pos, len);
1112   return -int32_t(len);
1113 }
1114 
ReplaceSegment(uint32_t pos,uint32_t len,const nsACString & val)1115 int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
1116                                       const nsACString& val) {
1117   if (len == 0) {
1118     mSpec.Insert(val, pos);
1119   } else {
1120     mSpec.Replace(pos, len, val);
1121   }
1122   return val.Length() - len;
1123 }
1124 
ParseURL(const char * spec,int32_t specLen)1125 nsresult nsStandardURL::ParseURL(const char* spec, int32_t specLen) {
1126   nsresult rv;
1127 
1128   if (specLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
1129     return NS_ERROR_MALFORMED_URI;
1130   }
1131 
1132   //
1133   // parse given URL string
1134   //
1135   rv = mParser->ParseURL(spec, specLen, &mScheme.mPos, &mScheme.mLen,
1136                          &mAuthority.mPos, &mAuthority.mLen, &mPath.mPos,
1137                          &mPath.mLen);
1138   if (NS_FAILED(rv)) {
1139     return rv;
1140   }
1141 
1142 #ifdef DEBUG
1143   if (mScheme.mLen <= 0) {
1144     printf("spec=%s\n", spec);
1145     NS_WARNING("malformed url: no scheme");
1146   }
1147 #endif
1148 
1149   if (mAuthority.mLen > 0) {
1150     rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
1151                                  &mUsername.mPos, &mUsername.mLen,
1152                                  &mPassword.mPos, &mPassword.mLen, &mHost.mPos,
1153                                  &mHost.mLen, &mPort);
1154     if (NS_FAILED(rv)) {
1155       return rv;
1156     }
1157 
1158     // Don't allow mPort to be set to this URI's default port
1159     if (mPort == mDefaultPort) {
1160       mPort = -1;
1161     }
1162 
1163     mUsername.mPos += mAuthority.mPos;
1164     mPassword.mPos += mAuthority.mPos;
1165     mHost.mPos += mAuthority.mPos;
1166   }
1167 
1168   if (mPath.mLen > 0) {
1169     rv = ParsePath(spec, mPath.mPos, mPath.mLen);
1170   }
1171 
1172   return rv;
1173 }
1174 
ParsePath(const char * spec,uint32_t pathPos,int32_t pathLen)1175 nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos,
1176                                   int32_t pathLen) {
1177   LOG(("ParsePath: %s pathpos %d len %d\n", spec, pathPos, pathLen));
1178 
1179   if (pathLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
1180     return NS_ERROR_MALFORMED_URI;
1181   }
1182 
1183   nsresult rv = mParser->ParsePath(spec + pathPos, pathLen, &mFilepath.mPos,
1184                                    &mFilepath.mLen, &mQuery.mPos, &mQuery.mLen,
1185                                    &mRef.mPos, &mRef.mLen);
1186   if (NS_FAILED(rv)) {
1187     return rv;
1188   }
1189 
1190   mFilepath.mPos += pathPos;
1191   mQuery.mPos += pathPos;
1192   mRef.mPos += pathPos;
1193 
1194   if (mFilepath.mLen > 0) {
1195     rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
1196                                 &mDirectory.mPos, &mDirectory.mLen,
1197                                 &mBasename.mPos, &mBasename.mLen,
1198                                 &mExtension.mPos, &mExtension.mLen);
1199     if (NS_FAILED(rv)) {
1200       return rv;
1201     }
1202 
1203     mDirectory.mPos += mFilepath.mPos;
1204     mBasename.mPos += mFilepath.mPos;
1205     mExtension.mPos += mFilepath.mPos;
1206   }
1207   return NS_OK;
1208 }
1209 
AppendToSubstring(uint32_t pos,int32_t len,const char * tail)1210 char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len,
1211                                        const char* tail) {
1212   // Verify pos and length are within boundaries
1213   if (pos > mSpec.Length()) {
1214     return nullptr;
1215   }
1216   if (len < 0) {
1217     return nullptr;
1218   }
1219   if ((uint32_t)len > (mSpec.Length() - pos)) {
1220     return nullptr;
1221   }
1222   if (!tail) {
1223     return nullptr;
1224   }
1225 
1226   uint32_t tailLen = strlen(tail);
1227 
1228   // Check for int overflow for proposed length of combined string
1229   if (UINT32_MAX - ((uint32_t)len + 1) < tailLen) {
1230     return nullptr;
1231   }
1232 
1233   char* result = (char*)moz_xmalloc(len + tailLen + 1);
1234   memcpy(result, mSpec.get() + pos, len);
1235   memcpy(result + len, tail, tailLen);
1236   result[len + tailLen] = '\0';
1237   return result;
1238 }
1239 
ReadSegment(nsIBinaryInputStream * stream,URLSegment & seg)1240 nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream,
1241                                     URLSegment& seg) {
1242   nsresult rv;
1243 
1244   rv = stream->Read32(&seg.mPos);
1245   if (NS_FAILED(rv)) {
1246     return rv;
1247   }
1248 
1249   rv = stream->Read32((uint32_t*)&seg.mLen);
1250   if (NS_FAILED(rv)) {
1251     return rv;
1252   }
1253 
1254   return NS_OK;
1255 }
1256 
WriteSegment(nsIBinaryOutputStream * stream,const URLSegment & seg)1257 nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream,
1258                                      const URLSegment& seg) {
1259   nsresult rv;
1260 
1261   rv = stream->Write32(seg.mPos);
1262   if (NS_FAILED(rv)) {
1263     return rv;
1264   }
1265 
1266   rv = stream->Write32(uint32_t(seg.mLen));
1267   if (NS_FAILED(rv)) {
1268     return rv;
1269   }
1270 
1271   return NS_OK;
1272 }
1273 
1274 #define SHIFT_FROM(name, what)               \
1275   void nsStandardURL::name(int32_t diff) {   \
1276     if (!diff) return;                       \
1277     if ((what).mLen >= 0) {                  \
1278       CheckedInt<int32_t> pos = (what).mPos; \
1279       pos += diff;                           \
1280       MOZ_ASSERT(pos.isValid());             \
1281       (what).mPos = pos.value();             \
1282     } else {                                 \
1283       MOZ_RELEASE_ASSERT((what).mLen == -1); \
1284     }
1285 
1286 #define SHIFT_FROM_NEXT(name, what, next) \
1287   SHIFT_FROM(name, what)                  \
1288   next(diff);                             \
1289   }
1290 
1291 #define SHIFT_FROM_LAST(name, what) \
1292   SHIFT_FROM(name, what)            \
1293   }
1294 
1295 SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
1296 SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
1297 SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
1298 SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
1299 SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
1300 SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
1301 SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
1302 SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
1303 SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
1304 SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
1305 SHIFT_FROM_LAST(ShiftFromRef, mRef)
1306 
1307 //----------------------------------------------------------------------------
1308 // nsStandardURL::nsIClassInfo
1309 //----------------------------------------------------------------------------
1310 
1311 NS_IMPL_CLASSINFO(nsStandardURL, nullptr, nsIClassInfo::THREADSAFE,
1312                   NS_STANDARDURL_CID)
1313 // Empty CI getter. We only need nsIClassInfo for Serialization
1314 NS_IMPL_CI_INTERFACE_GETTER0(nsStandardURL)
1315 
1316 //----------------------------------------------------------------------------
1317 // nsStandardURL::nsISupports
1318 //----------------------------------------------------------------------------
1319 
1320 NS_IMPL_ADDREF(nsStandardURL)
1321 NS_IMPL_RELEASE(nsStandardURL)
1322 
1323 NS_INTERFACE_MAP_BEGIN(nsStandardURL)
1324   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
1325   NS_INTERFACE_MAP_ENTRY(nsIURI)
1326   NS_INTERFACE_MAP_ENTRY(nsIURL)
1327   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
1328   NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
1329   NS_INTERFACE_MAP_ENTRY(nsISerializable)
1330   NS_IMPL_QUERY_CLASSINFO(nsStandardURL)
1331   NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
1332   // see nsStandardURL::Equals
1333   if (aIID.Equals(kThisImplCID)) {
1334     foundInterface = static_cast<nsIURI*>(this);
1335   } else
NS_INTERFACE_MAP_ENTRY(nsISizeOf)1336     NS_INTERFACE_MAP_ENTRY(nsISizeOf)
1337 NS_INTERFACE_MAP_END
1338 
1339 //----------------------------------------------------------------------------
1340 // nsStandardURL::nsIURI
1341 //----------------------------------------------------------------------------
1342 
1343 // result may contain unescaped UTF-8 characters
1344 NS_IMETHODIMP
1345 nsStandardURL::GetSpec(nsACString& result) {
1346   MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
1347              "The spec should never be this long, we missed a check.");
1348   result = mSpec;
1349   return NS_OK;
1350 }
1351 
1352 // result may contain unescaped UTF-8 characters
1353 NS_IMETHODIMP
GetSensitiveInfoHiddenSpec(nsACString & result)1354 nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString& result) {
1355   nsresult rv = GetSpec(result);
1356   if (NS_FAILED(rv)) {
1357     return rv;
1358   }
1359   if (mPassword.mLen > 0) {
1360     result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****");
1361   }
1362   return NS_OK;
1363 }
1364 
1365 // result may contain unescaped UTF-8 characters
1366 NS_IMETHODIMP
GetSpecIgnoringRef(nsACString & result)1367 nsStandardURL::GetSpecIgnoringRef(nsACString& result) {
1368   // URI without ref is 0 to one char before ref
1369   if (mRef.mLen < 0) {
1370     return GetSpec(result);
1371   }
1372 
1373   URLSegment noRef(0, mRef.mPos - 1);
1374   result = Segment(noRef);
1375   MOZ_ASSERT(mCheckedIfHostA);
1376   return NS_OK;
1377 }
1378 
CheckIfHostIsAscii()1379 nsresult nsStandardURL::CheckIfHostIsAscii() {
1380   nsresult rv;
1381   if (mCheckedIfHostA) {
1382     return NS_OK;
1383   }
1384 
1385   mCheckedIfHostA = true;
1386 
1387   if (!gIDN) {
1388     return NS_ERROR_NOT_INITIALIZED;
1389   }
1390 
1391   nsAutoCString displayHost;
1392   bool isAscii;
1393   rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost);
1394   if (NS_FAILED(rv)) {
1395     mDisplayHost.Truncate();
1396     mCheckedIfHostA = false;
1397     return rv;
1398   }
1399 
1400   if (!isAscii) {
1401     mDisplayHost = displayHost;
1402   }
1403 
1404   return NS_OK;
1405 }
1406 
1407 NS_IMETHODIMP
GetDisplaySpec(nsACString & aUnicodeSpec)1408 nsStandardURL::GetDisplaySpec(nsACString& aUnicodeSpec) {
1409   aUnicodeSpec.Assign(mSpec);
1410   MOZ_ASSERT(mCheckedIfHostA);
1411   if (!mDisplayHost.IsEmpty()) {
1412     aUnicodeSpec.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
1413   }
1414 
1415   return NS_OK;
1416 }
1417 
1418 NS_IMETHODIMP
GetDisplayHostPort(nsACString & aUnicodeHostPort)1419 nsStandardURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
1420   nsAutoCString unicodeHostPort;
1421 
1422   nsresult rv = GetDisplayHost(unicodeHostPort);
1423   if (NS_FAILED(rv)) {
1424     return rv;
1425   }
1426 
1427   if (StringBeginsWith(Hostport(), "["_ns)) {
1428     aUnicodeHostPort.AssignLiteral("[");
1429     aUnicodeHostPort.Append(unicodeHostPort);
1430     aUnicodeHostPort.AppendLiteral("]");
1431   } else {
1432     aUnicodeHostPort.Assign(unicodeHostPort);
1433   }
1434 
1435   uint32_t pos = mHost.mPos + mHost.mLen;
1436   if (pos < mPath.mPos) {
1437     aUnicodeHostPort += Substring(mSpec, pos, mPath.mPos - pos);
1438   }
1439 
1440   return NS_OK;
1441 }
1442 
1443 NS_IMETHODIMP
GetDisplayHost(nsACString & aUnicodeHost)1444 nsStandardURL::GetDisplayHost(nsACString& aUnicodeHost) {
1445   MOZ_ASSERT(mCheckedIfHostA);
1446   if (mDisplayHost.IsEmpty()) {
1447     return GetAsciiHost(aUnicodeHost);
1448   }
1449 
1450   aUnicodeHost = mDisplayHost;
1451   return NS_OK;
1452 }
1453 
1454 // result may contain unescaped UTF-8 characters
1455 NS_IMETHODIMP
GetPrePath(nsACString & result)1456 nsStandardURL::GetPrePath(nsACString& result) {
1457   result = Prepath();
1458   MOZ_ASSERT(mCheckedIfHostA);
1459   return NS_OK;
1460 }
1461 
1462 // result may contain unescaped UTF-8 characters
1463 NS_IMETHODIMP
GetDisplayPrePath(nsACString & result)1464 nsStandardURL::GetDisplayPrePath(nsACString& result) {
1465   result = Prepath();
1466   MOZ_ASSERT(mCheckedIfHostA);
1467   if (!mDisplayHost.IsEmpty()) {
1468     result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
1469   }
1470   return NS_OK;
1471 }
1472 
1473 // result is strictly US-ASCII
1474 NS_IMETHODIMP
GetScheme(nsACString & result)1475 nsStandardURL::GetScheme(nsACString& result) {
1476   result = Scheme();
1477   return NS_OK;
1478 }
1479 
1480 // result may contain unescaped UTF-8 characters
1481 NS_IMETHODIMP
GetUserPass(nsACString & result)1482 nsStandardURL::GetUserPass(nsACString& result) {
1483   result = Userpass();
1484   return NS_OK;
1485 }
1486 
1487 // result may contain unescaped UTF-8 characters
1488 NS_IMETHODIMP
GetUsername(nsACString & result)1489 nsStandardURL::GetUsername(nsACString& result) {
1490   result = Username();
1491   return NS_OK;
1492 }
1493 
1494 // result may contain unescaped UTF-8 characters
1495 NS_IMETHODIMP
GetPassword(nsACString & result)1496 nsStandardURL::GetPassword(nsACString& result) {
1497   result = Password();
1498   return NS_OK;
1499 }
1500 
1501 NS_IMETHODIMP
GetHostPort(nsACString & result)1502 nsStandardURL::GetHostPort(nsACString& result) {
1503   return GetAsciiHostPort(result);
1504 }
1505 
1506 NS_IMETHODIMP
GetHost(nsACString & result)1507 nsStandardURL::GetHost(nsACString& result) { return GetAsciiHost(result); }
1508 
1509 NS_IMETHODIMP
GetPort(int32_t * result)1510 nsStandardURL::GetPort(int32_t* result) {
1511   // should never be more than 16 bit
1512   MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
1513   *result = mPort;
1514   return NS_OK;
1515 }
1516 
1517 // result may contain unescaped UTF-8 characters
1518 NS_IMETHODIMP
GetPathQueryRef(nsACString & result)1519 nsStandardURL::GetPathQueryRef(nsACString& result) {
1520   result = Path();
1521   return NS_OK;
1522 }
1523 
1524 // result is ASCII
1525 NS_IMETHODIMP
GetAsciiSpec(nsACString & result)1526 nsStandardURL::GetAsciiSpec(nsACString& result) {
1527   result = mSpec;
1528   return NS_OK;
1529 }
1530 
1531 // result is ASCII
1532 NS_IMETHODIMP
GetAsciiHostPort(nsACString & result)1533 nsStandardURL::GetAsciiHostPort(nsACString& result) {
1534   result = Hostport();
1535   return NS_OK;
1536 }
1537 
1538 // result is ASCII
1539 NS_IMETHODIMP
GetAsciiHost(nsACString & result)1540 nsStandardURL::GetAsciiHost(nsACString& result) {
1541   result = Host();
1542   return NS_OK;
1543 }
1544 
IsSpecialProtocol(const nsACString & input)1545 static bool IsSpecialProtocol(const nsACString& input) {
1546   nsACString::const_iterator start, end;
1547   input.BeginReading(start);
1548   nsACString::const_iterator iterator(start);
1549   input.EndReading(end);
1550 
1551   while (iterator != end && *iterator != ':') {
1552     iterator++;
1553   }
1554 
1555   nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get()));
1556 
1557   return protocol.LowerCaseEqualsLiteral("http") ||
1558          protocol.LowerCaseEqualsLiteral("https") ||
1559          protocol.LowerCaseEqualsLiteral("ftp") ||
1560          protocol.LowerCaseEqualsLiteral("ws") ||
1561          protocol.LowerCaseEqualsLiteral("wss") ||
1562          protocol.LowerCaseEqualsLiteral("file") ||
1563          protocol.LowerCaseEqualsLiteral("gopher");
1564 }
1565 
SetSpecInternal(const nsACString & input)1566 nsresult nsStandardURL::SetSpecInternal(const nsACString& input) {
1567   return SetSpecWithEncoding(input, nullptr);
1568 }
1569 
SetSpecWithEncoding(const nsACString & input,const Encoding * encoding)1570 nsresult nsStandardURL::SetSpecWithEncoding(const nsACString& input,
1571                                             const Encoding* encoding) {
1572   const nsPromiseFlatCString& flat = PromiseFlatCString(input);
1573   LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
1574 
1575   if (input.Length() > StaticPrefs::network_standard_url_max_length()) {
1576     return NS_ERROR_MALFORMED_URI;
1577   }
1578 
1579   // filter out unexpected chars "\r\n\t" if necessary
1580   nsAutoCString filteredURI;
1581   net_FilterURIString(flat, filteredURI);
1582 
1583   if (filteredURI.Length() == 0) {
1584     return NS_ERROR_MALFORMED_URI;
1585   }
1586 
1587   // Make a backup of the current URL
1588   nsStandardURL prevURL(false, false);
1589   prevURL.CopyMembers(this, eHonorRef, ""_ns);
1590   Clear();
1591 
1592   if (IsSpecialProtocol(filteredURI)) {
1593     // Bug 652186: Replace all backslashes with slashes when parsing paths
1594     // Stop when we reach the query or the hash.
1595     auto* start = filteredURI.BeginWriting();
1596     auto* end = filteredURI.EndWriting();
1597     while (start != end) {
1598       if (*start == '?' || *start == '#') {
1599         break;
1600       }
1601       if (*start == '\\') {
1602         *start = '/';
1603       }
1604       start++;
1605     }
1606   }
1607 
1608   const char* spec = filteredURI.get();
1609   int32_t specLength = filteredURI.Length();
1610 
1611   // parse the given URL...
1612   nsresult rv = ParseURL(spec, specLength);
1613   if (mScheme.mLen <= 0) {
1614     rv = NS_ERROR_MALFORMED_URI;
1615   }
1616   if (NS_SUCCEEDED(rv)) {
1617     // finally, use the URLSegment member variables to build a normalized
1618     // copy of |spec|
1619     rv = BuildNormalizedSpec(spec, encoding);
1620   }
1621 
1622   // Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
1623   if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) {
1624     rv = NS_ERROR_MALFORMED_URI;
1625   }
1626 
1627   if (NS_FAILED(rv)) {
1628     Clear();
1629     // If parsing the spec has failed, restore the old URL
1630     // so we don't end up with an empty URL.
1631     CopyMembers(&prevURL, eHonorRef, ""_ns);
1632     return rv;
1633   }
1634 
1635   if (LOG_ENABLED()) {
1636     LOG((" spec      = %s\n", mSpec.get()));
1637     LOG((" port      = %d\n", mPort));
1638     LOG((" scheme    = (%u,%d)\n", mScheme.mPos, mScheme.mLen));
1639     LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen));
1640     LOG((" username  = (%u,%d)\n", mUsername.mPos, mUsername.mLen));
1641     LOG((" password  = (%u,%d)\n", mPassword.mPos, mPassword.mLen));
1642     LOG((" hostname  = (%u,%d)\n", mHost.mPos, mHost.mLen));
1643     LOG((" path      = (%u,%d)\n", mPath.mPos, mPath.mLen));
1644     LOG((" filepath  = (%u,%d)\n", mFilepath.mPos, mFilepath.mLen));
1645     LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen));
1646     LOG((" basename  = (%u,%d)\n", mBasename.mPos, mBasename.mLen));
1647     LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen));
1648     LOG((" query     = (%u,%d)\n", mQuery.mPos, mQuery.mLen));
1649     LOG((" ref       = (%u,%d)\n", mRef.mPos, mRef.mLen));
1650   }
1651 
1652   SanityCheck();
1653   return rv;
1654 }
1655 
SetScheme(const nsACString & input)1656 nsresult nsStandardURL::SetScheme(const nsACString& input) {
1657   const nsPromiseFlatCString& scheme = PromiseFlatCString(input);
1658 
1659   LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
1660 
1661   if (scheme.IsEmpty()) {
1662     NS_WARNING("cannot remove the scheme from an url");
1663     return NS_ERROR_UNEXPECTED;
1664   }
1665   if (mScheme.mLen < 0) {
1666     NS_WARNING("uninitialized");
1667     return NS_ERROR_NOT_INITIALIZED;
1668   }
1669 
1670   if (!net_IsValidScheme(scheme)) {
1671     NS_WARNING("the given url scheme contains invalid characters");
1672     return NS_ERROR_UNEXPECTED;
1673   }
1674 
1675   if (mSpec.Length() + input.Length() - Scheme().Length() >
1676       StaticPrefs::network_standard_url_max_length()) {
1677     return NS_ERROR_MALFORMED_URI;
1678   }
1679 
1680   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
1681 
1682   InvalidateCache();
1683 
1684   int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
1685 
1686   if (shift) {
1687     mScheme.mLen = scheme.Length();
1688     ShiftFromAuthority(shift);
1689   }
1690 
1691   // ensure new scheme is lowercase
1692   //
1693   // XXX the string code unfortunately doesn't provide a ToLowerCase
1694   //     that operates on a substring.
1695   net_ToLowerCase((char*)mSpec.get(), mScheme.mLen);
1696   return NS_OK;
1697 }
1698 
SetUserPass(const nsACString & input)1699 nsresult nsStandardURL::SetUserPass(const nsACString& input) {
1700   const nsPromiseFlatCString& userpass = PromiseFlatCString(input);
1701 
1702   LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get()));
1703 
1704   if (mURLType == URLTYPE_NO_AUTHORITY) {
1705     if (userpass.IsEmpty()) {
1706       return NS_OK;
1707     }
1708     NS_WARNING("cannot set user:pass on no-auth url");
1709     return NS_ERROR_UNEXPECTED;
1710   }
1711   if (mAuthority.mLen < 0) {
1712     NS_WARNING("uninitialized");
1713     return NS_ERROR_NOT_INITIALIZED;
1714   }
1715 
1716   if (mSpec.Length() + input.Length() - Userpass(true).Length() >
1717       StaticPrefs::network_standard_url_max_length()) {
1718     return NS_ERROR_MALFORMED_URI;
1719   }
1720 
1721   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
1722   InvalidateCache();
1723 
1724   NS_ASSERTION(mHost.mLen >= 0, "uninitialized");
1725 
1726   nsresult rv;
1727   uint32_t usernamePos, passwordPos;
1728   int32_t usernameLen, passwordLen;
1729 
1730   rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(), &usernamePos,
1731                               &usernameLen, &passwordPos, &passwordLen);
1732   if (NS_FAILED(rv)) {
1733     return rv;
1734   }
1735 
1736   // build new user:pass in |buf|
1737   nsAutoCString buf;
1738   if (usernameLen > 0 || passwordLen > 0) {
1739     nsSegmentEncoder encoder;
1740     bool ignoredOut;
1741     usernameLen = encoder.EncodeSegmentCount(
1742         userpass.get(), URLSegment(usernamePos, usernameLen),
1743         esc_Username | esc_AlwaysCopy, buf, ignoredOut);
1744     if (passwordLen > 0) {
1745       buf.Append(':');
1746       passwordLen = encoder.EncodeSegmentCount(
1747           userpass.get(), URLSegment(passwordPos, passwordLen),
1748           esc_Password | esc_AlwaysCopy, buf, ignoredOut);
1749     } else {
1750       passwordLen = -1;
1751     }
1752     if (mUsername.mLen < 0 && mPassword.mLen < 0) {
1753       buf.Append('@');
1754     }
1755   }
1756 
1757   int32_t shift = 0;
1758 
1759   if (mUsername.mLen < 0 && mPassword.mLen < 0) {
1760     // no existing user:pass
1761     if (!buf.IsEmpty()) {
1762       mSpec.Insert(buf, mHost.mPos);
1763       mUsername.mPos = mHost.mPos;
1764       shift = buf.Length();
1765     }
1766   } else {
1767     // replace existing user:pass
1768     uint32_t userpassLen = 0;
1769     if (mUsername.mLen > 0) {
1770       userpassLen += mUsername.mLen;
1771     }
1772     if (mPassword.mLen > 0) {
1773       userpassLen += (mPassword.mLen + 1);
1774     }
1775     if (buf.IsEmpty()) {
1776       // remove `@` character too
1777       userpassLen++;
1778     }
1779     mSpec.Replace(mAuthority.mPos, userpassLen, buf);
1780     shift = buf.Length() - userpassLen;
1781   }
1782   if (shift) {
1783     ShiftFromHost(shift);
1784     MOZ_DIAGNOSTIC_ASSERT(mAuthority.mLen >= -shift);
1785     mAuthority.mLen += shift;
1786   }
1787   // update positions and lengths
1788   mUsername.mLen = usernameLen > 0 ? usernameLen : -1;
1789   mUsername.mPos = mAuthority.mPos;
1790   mPassword.mLen = passwordLen > 0 ? passwordLen : -1;
1791   if (passwordLen > 0) {
1792     if (mUsername.mLen > 0) {
1793       mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
1794     } else {
1795       mPassword.mPos = mAuthority.mPos + 1;
1796     }
1797   }
1798 
1799   MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
1800   return NS_OK;
1801 }
1802 
SetUsername(const nsACString & input)1803 nsresult nsStandardURL::SetUsername(const nsACString& input) {
1804   const nsPromiseFlatCString& username = PromiseFlatCString(input);
1805 
1806   LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get()));
1807 
1808   if (mURLType == URLTYPE_NO_AUTHORITY) {
1809     if (username.IsEmpty()) {
1810       return NS_OK;
1811     }
1812     NS_WARNING("cannot set username on no-auth url");
1813     return NS_ERROR_UNEXPECTED;
1814   }
1815 
1816   if (mSpec.Length() + input.Length() - Username().Length() >
1817       StaticPrefs::network_standard_url_max_length()) {
1818     return NS_ERROR_MALFORMED_URI;
1819   }
1820 
1821   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
1822 
1823   InvalidateCache();
1824 
1825   // escape username if necessary
1826   nsAutoCString buf;
1827   nsSegmentEncoder encoder;
1828   const nsACString& escUsername =
1829       encoder.EncodeSegment(username, esc_Username, buf);
1830 
1831   int32_t shift = 0;
1832 
1833   if (mUsername.mLen < 0 && escUsername.IsEmpty()) {
1834     return NS_OK;
1835   }
1836 
1837   if (mUsername.mLen < 0 && mPassword.mLen < 0) {
1838     MOZ_ASSERT(!escUsername.IsEmpty(), "Should not be empty at this point");
1839     mUsername.mPos = mAuthority.mPos;
1840     mSpec.Insert(escUsername + "@"_ns, mUsername.mPos);
1841     shift = escUsername.Length() + 1;
1842     mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
1843   } else {
1844     uint32_t pos = mUsername.mLen < 0 ? mAuthority.mPos : mUsername.mPos;
1845     int32_t len = mUsername.mLen < 0 ? 0 : mUsername.mLen;
1846 
1847     if (mPassword.mLen < 0 && escUsername.IsEmpty()) {
1848       len++;  // remove the @ character too
1849     }
1850     shift = ReplaceSegment(pos, len, escUsername);
1851     mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
1852     mUsername.mPos = pos;
1853   }
1854 
1855   if (shift) {
1856     mAuthority.mLen += shift;
1857     ShiftFromPassword(shift);
1858   }
1859 
1860   MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
1861   return NS_OK;
1862 }
1863 
SetPassword(const nsACString & input)1864 nsresult nsStandardURL::SetPassword(const nsACString& input) {
1865   const nsPromiseFlatCString& password = PromiseFlatCString(input);
1866 
1867   auto clearedPassword = MakeScopeExit([&password, this]() {
1868     // Check that if this method is called with the empty string then the
1869     // password is definitely cleared when exiting this method.
1870     if (password.IsEmpty()) {
1871       MOZ_DIAGNOSTIC_ASSERT(this->Password().IsEmpty());
1872     }
1873     Unused << this;  // silence compiler -Wunused-lambda-capture
1874   });
1875 
1876   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
1877 
1878   LOG(("nsStandardURL::SetPassword [password=%s]\n", password.get()));
1879 
1880   if (mURLType == URLTYPE_NO_AUTHORITY) {
1881     if (password.IsEmpty()) {
1882       return NS_OK;
1883     }
1884     NS_WARNING("cannot set password on no-auth url");
1885     return NS_ERROR_UNEXPECTED;
1886   }
1887 
1888   if (mSpec.Length() + input.Length() - Password().Length() >
1889       StaticPrefs::network_standard_url_max_length()) {
1890     return NS_ERROR_MALFORMED_URI;
1891   }
1892 
1893   InvalidateCache();
1894 
1895   if (password.IsEmpty()) {
1896     if (mPassword.mLen > 0) {
1897       // cut(":password")
1898       int32_t len = mPassword.mLen;
1899       if (mUsername.mLen < 0) {
1900         len++;  // also cut the @ character
1901       }
1902       len++;  // for the : character
1903       mSpec.Cut(mPassword.mPos - 1, len);
1904       ShiftFromHost(-len);
1905       mAuthority.mLen -= len;
1906       mPassword.mLen = -1;
1907     }
1908     MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
1909     return NS_OK;
1910   }
1911 
1912   // escape password if necessary
1913   nsAutoCString buf;
1914   nsSegmentEncoder encoder;
1915   const nsACString& escPassword =
1916       encoder.EncodeSegment(password, esc_Password, buf);
1917 
1918   int32_t shift;
1919 
1920   if (mPassword.mLen < 0) {
1921     if (mUsername.mLen > 0) {
1922       mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
1923       mSpec.Insert(":"_ns + escPassword, mPassword.mPos - 1);
1924       shift = escPassword.Length() + 1;
1925     } else {
1926       mPassword.mPos = mAuthority.mPos + 1;
1927       mSpec.Insert(":"_ns + escPassword + "@"_ns, mPassword.mPos - 1);
1928       shift = escPassword.Length() + 2;
1929     }
1930   } else {
1931     shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);
1932   }
1933 
1934   if (shift) {
1935     mPassword.mLen = escPassword.Length();
1936     mAuthority.mLen += shift;
1937     ShiftFromHost(shift);
1938   }
1939 
1940   MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
1941   return NS_OK;
1942 }
1943 
FindHostLimit(nsACString::const_iterator & aStart,nsACString::const_iterator & aEnd)1944 void nsStandardURL::FindHostLimit(nsACString::const_iterator& aStart,
1945                                   nsACString::const_iterator& aEnd) {
1946   for (int32_t i = 0; gHostLimitDigits[i]; ++i) {
1947     nsACString::const_iterator c(aStart);
1948     if (FindCharInReadable(gHostLimitDigits[i], c, aEnd)) {
1949       aEnd = c;
1950     }
1951   }
1952 }
1953 
1954 // If aValue only has a host part and no port number, the port
1955 // will not be reset!!!
SetHostPort(const nsACString & aValue)1956 nsresult nsStandardURL::SetHostPort(const nsACString& aValue) {
1957   // We cannot simply call nsIURI::SetHost because that would treat the name as
1958   // an IPv6 address (like http:://[server:443]/).  We also cannot call
1959   // nsIURI::SetHostPort because that isn't implemented.  Sadfaces.
1960 
1961   nsACString::const_iterator start, end;
1962   aValue.BeginReading(start);
1963   aValue.EndReading(end);
1964   nsACString::const_iterator iter(start);
1965   bool isIPv6 = false;
1966 
1967   FindHostLimit(start, end);
1968 
1969   if (*start == '[') {  // IPv6 address
1970     if (!FindCharInReadable(']', iter, end)) {
1971       // the ] character is missing
1972       return NS_ERROR_MALFORMED_URI;
1973     }
1974     // iter now at the ']' character
1975     isIPv6 = true;
1976   } else {
1977     nsACString::const_iterator iter2(start);
1978     if (FindCharInReadable(']', iter2, end)) {
1979       // if the first char isn't [ then there should be no ] character
1980       return NS_ERROR_MALFORMED_URI;
1981     }
1982   }
1983 
1984   FindCharInReadable(':', iter, end);
1985 
1986   if (!isIPv6 && iter != end) {
1987     nsACString::const_iterator iter2(iter);
1988     iter2++;  // Skip over the first ':' character
1989     if (FindCharInReadable(':', iter2, end)) {
1990       // If there is more than one ':' character it suggests an IPv6
1991       // The format should be [2001::1]:80 where the port is optional
1992       return NS_ERROR_MALFORMED_URI;
1993     }
1994   }
1995 
1996   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
1997 
1998   nsresult rv = SetHost(Substring(start, iter));
1999   NS_ENSURE_SUCCESS(rv, rv);
2000 
2001   if (iter == end) {
2002     // does not end in colon
2003     return NS_OK;
2004   }
2005 
2006   iter++;  // advance over the colon
2007   if (iter == end) {
2008     // port number is missing
2009     return NS_OK;
2010   }
2011 
2012   nsCString portStr(Substring(iter, end));
2013   int32_t port = portStr.ToInteger(&rv);
2014   if (NS_FAILED(rv)) {
2015     // Failure parsing the port number
2016     return NS_OK;
2017   }
2018 
2019   Unused << SetPort(port);
2020   return NS_OK;
2021 }
2022 
SetHost(const nsACString & input)2023 nsresult nsStandardURL::SetHost(const nsACString& input) {
2024   const nsPromiseFlatCString& hostname = PromiseFlatCString(input);
2025 
2026   nsACString::const_iterator start, end;
2027   hostname.BeginReading(start);
2028   hostname.EndReading(end);
2029 
2030   FindHostLimit(start, end);
2031 
2032   const nsCString unescapedHost(Substring(start, end));
2033   // Do percent decoding on the the input.
2034   nsAutoCString flat;
2035   NS_UnescapeURL(unescapedHost.BeginReading(), unescapedHost.Length(),
2036                  esc_AlwaysCopy | esc_Host, flat);
2037   const char* host = flat.get();
2038 
2039   LOG(("nsStandardURL::SetHost [host=%s]\n", host));
2040 
2041   if (mURLType == URLTYPE_NO_AUTHORITY) {
2042     if (flat.IsEmpty()) {
2043       return NS_OK;
2044     }
2045     NS_WARNING("cannot set host on no-auth url");
2046     return NS_ERROR_UNEXPECTED;
2047   }
2048   if (flat.IsEmpty()) {
2049     // Setting an empty hostname is not allowed for
2050     // URLTYPE_STANDARD and URLTYPE_AUTHORITY.
2051     return NS_ERROR_UNEXPECTED;
2052   }
2053 
2054   if (strlen(host) < flat.Length()) {
2055     return NS_ERROR_MALFORMED_URI;  // found embedded null
2056   }
2057 
2058   // For consistency with SetSpec/nsURLParsers, don't allow spaces
2059   // in the hostname.
2060   if (strchr(host, ' ')) {
2061     return NS_ERROR_MALFORMED_URI;
2062   }
2063 
2064   if (mSpec.Length() + strlen(host) - Host().Length() >
2065       StaticPrefs::network_standard_url_max_length()) {
2066     return NS_ERROR_MALFORMED_URI;
2067   }
2068 
2069   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
2070   InvalidateCache();
2071 
2072   uint32_t len;
2073   nsAutoCString hostBuf;
2074   nsresult rv = NormalizeIDN(flat, hostBuf);
2075   if (NS_FAILED(rv)) {
2076     return rv;
2077   }
2078 
2079   if (!SegmentIs(mScheme, "resource") && !SegmentIs(mScheme, "chrome")) {
2080     nsAutoCString ipString;
2081     if (hostBuf.Length() > 0 && hostBuf.First() == '[' &&
2082         hostBuf.Last() == ']' &&
2083         ValidIPv6orHostname(hostBuf.get(), hostBuf.Length())) {
2084       rv = (nsresult)rusturl_parse_ipv6addr(&hostBuf, &ipString);
2085       if (NS_FAILED(rv)) {
2086         return rv;
2087       }
2088       hostBuf = ipString;
2089     } else if (NS_SUCCEEDED(NormalizeIPv4(hostBuf, ipString))) {
2090       hostBuf = ipString;
2091     }
2092   }
2093 
2094   // NormalizeIDN always copies if the call was successful
2095   host = hostBuf.get();
2096   len = hostBuf.Length();
2097 
2098   if (!ValidIPv6orHostname(host, len)) {
2099     return NS_ERROR_MALFORMED_URI;
2100   }
2101 
2102   if (mHost.mLen < 0) {
2103     int port_length = 0;
2104     if (mPort != -1) {
2105       nsAutoCString buf;
2106       buf.Assign(':');
2107       buf.AppendInt(mPort);
2108       port_length = buf.Length();
2109     }
2110     if (mAuthority.mLen > 0) {
2111       mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
2112       mHost.mLen = 0;
2113     } else if (mScheme.mLen > 0) {
2114       mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
2115       mHost.mLen = 0;
2116     }
2117   }
2118 
2119   int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);
2120 
2121   if (shift) {
2122     mHost.mLen = len;
2123     mAuthority.mLen += shift;
2124     ShiftFromPath(shift);
2125   }
2126 
2127   // Now canonicalize the host to lowercase
2128   net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
2129   return NS_OK;
2130 }
2131 
SetPort(int32_t port)2132 nsresult nsStandardURL::SetPort(int32_t port) {
2133   LOG(("nsStandardURL::SetPort [port=%d]\n", port));
2134 
2135   if ((port == mPort) || (mPort == -1 && port == mDefaultPort)) {
2136     return NS_OK;
2137   }
2138 
2139   // ports must be >= 0 and 16 bit
2140   // -1 == use default
2141   if (port < -1 || port > std::numeric_limits<uint16_t>::max()) {
2142     return NS_ERROR_MALFORMED_URI;
2143   }
2144 
2145   if (mURLType == URLTYPE_NO_AUTHORITY) {
2146     NS_WARNING("cannot set port on no-auth url");
2147     return NS_ERROR_UNEXPECTED;
2148   }
2149 
2150   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
2151 
2152   InvalidateCache();
2153   if (port == mDefaultPort) {
2154     port = -1;
2155   }
2156 
2157   ReplacePortInSpec(port);
2158 
2159   mPort = port;
2160   return NS_OK;
2161 }
2162 
2163 /**
2164  * Replaces the existing port in mSpec with aNewPort.
2165  *
2166  * The caller is responsible for:
2167  *  - Calling InvalidateCache (since our mSpec is changing).
2168  *  - Checking whether aNewPort is mDefaultPort (in which case the
2169  *    caller should pass aNewPort=-1).
2170  */
ReplacePortInSpec(int32_t aNewPort)2171 void nsStandardURL::ReplacePortInSpec(int32_t aNewPort) {
2172   NS_ASSERTION(aNewPort != mDefaultPort || mDefaultPort == -1,
2173                "Caller should check its passed-in value and pass -1 instead of "
2174                "mDefaultPort, to avoid encoding default port into mSpec");
2175 
2176   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
2177 
2178   // Create the (possibly empty) string that we're planning to replace:
2179   nsAutoCString buf;
2180   if (mPort != -1) {
2181     buf.Assign(':');
2182     buf.AppendInt(mPort);
2183   }
2184   // Find the position & length of that string:
2185   const uint32_t replacedLen = buf.Length();
2186   const uint32_t replacedStart =
2187       mAuthority.mPos + mAuthority.mLen - replacedLen;
2188 
2189   // Create the (possibly empty) replacement string:
2190   if (aNewPort == -1) {
2191     buf.Truncate();
2192   } else {
2193     buf.Assign(':');
2194     buf.AppendInt(aNewPort);
2195   }
2196   // Perform the replacement:
2197   mSpec.Replace(replacedStart, replacedLen, buf);
2198 
2199   // Bookkeeping to reflect the new length:
2200   int32_t shift = buf.Length() - replacedLen;
2201   mAuthority.mLen += shift;
2202   ShiftFromPath(shift);
2203 }
2204 
SetPathQueryRef(const nsACString & input)2205 nsresult nsStandardURL::SetPathQueryRef(const nsACString& input) {
2206   const nsPromiseFlatCString& path = PromiseFlatCString(input);
2207   LOG(("nsStandardURL::SetPathQueryRef [path=%s]\n", path.get()));
2208   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
2209 
2210   InvalidateCache();
2211 
2212   if (!path.IsEmpty()) {
2213     nsAutoCString spec;
2214 
2215     spec.Assign(mSpec.get(), mPath.mPos);
2216     if (path.First() != '/') {
2217       spec.Append('/');
2218     }
2219     spec.Append(path);
2220 
2221     return SetSpecInternal(spec);
2222   }
2223   if (mPath.mLen >= 1) {
2224     mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1);
2225     // these contain only a '/'
2226     mPath.mLen = 1;
2227     mDirectory.mLen = 1;
2228     mFilepath.mLen = 1;
2229     // these are no longer defined
2230     mBasename.mLen = -1;
2231     mExtension.mLen = -1;
2232     mQuery.mLen = -1;
2233     mRef.mLen = -1;
2234   }
2235   return NS_OK;
2236 }
2237 
2238 // When updating this also update SubstitutingURL::Mutator
2239 // Queries this list of interfaces. If none match, it queries mURI.
NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsStandardURL::Mutator,nsIURISetters,nsIURIMutator,nsIStandardURLMutator,nsIURLMutator,nsIFileURLMutator,nsISerializable)2240 NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsStandardURL::Mutator, nsIURISetters,
2241                                 nsIURIMutator, nsIStandardURLMutator,
2242                                 nsIURLMutator, nsIFileURLMutator,
2243                                 nsISerializable)
2244 
2245 NS_IMETHODIMP
2246 nsStandardURL::Mutate(nsIURIMutator** aMutator) {
2247   RefPtr<nsStandardURL::Mutator> mutator = new nsStandardURL::Mutator();
2248   nsresult rv = mutator->InitFromURI(this);
2249   if (NS_FAILED(rv)) {
2250     return rv;
2251   }
2252   mutator.forget(aMutator);
2253   return NS_OK;
2254 }
2255 
2256 NS_IMETHODIMP
Equals(nsIURI * unknownOther,bool * result)2257 nsStandardURL::Equals(nsIURI* unknownOther, bool* result) {
2258   return EqualsInternal(unknownOther, eHonorRef, result);
2259 }
2260 
2261 NS_IMETHODIMP
EqualsExceptRef(nsIURI * unknownOther,bool * result)2262 nsStandardURL::EqualsExceptRef(nsIURI* unknownOther, bool* result) {
2263   return EqualsInternal(unknownOther, eIgnoreRef, result);
2264 }
2265 
EqualsInternal(nsIURI * unknownOther,nsStandardURL::RefHandlingEnum refHandlingMode,bool * result)2266 nsresult nsStandardURL::EqualsInternal(
2267     nsIURI* unknownOther, nsStandardURL::RefHandlingEnum refHandlingMode,
2268     bool* result) {
2269   NS_ENSURE_ARG_POINTER(unknownOther);
2270   MOZ_ASSERT(result, "null pointer");
2271 
2272   RefPtr<nsStandardURL> other;
2273   nsresult rv =
2274       unknownOther->QueryInterface(kThisImplCID, getter_AddRefs(other));
2275   if (NS_FAILED(rv)) {
2276     *result = false;
2277     return NS_OK;
2278   }
2279 
2280   // First, check whether one URIs is an nsIFileURL while the other
2281   // is not.  If that's the case, they're different.
2282   if (mSupportsFileURL != other->mSupportsFileURL) {
2283     *result = false;
2284     return NS_OK;
2285   }
2286 
2287   // Next check parts of a URI that, if different, automatically make the
2288   // URIs different
2289   if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
2290       // Check for host manually, since conversion to file will
2291       // ignore the host!
2292       !SegmentIs(mHost, other->mSpec.get(), other->mHost) ||
2293       !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) ||
2294       !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) ||
2295       !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) ||
2296       Port() != other->Port()) {
2297     // No need to compare files or other URI parts -- these are different
2298     // beasties
2299     *result = false;
2300     return NS_OK;
2301   }
2302 
2303   if (refHandlingMode == eHonorRef &&
2304       !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
2305     *result = false;
2306     return NS_OK;
2307   }
2308 
2309   // Then check for exact identity of URIs.  If we have it, they're equal
2310   if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) &&
2311       SegmentIs(mBasename, other->mSpec.get(), other->mBasename) &&
2312       SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) {
2313     *result = true;
2314     return NS_OK;
2315   }
2316 
2317   // At this point, the URIs are not identical, but they only differ in the
2318   // directory/filename/extension.  If these are file URLs, then get the
2319   // corresponding file objects and compare those, since two filenames that
2320   // differ, eg, only in case could still be equal.
2321   if (mSupportsFileURL) {
2322     // Assume not equal for failure cases... but failures in GetFile are
2323     // really failures, more or less, so propagate them to caller.
2324     *result = false;
2325 
2326     rv = EnsureFile();
2327     nsresult rv2 = other->EnsureFile();
2328     // special case for resource:// urls that don't resolve to files
2329     if (rv == NS_ERROR_NO_INTERFACE && rv == rv2) {
2330       return NS_OK;
2331     }
2332 
2333     if (NS_FAILED(rv)) {
2334       LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
2335            this, mSpec.get()));
2336       return rv;
2337     }
2338     NS_ASSERTION(mFile, "EnsureFile() lied!");
2339     rv = rv2;
2340     if (NS_FAILED(rv)) {
2341       LOG(
2342           ("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure "
2343            "file",
2344            other.get(), other->mSpec.get()));
2345       return rv;
2346     }
2347     NS_ASSERTION(other->mFile, "EnsureFile() lied!");
2348     return mFile->Equals(other->mFile, result);
2349   }
2350 
2351   // The URLs are not identical, and they do not correspond to the
2352   // same file, so they are different.
2353   *result = false;
2354 
2355   return NS_OK;
2356 }
2357 
2358 NS_IMETHODIMP
SchemeIs(const char * scheme,bool * result)2359 nsStandardURL::SchemeIs(const char* scheme, bool* result) {
2360   MOZ_ASSERT(result, "null pointer");
2361   if (!scheme) {
2362     *result = false;
2363     return NS_OK;
2364   }
2365 
2366   *result = SegmentIs(mScheme, scheme);
2367   return NS_OK;
2368 }
2369 
StartClone()2370 /* virtual */ nsStandardURL* nsStandardURL::StartClone() {
2371   nsStandardURL* clone = new nsStandardURL();
2372   return clone;
2373 }
2374 
Clone(nsIURI ** aURI)2375 nsresult nsStandardURL::Clone(nsIURI** aURI) {
2376   return CloneInternal(eHonorRef, ""_ns, aURI);
2377 }
2378 
CloneInternal(nsStandardURL::RefHandlingEnum aRefHandlingMode,const nsACString & aNewRef,nsIURI ** aClone)2379 nsresult nsStandardURL::CloneInternal(
2380     nsStandardURL::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef,
2381     nsIURI** aClone)
2382 
2383 {
2384   RefPtr<nsStandardURL> clone = StartClone();
2385   if (!clone) {
2386     return NS_ERROR_OUT_OF_MEMORY;
2387   }
2388 
2389   // Copy local members into clone.
2390   // Also copies the cached members mFile, mDisplayHost
2391   clone->CopyMembers(this, aRefHandlingMode, aNewRef, true);
2392 
2393   clone.forget(aClone);
2394   return NS_OK;
2395 }
2396 
CopyMembers(nsStandardURL * source,nsStandardURL::RefHandlingEnum refHandlingMode,const nsACString & newRef,bool copyCached)2397 nsresult nsStandardURL::CopyMembers(
2398     nsStandardURL* source, nsStandardURL::RefHandlingEnum refHandlingMode,
2399     const nsACString& newRef, bool copyCached) {
2400   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
2401 
2402   mSpec = source->mSpec;
2403   mDefaultPort = source->mDefaultPort;
2404   mPort = source->mPort;
2405   mScheme = source->mScheme;
2406   mAuthority = source->mAuthority;
2407   mUsername = source->mUsername;
2408   mPassword = source->mPassword;
2409   mHost = source->mHost;
2410   mPath = source->mPath;
2411   mFilepath = source->mFilepath;
2412   mDirectory = source->mDirectory;
2413   mBasename = source->mBasename;
2414   mExtension = source->mExtension;
2415   mQuery = source->mQuery;
2416   mRef = source->mRef;
2417   mURLType = source->mURLType;
2418   mParser = source->mParser;
2419   mSupportsFileURL = source->mSupportsFileURL;
2420   mCheckedIfHostA = source->mCheckedIfHostA;
2421   mDisplayHost = source->mDisplayHost;
2422 
2423   if (copyCached) {
2424     mFile = source->mFile;
2425   } else {
2426     InvalidateCache(true);
2427   }
2428 
2429   if (refHandlingMode == eIgnoreRef) {
2430     SetRef(""_ns);
2431   } else if (refHandlingMode == eReplaceRef) {
2432     SetRef(newRef);
2433   }
2434 
2435   return NS_OK;
2436 }
2437 
2438 NS_IMETHODIMP
Resolve(const nsACString & in,nsACString & out)2439 nsStandardURL::Resolve(const nsACString& in, nsACString& out) {
2440   const nsPromiseFlatCString& flat = PromiseFlatCString(in);
2441   // filter out unexpected chars "\r\n\t" if necessary
2442   nsAutoCString buf;
2443   net_FilterURIString(flat, buf);
2444 
2445   const char* relpath = buf.get();
2446   int32_t relpathLen = buf.Length();
2447 
2448   char* result = nullptr;
2449 
2450   LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n", this,
2451        mSpec.get(), relpath));
2452 
2453   NS_ASSERTION(mParser, "no parser: unitialized");
2454 
2455   // NOTE: there is no need for this function to produce normalized
2456   // output.  normalization will occur when the result is used to
2457   // initialize a nsStandardURL object.
2458 
2459   if (mScheme.mLen < 0) {
2460     NS_WARNING("unable to Resolve URL: this URL not initialized");
2461     return NS_ERROR_NOT_INITIALIZED;
2462   }
2463 
2464   nsresult rv;
2465   URLSegment scheme;
2466   char* resultPath = nullptr;
2467   bool relative = false;
2468   uint32_t offset = 0;
2469   netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
2470 
2471   nsAutoCString baseProtocol(Scheme());
2472   nsAutoCString protocol;
2473   rv = net_ExtractURLScheme(buf, protocol);
2474 
2475   // Normally, if we parse a scheme, then it's an absolute URI. But because
2476   // we still support a deprecated form of relative URIs such as: http:file or
2477   // http:/path/file we can't do that for all protocols.
2478   // So we just make sure that if there a protocol, it's the same as the
2479   // current one, otherwise we treat it as an absolute URI.
2480   if (NS_SUCCEEDED(rv) && protocol != baseProtocol) {
2481     out = buf;
2482     return NS_OK;
2483   }
2484 
2485   // relative urls should never contain a host, so we always want to use
2486   // the noauth url parser.
2487   // use it to extract a possible scheme
2488   rv = mParser->ParseURL(relpath, relpathLen, &scheme.mPos, &scheme.mLen,
2489                          nullptr, nullptr, nullptr, nullptr);
2490 
2491   // if the parser fails (for example because there is no valid scheme)
2492   // reset the scheme and assume a relative url
2493   if (NS_FAILED(rv)) {
2494     scheme.Reset();
2495   }
2496 
2497   protocol.Assign(Segment(scheme));
2498 
2499   // We need to do backslash replacement for the following cases:
2500   // 1. The input is an absolute path with a http/https/ftp scheme
2501   // 2. The input is a relative path, and the base URL has a http/https/ftp
2502   // scheme
2503   if ((protocol.IsEmpty() && IsSpecialProtocol(baseProtocol)) ||
2504       IsSpecialProtocol(protocol)) {
2505     auto* start = buf.BeginWriting();
2506     auto* end = buf.EndWriting();
2507     while (start != end) {
2508       if (*start == '?' || *start == '#') {
2509         break;
2510       }
2511       if (*start == '\\') {
2512         *start = '/';
2513       }
2514       start++;
2515     }
2516   }
2517 
2518   if (scheme.mLen >= 0) {
2519     // add some flags to coalesceFlag if it is an ftp-url
2520     // need this later on when coalescing the resulting URL
2521     if (SegmentIs(relpath, scheme, "ftp", true)) {
2522       coalesceFlag =
2523           (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
2524                              NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
2525     }
2526     // this URL appears to be absolute
2527     // but try to find out more
2528     if (SegmentIs(mScheme, relpath, scheme, true)) {
2529       // mScheme and Scheme are the same
2530       // but this can still be relative
2531       if (strncmp(relpath + scheme.mPos + scheme.mLen, "://", 3) == 0) {
2532         // now this is really absolute
2533         // because a :// follows the scheme
2534         result = NS_xstrdup(relpath);
2535       } else {
2536         // This is a deprecated form of relative urls like
2537         // http:file or http:/path/file
2538         // we will support it for now ...
2539         relative = true;
2540         offset = scheme.mLen + 1;
2541       }
2542     } else {
2543       // the schemes are not the same, we are also done
2544       // because we have to assume this is absolute
2545       result = NS_xstrdup(relpath);
2546     }
2547   } else {
2548     // add some flags to coalesceFlag if it is an ftp-url
2549     // need this later on when coalescing the resulting URL
2550     if (SegmentIs(mScheme, "ftp")) {
2551       coalesceFlag =
2552           (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
2553                              NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
2554     }
2555     if (relpath[0] == '/' && relpath[1] == '/') {
2556       // this URL //host/path is almost absolute
2557       result = AppendToSubstring(mScheme.mPos, mScheme.mLen + 1, relpath);
2558     } else {
2559       // then it must be relative
2560       relative = true;
2561     }
2562   }
2563   if (relative) {
2564     uint32_t len = 0;
2565     const char* realrelpath = relpath + offset;
2566     switch (*realrelpath) {
2567       case '/':
2568         // overwrite everything after the authority
2569         len = mAuthority.mPos + mAuthority.mLen;
2570         break;
2571       case '?':
2572         // overwrite the existing ?query and #ref
2573         if (mQuery.mLen >= 0) {
2574           len = mQuery.mPos - 1;
2575         } else if (mRef.mLen >= 0) {
2576           len = mRef.mPos - 1;
2577         } else {
2578           len = mPath.mPos + mPath.mLen;
2579         }
2580         break;
2581       case '#':
2582       case '\0':
2583         // overwrite the existing #ref
2584         if (mRef.mLen < 0) {
2585           len = mPath.mPos + mPath.mLen;
2586         } else {
2587           len = mRef.mPos - 1;
2588         }
2589         break;
2590       default:
2591         if (coalesceFlag & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
2592           if (Filename().Equals("%2F"_ns, nsCaseInsensitiveCStringComparator)) {
2593             // if ftp URL ends with %2F then simply
2594             // append relative part because %2F also
2595             // marks the root directory with ftp-urls
2596             len = mFilepath.mPos + mFilepath.mLen;
2597           } else {
2598             // overwrite everything after the directory
2599             len = mDirectory.mPos + mDirectory.mLen;
2600           }
2601         } else {
2602           // overwrite everything after the directory
2603           len = mDirectory.mPos + mDirectory.mLen;
2604         }
2605     }
2606     result = AppendToSubstring(0, len, realrelpath);
2607     // locate result path
2608     resultPath = result + mPath.mPos;
2609   }
2610   if (!result) {
2611     return NS_ERROR_OUT_OF_MEMORY;
2612   }
2613 
2614   if (resultPath) {
2615     net_CoalesceDirs(coalesceFlag, resultPath);
2616   } else {
2617     // locate result path
2618     resultPath = PL_strstr(result, "://");
2619     if (resultPath) {
2620       resultPath = PL_strchr(resultPath + 3, '/');
2621       if (resultPath) {
2622         net_CoalesceDirs(coalesceFlag, resultPath);
2623       }
2624     }
2625   }
2626   out.Adopt(result);
2627   return NS_OK;
2628 }
2629 
2630 // result may contain unescaped UTF-8 characters
2631 NS_IMETHODIMP
GetCommonBaseSpec(nsIURI * uri2,nsACString & aResult)2632 nsStandardURL::GetCommonBaseSpec(nsIURI* uri2, nsACString& aResult) {
2633   NS_ENSURE_ARG_POINTER(uri2);
2634 
2635   // if uri's are equal, then return uri as is
2636   bool isEquals = false;
2637   if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
2638     return GetSpec(aResult);
2639   }
2640 
2641   aResult.Truncate();
2642 
2643   // check pre-path; if they don't match, then return empty string
2644   RefPtr<nsStandardURL> stdurl2;
2645   nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2));
2646   isEquals = NS_SUCCEEDED(rv) &&
2647              SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) &&
2648              SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) &&
2649              SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) &&
2650              SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) &&
2651              (Port() == stdurl2->Port());
2652   if (!isEquals) {
2653     return NS_OK;
2654   }
2655 
2656   // scan for first mismatched character
2657   const char *thisIndex, *thatIndex, *startCharPos;
2658   startCharPos = mSpec.get() + mDirectory.mPos;
2659   thisIndex = startCharPos;
2660   thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
2661   while ((*thisIndex == *thatIndex) && *thisIndex) {
2662     thisIndex++;
2663     thatIndex++;
2664   }
2665 
2666   // backup to just after previous slash so we grab an appropriate path
2667   // segment such as a directory (not partial segments)
2668   // todo:  also check for file matches which include '?' and '#'
2669   while ((thisIndex != startCharPos) && (*(thisIndex - 1) != '/')) {
2670     thisIndex--;
2671   }
2672 
2673   // grab spec from beginning to thisIndex
2674   aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get());
2675 
2676   return rv;
2677 }
2678 
2679 NS_IMETHODIMP
GetRelativeSpec(nsIURI * uri2,nsACString & aResult)2680 nsStandardURL::GetRelativeSpec(nsIURI* uri2, nsACString& aResult) {
2681   NS_ENSURE_ARG_POINTER(uri2);
2682 
2683   aResult.Truncate();
2684 
2685   // if uri's are equal, then return empty string
2686   bool isEquals = false;
2687   if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
2688     return NS_OK;
2689   }
2690 
2691   RefPtr<nsStandardURL> stdurl2;
2692   nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2));
2693   isEquals = NS_SUCCEEDED(rv) &&
2694              SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) &&
2695              SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) &&
2696              SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) &&
2697              SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) &&
2698              (Port() == stdurl2->Port());
2699   if (!isEquals) {
2700     return uri2->GetSpec(aResult);
2701   }
2702 
2703   // scan for first mismatched character
2704   const char *thisIndex, *thatIndex, *startCharPos;
2705   startCharPos = mSpec.get() + mDirectory.mPos;
2706   thisIndex = startCharPos;
2707   thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
2708 
2709 #ifdef XP_WIN
2710   bool isFileScheme = SegmentIs(mScheme, "file");
2711   if (isFileScheme) {
2712     // on windows, we need to match the first segment of the path
2713     // if these don't match then we need to return an absolute path
2714     // skip over any leading '/' in path
2715     while ((*thisIndex == *thatIndex) && (*thisIndex == '/')) {
2716       thisIndex++;
2717       thatIndex++;
2718     }
2719     // look for end of first segment
2720     while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/')) {
2721       thisIndex++;
2722       thatIndex++;
2723     }
2724 
2725     // if we didn't match through the first segment, return absolute path
2726     if ((*thisIndex != '/') || (*thatIndex != '/')) {
2727       return uri2->GetSpec(aResult);
2728     }
2729   }
2730 #endif
2731 
2732   while ((*thisIndex == *thatIndex) && *thisIndex) {
2733     thisIndex++;
2734     thatIndex++;
2735   }
2736 
2737   // backup to just after previous slash so we grab an appropriate path
2738   // segment such as a directory (not partial segments)
2739   // todo:  also check for file matches with '#' and '?'
2740   while ((*(thatIndex - 1) != '/') && (thatIndex != startCharPos)) {
2741     thatIndex--;
2742   }
2743 
2744   const char* limit = mSpec.get() + mFilepath.mPos + mFilepath.mLen;
2745 
2746   // need to account for slashes and add corresponding "../"
2747   for (; thisIndex <= limit && *thisIndex; ++thisIndex) {
2748     if (*thisIndex == '/') {
2749       aResult.AppendLiteral("../");
2750     }
2751   }
2752 
2753   // grab spec from thisIndex to end
2754   uint32_t startPos = stdurl2->mScheme.mPos + thatIndex - stdurl2->mSpec.get();
2755   aResult.Append(
2756       Substring(stdurl2->mSpec, startPos, stdurl2->mSpec.Length() - startPos));
2757 
2758   return rv;
2759 }
2760 
2761 //----------------------------------------------------------------------------
2762 // nsStandardURL::nsIURL
2763 //----------------------------------------------------------------------------
2764 
2765 // result may contain unescaped UTF-8 characters
2766 NS_IMETHODIMP
GetFilePath(nsACString & result)2767 nsStandardURL::GetFilePath(nsACString& result) {
2768   result = Filepath();
2769   return NS_OK;
2770 }
2771 
2772 // result may contain unescaped UTF-8 characters
2773 NS_IMETHODIMP
GetQuery(nsACString & result)2774 nsStandardURL::GetQuery(nsACString& result) {
2775   result = Query();
2776   return NS_OK;
2777 }
2778 
2779 // result may contain unescaped UTF-8 characters
2780 NS_IMETHODIMP
GetRef(nsACString & result)2781 nsStandardURL::GetRef(nsACString& result) {
2782   result = Ref();
2783   return NS_OK;
2784 }
2785 
2786 NS_IMETHODIMP
GetHasRef(bool * result)2787 nsStandardURL::GetHasRef(bool* result) {
2788   *result = (mRef.mLen >= 0);
2789   return NS_OK;
2790 }
2791 
2792 // result may contain unescaped UTF-8 characters
2793 NS_IMETHODIMP
GetDirectory(nsACString & result)2794 nsStandardURL::GetDirectory(nsACString& result) {
2795   result = Directory();
2796   return NS_OK;
2797 }
2798 
2799 // result may contain unescaped UTF-8 characters
2800 NS_IMETHODIMP
GetFileName(nsACString & result)2801 nsStandardURL::GetFileName(nsACString& result) {
2802   result = Filename();
2803   return NS_OK;
2804 }
2805 
2806 // result may contain unescaped UTF-8 characters
2807 NS_IMETHODIMP
GetFileBaseName(nsACString & result)2808 nsStandardURL::GetFileBaseName(nsACString& result) {
2809   result = Basename();
2810   return NS_OK;
2811 }
2812 
2813 // result may contain unescaped UTF-8 characters
2814 NS_IMETHODIMP
GetFileExtension(nsACString & result)2815 nsStandardURL::GetFileExtension(nsACString& result) {
2816   result = Extension();
2817   return NS_OK;
2818 }
2819 
SetFilePath(const nsACString & input)2820 nsresult nsStandardURL::SetFilePath(const nsACString& input) {
2821   const nsPromiseFlatCString& flat = PromiseFlatCString(input);
2822   const char* filepath = flat.get();
2823 
2824   LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));
2825   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
2826 
2827   // if there isn't a filepath, then there can't be anything
2828   // after the path either.  this url is likely uninitialized.
2829   if (mFilepath.mLen < 0) {
2830     return SetPathQueryRef(flat);
2831   }
2832 
2833   if (filepath && *filepath) {
2834     nsAutoCString spec;
2835     uint32_t dirPos, basePos, extPos;
2836     int32_t dirLen, baseLen, extLen;
2837     nsresult rv;
2838 
2839     rv = mParser->ParseFilePath(filepath, flat.Length(), &dirPos, &dirLen,
2840                                 &basePos, &baseLen, &extPos, &extLen);
2841     if (NS_FAILED(rv)) {
2842       return rv;
2843     }
2844 
2845     // build up new candidate spec
2846     spec.Assign(mSpec.get(), mPath.mPos);
2847 
2848     // ensure leading '/'
2849     if (filepath[dirPos] != '/') {
2850       spec.Append('/');
2851     }
2852 
2853     nsSegmentEncoder encoder;
2854 
2855     // append encoded filepath components
2856     if (dirLen > 0) {
2857       encoder.EncodeSegment(
2858           Substring(filepath + dirPos, filepath + dirPos + dirLen),
2859           esc_Directory | esc_AlwaysCopy, spec);
2860     }
2861     if (baseLen > 0) {
2862       encoder.EncodeSegment(
2863           Substring(filepath + basePos, filepath + basePos + baseLen),
2864           esc_FileBaseName | esc_AlwaysCopy, spec);
2865     }
2866     if (extLen >= 0) {
2867       spec.Append('.');
2868       if (extLen > 0) {
2869         encoder.EncodeSegment(
2870             Substring(filepath + extPos, filepath + extPos + extLen),
2871             esc_FileExtension | esc_AlwaysCopy, spec);
2872       }
2873     }
2874 
2875     // compute the ending position of the current filepath
2876     if (mFilepath.mLen >= 0) {
2877       uint32_t end = mFilepath.mPos + mFilepath.mLen;
2878       if (mSpec.Length() > end) {
2879         spec.Append(mSpec.get() + end, mSpec.Length() - end);
2880       }
2881     }
2882 
2883     return SetSpecInternal(spec);
2884   }
2885   if (mPath.mLen > 1) {
2886     mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
2887     // left shift query, and ref
2888     ShiftFromQuery(1 - mFilepath.mLen);
2889     // One character for '/', and if we have a query or ref we add their
2890     // length and one extra for each '?' or '#' characters
2891     mPath.mLen = 1 + (mQuery.mLen >= 0 ? (mQuery.mLen + 1) : 0) +
2892                  (mRef.mLen >= 0 ? (mRef.mLen + 1) : 0);
2893     // these contain only a '/'
2894     mDirectory.mLen = 1;
2895     mFilepath.mLen = 1;
2896     // these are no longer defined
2897     mBasename.mLen = -1;
2898     mExtension.mLen = -1;
2899   }
2900   return NS_OK;
2901 }
2902 
IsUTFEncoding(const Encoding * aEncoding)2903 inline bool IsUTFEncoding(const Encoding* aEncoding) {
2904   return aEncoding == UTF_8_ENCODING || aEncoding == UTF_16BE_ENCODING ||
2905          aEncoding == UTF_16LE_ENCODING;
2906 }
2907 
SetQuery(const nsACString & input)2908 nsresult nsStandardURL::SetQuery(const nsACString& input) {
2909   return SetQueryWithEncoding(input, nullptr);
2910 }
2911 
SetQueryWithEncoding(const nsACString & input,const Encoding * encoding)2912 nsresult nsStandardURL::SetQueryWithEncoding(const nsACString& input,
2913                                              const Encoding* encoding) {
2914   const nsPromiseFlatCString& flat = PromiseFlatCString(input);
2915   const char* query = flat.get();
2916 
2917   LOG(("nsStandardURL::SetQuery [query=%s]\n", query));
2918   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
2919 
2920   if (IsUTFEncoding(encoding)) {
2921     encoding = nullptr;
2922   }
2923 
2924   if (mPath.mLen < 0) {
2925     return SetPathQueryRef(flat);
2926   }
2927 
2928   if (mSpec.Length() + input.Length() - Query().Length() >
2929       StaticPrefs::network_standard_url_max_length()) {
2930     return NS_ERROR_MALFORMED_URI;
2931   }
2932 
2933   InvalidateCache();
2934 
2935   if (!query || !*query) {
2936     // remove existing query
2937     if (mQuery.mLen >= 0) {
2938       // remove query and leading '?'
2939       mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1);
2940       ShiftFromRef(-(mQuery.mLen + 1));
2941       mPath.mLen -= (mQuery.mLen + 1);
2942       mQuery.mPos = 0;
2943       mQuery.mLen = -1;
2944     }
2945     return NS_OK;
2946   }
2947 
2948   // filter out unexpected chars "\r\n\t" if necessary
2949   nsAutoCString filteredURI(flat);
2950   const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
2951   filteredURI.StripTaggedASCII(mask);
2952 
2953   query = filteredURI.get();
2954   int32_t queryLen = filteredURI.Length();
2955   if (query[0] == '?') {
2956     query++;
2957     queryLen--;
2958   }
2959 
2960   if (mQuery.mLen < 0) {
2961     if (mRef.mLen < 0) {
2962       mQuery.mPos = mSpec.Length();
2963     } else {
2964       mQuery.mPos = mRef.mPos - 1;
2965     }
2966     mSpec.Insert('?', mQuery.mPos);
2967     mQuery.mPos++;
2968     mQuery.mLen = 0;
2969     // the insertion pushes these out by 1
2970     mPath.mLen++;
2971     mRef.mPos++;
2972   }
2973 
2974   // encode query if necessary
2975   nsAutoCString buf;
2976   bool encoded;
2977   nsSegmentEncoder encoder(encoding);
2978   encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query, buf,
2979                              encoded);
2980   if (encoded) {
2981     query = buf.get();
2982     queryLen = buf.Length();
2983   }
2984 
2985   int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen);
2986 
2987   if (shift) {
2988     mQuery.mLen = queryLen;
2989     mPath.mLen += shift;
2990     ShiftFromRef(shift);
2991   }
2992   return NS_OK;
2993 }
2994 
SetRef(const nsACString & input)2995 nsresult nsStandardURL::SetRef(const nsACString& input) {
2996   const nsPromiseFlatCString& flat = PromiseFlatCString(input);
2997   const char* ref = flat.get();
2998 
2999   LOG(("nsStandardURL::SetRef [ref=%s]\n", ref));
3000   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
3001 
3002   if (mPath.mLen < 0) {
3003     return SetPathQueryRef(flat);
3004   }
3005 
3006   if (mSpec.Length() + input.Length() - Ref().Length() >
3007       StaticPrefs::network_standard_url_max_length()) {
3008     return NS_ERROR_MALFORMED_URI;
3009   }
3010 
3011   InvalidateCache();
3012 
3013   if (!ref || !*ref) {
3014     // remove existing ref
3015     if (mRef.mLen >= 0) {
3016       // remove ref and leading '#'
3017       mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1);
3018       mPath.mLen -= (mRef.mLen + 1);
3019       mRef.mPos = 0;
3020       mRef.mLen = -1;
3021     }
3022     return NS_OK;
3023   }
3024 
3025   // filter out unexpected chars "\r\n\t" if necessary
3026   nsAutoCString filteredURI(flat);
3027   const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
3028   filteredURI.StripTaggedASCII(mask);
3029 
3030   ref = filteredURI.get();
3031   int32_t refLen = filteredURI.Length();
3032   if (ref[0] == '#') {
3033     ref++;
3034     refLen--;
3035   }
3036 
3037   if (mRef.mLen < 0) {
3038     mSpec.Append('#');
3039     ++mPath.mLen;  // Include the # in the path.
3040     mRef.mPos = mSpec.Length();
3041     mRef.mLen = 0;
3042   }
3043 
3044   // If precent encoding is necessary, `ref` will point to `buf`'s content.
3045   // `buf` needs to outlive any use of the `ref` pointer.
3046   nsAutoCString buf;
3047   // encode ref if necessary
3048   bool encoded;
3049   nsSegmentEncoder encoder;
3050   encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref, buf, encoded);
3051   if (encoded) {
3052     ref = buf.get();
3053     refLen = buf.Length();
3054   }
3055 
3056   int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen);
3057   mPath.mLen += shift;
3058   mRef.mLen = refLen;
3059   return NS_OK;
3060 }
3061 
SetFileNameInternal(const nsACString & input)3062 nsresult nsStandardURL::SetFileNameInternal(const nsACString& input) {
3063   const nsPromiseFlatCString& flat = PromiseFlatCString(input);
3064   const char* filename = flat.get();
3065 
3066   LOG(("nsStandardURL::SetFileNameInternal [filename=%s]\n", filename));
3067   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
3068 
3069   if (mPath.mLen < 0) {
3070     return SetPathQueryRef(flat);
3071   }
3072 
3073   if (mSpec.Length() + input.Length() - Filename().Length() >
3074       StaticPrefs::network_standard_url_max_length()) {
3075     return NS_ERROR_MALFORMED_URI;
3076   }
3077 
3078   int32_t shift = 0;
3079 
3080   if (!(filename && *filename)) {
3081     // remove the filename
3082     if (mBasename.mLen > 0) {
3083       if (mExtension.mLen >= 0) {
3084         mBasename.mLen += (mExtension.mLen + 1);
3085       }
3086       mSpec.Cut(mBasename.mPos, mBasename.mLen);
3087       shift = -mBasename.mLen;
3088       mBasename.mLen = 0;
3089       mExtension.mLen = -1;
3090     }
3091   } else {
3092     nsresult rv;
3093     URLSegment basename, extension;
3094 
3095     // let the parser locate the basename and extension
3096     rv = mParser->ParseFileName(filename, flat.Length(), &basename.mPos,
3097                                 &basename.mLen, &extension.mPos,
3098                                 &extension.mLen);
3099     if (NS_FAILED(rv)) {
3100       return rv;
3101     }
3102 
3103     if (basename.mLen < 0) {
3104       // remove existing filename
3105       if (mBasename.mLen >= 0) {
3106         uint32_t len = mBasename.mLen;
3107         if (mExtension.mLen >= 0) {
3108           len += (mExtension.mLen + 1);
3109         }
3110         mSpec.Cut(mBasename.mPos, len);
3111         shift = -int32_t(len);
3112         mBasename.mLen = 0;
3113         mExtension.mLen = -1;
3114       }
3115     } else {
3116       nsAutoCString newFilename;
3117       bool ignoredOut;
3118       nsSegmentEncoder encoder;
3119       basename.mLen = encoder.EncodeSegmentCount(
3120           filename, basename, esc_FileBaseName | esc_AlwaysCopy, newFilename,
3121           ignoredOut);
3122       if (extension.mLen >= 0) {
3123         newFilename.Append('.');
3124         extension.mLen = encoder.EncodeSegmentCount(
3125             filename, extension, esc_FileExtension | esc_AlwaysCopy,
3126             newFilename, ignoredOut);
3127       }
3128 
3129       if (mBasename.mLen < 0) {
3130         // insert new filename
3131         mBasename.mPos = mDirectory.mPos + mDirectory.mLen;
3132         mSpec.Insert(newFilename, mBasename.mPos);
3133         shift = newFilename.Length();
3134       } else {
3135         // replace existing filename
3136         uint32_t oldLen = uint32_t(mBasename.mLen);
3137         if (mExtension.mLen >= 0) {
3138           oldLen += (mExtension.mLen + 1);
3139         }
3140         mSpec.Replace(mBasename.mPos, oldLen, newFilename);
3141         shift = newFilename.Length() - oldLen;
3142       }
3143 
3144       mBasename.mLen = basename.mLen;
3145       mExtension.mLen = extension.mLen;
3146       if (mExtension.mLen >= 0) {
3147         mExtension.mPos = mBasename.mPos + mBasename.mLen + 1;
3148       }
3149     }
3150   }
3151   if (shift) {
3152     ShiftFromQuery(shift);
3153     mFilepath.mLen += shift;
3154     mPath.mLen += shift;
3155   }
3156   return NS_OK;
3157 }
3158 
SetFileBaseNameInternal(const nsACString & input)3159 nsresult nsStandardURL::SetFileBaseNameInternal(const nsACString& input) {
3160   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
3161   nsAutoCString extension;
3162   nsresult rv = GetFileExtension(extension);
3163   NS_ENSURE_SUCCESS(rv, rv);
3164 
3165   nsAutoCString newFileName(input);
3166 
3167   if (!extension.IsEmpty()) {
3168     newFileName.Append('.');
3169     newFileName.Append(extension);
3170   }
3171 
3172   return SetFileNameInternal(newFileName);
3173 }
3174 
SetFileExtensionInternal(const nsACString & input)3175 nsresult nsStandardURL::SetFileExtensionInternal(const nsACString& input) {
3176   auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
3177   nsAutoCString newFileName;
3178   nsresult rv = GetFileBaseName(newFileName);
3179   NS_ENSURE_SUCCESS(rv, rv);
3180 
3181   if (!input.IsEmpty()) {
3182     newFileName.Append('.');
3183     newFileName.Append(input);
3184   }
3185 
3186   return SetFileNameInternal(newFileName);
3187 }
3188 
3189 //----------------------------------------------------------------------------
3190 // nsStandardURL::nsIFileURL
3191 //----------------------------------------------------------------------------
3192 
EnsureFile()3193 nsresult nsStandardURL::EnsureFile() {
3194   MOZ_ASSERT(mSupportsFileURL,
3195              "EnsureFile() called on a URL that doesn't support files!");
3196 
3197   if (mFile) {
3198     // Nothing to do
3199     return NS_OK;
3200   }
3201 
3202   // Parse the spec if we don't have a cached result
3203   if (mSpec.IsEmpty()) {
3204     NS_WARNING("url not initialized");
3205     return NS_ERROR_NOT_INITIALIZED;
3206   }
3207 
3208   if (!SegmentIs(mScheme, "file")) {
3209     NS_WARNING("not a file URL");
3210     return NS_ERROR_FAILURE;
3211   }
3212 
3213   return net_GetFileFromURLSpec(mSpec, getter_AddRefs(mFile));
3214 }
3215 
3216 NS_IMETHODIMP
GetFile(nsIFile ** result)3217 nsStandardURL::GetFile(nsIFile** result) {
3218   MOZ_ASSERT(mSupportsFileURL,
3219              "GetFile() called on a URL that doesn't support files!");
3220 
3221   nsresult rv = EnsureFile();
3222   if (NS_FAILED(rv)) {
3223     return rv;
3224   }
3225 
3226   if (LOG_ENABLED()) {
3227     LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n", this,
3228          mSpec.get(), mFile->HumanReadablePath().get()));
3229   }
3230 
3231   // clone the file, so the caller can modify it.
3232   // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the
3233   // nsIFile returned from this method; but it seems that some folks do
3234   // (see bug 161921). until we can be sure that all the consumers are
3235   // behaving themselves, we'll stay on the safe side and clone the file.
3236   // see bug 212724 about fixing the consumers.
3237   return mFile->Clone(result);
3238 }
3239 
SetFile(nsIFile * file)3240 nsresult nsStandardURL::SetFile(nsIFile* file) {
3241   NS_ENSURE_ARG_POINTER(file);
3242 
3243   nsresult rv;
3244   nsAutoCString url;
3245 
3246   rv = net_GetURLSpecFromFile(file, url);
3247   if (NS_FAILED(rv)) {
3248     return rv;
3249   }
3250 
3251   uint32_t oldURLType = mURLType;
3252   uint32_t oldDefaultPort = mDefaultPort;
3253   rv = Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, url, nullptr, nullptr);
3254 
3255   if (NS_FAILED(rv)) {
3256     // Restore the old url type and default port if the call to Init fails.
3257     mURLType = oldURLType;
3258     mDefaultPort = oldDefaultPort;
3259     return rv;
3260   }
3261 
3262   // must clone |file| since its value is not guaranteed to remain constant
3263   InvalidateCache();
3264   if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
3265     NS_WARNING("nsIFile::Clone failed");
3266     // failure to clone is not fatal (GetFile will generate mFile)
3267     mFile = nullptr;
3268   }
3269 
3270   return NS_OK;
3271 }
3272 
3273 //----------------------------------------------------------------------------
3274 // nsStandardURL::nsIStandardURL
3275 //----------------------------------------------------------------------------
3276 
Init(uint32_t urlType,int32_t defaultPort,const nsACString & spec,const char * charset,nsIURI * baseURI)3277 nsresult nsStandardURL::Init(uint32_t urlType, int32_t defaultPort,
3278                              const nsACString& spec, const char* charset,
3279                              nsIURI* baseURI) {
3280   if (spec.Length() > StaticPrefs::network_standard_url_max_length() ||
3281       defaultPort > std::numeric_limits<uint16_t>::max()) {
3282     return NS_ERROR_MALFORMED_URI;
3283   }
3284 
3285   InvalidateCache();
3286 
3287   switch (urlType) {
3288     case URLTYPE_STANDARD:
3289       mParser = net_GetStdURLParser();
3290       break;
3291     case URLTYPE_AUTHORITY:
3292       mParser = net_GetAuthURLParser();
3293       break;
3294     case URLTYPE_NO_AUTHORITY:
3295       mParser = net_GetNoAuthURLParser();
3296       break;
3297     default:
3298       MOZ_ASSERT_UNREACHABLE("bad urlType");
3299       return NS_ERROR_INVALID_ARG;
3300   }
3301   mDefaultPort = defaultPort;
3302   mURLType = urlType;
3303 
3304   const auto* encoding =
3305       charset ? Encoding::ForLabelNoReplacement(MakeStringSpan(charset))
3306               : nullptr;
3307   // URI can't be encoded in UTF-16BE or UTF-16LE. Truncate encoding
3308   // if it is one of utf encodings (since a null encoding implies
3309   // UTF-8, this is safe even if encoding is UTF-8).
3310   if (IsUTFEncoding(encoding)) {
3311     encoding = nullptr;
3312   }
3313 
3314   if (baseURI && net_IsAbsoluteURL(spec)) {
3315     baseURI = nullptr;
3316   }
3317 
3318   if (!baseURI) {
3319     return SetSpecWithEncoding(spec, encoding);
3320   }
3321 
3322   nsAutoCString buf;
3323   nsresult rv = baseURI->Resolve(spec, buf);
3324   if (NS_FAILED(rv)) {
3325     return rv;
3326   }
3327 
3328   return SetSpecWithEncoding(buf, encoding);
3329 }
3330 
SetDefaultPort(int32_t aNewDefaultPort)3331 nsresult nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort) {
3332   InvalidateCache();
3333 
3334   // should never be more than 16 bit
3335   if (aNewDefaultPort >= std::numeric_limits<uint16_t>::max()) {
3336     return NS_ERROR_MALFORMED_URI;
3337   }
3338 
3339   // If we're already using the new default-port as a custom port, then clear
3340   // it off of our mSpec & set mPort to -1, to indicate that we'll be using
3341   // the default from now on (which happens to match what we already had).
3342   if (mPort == aNewDefaultPort) {
3343     ReplacePortInSpec(-1);
3344     mPort = -1;
3345   }
3346   mDefaultPort = aNewDefaultPort;
3347 
3348   return NS_OK;
3349 }
3350 
3351 //----------------------------------------------------------------------------
3352 // nsStandardURL::nsISerializable
3353 //----------------------------------------------------------------------------
3354 
3355 NS_IMETHODIMP
Read(nsIObjectInputStream * stream)3356 nsStandardURL::Read(nsIObjectInputStream* stream) {
3357   MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
3358   return NS_ERROR_NOT_IMPLEMENTED;
3359 }
3360 
ReadPrivate(nsIObjectInputStream * stream)3361 nsresult nsStandardURL::ReadPrivate(nsIObjectInputStream* stream) {
3362   MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
3363 
3364   // If we exit early, make sure to clear the URL so we don't fail the sanity
3365   // check in the destructor
3366   auto clearOnExit = MakeScopeExit([&] { Clear(); });
3367 
3368   nsresult rv;
3369 
3370   uint32_t urlType;
3371   rv = stream->Read32(&urlType);
3372   if (NS_FAILED(rv)) {
3373     return rv;
3374   }
3375   mURLType = urlType;
3376   switch (mURLType) {
3377     case URLTYPE_STANDARD:
3378       mParser = net_GetStdURLParser();
3379       break;
3380     case URLTYPE_AUTHORITY:
3381       mParser = net_GetAuthURLParser();
3382       break;
3383     case URLTYPE_NO_AUTHORITY:
3384       mParser = net_GetNoAuthURLParser();
3385       break;
3386     default:
3387       MOZ_ASSERT_UNREACHABLE("bad urlType");
3388       return NS_ERROR_FAILURE;
3389   }
3390 
3391   rv = stream->Read32((uint32_t*)&mPort);
3392   if (NS_FAILED(rv)) {
3393     return rv;
3394   }
3395 
3396   rv = stream->Read32((uint32_t*)&mDefaultPort);
3397   if (NS_FAILED(rv)) {
3398     return rv;
3399   }
3400 
3401   rv = NS_ReadOptionalCString(stream, mSpec);
3402   if (NS_FAILED(rv)) {
3403     return rv;
3404   }
3405 
3406   rv = ReadSegment(stream, mScheme);
3407   if (NS_FAILED(rv)) {
3408     return rv;
3409   }
3410 
3411   rv = ReadSegment(stream, mAuthority);
3412   if (NS_FAILED(rv)) {
3413     return rv;
3414   }
3415 
3416   rv = ReadSegment(stream, mUsername);
3417   if (NS_FAILED(rv)) {
3418     return rv;
3419   }
3420 
3421   rv = ReadSegment(stream, mPassword);
3422   if (NS_FAILED(rv)) {
3423     return rv;
3424   }
3425 
3426   rv = ReadSegment(stream, mHost);
3427   if (NS_FAILED(rv)) {
3428     return rv;
3429   }
3430 
3431   rv = ReadSegment(stream, mPath);
3432   if (NS_FAILED(rv)) {
3433     return rv;
3434   }
3435 
3436   rv = ReadSegment(stream, mFilepath);
3437   if (NS_FAILED(rv)) {
3438     return rv;
3439   }
3440 
3441   rv = ReadSegment(stream, mDirectory);
3442   if (NS_FAILED(rv)) {
3443     return rv;
3444   }
3445 
3446   rv = ReadSegment(stream, mBasename);
3447   if (NS_FAILED(rv)) {
3448     return rv;
3449   }
3450 
3451   rv = ReadSegment(stream, mExtension);
3452   if (NS_FAILED(rv)) {
3453     return rv;
3454   }
3455 
3456   // handle forward compatibility from older serializations that included mParam
3457   URLSegment old_param;
3458   rv = ReadSegment(stream, old_param);
3459   if (NS_FAILED(rv)) {
3460     return rv;
3461   }
3462 
3463   rv = ReadSegment(stream, mQuery);
3464   if (NS_FAILED(rv)) {
3465     return rv;
3466   }
3467 
3468   rv = ReadSegment(stream, mRef);
3469   if (NS_FAILED(rv)) {
3470     return rv;
3471   }
3472 
3473   nsAutoCString oldOriginCharset;
3474   rv = NS_ReadOptionalCString(stream, oldOriginCharset);
3475   if (NS_FAILED(rv)) {
3476     return rv;
3477   }
3478 
3479   bool isMutable;
3480   rv = stream->ReadBoolean(&isMutable);
3481   if (NS_FAILED(rv)) {
3482     return rv;
3483   }
3484   Unused << isMutable;
3485 
3486   bool supportsFileURL;
3487   rv = stream->ReadBoolean(&supportsFileURL);
3488   if (NS_FAILED(rv)) {
3489     return rv;
3490   }
3491   mSupportsFileURL = supportsFileURL;
3492 
3493   // wait until object is set up, then modify path to include the param
3494   if (old_param.mLen >= 0) {  // note that mLen=0 is ";"
3495     // If this wasn't empty, it marks characters between the end of the
3496     // file and start of the query - mPath should include the param,
3497     // query and ref already.  Bump the mFilePath and
3498     // directory/basename/extension components to include this.
3499     mFilepath.Merge(mSpec, ';', old_param);
3500     mDirectory.Merge(mSpec, ';', old_param);
3501     mBasename.Merge(mSpec, ';', old_param);
3502     mExtension.Merge(mSpec, ';', old_param);
3503   }
3504 
3505   rv = CheckIfHostIsAscii();
3506   if (NS_FAILED(rv)) {
3507     return rv;
3508   }
3509 
3510   if (!IsValid()) {
3511     return NS_ERROR_MALFORMED_URI;
3512   }
3513 
3514   clearOnExit.release();
3515 
3516   return NS_OK;
3517 }
3518 
3519 NS_IMETHODIMP
Write(nsIObjectOutputStream * stream)3520 nsStandardURL::Write(nsIObjectOutputStream* stream) {
3521   MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
3522              "The spec should never be this long, we missed a check.");
3523   nsresult rv;
3524 
3525   rv = stream->Write32(mURLType);
3526   if (NS_FAILED(rv)) {
3527     return rv;
3528   }
3529 
3530   rv = stream->Write32(uint32_t(mPort));
3531   if (NS_FAILED(rv)) {
3532     return rv;
3533   }
3534 
3535   rv = stream->Write32(uint32_t(mDefaultPort));
3536   if (NS_FAILED(rv)) {
3537     return rv;
3538   }
3539 
3540   rv = NS_WriteOptionalStringZ(stream, mSpec.get());
3541   if (NS_FAILED(rv)) {
3542     return rv;
3543   }
3544 
3545   rv = WriteSegment(stream, mScheme);
3546   if (NS_FAILED(rv)) {
3547     return rv;
3548   }
3549 
3550   rv = WriteSegment(stream, mAuthority);
3551   if (NS_FAILED(rv)) {
3552     return rv;
3553   }
3554 
3555   rv = WriteSegment(stream, mUsername);
3556   if (NS_FAILED(rv)) {
3557     return rv;
3558   }
3559 
3560   rv = WriteSegment(stream, mPassword);
3561   if (NS_FAILED(rv)) {
3562     return rv;
3563   }
3564 
3565   rv = WriteSegment(stream, mHost);
3566   if (NS_FAILED(rv)) {
3567     return rv;
3568   }
3569 
3570   rv = WriteSegment(stream, mPath);
3571   if (NS_FAILED(rv)) {
3572     return rv;
3573   }
3574 
3575   rv = WriteSegment(stream, mFilepath);
3576   if (NS_FAILED(rv)) {
3577     return rv;
3578   }
3579 
3580   rv = WriteSegment(stream, mDirectory);
3581   if (NS_FAILED(rv)) {
3582     return rv;
3583   }
3584 
3585   rv = WriteSegment(stream, mBasename);
3586   if (NS_FAILED(rv)) {
3587     return rv;
3588   }
3589 
3590   rv = WriteSegment(stream, mExtension);
3591   if (NS_FAILED(rv)) {
3592     return rv;
3593   }
3594 
3595   // for backwards compatibility since we removed mParam.  Note that this will
3596   // mean that an older browser will read "" for mParam, and the param(s) will
3597   // be part of mPath (as they after the removal of special handling).  It only
3598   // matters if you downgrade a browser to before the patch.
3599   URLSegment empty;
3600   rv = WriteSegment(stream, empty);
3601   if (NS_FAILED(rv)) {
3602     return rv;
3603   }
3604 
3605   rv = WriteSegment(stream, mQuery);
3606   if (NS_FAILED(rv)) {
3607     return rv;
3608   }
3609 
3610   rv = WriteSegment(stream, mRef);
3611   if (NS_FAILED(rv)) {
3612     return rv;
3613   }
3614 
3615   // former origin charset
3616   rv = NS_WriteOptionalStringZ(stream, "");
3617   if (NS_FAILED(rv)) {
3618     return rv;
3619   }
3620 
3621   // former mMutable
3622   rv = stream->WriteBoolean(false);
3623   if (NS_FAILED(rv)) {
3624     return rv;
3625   }
3626 
3627   rv = stream->WriteBoolean(mSupportsFileURL);
3628   if (NS_FAILED(rv)) {
3629     return rv;
3630   }
3631 
3632   // mDisplayHost is just a cache that can be recovered as needed.
3633 
3634   return NS_OK;
3635 }
3636 
ToIPCSegment(const nsStandardURL::URLSegment & aSegment)3637 inline ipc::StandardURLSegment ToIPCSegment(
3638     const nsStandardURL::URLSegment& aSegment) {
3639   return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen);
3640 }
3641 
FromIPCSegment(const nsACString & aSpec,const ipc::StandardURLSegment & aSegment,nsStandardURL::URLSegment & aTarget)3642 [[nodiscard]] inline bool FromIPCSegment(
3643     const nsACString& aSpec, const ipc::StandardURLSegment& aSegment,
3644     nsStandardURL::URLSegment& aTarget) {
3645   // This seems to be just an empty segment.
3646   if (aSegment.length() == -1) {
3647     aTarget = nsStandardURL::URLSegment();
3648     return true;
3649   }
3650 
3651   // A value of -1 means an empty segment, but < -1 is undefined.
3652   if (NS_WARN_IF(aSegment.length() < -1)) {
3653     return false;
3654   }
3655 
3656   CheckedInt<uint32_t> segmentLen = aSegment.position();
3657   segmentLen += aSegment.length();
3658   // Make sure the segment does not extend beyond the spec.
3659   if (NS_WARN_IF(!segmentLen.isValid() ||
3660                  segmentLen.value() > aSpec.Length())) {
3661     return false;
3662   }
3663 
3664   aTarget.mPos = aSegment.position();
3665   aTarget.mLen = aSegment.length();
3666 
3667   return true;
3668 }
3669 
Serialize(URIParams & aParams)3670 void nsStandardURL::Serialize(URIParams& aParams) {
3671   MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
3672              "The spec should never be this long, we missed a check.");
3673   StandardURLParams params;
3674 
3675   params.urlType() = mURLType;
3676   params.port() = mPort;
3677   params.defaultPort() = mDefaultPort;
3678   params.spec() = mSpec;
3679   params.scheme() = ToIPCSegment(mScheme);
3680   params.authority() = ToIPCSegment(mAuthority);
3681   params.username() = ToIPCSegment(mUsername);
3682   params.password() = ToIPCSegment(mPassword);
3683   params.host() = ToIPCSegment(mHost);
3684   params.path() = ToIPCSegment(mPath);
3685   params.filePath() = ToIPCSegment(mFilepath);
3686   params.directory() = ToIPCSegment(mDirectory);
3687   params.baseName() = ToIPCSegment(mBasename);
3688   params.extension() = ToIPCSegment(mExtension);
3689   params.query() = ToIPCSegment(mQuery);
3690   params.ref() = ToIPCSegment(mRef);
3691   params.supportsFileURL() = !!mSupportsFileURL;
3692   params.isSubstituting() = false;
3693   // mDisplayHost is just a cache that can be recovered as needed.
3694 
3695   aParams = params;
3696 }
3697 
Deserialize(const URIParams & aParams)3698 bool nsStandardURL::Deserialize(const URIParams& aParams) {
3699   MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
3700   MOZ_ASSERT(!mFile, "Shouldn't have cached file");
3701 
3702   if (aParams.type() != URIParams::TStandardURLParams) {
3703     NS_ERROR("Received unknown parameters from the other process!");
3704     return false;
3705   }
3706 
3707   // If we exit early, make sure to clear the URL so we don't fail the sanity
3708   // check in the destructor
3709   auto clearOnExit = MakeScopeExit([&] { Clear(); });
3710 
3711   const StandardURLParams& params = aParams.get_StandardURLParams();
3712 
3713   mURLType = params.urlType();
3714   switch (mURLType) {
3715     case URLTYPE_STANDARD:
3716       mParser = net_GetStdURLParser();
3717       break;
3718     case URLTYPE_AUTHORITY:
3719       mParser = net_GetAuthURLParser();
3720       break;
3721     case URLTYPE_NO_AUTHORITY:
3722       mParser = net_GetNoAuthURLParser();
3723       break;
3724     default:
3725       MOZ_ASSERT_UNREACHABLE("bad urlType");
3726       return false;
3727   }
3728 
3729   mPort = params.port();
3730   mDefaultPort = params.defaultPort();
3731   mSpec = params.spec();
3732   NS_ENSURE_TRUE(
3733       mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), false);
3734   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.scheme(), mScheme), false);
3735   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.authority(), mAuthority), false);
3736   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.username(), mUsername), false);
3737   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.password(), mPassword), false);
3738   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.host(), mHost), false);
3739   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.path(), mPath), false);
3740   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.filePath(), mFilepath), false);
3741   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.directory(), mDirectory), false);
3742   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.baseName(), mBasename), false);
3743   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.extension(), mExtension), false);
3744   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.query(), mQuery), false);
3745   NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.ref(), mRef), false);
3746 
3747   mSupportsFileURL = params.supportsFileURL();
3748 
3749   nsresult rv = CheckIfHostIsAscii();
3750   if (NS_FAILED(rv)) {
3751     return false;
3752   }
3753 
3754   // Some sanity checks
3755   NS_ENSURE_TRUE(mScheme.mPos == 0, false);
3756   NS_ENSURE_TRUE(mScheme.mLen > 0, false);
3757   // Make sure scheme is followed by :// (3 characters)
3758   NS_ENSURE_TRUE(mScheme.mLen < INT32_MAX - 3, false);  // avoid overflow
3759   NS_ENSURE_TRUE(mSpec.Length() >= (uint32_t)mScheme.mLen + 3, false);
3760   NS_ENSURE_TRUE(
3761       nsDependentCSubstring(mSpec, mScheme.mLen, 3).EqualsLiteral("://"),
3762       false);
3763   NS_ENSURE_TRUE(mPath.mLen != -1 && mSpec.CharAt(mPath.mPos) == '/', false);
3764   NS_ENSURE_TRUE(mPath.mPos == mFilepath.mPos, false);
3765   NS_ENSURE_TRUE(mQuery.mLen == -1 ||
3766                      (mQuery.mPos > 0 && mSpec.CharAt(mQuery.mPos - 1) == '?'),
3767                  false);
3768   NS_ENSURE_TRUE(
3769       mRef.mLen == -1 || (mRef.mPos > 0 && mSpec.CharAt(mRef.mPos - 1) == '#'),
3770       false);
3771 
3772   if (!IsValid()) {
3773     return false;
3774   }
3775 
3776   clearOnExit.release();
3777 
3778   return true;
3779 }
3780 
3781 //----------------------------------------------------------------------------
3782 // nsStandardURL::nsISizeOf
3783 //----------------------------------------------------------------------------
3784 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const3785 size_t nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
3786   return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
3787          mDisplayHost.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
3788 
3789   // Measurement of the following members may be added later if DMD finds it is
3790   // worthwhile:
3791   // - mParser
3792   // - mFile
3793 }
3794 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const3795 size_t nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
3796   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
3797 }
3798 
3799 }  // namespace net
3800 }  // namespace mozilla
3801 
3802 // For unit tests.  Including nsStandardURL.h seems to cause problems
Test_NormalizeIPv4(const nsACString & host,nsCString & result)3803 nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result) {
3804   return mozilla::net::nsStandardURL::NormalizeIPv4(host, result);
3805 }
3806