aboutsummaryrefslogtreecommitdiff
path: root/gemini
diff options
context:
space:
mode:
authoraqua <aqua@iserlohn-fortress.net>2021-08-04 11:57:26 +0300
committeraqua <aqua@iserlohn-fortress.net>2021-08-04 11:57:26 +0300
commit479cd8d24eda5edf63235210112e4c5ddfa6f370 (patch)
treea771e6c1c988f7f6539927bee16d8b27b53f5422 /gemini
parentgemcat can now do multiple requests at once (diff)
downloadgemcat-479cd8d24eda5edf63235210112e4c5ddfa6f370.tar.xz
Move gemini code into its own module
Diffstat (limited to 'gemini')
-rw-r--r--gemini/gemini.go86
-rw-r--r--gemini/gemini_test.go43
-rw-r--r--gemini/go.mod3
3 files changed, 132 insertions, 0 deletions
diff --git a/gemini/gemini.go b/gemini/gemini.go
new file mode 100644
index 0000000..8f4c6ef
--- /dev/null
+++ b/gemini/gemini.go
@@ -0,0 +1,86 @@
+package gemini
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+)
+
+const GeminiNetwork = "tcp"
+const GeminiPort = 1965
+const GeminiPrefix = "gemini://"
+
+func FormatRequest(request string) []byte {
+ if !strings.HasPrefix(request, GeminiPrefix) {
+ request = GeminiPrefix + request
+ }
+ request = request + "\r\n"
+ return []byte(request)
+}
+
+type GeminiConn struct {
+ host string
+ config *tls.Config
+}
+
+func NewGeminiConn(host string, port int) *GeminiConn {
+ conf := tls.Config{
+ MinVersion: tls.VersionTLS12,
+ InsecureSkipVerify: true,
+ }
+
+ if strings.Contains(host, ":") {
+ return &GeminiConn{host, &conf}
+ } else {
+ return &GeminiConn{host + ":" + strconv.Itoa(port), &conf}
+ }
+}
+
+func NewGeminiConnFromRequest(request string, port int) (*GeminiConn, error) {
+ if !strings.HasPrefix(request, GeminiPrefix) {
+ request = GeminiPrefix + request
+ }
+
+ u, err := url.Parse(request)
+ if err != nil {
+ return nil, err
+ }
+ return NewGeminiConn(u.Host, port), nil
+}
+
+func (c *GeminiConn) Get(request []byte, out *os.File) error {
+ conn, err := tls.Dial(GeminiNetwork, c.host, c.config)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ n, err := conn.Write(request)
+ if err != nil {
+ return err
+ }
+
+ buf := make([]byte, 1024)
+
+ for {
+ n, err = conn.Read(buf)
+ if err != nil {
+ return err
+ }
+ written, err := out.Write(buf[:n])
+ if written != n {
+ return fmt.Errorf("Got %d bytes, but only wrote %d bytes", n, written)
+ }
+ if err != nil {
+ return err
+ }
+
+ if n == 0 {
+ break
+ }
+ }
+ return nil
+}
diff --git a/gemini/gemini_test.go b/gemini/gemini_test.go
new file mode 100644
index 0000000..ffd2588
--- /dev/null
+++ b/gemini/gemini_test.go
@@ -0,0 +1,43 @@
+package gemini
+
+import (
+ "bytes"
+ "strconv"
+ "testing"
+)
+
+func TestNewGeminiConn(t *testing.T) {
+ tables := []struct {
+ url string
+ port int
+ host string
+ }{
+ {"hostname.com", GeminiPort, "hostname.com:" + strconv.Itoa(GeminiPort)},
+ {"hostname.com", 1234, "hostname.com:1234"},
+ }
+
+ for _, table := range tables {
+ conn, err := NewGeminiConnFromRequest(table.url, table.port)
+ if err != nil {
+ t.Fatalf("NewGeminiConn error: %s", err.Error())
+ }
+ if conn.host != table.host {
+ t.Fatalf("NewGeminiConn error: wrong hostname %s", conn.host)
+ }
+ }
+}
+
+func TestFormatRequest(t *testing.T) {
+ tables := []struct {
+ input string
+ output []byte
+ }{
+ {"hostname.com", []byte("gemini://hostname.com\r\n")},
+ }
+
+ for _, table := range tables {
+ if !bytes.Equal(FormatRequest(table.input), table.output) {
+ t.Fatalf("FormatRequest failed on: %s\n", table.input)
+ }
+ }
+}
diff --git a/gemini/go.mod b/gemini/go.mod
new file mode 100644
index 0000000..228a120
--- /dev/null
+++ b/gemini/go.mod
@@ -0,0 +1,3 @@
+module gemini
+
+go 1.16