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