aboutsummaryrefslogtreecommitdiff
path: root/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'client.go')
-rw-r--r--client.go127
1 files changed, 68 insertions, 59 deletions
diff --git a/client.go b/client.go
index d12f51c..4a4f32c 100644
--- a/client.go
+++ b/client.go
@@ -4,6 +4,7 @@ package gemini
import (
"bufio"
"crypto/tls"
+ "crypto/x509"
"errors"
"io/ioutil"
"net"
@@ -17,17 +18,7 @@ var (
ErrInvalidURL = errors.New("gemini: requested URL is invalid")
)
-// Request is a Gemini request.
-//
-// A Request can optionally be configured with a client certificate. Example:
-//
-// req := NewRequest(url)
-// cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
-// if err != nil {
-// panic(err)
-// }
-// req.TLSConfig.Certificates = append(req.TLSConfig.Certificates, cert)
-//
+// Request represents a Gemini request.
type Request struct {
// URL specifies the URL being requested.
URL *url.URL
@@ -37,7 +28,7 @@ type Request struct {
// This field is ignored by the server.
Host string
- // The certificate to use for the request.
+ // Certificate specifies the TLS certificate to use for the request.
Certificate tls.Certificate
// RemoteAddr allows servers and other software to record the network
@@ -120,80 +111,98 @@ type Response struct {
TLS tls.ConnectionState
}
-// Do sends a Gemini request and returns a Gemini response.
-func Do(req *Request) (*Response, error) {
- // Connect to the host
- config := &tls.Config{
- InsecureSkipVerify: true,
- Certificates: []tls.Certificate{req.Certificate},
- }
- conn, err := tls.Dial("tcp", req.Host, config)
- if err != nil {
- return nil, err
- }
- defer conn.Close()
-
- // Write the request
- // TODO: Is buffered I/O necessary here?
- w := bufio.NewWriter(conn)
- req.write(w)
- if err := w.Flush(); err != nil {
- return nil, err
- }
-
- // Read the response status
- r := bufio.NewReader(conn)
+// read reads a Gemini response from the provided buffered reader.
+func (resp *Response) read(r *bufio.Reader) error {
+ // Read the status
statusB := make([]byte, 2)
if _, err := r.Read(statusB); err != nil {
- return nil, err
+ return err
}
status, err := strconv.Atoi(string(statusB))
if err != nil {
- return nil, err
+ return err
}
+ resp.Status = status
// Read one space
if b, err := r.ReadByte(); err != nil {
- return nil, err
+ return err
} else if b != ' ' {
- return nil, ErrProtocol
+ return ErrProtocol
}
// Read the meta
meta, err := r.ReadString('\r')
if err != nil {
- return nil, err
- }
-
- // Read terminating newline
- if b, err := r.ReadByte(); err != nil {
- return nil, err
- } else if b != '\n' {
- return nil, ErrProtocol
+ return err
}
-
// Trim carriage return
meta = meta[:len(meta)-1]
-
// Ensure meta is less than or equal to 1024 bytes
if len(meta) > 1024 {
- return nil, ErrProtocol
+ return ErrProtocol
+ }
+ resp.Meta = meta
+
+ // Read terminating newline
+ if b, err := r.ReadByte(); err != nil {
+ return err
+ } else if b != '\n' {
+ return ErrProtocol
}
// Read response body
- var body []byte
if status/10 == StatusClassSuccess {
var err error
- body, err = ioutil.ReadAll(r)
+ resp.Body, err = ioutil.ReadAll(r)
if err != nil {
- return nil, err
+ return err
}
}
+ return nil
+}
- return &Response{
- Status: status,
- Meta: meta,
- Body: body,
- TLS: conn.ConnectionState(),
- }, nil
+// Client represents a Gemini client.
+type Client struct {
+ // VerifyCertificate, if not nil, will be called to verify the server certificate.
+ // If error is not nil, the connection will be aborted.
+ VerifyCertificate func(cert *x509.Certificate) error
+}
+
+// Send sends a Gemini request and returns a Gemini response.
+func (c *Client) Send(req *Request) (*Response, error) {
+ // Connect to the host
+ config := &tls.Config{
+ InsecureSkipVerify: true,
+ Certificates: []tls.Certificate{req.Certificate},
+ VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
+ cert, err := x509.ParseCertificate(rawCerts[0])
+ if err != nil {
+ return err
+ }
+ return c.VerifyCertificate(cert)
+ },
+ }
+ conn, err := tls.Dial("tcp", req.Host, config)
+ if err != nil {
+ return nil, err
+ }
+ defer conn.Close()
+
+ // Write the request
+ w := bufio.NewWriter(conn)
+ req.write(w)
+ if err := w.Flush(); err != nil {
+ return nil, err
+ }
+
+ // Read the response
+ resp := &Response{}
+ r := bufio.NewReader(conn)
+ // Store connection information
+ resp.TLS = conn.ConnectionState()
+ if err := resp.read(r); err != nil {
+ return nil, err
+ }
+ return resp, nil
}