diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index d269ddb5e..22168f5b2 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -518,11 +518,8 @@ class GutenbergView : FrameLayout { } // Allow local development server if configured - if (BuildConfig.GUTENBERG_EDITOR_URL.isNotEmpty()) { - val editorUrl = Uri.parse(BuildConfig.GUTENBERG_EDITOR_URL) - if (url.host == editorUrl.host) { - return false - } + if (isDevServerUrl(url, BuildConfig.GUTENBERG_EDITOR_URL)) { + return false } // For all other URLs, open in external browser @@ -1400,6 +1397,19 @@ class GutenbergView : FrameLayout { return if (uri.port != -1 && uri.port != defaultPort) "$host:${uri.port}" else host } + /** + * Whether [url] is on the local development server at [editorUrl]. Compares + * host and port so another port on the same host, such as a local WordPress + * site beside the dev server, isn't treated as the dev server. + * + * Returns false when [editorUrl] has no host (unset or missing a scheme), so + * host-less URLs like `mailto:` never match it. + */ + internal fun isDevServerUrl(url: Uri, editorUrl: String): Boolean { + val devServerAuthority = originAuthority(editorUrl) ?: return false + return url.authority == devServerAuthority + } + private const val ASSET_LOADING_TIMEOUT_MS = 5000L /** diff --git a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt index f4c283653..07fe1668c 100644 --- a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt +++ b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt @@ -292,6 +292,48 @@ class GutenbergViewTest { assertEquals(null, GutenbergView.originAuthority("not a url")) } + // ===== isDevServerUrl ===== + + @Test + fun `isDevServerUrl matches the dev server's host and port`() { + assertTrue( + GutenbergView.isDevServerUrl( + Uri.parse("http://10.0.2.2:5173/src/index.html"), + "http://10.0.2.2:5173/" + ) + ) + } + + @Test + fun `isDevServerUrl rejects another port on the dev server's host`() { + assertFalse( + GutenbergView.isDevServerUrl(Uri.parse("http://10.0.2.2:8888/"), "http://10.0.2.2:5173/") + ) + assertFalse( + GutenbergView.isDevServerUrl(Uri.parse("http://10.0.2.2/"), "http://10.0.2.2:5173/") + ) + } + + @Test + fun `isDevServerUrl matches a dev server URL written with its default port`() { + // Chromium drops a default port before the URL reaches the WebViewClient. + assertTrue( + GutenbergView.isDevServerUrl(Uri.parse("http://localhost/"), "http://localhost:80/") + ) + } + + @Test + fun `isDevServerUrl rejects every URL when no dev server is configured`() { + assertFalse(GutenbergView.isDevServerUrl(Uri.parse("http://localhost:5173/"), "")) + } + + @Test + fun `isDevServerUrl rejects host-less URLs when the dev server URL has no host`() { + // Without a scheme, the dev server URL has no authority, and neither do these. + assertFalse(GutenbergView.isDevServerUrl(Uri.parse("mailto:a@example.com"), "10.0.2.2:5173")) + assertFalse(GutenbergView.isDevServerUrl(Uri.parse("tel:5551234"), "10.0.2.2:5173")) + } + // ===== REST API navigation ===== @Test diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 454b87067..c693855b1 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -6,7 +6,7 @@ if (localPropertiesFile.exists()) { } ext { - set("gutenbergEditorUrl", localProperties.getProperty("GUTENBERG_EDITOR_URL") ?: "") + set("gutenbergEditorUrl", localProperties.getProperty("GUTENBERG_EDITOR_URL")?.trim() ?: "") } plugins {