Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix boundary value extraction for form-data requests #7518

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this part was missing in the iOS implementation but present for Android already.

connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
}

this.writeFormDataRequestBody(boundary, body.toJSArray());
} else {
this.writeRequestBody(body.toString());
}
Expand Down Expand Up @@ -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 = "--";

Expand Down Expand Up @@ -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.
Expand Down
25 changes: 23 additions & 2 deletions ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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[..<endRange.lowerBound]
}

if boundary.hasPrefix("\"") && boundary.hasSuffix("\"") {
return String(boundary.dropFirst().dropLast())
} else {
return String(boundary)
}
}

return nil
}

public func getRequestDataAsString(_ data: JSValue) throws -> Data {
guard let stringData = data as? String else {
Expand All @@ -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)
Expand Down