aboutsummaryrefslogtreecommitdiff
path: root/client.go
diff options
context:
space:
mode:
authoradnano <[email protected]>2020-09-21 15:48:42 -0400
committeradnano <[email protected]>2020-09-21 15:49:09 -0400
commit257b8076756fa25e3b07d6d85e76874d1fe33268 (patch)
tree5cf003942047b7a4d25e8a16bdca3e856f600bd4 /client.go
downloadgo-gemini-257b8076756fa25e3b07d6d85e76874d1fe33268.tar.xz
go-gemini-257b8076756fa25e3b07d6d85e76874d1fe33268.zip
Initial commit
Diffstat (limited to 'client.go')
-rw-r--r--client.go135
1 files changed, 135 insertions, 0 deletions
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..b44c8fb
--- /dev/null
+++ b/client.go
@@ -0,0 +1,135 @@
+package gemini
+
+import (
+ "bufio"
+ "crypto/tls"
+ "errors"
+ "io/ioutil"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+var (
+ ProtocolError = errors.New("Protocol error")
+)
+
+// Client is a Gemini client.
+type Client struct {
+ TLSConfig tls.Config
+}
+
+func (c *Client) Get(url string) (*Response, error) {
+ req, err := NewRequest(url)
+ if err != nil {
+ return nil, err
+ }
+ return c.Do(req)
+}
+
+func (c *Client) GetProxy(host, url string) (*Response, error) {
+ req, err := NewProxyRequest(host, url)
+ if err != nil {
+ return nil, err
+ }
+ return c.Do(req)
+}
+
+// Request is a Gemini request.
+type Request struct {
+ Host string // host or host:port
+ URL *url.URL // The URL to request
+}
+
+// NewRequest returns a new request. The host is inferred from the provided url.
+func NewRequest(rawurl string) (*Request, error) {
+ u, err := url.Parse(rawurl)
+ if err != nil {
+ return nil, err
+ }
+
+ // Ignore UserInfo if present
+ u.User = nil
+
+ return &Request{
+ Host: u.Host,
+ URL: u,
+ }, nil
+}
+
+// NewProxyRequest makes a new request using the provided host.
+func NewProxyRequest(host, rawurl string) (*Request, error) {
+ u, err := url.Parse(rawurl)
+ if err != nil {
+ return nil, err
+ }
+
+ // Ignore UserInfo if present
+ u.User = nil
+
+ return &Request{
+ Host: host,
+ URL: u,
+ }, nil
+}
+
+func (c *Client) Do(req *Request) (*Response, error) {
+ host := req.Host
+ if strings.LastIndex(host, ":") == -1 {
+ // The default port is 1965
+ host += ":1965"
+ }
+
+ config := &tls.Config{
+ // Allow self-signed certificates
+ // TODO: Trust on first use
+ InsecureSkipVerify: true,
+ }
+ conn, err := tls.Dial("tcp", host, config)
+ if err != nil {
+ return nil, err
+ }
+ defer conn.Close()
+
+ // Write the request
+ request := req.URL.String() + "\r\n"
+ if _, err := conn.Write([]byte(request)); err != nil {
+ return nil, err
+ }
+
+ // Read the response header
+ code := make([]byte, 2)
+ if _, err := conn.Read(code); err != nil {
+ return nil, err
+ }
+ status, err := strconv.Atoi(string(code))
+ if err != nil {
+ return nil, err
+ }
+
+ // Read one space
+ space := make([]byte, 1)
+ if _, err := conn.Read(space); err != nil {
+ return nil, err
+ }
+ if space[0] != ' ' {
+ return nil, ProtocolError
+ }
+
+ // Read the meta
+ scanner := bufio.NewScanner(conn)
+ scanner.Scan()
+ meta := scanner.Text()
+
+ // Read the response body
+ body, err := ioutil.ReadAll(conn)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Response{
+ Status: status,
+ Meta: meta,
+ Body: body,
+ }, nil
+}