2
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
4
5
- const lazy = { } ;
6
- ChromeUtils . defineLazyGetter ( lazy , "log" , ( ) => {
7
- let { ConsoleAPI } = ChromeUtils . importESModule (
8
- "resource://gre/modules/Console.sys.mjs"
9
- ) ;
10
- let consoleOptions = {
11
- // tip: set maxLogLevel to "debug" and use lazy.log.debug() to create
12
- // detailed messages during development. See LOG_LEVELS in Console.sys.mjs
13
- // for details.
14
- maxLogLevel : "error" ,
15
- maxLogLevelPref : "browser.attribution.mac.loglevel" ,
16
- prefix : "MacAttribution" ,
17
- } ;
18
- return new ConsoleAPI ( consoleOptions ) ;
19
- } ) ;
20
-
21
- ChromeUtils . defineESModuleGetters ( lazy , {
22
- Subprocess : "resource://gre/modules/Subprocess.sys.mjs" ,
23
- } ) ;
24
-
25
- /**
26
- * Get the location of the user's macOS quarantine database.
27
- * @return {String } path.
28
- */
29
- function getQuarantineDatabasePath ( ) {
30
- let file = Services . dirsvc . get ( "Home" , Ci . nsIFile ) ;
31
- file . append ( "Library" ) ;
32
- file . append ( "Preferences" ) ;
33
- file . append ( "com.apple.LaunchServices.QuarantineEventsV2" ) ;
34
- return file . path ;
35
- }
36
-
37
- /**
38
- * Query given path for quarantine extended attributes.
39
- * @param {String } path of the file to query.
40
- * @return {[String, String] } pair of the quarantine data GUID and remaining
41
- * quarantine data (usually, Gatekeeper flags).
42
- * @throws NS_ERROR_NOT_AVAILABLE if there is no quarantine GUID for the given path.
43
- * @throws NS_ERROR_UNEXPECTED if there is a quarantine GUID, but it is malformed.
44
- */
45
- async function getQuarantineAttributes ( path ) {
46
- let bytes = await IOUtils . getMacXAttr ( path , "com.apple.quarantine" ) ;
47
- if ( ! bytes ) {
48
- throw new Components . Exception (
49
- `No macOS quarantine xattrs found for ${ path } ` ,
50
- Cr . NS_ERROR_NOT_AVAILABLE
51
- ) ;
52
- }
53
-
54
- let string = new TextDecoder ( "utf-8" ) . decode ( bytes ) ;
55
- let parts = string . split ( ";" ) ;
56
- if ( ! parts . length ) {
57
- throw new Components . Exception (
58
- `macOS quarantine data is not ; separated` ,
59
- Cr . NS_ERROR_UNEXPECTED
60
- ) ;
61
- }
62
- let guid = parts [ parts . length - 1 ] ;
63
- if ( guid . length != 36 ) {
64
- // Like "12345678-90AB-CDEF-1234-567890ABCDEF".
65
- throw new Components . Exception (
66
- `macOS quarantine data guid is not length 36: ${ guid . length } ` ,
67
- Cr . NS_ERROR_UNEXPECTED
68
- ) ;
69
- }
70
-
71
- return { guid, parts } ;
72
- }
73
-
74
- /**
75
- * Invoke system SQLite binary to extract the referrer URL corresponding to
76
- * the given GUID from the given macOS quarantine database.
77
- * @param {String } path of the user's macOS quarantine database.
78
- * @param {String } guid to query.
79
- * @return {String } referrer URL.
80
- */
81
- async function queryQuarantineDatabase (
82
- guid ,
83
- path = getQuarantineDatabasePath ( )
84
- ) {
85
- let query = `SELECT COUNT(*), LSQuarantineOriginURLString
86
- FROM LSQuarantineEvent
87
- WHERE LSQuarantineEventIdentifier = '${ guid } '
88
- ORDER BY LSQuarantineTimeStamp DESC LIMIT 1` ;
89
-
90
- let proc = await lazy . Subprocess . call ( {
91
- command : "/usr/bin/sqlite3" ,
92
- arguments : [ path , query ] ,
93
- environment : { } ,
94
- stderr : "stdout" ,
95
- } ) ;
96
-
97
- let stdout = await proc . stdout . readString ( ) ;
98
-
99
- let { exitCode } = await proc . wait ( ) ;
100
- if ( exitCode != 0 ) {
101
- throw new Components . Exception (
102
- "Failed to run sqlite3" ,
103
- Cr . NS_ERROR_UNEXPECTED
104
- ) ;
105
- }
106
-
107
- // Output is like "integer|url".
108
- let parts = stdout . split ( "|" , 2 ) ;
109
- if ( parts . length != 2 ) {
110
- throw new Components . Exception (
111
- "Failed to parse sqlite3 output" ,
112
- Cr . NS_ERROR_UNEXPECTED
113
- ) ;
114
- }
115
-
116
- if ( parts [ 0 ] . trim ( ) == "0" ) {
117
- throw new Components . Exception (
118
- `Quarantine database does not contain URL for guid ${ guid } ` ,
119
- Cr . NS_ERROR_UNEXPECTED
120
- ) ;
121
- }
122
-
123
- return parts [ 1 ] . trim ( ) ;
124
- }
5
+ const NUL = 0x0 ;
6
+ const TAB = 0x9 ;
125
7
126
8
export var MacAttribution = {
127
9
/**
@@ -132,44 +14,36 @@ export var MacAttribution = {
132
14
return Services . dirsvc . get ( "GreD" , Ci . nsIFile ) . parent . parent . path ;
133
15
} ,
134
16
135
- /**
136
- * Used by the Attributions system to get the download referrer.
137
- *
138
- * @param {String } path to get the quarantine data from.
139
- * Usually this is a `.app` directory but can be any
140
- * (existing) file or directory. Default: `this.applicationPath`.
141
- * @return {String } referrer URL.
142
- * @throws NS_ERROR_NOT_AVAILABLE if there is no quarantine GUID for the given path.
143
- * @throws NS_ERROR_UNEXPECTED if there is a quarantine GUID, but no corresponding referrer URL is known.
144
- */
145
- async getReferrerUrl ( path = this . applicationPath ) {
146
- lazy . log . debug ( `getReferrerUrl(${ JSON . stringify ( path ) } )` ) ;
17
+ async setAttributionString ( aAttrStr , path = this . applicationPath ) {
18
+ return IOUtils . setMacXAttr (
19
+ path ,
20
+ "com.apple.application-instance" ,
21
+ new TextEncoder ( ) . encode ( aAttrStr )
22
+ ) ;
23
+ } ,
147
24
148
- // First, determine the quarantine GUID assigned by macOS to the given path.
149
- let guid ;
150
- try {
151
- guid = ( await getQuarantineAttributes ( path ) ) . guid ;
152
- } catch ( ex ) {
153
- throw new Components . Exception (
154
- `No macOS quarantine GUID found for ${ path } ` ,
155
- Cr . NS_ERROR_NOT_AVAILABLE
25
+ async getAttributionString ( path = this . applicationPath ) {
26
+ let promise = IOUtils . getMacXAttr ( path , "com.apple.application-instance" ) ;
27
+ return promise . then ( bytes => {
28
+ // We need to process the extended attribute a little bit to isolate
29
+ // the attribution string:
30
+ // - nul bytes and tabs may be present in raw attribution strings, but are
31
+ // never part of the attribution data
32
+ // - attribution data is expected to be preceeded by the string `__MOZCUSTOM__`
33
+ let attrStr = new TextDecoder ( ) . decode (
34
+ bytes . filter ( b => b != NUL && b != TAB )
156
35
) ;
157
- }
158
- lazy . log . debug ( `getReferrerUrl: guid: ${ guid } ` ) ;
159
36
160
- // Second, fish the relevant record from the quarantine database.
161
- let url = "" ;
162
- try {
163
- url = await queryQuarantineDatabase ( guid ) ;
164
- lazy . log . debug ( `getReferrerUrl: url: ${ url } ` ) ;
165
- } catch ( ex ) {
166
- // This path is known to macOS but we failed to extract a referrer -- be noisy.
167
- throw new Components . Exception (
168
- `No macOS quarantine referrer URL found for ${ path } with GUID ${ guid } ` ,
169
- Cr . NS_ERROR_UNEXPECTED
170
- ) ;
171
- }
37
+ if ( attrStr . startsWith ( "__MOZCUSTOM__" ) ) {
38
+ // Return everything after __MOZCUSTOM__
39
+ return attrStr . slice ( 13 ) ;
40
+ }
41
+
42
+ throw new Error ( `No attribution data found in ${ path } ` ) ;
43
+ } ) ;
44
+ } ,
172
45
173
- return url ;
46
+ async delAttributionString ( path = this . applicationPath ) {
47
+ return IOUtils . delMacXAttr ( path , "com.apple.application-instance" ) ;
174
48
} ,
175
49
} ;
0 commit comments