Commit 7962eaca authored by Shane Caraveo's avatar Shane Caraveo
Browse files

Bug 1344771 - Implement attribution on OSX using quarantine data, r=mossop,spohl

MozReview-Commit-ID: NgjE1HZS7M

--HG--
rename : browser/modules/AttributionCode.jsm => browser/components/attribution/AttributionCode.jsm
rename : browser/modules/test/unit/test_AttributionCode.js => browser/components/attribution/test/xpcshell/test_AttributionCode.js
extra : rebase_source : 197f427e0d2fcbdbe85d5ebe65067dcc9eee6407
parent 5d9f279d
......@@ -12,12 +12,14 @@ ChromeUtils.defineModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
const ATTR_CODE_MAX_LENGTH = 200;
const ATTR_CODE_KEYS_REGEX = /^source|medium|campaign|content$/;
const ATTR_CODE_VALUE_REGEX = /[a-zA-Z0-9_%\\-\\.\\(\\)]*/;
const ATTR_CODE_FIELD_SEPARATOR = "%26"; // URL-encoded &
const ATTR_CODE_KEY_VALUE_SEPARATOR = "%3D"; // URL-encoded =
const ATTR_CODE_KEYS = ["source", "medium", "campaign", "content"];
let gCachedAttrData = null;
......@@ -65,6 +67,10 @@ var AttributionCode = {
* Returns a promise that fulfills with an object containing the parsed
* attribution data if the code could be read and is valid,
* or an empty object otherwise.
*
* On windows the attribution service converts utm_* keys, removing "utm_".
* On OSX the attributions are set directly on download and retain "utm_". We
* strip "utm_" while retrieving the params.
*/
getAttrDataAsync() {
return (async function() {
......@@ -72,18 +78,38 @@ var AttributionCode = {
return gCachedAttrData;
}
let code = "";
try {
let bytes = await OS.File.read(getAttributionFile().path);
let decoder = new TextDecoder();
code = decoder.decode(bytes);
} catch (ex) {
// The attribution file may already have been deleted,
// or it may have never been installed at all;
// failure to open or read it isn't an error.
gCachedAttrData = {};
if (AppConstants.platform == "win") {
try {
let bytes = await OS.File.read(getAttributionFile().path);
let decoder = new TextDecoder();
let code = decoder.decode(bytes);
gCachedAttrData = parseAttributionCode(code);
} catch (ex) {
// The attribution file may already have been deleted,
// or it may have never been installed at all;
// failure to open or read it isn't an error.
}
} else if (AppConstants.platform == "macosx") {
try {
let appPath = Services.dirsvc.get("GreD", Ci.nsIFile).parent.parent.path;
let attributionSvc = Cc["@mozilla.org/mac-attribution;1"]
.getService(Ci.nsIMacAttributionService);
let referrer = attributionSvc.getReferrerUrl(appPath);
let params = new URL(referrer).searchParams;
for (let key of ATTR_CODE_KEYS) {
let utm_key = `utm_${key}`;
if (params.has(utm_key)) {
let value = params.get(utm_key);
if (value && ATTR_CODE_VALUE_REGEX.test(value)) {
gCachedAttrData[key] = value;
}
}
}
} catch (ex) {
// No attributions
}
}
gCachedAttrData = parseAttributionCode(code);
return gCachedAttrData;
})();
},
......
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Toolkit", "Telemetry")
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
EXTRA_JS_MODULES += [
'AttributionCode.jsm',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
XPIDL_SOURCES += [
'nsIMacAttribution.idl',
]
XPIDL_MODULE = 'attribution'
EXPORTS += [
'nsMacAttribution.h',
]
SOURCES += [
'nsMacAttribution.cpp',
]
FINAL_LIBRARY = 'browsercomps'
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, uuid(6FC66A78-6CBC-4B3F-B7BA-379289B29276)]
interface nsIMacAttributionService : nsISupports
{
/**
* Used by the Attributions system to get the download referrer.
*
* @param aFilePath A path to the file to get the quarantine data from.
* @returns referrerUrl
*/
AString getReferrerUrl(in ACString aFilePath);
/**
* Used by the tests.
*
* @param aFilePath A path to the file to set the quarantine data on.
* @param aReferrer A url to set as the referrer for the download.
* @param aCreate If true, creates new quarantine properties, overwriting
* any existing properties. If false, the referrer is only
* set if quarantine properties already exist on the file.
*/
void setReferrerUrl(in ACString aFilePath,
in ACString aReferrer,
in boolean aCreate);
};
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsMacAttribution.h"
#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include "../../../xpcom/io/CocoaFileUtils.h"
#include "nsCocoaFeatures.h"
#include "nsString.h"
using namespace mozilla;
NS_IMPL_ISUPPORTS(nsMacAttributionService, nsIMacAttributionService)
NS_IMETHODIMP
nsMacAttributionService::GetReferrerUrl(const nsACString& aFilePath,
nsAString& aReferrer)
{
const nsCString& flat = PromiseFlatCString(aFilePath);
CFStringRef filePath = ::CFStringCreateWithCString(kCFAllocatorDefault,
flat.get(),
kCFStringEncodingUTF8);
CocoaFileUtils::CopyQuarantineReferrerUrl(filePath, aReferrer);
::CFRelease(filePath);
return NS_OK;
}
NS_IMETHODIMP
nsMacAttributionService::SetReferrerUrl(const nsACString& aFilePath,
const nsACString& aReferrerUrl,
const bool aCreate)
{
const nsCString& flat = PromiseFlatCString(aFilePath);
CFStringRef filePath = ::CFStringCreateWithCString(kCFAllocatorDefault,
flat.get(),
kCFStringEncodingUTF8);
if (!filePath) {
return NS_ERROR_UNEXPECTED;
}
const nsCString& flatReferrer = PromiseFlatCString(aReferrerUrl);
CFStringRef referrer = ::CFStringCreateWithCString(kCFAllocatorDefault,
flatReferrer.get(),
kCFStringEncodingUTF8);
if (!referrer) {
::CFRelease(filePath);
return NS_ERROR_UNEXPECTED;
}
CFURLRef referrerURL = ::CFURLCreateWithString(kCFAllocatorDefault,
referrer, nullptr);
CocoaFileUtils::AddQuarantineMetadataToFile(filePath,
NULL,
referrerURL,
true,
aCreate);
::CFRelease(filePath);
::CFRelease(referrer);
::CFRelease(referrerURL);
return NS_OK;
}
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsmacattribution_h____
#define nsmacattribution_h____
#include "nsIMacAttribution.h"
class nsMacAttributionService : public nsIMacAttributionService
{
public:
nsMacAttributionService() {};
NS_DECL_ISUPPORTS
NS_DECL_NSIMACATTRIBUTIONSERVICE
protected:
virtual ~nsMacAttributionService() {};
};
#endif // nsmacattribution_h____
"use strict";
module.exports = {
"extends": "plugin:mozilla/xpcshell-test",
};
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
ChromeUtils.import("resource://gre/modules/Services.jsm");
add_task(async function test_attribution() {
let appPath = Services.dirsvc.get("GreD", Ci.nsIFile).parent.parent.path;
let attributionSvc = Cc["@mozilla.org/mac-attribution;1"]
.getService(Ci.nsIMacAttributionService);
attributionSvc.setReferrerUrl(appPath, "", true);
let referrer = attributionSvc.getReferrerUrl(appPath);
equal(referrer, "", "force an empty referrer url");
// Set a url referrer
let url = "http://example.com";
attributionSvc.setReferrerUrl(appPath, url, true);
referrer = attributionSvc.getReferrerUrl(appPath);
equal(referrer, url, "overwrite referrer url");
// Does not overwrite existing properties.
attributionSvc.setReferrerUrl(appPath, "http://test.com", false);
referrer = attributionSvc.getReferrerUrl(appPath);
equal(referrer, url, "referrer url is not changed");
});
[DEFAULT]
firefox-appdir = browser
skip-if = toolkit == 'android'
[test_AttributionCode.js]
skip-if = os != 'win' # windows specific tests
[test_attribution.js]
skip-if = toolkit != "cocoa" # osx specific tests
......@@ -41,3 +41,11 @@
// {6DEB193C-F87D-4078-BC78-5E64655B4D62}
#define NS_BROWSERDIRECTORYPROVIDER_CID \
{ 0x6deb193c, 0xf87d, 0x4078, { 0xbc, 0x78, 0x5e, 0x64, 0x65, 0x5b, 0x4d, 0x62 } }
#if defined(MOZ_WIDGET_COCOA)
#define NS_MACATTRIBUTIONSERVICE_CONTRACTID \
"@mozilla.org/mac-attribution;1"
#define NS_MACATTRIBUTIONSERVICE_CID \
{ 0x6FC66A78, 0x6CBC, 0x4B3F, { 0xB7, 0xBA, 0x37, 0x92, 0x89, 0xB2, 0x92, 0x76 } }
#endif
......@@ -16,6 +16,10 @@
#include "nsGNOMEShellService.h"
#endif
#if defined(MOZ_WIDGET_COCOA)
#include "nsMacAttribution.h"
#endif
#if defined(XP_WIN)
#include "nsIEHistoryEnumerator.h"
#endif
......@@ -39,6 +43,10 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacShellService)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
#endif
#if defined(MOZ_WIDGET_COCOA)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacAttributionService)
#endif
#if defined(XP_WIN)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsIEHistoryEnumerator)
#endif
......@@ -58,6 +66,9 @@ NS_DEFINE_NAMED_CID(NS_WINIEHISTORYENUMERATOR_CID);
#elif defined(XP_MACOSX)
NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
#endif
#if defined(MOZ_WIDGET_COCOA)
NS_DEFINE_NAMED_CID(NS_MACATTRIBUTIONSERVICE_CID);
#endif
static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
{ &kNS_BROWSERDIRECTORYPROVIDER_CID, false, nullptr, DirectoryProviderConstructor },
......@@ -72,6 +83,9 @@ static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
{ &kNS_WINIEHISTORYENUMERATOR_CID, false, nullptr, nsIEHistoryEnumeratorConstructor },
#elif defined(XP_MACOSX)
{ &kNS_SHELLSERVICE_CID, false, nullptr, nsMacShellServiceConstructor },
#endif
#if defined(MOZ_WIDGET_COCOA)
{ &kNS_MACATTRIBUTIONSERVICE_CID, false, nullptr, nsMacAttributionServiceConstructor },
#endif
{ nullptr }
};
......@@ -107,6 +121,9 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
#elif defined(XP_MACOSX)
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#endif
#if defined(MOZ_WIDGET_COCOA)
{ NS_MACATTRIBUTIONSERVICE_CONTRACTID, &kNS_MACATTRIBUTIONSERVICE_CID },
#endif
{ nullptr }
};
......
......@@ -34,6 +34,7 @@ with Files('controlcenter/**'):
DIRS += [
'about',
'attribution',
'contextualidentity',
'customizableui',
'dirprovider',
......
......@@ -28,9 +28,6 @@ with Files("test/browser/browser_taskbar_preview.js"):
with Files("test/browser/browser_urlBar_zoom.js"):
BUG_COMPONENT = ("Firefox", "General")
with Files("test/unit/test_AttributionCode.js"):
BUG_COMPONENT = ("Toolkit", "Telemetry")
with Files("test/unit/test_E10SUtils_nested_URIs.js"):
BUG_COMPONENT = ("Core", "Security: Process Sandboxing")
......@@ -46,9 +43,6 @@ with Files("AboutNewTab.jsm"):
with Files('AsyncTabSwitcher.jsm'):
BUG_COMPONENT = ('Firefox', 'Tabbed Browser')
with Files("AttributionCode.jsm"):
BUG_COMPONENT = ("Toolkit", "Telemetry")
with Files("BrowserWindowTracker.jsm"):
BUG_COMPONENT = ("Core", "Networking")
......@@ -140,7 +134,6 @@ XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
EXTRA_JS_MODULES += [
'AboutNewTab.jsm',
'AsyncTabSwitcher.jsm',
'AttributionCode.jsm',
'BlockedSiteContent.jsm',
'BrowserErrorReporter.jsm',
'BrowserUsageTelemetry.jsm',
......
......@@ -3,8 +3,6 @@ head =
firefox-appdir = browser
skip-if = toolkit == 'android'
[test_AttributionCode.js]
skip-if = os != 'win'
[test_E10SUtils_nested_URIs.js]
[test_HomePage.js]
[test_Sanitizer_interrupted.js]
......
......@@ -11,6 +11,7 @@
#define CocoaFileUtils_h_
#include "nscore.h"
#include "nsString.h"
#include <CoreFoundation/CoreFoundation.h>
namespace CocoaFileUtils {
......@@ -27,7 +28,10 @@ void AddOriginMetadataToFile(const CFStringRef filePath,
void AddQuarantineMetadataToFile(const CFStringRef filePath,
const CFURLRef sourceURL,
const CFURLRef referrerURL,
const bool isFromWeb);
const bool isFromWeb,
const bool createProps=false);
void CopyQuarantineReferrerUrl(const CFStringRef aFilePath,
nsAString& aReferrer);
CFURLRef GetTemporaryFolderCFURLRef();
} // namespace CocoaFileUtils
......
......@@ -10,6 +10,8 @@
#include <Cocoa/Cocoa.h>
#include "nsObjCExceptions.h"
#include "nsDebug.h"
#include "nsString.h"
#include "mozilla/MacStringHelpers.h"
// Need to cope with us using old versions of the SDK and needing this on 10.10+
#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
......@@ -191,65 +193,90 @@ void AddOriginMetadataToFile(const CFStringRef filePath,
::CFRelease(mdItem);
}
void AddQuarantineMetadataToFile(const CFStringRef filePath,
const CFURLRef sourceURL,
const CFURLRef referrerURL,
const bool isFromWeb) {
CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
filePath,
kCFURLPOSIXPathStyle,
false);
// The properties key changed in 10.10:
CFStringRef quarantinePropKey;
CFStringRef GetQuarantinePropKey() {
if (nsCocoaFeatures::OnYosemiteOrLater()) {
quarantinePropKey = kCFURLQuarantinePropertiesKey;
} else {
quarantinePropKey = kLSItemQuarantineProperties;
return kCFURLQuarantinePropertiesKey;
}
return kLSItemQuarantineProperties;
}
CFMutableDictionaryRef CreateQuarantineDictionary(const CFURLRef aFileURL,
const bool aCreateProps) {
// The properties key changed in 10.10:
CFDictionaryRef quarantineProps = NULL;
Boolean success = ::CFURLCopyResourcePropertyForKey(fileURL,
quarantinePropKey,
&quarantineProps,
NULL);
// If there aren't any quarantine properties then the user probably
// set up an exclusion and we don't need to add metadata.
if (!success || !quarantineProps) {
::CFRelease(fileURL);
return;
if (aCreateProps) {
quarantineProps = ::CFDictionaryCreate(NULL, NULL, NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
} else {
Boolean success = ::CFURLCopyResourcePropertyForKey(aFileURL,
GetQuarantinePropKey(),
&quarantineProps,
NULL);
// If there aren't any quarantine properties then the user probably
// set up an exclusion and we don't need to add metadata.
if (!success || !quarantineProps) {
return NULL;
}
}
// We don't know what to do if the props aren't a dictionary.
if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) {
::CFRelease(fileURL);
::CFRelease(quarantineProps);
return;
return NULL;
}
// Make a mutable copy of the properties.
CFMutableDictionaryRef mutQuarantineProps =
::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps);
::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0,
(CFDictionaryRef)quarantineProps);
::CFRelease(quarantineProps);
return mutQuarantineProps;
}
void AddQuarantineMetadataToFile(const CFStringRef filePath,
const CFURLRef sourceURL,
const CFURLRef referrerURL,
const bool isFromWeb,
const bool createProps /* = false */) {
CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
filePath,
kCFURLPOSIXPathStyle,
false);
CFMutableDictionaryRef mutQuarantineProps =
CreateQuarantineDictionary(fileURL, createProps);
if (!mutQuarantineProps) {
::CFRelease(fileURL);
return;
}
// Add metadata that the OS couldn't infer.
if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineTypeKey)) {
CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload : kLSQuarantineTypeOtherDownload;
CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload :
kLSQuarantineTypeOtherDownload;
::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineTypeKey, type);
}
if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) && referrerURL) {
::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineOriginURLKey, referrerURL);
if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) &&
referrerURL) {
::CFDictionarySetValue(mutQuarantineProps,
kLSQuarantineOriginURLKey,
referrerURL);
}
if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) && sourceURL) {
::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineDataURLKey, sourceURL);
if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) &&
sourceURL) {
::CFDictionarySetValue(mutQuarantineProps,
kLSQuarantineDataURLKey,
sourceURL);
}
// Set quarantine properties on file.
::CFURLSetResourcePropertyForKey(fileURL,
quarantinePropKey,
GetQuarantinePropKey(),
mutQuarantineProps,
NULL);
......@@ -257,6 +284,33 @@ void AddQuarantineMetadataToFile(const CFStringRef filePath,
::CFRelease(mutQuarantineProps);
}
void CopyQuarantineReferrerUrl(const CFStringRef aFilePath,
nsAString& aReferrer)
{
CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
aFilePath,
kCFURLPOSIXPathStyle,
false);
CFMutableDictionaryRef mutQuarantineProps =
CreateQuarantineDictionary(fileURL, false);
::CFRelease(fileURL);
if (!mutQuarantineProps) {
return;
}
CFTypeRef referrerRef = ::CFDictionaryGetValue(mutQuarantineProps,
kLSQuarantineOriginURLKey);
if (referrerRef && ::CFGetTypeID(referrerRef) == ::CFURLGetTypeID()) {
// URL string must be copied prior to releasing the dictionary.
mozilla::CopyCocoaStringToXPCOMString(
(NSString*)::CFURLGetString(static_cast<CFURLRef>(referrerRef)),
aReferrer);
}
::CFRelease(mutQuarantineProps);
}
CFURLRef GetTemporaryFolderCFURLRef()
{
NSString* tempDir = ::NSTemporaryDirectory();
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment