-
Notifications
You must be signed in to change notification settings - Fork 554
Expand file tree
/
Copy pathSwifterTestsHttpParser.swift
More file actions
189 lines (151 loc) · 8.83 KB
/
SwifterTestsHttpParser.swift
File metadata and controls
189 lines (151 loc) · 8.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//
// SwifterTests.swift
// SwifterTests
//
// Copyright © 2016 Damian Kołakowski. All rights reserved.
//
import XCTest
@testable import Swifter
class SwifterTestsHttpParser: XCTestCase {
/// A specialized Socket which creates a linked socket pair with a pipe, and
/// immediately writes in fixed data. This enables tests to static fixture
/// data into the regular Socket flow.
class TestSocket: Socket {
init(_ content: String) {
/// Create an array to hold the read and write sockets that pipe creates
var fds = [Int32](repeating: 0, count: 2)
fds.withUnsafeMutableBufferPointer { ptr in
let received = pipe(ptr.baseAddress!)
guard received >= 0 else { fatalError("Pipe error!") }
}
// Extract the read and write handles into friendly variables
let fdRead = fds[0]
let fdWrite = fds[1]
// Set non-blocking I/O on both sockets. This is required!
_ = fcntl(fdWrite, F_SETFL, O_NONBLOCK)
_ = fcntl(fdRead, F_SETFL, O_NONBLOCK)
// Push the content bytes into the write socket.
content.withCString { stringPointer in
// Count will be either >=0 to indicate bytes written, or -1
// if the bytes will be written later (non-blocking).
let count = write(fdWrite, stringPointer, content.lengthOfBytes(using: .utf8) + 1)
guard count != -1 || errno == EAGAIN else { fatalError("Write error!") }
}
// Close the write socket immediately. The OS will add an EOF byte
// and the read socket will remain open.
#if os(Linux)
Glibc.close(fdWrite)
#else
Darwin.close(fdWrite) // the super instance will close fdRead in deinit!
#endif
super.init(socketFileDescriptor: fdRead)
}
}
// swiftlint:disable function_body_length
func testParser() {
let parser = HttpParser()
do {
_ = try parser.readHttpRequest(TestSocket(""))
XCTAssert(false, "Parser should throw an error if socket is empty.")
} catch { }
do {
_ = try parser.readHttpRequest(TestSocket("12345678"))
XCTAssert(false, "Parser should throw an error if status line has single token.")
} catch { }
do {
_ = try parser.readHttpRequest(TestSocket("GET HTTP/1.0"))
XCTAssert(false, "Parser should throw an error if status line has not enough tokens.")
} catch { }
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
XCTAssert(false, "Parser should throw an error if there is no next line symbol.")
} catch { }
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
XCTAssert(false, "Parser should throw an error if there is no next line symbol.")
} catch { }
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\r"))
XCTAssert(false, "Parser should throw an error if there is no next line symbol.")
} catch { }
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\n"))
XCTAssert(false, "Parser should throw an error if there is no 'Content-Length' header.")
} catch { }
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n"))
} catch {
XCTAssert(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
}
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 0\r\n\n"))
} catch {
XCTAssert(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
}
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\r\nContent-Length: -1\r\n\r\n"))
} catch let error {
let error = error as? HttpParserError
XCTAssertNotNil(error)
XCTAssertEqual(error!, HttpParserError.negativeContentLength)
}
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 5\n\n12345"))
} catch {
XCTAssert(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
}
do {
_ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 10\r\n\n"))
XCTAssert(false, "Parser should throw an error if request' body is too short.")
} catch { }
do { // test payload less than 1 read segmant
let contentLength = Socket.kBufferLength - 128
let bodyString = [String](repeating: "A", count: contentLength).joined(separator: "")
let payload = "GET / HTTP/1.0\nContent-Length: \(contentLength)\n\n".appending(bodyString)
let request = try parser.readHttpRequest(TestSocket(payload))
XCTAssert(bodyString.lengthOfBytes(using: .utf8) == contentLength, "Has correct request size")
let unicodeBytes = bodyString.utf8.map { return $0 }
XCTAssert(request.body == unicodeBytes, "Request body must be correct")
} catch { }
do { // test payload equal to 1 read segmant
let contentLength = Socket.kBufferLength
let bodyString = [String](repeating: "B", count: contentLength).joined(separator: "")
let payload = "GET / HTTP/1.0\nContent-Length: \(contentLength)\n\n".appending(bodyString)
let request = try parser.readHttpRequest(TestSocket(payload))
XCTAssert(bodyString.lengthOfBytes(using: .utf8) == contentLength, "Has correct request size")
let unicodeBytes = bodyString.utf8.map { return $0 }
XCTAssert(request.body == unicodeBytes, "Request body must be correct")
} catch { }
do { // test very large multi-segment payload
let contentLength = Socket.kBufferLength * 4
let bodyString = [String](repeating: "C", count: contentLength).joined(separator: "")
let payload = "GET / HTTP/1.0\nContent-Length: \(contentLength)\n\n".appending(bodyString)
let request = try parser.readHttpRequest(TestSocket(payload))
XCTAssert(bodyString.lengthOfBytes(using: .utf8) == contentLength, "Has correct request size")
let unicodeBytes = bodyString.utf8.map { return $0 }
XCTAssert(request.body == unicodeBytes, "Request body must be correct")
} catch { }
var resp = try? parser.readHttpRequest(TestSocket("GET /open?link=https://www.youtube.com/watch?v=D2cUBG4PnOA HTTP/1.0\nContent-Length: 10\n\n1234567890"))
XCTAssertEqual(resp?.queryParams.filter({ $0.0 == "link"}).first?.1, "https://www.youtube.com/watch?v=D2cUBG4PnOA")
XCTAssertEqual(resp?.method, "GET", "Parser should extract HTTP method name from the status line.")
XCTAssertEqual(resp?.path, "/open", "Parser should extract HTTP path value from the status line.")
XCTAssertEqual(resp?.headers["content-length"], "10", "Parser should extract Content-Length header value.")
resp = try? parser.readHttpRequest(TestSocket("POST / HTTP/1.0\nContent-Length: 10\n\n1234567890"))
XCTAssertEqual(resp?.method, "POST", "Parser should extract HTTP method name from the status line.")
resp = try? parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nHeader1: 1:1:34\nHeader2: 12345\nContent-Length: 0\n\n"))
XCTAssertEqual(resp?.headers["header1"], "1:1:34", "Parser should properly extract header name and value in case the value has ':' character.")
resp = try? parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nHeader1: 1\nHeader2: 2\nContent-Length: 0\n\n"))
XCTAssertEqual(resp?.headers["header1"], "1", "Parser should extract multiple headers from the request.")
XCTAssertEqual(resp?.headers["header2"], "2", "Parser should extract multiple headers from the request.")
resp = try? parser.readHttpRequest(TestSocket("GET /some/path?subscript_query[]=1&subscript_query[]=2 HTTP/1.0\nContent-Length: 10\n\n1234567890"))
let queryPairs = resp?.queryParams ?? []
XCTAssertEqual(queryPairs.count, 2)
XCTAssertEqual(queryPairs.first?.0, "subscript_query[]")
XCTAssertEqual(queryPairs.first?.1, "1")
XCTAssertEqual(queryPairs.last?.0, "subscript_query[]")
XCTAssertEqual(queryPairs.last?.1, "2")
XCTAssertEqual(resp?.method, "GET", "Parser should extract HTTP method name from the status line.")
XCTAssertEqual(resp?.path, "/some/path", "Parser should extract HTTP path value from the status line.")
XCTAssertEqual(resp?.headers["content-length"], "10", "Parser should extract Content-Length header value.")
}
}