1
0
Fork 0
mirror of https://git.sr.ht/~adnano/go-gemini synced 2024-06-02 14:46:05 +02:00
go-gemini/client.go

116 lines
2.8 KiB
Go
Raw Normal View History

2020-10-24 21:15:32 +02:00
package gemini
2020-09-22 04:09:50 +02:00
import (
2020-09-24 06:30:21 +02:00
"bufio"
2020-12-17 23:16:55 +01:00
"context"
2020-09-22 04:09:50 +02:00
"crypto/tls"
2020-09-26 01:53:50 +02:00
"crypto/x509"
2020-10-28 00:21:33 +01:00
"net"
2020-10-28 18:40:25 +01:00
"strings"
2020-11-01 01:55:56 +01:00
"time"
2020-09-22 04:09:50 +02:00
)
2020-10-28 18:40:25 +01:00
// Client is a Gemini client.
2020-09-26 05:06:54 +02:00
type Client struct {
2020-12-18 01:50:26 +01:00
// TrustCertificate is called to determine whether the client
// should trust the certificate provided by the server.
// If TrustCertificate is nil, the client will accept any certificate.
// If the returned error is not nil, the certificate will not be trusted
// and the request will be aborted.
TrustCertificate func(hostname string, cert *x509.Certificate) error
2020-11-01 01:55:56 +01:00
2020-12-18 01:50:26 +01:00
// Timeout specifies a time limit for requests made by this
// Client. The timeout includes connection time and reading
// the response body. The timer remains running after
// Get and Do return and will interrupt reading of the Response.Body.
2020-11-01 04:05:31 +01:00
//
2020-12-18 01:50:26 +01:00
// A Timeout of zero means no timeout.
Timeout time.Duration
2020-09-26 01:53:50 +02:00
}
2020-12-18 01:50:26 +01:00
// Get performs a Gemini request for the given URL.
2020-10-28 00:21:33 +01:00
func (c *Client) Get(url string) (*Response, error) {
req, err := NewRequest(url)
if err != nil {
return nil, err
}
return c.Do(req)
}
// Do performs a Gemini request and returns a Gemini response.
func (c *Client) Do(req *Request) (*Response, error) {
2020-11-27 23:45:15 +01:00
// Extract hostname
colonPos := strings.LastIndex(req.Host, ":")
if colonPos == -1 {
colonPos = len(req.Host)
}
hostname := req.Host[:colonPos]
2020-09-26 01:53:50 +02:00
// Connect to the host
config := &tls.Config{
InsecureSkipVerify: true,
2020-09-26 06:31:16 +02:00
MinVersion: tls.VersionTLS12,
2020-10-28 18:40:25 +01:00
GetClientCertificate: func(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) {
2020-12-17 22:46:16 +01:00
if req.Certificate != nil {
return req.Certificate, nil
}
return &tls.Certificate{}, nil
2020-09-26 21:14:34 +02:00
},
VerifyConnection: func(cs tls.ConnectionState) error {
2020-10-28 18:40:25 +01:00
return c.verifyConnection(req, cs)
2020-09-26 01:53:50 +02:00
},
2020-11-27 23:45:15 +01:00
ServerName: hostname,
2020-09-26 01:53:50 +02:00
}
// Set connection context
2020-12-17 23:16:55 +01:00
ctx := req.Context
if ctx == nil {
ctx = context.Background()
}
netConn, err := (&net.Dialer{}).DialContext(ctx, "tcp", req.Host)
2020-09-26 01:53:50 +02:00
if err != nil {
return nil, err
}
2020-11-26 06:42:25 +01:00
conn := tls.Client(netConn, config)
2020-11-01 01:55:56 +01:00
// Set connection deadline
if d := c.Timeout; d != 0 {
conn.SetDeadline(time.Now().Add(d))
2020-11-01 01:55:56 +01:00
}
2020-09-26 01:53:50 +02:00
// Write the request
w := bufio.NewWriter(conn)
req.write(w)
if err := w.Flush(); err != nil {
return nil, err
}
// Read the response
resp := &Response{}
2020-10-28 00:16:55 +01:00
if err := resp.read(conn); err != nil {
2020-09-26 01:53:50 +02:00
return nil, err
}
2020-10-28 18:40:25 +01:00
// Store connection state
2020-09-28 01:56:33 +02:00
resp.TLS = conn.ConnectionState()
2020-09-26 01:53:50 +02:00
return resp, nil
2020-09-24 06:30:21 +02:00
}
2020-10-28 00:21:33 +01:00
2020-10-28 18:40:25 +01:00
func (c *Client) verifyConnection(req *Request, cs tls.ConnectionState) error {
// Verify the hostname
var hostname string
if host, _, err := net.SplitHostPort(req.Host); err == nil {
hostname = host
} else {
hostname = req.Host
}
cert := cs.PeerCertificates[0]
if err := verifyHostname(cert, hostname); err != nil {
return err
}
2020-11-05 21:27:12 +01:00
2020-12-18 01:50:26 +01:00
// See if the client trusts the certificate
if c.TrustCertificate != nil {
return c.TrustCertificate(hostname, cert)
2020-10-28 00:21:33 +01:00
}
2020-12-18 01:50:26 +01:00
return nil
2020-10-28 00:21:33 +01:00
}