aboutsummaryrefslogtreecommitdiff
path: root/Sources
diff options
context:
space:
mode:
Diffstat (limited to 'Sources')
-rw-r--r--Sources/GopherHelpers/GopherHelpers.swift259
-rw-r--r--Sources/swift-gopher/fileTypes.swift133
-rw-r--r--Sources/swift-gopher/gopherHandler.swift1
-rw-r--r--Sources/swift-gopher/helpers.swift7
-rw-r--r--Sources/swiftGopherClient/gopherClient.swift50
-rw-r--r--Sources/swiftGopherClient/gopherRequestResponseHandler.swift37
-rw-r--r--Sources/swiftGopherClient/gopherTypes.swift122
7 files changed, 312 insertions, 297 deletions
diff --git a/Sources/GopherHelpers/GopherHelpers.swift b/Sources/GopherHelpers/GopherHelpers.swift
new file mode 100644
index 0000000..22545b3
--- /dev/null
+++ b/Sources/GopherHelpers/GopherHelpers.swift
@@ -0,0 +1,259 @@
+//
+// File.swift
+//
+//
+// Created by Navan Chauhan on 12/16/23.
+//
+
+import Foundation
+import NIOCore
+
+/*
+
+ From Wikipedia
+
+ Canonical types
+ 0 Text file
+ 1 Gopher submenu
+ 2 CCSO Nameserver
+ 3 Error code returned by a Gopher server to indicate failure
+ 4 BinHex-encoded file (primarily for Macintosh computers)
+ 5 DOS file
+ 6 uuencoded file
+ 7 Gopher full-text search
+ 8 Telnet
+ 9 Binary file
+ + Mirror or alternate server (for load balancing or in case of primary server downtime)
+ g GIF file
+ I Image file
+ T Telnet 3270
+ gopher+ types
+ : Bitmap image
+ ; Movie file
+ < Sound file
+ Non-canonical types
+ d Doc. Seen used alongside PDF's and .DOC's
+ h HTML file
+ i Informational message, widely used.[25]
+ p image file "(especially the png format)"
+ r document rtf file "rich text Format")
+ s Sound file (especially the WAV format)
+ P document pdf file "Portable Document Format")
+ X document xml file "eXtensive Markup Language" )
+ */
+
+public enum gopherItemType {
+ case text
+ case directory
+ case nameserver
+ case error
+ case binhex
+ case bindos
+ case uuencoded
+ case search
+ case telnet
+ case binary
+ case mirror
+ case gif
+ case image
+ case tn3270Session
+ case bitmap
+ case movie
+ case sound
+ case doc
+ case html
+ case info
+}
+
+public struct gopherItem {
+
+ public var rawLine: String
+ public var rawData: ByteBuffer?
+ public var message: String = ""
+ public var parsedItemType: gopherItemType = .info
+ public var host: String = "error.host"
+ public var port: Int = 1
+ public var selector: String = ""
+ public var valid: Bool = true
+
+ public init(rawLine: String) {
+ self.rawLine = rawLine
+ }
+}
+
+public func getGopherFileType(item: String) -> gopherItemType {
+ switch item {
+ case "0":
+ return .text
+ case "1":
+ return .directory
+ case "2":
+ return .nameserver
+ case "3":
+ return .error
+ case "4":
+ return .binhex
+ case "5":
+ return .bindos
+ case "6":
+ return .uuencoded
+ case "7":
+ return .search
+ case "8":
+ return .telnet
+ case "9":
+ return .binary
+ case "+":
+ return .mirror
+ case "g":
+ return .gif
+ case "I":
+ return .image
+ case "T":
+ return .tn3270Session
+ case ":":
+ return .bitmap
+ case ";":
+ return .movie
+ case "<":
+ return .sound
+ case "d":
+ return .doc
+ case "h":
+ return .html
+ case "i":
+ return .info
+ case "p":
+ return .image
+ case "r":
+ return .doc
+ case "s":
+ return .doc
+ case "P":
+ return .doc
+ case "X":
+ return .doc
+ default:
+ return .info
+ }
+}
+
+public func getFileType(fileExtension: String) -> gopherItemType {
+ switch fileExtension {
+ case "txt":
+ return .text
+ case "md":
+ return .text
+ case "html":
+ return .html
+ case "pdf":
+ return .doc
+ case "png":
+ return .image
+ case "gif":
+ return .gif
+ case "jpg":
+ return .image
+ case "jpeg":
+ return .image
+ case "mp3":
+ return .sound
+ case "wav":
+ return .sound
+ case "mp4":
+ return .movie
+ case "mov":
+ return .movie
+ case "avi":
+ return .movie
+ case "rtf":
+ return .doc
+ case "xml":
+ return .doc
+ default:
+ return .binary
+ }
+}
+
+
+public func fileTypeToGopherItem(fileType: gopherItemType) -> String {
+ switch fileType {
+ case .text:
+ return "0"
+ case .directory:
+ return "1"
+ case .nameserver:
+ return "2"
+ case .error:
+ return "3"
+ case .binhex:
+ return "4"
+ case .bindos:
+ return "5"
+ case .uuencoded:
+ return "6"
+ case .search:
+ return "7"
+ case .telnet:
+ return "8"
+ case .binary:
+ return "9"
+ case .mirror:
+ return "+"
+ case .tn3270Session:
+ return "T"
+ case .gif:
+ return "g"
+ case .image:
+ return "I"
+ case .bitmap:
+ return "b"
+ case .movie:
+ return "M"
+ case .sound:
+ return "s"
+ case .doc:
+ return "d"
+ case .html:
+ return "h"
+ case .info:
+ return "i"
+// case .png:
+// return "p"
+// case .rtf:
+// return "t"
+// case .wavfile:
+// return "w"
+// case .pdf:
+// return "P"
+// case .xml:
+// return "x"
+ }
+}
+
+public func itemToImageType(_ item: gopherItem) -> String {
+ switch item.parsedItemType {
+ case .text:
+ return "doc.plaintext"
+ case .directory:
+ return "folder"
+ case .error:
+ return "exclamationmark.triangle"
+ case .gif:
+ return "photo.stack"
+ case .image:
+ return "photo"
+ case .doc:
+ return "doc.richtext"
+ case .sound:
+ return "music.note"
+ case .bitmap:
+ return "photo"
+ case .html:
+ return "globe"
+ case .movie:
+ return "videoprojector"
+ default:
+ return "questionmark.square.dashed"
+ }
+}
diff --git a/Sources/swift-gopher/fileTypes.swift b/Sources/swift-gopher/fileTypes.swift
deleted file mode 100644
index 221b7b2..0000000
--- a/Sources/swift-gopher/fileTypes.swift
+++ /dev/null
@@ -1,133 +0,0 @@
-//
-// fileTypes.swift
-//
-//
-// Created by Navan Chauhan on 12/3/23.
-//
-
-import Foundation
-
-enum ResponseType {
- case string(String)
- case data(Data)
-}
-
-enum gopherFileType {
- case text
- case directory
- case nameserver
- case error
- case binhex
- case bindos
- case uuencoded
- case indexSearch
- case telnet
- case binary
- case redundantServer
- case tn3270Session
- case gif
- case image
- case bitmap
- case movie
- case sound
- case doc
- case html
- case message
- case png
- case rtf
- case wavfile
- case pdf
- case xml
-}
-
-func getFileType(fileExtension: String) -> gopherFileType {
- switch fileExtension {
- case "txt":
- return .text
- case "md":
- return .text
- case "html":
- return .html
- case "pdf":
- return .pdf
- case "png":
- return .png
- case "gif":
- return .gif
- case "jpg":
- return .image
- case "jpeg":
- return .image
- case "mp3":
- return .sound
- case "wav":
- return .wavfile
- case "mp4":
- return .movie
- case "mov":
- return .movie
- case "avi":
- return .movie
- case "rtf":
- return .rtf
- case "xml":
- return .xml
- default:
- return .binary
- }
-}
-
-func fileTypeToGopherItem(fileType: gopherFileType) -> String {
- switch fileType {
- case .text:
- return "0"
- case .directory:
- return "1"
- case .nameserver:
- return "2"
- case .error:
- return "3"
- case .binhex:
- return "4"
- case .bindos:
- return "5"
- case .uuencoded:
- return "6"
- case .indexSearch:
- return "7"
- case .telnet:
- return "8"
- case .binary:
- return "9"
- case .redundantServer:
- return "+"
- case .tn3270Session:
- return "T"
- case .gif:
- return "g"
- case .image:
- return "I"
- case .bitmap:
- return "b"
- case .movie:
- return "M"
- case .sound:
- return "s"
- case .doc:
- return "d"
- case .html:
- return "h"
- case .message:
- return "i"
- case .png:
- return "p"
- case .rtf:
- return "t"
- case .wavfile:
- return "w"
- case .pdf:
- return "P"
- case .xml:
- return "x"
- }
-}
diff --git a/Sources/swift-gopher/gopherHandler.swift b/Sources/swift-gopher/gopherHandler.swift
index 00a3a30..be06b97 100644
--- a/Sources/swift-gopher/gopherHandler.swift
+++ b/Sources/swift-gopher/gopherHandler.swift
@@ -2,6 +2,7 @@ import ArgumentParser
import Foundation
import Logging
import NIO
+import GopherHelpers
final class GopherHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
diff --git a/Sources/swift-gopher/helpers.swift b/Sources/swift-gopher/helpers.swift
index caf841b..f054d17 100644
--- a/Sources/swift-gopher/helpers.swift
+++ b/Sources/swift-gopher/helpers.swift
@@ -6,4 +6,9 @@ func buildVersionStringResponse() -> String {
let repeatedString = "i" + String(repeating: "-", count: 80) + "\t\terror.host\t1\r\n"
let versionResponseString = "i" + String(repeating: " ", count: 80 - versionString.count) + versionString + "\t\terror.host\t1\r\n"
return "\(repeatedString)\(versionResponseString)"
-} \ No newline at end of file
+}
+
+enum ResponseType {
+ case string(String)
+ case data(Data)
+}
diff --git a/Sources/swiftGopherClient/gopherClient.swift b/Sources/swiftGopherClient/gopherClient.swift
index e508285..eabbbc4 100644
--- a/Sources/swiftGopherClient/gopherClient.swift
+++ b/Sources/swiftGopherClient/gopherClient.swift
@@ -7,12 +7,18 @@
import Foundation
import NIO
+import NIOTransportServices
+import GopherHelpers
public class GopherClient {
private let group: EventLoopGroup
public init() {
- self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
+ if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
+ self.group = NIOTSEventLoopGroup()
+ } else {
+ self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
+ }
}
deinit {
@@ -20,22 +26,40 @@ public class GopherClient {
}
public func sendRequest(to host: String, port: Int = 70, message: String, completion: @escaping (Result<[gopherItem], Error>) -> Void) {
- let bootstrap = ClientBootstrap(group: group)
- .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
- .channelInitializer { channel in
- channel.pipeline.addHandler(GopherRequestResponseHandler(message: message, completion: completion))
+ if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
+ let bootstrap = NIOTSConnectionBootstrap(group: group)
+ .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
+ .channelInitializer { channel in
+ channel.pipeline.addHandler(GopherRequestResponseHandler(message: message, completion: completion))
+ }
+ bootstrap.connect(host: host, port: port).whenComplete { result in
+ switch result {
+ case .success(let channel):
+ channel.closeFuture.whenComplete { _ in
+ print("Connection closed")
+ }
+ case .failure(let error):
+ completion(.failure(error))
+ }
}
-
- bootstrap.connect(host: host, port: port).whenComplete { result in
- switch result {
- case .success(let channel):
- channel.closeFuture.whenComplete { _ in
- print("Connection closed")
+ } else {
+ let bootstrap = ClientBootstrap(group: group)
+ .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
+ .channelInitializer { channel in
+ channel.pipeline.addHandler(GopherRequestResponseHandler(message: message, completion: completion))
+ }
+ bootstrap.connect(host: host, port: port).whenComplete { result in
+ switch result {
+ case .success(let channel):
+ channel.closeFuture.whenComplete { _ in
+ print("Connection closed")
+ }
+ case .failure(let error):
+ completion(.failure(error))
}
- case .failure(let error):
- completion(.failure(error))
}
}
+
}
private func shutdownEventLoopGroup() {
diff --git a/Sources/swiftGopherClient/gopherRequestResponseHandler.swift b/Sources/swiftGopherClient/gopherRequestResponseHandler.swift
index 3d15d66..d69c8c8 100644
--- a/Sources/swiftGopherClient/gopherRequestResponseHandler.swift
+++ b/Sources/swiftGopherClient/gopherRequestResponseHandler.swift
@@ -7,6 +7,7 @@
import Foundation
import NIO
+import GopherHelpers
final class GopherRequestResponseHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
@@ -31,17 +32,12 @@ final class GopherRequestResponseHandler: ChannelInboundHandler {
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
var buffer = unwrapInboundIn(data)
accumulatedData.writeBuffer(&buffer)
- if let receivedString = buffer.getString(at: 0, length: buffer.readableBytes) {
- print("Received from server: \(receivedString)")
- }
- //completion(.success(receivedString))
- //context.close(promise: nil)
}
func channelInactive(context: ChannelHandlerContext) {
- // Parse GopherServerResponse
- parseGopherServerResponse(response: accumulatedData.readString(length: accumulatedData.readableBytes) ?? "")
- //completion(.success(accumulatedData.readString(length: accumulatedData.readableBytes) ?? ""))
+ if let dataCopy = accumulatedData.getSlice(at: 0, length: accumulatedData.readableBytes) {
+ parseGopherServerResponse(response: accumulatedData.readString(length: accumulatedData.readableBytes) ?? "", originalBytes: dataCopy)
+ }
}
func errorCaught(context: ChannelHandlerContext, error: Error) {
@@ -49,9 +45,10 @@ final class GopherRequestResponseHandler: ChannelInboundHandler {
context.close(promise: nil)
}
- func createGopherItem(rawLine: String, itemType: gopherItemType = .info) -> gopherItem {
+ func createGopherItem(rawLine: String, itemType: gopherItemType = .info, rawData: ByteBuffer) -> gopherItem {
var item = gopherItem(rawLine: rawLine)
item.parsedItemType = itemType
+ item.rawData = rawData
if rawLine.isEmpty {
item.valid = false
@@ -77,7 +74,7 @@ final class GopherRequestResponseHandler: ChannelInboundHandler {
return item
}
- func parseGopherServerResponse(response: String) {
+ func parseGopherServerResponse(response: String, originalBytes: ByteBuffer) {
var gopherServerResponse: [gopherItem] = []
print("parsing")
@@ -87,8 +84,7 @@ final class GopherRequestResponseHandler: ChannelInboundHandler {
for line in response.split(separator: "\r\n") {
let lineItemType = getGopherFileType(item: "\(line.first ?? " ")")
- let item = createGopherItem(rawLine: String(line), itemType: lineItemType)
- print(item.message)
+ let item = createGopherItem(rawLine: String(line), itemType: lineItemType, rawData: originalBytes)
gopherServerResponse.append(item)
}
@@ -96,23 +92,8 @@ final class GopherRequestResponseHandler: ChannelInboundHandler {
print("done parsing")
completion(.success(gopherServerResponse))
-
- //completion(.success(response))
}
}
-public struct gopherItem {
-
- public var rawLine: String
- public var message: String = ""
- public var parsedItemType: gopherItemType = .info
- public var host: String = "error.host"
- public var port: Int = 1
- public var selector: String = ""
- public var valid: Bool = true
-
- public init(rawLine: String) {
- self.rawLine = rawLine
- }
-}
+
diff --git a/Sources/swiftGopherClient/gopherTypes.swift b/Sources/swiftGopherClient/gopherTypes.swift
deleted file mode 100644
index efec92e..0000000
--- a/Sources/swiftGopherClient/gopherTypes.swift
+++ /dev/null
@@ -1,122 +0,0 @@
-//
-// gopherTypes.swift
-//
-//
-// Created by Navan Chauhan on 12/12/23.
-//
-
-import Foundation
-
-/*
-
- From Wikipedia
-
- Canonical types
- 0 Text file
- 1 Gopher submenu
- 2 CCSO Nameserver
- 3 Error code returned by a Gopher server to indicate failure
- 4 BinHex-encoded file (primarily for Macintosh computers)
- 5 DOS file
- 6 uuencoded file
- 7 Gopher full-text search
- 8 Telnet
- 9 Binary file
- + Mirror or alternate server (for load balancing or in case of primary server downtime)
- g GIF file
- I Image file
- T Telnet 3270
- gopher+ types
- : Bitmap image
- ; Movie file
- < Sound file
- Non-canonical types
- d Doc. Seen used alongside PDF's and .DOC's
- h HTML file
- i Informational message, widely used.[25]
- p image file "(especially the png format)"
- r document rtf file "rich text Format")
- s Sound file (especially the WAV format)
- P document pdf file "Portable Document Format")
- X document xml file "eXtensive Markup Language" )
- */
-
-public enum gopherItemType {
- case text
- case directory
- case nameserver
- case error
- case binhex
- case bindos
- case uuencoded
- case search
- case telnet
- case binary
- case mirror
- case gif
- case image
- case tn3270Session
- case bitmap
- case movie
- case sound
- case doc
- case html
- case info
-}
-
-func getGopherFileType(item: String) -> gopherItemType {
- switch item {
- case "0":
- return .text
- case "1":
- return .directory
- case "2":
- return .nameserver
- case "3":
- return .error
- case "4":
- return .binhex
- case "5":
- return .bindos
- case "6":
- return .uuencoded
- case "7":
- return .search
- case "8":
- return .telnet
- case "9":
- return .binary
- case "+":
- return .mirror
- case "g":
- return .gif
- case "I":
- return .image
- case "T":
- return .tn3270Session
- case ":":
- return .bitmap
- case ";":
- return .movie
- case "<":
- return .sound
- case "d":
- return .doc
- case "h":
- return .html
- case "i":
- return .info
- case "p":
- return .image
- case "r":
- return .doc
- case "s":
- return .doc
- case "P":
- return .doc
- case "X":
- return .doc
- default:
- return .info
- }
-}