From 876b3fd1e6a204837b43f00345e0a85f5a13a26b Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 21 Jul 2025 15:57:58 +0200 Subject: [PATCH 1/6] Using a more straightforward kotlin way to prevent access issues --- .../rs/wordpress/api/kotlin/WpRequestExecutor.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt index dcc6462b..8248399b 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt @@ -88,13 +88,10 @@ class WpRequestExecutor( mediaUploadRequest.mediaParams().forEach { (k, v) -> multipartBodyBuilder.addFormDataPart(k, v) } - val filePath = mediaUploadRequest.filePath() - val file = - WpRequestExecutor::class.java.classLoader?.getResource(filePath)?.file?.let { - File( - it - ) - } ?: throw MediaUploadRequestExecutionException.MediaFileNotFound(filePath) + val file = File(mediaUploadRequest.filePath()) + if (!file.canRead()) { + throw MediaUploadRequestExecutionException.MediaFileNotFound(mediaUploadRequest.filePath()) + } multipartBodyBuilder.addFormDataPart( name = "file", filename = file.name, From fd9479a4a2f29f68497c6b183b0981170f32d6bf Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 21 Jul 2025 16:31:14 +0200 Subject: [PATCH 2/6] Adding more file checks --- .../main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt index 8248399b..cef9bb8d 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt @@ -89,7 +89,7 @@ class WpRequestExecutor( multipartBodyBuilder.addFormDataPart(k, v) } val file = File(mediaUploadRequest.filePath()) - if (!file.canRead()) { + if (!file.exists() || !file.isFile || !file.canRead()) { throw MediaUploadRequestExecutionException.MediaFileNotFound(mediaUploadRequest.filePath()) } multipartBodyBuilder.addFormDataPart( From ae060567af14e5702b9694710210a3f73e1731bc Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 21 Jul 2025 17:55:34 +0200 Subject: [PATCH 3/6] Adding full path to test media file --- .../api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt index 5ac672aa..211b0fec 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt @@ -66,7 +66,7 @@ class MediaEndpointTest { val response = client.request { requestBuilder -> requestBuilder.media().create( params = MediaCreateParams(title = title), - "test_media.jpg", + "/test-data/test_media.jpg", "image/jpeg", null ) From 6e87d02eeff51b900a404897ec9d1db3b7579b83 Mon Sep 17 00:00:00 2001 From: adalpari Date: Tue, 22 Jul 2025 00:04:21 +0200 Subject: [PATCH 4/6] Adding a FileResover injection approach --- .../kotlin/MediaEndpointTest.kt | 31 +++++++++++++++++-- .../wordpress/api/kotlin/WpRequestExecutor.kt | 11 +++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt index 211b0fec..c2e3a353 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt @@ -6,13 +6,15 @@ import org.junit.jupiter.api.Test import uniffi.wp_api.MediaCreateParams import uniffi.wp_api.MediaListParams import uniffi.wp_api.SparseMediaFieldWithEditContext +import uniffi.wp_api.WpAuthenticationProvider +import java.io.File import kotlin.test.assertEquals import kotlin.test.assertNotNull private const val MEDIA_ID_611: Long = 611 class MediaEndpointTest { - private val client = defaultApiClient() + private val client = mediaApiClient() @Test fun testMediaListRequest() = runTest { @@ -66,7 +68,7 @@ class MediaEndpointTest { val response = client.request { requestBuilder -> requestBuilder.media().create( params = MediaCreateParams(title = title), - "/test-data/test_media.jpg", + "test_media.jpg", "image/jpeg", null ) @@ -74,4 +76,29 @@ class MediaEndpointTest { assertEquals(title, response.title.rendered) restoreTestServer() } + + fun mediaApiClient(): WpApiClient { + val testCredentials = TestCredentials.INSTANCE + val authProvider = WpAuthenticationProvider.staticWithUsernameAndPassword( + username = testCredentials.adminUsername, password = testCredentials.adminPassword + ) + val requestExecutor = WpRequestExecutor( + fileResolver = FileResolverMock() + ) + return WpApiClient( + wpOrgSiteApiRootUrl = testCredentials.apiRootUrl, + authProvider = authProvider, + requestExecutor = requestExecutor + ) + } + + class FileResolverMock: FileResolver() { + // in order to properly resolve the file from the test assets, we need to do it in the following way + override fun getFile(path: String): File? = + WpAuthenticationProvider::class.java.classLoader?.getResource(path)?.file?.let { + File( + it + ) + } + } } diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt index cef9bb8d..8361b4c3 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt @@ -32,7 +32,8 @@ const val USER_AGENT_HEADER_NAME = "User-Agent" class WpRequestExecutor( private val httpClient: WpHttpClient = WpHttpClient.DefaultHttpClient(), - private val dispatcher: CoroutineDispatcher = Dispatchers.IO + private val dispatcher: CoroutineDispatcher = Dispatchers.IO, + private val fileResolver: FileResolver = FileResolver() ) : RequestExecutor { override suspend fun execute(request: WpNetworkRequest): WpNetworkResponse = withContext(dispatcher) { @@ -88,8 +89,8 @@ class WpRequestExecutor( mediaUploadRequest.mediaParams().forEach { (k, v) -> multipartBodyBuilder.addFormDataPart(k, v) } - val file = File(mediaUploadRequest.filePath()) - if (!file.exists() || !file.isFile || !file.canRead()) { + val file = fileResolver.getFile(mediaUploadRequest.filePath()) + if (file == null || !file.exists() || !file.isFile || !file.canRead()) { throw MediaUploadRequestExecutionException.MediaFileNotFound(mediaUploadRequest.filePath()) } multipartBodyBuilder.addFormDataPart( @@ -165,3 +166,7 @@ private fun requestExecutionFailedWith(reason: RequestExecutionErrorReason) = redirects = null, reason = reason ) + +open class FileResolver { + open fun getFile(path: String): File? = File(path) +} \ No newline at end of file From f8016499cadf7990ba269f571159fd992d1abf45 Mon Sep 17 00:00:00 2001 From: adalpari Date: Tue, 22 Jul 2025 00:23:11 +0200 Subject: [PATCH 5/6] detekt --- .../main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt index 8361b4c3..b2daa903 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt @@ -81,6 +81,7 @@ class WpRequestExecutor( } } + @Suppress("ComplexCondition") override suspend fun uploadMedia(mediaUploadRequest: MediaUploadRequest): WpNetworkResponse = withContext(dispatcher) { val requestBuilder = Request.Builder().url(mediaUploadRequest.url()) @@ -169,4 +170,4 @@ private fun requestExecutionFailedWith(reason: RequestExecutionErrorReason) = open class FileResolver { open fun getFile(path: String): File? = File(path) -} \ No newline at end of file +} From 1106a8c27c8b59dc58399147ff92f19160c5bb32 Mon Sep 17 00:00:00 2001 From: adalpari Date: Tue, 22 Jul 2025 09:35:22 +0200 Subject: [PATCH 6/6] PR suggestions. Making the fileResolver an interface and extracting some conditions --- .../src/integrationTest/kotlin/MediaEndpointTest.kt | 2 +- .../rs/wordpress/api/kotlin/DefaultFileResolver.kt | 7 +++++++ .../kotlin/rs/wordpress/api/kotlin/FileResolver.kt | 7 +++++++ .../rs/wordpress/api/kotlin/WpRequestExecutor.kt | 11 ++++------- 4 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/DefaultFileResolver.kt create mode 100644 native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/FileResolver.kt diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt index c2e3a353..8b77edf6 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt @@ -92,7 +92,7 @@ class MediaEndpointTest { ) } - class FileResolverMock: FileResolver() { + class FileResolverMock: FileResolver { // in order to properly resolve the file from the test assets, we need to do it in the following way override fun getFile(path: String): File? = WpAuthenticationProvider::class.java.classLoader?.getResource(path)?.file?.let { diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/DefaultFileResolver.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/DefaultFileResolver.kt new file mode 100644 index 00000000..7f9a072c --- /dev/null +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/DefaultFileResolver.kt @@ -0,0 +1,7 @@ +package rs.wordpress.api.kotlin + +import java.io.File + +class DefaultFileResolver : FileResolver { + override fun getFile(path: String): File? = File(path) +} diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/FileResolver.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/FileResolver.kt new file mode 100644 index 00000000..9569d3ee --- /dev/null +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/FileResolver.kt @@ -0,0 +1,7 @@ +package rs.wordpress.api.kotlin + +import java.io.File + +interface FileResolver { + fun getFile(path: String): File? +} diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt index b2daa903..474fc2ed 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt @@ -33,7 +33,7 @@ const val USER_AGENT_HEADER_NAME = "User-Agent" class WpRequestExecutor( private val httpClient: WpHttpClient = WpHttpClient.DefaultHttpClient(), private val dispatcher: CoroutineDispatcher = Dispatchers.IO, - private val fileResolver: FileResolver = FileResolver() + private val fileResolver: FileResolver = DefaultFileResolver() ) : RequestExecutor { override suspend fun execute(request: WpNetworkRequest): WpNetworkResponse = withContext(dispatcher) { @@ -81,7 +81,6 @@ class WpRequestExecutor( } } - @Suppress("ComplexCondition") override suspend fun uploadMedia(mediaUploadRequest: MediaUploadRequest): WpNetworkResponse = withContext(dispatcher) { val requestBuilder = Request.Builder().url(mediaUploadRequest.url()) @@ -91,7 +90,7 @@ class WpRequestExecutor( multipartBodyBuilder.addFormDataPart(k, v) } val file = fileResolver.getFile(mediaUploadRequest.filePath()) - if (file == null || !file.exists() || !file.isFile || !file.canRead()) { + if (file == null || !file.canBeUploaded()) { throw MediaUploadRequestExecutionException.MediaFileNotFound(mediaUploadRequest.filePath()) } multipartBodyBuilder.addFormDataPart( @@ -123,6 +122,8 @@ class WpRequestExecutor( override suspend fun sleep(millis: ULong) { delay(millis.toLong()) } + + private fun File.canBeUploaded() = exists() && isFile && canRead() } private fun RequestExecutionErrorReason.Companion.unknownHost(e: UnknownHostException) = @@ -167,7 +168,3 @@ private fun requestExecutionFailedWith(reason: RequestExecutionErrorReason) = redirects = null, reason = reason ) - -open class FileResolver { - open fun getFile(path: String): File? = File(path) -}