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