aboutsummaryrefslogtreecommitdiff
path: root/examples/client.go
diff options
context:
space:
mode:
authoradnano <[email protected]>2020-10-12 16:34:52 -0400
committeradnano <[email protected]>2020-10-12 16:34:59 -0400
commita33a5be0634cbbca6c6f8eb2fd53bceb014fed02 (patch)
treea23fdfdab8807baa8a604023f25d729f3edaabf1 /examples/client.go
parentUse a map for registering server handlers (diff)
downloadgo-gemini-a33a5be0634cbbca6c6f8eb2fd53bceb014fed02.tar.xz
go-gemini-a33a5be0634cbbca6c6f8eb2fd53bceb014fed02.zip
Update documentation
Diffstat (limited to 'examples/client.go')
-rw-r--r--examples/client.go165
1 files changed, 165 insertions, 0 deletions
diff --git a/examples/client.go b/examples/client.go
new file mode 100644
index 0000000..d2819ec
--- /dev/null
+++ b/examples/client.go
@@ -0,0 +1,165 @@
+// +build ignore
+
+package main
+
+import (
+ "bufio"
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "os"
+ "time"
+
+ "git.sr.ht/~adnano/gmi"
+)
+
+var (
+ scanner = bufio.NewScanner(os.Stdin)
+ client = &gmi.Client{}
+)
+
+func init() {
+ // Initialize the client
+ client.KnownHosts.Load() // Load known hosts
+ client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error {
+ err := knownHosts.Lookup(hostname, cert)
+ if err != nil {
+ switch err {
+ case gmi.ErrCertificateNotTrusted:
+ // Alert the user that the certificate is not trusted
+ fmt.Printf("Warning: Certificate for %s is not trusted!\n", hostname)
+ fmt.Println("This could indicate a Man-in-the-Middle attack.")
+ case gmi.ErrUnknownCertificate:
+ // Prompt the user to trust the certificate
+ trust := trustCertificate(cert)
+ switch trust {
+ case trustOnce:
+ // Temporarily trust the certificate
+ knownHosts.AddTemporary(hostname, cert)
+ return nil
+ case trustAlways:
+ // Add the certificate to the known hosts file
+ knownHosts.Add(hostname, cert)
+ return nil
+ }
+ }
+ }
+ return err
+ }
+ client.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate {
+ // If the certificate is in the store, return it
+ if cert, err := store.Lookup(hostname); err == nil {
+ return cert
+ }
+ // Otherwise, generate a certificate
+ fmt.Println("Generating client certificate for", hostname)
+ duration := time.Hour
+ cert, err := gmi.NewCertificate(hostname, duration)
+ if err != nil {
+ return nil
+ }
+ // Store and return the certificate
+ store.Add(hostname, cert)
+ return &cert
+ }
+}
+
+// sendRequest sends a request to the given URL.
+func sendRequest(req *gmi.Request) error {
+ resp, err := client.Send(req)
+ if err != nil {
+ return err
+ }
+
+ // TODO: More fine-grained analysis of the status code.
+ switch resp.Status / 10 {
+ case gmi.StatusClassInput:
+ fmt.Printf("%s: ", resp.Meta)
+ scanner.Scan()
+ req.URL.RawQuery = scanner.Text()
+ return sendRequest(req)
+ case gmi.StatusClassSuccess:
+ fmt.Print(string(resp.Body))
+ return nil
+ case gmi.StatusClassRedirect:
+ fmt.Println("Redirecting to", resp.Meta)
+ // Make the request to the same host
+ red, err := gmi.NewRequestTo(resp.Meta, req.Host)
+ if err != nil {
+ return err
+ }
+ // Handle relative redirects
+ red.URL = req.URL.ResolveReference(red.URL)
+ return sendRequest(red)
+ case gmi.StatusClassTemporaryFailure:
+ return fmt.Errorf("Temporary failure: %s", resp.Meta)
+ case gmi.StatusClassPermanentFailure:
+ return fmt.Errorf("Permanent failure: %s", resp.Meta)
+ case gmi.StatusClassCertificateRequired:
+ // Note that this should not happen unless the server responds with
+ // CertificateRequired even after we send a certificate.
+ // CertificateNotAuthorized and CertificateNotValid are handled here.
+ return fmt.Errorf("Certificate required: %s", resp.Meta)
+ }
+ panic("unreachable")
+}
+
+type trust int
+
+const (
+ trustAbort trust = iota
+ trustOnce
+ trustAlways
+)
+
+const trustPrompt = `The certificate offered by this server is of unknown trust. Its fingerprint is:
+%s
+
+If you knew the fingerprint to expect in advance, verify that this matches.
+Otherwise, this should be safe to trust.
+
+[t]rust always; trust [o]nce; [a]bort
+=> `
+
+func trustCertificate(cert *x509.Certificate) trust {
+ fmt.Printf(trustPrompt, gmi.Fingerprint(cert))
+ scanner.Scan()
+ switch scanner.Text() {
+ case "t":
+ return trustAlways
+ case "o":
+ return trustOnce
+ default:
+ return trustAbort
+ }
+}
+
+func main() {
+ if len(os.Args) < 2 {
+ fmt.Printf("usage: %s gemini://...", os.Args[0])
+ os.Exit(1)
+ }
+
+ var host string
+ if len(os.Args) >= 3 {
+ host = os.Args[2]
+ }
+
+ url := os.Args[1]
+ var req *gmi.Request
+ var err error
+ if host != "" {
+ req, err = gmi.NewRequestTo(url, host)
+ } else {
+ req, err = gmi.NewRequest(url)
+ }
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ if err := sendRequest(req); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}