URIFixup.sys.mjs (41614B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 * vim: sw=2 ts=2 sts=2 expandtab 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 /** 8 * This component handles fixing up URIs, by correcting obvious typos and adding 9 * missing schemes. 10 * URI references: 11 * http://www.faqs.org/rfcs/rfc1738.html 12 * http://www.faqs.org/rfcs/rfc2396.html 13 */ 14 15 // TODO (Bug 1641220) getFixupURIInfo has a complex logic, that likely could be 16 // simplified, but the risk of regressing its behavior is high. 17 /* eslint complexity: ["error", 46] */ 18 19 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 20 21 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 22 23 const lazy = {}; 24 25 XPCOMUtils.defineLazyServiceGetter( 26 lazy, 27 "externalProtocolService", 28 "@mozilla.org/uriloader/external-protocol-service;1", 29 Ci.nsIExternalProtocolService 30 ); 31 32 XPCOMUtils.defineLazyServiceGetter( 33 lazy, 34 "defaultProtocolHandler", 35 "@mozilla.org/network/protocol;1?name=default", 36 Ci.nsIProtocolHandler 37 ); 38 39 XPCOMUtils.defineLazyServiceGetter( 40 lazy, 41 "fileProtocolHandler", 42 "@mozilla.org/network/protocol;1?name=file", 43 Ci.nsIFileProtocolHandler 44 ); 45 46 XPCOMUtils.defineLazyPreferenceGetter( 47 lazy, 48 "fixupSchemeTypos", 49 "browser.fixup.typo.scheme", 50 true 51 ); 52 XPCOMUtils.defineLazyPreferenceGetter( 53 lazy, 54 "dnsFirstForSingleWords", 55 "browser.fixup.dns_first_for_single_words", 56 false 57 ); 58 XPCOMUtils.defineLazyPreferenceGetter( 59 lazy, 60 "keywordEnabled", 61 "keyword.enabled", 62 true 63 ); 64 XPCOMUtils.defineLazyPreferenceGetter( 65 lazy, 66 "alternateProtocol", 67 "browser.fixup.alternate.protocol", 68 "https" 69 ); 70 71 XPCOMUtils.defineLazyPreferenceGetter( 72 lazy, 73 "dnsResolveFullyQualifiedNames", 74 "browser.urlbar.dnsResolveFullyQualifiedNames", 75 true 76 ); 77 78 const { 79 FIXUP_FLAG_NONE, 80 FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, 81 FIXUP_FLAGS_MAKE_ALTERNATE_URI, 82 FIXUP_FLAG_PRIVATE_CONTEXT, 83 FIXUP_FLAG_FIX_SCHEME_TYPOS, 84 } = Ci.nsIURIFixup; 85 86 const COMMON_PROTOCOLS = ["http", "https", "file"]; 87 88 const HTTPISH = new Set(["http", "https"]); 89 90 // Regex used to identify user:password tokens in url strings. 91 // This is not a strict valid characters check, because we try to fixup this 92 // part of the url too. 93 ChromeUtils.defineLazyGetter( 94 lazy, 95 "userPasswordRegex", 96 () => /^([a-z+.-]+:\/{0,3})*([^\/@]+@).+/i 97 ); 98 99 // Regex used to identify the string that starts with port expression. 100 ChromeUtils.defineLazyGetter(lazy, "portRegex", () => /^:\d{1,5}([?#/]|$)/); 101 102 // Regex used to identify numbers. 103 ChromeUtils.defineLazyGetter(lazy, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/); 104 105 // Regex used to identify tab separated content (having at least 2 tabs). 106 ChromeUtils.defineLazyGetter(lazy, "maxOneTabRegex", () => /^[^\t]*\t?[^\t]*$/); 107 108 // Regex used to test if a string with a protocol might instead be a url 109 // without a protocol but with a port: 110 // 111 // <hostname>:<port> or 112 // <hostname>:<port>/ 113 // 114 // Where <hostname> is a string of alphanumeric characters and dashes 115 // separated by dots. 116 // and <port> is a 5 or less digits. This actually breaks the rfc2396 117 // definition of a scheme which allows dots in schemes. 118 // 119 // Note: 120 // People expecting this to work with 121 // <user>:<password>@<host>:<port>/<url-path> will be disappointed! 122 // 123 // Note: Parser could be a lot tighter, tossing out silly hostnames 124 // such as those containing consecutive dots and so on. 125 ChromeUtils.defineLazyGetter( 126 lazy, 127 "possiblyHostPortRegex", 128 () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i 129 ); 130 131 // Regex used to strip newlines. 132 ChromeUtils.defineLazyGetter(lazy, "newLinesRegex", () => /[\r\n]/g); 133 134 // Regex used to match a possible protocol. 135 // This resembles the logic in Services.io.extractScheme, thus \t is admitted 136 // and stripped later. We don't use Services.io.extractScheme because of 137 // performance bottleneck caused by crossing XPConnect. 138 ChromeUtils.defineLazyGetter( 139 lazy, 140 "possibleProtocolRegex", 141 () => /^([a-z][a-z0-9.+\t-]*)(:|;)?(\/\/)?/i 142 ); 143 144 // Regex used to match IPs. Note that these are not made to validate IPs, but 145 // just to detect strings that look like an IP. They also skip protocol. 146 // For IPv4 this also accepts a shorthand format with just 2 dots. 147 ChromeUtils.defineLazyGetter( 148 lazy, 149 "IPv4LikeRegex", 150 () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i 151 ); 152 ChromeUtils.defineLazyGetter( 153 lazy, 154 "IPv6LikeRegex", 155 () => 156 /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i 157 ); 158 159 // Cache of known domains. 160 ChromeUtils.defineLazyGetter(lazy, "knownDomains", () => { 161 const branch = "browser.fixup.domainwhitelist."; 162 let domains = new Set( 163 Services.prefs 164 .getChildList(branch) 165 .filter(p => Services.prefs.getBoolPref(p, false)) 166 .map(p => p.substring(branch.length)) 167 ); 168 // Hold onto the observer to avoid it being GC-ed. 169 domains._observer = { 170 observe(subject, topic, data) { 171 let domain = data.substring(branch.length); 172 if (Services.prefs.getBoolPref(data, false)) { 173 domains.add(domain); 174 } else { 175 domains.delete(domain); 176 } 177 }, 178 QueryInterface: ChromeUtils.generateQI([ 179 "nsIObserver", 180 "nsISupportsWeakReference", 181 ]), 182 }; 183 Services.prefs.addObserver(branch, domains._observer, true); 184 return domains; 185 }); 186 187 // Cache of known suffixes. 188 // This works differently from the known domains, because when we examine a 189 // domain we can't tell how many dot-separated parts constitute the suffix. 190 // We create a Map keyed by the last dotted part, containing a Set of 191 // all the suffixes ending with that part: 192 // "two" => ["two"] 193 // "three" => ["some.three", "three"] 194 // When searching we can restrict the linear scan based on the last part. 195 // The ideal structure for this would be a Directed Acyclic Word Graph, but 196 // since we expect this list to be small it's not worth the complication. 197 ChromeUtils.defineLazyGetter(lazy, "knownSuffixes", () => { 198 const branch = "browser.fixup.domainsuffixwhitelist."; 199 let suffixes = new Map(); 200 let prefs = Services.prefs 201 .getChildList(branch) 202 .filter(p => Services.prefs.getBoolPref(p, false)); 203 for (let pref of prefs) { 204 let suffix = pref.substring(branch.length); 205 let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); 206 if (lastPart) { 207 let entries = suffixes.get(lastPart); 208 if (!entries) { 209 entries = new Set(); 210 suffixes.set(lastPart, entries); 211 } 212 entries.add(suffix); 213 } 214 } 215 // Hold onto the observer to avoid it being GC-ed. 216 suffixes._observer = { 217 observe(subject, topic, data) { 218 let suffix = data.substring(branch.length); 219 let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); 220 let entries = suffixes.get(lastPart); 221 if (Services.prefs.getBoolPref(data, false)) { 222 // Add the suffix. 223 if (!entries) { 224 entries = new Set(); 225 suffixes.set(lastPart, entries); 226 } 227 entries.add(suffix); 228 } else if (entries) { 229 // Remove the suffix. 230 entries.delete(suffix); 231 if (!entries.size) { 232 suffixes.delete(lastPart); 233 } 234 } 235 }, 236 QueryInterface: ChromeUtils.generateQI([ 237 "nsIObserver", 238 "nsISupportsWeakReference", 239 ]), 240 }; 241 Services.prefs.addObserver(branch, suffixes._observer, true); 242 return suffixes; 243 }); 244 245 export function URIFixup() {} 246 247 URIFixup.prototype = { 248 get FIXUP_FLAG_NONE() { 249 return FIXUP_FLAG_NONE; 250 }, 251 get FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP() { 252 return FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; 253 }, 254 get FIXUP_FLAGS_MAKE_ALTERNATE_URI() { 255 return FIXUP_FLAGS_MAKE_ALTERNATE_URI; 256 }, 257 get FIXUP_FLAG_PRIVATE_CONTEXT() { 258 return FIXUP_FLAG_PRIVATE_CONTEXT; 259 }, 260 get FIXUP_FLAG_FIX_SCHEME_TYPOS() { 261 return FIXUP_FLAG_FIX_SCHEME_TYPOS; 262 }, 263 264 getFixupURIInfo(uriString, fixupFlags = FIXUP_FLAG_NONE) { 265 let isPrivateContext = fixupFlags & FIXUP_FLAG_PRIVATE_CONTEXT; 266 let untrimmedURIString = uriString; 267 268 // Eliminate embedded newlines, which single-line text fields now allow, 269 // and cleanup the empty spaces and tabs that might be on each end. 270 uriString = uriString.trim().replace(lazy.newLinesRegex, ""); 271 272 if (!uriString) { 273 throw new Components.Exception( 274 "Should pass a non-null uri", 275 Cr.NS_ERROR_FAILURE 276 ); 277 } 278 279 let info = new URIFixupInfo(uriString); 280 281 const { scheme, fixedSchemeUriString, fixupChangedProtocol } = 282 extractScheme(uriString, fixupFlags); 283 uriString = fixedSchemeUriString; 284 info.fixupChangedProtocol = fixupChangedProtocol; 285 286 if (scheme == "view-source") { 287 let { preferredURI, postData } = fixupViewSource(uriString, fixupFlags); 288 info.preferredURI = info.fixedURI = preferredURI; 289 info.postData = postData; 290 return info; 291 } 292 293 if (scheme.length < 2) { 294 // Check if it is a file path. We skip most schemes because the only case 295 // where a file path may look like having a scheme is "X:" on Windows. 296 let fileURI = fileURIFixup(uriString); 297 if (fileURI) { 298 info.preferredURI = info.fixedURI = fileURI; 299 info.fixupChangedProtocol = true; 300 return info; 301 } 302 } 303 304 const isCommonProtocol = COMMON_PROTOCOLS.includes(scheme); 305 306 let canHandleProtocol = 307 scheme && 308 (isCommonProtocol || 309 Services.io.getProtocolHandler(scheme) != lazy.defaultProtocolHandler || 310 this._isKnownExternalProtocol(scheme)); 311 312 if ( 313 canHandleProtocol || 314 // If it's an unknown handler and the given URL looks like host:port or 315 // has a user:password we can't pass it to the external protocol handler. 316 // We'll instead try fixing it with http later. 317 (!lazy.possiblyHostPortRegex.test(uriString) && 318 !lazy.userPasswordRegex.test(uriString)) 319 ) { 320 // Just try to create a URL out of it. 321 try { 322 info.fixedURI = makeURIWithFixedLocalHosts(uriString, fixupFlags); 323 } catch (ex) { 324 if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { 325 throw ex; 326 } 327 } 328 } 329 330 // We're dealing with a theoretically valid URI but we have no idea how to 331 // load it. (e.g. "christmas:humbug") 332 // It's more likely the user wants to search, and so we chuck this over to 333 // their preferred search provider. 334 // TODO (Bug 1588118): Should check FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP 335 // instead of FIXUP_FLAG_FIX_SCHEME_TYPOS. 336 if ( 337 info.fixedURI && 338 lazy.keywordEnabled && 339 fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS && 340 scheme && 341 !canHandleProtocol 342 ) { 343 tryKeywordFixupForURIInfo(uriString, info, isPrivateContext); 344 } 345 346 if (info.fixedURI) { 347 if (!info.preferredURI) { 348 maybeSetAlternateFixedURI(info, fixupFlags); 349 info.preferredURI = info.fixedURI; 350 } 351 fixupConsecutiveDotsHost(info); 352 return info; 353 } 354 355 // Fix up protocol string before calling KeywordURIFixup, because 356 // it cares about the hostname of such URIs. 357 // Prune duff protocol schemes: 358 // ://totallybroken.url.com 359 // //shorthand.url.com 360 let inputHadDuffProtocol = 361 uriString.startsWith("://") || uriString.startsWith("//"); 362 if (inputHadDuffProtocol) { 363 uriString = uriString.replace(/^:?\/\//, ""); 364 } 365 366 let detectSpaceInCredentials = val => { 367 // Only search the first 512 chars for performance reasons. 368 let firstChars = val.slice(0, 512); 369 if (!firstChars.includes("@")) { 370 return false; 371 } 372 let credentials = firstChars.split("@")[0]; 373 return !credentials.includes("/") && /\s/.test(credentials); 374 }; 375 376 // Avoid fixing up content that looks like tab-separated values. 377 // Assume that 1 tab is accidental, but more than 1 implies this is 378 // supposed to be tab-separated content. 379 if ( 380 !isCommonProtocol && 381 lazy.maxOneTabRegex.test(uriString) && 382 !detectSpaceInCredentials(untrimmedURIString) 383 ) { 384 let uriWithProtocol = fixupURIProtocol(uriString, fixupFlags); 385 if (uriWithProtocol) { 386 info.fixedURI = uriWithProtocol; 387 info.fixupChangedProtocol = true; 388 info.schemelessInput = Ci.nsILoadInfo.SchemelessInputTypeSchemeless; 389 maybeSetAlternateFixedURI(info, fixupFlags); 390 info.preferredURI = info.fixedURI; 391 // Check if it's a forced visit. The user can enforce a visit by 392 // appending a slash, but the string must be in a valid uri format. 393 if (uriString.endsWith("/")) { 394 fixupConsecutiveDotsHost(info); 395 return info; 396 } 397 } 398 } 399 400 // Handle "www.<something>" as a URI. 401 const asciiHost = info.fixedURI?.asciiHost; 402 if ( 403 asciiHost?.length > 4 && 404 asciiHost?.startsWith("www.") && 405 asciiHost?.lastIndexOf(".") == 3 406 ) { 407 return info; 408 } 409 410 // Memoize the public suffix check, since it may be expensive and should 411 // only run once when necessary. 412 let suffixInfo; 413 function checkSuffix(i) { 414 if (!suffixInfo) { 415 suffixInfo = checkAndFixPublicSuffix(i); 416 } 417 return suffixInfo; 418 } 419 420 // See if it is a keyword and whether a keyword must be fixed up. 421 if ( 422 lazy.keywordEnabled && 423 fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP && 424 !inputHadDuffProtocol && 425 !checkSuffix(info).suffix && 426 keywordURIFixup(uriString, info, isPrivateContext) 427 ) { 428 fixupConsecutiveDotsHost(info); 429 return info; 430 } 431 432 if ( 433 info.fixedURI && 434 (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix) 435 ) { 436 fixupConsecutiveDotsHost(info); 437 return info; 438 } 439 440 // If we still haven't been able to construct a valid URI, try to force a 441 // keyword match. 442 if (lazy.keywordEnabled && fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) { 443 tryKeywordFixupForURIInfo(info.originalInput, info, isPrivateContext); 444 } 445 446 if (!info.preferredURI) { 447 // We couldn't salvage anything. 448 throw new Components.Exception( 449 "Couldn't build a valid uri", 450 Cr.NS_ERROR_MALFORMED_URI 451 ); 452 } 453 454 fixupConsecutiveDotsHost(info); 455 return info; 456 }, 457 458 webNavigationFlagsToFixupFlags(href, navigationFlags) { 459 try { 460 Services.io.newURI(href); 461 // Remove LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP for valid uris. 462 navigationFlags &= 463 ~Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; 464 } catch (ex) {} 465 466 let fixupFlags = FIXUP_FLAG_NONE; 467 if ( 468 navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP 469 ) { 470 fixupFlags |= FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; 471 } 472 if (navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) { 473 fixupFlags |= FIXUP_FLAG_FIX_SCHEME_TYPOS; 474 } 475 return fixupFlags; 476 }, 477 478 keywordToURI(keyword, isPrivateContext) { 479 if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) { 480 // There's no search service in the content process, thus all the calls 481 // from it that care about keywords conversion should go through the 482 // parent process. 483 throw new Components.Exception( 484 "Can't invoke URIFixup in the content process", 485 Cr.NS_ERROR_NOT_AVAILABLE 486 ); 487 } 488 let info = new URIFixupInfo(keyword); 489 490 // Strip leading "?" and leading/trailing spaces from aKeyword 491 if (keyword.startsWith("?")) { 492 keyword = keyword.substring(1); 493 } 494 keyword = keyword.trim(); 495 496 if (!Services.search.hasSuccessfullyInitialized) { 497 return info; 498 } 499 500 // Try falling back to the search service's default search engine 501 // We must use an appropriate search engine depending on the private 502 // context. 503 let engine = isPrivateContext 504 ? Services.search.defaultPrivateEngine 505 : Services.search.defaultEngine; 506 507 // We allow default search plugins to specify alternate parameters that are 508 // specific to keyword searches. 509 let responseType = null; 510 if (engine.supportsResponseType("application/x-moz-keywordsearch")) { 511 responseType = "application/x-moz-keywordsearch"; 512 } 513 let submission = engine.getSubmission(keyword, responseType); 514 if ( 515 !submission || 516 // For security reasons (avoid redirecting to file, data, or other unsafe 517 // protocols) we only allow fixup to http/https search engines. 518 !HTTPISH.has(submission.uri.scheme) 519 ) { 520 throw new Components.Exception( 521 "Invalid search submission uri", 522 Cr.NS_ERROR_NOT_AVAILABLE 523 ); 524 } 525 let submissionPostDataStream = submission.postData; 526 if (submissionPostDataStream) { 527 info.postData = submissionPostDataStream; 528 } 529 530 info.keywordProviderName = engine.name; 531 info.keywordAsSent = keyword; 532 info.preferredURI = submission.uri; 533 return info; 534 }, 535 536 forceHttpFixup(uriString) { 537 if (!uriString) { 538 throw new Components.Exception( 539 "Should pass a non-null uri", 540 Cr.NS_ERROR_FAILURE 541 ); 542 } 543 544 let info = new URIFixupInfo(uriString); 545 let { scheme, fixedSchemeUriString, fixupChangedProtocol } = extractScheme( 546 uriString, 547 FIXUP_FLAG_FIX_SCHEME_TYPOS 548 ); 549 550 if (!HTTPISH.has(scheme)) { 551 throw new Components.Exception( 552 "Scheme should be either http or https", 553 Cr.NS_ERROR_FAILURE 554 ); 555 } 556 557 info.fixupChangedProtocol = fixupChangedProtocol; 558 info.fixedURI = Services.io.newURI(fixedSchemeUriString); 559 560 let host = info.fixedURI.host; 561 if (!HTTPISH.has(host) && host != "localhost") { 562 let modifiedHostname = maybeAddPrefixAndSuffix(host); 563 updateHostAndScheme(info, modifiedHostname); 564 info.preferredURI = info.fixedURI; 565 } 566 567 return info; 568 }, 569 570 checkHost(uri, listener, originAttributes) { 571 let { displayHost, asciiHost } = uri; 572 if (!displayHost) { 573 throw new Components.Exception( 574 "URI must have displayHost", 575 Cr.NS_ERROR_FAILURE 576 ); 577 } 578 if (!asciiHost) { 579 throw new Components.Exception( 580 "URI must have asciiHost", 581 Cr.NS_ERROR_FAILURE 582 ); 583 } 584 585 let isIPv4Address = host => { 586 let parts = host.split("."); 587 if (parts.length != 4) { 588 return false; 589 } 590 return parts.every(part => { 591 let n = parseInt(part, 10); 592 return n >= 0 && n <= 255; 593 }); 594 }; 595 596 // Avoid showing fixup information if we're suggesting an IP. Note that 597 // decimal representations of IPs are normalized to a 'regular' 598 // dot-separated IP address by network code, but that only happens for 599 // numbers that don't overflow. Longer numbers do not get normalized, 600 // but still work to access IP addresses. So for instance, 601 // 1097347366913 (ff7f000001) gets resolved by using the final bytes, 602 // making it the same as 7f000001, which is 127.0.0.1 aka localhost. 603 // While 2130706433 would get normalized by network, 1097347366913 604 // does not, and we have to deal with both cases here: 605 if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost)) { 606 return; 607 } 608 609 // For dotless hostnames, we want to ensure this ends with a '.' but don't 610 // want the . showing up in the UI if we end up notifying the user, so we 611 // use a separate variable. 612 let lookupName = displayHost; 613 if (lazy.dnsResolveFullyQualifiedNames && !lookupName.includes(".")) { 614 lookupName += "."; 615 } 616 617 Services.obs.notifyObservers(null, "uri-fixup-check-dns"); 618 Services.dns.asyncResolve( 619 lookupName, 620 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 621 0, 622 null, 623 listener, 624 Services.tm.mainThread, 625 originAttributes 626 ); 627 }, 628 629 isDomainKnown, 630 631 _isKnownExternalProtocol(scheme) { 632 if (AppConstants.platform == "android") { 633 // On Android, externalProtocolHandlerExists ~always returns true (see 634 // nsOSHelperAppService::OSProtocolHandlerExists). For now, this 635 // preserves behavior from before bug 1966666. 636 return false; 637 } 638 return lazy.externalProtocolService.externalProtocolHandlerExists(scheme); 639 }, 640 641 classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"), 642 QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]), 643 }; 644 645 export function URIFixupInfo(originalInput = "") { 646 this._originalInput = originalInput; 647 } 648 649 URIFixupInfo.prototype = { 650 set consumer(consumer) { 651 this._consumer = consumer || null; 652 }, 653 get consumer() { 654 return this._consumer || null; 655 }, 656 657 set preferredURI(uri) { 658 this._preferredURI = uri; 659 }, 660 get preferredURI() { 661 return this._preferredURI || null; 662 }, 663 664 set fixedURI(uri) { 665 this._fixedURI = uri; 666 }, 667 get fixedURI() { 668 return this._fixedURI || null; 669 }, 670 671 set keywordProviderName(name) { 672 this._keywordProviderName = name; 673 }, 674 get keywordProviderName() { 675 return this._keywordProviderName || ""; 676 }, 677 678 set keywordAsSent(keyword) { 679 this._keywordAsSent = keyword; 680 }, 681 get keywordAsSent() { 682 return this._keywordAsSent || ""; 683 }, 684 685 set schemelessInput(changed) { 686 this._schemelessInput = changed; 687 }, 688 get schemelessInput() { 689 return this._schemelessInput ?? Ci.nsILoadInfo.SchemelessInputTypeUnset; 690 }, 691 692 set fixupChangedProtocol(changed) { 693 this._fixupChangedProtocol = changed; 694 }, 695 get fixupChangedProtocol() { 696 return !!this._fixupChangedProtocol; 697 }, 698 699 set fixupCreatedAlternateURI(changed) { 700 this._fixupCreatedAlternateURI = changed; 701 }, 702 get fixupCreatedAlternateURI() { 703 return !!this._fixupCreatedAlternateURI; 704 }, 705 706 set originalInput(input) { 707 this._originalInput = input; 708 }, 709 get originalInput() { 710 return this._originalInput || ""; 711 }, 712 713 set postData(postData) { 714 this._postData = postData; 715 }, 716 get postData() { 717 return this._postData || null; 718 }, 719 720 classID: Components.ID("{33d75835-722f-42c0-89cc-44f328e56a86}"), 721 QueryInterface: ChromeUtils.generateQI(["nsIURIFixupInfo"]), 722 }; 723 724 // Helpers 725 726 /** 727 * Implementation of isDomainKnown, so we don't have to go through the 728 * service. 729 * 730 * @param {string} asciiHost 731 * @returns {boolean} whether the domain is known 732 */ 733 function isDomainKnown(asciiHost) { 734 if (lazy.dnsFirstForSingleWords) { 735 return true; 736 } 737 // Check if this domain is known as an actual 738 // domain (which will prevent a keyword query) 739 // Note that any processing of the host here should stay in sync with 740 // code in the front-end(s) that set the pref. 741 let lastDotIndex = asciiHost.lastIndexOf("."); 742 if (lastDotIndex == asciiHost.length - 1) { 743 asciiHost = asciiHost.substring(0, asciiHost.length - 1); 744 lastDotIndex = asciiHost.lastIndexOf("."); 745 } 746 if (lazy.knownDomains.has(asciiHost.toLowerCase())) { 747 return true; 748 } 749 // If there's no dot or only a leading dot we are done, otherwise we'll check 750 // against the known suffixes. 751 if (lastDotIndex <= 0) { 752 return false; 753 } 754 // Don't use getPublicSuffix here, since the suffix is not in the PSL, 755 // thus it couldn't tell if the suffix is made up of one or multiple 756 // dot-separated parts. 757 let lastPart = asciiHost.substr(lastDotIndex + 1); 758 let suffixes = lazy.knownSuffixes.get(lastPart); 759 if (suffixes) { 760 return Array.from(suffixes).some(s => asciiHost.endsWith(s)); 761 } 762 return false; 763 } 764 765 /** 766 * Checks the suffix of ``info.fixedURI`` against the Public Suffix List. 767 * If the suffix is unknown due to a typo this will try to fix it up in-place, 768 * by modifying the public suffix of ``info.fixedURI``. 769 * 770 * @param {URIFixupInfo} info about the uri to check. 771 * @returns {object} result The lookup result. 772 * @returns {string} result.suffix The public suffix if one can be identified. 773 * @returns {boolean} result.hasUnknownSuffix True when the suffix is not in the 774 * Public Suffix List and it's not in knownSuffixes. False in the other cases. 775 */ 776 function checkAndFixPublicSuffix(info) { 777 let uri = info.fixedURI; 778 let asciiHost = uri?.asciiHost; 779 780 // If the original input ends in a "。" character (U+3002), we consider the 781 // input a search query if there is no valid suffix. 782 // While the "。" character is equivalent to a period in domains, it's more 783 // commonly used to terminate search phrases. We're preserving the historical 784 // behavior of the ascii period for now, as that may be more commonly expected 785 // by technical users. 786 if ( 787 !asciiHost || 788 !asciiHost.includes(".") || 789 (asciiHost.endsWith(".") && !info.originalInput.endsWith("。")) || 790 isDomainKnown(asciiHost) 791 ) { 792 return { suffix: "", hasUnknownSuffix: false }; 793 } 794 795 // Quick bailouts for most common cases, according to Alexa Top 1 million. 796 if ( 797 /^\w/.test(asciiHost) && 798 (asciiHost.endsWith(".com") || 799 asciiHost.endsWith(".net") || 800 asciiHost.endsWith(".org") || 801 asciiHost.endsWith(".ru") || 802 asciiHost.endsWith(".de")) 803 ) { 804 return { 805 suffix: asciiHost.substring(asciiHost.lastIndexOf(".") + 1), 806 hasUnknownSuffix: false, 807 }; 808 } 809 try { 810 let suffix = Services.eTLD.getKnownPublicSuffix(uri); 811 if (suffix) { 812 return { suffix, hasUnknownSuffix: false }; 813 } 814 } catch (ex) { 815 return { suffix: "", hasUnknownSuffix: false }; 816 } 817 // Suffix is unknown, try to fix most common 3 chars TLDs typos. 818 // .com is the most commonly mistyped tld, so it has more cases. 819 let suffix = Services.eTLD.getPublicSuffix(uri); 820 if (!suffix || lazy.numberRegex.test(suffix)) { 821 return { suffix: "", hasUnknownSuffix: false }; 822 } 823 for (let [typo, fixed] of [ 824 ["ocm", "com"], 825 ["con", "com"], 826 ["cmo", "com"], 827 ["xom", "com"], 828 ["vom", "com"], 829 ["cpm", "com"], 830 ["com'", "com"], 831 ["ent", "net"], 832 ["ner", "net"], 833 ["nte", "net"], 834 ["met", "net"], 835 ["rog", "org"], 836 ["ogr", "org"], 837 ["prg", "org"], 838 ["orh", "org"], 839 ]) { 840 if (suffix == typo) { 841 let host = uri.host.substring(0, uri.host.length - typo.length) + fixed; 842 let updatePreferredURI = info.preferredURI == info.fixedURI; 843 info.fixedURI = uri.mutate().setHost(host).finalize(); 844 if (updatePreferredURI) { 845 info.preferredURI = info.fixedURI; 846 } 847 return { suffix: fixed, hasUnknownSuffix: false }; 848 } 849 } 850 return { suffix: "", hasUnknownSuffix: true }; 851 } 852 853 function tryKeywordFixupForURIInfo(uriString, fixupInfo, isPrivateContext) { 854 try { 855 let keywordInfo = Services.uriFixup.keywordToURI( 856 uriString, 857 isPrivateContext 858 ); 859 fixupInfo.keywordProviderName = keywordInfo.keywordProviderName; 860 fixupInfo.keywordAsSent = keywordInfo.keywordAsSent; 861 fixupInfo.preferredURI = keywordInfo.preferredURI; 862 return true; 863 } catch (ex) {} 864 return false; 865 } 866 867 /** 868 * This generates an alternate fixedURI, by adding a prefix and a suffix to 869 * the fixedURI host, if and only if the protocol is http. It should _never_ 870 * modify URIs with other protocols. 871 * 872 * @param {URIFixupInfo} info an URIInfo object 873 * @param {integer} fixupFlags the fixup flags 874 * @returns {boolean} Whether an alternate uri was generated 875 */ 876 function maybeSetAlternateFixedURI(info, fixupFlags) { 877 let uri = info.fixedURI; 878 if ( 879 !(fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) || 880 // Code only works for http. Not for any other protocol including https! 881 !uri.schemeIs("http") || 882 // Security - URLs with user / password info should NOT be fixed up 883 uri.userPass || 884 // Don't fix up hosts with ports 885 uri.port != -1 886 ) { 887 return false; 888 } 889 890 let oldHost = uri.host; 891 // Don't create an alternate uri for localhost, because it would be confusing. 892 // Ditto for 'http' and 'https' as these are frequently the result of typos, e.g. 893 // 'https//foo' (note missing : ). 894 if (oldHost == "localhost" || HTTPISH.has(oldHost)) { 895 return false; 896 } 897 898 // Get the prefix and suffix to stick onto the new hostname. By default these 899 // are www. & .com but they could be any other value, e.g. www. & .org 900 let newHost = maybeAddPrefixAndSuffix(oldHost); 901 902 if (newHost == oldHost) { 903 return false; 904 } 905 906 return updateHostAndScheme(info, newHost); 907 } 908 909 /** 910 * Try to fixup a file URI. 911 * 912 * @param {string} uriString The file URI to fix. 913 * @returns {?nsIURI} a fixed uri if it has to add the file: protocol or null. 914 */ 915 function fileURIFixup(uriString) { 916 let attemptFixup = false; 917 let path = uriString; 918 if (AppConstants.platform == "win") { 919 // Check for "\"" in the url-string, just a drive (e.g. C:), 920 // or 'A:/...' where the "protocol" is also a single letter. 921 attemptFixup = 922 uriString.includes("\\") || 923 (uriString[1] == ":" && (uriString.length == 2 || uriString[2] == "/")); 924 if (uriString[1] == ":" && uriString[2] == "/") { 925 path = uriString.replace(/\//g, "\\"); 926 } 927 } else { 928 // UNIX: Check if it starts with "/" or "~". 929 attemptFixup = /^[~/]/.test(uriString); 930 const originalHome = Services.env.get("BB_ORIGINAL_HOME"); 931 if (originalHome && (uriString === "~" || uriString.startsWith("~/"))) { 932 path = originalHome + path.substring(1); 933 } 934 } 935 if (attemptFixup) { 936 try { 937 // Test if this is a valid path by trying to create a local file 938 // object. The URL of that is returned if successful. 939 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); 940 file.initWithPath(path); 941 return Services.io.newURI( 942 lazy.fileProtocolHandler.getURLSpecFromActualFile(file) 943 ); 944 } catch (ex) { 945 // Not a file uri. 946 } 947 } 948 return null; 949 } 950 951 /** 952 * Tries to fixup a string to an nsIURI by adding the default protocol. 953 * 954 * Should fix things like: 955 * no-scheme.com 956 * ftp.no-scheme.com 957 * ftp4.no-scheme.com 958 * no-scheme.com/query?foo=http://www.foo.com 959 * user:pass@no-scheme.com 960 * 961 * @param {string} uriString The string to fixup. 962 * @param {number} fixupFlags The fixup flags to use. 963 * @returns {nsIURI} an nsIURI built adding the default protocol to the string, 964 * or null if fixing was not possible. 965 */ 966 function fixupURIProtocol(uriString, fixupFlags) { 967 // The longest URI scheme on the IANA list is 36 chars + 3 for :// 968 let schemeChars = uriString.slice(0, 39); 969 970 let schemePos = schemeChars.indexOf("://"); 971 if (schemePos == -1 || schemePos > schemeChars.search(/[:\/]/)) { 972 uriString = "http://" + uriString; 973 } 974 try { 975 return makeURIWithFixedLocalHosts(uriString, fixupFlags); 976 } catch (ex) { 977 // We generated an invalid uri. 978 } 979 return null; 980 } 981 982 /** 983 * A thin wrapper around `newURI` that fixes up the host if it's 984 * 0.0.0.0 or ::, which are no longer valid. Aims to facilitate 985 * user typos and/or "broken" links output by commandline tools. 986 * 987 * @param {string} uriString The string to make into a URI. 988 * @param {number} fixupFlags The fixup flags to use. 989 * @throws NS_ERROR_MALFORMED_URI if the uri is invalid. 990 */ 991 function makeURIWithFixedLocalHosts(uriString, fixupFlags) { 992 let uri = Services.io.newURI(uriString); 993 994 // We only want to fix up 0.0.0.0 if the URL came from the user, either 995 // from the address bar or as a commandline argument (ie clicking links 996 // in other applications, terminal, etc.). We can't use 997 // FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP for this as that isn't normally allowed 998 // for external links, and the other flags are sometimes used for 999 // web-provided content. So we cheat and use the scheme typo flag. 1000 if (fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS && HTTPISH.has(uri.scheme)) { 1001 if (uri.host == "0.0.0.0") { 1002 uri = uri.mutate().setHost("127.0.0.1").finalize(); 1003 } else if (uri.host == "::") { 1004 uri = uri.mutate().setHost("[::1]").finalize(); 1005 } 1006 } 1007 return uri; 1008 } 1009 1010 /** 1011 * Tries to fixup a string to a search url. 1012 * 1013 * @param {string} uriString the string to fixup. 1014 * @param {URIFixupInfo} fixupInfo The fixup info object, modified in-place. 1015 * @param {boolean} isPrivateContext Whether this happens in a private context. 1016 * @param {nsIInputStream} postData optional POST data for the search 1017 * @returns {boolean} Whether the keyword fixup was succesful. 1018 */ 1019 function keywordURIFixup(uriString, fixupInfo, isPrivateContext) { 1020 // Here is a few examples of strings that should be searched: 1021 // "what is mozilla" 1022 // "what is mozilla?" 1023 // "docshell site:mozilla.org" - has a space in the origin part 1024 // "?site:mozilla.org - anything that begins with a question mark 1025 // "mozilla'.org" - Things that have a quote before the first dot/colon 1026 // "mozilla/test" - unknown host 1027 // ".mozilla", "mozilla." - starts or ends with a dot () 1028 // "user@nonQualifiedHost" 1029 1030 // These other strings should not be searched, because they could be URIs: 1031 // "www.blah.com" - Domain with a standard or known suffix 1032 // "knowndomain" - known domain 1033 // "nonQualifiedHost:8888?something" - has a port 1034 // "user:pass@nonQualifiedHost" 1035 // "blah.com." 1036 1037 // We do keyword lookups if the input starts with a question mark. 1038 if (uriString.startsWith("?")) { 1039 return tryKeywordFixupForURIInfo( 1040 fixupInfo.originalInput, 1041 fixupInfo, 1042 isPrivateContext 1043 ); 1044 } 1045 1046 // Check for IPs. 1047 const userPassword = lazy.userPasswordRegex.exec(uriString); 1048 const ipString = userPassword 1049 ? uriString.replace(userPassword[2], "") 1050 : uriString; 1051 if (lazy.IPv4LikeRegex.test(ipString) || lazy.IPv6LikeRegex.test(ipString)) { 1052 return false; 1053 } 1054 1055 // Avoid keyword lookup if we can identify a host and it's known, or ends 1056 // with a dot and has some path. 1057 // Note that if dnsFirstForSingleWords is true isDomainKnown will always 1058 // return true, so we can avoid checking dnsFirstForSingleWords after this. 1059 let asciiHost = fixupInfo.fixedURI?.asciiHost; 1060 if ( 1061 asciiHost && 1062 (isDomainKnown(asciiHost) || 1063 (asciiHost.endsWith(".") && 1064 asciiHost.indexOf(".") != asciiHost.length - 1)) 1065 ) { 1066 return false; 1067 } 1068 1069 // Avoid keyword lookup if the url seems to have password. 1070 if (fixupInfo.fixedURI?.password) { 1071 return false; 1072 } 1073 1074 // Even if the host is unknown, avoid keyword lookup if the string has 1075 // uri-like characteristics, unless it looks like "user@unknownHost". 1076 // Note we already excluded passwords at this point. 1077 if ( 1078 !isURILike(uriString, fixupInfo.fixedURI?.displayHost) || 1079 (fixupInfo.fixedURI?.userPass && fixupInfo.fixedURI?.pathQueryRef === "/") 1080 ) { 1081 return tryKeywordFixupForURIInfo( 1082 fixupInfo.originalInput, 1083 fixupInfo, 1084 isPrivateContext 1085 ); 1086 } 1087 1088 return false; 1089 } 1090 1091 /** 1092 * Mimics the logic in Services.io.extractScheme, but avoids crossing XPConnect. 1093 * This also tries to fixup the scheme if it was clearly mistyped. 1094 * 1095 * @param {string} uriString the string to examine 1096 * @param {integer} fixupFlags The original fixup flags 1097 * @returns {object} 1098 * scheme: a typo fixed scheme or empty string if one could not be identified 1099 * fixedSchemeUriString: uri string with a typo fixed scheme 1100 * fixupChangedProtocol: true if the scheme is fixed up 1101 */ 1102 function extractScheme(uriString, fixupFlags = FIXUP_FLAG_NONE) { 1103 const matches = uriString.match(lazy.possibleProtocolRegex); 1104 const hasColon = matches?.[2] === ":"; 1105 const hasSlash2 = matches?.[3] === "//"; 1106 1107 const isFixupSchemeTypos = 1108 lazy.fixupSchemeTypos && fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS; 1109 1110 if ( 1111 !matches || 1112 (!hasColon && !hasSlash2) || 1113 (!hasColon && !isFixupSchemeTypos) 1114 ) { 1115 return { 1116 scheme: "", 1117 fixedSchemeUriString: uriString, 1118 fixupChangedProtocol: false, 1119 }; 1120 } 1121 1122 let scheme = matches[1].replace("\t", "").toLowerCase(); 1123 let fixedSchemeUriString = uriString; 1124 1125 if (isFixupSchemeTypos && hasSlash2) { 1126 // Fix up typos for string that user would have intented as protocol. 1127 const afterProtocol = uriString.substring(matches[0].length); 1128 fixedSchemeUriString = `${scheme}://${afterProtocol}`; 1129 } 1130 1131 let fixupChangedProtocol = false; 1132 1133 if (isFixupSchemeTypos) { 1134 // Fix up common scheme typos. 1135 // TODO: Use levenshtein distance here? 1136 fixupChangedProtocol = [ 1137 ["ttp", "http"], 1138 ["htp", "http"], 1139 ["ttps", "https"], 1140 ["tps", "https"], 1141 ["ps", "https"], 1142 ["htps", "https"], 1143 ["ile", "file"], 1144 ["le", "file"], 1145 ].some(([typo, fixed]) => { 1146 if (scheme === typo) { 1147 scheme = fixed; 1148 fixedSchemeUriString = 1149 scheme + fixedSchemeUriString.substring(typo.length); 1150 return true; 1151 } 1152 return false; 1153 }); 1154 } 1155 1156 return { 1157 scheme, 1158 fixedSchemeUriString, 1159 fixupChangedProtocol, 1160 }; 1161 } 1162 1163 /** 1164 * View-source is a pseudo scheme. We're interested in fixing up the stuff 1165 * after it. The easiest way to do that is to call this method again with 1166 * the "view-source:" lopped off and then prepend it again afterwards. 1167 * 1168 * @param {string} uriString The original string to fixup 1169 * @param {integer} fixupFlags The original fixup flags 1170 * @param {nsIInputStream} postData Optional POST data for the search 1171 * @returns {object} {preferredURI, postData} The fixed URI and relative postData 1172 * @throws if it's not possible to fixup the url 1173 */ 1174 function fixupViewSource(uriString, fixupFlags) { 1175 // We disable keyword lookup and alternate URIs so that small typos don't 1176 // cause us to look at very different domains. 1177 let newFixupFlags = fixupFlags & ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; 1178 let innerURIString = uriString.substring(12).trim(); 1179 1180 // Prevent recursion. 1181 const { scheme: innerScheme } = extractScheme(innerURIString); 1182 if (innerScheme == "view-source") { 1183 throw new Components.Exception( 1184 "Prevent view-source recursion", 1185 Cr.NS_ERROR_FAILURE 1186 ); 1187 } 1188 1189 let info = Services.uriFixup.getFixupURIInfo(innerURIString, newFixupFlags); 1190 if (!info.preferredURI) { 1191 throw new Components.Exception( 1192 "Couldn't build a valid uri", 1193 Cr.NS_ERROR_MALFORMED_URI 1194 ); 1195 } 1196 return { 1197 preferredURI: Services.io.newURI("view-source:" + info.preferredURI.spec), 1198 postData: info.postData, 1199 }; 1200 } 1201 1202 /** 1203 * Fixup the host of fixedURI if it contains consecutive dots. 1204 * 1205 * @param {URIFixupInfo} info an URIInfo object 1206 */ 1207 function fixupConsecutiveDotsHost(fixupInfo) { 1208 const uri = fixupInfo.fixedURI; 1209 1210 try { 1211 if (!uri?.host.includes("..")) { 1212 return; 1213 } 1214 } catch (e) { 1215 return; 1216 } 1217 1218 try { 1219 const isPreferredEqualsToFixed = fixupInfo.preferredURI?.equals(uri); 1220 1221 fixupInfo.fixedURI = uri 1222 .mutate() 1223 .setHost(uri.host.replace(/\.+/g, ".")) 1224 .finalize(); 1225 1226 if (isPreferredEqualsToFixed) { 1227 fixupInfo.preferredURI = fixupInfo.fixedURI; 1228 } 1229 } catch (e) { 1230 if (e.result !== Cr.NS_ERROR_MALFORMED_URI) { 1231 throw e; 1232 } 1233 } 1234 } 1235 1236 /** 1237 * Return whether or not given string is uri like. 1238 * This function returns true like following strings. 1239 * - ":8080" 1240 * - "localhost:8080" (if given host is "localhost") 1241 * - "/foo?bar" 1242 * - "/foo#bar" 1243 * 1244 * @param {string} uriString. 1245 * @param {string} host. 1246 * @param {boolean} true if uri like. 1247 */ 1248 function isURILike(uriString, host) { 1249 const indexOfSlash = uriString.indexOf("/"); 1250 if ( 1251 indexOfSlash >= 0 && 1252 (indexOfSlash < uriString.indexOf("?", indexOfSlash) || 1253 indexOfSlash < uriString.indexOf("#", indexOfSlash)) 1254 ) { 1255 return true; 1256 } 1257 1258 if (uriString.startsWith(host)) { 1259 uriString = uriString.substring(host.length); 1260 } 1261 1262 return lazy.portRegex.test(uriString); 1263 } 1264 1265 /** 1266 * Add prefix and suffix to a hostname if both are missing. 1267 * 1268 * If the host does not start with the prefix, add the prefix to 1269 * the hostname. 1270 * 1271 * By default the prefix and suffix are www. and .com but they could 1272 * be any value e.g. www. and .org as they use the preferences 1273 * "browser.fixup.alternate.prefix" and "browser.fixup.alternative.suffix" 1274 * 1275 * If no changes were made, it returns an empty string. 1276 * 1277 * @param {string} oldHost. 1278 * @return {string} Fixed up hostname or an empty string. 1279 */ 1280 function maybeAddPrefixAndSuffix(oldHost) { 1281 let prefix = Services.prefs.getCharPref( 1282 "browser.fixup.alternate.prefix", 1283 "www." 1284 ); 1285 let suffix = Services.prefs.getCharPref( 1286 "browser.fixup.alternate.suffix", 1287 ".com" 1288 ); 1289 let newHost = ""; 1290 let numDots = (oldHost.match(/\./g) || []).length; 1291 if (numDots == 0) { 1292 newHost = prefix + oldHost + suffix; 1293 } else if (numDots == 1) { 1294 if (prefix && oldHost == prefix) { 1295 newHost = oldHost + suffix; 1296 } else if (suffix && !oldHost.startsWith(prefix)) { 1297 newHost = prefix + oldHost; 1298 } 1299 } 1300 return newHost ? newHost : oldHost; 1301 } 1302 1303 /** 1304 * Given an instance of URIFixupInfo, update its fixedURI. 1305 * 1306 * First, change the protocol to the one stored in 1307 * "browser.fixup.alternate.protocol". 1308 * 1309 * Then, try to update fixedURI's host to newHost. 1310 * 1311 * @param {URIFixupInfo} info. 1312 * @param {string} newHost. 1313 * @return {boolean} 1314 * True, if info was updated without any errors. 1315 * False, if NS_ERROR_MALFORMED_URI error. 1316 * @throws If a non-NS_ERROR_MALFORMED_URI error occurs. 1317 */ 1318 function updateHostAndScheme(info, newHost) { 1319 let oldHost = info.fixedURI.host; 1320 let oldScheme = info.fixedURI.scheme; 1321 try { 1322 info.fixedURI = info.fixedURI 1323 .mutate() 1324 .setScheme(lazy.alternateProtocol) 1325 .setHost(newHost) 1326 .finalize(); 1327 } catch (ex) { 1328 if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { 1329 throw ex; 1330 } 1331 return false; 1332 } 1333 if (oldScheme != info.fixedURI.scheme) { 1334 info.fixupChangedProtocol = true; 1335 } 1336 if (oldHost != info.fixedURI.host) { 1337 info.fixupCreatedAlternateURI = true; 1338 } 1339 return true; 1340 }