commit 968407e61d0a0f19954f9ba7851c437bab4d47d2
parent c89452c9ee339f83122681c7d6b64f52c9892b16
Author: FIGBERT <figbert@figbert.com>
Date: Sun, 4 Jul 2021 18:44:06 +0300
Add rough networking implementation
It's untested and I wrote it on a plane flight after being awake for
over twenty-four hours straight. I'll probably need to tweak some stuff
before it hits production...
Diffstat:
3 files changed, 129 insertions(+), 2 deletions(-)
diff --git a/Sources/SwiftGemini/MetaHeader.swift b/Sources/SwiftGemini/MetaHeader.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+enum MetaHeader {
+ case GemenonDefault
+ case Prompt(String)
+ case MIME(String)
+ case Redirect(URL)
+ case FailureInfo(String)
+ case Seconds(Int)
+ case CertInfo(String)
+}
diff --git a/Sources/SwiftGemini/StatusCode.swift b/Sources/SwiftGemini/StatusCode.swift
@@ -0,0 +1,23 @@
+import Foundation
+
+enum StatusCode: Int {
+ case GemenonDefault = 0
+ case Input = 10
+ case SensitiveInput = 11
+ case Success = 20
+ case TemporaryRedirect = 30
+ case PermanentRedirect = 31
+ case TemporaryFailure = 40
+ case ServerUnavailable = 41
+ case CGIError = 42
+ case ProxyError = 43
+ case SlowDown = 44
+ case PermanentFailure = 50
+ case NotFound = 51
+ case Gone = 52
+ case ProxyRequestRefused = 53
+ case BadRequest = 59
+ case ClientCertRequired = 60
+ case ClientCertNotAuth = 61
+ case ClientCertNotValid = 62
+}
diff --git a/Sources/SwiftGemini/SwiftGemini.swift b/Sources/SwiftGemini/SwiftGemini.swift
@@ -1,3 +1,96 @@
-struct SwiftGemini {
- var text = "Hello, World!"
+import Foundation
+import Network
+
+struct GeminiRequest {
+ public let url: URL
+ public let status: StatusCode
+ public let meta: MetaHeader
+ public let raw: Data
+ public let text: String
+
+ init?(_ site: String) throws {
+ guard var components = URLComponents(url: URL(string: site)!, resolvingAgainstBaseURL: true) else { return nil }
+ guard components.scheme == "gemini" else { return nil }
+ guard components.user!.isEmpty && components.password!.isEmpty else { return nil }
+ if components.port == nil { components.port = 1965 }
+ let url = components.url!
+ if url.absoluteString.data(using: .utf8)!.count > 1024 { return nil }
+
+ let queue = DispatchQueue(label: "gemenon")
+ let endpoint = NWEndpoint.Host.init(url.host!)
+
+ let options = NWProtocolTLS.Options()
+ sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
+ sec_protocol_verify_complete(true)
+ }, queue)
+
+ let connection = NWConnection(
+ host: endpoint,
+ port: .init(rawValue: UInt16(url.port!))!,
+ using: NWParameters(tls: options)
+ )
+
+ var status = StatusCode.GemenonDefault
+ var meta = MetaHeader.GemenonDefault
+ var raw = Data()
+ var text = String()
+
+ // Get status code
+ connection.receive(minimumIncompleteLength: 2, maximumLength: 2) { (data, contentContext, isComplete, error) in
+ guard data != nil else { return }
+ status = StatusCode(rawValue: Int(String(data: data!, encoding: .utf8)!)!)!
+ }
+ // Get meta header
+ connection.receive(minimumIncompleteLength: 0, maximumLength: 1024 + 3) { (data, contentContext, isComplete, error) in
+ guard data != nil else { return }
+ let header = String(data: data!, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
+ switch status {
+ case .Input, .SensitiveInput:
+ meta = .Prompt(header)
+ case .Success:
+ meta = .MIME(header)
+ case .TemporaryRedirect, .PermanentRedirect:
+ meta = .Redirect(URL(string: header)!)
+ case .TemporaryFailure, .ServerUnavailable, .CGIError,
+ .ProxyError, .PermanentFailure, .NotFound,
+ .Gone, .ProxyRequestRefused, .BadRequest:
+ meta = .FailureInfo(header)
+ case .SlowDown:
+ meta = .Seconds(Int(header)!)
+ case .ClientCertRequired, .ClientCertNotAuth, .ClientCertNotValid:
+ meta = .CertInfo(header)
+ case .GemenonDefault:
+ return
+ }
+ }
+ if status == .Success {
+ // Get content
+ connection.receive(minimumIncompleteLength: Int.min, maximumLength: Int.max) { (data, contentContext, isComplete, error) in
+ guard data != nil else { return }
+ raw = data!
+ text = String(data: data!, encoding: .utf8) ?? ""
+
+ if isComplete {
+ connection.cancel()
+ } else if let error = error {
+ print("setupReceive: error \(error.localizedDescription)")
+ }
+ }
+ }
+
+ connection.start(queue: queue)
+ connection.send(content: "\(url.absoluteString)\r\n".data(using: .utf8)!, completion: .contentProcessed( { error in
+ print("data sent")
+ if let error = error {
+ print("got error: \(error)")
+ return
+ }
+ }))
+
+ self.url = url
+ self.status = status
+ self.meta = meta
+ self.raw = raw
+ self.text = text
+ }
}