/
HTTPHeaders+Directive.swift
272 lines (241 loc) · 8.35 KB
/
HTTPHeaders+Directive.swift
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
extension HTTPHeaders {
struct Directive: Equatable, CustomStringConvertible {
var value: Substring
var parameter: Substring?
var description: String {
if let parameter = self.parameter {
return "Directive(value: \(self.value.debugDescription), parameter: \(parameter.debugDescription))"
} else {
return "Directive(value: \(self.value.debugDescription))"
}
}
init(value: String, parameter: String? = nil) {
self.value = .init(value)
self.parameter = parameter.flatMap { .init($0) }
}
init(value: Substring, parameter: Substring? = nil) {
self.value = value
self.parameter = parameter
}
}
func parseDirectives(name: Name) -> [[Directive]] {
let headers = self[name]
var values: [[Directive]] = []
for header in headers {
var parser = DirectiveParser(string: header)
while let directives = parser.nextDirectives() {
values.append(directives)
}
}
return values
}
mutating func serializeDirectives(_ directives: [[Directive]], name: Name) {
let serializer = DirectiveSerializer(directives: directives)
self.replaceOrAdd(name: name, value: serializer.serialize())
}
struct DirectiveParser {
var current: Substring
init<S>(string: S)
where S: StringProtocol
{
self.current = .init(string)
}
mutating func nextDirectives() -> [Directive]? {
guard !self.current.isEmpty else {
return nil
}
var directives: [Directive] = []
while let directive = self.nextDirective() {
directives.append(directive)
}
return directives
}
private mutating func nextDirective() -> Directive? {
self.popWhitespace()
guard !self.current.isEmpty else {
return nil
}
if self.current.first == .comma {
self.pop()
return nil
}
let value: Substring
let parameter: Substring?
if let equals = self.firstParameterToken() {
value = self.pop(to: equals)
self.pop()
parameter = self.nextDirectiveValue()
} else {
value = self.nextDirectiveValue()
parameter = nil
}
return .init(
value: value.trimLinearWhitespace(),
parameter: parameter?.trimLinearWhitespace()
.unescapingDoubleQuotes()
)
}
private mutating func nextDirectiveValue() -> Substring {
let value: Substring
self.popWhitespace()
if self.current.first == .doubleQuote {
self.pop()
guard let nextDoubleQuote = self.firstUnescapedDoubleQuote() else {
return self.pop(to: self.current.endIndex)
}
value = self.pop(to: nextDoubleQuote).unescapingDoubleQuotes()
self.pop()
self.popWhitespace()
if self.current.first == .semicolon {
self.pop()
}
} else if let semicolon = self.current.firstIndex(of: .semicolon) {
value = self.pop(to: semicolon)
self.pop()
} else if let comma = self.current.firstIndex(of: .comma) {
value = self.pop(to: comma)
} else {
value = self.pop(to: self.current.endIndex)
}
return value
}
private mutating func popWhitespace() {
if let nonWhitespace = self.current.firstIndex(where: { !$0.isLinearWhitespace }) {
self.current = self.current[nonWhitespace...]
} else {
self.current = ""
}
}
private mutating func pop() {
self.current = self.current.dropFirst()
}
private mutating func pop(to index: Substring.Index) -> Substring {
let value = self.current[..<index]
self.current = self.current[index...]
return value
}
private func firstParameterToken() -> Substring.Index? {
for index in self.current.indices {
let character = self.current[index]
if character == .equals {
return index
} else if !character.isDirectiveKey {
return nil
}
}
return nil
}
private func firstUnescapedDoubleQuote() -> Substring.Index? {
var startIndex = self.current.startIndex
var nextDoubleQuote: Substring.Index?
while nextDoubleQuote == nil {
guard let possibleDoubleQuote = self.current[startIndex...].firstIndex(of: "\"") else {
return nil
}
// Check if quote is escaped.
if self.current.startIndex == possibleDoubleQuote || self.current[self.current.index(before: possibleDoubleQuote)] != "\\" {
nextDoubleQuote = possibleDoubleQuote
} else if possibleDoubleQuote < self.current.endIndex {
startIndex = self.current.index(after: possibleDoubleQuote)
} else {
return nil
}
}
return nextDoubleQuote
}
}
struct DirectiveSerializer {
let directives: [[Directive]]
init(directives: [[Directive]]) {
self.directives = directives
}
func serialize() -> String {
var main: [String] = []
for directives in self.directives {
var sub: [String] = []
for directive in directives {
let string: String
if let parameter = directive.parameter {
if self.shouldQuote(parameter) {
string = "\(directive.value)=\"\(parameter.escapingDoubleQuotes())\""
} else {
string = "\(directive.value)=\(parameter)"
}
} else {
string = .init(directive.value)
}
sub.append(string)
}
main.append(sub.joined(separator: "; "))
}
return main.joined(separator: ", ")
}
private func shouldQuote(_ parameter: Substring) -> Bool {
parameter.contains(where: {
$0 == .space || $0 == .doubleQuote
})
}
}
}
private extension Substring {
/// Converts all `\"` to `"`.
func unescapingDoubleQuotes() -> Substring {
self.lazy.split(separator: "\\").reduce(into: "") { (result, part) in
if result.isEmpty || part.first == "\"" {
result += part
} else {
result += "\\" + part
}
}
}
/// Converts all `"` to `\"`.
func escapingDoubleQuotes() -> String {
self.split(separator: "\"").joined(separator: "\\\"")
}
}
private extension Character {
static var doubleQuote: Self {
.init(Unicode.Scalar(0x22))
}
static var semicolon: Self {
.init(";")
}
static var equals: Self {
.init("=")
}
static var dash: Self {
.init("-")
}
static var comma: Self {
.init(",")
}
static var underscore: Self {
.init("_")
}
static var period: Self {
.init(".")
}
static var space: Self {
.init(" ")
}
var isDirectiveKey: Bool {
self.isLetter || self.isNumber || self == .dash || self == .underscore || self == .period
}
}
private extension Character {
var isLinearWhitespace: Bool {
self == " " || self == "\t"
}
}
private extension Substring {
func trimLinearWhitespace() -> Substring {
var me = self
while me.first?.isLinearWhitespace == .some(true) {
me = me.dropFirst()
}
while me.last?.isLinearWhitespace == .some(true) {
me = me.dropLast()
}
return me
}
}