1
- import { BlacklightEvent , JsInstrumentEvent } from './types' ;
2
-
3
1
/**
4
2
* @fileOverview Utility functions for canvas finerprinting analysis.
5
3
* Implemented following the Princeton study's methodology.
6
4
*/
7
-
8
- import { getScriptUrl , serializeCanvasCallMap } from './utils' ;
5
+ import { BlacklightEvent , JsInstrumentEvent , CanvasCallMap } from './types' ;
6
+ import { getScriptUrl , serializeCanvasCallMap } from './helpers/ utils' ;
9
7
10
8
const MIN_CANVAS_IMAGE_WIDTH = 16 ;
11
9
const MIN_CANVAS_IMAGE_HEIGHT = 16 ;
12
10
const MIN_FONT_LIST_SIZE = 50 ;
13
11
const MIN_TEXT_MEASURE_COUNT = 50 ;
14
12
const MIN_TEXT_LENGTH = 10 ;
13
+
14
+ const CANVAS_READ_FUNCS = [ 'HTMLCanvasElement.toDataURL' , 'CanvasRenderingContext2D.getImageData' ] ;
15
+ const CANVAS_WRITE_FUNCS = [ 'CanvasRenderingContext2D.fillText' , 'CanvasRenderingContext2D.strokeText' ] ;
16
+ const CANVAS_FP_DO_NOT_CALL_LIST = [ 'CanvasRenderingContext2D.save' , 'CanvasRenderingContext2D.restore' , 'HTMLCanvasElement.addEventListener' ] ;
17
+ const CANVAS_FONT = [ 'CanvasRenderingContext2D.measureText' , 'CanvasRenderingContext2D.font' ] ;
18
+
15
19
/**
16
20
* Return the string that is written onto canvas from function arguments
17
- * @param arguments
21
+ * @param args
18
22
*/
19
23
const getCanvasText = ( args : string [ ] ) => {
20
24
if ( ! args || ! args [ 0 ] ) {
@@ -31,40 +35,34 @@ const getTextLength = (text: string) => {
31
35
/**
32
36
* Check if the retrieved pixel data is larger than min. dimensions.
33
37
* {@link https://developer.mozilla.org/en-US/docsz1/Web/API/CanvasRenderingContext2D/getImageData#Parameters | Web API Image Data Parameters}
34
- * @param arguments
38
+ * @param args
35
39
*/
36
- const isGetImageDataDimsTooSmall = ( args : string [ ] ) => {
37
- const sw = parseInt ( args [ 2 ] , 10 ) ;
38
- const sh = parseInt ( args [ 3 ] , 10 ) ;
39
- return sw < MIN_CANVAS_IMAGE_WIDTH || sh < MIN_CANVAS_IMAGE_HEIGHT ;
40
+ const isImageTooSmall = ( args : string [ ] ) => {
41
+ const width = parseInt ( args [ 2 ] , 10 ) ;
42
+ const height = parseInt ( args [ 3 ] , 10 ) ;
43
+ return width < MIN_CANVAS_IMAGE_WIDTH || height < MIN_CANVAS_IMAGE_HEIGHT ;
40
44
} ;
41
- type ScriptUrl = string ;
42
- type CanvasCallValue = string ;
43
- type CanvasCallMap = Map < ScriptUrl , Set < CanvasCallValue > > ;
45
+
44
46
/**
45
- * This function takes a list of Javascript calls to HTML Canvas properties from a browsers window object.
47
+ * This function takes a list of Javascript calls to HTML Canvas properties from a browser's window object.
46
48
* It sorts the functions in order to evaluate which script are fingerprinting a browser using the criteria
47
49
* described by Englehardt & Narayanan, 2016
48
50
* We Filter for 4 Criteria
49
- * Criteria 1: The canvas element’s height and width properties must not be set below 16
50
- * Criteria 2: Text must be written to canvas with least two colors or at least 10 distinct charachters
51
- * Criteria 3: The script should not call the save, restore, or addEventListener methods of the rendering context.
52
- * Criteria 4: The script extracts an image withtoDataURL or with a single call togetImageData that specifies an area with a minimum size of 16px×16px
51
+ * Criteria 1: The canvas element’s height and width properties must not be set below 16px
52
+ * Criteria 2: Text must be written to canvas with least two colors or at least 10 distinct characters
53
+ * Criteria 3: The script should not call the save, restore, or addEventListener methods of the rendering context.
54
+ * Criteria 4: The script extracts an image with toDataURL or with a single call to getImageData that specifies an area with a minimum size of 16px×16px
53
55
* @param canvasCalls
54
56
* @see {@link http://randomwalker.info/publications/OpenWPM_1_million_site_tracking_measurement.pdf#page=12 }
55
57
*/
56
58
export const sortCanvasCalls = ( canvasCalls : BlacklightEvent [ ] ) => {
57
- const CANVAS_READ_FUNCS = [ 'HTMLCanvasElement.toDataURL' , 'CanvasRenderingContext2D.getImageData' ] ;
58
-
59
- const CANVAS_WRITE_FUNCS = [ 'CanvasRenderingContext2D.fillText' , 'CanvasRenderingContext2D.strokeText' ] ;
60
- const CANVAS_FP_DO_NOT_CALL_LIST = [ 'CanvasRenderingContext2D.save' , 'CanvasRenderingContext2D.restore' , 'HTMLCanvasElement.addEventListener' ] ;
61
-
62
- const cReads = new Map ( ) as CanvasCallMap ;
59
+ const cReads = new Map ( ) as CanvasCallMap ;
63
60
const cDataUrls = new Map ( ) as CanvasCallMap ;
64
- const cWrites = new Map ( ) as CanvasCallMap ;
65
- const cTexts = new Map ( ) as CanvasCallMap ;
66
- const cBanned = new Map ( ) as CanvasCallMap ;
67
- const cStyles = new Map ( ) as CanvasCallMap ;
61
+ const cWrites = new Map ( ) as CanvasCallMap ;
62
+ const cTexts = new Map ( ) as CanvasCallMap ;
63
+ const cBanned = new Map ( ) as CanvasCallMap ;
64
+ const cStyles = new Map ( ) as CanvasCallMap ;
65
+
68
66
for ( const item of canvasCalls ) {
69
67
const { url, data } = item as JsInstrumentEvent ;
70
68
const url_host = new URL ( url ) . hostname ;
@@ -75,7 +73,7 @@ export const sortCanvasCalls = (canvasCalls: BlacklightEvent[]) => {
75
73
}
76
74
77
75
if ( CANVAS_READ_FUNCS . includes ( symbol ) && operation === 'call' ) {
78
- if ( symbol === 'CanvasRenderingContext2D.getImageData' && isGetImageDataDimsTooSmall ( data . arguments ) ) {
76
+ if ( symbol === 'CanvasRenderingContext2D.getImageData' && isImageTooSmall ( data . arguments ) ) {
79
77
continue ;
80
78
}
81
79
if ( symbol === 'HTMLCanvasElement.toDataURL' ) {
@@ -111,37 +109,31 @@ export const sortCanvasCalls = (canvasCalls: BlacklightEvent[]) => {
111
109
* @see {@link sortCanvasCalls }
112
110
* @param canvasCalls
113
111
*/
114
- export const getCanvasFp = (
112
+ export const getCanvasFingerprinters = (
115
113
canvasCalls
116
114
) : {
117
115
fingerprinters : string [ ] ;
118
116
texts : any ;
119
117
styles : any ;
120
118
data_url : any ;
121
119
} => {
120
+ const fingerprinters : Set < string > = new Set ( ) ;
122
121
const { cDataUrls, cReads, cWrites, cBanned, cTexts, cStyles } = sortCanvasCalls ( canvasCalls ) ;
123
122
124
- const fingerprinters : Set < string > = new Set ( ) ;
125
123
for ( const [ script_url , url_hosts ] of cReads . entries ( ) ) {
126
- if ( fingerprinters . has ( script_url ) ) {
127
- continue ;
128
- }
124
+ if ( fingerprinters . has ( script_url ) ) continue ;
129
125
130
126
const rwIntersection = new Set ( [ ...url_hosts ] . filter ( x => cWrites . has ( script_url ) && cWrites . get ( script_url ) . has ( x ) ) ) ;
131
-
132
- if ( rwIntersection . size < 1 ) {
133
- continue ;
134
- }
127
+ if ( rwIntersection . size < 1 ) continue ;
128
+
135
129
for ( const canvasRwVisit of rwIntersection . values ( ) ) {
136
130
if ( cBanned . has ( script_url ) && cBanned . get ( script_url ) . has ( canvasRwVisit ) ) {
137
- // console.log(
138
- // `Ignoring script ${script_url} from url_host ${canvasRwVisit}`
139
- // );
140
131
continue ;
141
132
}
142
133
fingerprinters . add ( script_url ) ;
143
134
}
144
135
}
136
+
145
137
return {
146
138
data_url : serializeCanvasCallMap ( cDataUrls ) ,
147
139
fingerprinters : Array . from ( fingerprinters ) ,
@@ -150,12 +142,12 @@ export const getCanvasFp = (
150
142
} ;
151
143
} ;
152
144
153
- export const getCanvasFontFp = jsCalls => {
154
- const CANVAS_FONT = [ 'CanvasRenderingContext2D.measureText' , 'CanvasRenderingContext2D.font' ] ;
145
+ export const getCanvasFontFingerprinters = jsCalls => {
155
146
const font_shorthand =
156
147
/ ^ \s * (? = (?: (?: [ - a - z ] + \s * ) { 0 , 2 } ( i t a l i c | o b l i q u e ) ) ? ) (? = (?: (?: [ - a - z ] + \s * ) { 0 , 2 } ( s m a l l - c a p s ) ) ? ) (? = (?: (?: [ - a - z ] + \s * ) { 0 , 2 } ( b o l d (?: e r ) ? | l i g h t e r | [ 1 - 9 ] 0 0 ) ) ? ) (?: (?: n o r m a l | \1| \2| \3) \s * ) { 0 , 3 } ( (?: x x ? - ) ? (?: s m a l l | l a r g e ) | m e d i u m | s m a l l e r | l a r g e r | [ . \d ] + (?: \% | i n | [ c e m ] m | e x | p [ c t x ] ) ) (?: \s * \/ \s * ( n o r m a l | [ . \d ] + (?: \% | i n | [ c e m ] m | e x | p [ c t x ] ) ) ) ? \s * ( [ - _ \{ \} \( \) \& ! \' , \* \. \" \s a - z A - Z 0 - 9 ] + ?) \s * $ / g;
157
148
const textMeasure = new Map ( ) as Map < string , any > ;
158
149
const canvasFont = new Map ( ) as CanvasCallMap ;
150
+
159
151
for ( const item of jsCalls ) {
160
152
const script_url = getScriptUrl ( item ) ;
161
153
const { symbol, value } = item . data ;
0 commit comments