Add Client.GetInput field

This commit is contained in:
Adnan Maolood 2020-10-27 23:35:22 -04:00
parent d1dcf070ff
commit 4c5167f590
6 changed files with 61 additions and 60 deletions

View File

@ -24,6 +24,10 @@ type Client struct {
// redirects will be enforced.
CheckRedirect func(req *Request, via []*Request) error
// GetInput, if not nil, will be called to retrieve input when the server
// requests it.
GetInput func(prompt string, sensitive bool) (string, bool)
// GetCertificate, if not nil, will be called when a server requests a certificate.
// The returned certificate will be used when sending the request again.
// If the certificate is nil, the request will not be sent again and
@ -141,7 +145,17 @@ func (c *Client) do(req *Request, via []*Request) (*Response, error) {
return resp, ErrTooManyRedirects
}
return c.do(redirect, via)
} else if resp.Status.Class() == StatusClassInput {
if c.GetInput != nil {
input, ok := c.GetInput(resp.Meta, resp.Status == StatusSensitiveInput)
if ok {
req.URL.ForceQuery = true
req.URL.RawQuery = url.QueryEscape(input)
return c.do(req, via)
}
}
}
return resp, nil
}

View File

@ -7,7 +7,7 @@ import (
"fmt"
"log"
gmi "git.sr.ht/~adnano/go-gemini"
"git.sr.ht/~adnano/go-gemini"
)
type user struct {
@ -33,15 +33,14 @@ var (
)
func main() {
var mux gmi.ServeMux
mux.HandleFunc("/", welcome)
mux.HandleFunc("/login", login)
mux.HandleFunc("/login/password", loginPassword)
var mux gemini.ServeMux
mux.HandleFunc("/", login)
mux.HandleFunc("/password", loginPassword)
mux.HandleFunc("/profile", profile)
mux.HandleFunc("/admin", admin)
mux.HandleFunc("/logout", logout)
var server gmi.Server
var server gemini.Server
if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil {
log.Fatal(err)
}
@ -53,74 +52,69 @@ func main() {
}
func getSession(crt *x509.Certificate) (*session, bool) {
fingerprint := gmi.Fingerprint(crt)
fingerprint := gemini.Fingerprint(crt)
session, ok := sessions[fingerprint]
return session, ok
}
func welcome(w *gmi.ResponseWriter, r *gmi.Request) {
fmt.Fprintln(w, "Welcome to this example.")
fmt.Fprintln(w, "=> /login Login")
}
func login(w *gmi.ResponseWriter, r *gmi.Request) {
cert, ok := gmi.Certificate(w, r)
func login(w *gemini.ResponseWriter, r *gemini.Request) {
cert, ok := gemini.Certificate(w, r)
if !ok {
return
}
username, ok := gmi.Input(w, r, "Username")
username, ok := gemini.Input(w, r, "Username")
if !ok {
return
}
fingerprint := gmi.Fingerprint(cert)
fingerprint := gemini.Fingerprint(cert)
sessions[fingerprint] = &session{
username: username,
}
gmi.Redirect(w, "/login/password")
gemini.Redirect(w, "/password")
}
func loginPassword(w *gmi.ResponseWriter, r *gmi.Request) {
cert, ok := gmi.Certificate(w, r)
func loginPassword(w *gemini.ResponseWriter, r *gemini.Request) {
cert, ok := gemini.Certificate(w, r)
if !ok {
return
}
session, ok := getSession(cert)
if !ok {
w.WriteStatus(gmi.StatusCertificateNotAuthorized)
w.WriteStatus(gemini.StatusCertificateNotAuthorized)
return
}
password, ok := gmi.SensitiveInput(w, r, "Password")
password, ok := gemini.SensitiveInput(w, r, "Password")
if !ok {
return
}
expected := logins[session.username].password
if password == expected {
session.authorized = true
gmi.Redirect(w, "/profile")
gemini.Redirect(w, "/profile")
} else {
gmi.SensitiveInput(w, r, "Wrong password. Try again")
gemini.SensitiveInput(w, r, "Wrong password. Try again")
}
}
func logout(w *gmi.ResponseWriter, r *gmi.Request) {
cert, ok := gmi.Certificate(w, r)
func logout(w *gemini.ResponseWriter, r *gemini.Request) {
cert, ok := gemini.Certificate(w, r)
if !ok {
return
}
fingerprint := gmi.Fingerprint(cert)
fingerprint := gemini.Fingerprint(cert)
delete(sessions, fingerprint)
fmt.Fprintln(w, "Successfully logged out.")
}
func profile(w *gmi.ResponseWriter, r *gmi.Request) {
cert, ok := gmi.Certificate(w, r)
func profile(w *gemini.ResponseWriter, r *gemini.Request) {
cert, ok := gemini.Certificate(w, r)
if !ok {
return
}
session, ok := getSession(cert)
if !ok {
w.WriteStatus(gmi.StatusCertificateNotAuthorized)
w.WriteStatus(gemini.StatusCertificateNotAuthorized)
return
}
user := logins[session.username]
@ -129,19 +123,19 @@ func profile(w *gmi.ResponseWriter, r *gmi.Request) {
fmt.Fprintln(w, "=> /logout Logout")
}
func admin(w *gmi.ResponseWriter, r *gmi.Request) {
cert, ok := gmi.Certificate(w, r)
func admin(w *gemini.ResponseWriter, r *gemini.Request) {
cert, ok := gemini.Certificate(w, r)
if !ok {
return
}
session, ok := getSession(cert)
if !ok {
w.WriteStatus(gmi.StatusCertificateNotAuthorized)
w.WriteStatus(gemini.StatusCertificateNotAuthorized)
return
}
user := logins[session.username]
if !user.admin {
w.WriteStatus(gmi.StatusCertificateNotAuthorized)
w.WriteStatus(gemini.StatusCertificateNotAuthorized)
return
}
fmt.Fprintln(w, "Welcome to the admin portal.")

View File

@ -11,13 +11,13 @@ import (
"os"
"time"
gmi "git.sr.ht/~adnano/go-gemini"
"git.sr.ht/~adnano/go-gemini"
)
func main() {
host := "localhost"
duration := 365 * 24 * time.Hour
cert, err := gmi.NewCertificate(host, duration)
cert, err := gemini.NewCertificate(host, duration)
if err != nil {
log.Fatal(err)
}

View File

@ -76,13 +76,7 @@ func sendRequest(req *gemini.Request) error {
return err
}
switch resp.Status.Class() {
case gemini.StatusClassInput:
fmt.Printf("%s: ", resp.Meta)
scanner.Scan()
req.URL.RawQuery = url.QueryEscape(scanner.Text())
return sendRequest(req)
case gemini.StatusClassSuccess:
if resp.Status.Class() == gemini.StatusClassSuccess {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
@ -90,20 +84,8 @@ func sendRequest(req *gemini.Request) error {
}
fmt.Print(string(body))
return nil
case gemini.StatusClassRedirect:
// This should not happen unless CheckRedirect returns false.
return fmt.Errorf("Failed to redirect to %s", resp.Meta)
case gemini.StatusClassTemporaryFailure:
return fmt.Errorf("Temporary failure: %s", resp.Meta)
case gemini.StatusClassPermanentFailure:
return fmt.Errorf("Permanent failure: %s", resp.Meta)
case gemini.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")
return fmt.Errorf("request failed: %d %s: %s", resp.Status, resp.Status.Message(), resp.Meta)
}
type trust int

View File

@ -273,7 +273,8 @@ type Responder interface {
// If no input is provided, it responds with StatusInput.
func Input(w *ResponseWriter, r *Request, prompt string) (string, bool) {
if r.URL.ForceQuery || r.URL.RawQuery != "" {
return r.URL.RawQuery, true
query, err := url.QueryUnescape(r.URL.RawQuery)
return query, err == nil
}
w.WriteHeader(StatusInput, prompt)
return "", false
@ -283,7 +284,8 @@ func Input(w *ResponseWriter, r *Request, prompt string) (string, bool) {
// If no input is provided, it responds with StatusSensitiveInput.
func SensitiveInput(w *ResponseWriter, r *Request, prompt string) (string, bool) {
if r.URL.ForceQuery || r.URL.RawQuery != "" {
return r.URL.RawQuery, true
query, err := url.QueryUnescape(r.URL.RawQuery)
return query, err == nil
}
w.WriteHeader(StatusSensitiveInput, prompt)
return "", false

View File

@ -30,11 +30,20 @@ func (s Status) Class() StatusClass {
}
// Message returns a status message corresponding to this status code.
// It returns an empty string for input, successs, and redirect status codes.
func (s Status) Message() string {
switch s {
case StatusInput:
return "Input"
case StatusSensitiveInput:
return "Sensitive input"
case StatusSuccess:
return "Success"
case StatusRedirect:
return "Redirect"
case StatusRedirectPermanent:
return "Permanent redirect"
case StatusTemporaryFailure:
return "TemporaryFailure"
return "Temporary failure"
case StatusServerUnavailable:
return "Server unavailable"
case StatusCGIError:
@ -44,7 +53,7 @@ func (s Status) Message() string {
case StatusSlowDown:
return "Slow down"
case StatusPermanentFailure:
return "PermanentFailure"
return "Permanent failure"
case StatusNotFound:
return "Not found"
case StatusGone: