From 79165833dea2884b3d7647afc65fe2467b310c18 Mon Sep 17 00:00:00 2001 From: Adnan Maolood Date: Tue, 27 Oct 2020 13:30:35 -0400 Subject: [PATCH] Add (*ResponseWriter).WriteStatus function --- fs.go | 2 +- gemini.go | 93 +++++++++++++++++++++++++++++++++++++++-------------- response.go | 8 ++--- server.go | 54 ++++++++++++------------------- 4 files changed, 95 insertions(+), 62 deletions(-) diff --git a/fs.go b/fs.go index cafd207..66d08b9 100644 --- a/fs.go +++ b/fs.go @@ -28,7 +28,7 @@ func (fsh fsHandler) Respond(w *ResponseWriter, r *Request) { path := path.Clean(r.URL.Path) f, err := fsh.Open(path) if err != nil { - NotFound(w, r) + w.WriteStatus(StatusNotFound) return } // Detect mimetype diff --git a/gemini.go b/gemini.go index f03fd82..3faa420 100644 --- a/gemini.go +++ b/gemini.go @@ -9,35 +9,80 @@ import ( ) // Status codes. +type Status int + const ( - StatusInput = 10 - StatusSensitiveInput = 11 - StatusSuccess = 20 - StatusRedirect = 30 - StatusRedirectPermanent = 31 - StatusTemporaryFailure = 40 - StatusServerUnavailable = 41 - StatusCGIError = 42 - StatusProxyError = 43 - StatusSlowDown = 44 - StatusPermanentFailure = 50 - StatusNotFound = 51 - StatusGone = 52 - StatusProxyRequestRefused = 53 - StatusBadRequest = 59 - StatusCertificateRequired = 60 - StatusCertificateNotAuthorized = 61 - StatusCertificateNotValid = 62 + StatusInput Status = 10 + StatusSensitiveInput Status = 11 + StatusSuccess Status = 20 + StatusRedirect Status = 30 + StatusRedirectPermanent Status = 31 + StatusTemporaryFailure Status = 40 + StatusServerUnavailable Status = 41 + StatusCGIError Status = 42 + StatusProxyError Status = 43 + StatusSlowDown Status = 44 + StatusPermanentFailure Status = 50 + StatusNotFound Status = 51 + StatusGone Status = 52 + StatusProxyRequestRefused Status = 53 + StatusBadRequest Status = 59 + StatusCertificateRequired Status = 60 + StatusCertificateNotAuthorized Status = 61 + StatusCertificateNotValid Status = 62 ) +// Class returns the status class for this status code. +func (s Status) Class() StatusClass { + return StatusClass(s / 10) +} + +// StatusMessage returns the status message corresponding to the provided +// status code. +// StatusMessage returns an empty string for input, successs, and redirect +// status codes. +func (s Status) Message() string { + switch s { + case StatusTemporaryFailure: + return "TemporaryFailure" + case StatusServerUnavailable: + return "Server unavailable" + case StatusCGIError: + return "CGI error" + case StatusProxyError: + return "Proxy error" + case StatusSlowDown: + return "Slow down" + case StatusPermanentFailure: + return "PermanentFailure" + case StatusNotFound: + return "Not found" + case StatusGone: + return "Gone" + case StatusProxyRequestRefused: + return "Proxy request refused" + case StatusBadRequest: + return "Bad request" + case StatusCertificateRequired: + return "Certificate required" + case StatusCertificateNotAuthorized: + return "Certificate not authorized" + case StatusCertificateNotValid: + return "Certificate not valid" + } + return "" +} + // Status code categories. +type StatusClass int + const ( - StatusClassInput = 1 - StatusClassSuccess = 2 - StatusClassRedirect = 3 - StatusClassTemporaryFailure = 4 - StatusClassPermanentFailure = 5 - StatusClassCertificateRequired = 6 + StatusClassInput StatusClass = 1 + StatusClassSuccess StatusClass = 2 + StatusClassRedirect StatusClass = 3 + StatusClassTemporaryFailure StatusClass = 4 + StatusClassPermanentFailure StatusClass = 5 + StatusClassCertificateRequired StatusClass = 6 ) // Errors. diff --git a/response.go b/response.go index 81d8dea..40d9046 100644 --- a/response.go +++ b/response.go @@ -10,7 +10,7 @@ import ( // Response is a Gemini response. type Response struct { // Status represents the response status. - Status int + Status Status // Meta contains more information related to the response status. // For successful responses, Meta should contain the mimetype of the response. @@ -37,11 +37,11 @@ func (resp *Response) read(r *bufio.Reader) error { if err != nil { return err } - resp.Status = status + resp.Status = Status(status) // Disregard invalid status codes const minStatus, maxStatus = 1, 6 - statusClass := status / 10 + statusClass := resp.Status.Class() if statusClass < minStatus || statusClass > maxStatus { return ErrInvalidResponse } @@ -74,7 +74,7 @@ func (resp *Response) read(r *bufio.Reader) error { } // Read response body - if status/10 == StatusClassSuccess { + if resp.Status.Class() == StatusClassSuccess { var err error resp.Body, err = ioutil.ReadAll(r) if err != nil { diff --git a/server.go b/server.go index 5e649d8..e0d6214 100644 --- a/server.go +++ b/server.go @@ -168,7 +168,12 @@ func (s *Server) respond(conn net.Conn) { RemoteAddr: conn.RemoteAddr(), TLS: conn.(*tls.Conn).ConnectionState(), } - s.responder(req).Respond(w, req) + resp := s.responder(req) + if resp != nil { + resp.Respond(w, req) + } else { + w.WriteStatus(StatusNotFound) + } } w.b.Flush() conn.Close() @@ -184,7 +189,7 @@ func (s *Server) responder(r *Request) Responder { return h } } - return ResponderFunc(NotFound) + return nil } // ResponseWriter is used by a Gemini handler to construct a Gemini response. @@ -208,22 +213,27 @@ func newResponseWriter(conn net.Conn) *ResponseWriter { // For successful responses, Meta should contain the mimetype of the response. // For failure responses, Meta should contain a short description of the failure. // Meta should not be longer than 1024 bytes. -func (w *ResponseWriter) WriteHeader(status int, meta string) { +func (w *ResponseWriter) WriteHeader(status Status, meta string) { if w.wroteHeader { return } - w.b.WriteString(strconv.Itoa(status)) + w.b.WriteString(strconv.Itoa(int(status))) w.b.WriteByte(' ') w.b.WriteString(meta) w.b.Write(crlf) // Only allow body to be written on successful status codes. - if status/10 == StatusClassSuccess { + if status.Class() == StatusClassSuccess { w.bodyAllowed = true } w.wroteHeader = true } +// WriteStatus writes the response header with the given status code. +func (w *ResponseWriter) WriteStatus(status Status) { + w.WriteHeader(status, status.Message()) +} + // SetMimetype sets the mimetype that will be written for a successful response. // The provided mimetype will only be used if Write is called without calling // WriteHeader. @@ -280,42 +290,20 @@ func SensitiveInput(w *ResponseWriter, r *Request, prompt string) (string, bool) } // Redirect replies to the request with a redirect to the given URL. -func Redirect(w *ResponseWriter, r *Request, url string) { +func Redirect(w *ResponseWriter, url string) { w.WriteHeader(StatusRedirect, url) } // PermanentRedirect replies to the request with a permanent redirect to the given URL. -func PermanentRedirect(w *ResponseWriter, r *Request, url string) { +func PermanentRedirect(w *ResponseWriter, url string) { w.WriteHeader(StatusRedirectPermanent, url) } -// NotFound replies to the request with the NotFound status code. -func NotFound(w *ResponseWriter, r *Request) { - w.WriteHeader(StatusNotFound, "Not found") -} - -// Gone replies to the request with the Gone status code. -func Gone(w *ResponseWriter, r *Request) { - w.WriteHeader(StatusGone, "Gone") -} - -// CertificateRequired responds to the request with the CertificateRequired -// status code. -func CertificateRequired(w *ResponseWriter, r *Request) { - w.WriteHeader(StatusCertificateRequired, "Certificate required") -} - -// CertificateNotAuthorized responds to the request with -// the CertificateNotAuthorized status code. -func CertificateNotAuthorized(w *ResponseWriter, r *Request) { - w.WriteHeader(StatusCertificateNotAuthorized, "Certificate not authorized") -} - // Certificate returns the request certificate. If one is not provided, // it returns nil and responds with StatusCertificateRequired. func Certificate(w *ResponseWriter, r *Request) (*x509.Certificate, bool) { if len(r.TLS.PeerCertificates) == 0 { - CertificateRequired(w, r) + w.WriteStatus(StatusCertificateRequired) return nil, false } return r.TLS.PeerCertificates[0], true @@ -458,14 +446,14 @@ func (mux *ServeMux) Respond(w *ResponseWriter, r *Request) { // If the given path is /tree and its handler is not registered, // redirect for /tree/. if u, ok := mux.redirectToPathSlash(path, r.URL); ok { - Redirect(w, r, u.String()) + Redirect(w, u.String()) return } if path != r.URL.Path { u := *r.URL u.Path = path - Redirect(w, r, u.String()) + Redirect(w, u.String()) return } @@ -474,7 +462,7 @@ func (mux *ServeMux) Respond(w *ResponseWriter, r *Request) { resp := mux.match(path) if resp == nil { - NotFound(w, r) + w.WriteStatus(StatusNotFound) return } resp.Respond(w, r)