Gemini/GeminiProtocol/GeminiProtocol.swift

97 lines
3.7 KiB
Swift

//
// GeminiProtocol.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Network
class GeminiProtocol: NWProtocolFramerImplementation {
static let definition = NWProtocolFramer.Definition(implementation: GeminiProtocol.self)
static let label = "Gemini"
required init(framer: NWProtocolFramer.Instance) {
}
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult {
return .ready
}
func wakeup(framer: NWProtocolFramer.Instance) {
}
func stop(framer: NWProtocolFramer.Instance) -> Bool {
return true
}
func cleanup(framer: NWProtocolFramer.Instance) {
}
func handleInput(framer: NWProtocolFramer.Instance) -> Int {
while true {
var tempStatusCode: GeminiResponse.StatusCode?
let parsedStatusCodeAndSpace = framer.parseInput(minimumIncompleteLength: 3, maximumLength: 3) { (buffer, isComplete) -> Int in
guard let buffer = buffer,
buffer.count == 3 else { return 0 }
tempStatusCode = GeminiResponse.StatusCode(buffer)
return 3
}
guard parsedStatusCodeAndSpace, let statusCode = tempStatusCode else {
return 3
}
var tempMeta: String?
// Minimum length is 2 bytes, spec does not say meta string is required
let parsedMeta = framer.parseInput(minimumIncompleteLength: 2, maximumLength: 1024 + 2) { (buffer, isComplete) -> Int in
guard let buffer = buffer,
buffer.count >= 2 else { return 0 }
let lastPossibleCRIndex = buffer.index(before: buffer.index(before: buffer.endIndex))
var index = buffer.startIndex
var found = false
while index <= lastPossibleCRIndex {
// <CR><LF>
if buffer[index] == 13 && buffer[buffer.index(after: index)] == 10 {
found = true
break
}
index = buffer.index(after: index)
}
guard found else { fatalError("Didn't find <CR><LF> in buffer. Meta string was longer than 1024 bytes") }
tempMeta = String(bytes: buffer[..<index], encoding: .utf8)
return buffer.startIndex.distance(to: index)
}
guard parsedMeta, let meta = tempMeta else {
// todo: what should actually be returned here? the minimum number of bytes necessary?
return 2
}
let header = GeminiResponseHeader(status: statusCode, meta: meta)
let message = NWProtocolFramer.Message(geminiResponseHeader: header)
if !framer.deliverInputNoCopy(length: 2 + 1 + meta.utf8.count + 2, message: message, isComplete: true) {
// todo: why return zero here?
return 0
}
}
}
func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
guard let request = message.geminiRequest else { fatalError("GeminiProtocol can't send message that doesn't have an associated GeminiRequest") }
framer.writeOutput(data: request.data)
}
}
fileprivate extension GeminiResponse.StatusCode {
init?(_ buffer: UnsafeMutableRawBufferPointer) {
guard let str = String(bytes: buffer[...buffer.index(after: buffer.startIndex)], encoding: .utf8),
let value = Int(str, radix: 10) else { return nil }
self.init(rawValue: value)
}
}