Bug 1832074 — Parse referrer URL with URLDecoder

fenix/115.2.0
James Hugman 1 year ago committed by mergify[bot]
parent 104b4d0264
commit 29aa1399da

@ -13,6 +13,7 @@ import com.android.installreferrer.api.InstallReferrerStateListener
import org.mozilla.fenix.GleanMetrics.PlayStoreAttribution
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
import java.net.URLDecoder
/**
* A metrics service used to derive the UTM parameters with the Google Play Install Referrer library.
@ -83,7 +84,10 @@ class InstallReferrerMetricsService(private val context: Context) : MetricsServi
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun recordInstallReferrer(settings: Settings, url: String?) {
val params = url?.let(UTMParams::fromUrl)
if (url.isNullOrBlank()) {
return
}
val params = UTMParams.fromURLString(url)
if (params == null || params.isEmpty()) {
return
}
@ -143,14 +147,40 @@ data class UTMParams(
const val UTM_TERM = "utm_term"
const val UTM_CONTENT = "utm_content"
/**
* Try and unpack the referrer URL by successively URLDecoding the URL.
*
* Once the url ceases to decode anymore, it gives up.
*/
fun fromURLString(string: String): UTMParams? {
// Look for the first time 'utm_' is detected, after the first '?'.
val utmIndex = string.indexOf("utm_", string.indexOf('?'))
if (utmIndex < 0) {
return null
}
var url = string.substring(utmIndex)
while (true) {
val params = fromQueryString(url)
if (!params.isEmpty()) {
return params
}
val newValue = URLDecoder.decode(url, "UTF-8")
if (newValue == url) {
break
}
url = newValue
}
return null
}
/**
* Derive a set of UTM parameters from a string URL.
*/
fun fromUrl(url: String): UTMParams =
fun fromQueryString(queryString: String): UTMParams =
with(UrlQuerySanitizer()) {
allowUnregisteredParamaters = true
unregisteredParameterValueSanitizer = UrlQuerySanitizer.getUrlAndSpaceLegal()
parseUrl(url)
parseQuery(queryString)
UTMParams(
source = getValue(UTM_SOURCE),
medium = getValue(UTM_MEDIUM),

@ -10,6 +10,7 @@ import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Rule
@ -28,24 +29,24 @@ internal class InstallReferrerMetricsServiceTest {
@Test
fun testUtmParamsFromUrl() {
assertEquals("SOURCE", UTMParams.fromUrl("https://example.com?utm_source=SOURCE").source)
assertEquals("MEDIUM", UTMParams.fromUrl("https://example.com?utm_medium=MEDIUM").medium)
assertEquals("CAMPAIGN", UTMParams.fromUrl("https://example.com?utm_campaign=CAMPAIGN").campaign)
assertEquals("TERM", UTMParams.fromUrl("https://example.com?utm_term=TERM").term)
assertEquals("CONTENT", UTMParams.fromUrl("https://example.com?utm_content=CONTENT").content)
assertEquals("SOURCE", UTMParams.fromQueryString("utm_source=SOURCE").source)
assertEquals("MEDIUM", UTMParams.fromQueryString("utm_medium=MEDIUM").medium)
assertEquals("CAMPAIGN", UTMParams.fromQueryString("utm_campaign=CAMPAIGN").campaign)
assertEquals("TERM", UTMParams.fromQueryString("utm_term=TERM").term)
assertEquals("CONTENT", UTMParams.fromQueryString("utm_content=CONTENT").content)
}
@Test
fun testUtmParamsFromUrlWithSpaces() {
assertEquals("WITH SPACE", UTMParams.fromUrl("https://example.com?utm_source=WITH+SPACE").source)
assertEquals("WITH SPACE", UTMParams.fromUrl("https://example.com?utm_medium=WITH%20SPACE").medium)
assertEquals("WITH SPACE", UTMParams.fromUrl("https://example.com?utm_campaign=WITH SPACE").campaign)
assertEquals("WITH SPACE", UTMParams.fromQueryString("utm_source=WITH+SPACE").source)
assertEquals("WITH SPACE", UTMParams.fromQueryString("utm_medium=WITH%20SPACE").medium)
assertEquals("WITH SPACE", UTMParams.fromQueryString("utm_campaign=WITH SPACE").campaign)
}
@Test
fun testUtmParamsFromUrlWithMissingParams() {
assertNull(UTMParams.fromUrl("https://example.com?missing=").source)
assertEquals("", UTMParams.fromUrl("https://example.com?utm_source=").source)
assertNull(UTMParams.fromQueryString("missing=").source)
assertEquals("", UTMParams.fromQueryString("utm_source=").source)
}
@Test
@ -117,7 +118,7 @@ internal class InstallReferrerMetricsServiceTest {
val expected = UTMParams(source = "SOURCE", medium = "MEDIUM", campaign = "CAMPAIGN", content = "CONTENT", term = "TERM")
val observed = UTMParams.fromSettings(settings)
assertEquals(observed, expected)
assertEquals(expected, observed)
assertEquals("SOURCE", PlayStoreAttribution.source.testGetValue())
assertEquals("MEDIUM", PlayStoreAttribution.medium.testGetValue())
@ -139,4 +140,43 @@ internal class InstallReferrerMetricsServiceTest {
val service = InstallReferrerMetricsService(context)
assertEquals(MetricServiceType.Marketing, service.type)
}
@Test
fun testDecodeReferrerUrl() {
// Example from https://developers.google.com/analytics/devguides/collection/android/v4/campaigns#google-play-url-builder
val params = UTMParams.fromURLString(
"https://play.google.com/store/apps/details?id=com.example.application" +
"&referrer=utm_source%3Dgoogle" +
"%26utm_medium%3Dcpc" +
"%26utm_term%3Drunning%252Bshoes" +
"%26utm_content%3Dlogolink" +
"%26utm_campaign%3Dspring_sale",
)
assertNotNull(params)
val expected = UTMParams(
source = "google",
medium = "cpc",
campaign = "spring_sale",
content = "logolink",
term = "running+shoes",
)
assertEquals(expected, params)
}
@Test
fun testDecodeReferrerAdMobUrl() {
val expected = UTMParams(source = "SOURCE", medium = "MEDIUM", campaign = "CAMPAIGN", content = "CONTENT", term = "TERM")
// Generated with https://developers.google.com/analytics/devguides/collection/android/v4/campaigns#google-play-url-builder
val fromUrl = UTMParams.fromURLString(
"https://play.google.com/store/apps/details?id=org.mozilla.fenix&referrer=utm_source%3DSOURCE%26utm_medium%3DMEDIUM%26utm_term%3DTERM%26utm_content%3DCONTENT%26utm_campaign%3DCAMPAIGN%26anid%3Dadmob",
)
assertNotNull(fromUrl)
assertEquals(expected, fromUrl)
val fromReferrerAttribute = UTMParams.fromURLString("utm_source%3DSOURCE%26utm_medium%3DMEDIUM%26utm_term%3DTERM%26utm_content%3DCONTENT%26utm_campaign%3DCAMPAIGN%26anid%3Dadmob")
assertNotNull(fromReferrerAttribute)
assertEquals(expected, fromReferrerAttribute)
}
}

Loading…
Cancel
Save