Add Client.GetInput field
This commit is contained in:
parent
d1dcf070ff
commit
4c5167f590
14
client.go
14
client.go
@ -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
|
||||
}
|
||||
|
||||
|
@ -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.")
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
15
status.go
15
status.go
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user