... |
... |
@@ -13,6 +13,7 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); |
13
|
13
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
14
|
14
|
ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
|
15
|
15
|
});
|
|
16
|
+XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);
|
16
|
17
|
|
17
|
18
|
// Module specific constants
|
18
|
19
|
const kMODULE_NAME = "Torbutton Drag and Drop Handler";
|
... |
... |
@@ -28,50 +29,55 @@ const URLISH_TYPES = Object.freeze([ |
28
|
29
|
"application/x-moz-file-promise-url",
|
29
|
30
|
]);
|
30
|
31
|
|
31
|
|
-/*
|
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).
|
36
|
|
-*/
|
37
|
|
-function isURLish(text) {
|
38
|
|
- // Ignore leading whitespace.
|
39
|
|
- text = text.trim();
|
40
|
|
-
|
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)) {
|
44
|
|
- // no protocol
|
45
|
|
- if (!/^[^.\s\/]+\.[^.\s\/]/.test(text)) {
|
46
|
|
- // no dot
|
47
|
|
- return false;
|
|
32
|
+const MAIN_PROCESS =
|
|
33
|
+ Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
|
|
34
|
+
|
|
35
|
+const EMPTY_PAYLOAD = {};
|
|
36
|
+const OpaqueDrag = {
|
|
37
|
+ listening: false,
|
|
38
|
+ payload: EMPTY_PAYLOAD,
|
|
39
|
+ store(value, type) {
|
|
40
|
+ let opaqueKey = crypto.randomUUID();
|
|
41
|
+ this.payload = { opaqueKey, value, type };
|
|
42
|
+ if (!this.listening && MAIN_PROCESS) {
|
|
43
|
+ Services.ppmm.addMessageListener(
|
|
44
|
+ "DragDropFilter:GetOpaqueDrag",
|
|
45
|
+ () => this.payload
|
|
46
|
+ );
|
|
47
|
+ this.listening = true;
|
48
|
48
|
}
|
49
|
|
- // Prepare for hostname validation via relative URL building.
|
50
|
|
- text = `//${text}`;
|
51
|
|
- }
|
52
|
|
- // Validate URL or hostname.
|
53
|
|
- try {
|
54
|
|
- new URL(text, "https://localhost");
|
55
|
|
- return true;
|
56
|
|
- } catch (e) {
|
57
|
|
- // invalid URL, bail out
|
58
|
|
- }
|
59
|
|
- return false;
|
60
|
|
-}
|
61
|
|
-
|
62
|
|
-// Returns true if any chunk of text is URLish
|
63
|
|
-const hasURLish = text => text.split(/[^\p{L}_.-:\/%~@$-]+/u).some(isURLish);
|
|
49
|
+ return opaqueKey;
|
|
50
|
+ },
|
|
51
|
+ retrieve(key) {
|
|
52
|
+ let { opaqueKey, value, type } = this.payload;
|
|
53
|
+ if (opaqueKey === key) {
|
|
54
|
+ return { value, type };
|
|
55
|
+ }
|
|
56
|
+ if (!MAIN_PROCESS) {
|
|
57
|
+ this.payload = Services.cpmm.sendSyncMessage(
|
|
58
|
+ "DragDropFilter:GetOpaqueDrag"
|
|
59
|
+ )[0];
|
|
60
|
+ if (key === this.payload.opaqueKey) {
|
|
61
|
+ return this.retrieve(key);
|
|
62
|
+ }
|
|
63
|
+ }
|
|
64
|
+ return EMPTY_PAYLOAD;
|
|
65
|
+ },
|
|
66
|
+};
|
64
|
67
|
|
65
|
68
|
function DragDropFilter() {
|
66
|
69
|
this.logger = Cc["@torproject.org/torbutton-logger;1"].getService(
|
67
|
70
|
Ci.nsISupports
|
68
|
71
|
).wrappedJSObject;
|
69
|
72
|
this.logger.log(3, "Component Load 0: New DragDropFilter.");
|
70
|
|
-
|
71
|
|
- try {
|
72
|
|
- Services.obs.addObserver(this, "on-datatransfer-available");
|
73
|
|
- } catch (e) {
|
74
|
|
- this.logger.log(5, "Failed to register drag observer");
|
|
73
|
+ if (MAIN_PROCESS) {
|
|
74
|
+ // We want to update our status in the main process only, in order to
|
|
75
|
+ // serve the same opaque drag payload in every process.
|
|
76
|
+ try {
|
|
77
|
+ Services.obs.addObserver(this, "on-datatransfer-available");
|
|
78
|
+ } catch (e) {
|
|
79
|
+ this.logger.log(5, "Failed to register drag observer");
|
|
80
|
+ }
|
75
|
81
|
}
|
76
|
82
|
}
|
77
|
83
|
|
... |
... |
@@ -109,23 +115,38 @@ DragDropFilter.prototype = { |
109
|
115
|
const types = aDataTransfer.mozTypesAt(i);
|
110
|
116
|
for (const type of types) {
|
111
|
117
|
this.logger.log(3, `Type is: ${type}.`);
|
112
|
|
- if (
|
113
|
|
- URLISH_TYPES.includes(type) ||
|
114
|
|
- ((type === "text/plain" || type === "text/html") &&
|
115
|
|
- hasURLish(aDataTransfer.getData(type)))
|
116
|
|
- ) {
|
|
118
|
+ if (URLISH_TYPES.includes(type)) {
|
117
|
119
|
this.logger.log(
|
118
|
120
|
3,
|
119
|
|
- `Removing transfer data ${aDataTransfer.getData(type)}`
|
|
121
|
+ `Removing transfer data ${aDataTransfer.mozGetDataAt(type, i)}`
|
120
|
122
|
);
|
|
123
|
+ const urlType = "text/x-moz-url";
|
|
124
|
+ // Fallback url type, to be parsed by this browser but not externally
|
|
125
|
+ const INTERNAL_FALLBACK = "application/x-torbrowser-opaque";
|
|
126
|
+ if (types.contains(urlType)) {
|
|
127
|
+ const link = aDataTransfer.mozGetDataAt(urlType, i);
|
|
128
|
+ const opaqueKey = OpaqueDrag.store(link, urlType);
|
|
129
|
+ aDataTransfer.mozSetDataAt(INTERNAL_FALLBACK, opaqueKey, i);
|
|
130
|
+ }
|
121
|
131
|
for (const type of types) {
|
122
|
|
- aDataTransfer.clearData(type);
|
|
132
|
+ if (
|
|
133
|
+ type !== INTERNAL_FALLBACK &&
|
|
134
|
+ type !== "text/x-moz-place" // don't touch bookmarks
|
|
135
|
+ ) {
|
|
136
|
+ aDataTransfer.mozClearDataAt(type, i);
|
|
137
|
+ }
|
123
|
138
|
}
|
124
|
139
|
break;
|
125
|
140
|
}
|
126
|
141
|
}
|
127
|
142
|
}
|
128
|
143
|
},
|
|
144
|
+
|
|
145
|
+ opaqueDrag: {
|
|
146
|
+ get(opaqueKey) {
|
|
147
|
+ return OpaqueDrag.retrieve(opaqueKey);
|
|
148
|
+ },
|
|
149
|
+ },
|
129
|
150
|
};
|
130
|
151
|
|
131
|
152
|
// Assign factory to global object.
|