1 /*************************************************************************
2 * Drag and Drop Handler.
4 * Implements an observer that filters drag events to prevent OS
5 * access to URLs (a potential proxy bypass vector).
6 *************************************************************************/
8 const { XPCOMUtils
} = ChromeUtils
.import(
9 "resource://gre/modules/XPCOMUtils.jsm"
11 const { Services
} = ChromeUtils
.import("resource://gre/modules/Services.jsm");
13 XPCOMUtils
.defineLazyModuleGetters(this, {
14 ComponentUtils
: "resource://gre/modules/ComponentUtils.jsm",
17 // Module specific constants
18 const kMODULE_NAME
= "Torbutton Drag and Drop Handler";
19 const kCONTRACT_ID
= "@torproject.org/torbutton-dragDropFilter;1";
20 const kMODULE_CID
= Components
.ID("f605ec27-d867-44b5-ad97-2a29276642c3");
22 const kInterfaces
= [Ci
.nsIObserver
, Ci
.nsIClassInfo
];
24 const URLISH_TYPES
= Object
.freeze([
26 "text/x-moz-url-data",
28 "application/x-moz-file-promise-url",
32 Returns true if the text resembles a URL or even just a hostname
33 in a way that may prompt the O.S. or other applications to send out a
34 validation DNS query, if not a full request (e.g. " torproject.org",
35 even with the leading whitespace).
37 function isURLish(text
) {
38 // Ignore leading whitespace.
41 // Without any protocol or dot in the first chunk, this is unlikely
42 // to be considered URLish (exception: localhost, but we don't care).
43 if (!/^[a-z][a-z0-9+-]*:\/\//i.test(text
)) {
45 if (!/^[^.\s\/]+\.[^.\s\/]/.test(text
)) {
49 // Prepare for hostname validation via relative URL building.
52 // Validate URL or hostname.
54 new URL(text
, "https://localhost");
57 // invalid URL, bail out
62 // Returns true if any chunk of text is URLish
63 const hasURLish
= text
=> text
.split(/[^\p{L}_.-:\/%~@$-]+/u).some(isURLish
);
65 function DragDropFilter() {
66 this.logger
= Cc
["@torproject.org/torbutton-logger;1"].getService(
69 this.logger
.log(3, "Component Load 0: New DragDropFilter.");
72 Services
.obs
.addObserver(this, "on-datatransfer-available");
74 this.logger
.log(5, "Failed to register drag observer");
78 DragDropFilter
.prototype = {
79 QueryInterface
: ChromeUtils
.generateQI([Ci
.nsIObserver
]),
81 // make this an nsIClassInfo object
82 flags
: Ci
.nsIClassInfo
.DOM_OBJECT
,
83 classDescription
: kMODULE_NAME
,
84 contractID
: kCONTRACT_ID
,
87 // method of nsIClassInfo
88 getInterfaces(count
) {
89 count
.value
= kInterfaces
.length
;
93 // method of nsIClassInfo
94 getHelperForLanguage(count
) {
98 // method of nsIObserver
99 observe(subject
, topic
, data
) {
100 if (topic
=== "on-datatransfer-available") {
101 this.logger
.log(3, "The DataTransfer is available");
102 this.filterDataTransferURLs(subject
);
106 filterDataTransferURLs(aDataTransfer
) {
107 for (let i
= 0, count
= aDataTransfer
.mozItemCount
; i
< count
; ++i
) {
108 this.logger
.log(3, `Inspecting the data transfer: ${i}.`);
109 const types
= aDataTransfer
.mozTypesAt(i
);
110 for (const type
of types
) {
111 this.logger
.log(3, `Type is: ${type}.`);
113 URLISH_TYPES
.includes(type
) ||
114 ((type
=== "text/plain" || type
=== "text/html") &&
115 hasURLish(aDataTransfer
.getData(type
)))
119 `Removing transfer data ${aDataTransfer.getData(type)}`
121 for (const type
of types
) {
122 aDataTransfer
.clearData(type
);
131 // Assign factory to global object.
132 const NSGetFactory
= XPCOMUtils
.generateNSGetFactory
133 ? XPCOMUtils
.generateNSGetFactory([DragDropFilter
])
134 : ComponentUtils
.generateNSGetFactory([DragDropFilter
]);