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(ios)!: solve FormData boundary issues #7306

Merged
merged 4 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions android/capacitor/src/main/assets/native-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,9 @@ var nativeBridge = (function (exports) {
}
else if (body instanceof FormData) {
const formData = await convertFormData(body);
const boundary = `${Date.now()}`;
return {
data: formData,
type: 'formData',
headers: {
'Content-Type': `multipart/form-data; boundary=--${boundary}`,
},
};
}
else if (body instanceof File) {
Expand Down
4 changes: 0 additions & 4 deletions core/native-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,9 @@ const convertBody = async (
};
} else if (body instanceof FormData) {
const formData = await convertFormData(body);
const boundary = `${Date.now()}`;
return {
data: formData,
type: 'formData',
headers: {
'Content-Type': `multipart/form-data; boundary=--${boundary}`,
},
};
} else if (body instanceof File) {
const fileData = await readFileAsBase64(body);
Expand Down
47 changes: 25 additions & 22 deletions ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
return nil
}

public func getRequestDataAsMultipartFormData(_ data: JSValue) throws -> Data {
public func getRequestDataAsMultipartFormData(_ data: JSValue, _ contentType: String) throws -> Data {
guard let obj = data as? JSObject else {
// Throw, other data types explicitly not supported.
throw CapacitorUrlRequestError.serializationError("[ data ] argument for request with content-type [ application/x-www-form-urlencoded ] may only be a plain javascript object")
Expand All @@ -62,11 +62,12 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
}

var data = Data()
let boundary = UUID().uuidString
let contentType = "multipart/form-data; boundary=\(boundary)"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
headers["Content-Type"] = contentType

var boundary = UUID().uuidString
if contentType.contains("="), let contentBoundary = contentType.components(separatedBy: "=").last {
boundary = contentBoundary
} else {
overrideContentType(boundary)
}
strings.forEach { key, value in
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
Expand All @@ -77,6 +78,12 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
return data
}

private func overrideContentType(_ boundary: String) {
let contentType = "multipart/form-data; boundary=\(boundary)"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
headers["Content-Type"] = contentType
}

public func getRequestDataAsString(_ data: JSValue) throws -> Data {
guard let stringData = data as? String else {
throw CapacitorUrlRequestError.serializationError("[ data ] argument could not be parsed as string")
Expand All @@ -93,20 +100,18 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
return normalized[index.lowercased()]
}

func getRequestDataFromFormData(_ data: JSValue) throws -> Data? {
public func getRequestDataFromFormData(_ data: JSValue, _ contentType: String) throws -> Data? {
guard let list = data as? JSArray else {
// Throw, other data types explicitly not supported.
throw CapacitorUrlRequestError.serializationError("Data must be an array for FormData")
}

var data = Data()

// Update the contentType with the new boundary
let boundary = UUID().uuidString
let contentType = "multipart/form-data; boundary=\(boundary)"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
headers["Content-Type"] = contentType

var boundary = UUID().uuidString
if contentType.contains("="), let contentBoundary = contentType.components(separatedBy: "=").last {
boundary = contentBoundary
} else {
overrideContentType(boundary)
}
for entry in list {
guard let item = entry as? [String: String] else {
throw CapacitorUrlRequestError.serializationError("Data must be an array for FormData")
Expand All @@ -120,7 +125,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
let fileName = item["fileName"]
let fileContentType = item["contentType"]

data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(key!)\"; filename=\"\(fileName!)\"\r\n".data(using: .utf8)!)
data.append("Content-Type: \(fileContentType!)\r\n".data(using: .utf8)!)
data.append("Content-Transfer-Encoding: binary\r\n".data(using: .utf8)!)
Expand All @@ -130,16 +135,14 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {

data.append("\r\n".data(using: .utf8)!)
} else if type == "string" {
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(key!)\"\r\n".data(using: .utf8)!)
data.append("\r\n".data(using: .utf8)!)
data.append(value.data(using: .utf8)!)
data.append("\r\n".data(using: .utf8)!)
}

}

data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
data.append("--\(boundary)--\r\n".data(using: .utf8)!)

return data
}
Expand All @@ -151,7 +154,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
}
return Data(base64Encoded: stringData)
} else if dataType == "formData" {
return try getRequestDataFromFormData(body)
return try getRequestDataFromFormData(body, contentType)
}

// If data can be parsed directly as a string, return that without processing.
Expand All @@ -162,7 +165,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
} else if contentType.contains("application/x-www-form-urlencoded") {
return try getRequestDataAsFormUrlEncoded(body)
} else if contentType.contains("multipart/form-data") {
return try getRequestDataAsMultipartFormData(body)
return try getRequestDataAsMultipartFormData(body, contentType)
} else {
throw CapacitorUrlRequestError.serializationError("[ data ] argument could not be parsed for content type [ \(contentType) ]")
}
Expand Down
1 change: 0 additions & 1 deletion ios/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ func tryParseJson(_ data: Data) -> Any {
}
}


/// Helper to convert the headers dictionary to lower case keys. This allows case-insensitive querying in the bridge javascript.
/// - Parameters:
/// - headers: The headers as dictionary. The type is unspecific because the incoming headers are coming from the
Expand Down
4 changes: 0 additions & 4 deletions ios/Capacitor/Capacitor/assets/native-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,9 @@ var nativeBridge = (function (exports) {
}
else if (body instanceof FormData) {
const formData = await convertFormData(body);
const boundary = `${Date.now()}`;
return {
data: formData,
type: 'formData',
headers: {
'Content-Type': `multipart/form-data; boundary=--${boundary}`,
},
};
}
else if (body instanceof File) {
Expand Down