diff options
Diffstat (limited to 'client.go')
| -rw-r--r-- | client.go | 127 |
1 files changed, 68 insertions, 59 deletions
@@ -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 } |