Skip to content

Commit 5e666ae

Browse files
committed
add proper crlf support
1 parent de12fb8 commit 5e666ae

File tree

6 files changed

+116
-75
lines changed

6 files changed

+116
-75
lines changed

Sources/Multipart/BoundaryParser.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ final class BoundaryParser {
5454
break main
5555
} else {
5656
if buffer == match {
57+
if byte == .carriageReturn {
58+
break main
59+
}
60+
5761
if byte == .newLine {
5862
switch trailingHyphenCount {
5963
case 0:

Sources/Multipart/HeaderParser.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ final class HeaderParser {
3434
func parse(_ byte: Byte) throws {
3535
main: switch state {
3636
case .none:
37-
if byte == .newLine {
37+
state = .parsingKey(buffer: [byte])
38+
case .parsingKey(var buffer):
39+
if (buffer + [byte]) == [.carriageReturn, .newLine] {
40+
state = .none
3841
break main
3942
}
4043

41-
state = .parsingKey(buffer: [byte])
42-
case .parsingKey(var buffer):
4344
if byte == .colon {
4445
state = .parsingValue(key: buffer, buffer: [])
4546
break main
@@ -48,6 +49,10 @@ final class HeaderParser {
4849
buffer.append(byte)
4950
state = .parsingKey(buffer: buffer)
5051
case .parsingValue(let key, var buffer):
52+
if byte == .carriageReturn {
53+
break main
54+
}
55+
5156
if byte == .newLine {
5257
state = .finished(key: key, value: buffer)
5358
break main
@@ -56,11 +61,6 @@ final class HeaderParser {
5661
buffer.append(byte)
5762
state = .parsingValue(key: key, buffer: buffer)
5863
case .finished:
59-
if byte == .newLine {
60-
state = .none
61-
break main
62-
}
63-
6464
state = .parsingKey(buffer: [byte])
6565
}
6666
}

Sources/Multipart/Parser.swift

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ public final class Parser {
127127
}
128128

129129
hasFinished = true
130-
let body = buffer
130+
131+
let raw = buffer
132+
let body = Array(raw.trimmed([.newLine, .carriageReturn]))
133+
131134
buffer = []
132135
onEpilogue?(body)
133136
}
@@ -138,13 +141,7 @@ public final class Parser {
138141
guard !hasFinished else {
139142
throw Error.hasAlreadyFinished
140143
}
141-
142-
// we only care about new lines
143-
// this requires that all line endings be `\n` or `\r\n`
144-
if byte == .carriageReturn {
145-
return
146-
}
147-
144+
148145
switch state {
149146
case .preamble(let bodyEndIndex):
150147
try boundaryParser.parse(byte)
@@ -164,12 +161,18 @@ public final class Parser {
164161

165162
let body = Array(buffer[0..<bodyEndIndex])
166163

167-
let pos = bodyEndIndex + boundarySize
168-
buffer = Array(buffer[pos..<buffer.count])
164+
// newline
165+
let pos = bodyEndIndex + boundarySize + 1
166+
167+
if pos > buffer.count {
168+
buffer = []
169+
} else {
170+
buffer = Array(buffer[pos..<buffer.count])
171+
}
169172

170173
onPreamble?(body)
171174
}
172-
case .part(let partState, var headers, let bodyEndIndex):
175+
case .part(let partState, var headers, let bodyEndIndex):
173176
switch partState {
174177
case .headers:
175178
try headerParser.parse(byte)
@@ -182,7 +185,9 @@ public final class Parser {
182185
let headerKey = HeaderKey(key.trimmed([.space]).string)
183186
headers[headerKey] = value.trimmed([.space]).string
184187

185-
let pos = key.count + 1 + value.count + 1
188+
// colon newline
189+
let pos = key.count + 1 + value.count + 2
190+
186191
buffer = Array(buffer[pos..<buffer.count])
187192

188193
state = .part(
@@ -191,7 +196,8 @@ public final class Parser {
191196
bodyEndIndex: 0
192197
)
193198
case .none:
194-
buffer = Array(buffer[1..<buffer.count])
199+
// crlf
200+
buffer = Array(buffer[2..<buffer.count])
195201

196202
state = .part(
197203
state: .body,
@@ -223,10 +229,15 @@ public final class Parser {
223229
state = .part(state: .headers, headers: headers, bodyEndIndex: 0)
224230
}
225231

226-
let body = Array(buffer[0..<bodyEndIndex])
232+
let raw = Array(buffer[0..<bodyEndIndex])
233+
let body = Array(raw.trimmed([.newLine, .carriageReturn]))
227234

228235
let pos = bodyEndIndex + boundarySize
229-
buffer = Array(buffer[pos..<buffer.count])
236+
if pos > buffer.count {
237+
buffer = []
238+
} else {
239+
buffer = Array(buffer[pos..<buffer.count])
240+
}
230241

231242
let part = Part(headers: headers, body: body)
232243
onPart?(part)

Sources/Multipart/Serializer.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import Core
22

3+
private let crlf: Bytes = [.carriageReturn, .newLine]
4+
35
/**
46
Creates a multipart formatted array of bytes from Parts
57
suitable for an HTTP response or request body.
@@ -61,18 +63,18 @@ public final class Serializer {
6163

6264
serialize([.hyphen, .hyphen])
6365
serialize(boundary)
64-
serialize([.carriageReturn, .newLine])
66+
serialize(crlf)
6567
for (key, value) in part.headers {
6668
serialize(key.key.bytes)
6769
serialize(.colon)
6870
serialize(.space)
6971
serialize(value.bytes)
70-
serialize([.carriageReturn, .newLine])
72+
serialize(crlf)
7173
}
72-
serialize([.carriageReturn, .newLine])
74+
serialize(crlf)
7375

7476
serialize(part.body)
75-
serialize([.carriageReturn, .newLine])
77+
serialize(crlf)
7678

7779
partsSerialized = true
7880
}
@@ -93,7 +95,7 @@ public final class Serializer {
9395
serialize([.hyphen, .hyphen])
9496
serialize(boundary)
9597
serialize([.hyphen, .hyphen])
96-
serialize([.carriageReturn, .newLine])
98+
serialize(crlf)
9799
serialize(epilogue)
98100

99101
epilogueSerialized = true

Tests/FormDataTests/ParserTests.swift

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ class ParserTests: XCTestCase {
1515

1616
var message = ""
1717

18-
message += "-----------------------------9051914041544843365972754266\n"
19-
message += "Content-Disposition: form-data; name=\"text\"\n"
20-
message += "\n"
21-
message += "text default\n"
22-
message += "-----------------------------9051914041544843365972754266\n"
23-
message += "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n"
24-
message += "Content-Type: text/plain\n"
25-
message += "\n"
26-
message += "Content of a.txt.\n"
27-
message += "\n"
28-
message += "-----------------------------9051914041544843365972754266\n"
29-
message += "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n"
30-
message += "Content-Type: text/html\n"
31-
message += "\n"
32-
message += "<!DOCTYPE html><title>Content of a.html.</title>\n"
33-
message += "\n"
34-
message += "-----------------------------9051914041544843365972754266--\n"
18+
message += "-----------------------------9051914041544843365972754266\r\n"
19+
message += "Content-Disposition: form-data; name=\"text\"\r\n"
20+
message += "\r\n"
21+
message += "text default\r\n"
22+
message += "-----------------------------9051914041544843365972754266\r\n"
23+
message += "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n"
24+
message += "Content-Type: text/plain\r\n"
25+
message += "\r\n"
26+
message += "Content of a.txt.\r\n"
27+
message += "\r\n"
28+
message += "-----------------------------9051914041544843365972754266\r\n"
29+
message += "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\r\n"
30+
message += "Content-Type: text/html\r\n"
31+
message += "\r\n"
32+
message += "<!DOCTYPE html><title>Content of a.html.</title>\r\n"
33+
message += "\r\n"
34+
message += "-----------------------------9051914041544843365972754266--\r\n"
3535

3636
var fields: [String: Field] = [:]
3737

@@ -77,4 +77,28 @@ class ParserTests: XCTestCase {
7777

7878
XCTAssertEqual(fields["file"]?.filename, "Screen Shot 2017-01-13 at 3.05.26 PM.png")
7979
}
80+
81+
func testForm() throws {
82+
var message = ""
83+
84+
message += "--vapor\r\n"
85+
message += "Content-Disposition: form-data; name=\"name\"\r\n"
86+
message += "Content-Type: text\r\n"
87+
message += "\r\n"
88+
message += "hi\r\n"
89+
message += "--vapor--\r\n"
90+
91+
let multipart = try Multipart.Parser(boundary: "vapor")
92+
let parser = FormData.Parser(multipart: multipart)
93+
94+
var fields: [String: Field] = [:]
95+
96+
parser.onField = { field in
97+
fields[field.name] = field
98+
}
99+
100+
try parser.multipart.parse(message)
101+
102+
XCTAssertEqual(fields["name"]?.part.body.string, "hi")
103+
}
80104
}

Tests/MultipartTests/ParserTests.swift

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class ParserTests: XCTestCase {
5454

5555
var message = ""
5656
message += preamble
57-
message += "--foo--\n"
57+
message += "--foo--\r\n"
5858

5959
let preambleExpectation = expectation(description: "preamble was parsed")
6060

@@ -81,10 +81,10 @@ class ParserTests: XCTestCase {
8181

8282
var message = ""
8383
message += preamble
84-
message += "--foo\n"
85-
message += "\n"
84+
message += "--foo\r\n"
85+
message += "\r\n"
8686
message += part1
87-
message += "--foo--\n"
87+
message += "--foo--\r\n"
8888

8989
let partExpectation = expectation(description: "part was parsed")
9090

@@ -105,12 +105,12 @@ class ParserTests: XCTestCase {
105105

106106
var message = ""
107107
message += "preamble"
108-
message += "--foo\n"
109-
message += "key: value\n"
110-
message += "foo:bar\n"
111-
message += "\n"
108+
message += "--foo\r\n"
109+
message += "key: value\r\n"
110+
message += "foo:bar\r\n"
111+
message += "\r\n"
112112
message += part1
113-
message += "--foo--\n"
113+
message += "--foo--\r\n"
114114

115115
let partExpectation = expectation(description: "part was parsed")
116116

@@ -130,14 +130,14 @@ class ParserTests: XCTestCase {
130130
func testEpilogue() throws {
131131
let parser = try Parser(boundary: "foo")
132132

133-
let epilogue = "\nepliogue"
133+
let epilogue = "epliogue"
134134

135135
var message = ""
136136
message += "preamble"
137-
message += "--foo\n"
138-
message += "\n"
137+
message += "--foo\r\n"
138+
message += "\r\n"
139139
message += "part"
140-
message += "--foo--\n"
140+
message += "--foo--\r\n"
141141
message += epilogue
142142

143143
let epilogueExpectation = expectation(description: "epilogue was parsed")
@@ -160,23 +160,23 @@ class ParserTests: XCTestCase {
160160

161161
var message = ""
162162

163-
message += "-----------------------------9051914041544843365972754266\n"
164-
message += "Content-Disposition: form-data; name=\"text\"\n"
165-
message += "\n"
166-
message += "text default\n"
167-
message += "-----------------------------9051914041544843365972754266\n"
168-
message += "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\n"
169-
message += "Content-Type: text/plain\n"
170-
message += "\n"
171-
message += "Content of a.txt.\n"
172-
message += "\n"
173-
message += "-----------------------------9051914041544843365972754266\n"
174-
message += "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\n"
175-
message += "Content-Type: text/html\n"
176-
message += "\n"
177-
message += "<!DOCTYPE html><title>Content of a.html.</title>\n"
178-
message += "\n"
179-
message += "-----------------------------9051914041544843365972754266--\n"
163+
message += "-----------------------------9051914041544843365972754266\r\n"
164+
message += "Content-Disposition: form-data; name=\"text\"\r\n"
165+
message += "\r\n"
166+
message += "text default\r\n"
167+
message += "-----------------------------9051914041544843365972754266\r\n"
168+
message += "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n"
169+
message += "Content-Type: text/plain\r\n"
170+
message += "\r\n"
171+
message += "Content of a.txt.\r\n"
172+
message += "\r\n"
173+
message += "-----------------------------9051914041544843365972754266\r\n"
174+
message += "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\r\n"
175+
message += "Content-Type: text/html\r\n"
176+
message += "\r\n"
177+
message += "<!DOCTYPE html><title>Content of a.html.</title>\r\n"
178+
message += "\r\n"
179+
message += "-----------------------------9051914041544843365972754266--\r\n"
180180

181181
var parts: [Part] = []
182182

0 commit comments

Comments
 (0)