Skip to content

Commit 8e9fa2c

Browse files
committed
Update to RFC_2046 0.2.1 API
- Remove unused swift-rfc-5322 direct dependency - Update formData() to use Headers and Content types - Update extractFormFields() to use typed properties - Fix tests to use new ContentType and Filename APIs
1 parent a66a7e4 commit 8e9fa2c

File tree

3 files changed

+28
-47
lines changed

3 files changed

+28
-47
lines changed

Package.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ let package = Package(
2121
.package(url: "https://github.com/swift-standards/swift-rfc-2045", from: "0.1.0"),
2222
.package(url: "https://github.com/swift-standards/swift-rfc-2046", from: "0.1.0"),
2323
.package(url: "https://github.com/swift-standards/swift-rfc-2183", from: "0.3.0"),
24-
.package(url: "https://github.com/swift-standards/swift-rfc-5322", from: "0.4.0"),
2524
],
2625
targets: [
2726
.target(

Sources/RFC 7578/Multipart+FormData.swift

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,7 @@ public import RFC_2046
33
public import RFC_2183
44

55
// MARK: - RFC 7578: Multipart/Form-Data
6-
7-
extension RFC_2046.Multipart.Subtype {
8-
/// Form data with file uploads
9-
///
10-
/// Used in HTTP POST requests with file uploads.
11-
/// Each part has a `Content-Disposition: form-data` header
12-
/// with the form field name.
13-
///
14-
/// **RFC 7578** - Returning Values from Forms: multipart/form-data
15-
///
16-
/// ## Example
17-
///
18-
/// ```swift
19-
/// let formData = try RFC_2046.Multipart.formData(
20-
/// fields: ["username": "john_doe"],
21-
/// files: [...]
22-
/// )
23-
/// ```
24-
public static let formData = RFC_2046.Multipart.Subtype(rawValue: "form-data")
25-
}
6+
// Note: .formData subtype is defined in RFC_2046.Multipart.Subtype
267

278
extension RFC_2046.Multipart {
289
/// Creates a multipart/form-data message
@@ -65,10 +46,13 @@ extension RFC_2046.Multipart {
6546

6647
// Add text fields
6748
for (name, value) in fields.sorted(by: { $0.key < $1.key }) {
49+
var headers = RFC_2046.BodyPart.Headers()
50+
headers.contentDisposition = RFC_2183.ContentDisposition.formData(name: name)
51+
headers.contentType = .textPlainUTF8
6852
parts.append(
6953
RFC_2046.BodyPart(
70-
headers: .formDataTextField(name: name),
71-
text: value
54+
headers: headers,
55+
content: RFC_2046.BodyPart.Content(Array(value.utf8))
7256
)
7357
)
7458
}
@@ -77,22 +61,25 @@ extension RFC_2046.Multipart {
7761
for file in files {
7862
// Note: Content-Transfer-Encoding not added per RFC 7578 §4.7
7963
// HTTP supports binary data natively
64+
var headers = RFC_2046.BodyPart.Headers()
65+
headers.contentDisposition = RFC_2183.ContentDisposition.formData(
66+
name: file.fieldName,
67+
filename: file.filename
68+
)
69+
headers.contentType = file.contentType
8070
parts.append(
8171
RFC_2046.BodyPart(
82-
headers: .formDataFile(
83-
name: file.fieldName,
84-
filename: file.filename,
85-
contentType: file.contentType
86-
),
87-
content: file.content
72+
headers: headers,
73+
content: RFC_2046.BodyPart.Content(file.content)
8874
)
8975
)
9076
}
9177

9278
// Generate boundary if not provided
93-
let effectiveBoundary =
79+
let effectiveBoundaryString =
9480
boundary
9581
?? "----FormData\(parts.count)\(parts.first?.headers.contentType?.type ?? "data")"
82+
let effectiveBoundary = try RFC_2046.Boundary(effectiveBoundaryString)
9683

9784
return try Self(
9885
subtype: .formData,
@@ -271,8 +258,7 @@ extension RFC_2046.Multipart {
271258
// Use typed Content-Disposition header
272259
guard let disposition = part.headers.contentDisposition,
273260
disposition.type == RFC_2183.DispositionType.formData,
274-
let fieldName = disposition.name,
275-
let textContent = part.textContent
261+
let fieldName = disposition.name
276262
else {
277263
continue
278264
}
@@ -282,6 +268,8 @@ extension RFC_2046.Multipart {
282268
continue
283269
}
284270

271+
// Get text content from raw bytes
272+
let textContent = String(part.content)
285273
fields[fieldName] = textContent
286274
}
287275

Tests/RFC 7578 Tests/ReadmeVerificationTests.swift

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct `README Verification` {
3030
let file = try RFC_7578.Form.Data.File(
3131
fieldName: "avatar",
3232
filename: try RFC_2183.Filename("photo.jpg"),
33-
contentType: RFC_2045.ContentType(type: "image", subtype: "jpeg"),
33+
contentType: try RFC_2045.ContentType("image/jpeg"),
3434
content: imageData
3535
)
3636

@@ -51,24 +51,17 @@ struct `README Verification` {
5151
try RFC_7578.Form.Data.File(
5252
fieldName: "",
5353
filename: try RFC_2183.Filename("photo.jpg"),
54-
contentType: RFC_2045.ContentType(type: "image", subtype: "jpeg"),
54+
contentType: try RFC_2045.ContentType("image/jpeg"),
5555
content: imageData
5656
)
5757
}
5858
}
5959

6060
@Test
6161
func `Validation: Invalid Filename Throws Error`() throws {
62-
let imageData: [UInt8] = [0xFF, 0xD8, 0xFF, 0xE0]
63-
6462
// RFC 2183 Filename validation should reject path traversal
65-
#expect(throws: RFC_2183.Error.filenameContainsPathTraversal) {
66-
try RFC_7578.Form.Data.File(
67-
fieldName: "avatar",
68-
filename: try RFC_2183.Filename("../etc/passwd"),
69-
contentType: RFC_2045.ContentType(type: "image", subtype: "jpeg"),
70-
content: imageData
71-
)
63+
#expect(throws: RFC_2183.Filename.Error.self) {
64+
_ = try RFC_2183.Filename("../etc/passwd")
7265
}
7366
}
7467

@@ -80,9 +73,10 @@ struct `README Verification` {
8073
)
8174

8275
let firstPart = formData.parts.first!
83-
let disposition = firstPart.headers["Content-Disposition"]!
76+
let disposition = firstPart.headers.contentDisposition!
8477

85-
#expect(disposition.contains("field\\\"name"))
78+
// Check the name contains the expected field name
79+
#expect(disposition.name == "field\"name")
8680
}
8781

8882
@Test
@@ -93,14 +87,14 @@ struct `README Verification` {
9387
let imageFile = try RFC_7578.Form.Data.File(
9488
fieldName: "avatar",
9589
filename: try RFC_2183.Filename("photo.jpg"),
96-
contentType: RFC_2045.ContentType(type: "image", subtype: "jpeg"),
90+
contentType: try RFC_2045.ContentType("image/jpeg"),
9791
content: imageData
9892
)
9993

10094
let textFile = try RFC_7578.Form.Data.File(
10195
fieldName: "document",
10296
filename: try RFC_2183.Filename("readme.txt"),
103-
contentType: RFC_2045.ContentType(type: "text", subtype: "plain"),
97+
contentType: try RFC_2045.ContentType("text/plain"),
10498
content: textData
10599
)
106100

0 commit comments

Comments
 (0)