Skip to content

Commit fe4dc02

Browse files
fix: open redirect_to URL in browser to prevent infinite deep link loop (#22648) (#22665)
* fix: open redirect_to URL in browser to prevent infinite deep link loop When a tracking URL's redirect_to target can't be handled in-app, open the extracted redirect_to URL in the browser instead of the original tracking URL. This prevents an infinite loop caused by the app's intent filter re-intercepting its own public-api.wordpress.com/bar/ URLs. * fix: fire server tracking pixel when redirect_to opens in browser When a tracking URL's redirect_to target can't be handled in-app, the app now opens the redirect destination in the browser (loop fix) but the server never sees the original tracking URL. Fire the pixel explicitly in this path so email link taps are still recorded. The no-redirect fallback still opens /bar/ in the browser, which handles tracking implicitly via the browser's HTTP request. Co-authored-by: David Calhoun <github@davidcalhoun.me>
1 parent 3757530 commit fe4dc02

2 files changed

Lines changed: 37 additions & 10 deletions

File tree

WordPress/src/main/java/org/wordpress/android/ui/deeplinks/DeepLinkingIntentReceiverViewModel.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,28 @@ class DeepLinkingIntentReceiverViewModel
131131

132132
private fun buildNavigateAction(uri: UriWrapper, rootUri: UriWrapper = uri): NavigateAction? {
133133
return when {
134-
deepLinkUriUtils.isTrackingUrl(uri) -> getRedirectUriAndBuildNavigateAction(uri, rootUri)
135-
?.also {
136-
// The new URL was build so we need to hit the original `mbar` tracking URL
134+
deepLinkUriUtils.isTrackingUrl(uri) -> {
135+
val redirectUri = deepLinkUriUtils.getRedirectUri(uri)
136+
val navigateAction = redirectUri?.let { buildNavigateAction(it, rootUri) }
137+
if (navigateAction != null) {
138+
// Handled in-app — fire the tracking pixel since the
139+
// server won't see the request otherwise.
137140
serverTrackingHandler.request(uri)
141+
navigateAction
142+
} else if (redirectUri != null) {
143+
// Can't handle in-app. Open the redirect destination
144+
// (not the tracking URL) to avoid an infinite loop with
145+
// the app's intent filter. Fire the tracking pixel
146+
// explicitly since the browser won't hit the tracking URL.
147+
serverTrackingHandler.request(uri)
148+
OpenInBrowser(redirectUri)
149+
} else {
150+
// No redirect_to param — open the /bar/ tracking URL in
151+
// the browser. The browser request itself will handle
152+
// server-side tracking, so no explicit pixel needed.
153+
OpenInBrowser(rootUri.copy(REGULAR_TRACKING_PATH))
138154
}
139-
?: OpenInBrowser(rootUri.copy(REGULAR_TRACKING_PATH))
155+
}
140156
deepLinkUriUtils.isWpLoginUrl(uri) -> getRedirectUriAndBuildNavigateAction(uri, rootUri)
141157
else -> deepLinkHandlers.buildNavigateAction(uri)
142158
}

WordPress/src/test/java/org/wordpress/android/ui/deeplinks/DeepLinkingIntentReceiverViewModelTest.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,37 @@ class DeepLinkingIntentReceiverViewModelTest : BaseUnitTest() {
137137
val startUrl = mock<UriWrapper>()
138138
val wpLoginUri = initWpLoginUri(startUrl)
139139
val uri = initTrackingUri(wpLoginUri)
140-
val barUri = buildUri("public-api.wordpress.com")
141140

142141
whenever(deepLinkHandlers.buildNavigateAction(startUrl)).thenReturn(null)
143-
whenever(uri.copy("bar")).thenReturn(barUri)
144142

145143
viewModel.start(null, uri, DEFAULT, null)
146144

147-
assertUriHandled(OpenInBrowser(barUri))
145+
assertUriHandled(OpenInBrowser(wpLoginUri))
146+
verify(serverTrackingHandler).request(uri)
147+
}
148+
149+
@Test
150+
fun `tracking URL with unhandled direct redirect opens redirect URL in browser`() {
151+
val redirectUri = buildUri("wordpress.com")
152+
val uri = initTrackingUri(redirectUri)
153+
154+
whenever(deepLinkHandlers.buildNavigateAction(redirectUri)).thenReturn(null)
155+
156+
viewModel.start(null, uri, DEFAULT, null)
157+
158+
assertUriHandled(OpenInBrowser(redirectUri))
159+
verify(serverTrackingHandler).request(uri)
148160
}
149161

150162
@Test
151163
fun `wp-login mbar URL redirects user to browser with missing second redirect`() {
152164
val wpLoginUri = initWpLoginUri()
153165
val uri = initTrackingUri(wpLoginUri)
154-
val barUri = buildUri("public-api.wordpress.com")
155-
whenever(uri.copy("bar")).thenReturn(barUri)
156166

157167
viewModel.start(null, uri, DEFAULT, null)
158168

159-
assertUriHandled(OpenInBrowser(barUri))
169+
assertUriHandled(OpenInBrowser(wpLoginUri))
170+
verify(serverTrackingHandler).request(uri)
160171
}
161172

162173
@Test

0 commit comments

Comments
 (0)