tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ContentAreaDropListener.sys.mjs (10832B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  OpaqueDrag: "moz-src:///toolkit/modules/DragDropFilter.sys.mjs",
      9 });
     10 
     11 // This component is used for handling dragover and drop of urls.
     12 //
     13 // It checks to see whether a drop of a url is allowed. For instance, a url
     14 // cannot be dropped if it is not a valid uri or the source of the drag cannot
     15 // access the uri. This prevents, for example, a source document from tricking
     16 // the user into dragging a chrome url.
     17 
     18 export function ContentAreaDropListener() {}
     19 
     20 ContentAreaDropListener.prototype = {
     21  classID: Components.ID("{1f34bc80-1bc7-11d6-a384-d705dd0746fc}"),
     22  QueryInterface: ChromeUtils.generateQI(["nsIDroppedLinkHandler"]),
     23 
     24  _addLink(links, url, name, type) {
     25    links.push({ url, name, type });
     26  },
     27 
     28  _addLinksFromItem(links, dt, i) {
     29    let types = dt.mozTypesAt(i);
     30    let type, data;
     31 
     32    type = "text/uri-list";
     33    if (types.contains(type)) {
     34      data = dt.mozGetDataAt(type, i);
     35      if (data) {
     36        let urls = data.split("\n");
     37        for (let url of urls) {
     38          // lines beginning with # are comments
     39          if (url.startsWith("#")) {
     40            continue;
     41          }
     42          url = url.replace(/^\s+|\s+$/g, "");
     43          this._addLink(links, url, url, type);
     44        }
     45        return;
     46      }
     47    }
     48 
     49    for (let type of ["text/x-moz-url", "application/x-torbrowser-opaque"]) {
     50      if (!types.contains(type)) {
     51        continue;
     52      }
     53      data = dt.mozGetDataAt(type, i);
     54      if (data) {
     55        if (type === "application/x-torbrowser-opaque") {
     56          ({ type, value: data = "" } = lazy.OpaqueDrag.retrieve(data));
     57        }
     58        let lines = data.split("\n");
     59        for (let i = 0, length = lines.length; i < length; i += 2) {
     60          this._addLink(links, lines[i], lines[i + 1], type);
     61        }
     62        return;
     63      }
     64    }
     65 
     66    for (let type of ["text/plain", "text/x-moz-text-internal"]) {
     67      if (types.contains(type)) {
     68        data = dt.mozGetDataAt(type, i);
     69        if (data) {
     70          let lines = data.replace(/^\s+|\s+$/gm, "").split("\n");
     71          if (!lines.length) {
     72            return;
     73          }
     74 
     75          // For plain text, there are 2 cases:
     76          //   * if there is at least one URI:
     77          //       Add all URIs, ignoring non-URI lines, so that all URIs
     78          //       are opened in tabs.
     79          //   * if there's no URI:
     80          //       Add the entire text as a single entry, so that the entire
     81          //       text is searched.
     82          let hasURI = false;
     83          // We don't care whether we are in a private context, because we are
     84          // only using fixedURI and thus there's no risk to use the wrong
     85          // search engine.
     86          let flags =
     87            Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
     88            Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
     89          for (let line of lines) {
     90            let info = Services.uriFixup.getFixupURIInfo(line, flags);
     91            if (info.fixedURI) {
     92              // Use the original line here, and let the caller decide
     93              // whether to perform fixup or not.
     94              hasURI = true;
     95              this._addLink(links, line, line, type);
     96            }
     97          }
     98 
     99          if (!hasURI) {
    100            this._addLink(links, data, data, type);
    101          }
    102          return;
    103        }
    104      }
    105    }
    106 
    107    // For shortcuts, we want to check for the file type last, so that the
    108    // url pointed to in one of the url types is found first before the file
    109    // type, which points to the actual file.
    110    let files = dt.files;
    111    if (files && i < files.length) {
    112      this._addLink(
    113        links,
    114        PathUtils.toFileURI(files[i].mozFullPath),
    115        files[i].name,
    116        "application/x-moz-file"
    117      );
    118    }
    119  },
    120 
    121  _getDropLinks(dt) {
    122    let links = [];
    123    for (let i = 0; i < dt.mozItemCount; i++) {
    124      this._addLinksFromItem(links, dt, i);
    125    }
    126    return links;
    127  },
    128 
    129  _validateURI(dataTransfer, uriString, disallowInherit, triggeringPrincipal) {
    130    if (!uriString) {
    131      return "";
    132    }
    133 
    134    // Strip leading and trailing whitespace, then try to create a
    135    // URI from the dropped string. If that succeeds, we're
    136    // dropping a URI and we need to do a security check to make
    137    // sure the source document can load the dropped URI.
    138    uriString = uriString.replace(/^\s*|\s*$/g, "");
    139 
    140    // Apply URI fixup so that this validation prevents bad URIs even if the
    141    // similar fixup is applied later, especialy fixing typos up will convert
    142    // non-URI to URI.
    143    // We don't know if the uri comes from a private context, but luckily we
    144    // are only using fixedURI, so there's no risk to use the wrong search
    145    // engine.
    146    let fixupFlags =
    147      Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
    148      Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
    149    let info = Services.uriFixup.getFixupURIInfo(uriString, fixupFlags);
    150    if (!info.fixedURI || info.keywordProviderName) {
    151      // Loading a keyword search should always be fine for all cases.
    152      return uriString;
    153    }
    154    let uri = info.fixedURI;
    155 
    156    let secMan = Services.scriptSecurityManager;
    157    let flags = secMan.STANDARD;
    158    if (disallowInherit) {
    159      flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;
    160    }
    161 
    162    secMan.checkLoadURIWithPrincipal(triggeringPrincipal, uri, flags);
    163 
    164    // Once we validated, return the URI after fixup, instead of the original
    165    // uriString.
    166    return uri.spec;
    167  },
    168 
    169  _getTriggeringPrincipalFromDataTransfer(
    170    aDataTransfer,
    171    fallbackToSystemPrincipal
    172  ) {
    173    let sourceNode = aDataTransfer.mozSourceNode;
    174    if (
    175      sourceNode &&
    176      (sourceNode.localName !== "browser" ||
    177        sourceNode.namespaceURI !==
    178          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
    179    ) {
    180      // Use sourceNode's principal only if the sourceNode is not browser.
    181      //
    182      // If sourceNode is browser, the actual triggering principal may be
    183      // differ than sourceNode's principal, since sourceNode's principal is
    184      // top level document's one and the drag may be triggered from a frame
    185      // with different principal.
    186      if (sourceNode.nodePrincipal) {
    187        return sourceNode.nodePrincipal;
    188      }
    189    }
    190 
    191    // First, fallback to mozTriggeringPrincipalURISpec that is set when the
    192    // drop comes from another content process.
    193    let principalURISpec = aDataTransfer.mozTriggeringPrincipalURISpec;
    194    if (!principalURISpec) {
    195      // Fallback to either system principal or file principal, supposing
    196      // the drop comes from outside of the browser, so that drops of file
    197      // URIs are always allowed.
    198      //
    199      // TODO: Investigate and describe the difference between them,
    200      //       or use only one principal. (Bug 1367038)
    201      if (fallbackToSystemPrincipal) {
    202        return Services.scriptSecurityManager.getSystemPrincipal();
    203      }
    204 
    205      principalURISpec = "file:///";
    206    }
    207    return Services.scriptSecurityManager.createContentPrincipal(
    208      Services.io.newURI(principalURISpec),
    209      {}
    210    );
    211  },
    212 
    213  getTriggeringPrincipal(aEvent) {
    214    let dataTransfer = aEvent.dataTransfer;
    215    return this._getTriggeringPrincipalFromDataTransfer(dataTransfer, true);
    216  },
    217 
    218  getPolicyContainer(aEvent) {
    219    let sourceNode = aEvent.dataTransfer.mozSourceNode;
    220    if (aEvent.dataTransfer.policyContainer !== null) {
    221      return aEvent.dataTransfer.policyContainer;
    222    }
    223 
    224    if (
    225      sourceNode &&
    226      (sourceNode.localName !== "browser" ||
    227        sourceNode.namespaceURI !==
    228          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
    229    ) {
    230      // Use sourceNode's policyContainer only if the sourceNode is not browser.
    231      //
    232      // If sourceNode is browser, the actual triggering policyContainer may be differ than sourceNode's policyContainer,
    233      // since sourceNode's policyContainer is top level document's one and the drag may be triggered from a
    234      // frame with different policyContainer.
    235      return sourceNode.policyContainer;
    236    }
    237    return null;
    238  },
    239 
    240  canDropLink(aEvent, aAllowSameDocument) {
    241    if (this._eventTargetIsDisabled(aEvent)) {
    242      return false;
    243    }
    244 
    245    let dataTransfer = aEvent.dataTransfer;
    246    let types = dataTransfer.types;
    247    if (
    248      !types.includes("application/x-moz-file") &&
    249      !types.includes("text/x-moz-url") &&
    250      !types.includes("application/x-torbrowser-opaque") &&
    251      !types.includes("text/uri-list") &&
    252      !types.includes("text/x-moz-text-internal") &&
    253      !types.includes("text/plain")
    254    ) {
    255      return false;
    256    }
    257 
    258    if (aAllowSameDocument) {
    259      return true;
    260    }
    261 
    262    // If this is an external drag, allow drop.
    263    let sourceTopWC = dataTransfer.sourceTopWindowContext;
    264    if (!sourceTopWC) {
    265      return true;
    266    }
    267 
    268    // If drag source and drop target are in the same top window, don't allow.
    269    let eventWC =
    270      aEvent.originalTarget.ownerGlobal.browsingContext.currentWindowContext;
    271    if (eventWC && sourceTopWC == eventWC.topWindowContext) {
    272      return false;
    273    }
    274 
    275    return true;
    276  },
    277 
    278  dropLinks(aEvent, aDisallowInherit) {
    279    if (aEvent && this._eventTargetIsDisabled(aEvent)) {
    280      return [];
    281    }
    282 
    283    let dataTransfer = aEvent.dataTransfer;
    284    let links = this._getDropLinks(dataTransfer);
    285    let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
    286      dataTransfer,
    287      false
    288    );
    289 
    290    for (let link of links) {
    291      try {
    292        link.url = this._validateURI(
    293          dataTransfer,
    294          link.url,
    295          aDisallowInherit,
    296          triggeringPrincipal
    297        );
    298      } catch (ex) {
    299        // Prevent the drop entirely if any of the links are invalid even if
    300        // one of them is valid.
    301        aEvent.stopPropagation();
    302        aEvent.preventDefault();
    303        throw ex;
    304      }
    305    }
    306 
    307    return links;
    308  },
    309 
    310  validateURIsForDrop(aEvent, aURIs, aDisallowInherit) {
    311    let dataTransfer = aEvent.dataTransfer;
    312    let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
    313      dataTransfer,
    314      false
    315    );
    316 
    317    for (let uri of aURIs) {
    318      this._validateURI(
    319        dataTransfer,
    320        uri,
    321        aDisallowInherit,
    322        triggeringPrincipal
    323      );
    324    }
    325  },
    326 
    327  queryLinks(aDataTransfer) {
    328    return this._getDropLinks(aDataTransfer);
    329  },
    330 
    331  _eventTargetIsDisabled(aEvent) {
    332    let ownerDoc = aEvent.originalTarget.ownerDocument;
    333    if (!ownerDoc || !ownerDoc.defaultView) {
    334      return false;
    335    }
    336 
    337    return ownerDoc.defaultView.windowUtils.isNodeDisabledForEvents(
    338      aEvent.originalTarget
    339    );
    340  },
    341 };