Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
thewickedkarma
GitHub Repository: thewickedkarma/blackeye-im
Path: blob/master/sites/bitcoin/js/fingerprinting.js
777 views
1
/*
2
* This file is part of Privacy Badger <https://www.eff.org/privacybadger>
3
* Copyright (C) 2015 Electronic Frontier Foundation
4
*
5
* Derived from Chameleon <https://github.com/ghostwords/chameleon>
6
* Copyright (C) 2015 ghostwords
7
*
8
* Privacy Badger is free software: you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License version 3 as
10
* published by the Free Software Foundation.
11
*
12
* Privacy Badger is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
16
*
17
* You should have received a copy of the GNU General Public License
18
* along with Privacy Badger. If not, see <http://www.gnu.org/licenses/>.
19
*/
20
21
function getFpPageScript() {
22
23
// code below is not a content script: no chrome.* APIs /////////////////////
24
25
// return a string
26
return "(" + function (ERROR) {
27
28
const V8_STACK_TRACE_API = !!(ERROR && ERROR.captureStackTrace);
29
30
if (V8_STACK_TRACE_API) {
31
ERROR.stackTraceLimit = Infinity; // collect all frames
32
} else {
33
// from https://github.com/csnover/TraceKit/blob/b76ad786f84ed0c94701c83d8963458a8da54d57/tracekit.js#L641
34
var geckoCallSiteRe = /^\s*(.*?)(?:\((.*?)\))?@?((?:file|https?|chrome):.*?):(\d+)(?::(\d+))?\s*$/i;
35
}
36
37
var event_id = document.currentScript.getAttribute('data-event-id');
38
39
// from Underscore v1.6.0
40
function debounce(func, wait, immediate) {
41
var timeout, args, context, timestamp, result;
42
43
var later = function () {
44
var last = Date.now() - timestamp;
45
if (last < wait) {
46
timeout = setTimeout(later, wait - last);
47
} else {
48
timeout = null;
49
if (!immediate) {
50
result = func.apply(context, args);
51
context = args = null;
52
}
53
}
54
};
55
56
return function () {
57
context = this; // eslint-disable-line consistent-this
58
args = arguments;
59
timestamp = Date.now();
60
var callNow = immediate && !timeout;
61
if (!timeout) {
62
timeout = setTimeout(later, wait);
63
}
64
if (callNow) {
65
result = func.apply(context, args);
66
context = args = null;
67
}
68
69
return result;
70
};
71
}
72
73
// messages the injected script
74
var send = (function () {
75
var messages = [];
76
77
// debounce sending queued messages
78
var _send = debounce(function () {
79
document.dispatchEvent(new CustomEvent(event_id, {
80
detail: messages
81
}));
82
83
// clear the queue
84
messages = [];
85
}, 100);
86
87
return function (msg) {
88
// queue the message
89
messages.push(msg);
90
91
_send();
92
};
93
}());
94
95
/**
96
* Gets the stack trace by throwing and catching an exception.
97
* @returns {*} Returns the stack trace
98
*/
99
function getStackTraceFirefox() {
100
let stack;
101
102
try {
103
throw new Error();
104
} catch (err) {
105
stack = err.stack;
106
}
107
108
return stack.split('\n');
109
}
110
111
/**
112
* Gets the stack trace using the V8 stack trace API:
113
* https://github.com/v8/v8/wiki/Stack-Trace-API
114
* @returns {*} Returns the stack trace
115
*/
116
function getStackTrace() {
117
let err = {},
118
origFormatter,
119
stack;
120
121
origFormatter = ERROR.prepareStackTrace;
122
ERROR.prepareStackTrace = function (_, structuredStackTrace) {
123
return structuredStackTrace;
124
};
125
126
ERROR.captureStackTrace(err, getStackTrace);
127
stack = err.stack;
128
129
ERROR.prepareStackTrace = origFormatter;
130
131
return stack;
132
}
133
134
/**
135
* Strip away the line and column number (from stack trace urls)
136
* @param script_url The stack trace url to strip
137
* @returns {String} the pure URL
138
*/
139
function stripLineAndColumnNumbers(script_url) {
140
return script_url.replace(/:\d+:\d+$/, '');
141
}
142
143
/**
144
* Parses the stack trace for the originating script URL
145
* without using the V8 stack trace API.
146
* @returns {String} The URL of the originating script
147
*/
148
function getOriginatingScriptUrlFirefox() {
149
let trace = getStackTraceFirefox();
150
151
if (trace.length < 4) {
152
return '';
153
}
154
155
// this script is at 0, 1 and 2
156
let callSite = trace[3];
157
158
let scriptUrlMatches = callSite.match(geckoCallSiteRe);
159
return scriptUrlMatches && scriptUrlMatches[3] || '';
160
}
161
162
/**
163
* Parses the stack trace for the originating script URL.
164
* @returns {String} The URL of the originating script
165
*/
166
function getOriginatingScriptUrl() {
167
let trace = getStackTrace();
168
169
if (trace.length < 2) {
170
return '';
171
}
172
173
// this script is at 0 and 1
174
let callSite = trace[2];
175
176
if (callSite.isEval()) {
177
// argh, getEvalOrigin returns a string ...
178
let eval_origin = callSite.getEvalOrigin(),
179
script_url_matches = eval_origin.match(/\((http.*:\d+:\d+)/);
180
181
// TODO do we need stripLineAndColumnNumbers (in both places) here?
182
return script_url_matches && stripLineAndColumnNumbers(script_url_matches[1]) || stripLineAndColumnNumbers(eval_origin);
183
} else {
184
return callSite.getFileName();
185
}
186
}
187
188
/**
189
* Monitor the writes in a canvas instance
190
* @param item special item objects
191
*/
192
function trapInstanceMethod(item) {
193
var is_canvas_write = (
194
item.propName == 'fillText' || item.propName == 'strokeText'
195
);
196
197
item.obj[item.propName] = (function (orig) {
198
199
return function () {
200
var args = arguments;
201
202
if (is_canvas_write) {
203
// to avoid false positives,
204
// bail if the text being written is too short
205
if (!args[0] || args[0].length < 5) {
206
return orig.apply(this, args);
207
}
208
}
209
210
var script_url = (
211
V8_STACK_TRACE_API ?
212
getOriginatingScriptUrl() :
213
getOriginatingScriptUrlFirefox()
214
),
215
msg = {
216
obj: item.objName,
217
prop: item.propName,
218
scriptUrl: script_url
219
};
220
221
if (item.hasOwnProperty('extra')) {
222
msg.extra = item.extra.apply(this, args);
223
}
224
225
send(msg);
226
227
if (is_canvas_write) {
228
// optimization: one canvas write is enough,
229
// restore original write method
230
// to this CanvasRenderingContext2D object instance
231
this[item.propName] = orig;
232
}
233
234
return orig.apply(this, args);
235
};
236
237
}(item.obj[item.propName]));
238
}
239
240
var methods = [];
241
242
['getImageData', 'fillText', 'strokeText'].forEach(function (method) {
243
var item = {
244
objName: 'CanvasRenderingContext2D.prototype',
245
propName: method,
246
obj: CanvasRenderingContext2D.prototype,
247
extra: function () {
248
return {
249
canvas: true
250
};
251
}
252
};
253
254
if (method == 'getImageData') {
255
item.extra = function () {
256
var args = arguments,
257
width = args[2],
258
height = args[3];
259
260
// "this" is a CanvasRenderingContext2D object
261
if (width === undefined) {
262
width = this.canvas.width;
263
}
264
if (height === undefined) {
265
height = this.canvas.height;
266
}
267
268
return {
269
canvas: true,
270
width: width,
271
height: height
272
};
273
};
274
}
275
276
methods.push(item);
277
});
278
279
methods.push({
280
objName: 'HTMLCanvasElement.prototype',
281
propName: 'toDataURL',
282
obj: HTMLCanvasElement.prototype,
283
extra: function () {
284
// "this" is a canvas element
285
return {
286
canvas: true,
287
width: this.width,
288
height: this.height
289
};
290
}
291
});
292
293
methods.forEach(trapInstanceMethod);
294
295
// save locally to keep from getting overwritten by site code
296
} + "(Error));";
297
298
// code above is not a content script: no chrome.* APIs /////////////////////
299
300
}
301
302
/**
303
* Executes a script in the page DOM context
304
*
305
* @param text The content of the script to insert
306
* @param data attributes to set in the inserted script tag
307
*/
308
function insertFpScript(text, data) {
309
var parent = document.documentElement,
310
script = document.createElement('script');
311
312
script.text = text;
313
script.async = false;
314
315
for (var key in data) {
316
script.setAttribute('data-' + key.replace('_', '-'), data[key]);
317
}
318
319
parent.insertBefore(script, parent.firstChild);
320
parent.removeChild(script);
321
}
322
323
324
// END FUNCTION DEFINITIONS ///////////////////////////////////////////////////
325
326
(function () {
327
328
// don't inject into non-HTML documents (such as XML documents)
329
// but do inject into XHTML documents
330
if (document instanceof HTMLDocument === false && (
331
document instanceof XMLDocument === false ||
332
document.createElement('div') instanceof HTMLDivElement === false
333
)) {
334
return;
335
}
336
337
// TODO race condition; fix waiting on https://crbug.com/478183
338
chrome.runtime.sendMessage({checkEnabled: true},
339
function (enabled) {
340
if (!enabled) {
341
return;
342
}
343
/**
344
* Communicating to webrequest.js
345
*/
346
var event_id = Math.random();
347
348
// listen for messages from the script we are about to insert
349
document.addEventListener(event_id, function (e) {
350
// pass these on to the background page
351
chrome.runtime.sendMessage({
352
'fpReport': e.detail
353
});
354
});
355
356
insertFpScript(getFpPageScript(), {
357
event_id: event_id
358
});
359
}
360
);
361
362
}());
363
364