public struct BodyFile { public var name = "" public var contentType = "" public var file: File // Handy method that can both rename and move the file to a new directory. public func rename(path: String) throws { try File.rename(oldPath: file.path, newPath: path) } } public struct Body { public var fields = [String: String]() public var files = [String: BodyFile]() public subscript(key: String) -> String? { get { return fields[key] ?? nil } set { fields[key] = newValue } } } // DIGIT / ALPHA / "'" / "(" / ")" / "+" / "_" // / "," / "-" / "." / "/" / ":" / "=" / "?" struct BoundaryCharTable { let table: [Bool] init() { var t = [Bool](repeating: false, count: 256) for i in 65..<91 { // A-Z t[i] = true } for i in 97..<123 { // a-z t[i] = true } for i in 48..<58 { // 0-9 t[i] = true } t[39] = true // ' t[40] = true // ( t[41] = true // ) t[43] = true // + t[44] = true // , t[45] = true // - t[46] = true // . t[47] = true // / t[58] = true // : t[61] = true // = t[63] = true // ? t[95] = true // _ table = t } subscript(key: UInt8) -> Bool { return table[Int(key)] } static let TABLE = BoundaryCharTable() } struct BodyTokenMatcher { let bytes: [UInt8] init(string: String) { bytes = [UInt8](string.utf8) } init(bytes: [UInt8]) { self.bytes = bytes } subscript(index: Int) -> UInt8 { return bytes[index] } func match(index: Int, c: UInt8) -> Bool { return bytes[index] == c } var count: Int { return bytes.count } static let CONTENT_DISPOSITION = BodyTokenMatcher( string: "Content-Disposition") static let NAME = BodyTokenMatcher(string: "name") static let FILENAME = BodyTokenMatcher(string: "filename") static let CONTENT_TYPE = BodyTokenMatcher(string: "Content-Type") static let FORM_DATA = BodyTokenMatcher(string: "form-data") static let _EMPTY = BodyTokenMatcher(string: "") } enum BodyParserEntry { case Body case BodyStarted case Key case NextKey case KeyStarted case Colon case Value case ValueStarted case Space case LineFeed case BeginMultipart case BeginMultipartStarted case MatchToken case ContentDisposition case ContentDispositionEnd case FormData case FormDataEnd case Name case NameEnd case NameValue case NameValueStarted case NameValueEnd case FileName case FileNameEnd case FileNameValue case FileNameValueStarted case FileNameValueEnd case ContentType case ContentTypeEnd case ContentTypeValue case ContentTypeValueStarted case ContentValue case ContentBody case ContentBodyStarted case ContentData case ContentDataStarted case ContentFile case ContentFileStarted } // Supports streaming. public struct BodyParser { var stream = [UInt8]() var entryParser: BodyParserEntry = .Body public var body = Body() var index = 0 var length = 0 var linedUpParser: BodyParserEntry = .Body var tokenIndex = -1 var keyToken = "" var tokenBuffer = [UInt8](repeating: 0, count: 1024) var tokenBufferEnd = 0 var done = false var boundary = BodyTokenMatcher._EMPTY var shadowMatch = BodyTokenMatcher._EMPTY var nameValue = "" var fileNameValue = "" var contentTypeValue = "" var boundaryMatch = [Int]() var boundTest1 = false // Match for the boundary token without line ending. var boundTest2 = false // Match for the boundary token plus first - suffix. var boundTest3 = false // Match for the boundary token plus -- suffix. var tempFile: TempFile? public init() { } public var isDone: Bool { return done } mutating func addToTokenBuffer(a: [UInt8], startIndex: Int, endIndex: Int) { let tbe = tokenBufferEnd let blen = tokenBuffer.count let ne = tbe + (endIndex - startIndex) if ne >= blen { var c = [UInt8](repeating: 0, count: ne * 2) for i in 0..<tbe { c[i] = tokenBuffer[i] } tokenBuffer = c } var j = tbe for i in startIndex..<endIndex { tokenBuffer[j] = a[i] j += 1 } tokenBufferEnd = j } mutating func next() throws { switch entryParser { case .Body: try inBody() case .BodyStarted: try inBodyStarted() case .NextKey: try inNextKey() case .KeyStarted: try inKeyStarted() case .Value: try inValue() case .ValueStarted: try inValueStarted() case .BeginMultipart: try inBeginMultipart() case .BeginMultipartStarted: try inBeginMultipartStarted() case .ContentDisposition: try inContentDisposition() case .ContentDispositionEnd: try inContentDispositionEnd() case .LineFeed: try inLineFeed() case .MatchToken: try inMatchToken() case .Space: try inSpace() case .FormData: try inFormData() case .FormDataEnd: try inFormDataEnd() case .Name: try inName() case .NameEnd: try inNameEnd() case .NameValue: try inNameValue() case .NameValueStarted: try inNameValueStarted() case .NameValueEnd: try inNameValueEnd() case .FileName: try inFileName() case .FileNameEnd: try inFileNameEnd() case .FileNameValue: try inFileNameValue() case .FileNameValueStarted: try inFileNameValueStarted() case .FileNameValueEnd: try inFileNameValueEnd() case .ContentType: try inContentType() case .ContentTypeEnd: try inContentTypeEnd() case .ContentTypeValue: try inContentTypeValue() case .ContentTypeValueStarted: try inContentTypeValueStarted() case .ContentBody: try inContentBody() case .ContentBodyStarted: try inContentBodyStarted() case .ContentData: try inContentData() case .ContentDataStarted: try inContentDataStarted() default: throw BodyParserError.ContentBody } } mutating public func parse(bytes: [UInt8], index si: Int = 0, maxBytes: Int = -1) throws { stream = bytes index = si length = maxBytes < 0 ? bytes.count : maxBytes while index < length { try next() } if tokenIndex >= 0 { addToTokenBuffer(a: stream, startIndex: tokenIndex, endIndex: length) tokenIndex = 0 // Set it at 0 to continue supporting addToTokenBuffer. if let tf = tempFile { if tokenBufferEnd >= 4096 { let len = tokenBufferEnd - 80 let _ = tf.writeBytes(bytes: tokenBuffer, maxBytes: len) for i in 0..<80 { tokenBuffer[i] = tokenBuffer[len + i] } tokenBufferEnd = 80 } } } } mutating func collectToken(endIndex: Int) -> [UInt8] { var a: [UInt8]? if tokenBufferEnd > 0 { addToTokenBuffer(a: stream, startIndex: tokenIndex, endIndex: endIndex) a = [UInt8](tokenBuffer[0..<tokenBufferEnd]) tokenBufferEnd = 0 } else { a = [UInt8](stream[tokenIndex..<endIndex]) } index = endIndex + 1 tokenIndex = -1 return a! } mutating func collectString(endIndex: Int) -> String? { return String.fromCharCodes(charCodes: collectToken(endIndex: endIndex)) } mutating func collectFormUrlDecodedString(endIndex: Int) -> String? { let a = collectToken(endIndex: endIndex) if let b = HexaUtils.formUrlDecode(bytes: a, maxBytes: a.count) { return String.fromCharCodes(charCodes: b) } return nil } mutating func inBody() throws { let i = index let c = stream[i] if c == 45 { // - entryParser = .BeginMultipart tokenIndex = i index = i + 1 } else if c >= 32 && c != 61 { // Some character, but not "=". entryParser = .KeyStarted tokenIndex = i index = i + 1 } else { throw BodyParserError.Body } } mutating func inBodyStarted() throws { throw BodyParserError.Body } mutating func inBeginMultipart() throws { let i = index if stream[i] == 45 { // - entryParser = .BeginMultipartStarted tokenIndex = i index = i + 1 } else { entryParser = .KeyStarted } } mutating func inBeginMultipartStarted() throws { var i = index let len = length repeat { let c = stream[i] if BoundaryCharTable.TABLE[c] { // Ignore. } else if c == 13 { boundary = BodyTokenMatcher(bytes: collectToken(endIndex: i)) if boundary.count <= 2 { throw BodyParserError.Key } entryParser = .LineFeed linedUpParser = .ContentDisposition break } else { throw BodyParserError.BeginMultipart } i += 1 } while i < len if i >= len { index = i } } mutating func inNextKey() throws { let i = index let c = stream[i] if c == 61 { // = throw HeaderParserError.Key } else if c >= 32 { tokenIndex = i index = i + 1 entryParser = .KeyStarted } else { throw HeaderParserError.Key } } mutating func inKeyStarted() throws { var i = index let len = length func process() throws { if let k = collectFormUrlDecodedString(endIndex: i) { keyToken = k } else { throw BodyParserError.Key } } repeat { let c = stream[i] if c == 61 { // = entryParser = .Value try process() break } else if c >= 32 { // ignore } else { throw BodyParserError.Key } i += 1 } while i < len if i >= len { index = i } } mutating func inValue() throws { let i = index if stream[i] == 38 { // & entryParser = .NextKey body[keyToken] = "" index = i + 1 } else if stream[i] >= 32 { tokenIndex = i index = i + 1 entryParser = .ValueStarted } else if stream[i] == 0 { body[keyToken] = "" done = true index = length // Done. Exit. } else { throw BodyParserError.Value } } mutating func inValueStarted() throws { var i = index let len = length repeat { let c = stream[i] if c == 38 { // & entryParser = .NextKey body[keyToken] = collectFormUrlDecodedString(endIndex: i) break } else if c >= 32 { // ignore } else if c == 0 { body[keyToken] = collectFormUrlDecodedString(endIndex: i) done = true index = length // Done. Exit. break } else { throw BodyParserError.Value } i += 1 } while i < len if i >= len { index = i } } mutating func inLineFeed() throws { if stream[index] == 10 { // \n index += 1 entryParser = linedUpParser } else { throw BodyParserError.LineFeed } } mutating func inContentDisposition() throws { shadowMatch = BodyTokenMatcher.CONTENT_DISPOSITION entryParser = .MatchToken linedUpParser = .ContentDispositionEnd tokenIndex = index } mutating func inContentDispositionEnd() throws { if stream[index] == 58 { // : index += 1 entryParser = .Space linedUpParser = .FormData } else { throw BodyParserError.ContentDisposition } } mutating func inFormData() throws { shadowMatch = BodyTokenMatcher.FORM_DATA entryParser = .MatchToken linedUpParser = .FormDataEnd tokenIndex = index try inMatchToken() } mutating func inFormDataEnd() throws { if stream[index] == 59 { // ; index += 1 entryParser = .Space linedUpParser = .Name } else { throw BodyParserError.FormData } } mutating func inName() throws { shadowMatch = BodyTokenMatcher.NAME entryParser = .MatchToken linedUpParser = .NameEnd tokenIndex = index try inMatchToken() } mutating func inNameEnd() throws { if stream[index] == 61 { // = index += 1 entryParser = .NameValue } else { throw BodyParserError.Name } } mutating func inNameValue() throws { if stream[index] == 34 { // " index += 1 nameValue = "" entryParser = .NameValueStarted tokenIndex = index } else { throw BodyParserError.NameValue } } mutating func inNameValueStarted() throws { var i = index let len = length repeat { let c = stream[i] if c == 34 { // " if let s = collectString(endIndex: i) { nameValue = s } else { throw BodyParserError.NameValue } entryParser = .NameValueEnd index = i + 1 break } else if c >= 32 { // Space. // ignore } else { throw BodyParserError.NameValue } i += 1 } while i < len if i >= len { index = i } } mutating func inNameValueEnd() throws { if stream[index] == 59 { // ; index += 1 entryParser = .Space linedUpParser = .FileName } else if stream[index] == 13 { // Carriage return. entryParser = .ContentBody index += 1 } else { throw BodyParserError.NameValue } } mutating func inFileName() throws { shadowMatch = BodyTokenMatcher.FILENAME entryParser = .MatchToken linedUpParser = .FileNameEnd tokenIndex = index try inMatchToken() } mutating func inFileNameEnd() throws { if stream[index] == 61 { // = index += 1 entryParser = .FileNameValue } else { throw BodyParserError.FileName } } mutating func inFileNameValue() throws { if stream[index] == 34 { // " index += 1 tokenIndex = index fileNameValue = "" entryParser = .FileNameValueStarted } else { throw BodyParserError.FileNameValue } } mutating func inFileNameValueStarted() throws { var i = index let len = length repeat { let c = stream[i] if c == 34 { // " if let s = collectString(endIndex: i) { fileNameValue = s } else { throw BodyParserError.FileNameValue } entryParser = .FileNameValueEnd index = i + 1 break } else if c >= 32 { // Space. // ignore } else { throw BodyParserError.FileNameValue } i += 1 } while i < len if i >= len { index = i } } mutating func inFileNameValueEnd() throws { if stream[index] == 13 { // Carriage return. entryParser = .LineFeed linedUpParser = .ContentType index += 1 } else { throw BodyParserError.FileNameValue } } mutating func inContentType() throws { shadowMatch = BodyTokenMatcher.CONTENT_TYPE entryParser = .MatchToken linedUpParser = .ContentTypeEnd tokenIndex = index try inMatchToken() } mutating func inContentTypeEnd() throws { if stream[index] == 58 { // : index += 1 entryParser = .Space linedUpParser = .ContentTypeValue } else { throw BodyParserError.ContentDisposition } } mutating func inContentTypeValue() throws { if stream[index] > 32 { tokenIndex = index contentTypeValue = "" index += 1 entryParser = .ContentTypeValueStarted } else { throw BodyParserError.ContentTypeValue } } mutating func inContentTypeValueStarted() throws { var i = index let len = length repeat { let c = stream[i] if c == 13 { if let s = collectString(endIndex: i) { contentTypeValue = s } else { throw BodyParserError.FileNameValue } entryParser = .ContentBody index = i + 1 break } else if c > 32 { // Space. // ignore } else { throw BodyParserError.ContentTypeValue } i += 1 } while i < len if i >= len { index = i } } mutating func inMatchToken() throws { var i = index let len = length let shadowLasti = shadowMatch.count - 1 repeat { let ci = i - tokenIndex if stream[i] == shadowMatch[tokenBufferEnd + ci] { if ci == shadowLasti { entryParser = linedUpParser index = i + 1 tokenIndex = -1 break } } else { throw BodyParserError.MatchToken } i += 1 } while i < len if i >= len { index = i } } mutating func inContentBody() throws { if stream[index] == 10 { index += 1 entryParser = .ContentBodyStarted } else { throw BodyParserError.ContentBody } } mutating func inContentBodyStarted() throws { if stream[index] == 13 { index += 1 entryParser = .LineFeed linedUpParser = .ContentData } else { throw BodyParserError.ContentBody } } mutating func inContentData() throws { tokenIndex = index entryParser = .ContentDataStarted boundaryMatch = [] boundTest1 = false boundTest2 = false boundTest3 = false if !fileNameValue.isEmpty || !contentTypeValue.isEmpty { tempFile = try TempFile(prefix: "bodyparser", suffix: "upload") } } mutating func storeFile(endIndex: Int) { let bb = collectToken(endIndex: endIndex) let _ = tempFile!.writeBytes(bytes: bb, maxBytes: bb.count) if let oldFile = body.files[nameValue] { (oldFile.file as! TempFile).closeAndUnlink() } body.files[nameValue] = BodyFile(name: fileNameValue, contentType: contentTypeValue, file: tempFile!) tempFile = nil } mutating func inContentDataStarted() throws { var i = index let len = length let blasti = boundary.count - 1 repeat { let c = stream[i] if boundTest3 && c == 13 { let ei = i - boundary.count - 5 if tempFile != nil { storeFile(endIndex: ei) } else { body.fields[nameValue] = collectString(endIndex: ei) } index = length // Body exit. done = true break } boundTest3 = false if boundTest2 && c == 45 { boundTest3 = true } boundTest2 = false if boundTest1 { if c == 13 { let ei = i - boundary.count - 3 if tempFile != nil { storeFile(endIndex: ei) } else { body.fields[nameValue] = collectString(endIndex: ei) } entryParser = .LineFeed linedUpParser = .ContentDisposition index = i + 1 break } else if c == 45 { boundTest2 = true } } boundTest1 = false if BoundaryCharTable.TABLE[c] { var a = [Int]() for j in 0..<boundaryMatch.count { let m = boundaryMatch[j] if boundary.match(index: m, c: c) { if m == blasti { boundTest1 = true } else { a.append(m + 1) } } } if c == 45 { a.append(1) } boundaryMatch = a } i += 1 } while i < len if i >= len { index = i } } mutating func inSpace() throws { while index < length && stream[index] == 32 { index += 1 } entryParser = linedUpParser } // Call this to help remove the temp files. // // When BodyParser is used from a main file, outside functions, temp files // could persist after the program has finished running. mutating func close() { for (_, bf) in body.files { (bf.file as! TempFile).closeAndUnlink() } } } enum BodyParserError: ErrorProtocol { case Body case Key case Value case BeginMultipart case MatchToken case ContentDisposition case LineFeed case FormData case Name case NameValue case FileName case FileNameValue case ContentBody case ContentTypeValue case Unreachable }