diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75ebec4..b66e7f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## 1.0.3
+
+### 2026-01-08
+
+- Fix uploading files with params.
+
## 1.0.2
### 2025-12-22
diff --git a/pom.xml b/pom.xml
index 9d46eb2..3581ec6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,5 +6,5 @@
4.0.0
io.ionic.libs
ionfiletransfer-android
- 1.0.2
+ 1.0.3
\ No newline at end of file
diff --git a/src/main/kotlin/io/ionic/libs/ionfiletransferlib/IONFLTRController.kt b/src/main/kotlin/io/ionic/libs/ionfiletransferlib/IONFLTRController.kt
index 961ad72..b568c9a 100644
--- a/src/main/kotlin/io/ionic/libs/ionfiletransferlib/IONFLTRController.kt
+++ b/src/main/kotlin/io/ionic/libs/ionfiletransferlib/IONFLTRController.kt
@@ -126,7 +126,9 @@ class IONFLTRController internal constructor(
fileHelper.createParentDirectories(targetFile)
// Setup connection
- val connection = connectionHelper.setupConnection(options.url, options.httpOptions)
+ val connection = connectionHelper.setupConnection(options.url, options.httpOptions).let {
+ connectionHelper.appendParamsToUrl(options.url, it, options.httpOptions, useChunkedMode = false)
+ }
return Pair(targetFile, connection)
}
@@ -250,7 +252,9 @@ class IONFLTRController internal constructor(
val file = fileHelper.getFileToUploadInfo(options.filePath)
// Setup connection
- val connection = connectionHelper.setupConnection(options.url, options.httpOptions)
+ val connection = connectionHelper.setupConnection(options.url, options.httpOptions).let {
+ connectionHelper.appendParamsToUrl(options.url, it, options.httpOptions, options.chunkedMode)
+ }
return Pair(file, connection)
}
@@ -268,18 +272,16 @@ class IONFLTRController internal constructor(
): Pair? {
var multiPartUpload = false
// Set content type if not already set
- if (!options.httpOptions.headers.containsKey("Content-Type")) {
+ if (connectionHelper.useMultipartFormData(options.httpOptions) && !useChunkedMode) {
+ multiPartUpload = true
+ connection.setRequestProperty(
+ "Content-Type",
+ "multipart/form-data; boundary=$BOUNDARY"
+ )
+ } else if (!options.httpOptions.headers.containsKey("Content-Type")) {
val mimeType = options.mimeType ?: fileHelper.getMimeType(options.filePath)
?: "application/octet-stream"
- if (isPostOrPutMethod(options.httpOptions.method)) {
- multiPartUpload = true
- connection.setRequestProperty(
- "Content-Type",
- "multipart/form-data; boundary=$BOUNDARY"
- )
- } else {
- connection.setRequestProperty("Content-Type", mimeType)
- }
+ connection.setRequestProperty("Content-Type", mimeType)
}
if (useChunkedMode) {
@@ -314,8 +316,9 @@ class IONFLTRController internal constructor(
val boundary = "$LINE_START$BOUNDARY$LINE_END"
val beforeData = buildString {
- // Write additional form parameters if any
- options.formParams?.forEach { (key, value) ->
+ // Write form parameters using the available attributes
+ val allParams = (options.formParams ?: emptyMap()) + options.httpOptions.params
+ allParams.forEach { (key, value) ->
append(boundary)
val paramHeader = "Content-Disposition: form-data; name=\"$key\"$LINE_END$LINE_END"
val paramValue = "$value$LINE_END"
@@ -395,7 +398,7 @@ class IONFLTRController internal constructor(
emit(createUploadFileProgress(bytes = 0, total = 0))
return 0L
}
-
+
var totalBytesWritten: Long
connection.outputStream.use { connOutputStream ->
@@ -446,14 +449,4 @@ class IONFLTRController internal constructor(
)
)
}
-
- /**
- * Checks if the HTTP method is either POST or PUT.
- *
- * @param method The HTTP method to check
- * @return True if the method is POST or PUT, false otherwise
- */
- private fun isPostOrPutMethod(method: String): Boolean {
- return method.equals("POST", ignoreCase = true) || method.equals("PUT", ignoreCase = true)
- }
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/ionic/libs/ionfiletransferlib/helpers/IONFLTRConnectionHelper.kt b/src/main/kotlin/io/ionic/libs/ionfiletransferlib/helpers/IONFLTRConnectionHelper.kt
index 03ed379..e5a71fe 100644
--- a/src/main/kotlin/io/ionic/libs/ionfiletransferlib/helpers/IONFLTRConnectionHelper.kt
+++ b/src/main/kotlin/io/ionic/libs/ionfiletransferlib/helpers/IONFLTRConnectionHelper.kt
@@ -26,7 +26,7 @@ inline fun HttpURLConnection.use(block: (HttpURLConnection) -> R): R {
* Extension function to assert that an HTTP response was successful (2xx status code).
* If the response was not successful, throws an IONFLTRException.HttpError with details
* from the error stream.
- *
+ *
* @throws IONFLTRException.HttpError if the response code is not in the 200-299 range
*/
@@ -46,27 +46,46 @@ fun HttpURLConnection.assertSuccessHttpResponse() {
/**
* Helper class for setting up HTTP connections with proper configuration.
*/
-class IONFLTRConnectionHelper {
+internal class IONFLTRConnectionHelper {
/**
* Sets up the HTTP connection with the provided options.
*/
fun setupConnection(urlString: String, httpOptions: IONFLTRTransferHttpOptions): HttpURLConnection {
val url = URL(urlString)
val connection = url.openConnection() as HttpURLConnection
-
+
// Set method
connection.requestMethod = httpOptions.method
-
+
// Set timeouts
connection.connectTimeout = httpOptions.connectTimeout
connection.readTimeout = httpOptions.readTimeout
-
+
// Set headers
httpOptions.headers.forEach { (key, value) ->
connection.setRequestProperty(key, value)
}
-
- // Set parameters
+
+ // Set redirect handling
+ connection.instanceFollowRedirects = !httpOptions.disableRedirects
+
+ // Set SSL factory if provided
+ if (httpOptions.sslSocketFactory != null && connection is javax.net.ssl.HttpsURLConnection) {
+ connection.sslSocketFactory = httpOptions.sslSocketFactory
+ }
+
+ return connection
+ }
+
+ /**
+ * append params to url, if applicable
+ */
+ fun appendParamsToUrl(
+ urlString: String,
+ connection: HttpURLConnection,
+ httpOptions: IONFLTRTransferHttpOptions,
+ useChunkedMode: Boolean
+ ): HttpURLConnection {
if (httpOptions.params.isNotEmpty()) {
val paramString = buildString {
httpOptions.params.forEach { (key, values) ->
@@ -82,27 +101,22 @@ class IONFLTRConnectionHelper {
}
}
}
-
- if (httpOptions.method.equals("GET", ignoreCase = true)) {
+
+ // for HTTP requests that aren't for multipart/form-data, params will be written in the request body.
+ if (!useMultipartFormData(httpOptions) || useChunkedMode) {
val separator = if (urlString.contains("?")) "&" else "?"
val newUrl = URL("$urlString$separator$paramString")
return newUrl.openConnection() as HttpURLConnection
- } else {
- connection.doOutput = true
- connection.outputStream.use { os ->
- os.write(paramString.toByteArray())
- }
}
}
-
- // Set redirect handling
- connection.instanceFollowRedirects = !httpOptions.disableRedirects
-
- // Set SSL factory if provided
- if (httpOptions.sslSocketFactory != null && connection is javax.net.ssl.HttpsURLConnection) {
- connection.sslSocketFactory = httpOptions.sslSocketFactory
- }
-
return connection
}
-}
\ No newline at end of file
+
+ /**
+ * @return true for any HTTP request that isn't a multipart/form-data
+ */
+ internal fun useMultipartFormData(options: IONFLTRTransferHttpOptions): Boolean =
+ !options.headers.containsKey("Content-Type") &&
+ (options.method.equals("POST", ignoreCase = true) || options.method.equals("PUT", ignoreCase = true))
+
+}