diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java index 441153749c..345d78c57b 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.UUID; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import org.json.JSONException; @@ -224,7 +225,16 @@ public void setRequestBody(PluginCall call, JSValue body, String bodyType) throw this.writeRequestBody(body.toString()); } } else if (bodyType != null && bodyType.equals("formData")) { - this.writeFormDataRequestBody(contentType, body.toJSArray()); + String boundary = extractBoundaryFromContentType(contentType); + if (boundary == null) { + // If no boundary is provided, generate a random one and set the Content-Type header accordingly + // or otherwise servers will not be able to parse the request body. Browsers do this automatically + // but here we need to do this manually in order to comply with browser api behavior. + boundary = UUID.randomUUID().toString(); + connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + } + + this.writeFormDataRequestBody(boundary, body.toJSArray()); } else { this.writeRequestBody(body.toString()); } @@ -260,9 +270,8 @@ private void writeObjectRequestBody(JSObject object) throws IOException, JSONExc } } - private void writeFormDataRequestBody(String contentType, JSArray entries) throws IOException, JSONException { + private void writeFormDataRequestBody(String boundary, JSArray entries) throws IOException, JSONException { try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { - String boundary = contentType.split(";")[1].split("=")[1]; String lineEnd = "\r\n"; String twoHyphens = "--"; @@ -303,6 +312,39 @@ private void writeFormDataRequestBody(String contentType, JSArray entries) throw } } + /** + * Extracts the boundary value from the `Content-Type` header for multipart/form-data requests, if provided. + * + * The boundary value might be surrounded by double quotes (") which will be stripped away. + * + * @param contentType The `Content-Type` header string. + * @return The boundary value if found, otherwise `null`. + */ + public static String extractBoundaryFromContentType(String contentType) { + String boundaryPrefix = "boundary="; + int boundaryIndex = contentType.indexOf(boundaryPrefix); + if (boundaryIndex == -1) { + return null; + } + + // Extract the substring starting right after "boundary=" + String boundary = contentType.substring(boundaryIndex + boundaryPrefix.length()); + + // Find the end of the boundary value by looking for the next ";" + int endIndex = boundary.indexOf(";"); + if (endIndex != -1) { + boundary = boundary.substring(0, endIndex); + } + + // Remove surrounding double quotes if present + boundary = boundary.trim(); + if (boundary.startsWith("\"") && boundary.endsWith("\"")) { + boundary = boundary.substring(1, boundary.length() - 1); + } + + return boundary; + } + /** * Opens a communications link to the resource referenced by this * URL, if such a connection has not already been established. diff --git a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift index d13457798c..19750a2534 100644 --- a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift +++ b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift @@ -63,7 +63,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate { var data = Data() var boundary = UUID().uuidString - if contentType.contains("="), let contentBoundary = contentType.components(separatedBy: "=").last { + if contentType.contains("boundary="), let contentBoundary = extractBoundary(from: contentType) { boundary = contentBoundary } else { overrideContentType(boundary) @@ -83,6 +83,27 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate { request.setValue(contentType, forHTTPHeaderField: "Content-Type") headers["Content-Type"] = contentType } + + /** + Extracts the boundary value of the `content-type` header for multiplart/form-data requests, if provided + The boundary value might be surrounded by double quotes (") which will be stripped away. + */ + private func extractBoundary(from contentType: String) -> String? { + if let boundaryRange = contentType.range(of: "boundary=") { + var boundary = contentType[boundaryRange.upperBound...] + if let endRange = boundary.range(of: ";") { + boundary = boundary[.. Data { guard let stringData = data as? String else { @@ -107,7 +128,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate { } var data = Data() var boundary = UUID().uuidString - if contentType.contains("="), let contentBoundary = contentType.components(separatedBy: "=").last { + if contentType.contains("boundary="), let contentBoundary = extractBoundary(from: contentType) { boundary = contentBoundary } else { overrideContentType(boundary)