diff --git a/Package.swift b/Package.swift index ea7d1548..223c6207 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,7 @@ let package = Package( Target(name: "Content", dependencies: ["CYAJL", "Core"]), Target(name: "Crypto", dependencies: ["Core", "CArgon2"]), Target(name: "IO", dependencies: ["Core", "CDsock"]), + Target(name: "JWT", dependencies: ["Crypto", "Content"]), Target(name: "HTTP", dependencies: ["Content", "IO", "CHTTPParser"]), ], dependencies: [ diff --git a/Sources/Content/Content/Content.swift b/Sources/Content/Content/Content.swift index e39e7f72..4916f8d7 100644 --- a/Sources/Content/Content/Content.swift +++ b/Sources/Content/Content/Content.swift @@ -11,6 +11,13 @@ public protocol Content { func serialize(to writable: Writable, deadline: Deadline) throws } +extension Content { + public static func parse(_ buffer: UnsafeRawBufferPointer, deadline: Deadline) throws -> Self { + let readable = BufferReadable(buffer: buffer) + return try parse(from: readable, deadline: deadline) + } +} + public protocol ContentConvertible { static var contentTypes: ContentTypes { get } } diff --git a/Sources/Content/JSON/JSON.swift b/Sources/Content/JSON/JSON.swift index e31164cf..1d4ec13e 100644 --- a/Sources/Content/JSON/JSON.swift +++ b/Sources/Content/JSON/JSON.swift @@ -1,9 +1,9 @@ import Foundation public enum JSONError : Error { - case noContent(type: JSONInitializable.Type) - case cannotInitializeWithContent(type: JSONInitializable.Type, content: Content.Type) - case cannotInitialize(type: JSONInitializable.Type, json: JSON) + case noContent(type: Any.Type) + case cannotInitializeWithContent(type: Any.Type, content: Content.Type) + case cannotInitialize(type: Any.Type, json: JSON) case valueNotArray(indexPath: [IndexPathComponentValue], json: JSON) case outOfBounds(indexPath: [IndexPathComponentValue], json: JSON) case valueNotDictionary(indexPath: [IndexPathComponentValue], json: JSON) @@ -39,7 +39,7 @@ public enum JSON { case int(Int) case string(String) case array([JSON]) - case dictionary([String: JSON]) + case object([String: JSON]) } extension JSON { @@ -75,7 +75,7 @@ extension JSON { dictionary[key] = value } - self = .dictionary(dictionary) + self = .object(dictionary) } else { self = .null } @@ -90,7 +90,7 @@ extension JSON { dictionary[key] = value } - self = .dictionary(dictionary) + self = .object(dictionary) } else { self = .null } @@ -138,15 +138,15 @@ extension String : IndexPathComponent { extension JSON { public func get(_ indexPath: IndexPathComponent...) throws -> T { - let content = try get(indexPath) + let content = try _get(indexPath as [IndexPathComponent]) return try T(json: content) } public func get(_ indexPath: IndexPathComponent...) throws -> JSON { - return try get(indexPath) + return try _get(indexPath as [IndexPathComponent]) } - private func get(_ indexPath: [IndexPathComponent]) throws -> JSON { + private func _get(_ indexPath: [IndexPathComponent]) throws -> JSON { var value = self var visited: [IndexPathComponentValue] = [] @@ -165,7 +165,7 @@ extension JSON { value = array[index] case let .key(key): - guard case let .dictionary(dictionary) = value else { + guard case let .object(dictionary) = value else { throw JSONError.valueNotDictionary(indexPath: visited, json: self) } @@ -191,7 +191,7 @@ extension JSON : Equatable { case let (.string(l), .string(r)) where l == r: return true case let (.double(l), .double(r)) where l == r: return true case let (.array(l), .array(r)) where l == r: return true - case let (.dictionary(l), .dictionary(r)) where l == r: return true + case let (.object(l), .object(r)) where l == r: return true default: return false } } @@ -244,21 +244,21 @@ extension JSON : ExpressibleByStringLiteral { extension JSON : ExpressibleByArrayLiteral { /// :nodoc: - public init(arrayLiteral elements: JSONRepresentable...) { + public init(arrayLiteral elements: JSON...) { self = .array(elements.map({ $0.json() })) } } extension JSON : ExpressibleByDictionaryLiteral { /// :nodoc: - public init(dictionaryLiteral elements: (String, JSONRepresentable)...) { + public init(dictionaryLiteral elements: (String, JSON)...) { var dictionary = [String: JSON](minimumCapacity: elements.count) for (key, value) in elements { dictionary[key] = value.json() } - self = .dictionary(dictionary) + self = .object(dictionary) } } @@ -300,7 +300,7 @@ extension JSON : CustomStringConvertible { case .int(let number): return String(number) case .string(let string): return escape(string) case .array(let array): return serialize(array: array) - case .dictionary(let dictionary): return serialize(dictionary: dictionary) + case .object(let dictionary): return serialize(dictionary: dictionary) } } diff --git a/Sources/Content/JSON/JSONConvertible.swift b/Sources/Content/JSON/JSONConvertible.swift index fc0a1393..754fd9fc 100644 --- a/Sources/Content/JSON/JSONConvertible.swift +++ b/Sources/Content/JSON/JSONConvertible.swift @@ -1,23 +1,41 @@ import struct Foundation.Data import struct Foundation.UUID -public protocol JSONInitializable { +public protocol JSONInitializable : ContentConvertible { init(json: JSON) throws } -public protocol JSONRepresentable { +extension JSONInitializable { + public static var contentTypes: ContentTypes { + return [ + ContentType(Self.init(json:)) + ] + } +} + +public protocol JSONRepresentable : ContentConvertible { func json() -> JSON } -public protocol JSONConvertible : ContentConvertible, JSONInitializable, JSONRepresentable {} +extension JSONRepresentable { + public static var contentTypes: ContentTypes { + return [ + ContentType(Self.json) + ] + } +} + +public protocol JSONConvertible : JSONInitializable, JSONRepresentable {} extension JSONConvertible { - static var contentTypes: ContentTypes { - return [ContentType(Self.init(json:), Self.json)] + public static var contentTypes: ContentTypes { + return [ + ContentType(Self.init(json:), Self.json) + ] } } -extension JSON : JSONInitializable, JSONRepresentable { +extension JSON : JSONConvertible { public init(json: JSON) throws { self = json } @@ -27,7 +45,7 @@ extension JSON : JSONInitializable, JSONRepresentable { } } -extension Int : JSONInitializable, JSONRepresentable { +extension Int : JSONConvertible { public init(json: JSON) throws { guard case let .int(value) = json else { throw JSONError.cannotInitialize(type: type(of: self), json: json) @@ -41,7 +59,7 @@ extension Int : JSONInitializable, JSONRepresentable { } } -extension Bool : JSONInitializable, JSONRepresentable { +extension Bool : JSONConvertible { public init(json: JSON) throws { guard case let .bool(value) = json else { throw JSONError.cannotInitialize(type: type(of: self), json: json) @@ -55,7 +73,7 @@ extension Bool : JSONInitializable, JSONRepresentable { } } -extension String : JSONInitializable, JSONRepresentable { +extension String : JSONConvertible { public init(json: JSON) throws { guard case let .string(value) = json else { throw JSONError.cannotInitialize(type: type(of: self), json: json) @@ -69,7 +87,7 @@ extension String : JSONInitializable, JSONRepresentable { } } -extension Double : JSONInitializable, JSONRepresentable { +extension Double : JSONConvertible { public init(json: JSON) throws { guard case let .double(value) = json else { throw JSONError.cannotInitialize(type: type(of: self), json: json) @@ -83,7 +101,7 @@ extension Double : JSONInitializable, JSONRepresentable { } } -extension UUID : JSONInitializable, JSONRepresentable { +extension UUID : JSONConvertible { public init(json: JSON) throws { guard case let .string(value) = json, let uuid = UUID(uuidString: value) else { throw JSONError.cannotInitialize(type: type(of: self), json: json) diff --git a/Sources/Content/JSON/JSONParser.swift b/Sources/Content/JSON/JSONParser.swift index 08616aa4..a9f2d0c2 100755 --- a/Sources/Content/JSON/JSONParser.swift +++ b/Sources/Content/JSON/JSONParser.swift @@ -114,9 +114,9 @@ public final class JSONParser { } if stack.count == 0 || final { - switch state.content { - case .dictionary(let value): - result = value["root"] + switch state.json { + case .object(let object): + result = object["root"] default: break } @@ -176,7 +176,7 @@ public final class JSONParser { } var previousState = stack.removeLast() - let result: Int32 = previousState.append(state.content) + let result: Int32 = previousState.append(state.json) state = previousState return result } @@ -193,7 +193,7 @@ public final class JSONParser { } var previousState = stack.removeLast() - let result: Int32 = previousState.append(state.content) + let result: Int32 = previousState.append(state.json) state = previousState return result } @@ -203,24 +203,24 @@ fileprivate struct JSONParserState { let isDictionary: Bool var dictionaryKey: String = "" - var content: JSON { + var json: JSON { if isDictionary { - return .dictionary(dictionary) + return .object(object) } else { return .array(array) } } - private var dictionary: [String: JSON] + private var object: [String: JSON] private var array: [JSON] init(dictionary: Bool) { self.isDictionary = dictionary if dictionary { - self.dictionary = Dictionary(minimumCapacity: 32) + self.object = Dictionary(minimumCapacity: 32) self.array = [] } else { - self.dictionary = [:] + self.object = [:] self.array = [] self.array.reserveCapacity(32) } @@ -228,7 +228,7 @@ fileprivate struct JSONParserState { mutating func append(_ value: Bool) -> Int32 { if isDictionary { - dictionary[dictionaryKey] = .bool(value) + object[dictionaryKey] = .bool(value) } else { array.append(.bool(value)) } @@ -238,7 +238,7 @@ fileprivate struct JSONParserState { mutating func append(_ value: Int64) -> Int32 { if isDictionary { - dictionary[self.dictionaryKey] = .int(Int(value)) + object[self.dictionaryKey] = .int(Int(value)) } else { array.append(.int(Int(value))) } @@ -248,7 +248,7 @@ fileprivate struct JSONParserState { mutating func append(_ value: Double) -> Int32 { if isDictionary { - dictionary[dictionaryKey] = .double(value) + object[dictionaryKey] = .double(value) } else { array.append(.double(value)) } @@ -258,7 +258,7 @@ fileprivate struct JSONParserState { mutating func append(_ value: String) -> Int32 { if isDictionary { - dictionary[dictionaryKey] = .string(value) + object[dictionaryKey] = .string(value) } else { array.append(.string(value)) } @@ -268,7 +268,7 @@ fileprivate struct JSONParserState { mutating func appendNull() -> Int32 { if isDictionary { - dictionary[dictionaryKey] = .null + object[dictionaryKey] = .null } else { array.append(.null) } @@ -278,7 +278,7 @@ fileprivate struct JSONParserState { mutating func append(_ value: JSON) -> Int32 { if isDictionary { - dictionary[dictionaryKey] = value + object[dictionaryKey] = value } else { array.append(value) } diff --git a/Sources/Content/JSON/JSONSerializer.swift b/Sources/Content/JSON/JSONSerializer.swift index 5803c8cc..67bd11bd 100755 --- a/Sources/Content/JSON/JSONSerializer.swift +++ b/Sources/Content/JSON/JSONSerializer.swift @@ -56,8 +56,8 @@ public final class JSONSerializer { try generate(string) case .array(let array): try generate(array, body: body) - case .dictionary(let dictionary): - try generate(dictionary, body: body) + case .object(let object): + try generate(object, body: body) } try write(highwater: bufferSize, body: body) diff --git a/Sources/Content/PlainText/PlainText.swift b/Sources/Content/PlainText/PlainText.swift index 854a874d..55aa3310 100644 --- a/Sources/Content/PlainText/PlainText.swift +++ b/Sources/Content/PlainText/PlainText.swift @@ -18,13 +18,7 @@ public protocol PlainTextRepresentable { func plainText() -> PlainText } -public protocol PlainTextConvertible : ContentConvertible, PlainTextInitializable, PlainTextRepresentable {} - -extension PlainTextConvertible { - static var contentTypes: ContentTypes { - return [ContentType(Self.init(plainText:), Self.plainText)] - } -} +public protocol PlainTextConvertible : PlainTextInitializable, PlainTextRepresentable {} extension PlainText : PlainTextInitializable { public init(plainText: PlainText) throws { diff --git a/Sources/Content/XML/XML.swift b/Sources/Content/XML/XML.swift index 2ba127b4..aea42fd3 100644 --- a/Sources/Content/XML/XML.swift +++ b/Sources/Content/XML/XML.swift @@ -106,15 +106,15 @@ public final class XML { } public func get(_ indexPath: IndexPathComponent...) throws -> XML { - return try get(indexPath) + return try _get(indexPath as [IndexPathComponent]) } public func get(_ indexPath: IndexPathComponent...) throws -> [XML] { - return try get(indexPath) + return try _get(indexPath as [IndexPathComponent]) } - func get(_ indexPath: [IndexPathComponent]) throws -> XML { - let elements: [XML] = try get(indexPath) + internal func _get(_ indexPath: [IndexPathComponent]) throws -> XML { + let elements: [XML] = try _get(indexPath) guard elements.count == 1, let element = elements.first else { throw XMLError.valueNotFound(indexPath: [], content: "") @@ -123,7 +123,7 @@ public final class XML { return element } - func get(_ indexPath: [IndexPathComponent]) throws -> [XML] { + internal func _get(_ indexPath: [IndexPathComponent]) throws -> [XML] { var value = [self] var single = true var visited: [IndexPathComponentValue] = [] diff --git a/Sources/Content/XML/XMLConvertible.swift b/Sources/Content/XML/XMLConvertible.swift index a92395d5..cc5f5608 100644 --- a/Sources/Content/XML/XMLConvertible.swift +++ b/Sources/Content/XML/XMLConvertible.swift @@ -46,12 +46,12 @@ public extension XML { } func get(_ indexPath: IndexPathComponent...) throws -> T { - let element: XML = try get(indexPath) + let element: XML = try _get(indexPath as [IndexPathComponent]) return try T(xml: element) } func get(_ indexPath: IndexPathComponent...) -> T? { - guard let element = try? get(indexPath) as XML else { + guard let element = try? _get(indexPath as [IndexPathComponent]) as XML else { return nil } @@ -59,7 +59,7 @@ public extension XML { } func get(_ indexPath: IndexPathComponent...) throws -> [T] { - return try get(indexPath).map({ try T(xml: $0) }) + return try _get(indexPath as [IndexPathComponent]).map({ try T(xml: $0) }) } } diff --git a/Sources/Core/Environment/Environment.swift b/Sources/Core/Environment/Environment.swift new file mode 100644 index 00000000..81a23fbf --- /dev/null +++ b/Sources/Core/Environment/Environment.swift @@ -0,0 +1,144 @@ +import Foundation + +public enum EnvironmentError : Error { + case valueNotFound(key: String, variables: [String: String]) + case cannotInitialize(type: LosslessStringConvertible.Type, variable: String) +} + +extension EnvironmentError : CustomStringConvertible { + /// :nodoc: + public var description: String { + switch self { + case let .valueNotFound(key, variables): + // TODO: pretty print the dictionary, like: + // key1: value1 + // key2: value2 + // key3: value3 + return "Cannot get variable for key \"\(key)\". Key is not present in variables \(variables)." + case let .cannotInitialize(type, variable): + return "Cannot initialize type \"\(String(describing: type))\" with variable \"\(variable)\"." + } + } +} + +public struct Environment { + public static var path = "" + public static var dotEnvEncoding: String.Encoding = .utf8 + + public static var variables: [String: String] = { + var variables = ProcessInfo.processInfo.environment + let filePath = path == "" ? packageDirectory + "/.env" : path + + guard let fileHandle = FileHandle(forReadingAtPath: filePath) else { + return variables + } + + let data = fileHandle.readDataToEndOfFile() + + guard let content = String(data: data, encoding: .utf8) else { + return variables + } + + for (key, value) in parse(content) { + variables[key] = value + } + + return variables + }() + + public static func variable(_ key: String) throws -> String { + guard let variable = variables[key] else { + throw EnvironmentError.valueNotFound(key: key, variables: variables) + } + + return variable + } + + public static func variable(_ key: String) throws -> V { + let string = try variable(key) + + guard let variable = V(string) else { + throw EnvironmentError.cannotInitialize(type: V.self, variable: string) + } + + return variable + } + + static func parse(_ content: String) -> [String: String] { + var variables: [String: String] = [:] + + let regex = try! NSRegularExpression( + pattern: "^\\s*([\\w\\.\\-]+)\\s*=\\s*(.*)?\\s*$" + ) + + for line in content.components(separatedBy: "\n") { + let matches = regex.matches( + in: line, + range: NSRange(location: 0, length: line.utf16.count) + ) + + guard let match = matches.first else { + continue + } + + guard let keyRange = match.rangeAt(1).range(for: line) else { + continue + } + + let key = line.substring(with: keyRange) + var value = "" + + if + match.numberOfRanges == 3, + let valueRange = match.rangeAt(2).range(for: line) + { + value = line.substring(with: valueRange) + } + + value = value.trimmingCharacters(in: .whitespaces) + + if + value.characters.count > 1, + value.characters.first == "\"", + value.characters.last == "\"" + { + value = value.replacingOccurrences( + of: "\\n", + with: "\n" + ) + + value.characters.removeFirst() + value.characters.removeLast() + } + + variables[key] = value + } + + return variables + } + + public static var packageDirectory: String { + if #file.contains(".build") { + return #file.components(separatedBy: "/.build").first ?? System.workingDirectory + } + + if #file.contains("Packages") { + return #file.components(separatedBy: "/Packages").first ?? System.workingDirectory + } + + return #file.components(separatedBy: "/Sources").first ?? System.workingDirectory + } +} + +extension NSRange { + func range(for str: String) -> Range? { + guard location != NSNotFound else { return nil } + + guard let fromUTFIndex = str.utf16.index(str.utf16.startIndex, offsetBy: location, limitedBy: str.utf16.endIndex) else { return nil } + guard let toUTFIndex = str.utf16.index(fromUTFIndex, offsetBy: length, limitedBy: str.utf16.endIndex) else { return nil } + guard let fromIndex = String.Index(fromUTFIndex, within: str) else { return nil } + guard let toIndex = String.Index(toUTFIndex, within: str) else { return nil } + + return fromIndex ..< toIndex + } +} diff --git a/Sources/Core/Logger/Logger.swift b/Sources/Core/Logger/Logger.swift index eb417de1..4d2dbcb2 100755 --- a/Sources/Core/Logger/Logger.swift +++ b/Sources/Core/Logger/Logger.swift @@ -5,7 +5,6 @@ #endif public protocol LogAppender { - var name: String { get } var levels: Logger.Level { get } func append(event: Logger.Event) } @@ -31,7 +30,6 @@ public struct Logger { public let locationInfo: LocationInfo public let timestamp: Int public let level: Logger.Level - public let logger: Logger public var message: Any? public var error: Error? } @@ -55,20 +53,13 @@ public struct Logger { } } - public let name: String - public let appenders: [LogAppender] - - public init(name: String = "Logger", appenders: [LogAppender] = [StandardOutputAppender()]) { - self.appenders = appenders - self.name = name - } + public static var appenders: [LogAppender] = [StandardOutputAppender()] - public func log(level: Level, item: Any?, error: Error? = nil, locationInfo: LocationInfo) { + private static func log(level: Level, item: Any?, error: Error? = nil, locationInfo: LocationInfo) { let event = Event( locationInfo: locationInfo, - timestamp: timestamp, + timestamp: getTimestamp(), level: level, - logger: self, message: item, error: error ) @@ -78,7 +69,7 @@ public struct Logger { } } - public func log( + private static func log( level: Level, item: Any?, error: Error? = nil, @@ -100,7 +91,7 @@ public struct Logger { ) } - public func trace( + public static func trace( _ item: Any?, error: Error? = nil, file: String = #file, @@ -119,7 +110,7 @@ public struct Logger { ) } - public func trace( + public static func trace( _ item: Any?, error: Error? = nil, locationInfo: LocationInfo @@ -132,7 +123,7 @@ public struct Logger { ) } - public func debug( + public static func debug( _ item: Any?, error: Error? = nil, file: String = #file, @@ -151,7 +142,7 @@ public struct Logger { ) } - public func debug( + public static func debug( _ item: Any?, error: Error? = nil, locationInfo: LocationInfo @@ -164,7 +155,7 @@ public struct Logger { ) } - public func info( + public static func info( _ item: Any?, error: Error? = nil, file: String = #file, @@ -183,7 +174,7 @@ public struct Logger { ) } - public func info( + public static func info( _ item: Any?, error: Error? = nil, locationInfo: LocationInfo @@ -196,7 +187,7 @@ public struct Logger { ) } - public func warning( + public static func warning( _ item: Any?, error: Error? = nil, file: String = #file, @@ -215,7 +206,7 @@ public struct Logger { ) } - public func warning( + public static func warning( _ item: Any?, error: Error? = nil, locationInfo: LocationInfo @@ -228,7 +219,7 @@ public struct Logger { ) } - public func error( + public static func error( _ item: Any?, error: Error? = nil, file: String = #file, @@ -247,7 +238,7 @@ public struct Logger { ) } - public func error( + public static func error( _ item: Any?, error: Error? = nil, locationInfo: LocationInfo @@ -260,7 +251,7 @@ public struct Logger { ) } - public func fatal( + public static func fatal( _ item: Any?, error: Error? = nil, file: String = #file, @@ -279,7 +270,7 @@ public struct Logger { ) } - public func fatal( + public static func fatal( _ item: Any?, error: Error? = nil, locationInfo: LocationInfo @@ -292,13 +283,43 @@ public struct Logger { ) } - private var timestamp: Int { + private static func getTimestamp() -> Int { var time: timeval = timeval(tv_sec: 0, tv_usec: 0) gettimeofday(&time, nil) return time.tv_sec } } +extension Logger.Level : CustomStringConvertible { + public var description: String { + if self == .trace { + return "TRACE" + } + + if self == .debug { + return "DEBUG" + } + + if self == .info { + return "INFO" + } + + if self == .warning { + return "WARNING" + } + + if self == .error { + return "ERROR" + } + + if self == .fatal { + return "FATAL" + } + + return "" + } +} + extension Logger.LocationInfo : CustomStringConvertible { public var description: String { return "\(file):\(function):\(line):\(column)" diff --git a/Sources/Core/Logger/StandardOutputAppender.swift b/Sources/Core/Logger/StandardOutputAppender.swift index e84e448b..c6809f8b 100755 --- a/Sources/Core/Logger/StandardOutputAppender.swift +++ b/Sources/Core/Logger/StandardOutputAppender.swift @@ -1,15 +1,16 @@ public struct StandardOutputAppender : LogAppender { - public let name: String public let levels: Logger.Level - public init(name: String = "Standard Output Appender", levels: Logger.Level = .all) { - self.name = name + public init(levels: Logger.Level = .all) { self.levels = levels } public func append(event: Logger.Event) { var logMessage = "" + let level = event.level.description + + logMessage += level == "" ? "" : "[" + level + "]" logMessage += "[" + event.timestamp.description + "]" logMessage += "[" + event.locationInfo.description + "]" diff --git a/Sources/Core/Stream/BufferStream.swift b/Sources/Core/Stream/BufferStream.swift new file mode 100644 index 00000000..61ef177a --- /dev/null +++ b/Sources/Core/Stream/BufferStream.swift @@ -0,0 +1,36 @@ +#if os(Linux) + import Glibc +#else + import Darwin.C +#endif + +import Venice + +public final class BufferReadable : Readable { + var buffer: UnsafeRawBufferPointer + + public init(buffer: UnsafeRawBufferPointer) { + self.buffer = buffer + } + + public func read( + _ buffer: UnsafeMutableRawBufferPointer, + deadline: Deadline + ) throws -> UnsafeRawBufferPointer { + guard !buffer.isEmpty && !self.buffer.isEmpty else { + return UnsafeRawBufferPointer(start: nil, count: 0) + } + + let readCount = min(buffer.count, self.buffer.count) + memcpy(buffer.baseAddress, self.buffer.baseAddress, readCount) + let read = self.buffer.prefix(readCount) + self.buffer = self.buffer.suffix(from: readCount) + return UnsafeRawBufferPointer(read) + } +} + +extension BufferReadable { + public static var empty: BufferReadable = BufferReadable( + buffer: UnsafeRawBufferPointer(start: nil, count: 0) + ) +} diff --git a/Sources/Core/Stream/Stream.swift b/Sources/Core/Stream/Stream.swift index da65db14..abcbd1f3 100755 --- a/Sources/Core/Stream/Stream.swift +++ b/Sources/Core/Stream/Stream.swift @@ -1,6 +1,8 @@ import Venice +/// Representation of a type which binary data can be read from. public protocol Readable { + /// Read binary data into `buffer` timing out at `deadline`. func read( _ buffer: UnsafeMutableRawBufferPointer, deadline: Deadline @@ -22,7 +24,9 @@ public protocol ReadableStream : Readable { func close(deadline: Deadline) throws } +/// Representation of a type which binary data can be written to. public protocol Writable { + /// Read `buffer` timing out at `deadline`. func write(_ buffer: UnsafeRawBufferPointer, deadline: Deadline) throws } diff --git a/Sources/Core/System/System.swift b/Sources/Core/System/System.swift new file mode 100644 index 00000000..26eaa25e --- /dev/null +++ b/Sources/Core/System/System.swift @@ -0,0 +1,23 @@ +#if os(Linux) + import Glibc +#else + import Darwin.C +#endif + +public enum System { + public static var workingDirectory: String { + let buffer = UnsafeMutableRawBufferPointer.allocate(count: Int(PATH_MAX)) + + defer { + buffer.deallocate() + } + + let bufferPointer = buffer.baseAddress?.assumingMemoryBound(to: Int8.self) + + guard let cString = getcwd(bufferPointer, buffer.count) else { + return "./" + } + + return String(cString: cString) + } +} diff --git a/Sources/Core/SystemError/SystemError.swift b/Sources/Core/System/SystemError.swift similarity index 100% rename from Sources/Core/SystemError/SystemError.swift rename to Sources/Core/System/SystemError.swift diff --git a/Sources/Crypto/Crypto.swift b/Sources/Crypto/Crypto.swift index 391d88bd..6d4c71ee 100644 --- a/Sources/Crypto/Crypto.swift +++ b/Sources/Crypto/Crypto.swift @@ -1,9 +1,9 @@ import Core -import COpenSSL import CArgon2 +import COpenSSL public struct Crypto { - public static func hmacSHA256( + public static func hs256( _ string: String, key: UnsafeRawBufferPointer, buffer: UnsafeMutableRawBufferPointer @@ -34,17 +34,17 @@ public struct Crypto { return UnsafeRawBufferPointer(buffer.prefix(upTo: Int(bufferCount))) } - public static func hmacSHA256( + public static func hs256( _ string: String, key: BufferRepresentable, buffer: UnsafeMutableRawBufferPointer ) -> UnsafeRawBufferPointer { return key.withBuffer { - hmacSHA256(string, key: $0, buffer: buffer) + hs256(string, key: $0, buffer: buffer) } } - public static func hmacSHA256( + public static func hs256( _ string: String, key: BufferRepresentable ) -> B { @@ -54,7 +54,7 @@ public struct Crypto { buffer.deallocate() } - return B(hmacSHA256(string, key: key, buffer: buffer)) + return B(hs256(string, key: key, buffer: buffer)) } public static func sha256( @@ -116,4 +116,64 @@ public struct Crypto { return String(cString: encoded + [0]) } + + public static func base64Encode( + input: UnsafeRawBufferPointer, + buffer: UnsafeMutableRawBufferPointer + ) { + let b64 = BIO_new(BIO_f_base64()) + let bio = BIO_new(BIO_s_mem()) + + defer { + BIO_free(b64) + } + + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL) + BIO_push(b64, bio) + + var result = BIO_write(b64, input.baseAddress, Int32(input.count)) + BIO_ctrl(b64, BIO_CTRL_FLUSH, 0, nil) + + guard result > 0 else { + // throw error + return + } + + result = BIO_read(bio, buffer.baseAddress, Int32(buffer.count)) + + guard result > 0 else { + // throw error + return + } + } + + public static func base64Decode( + input: UnsafeRawBufferPointer, + buffer: UnsafeMutableRawBufferPointer + ) { + let b64 = BIO_new(BIO_f_base64()) + let bio = BIO_new(BIO_s_mem()) + + defer { + BIO_free(b64) + } + + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL) + BIO_push(b64, bio) + + var result = BIO_write(bio, input.baseAddress, Int32(input.count)) + BIO_ctrl(bio, BIO_CTRL_FLUSH, 0, nil) + + guard result > 0 else { + // throw error + return + } + + result = BIO_read(b64, buffer.baseAddress, Int32(buffer.count)) + + guard result > 0 else { + // throw error + return + } + } } diff --git a/Sources/HTTP/Client/Client.swift b/Sources/HTTP/Client/Client.swift index 842ca17a..de91568f 100644 --- a/Sources/HTTP/Client/Client.swift +++ b/Sources/HTTP/Client/Client.swift @@ -56,15 +56,10 @@ open class Client { private let host: String private let port: Int - private let logger: Logger private let pool: Pool /// Creates a new HTTP client - public init( - uri: String, - logger: Logger = defaultLogger, - configuration: Configuration = .default - ) throws { + public init(uri: String, configuration: Configuration = .default) throws { let uri = try URI(uri) var secure = true @@ -89,7 +84,6 @@ open class Client { self.host = host self.port = port - self.logger = logger self.configuration = configuration self.pool = try Pool(size: configuration.poolSize) { @@ -133,10 +127,6 @@ open class Client { pool.close() } - private static var defaultLogger: Logger { - return Logger(name: "HTTP client") - } - public func send(_ request: Request) throws -> Response { var retryCount = 0 diff --git a/Sources/HTTP/Message/Body.swift b/Sources/HTTP/Message/Body.swift index ab9d9cbe..d641f12d 100644 --- a/Sources/HTTP/Message/Body.swift +++ b/Sources/HTTP/Message/Body.swift @@ -2,14 +2,14 @@ import Core public enum Body { public typealias Write = (Writable) throws -> Void - + case readable(Readable) case writable(Write) } extension Body { public static var empty: Body { - return .writable({ _ in }) + return .readable(BufferReadable.empty) } } diff --git a/Sources/HTTP/Message/Headers.swift b/Sources/HTTP/Message/Headers.swift index 47415995..60fb10c4 100755 --- a/Sources/HTTP/Message/Headers.swift +++ b/Sources/HTTP/Message/Headers.swift @@ -1,86 +1,25 @@ import Core import Foundation -public struct HeaderField { - public let string: String - - public init(_ string: String) { - self.string = string - } -} - -extension UTF8.CodeUnit { - fileprivate func lowercased() -> UTF8.CodeUnit { - let isUppercase = self >= 65 && self <= 90 - - if isUppercase { - return self + 32 - } - - return self - } -} - -extension String { - fileprivate func caseInsensitiveCompare(_ other: String) -> Bool { - if self.utf8.count != other.utf8.count { - return false - } - - for (lhs, rhs) in zip(self.utf8, other.utf8) { - if lhs.lowercased() != rhs.lowercased() { - return false - } - } - - return true - } -} - -extension HeaderField : Hashable { - public var hashValue: Int { - return string.hashValue - } - - public static func == (lhs: HeaderField, rhs: HeaderField) -> Bool { - if lhs.string == rhs.string { - return true - } - - return lhs.string.caseInsensitiveCompare(rhs.string) - } -} - -extension HeaderField : ExpressibleByStringLiteral { - public init(stringLiteral string: String) { - self.init(string) - } - - public init(extendedGraphemeClusterLiteral string: String){ - self.init(string) - } - - public init(unicodeScalarLiteral string: String){ - self.init(string) - } -} - -extension HeaderField : CustomStringConvertible { - public var description: String { - return string - } -} - public struct Headers { - public var headers: [HeaderField: String] + fileprivate var headers: [Field: String] - public init(_ headers: [HeaderField: String]) { + public init(_ headers: [Field: String]) { self.headers = headers } - public var fields: [HeaderField] { + public var fields: [Field] { return Array(headers.keys) } + + public struct Field { + public let original: String + + public init(_ original: String) { + self.original = original + } + } + } extension Headers { @@ -90,8 +29,8 @@ extension Headers { } extension Headers : ExpressibleByDictionaryLiteral { - public init(dictionaryLiteral elements: (HeaderField, String)...) { - var headers: [HeaderField: String] = [:] + public init(dictionaryLiteral elements: (Field, String)...) { + var headers: [Field: String] = [:] for (key, value) in elements { headers[key] = value @@ -102,7 +41,7 @@ extension Headers : ExpressibleByDictionaryLiteral { } extension Headers : Sequence { - public func makeIterator() -> DictionaryIterator { + public func makeIterator() -> DictionaryIterator { return headers.makeIterator() } @@ -114,7 +53,7 @@ extension Headers : Sequence { return headers.isEmpty } - public subscript(field: HeaderField) -> String? { + public subscript(field: Field) -> String? { get { return headers[field] } @@ -126,11 +65,11 @@ extension Headers : Sequence { public subscript(field: String) -> String? { get { - return self[HeaderField(field)] + return self[Field(field)] } set(header) { - self[HeaderField(field)] = header + self[Field(field)] = header } } } @@ -147,8 +86,70 @@ extension Headers : CustomStringConvertible { } } -extension Headers : Equatable {} +extension Headers : Equatable { + public static func == (lhs: Headers, rhs: Headers) -> Bool { + return lhs.headers == rhs.headers + } +} + +extension Headers.Field : Hashable { + public var hashValue: Int { + return original.hashValue + } + + public static func == (lhs: Headers.Field, rhs: Headers.Field) -> Bool { + if lhs.original == rhs.original { + return true + } + + return lhs.original.caseInsensitiveCompare(rhs.original) + } +} + +extension Headers.Field : ExpressibleByStringLiteral { + public init(stringLiteral string: String) { + self.init(string) + } + + public init(extendedGraphemeClusterLiteral string: String){ + self.init(string) + } + + public init(unicodeScalarLiteral string: String){ + self.init(string) + } +} -public func == (lhs: Headers, rhs: Headers) -> Bool { - return lhs.headers == rhs.headers +extension Headers.Field : CustomStringConvertible { + public var description: String { + return original + } +} + +extension UTF8.CodeUnit { + fileprivate func lowercased() -> UTF8.CodeUnit { + let isUppercase = self >= 65 && self <= 90 + + if isUppercase { + return self + 32 + } + + return self + } +} + +extension String { + fileprivate func caseInsensitiveCompare(_ other: String) -> Bool { + if self.utf8.count != other.utf8.count { + return false + } + + for (lhs, rhs) in zip(self.utf8, other.utf8) { + if lhs.lowercased() != rhs.lowercased() { + return false + } + } + + return true + } } diff --git a/Sources/HTTP/Message/Message.swift b/Sources/HTTP/Message/Message.swift index 462c55de..15524efd 100755 --- a/Sources/HTTP/Message/Message.swift +++ b/Sources/HTTP/Message/Message.swift @@ -3,12 +3,14 @@ import Content import Venice // TODO: Make error CustomStringConvertible and ResponseRepresentable -public enum MessageContentError : Error { +public enum MessageError : Error { case noReadableBody case noContentTypeHeader case unsupportedMediaType case noDefaultContentType case notContentRepresentable + case valueNotFound(key: String) + case incompatibleType(requestedType: Any.Type, actualType: Any.Type) } public typealias Storage = [String: Any] @@ -21,6 +23,22 @@ public protocol Message : class { } extension Message { + public func set(_ value: Any?, key: String) { + storage[key] = value + } + + public func get(_ key: String) throws -> T { + guard let value = storage[key] else { + throw MessageError.valueNotFound(key: key) + } + + guard let castedValue = value as? T else { + throw MessageError.incompatibleType(requestedType: T.self, actualType: type(of: value)) + } + + return castedValue + } + public var contentType: MediaType? { get { return headers["Content-Type"].flatMap({try? MediaType(string: $0)}) @@ -77,15 +95,15 @@ extension Message { public func content(deadline: Deadline = 5.minutes.fromNow()) throws -> C { guard let mediaType = self.contentType else { - throw MessageContentError.noContentTypeHeader + throw MessageError.noContentTypeHeader } guard mediaType == C.mediaType else { - throw MessageContentError.unsupportedMediaType + throw MessageError.unsupportedMediaType } guard let readable = body.readable else { - throw MessageContentError.noReadableBody + throw MessageError.noReadableBody } return try C.parse(from: readable, deadline: deadline) @@ -93,11 +111,11 @@ extension Message { public func content(deadline: Deadline = 5.minutes.fromNow()) throws -> C { guard let mediaType = self.contentType else { - throw MessageContentError.noContentTypeHeader + throw MessageError.noContentTypeHeader } guard let readable = body.readable else { - throw MessageContentError.noReadableBody + throw MessageError.noReadableBody } for contentType in C.contentTypes where contentType.mediaType.matches(other: mediaType) { @@ -110,6 +128,6 @@ extension Message { return initializer } - throw MessageContentError.unsupportedMediaType + throw MessageError.unsupportedMediaType } } diff --git a/Sources/HTTP/Parameters/Parameters.swift b/Sources/HTTP/Parameters/Parameters.swift index f9dd301a..4432e67d 100755 --- a/Sources/HTTP/Parameters/Parameters.swift +++ b/Sources/HTTP/Parameters/Parameters.swift @@ -20,3 +20,23 @@ extension ParametersError : CustomStringConvertible { public protocol ParametersInitializable { init(parameters: URI.Parameters) throws } + +extension URI.Parameters { + public func get(_ key: String) throws -> String { + guard let string = parameters[key] else { + throw ParametersError.valueNotFound(key: key, parameters: self) + } + + return string + } + + public func get

(_ key: String) throws -> P { + let string = try get(key) + + guard let parameter = P(string) else { + throw ParametersError.cannotInitialize(type: P.self, parameter: string) + } + + return parameter + } +} diff --git a/Sources/HTTP/Parser/Parser.swift b/Sources/HTTP/Parser/Parser.swift index 8d151d21..f103b825 100644 --- a/Sources/HTTP/Parser/Parser.swift +++ b/Sources/HTTP/Parser/Parser.swift @@ -66,7 +66,7 @@ internal class Parser { var uri: URI? var status: Response.Status? = nil var headers: Headers = [:] - var currentHeaderField: HeaderField? + var currentHeaderField: Headers.Field? weak var bodyStream: BodyStream? @@ -198,7 +198,7 @@ internal class Parser { return String(cString: pointer.baseAddress!) } - context.currentHeaderField = HeaderField(string) + context.currentHeaderField = Headers.Field(string) case .headerValue: bytes.append(0) diff --git a/Sources/HTTP/Request/Request.swift b/Sources/HTTP/Request/Request.swift index c313bd89..bb2b2768 100644 --- a/Sources/HTTP/Request/Request.swift +++ b/Sources/HTTP/Request/Request.swift @@ -2,8 +2,6 @@ import Core import Content import Venice -import struct Foundation.URL - public final class Request : Message { public typealias UpgradeConnection = (Response, DuplexStream) throws -> Void @@ -161,7 +159,7 @@ extension Request { return } - throw MessageContentError.unsupportedMediaType + throw MessageError.unsupportedMediaType } public convenience init( @@ -172,11 +170,11 @@ extension Request { timeout: Duration = 5.minutes ) throws { guard let contentType = C.contentTypes.default else { - throw MessageContentError.noDefaultContentType + throw MessageError.noDefaultContentType } guard let content = contentType.represent?(content)() else { - throw MessageContentError.notContentRepresentable + throw MessageError.notContentRepresentable } try self.init( @@ -190,6 +188,10 @@ extension Request { } extension Request { + public var path: String { + return uri.path! + } + public var accept: [MediaType] { get { return headers["Accept"].map({ MediaType.parse(acceptHeader: $0) }) ?? [] @@ -242,11 +244,11 @@ extension Request { } guard let contentType = C.contentTypes.default else { - throw MessageContentError.noDefaultContentType + throw MessageError.noDefaultContentType } guard let content = contentType.represent?(content)() else { - throw MessageContentError.notContentRepresentable + throw MessageError.notContentRepresentable } return content diff --git a/Sources/HTTP/Response/Response.swift b/Sources/HTTP/Response/Response.swift index cc6cbb0b..3687bc6c 100644 --- a/Sources/HTTP/Response/Response.swift +++ b/Sources/HTTP/Response/Response.swift @@ -28,6 +28,7 @@ public final class Response : Message { self.body = body } + // TODO: Check http://www.iana.org/assignments/http-status-codes public enum Status { case `continue` case switchingProtocols @@ -194,7 +195,7 @@ extension Response { return } - throw MessageContentError.unsupportedMediaType + throw MessageError.unsupportedMediaType } public convenience init( @@ -204,11 +205,11 @@ extension Response { timeout: Duration = 5.minutes ) throws { guard let contentType = C.contentTypes.default else { - throw MessageContentError.noDefaultContentType + throw MessageError.noDefaultContentType } guard let content = contentType.represent?(content)() else { - throw MessageContentError.notContentRepresentable + throw MessageError.notContentRepresentable } self.init( @@ -218,6 +219,20 @@ extension Response { timeout: timeout ) } + + public convenience init( + status: Status, + headers: Headers = [:], + content: C, + timeout: Duration = 5.minutes + ) throws { + self.init( + status: status, + headers: headers, + content: content as Content, + timeout: timeout + ) + } } extension Response : CustomStringConvertible { diff --git a/Sources/HTTP/Serializer/Serializer.swift b/Sources/HTTP/Serializer/Serializer.swift index 3bc1e0a8..512206eb 100644 --- a/Sources/HTTP/Serializer/Serializer.swift +++ b/Sources/HTTP/Serializer/Serializer.swift @@ -68,8 +68,8 @@ internal class Serializer { internal func serializeHeaders(_ message: Message, deadline: Deadline) throws { var header = "" - for (name, value) in message.headers.headers { - header += name.string + for (name, value) in message.headers { + header += name.description header += ": " header += value header += "\r\n" diff --git a/Sources/HTTP/Server/Server.swift b/Sources/HTTP/Server/Server.swift index 9acc7aff..138f4298 100755 --- a/Sources/HTTP/Server/Server.swift +++ b/Sources/HTTP/Server/Server.swift @@ -18,14 +18,12 @@ public final class Server { /// Close connection timeout public let closeConnectionTimeout: Duration - private let logger: Logger private let header: String private let group = Coroutine.Group() private let respond: Respond /// Creates a new HTTP server public init( - logger: Logger = defaultLogger, header: String = defaultHeader, parserBufferSize: Int = 4096, serializerBufferSize: Int = 4096, @@ -34,7 +32,6 @@ public final class Server { closeConnectionTimeout: Duration = 1.minute, respond: @escaping Respond ) { - self.logger = logger self.header = header self.parserBufferSize = parserBufferSize self.serializerBufferSize = serializerBufferSize @@ -86,13 +83,13 @@ public final class Server { do { try accept(host) } catch SystemError.tooManyOpenFiles { - logger.info("Too many open files while accepting connections. Retrying in 10 seconds.") + Logger.info("Too many open files while accepting connections. Retrying in 10 seconds.") try Coroutine.wakeUp(10.seconds.fromNow()) continue } catch VeniceError.canceledCoroutine { break } catch { - logger.error("Error while accepting connections.", error: error) + Logger.error("Error while accepting connections.", error: error) throw error } } @@ -100,14 +97,10 @@ public final class Server { /// Stop server public func stop() throws { - self.logger.info("Stopping HTTP server.") + Logger.info("Stopping HTTP server.") group.cancel() } - private static var defaultLogger: Logger { - return Logger(name: "HTTP server") - } - private static var defaultHeader: String { var header = "\n" header += " _____ \n" @@ -124,7 +117,7 @@ public final class Server { private func log(host: String, port: Int, locationInfo: Logger.LocationInfo) { var header = self.header header += "Started HTTP server at \(host), listening on port \(port)." - logger.info(header, locationInfo: locationInfo) + Logger.info(header, locationInfo: locationInfo) } @inline(__always) @@ -141,7 +134,7 @@ public final class Server { } catch VeniceError.canceledCoroutine { return } catch { - self.logger.error("Error while processing connection.", error: error) + Logger.error("Error while processing connection.", error: error) } try stream.close(deadline: self.closeConnectionTimeout.fromNow()) diff --git a/Sources/HTTP/URI/URI.swift b/Sources/HTTP/URI/URI.swift index d75c17ac..68b7d48c 100644 --- a/Sources/HTTP/URI/URI.swift +++ b/Sources/HTTP/URI/URI.swift @@ -88,7 +88,7 @@ public struct URI { } extension URI { - public mutating func set(parameter: String, for key: String) { + public mutating func set(parameter: String, key: String) { params.parameters[key] = parameter } @@ -97,21 +97,11 @@ extension URI { } public func parameter(_ key: String) throws -> String { - guard let string = params.parameters[key] else { - throw ParametersError.valueNotFound(key: key, parameters: params) - } - - return string + return try params.get(key) } public func parameter

(_ key: String) throws -> P { - let string = try parameter(key) - - guard let parameter = P(string) else { - throw ParametersError.cannotInitialize(type: P.self, parameter: string) - } - - return parameter + return try params.get(key) } } diff --git a/Sources/JWT/HS256.swift b/Sources/JWT/HS256.swift new file mode 100644 index 00000000..0387f0b3 --- /dev/null +++ b/Sources/JWT/HS256.swift @@ -0,0 +1,17 @@ +import Crypto + +extension JWT.Algorithm { + public static func hs256(key: String) -> JWT.Algorithm { + return JWT.Algorithm( + name: "HS256", + sign: { message in + Crypto.hs256(message, key: key) + }, + verify: { signature, message in + guard signature == Crypto.hs256(message, key: key) else { + throw JWTError.invalidSignature + } + } + ) + } +} diff --git a/Sources/JWT/Insecure.swift b/Sources/JWT/Insecure.swift new file mode 100644 index 00000000..a4bf06c7 --- /dev/null +++ b/Sources/JWT/Insecure.swift @@ -0,0 +1,13 @@ +import Foundation + +extension JWT.Algorithm { + public static var insecure: JWT.Algorithm = JWT.Algorithm( + name: "none", + sign: { _ in Data() }, + verify: { signature, message in + guard signature.isEmpty else { + throw JWTError.invalidSignature + } + } + ) +} diff --git a/Sources/JWT/JWT.swift b/Sources/JWT/JWT.swift new file mode 100644 index 00000000..772ff6ee --- /dev/null +++ b/Sources/JWT/JWT.swift @@ -0,0 +1,189 @@ +import Content +import Foundation + +public enum JWTError : Error { + case invalidHeader + case invalidPayload + case invalidToken + case algorithmDoesNotMatch + case invalidSignature +} + +public struct JWT { + public struct Algorithm { + public let name: String + let sign: (String) throws -> Data + let verify: (Data, String) throws -> Void + + public init( + name: String, + sign: @escaping (_ message: String) throws -> Data, + verify: @escaping (_ signature: Data, _ message: String) throws -> Void + ) { + self.name = name + self.sign = sign + self.verify = verify + } + } + + private let jsonHeader: JSON + private let jsonPayload: JSON + private let signature: Data + private let message: String + + public init( + header: JSON = [:], + payload: JSON, + algorithm: Algorithm + ) throws { + var defaultHeader: [String: JSON] = [ + "typ": "JWT", + "alg": .string(algorithm.name) + ] + + guard case let .object(object) = header, case .object = payload else { + throw JWTError.invalidHeader + } + + for (key, value) in object { + defaultHeader[key] = value + } + + self.jsonHeader = JSON.object(defaultHeader) + self.jsonPayload = payload + + guard let encodedHeader = self.jsonHeader.description.base64URLEncoded() else { + throw JWTError.invalidHeader + } + + guard let encodedPayload = self.jsonPayload.description.base64URLEncoded() else { + throw JWTError.invalidPayload + } + + self.message = encodedHeader + "." + encodedPayload + self.signature = try algorithm.sign(self.message) + } + + public init(token: String) throws { + let components = token.components(separatedBy: ".") + + guard components.count == 3 else { + throw JWTError.invalidToken + } + + guard let decodedHeader = components[0].base64URLDecoded() else { + throw JWTError.invalidToken + } + + self.jsonHeader = try decodedHeader.withBuffer { + try JSON.parse($0, deadline: .never) + } + + guard let decodedPayload = components[1].base64URLDecoded() else { + throw JWTError.invalidToken + } + + self.jsonPayload = try decodedPayload.withBuffer { + try JSON.parse($0, deadline: .never) + } + + guard let decodedSignature = components[2].base64URLDecoded() else { + throw JWTError.invalidToken + } + + self.message = components[0] + "." + components[1] + self.signature = decodedSignature + } + + public func token() throws -> String { + guard let signature = self.signature.base64URLEncoded() else { + throw JWTError.invalidSignature + } + + return message + "." + signature + } + + public static func token( + header: JSON = [:], + payload: JSON, + using algorithm: Algorithm + ) throws -> String { + let jwt = try JWT( + header: header, + payload: payload, + algorithm: algorithm + ) + + return try jwt.token() + } + + public func verify(using algorithm: Algorithm) throws { + guard try algorithm.name == jsonHeader.get("alg") else { + throw JWTError.algorithmDoesNotMatch + } + + try algorithm.verify(signature, message) + } + + public static func verify(_ token: String, using algorithm: Algorithm) throws { + let jwt = try JWT(token: token) + try jwt.verify(using: algorithm) + } + + public func header() -> JSON { + return jsonHeader + } + + public func header() throws -> I { + return try I(json: jsonHeader) + } + + public func payload() -> JSON { + return jsonPayload + } + + public func payload() throws -> I { + return try I(json: jsonPayload) + } +} + +extension Data { + func base64URLEncoded() -> String? { + return base64EncodedString() + .replacingOccurrences( + of: "+", + with: "-" + ).replacingOccurrences( + of: "/", + with: "_" + ).replacingOccurrences( + of: "=", + with: "" + ) + } +} + +extension String { + func base64URLEncoded() -> String? { + return data(using: .utf8)?.base64URLEncoded() + } + + func base64URLDecoded() -> Data? { + let remainder = utf8.count % 4 + let padding = remainder > 0 ? String(repeating: "=", count: 4 - remainder) : "" + + let base64 = replacingOccurrences( + of: "-", + with: "+", + options: [], + range: nil + ).replacingOccurrences( + of: "_", + with: "/", + options: [], + range: nil + ) + padding + + return Data(base64Encoded: base64) + } +}