From 479cd8d24eda5edf63235210112e4c5ddfa6f370 Mon Sep 17 00:00:00 2001 From: aqua Date: Wed, 4 Aug 2021 11:57:26 +0300 Subject: Move gemini code into its own module --- gemini/gemini.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++ gemini/gemini_test.go | 43 ++++++++++++++++++++++++++ gemini/go.mod | 3 ++ 3 files changed, 132 insertions(+) create mode 100644 gemini/gemini.go create mode 100644 gemini/gemini_test.go create mode 100644 gemini/go.mod (limited to 'gemini') 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 -- cgit v1.2.1