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