From 2ef530daa47b301a40c1ee93cd43b8f36fc68c0b Mon Sep 17 00:00:00 2001 From: tjpcc Date: Tue, 17 Jan 2023 15:59:29 -0700 Subject: pull request, response, handlers out of the gemini package --- gemini/response.go | 204 +++++++++++++++++++++++++---------------------------- 1 file changed, 95 insertions(+), 109 deletions(-) (limited to 'gemini/response.go') diff --git a/gemini/response.go b/gemini/response.go index 5b5ced4..0452462 100644 --- a/gemini/response.go +++ b/gemini/response.go @@ -6,65 +6,68 @@ import ( "errors" "io" "strconv" + + "tildegit.org/tjp/gus" ) -// StatusCategory represents the various types of responses. -type StatusCategory int +// ResponseCategory represents the various types of gemini responses. +type ResponseCategory int const ( - // StatusCategoryInput is for responses which request additional input. + // ResponseCategoryInput is for responses which request additional input. // // The META line will be the prompt to display to the user. - StatusCategoryInput StatusCategory = iota*10 + 10 - // StatusCategorySuccess is for successful responses. + ResponseCategoryInput ResponseCategory = iota*10 + 10 + // ResponseCategorySuccess is for successful responses. // // The META line will be the resource's mime type. // This is the only response status which indicates the presence of a response body, // and it will contain the resource itself. - StatusCategorySuccess - // StatusCategoryRedirect is for responses which direct the client to an alternative URL. + ResponseCategorySuccess + // ResponseCategoryRedirect is for responses which direct the client to an alternative URL. // // The META line will contain the new URL the client should try. - StatusCategoryRedirect - // StatusCategoryTemporaryFailure is for responses which indicate a transient server-side failure. + ResponseCategoryRedirect + // ResponseCategoryTemporaryFailure is for responses which indicate a transient server-side failure. // // The META line may contain a line with more information about the error. - StatusCategoryTemporaryFailure - // StatusCategoryPermanentFailure is for permanent failure responses. + ResponseCategoryTemporaryFailure + // ResponseCategoryPermanentFailure is for permanent failure responses. // // The META line may contain a line with more information about the error. - StatusCategoryPermanentFailure - // StatusCategoryCertificateRequired indicates client certificate related issues. + ResponseCategoryPermanentFailure + // ResponseCategoryCertificateRequired indicates client certificate related issues. // // The META line may contain a line with more information about the error. - StatusCategoryCertificateRequired + ResponseCategoryCertificateRequired ) -// Status is the integer status code of a gemini response. -type Status int +func ResponseCategoryForStatus(status gus.Status) ResponseCategory { + return ResponseCategory(status / 10) +} const ( // StatusInput indicates a required query parameter at the requested URL. - StatusInput Status = Status(StatusCategoryInput) + iota + StatusInput gus.Status = gus.Status(ResponseCategoryInput) + iota // StatusSensitiveInput indicates a sensitive query parameter is required. StatusSensitiveInput ) const ( // StatusSuccess is a successful response. - StatusSuccess = Status(StatusCategorySuccess) + iota + StatusSuccess = gus.Status(ResponseCategorySuccess) + iota ) const ( // StatusTemporaryRedirect indicates a temporary redirect to another URL. - StatusTemporaryRedirect = Status(StatusCategoryRedirect) + iota + StatusTemporaryRedirect = gus.Status(ResponseCategoryRedirect) + iota // StatusPermanentRedirect indicates that the resource should always be requested at the new URL. StatusPermanentRedirect ) const ( // StatusTemporaryFailure indicates that the request failed and there is no response body. - StatusTemporaryFailure = Status(StatusCategoryTemporaryFailure) + iota + StatusTemporaryFailure = gus.Status(ResponseCategoryTemporaryFailure) + iota // StatusServerUnavailable occurs when the server is unavailable due to overload or maintenance. StatusServerUnavailable // StatusCGIError is the result of a failure of a CGI script. @@ -80,7 +83,7 @@ const ( const ( // StatusPermanentFailure is a server failure which should be expected to continue indefinitely. - StatusPermanentFailure = Status(StatusCategoryPermanentFailure) + iota + StatusPermanentFailure = gus.Status(ResponseCategoryPermanentFailure) + iota // StatusNotFound means the resource doesn't exist but it may in the future. StatusNotFound // StatusGone occurs when a resource will not be available any longer. @@ -88,58 +91,37 @@ const ( // StatusProxyRequestRefused means the server is unwilling to act as a proxy for the resource. StatusProxyRequestRefused // StatusBadRequest indicates that the request was malformed somehow. - StatusBadRequest = Status(StatusCategoryPermanentFailure) + 9 + StatusBadRequest = gus.Status(ResponseCategoryPermanentFailure) + 9 ) const ( // StatusClientCertificateRequired is returned when a certificate was required but not provided. - StatusClientCertificateRequired = Status(StatusCategoryCertificateRequired) + iota + StatusClientCertificateRequired = gus.Status(ResponseCategoryCertificateRequired) + iota // StatusCertificateNotAuthorized means the certificate doesn't grant access to the requested resource. StatusCertificateNotAuthorized // StatusCertificateNotValid means the provided client certificate is invalid. StatusCertificateNotValid ) -// StatusCategory returns the category a specific status belongs to. -func (s Status) Category() StatusCategory { - return StatusCategory(s / 10) -} - -// Response contains everything in a gemini protocol response. -type Response struct { - // Status is the status code of the response. - Status Status - - // Meta is the status-specific line of additional information. - Meta string - - // Body is the response body, if any. - // - // It is not guaranteed to be readable more than once. - Body io.Reader - - reader io.Reader -} - // Input builds an input-prompting response. -func Input(prompt string) *Response { - return &Response{ +func Input(prompt string) *gus.Response { + return &gus.Response{ Status: StatusInput, Meta: prompt, } } // SensitiveInput builds a password-prompting response. -func SensitiveInput(prompt string) *Response { - return &Response{ +func SensitiveInput(prompt string) *gus.Response { + return &gus.Response{ Status: StatusSensitiveInput, Meta: prompt, } } // Success builds a success response with resource body. -func Success(mediatype string, body io.Reader) *Response { - return &Response{ +func Success(mediatype string, body io.Reader) *gus.Response { + return &gus.Response{ Status: StatusSuccess, Meta: mediatype, Body: body, @@ -147,120 +129,120 @@ func Success(mediatype string, body io.Reader) *Response { } // Redirect builds a redirect response. -func Redirect(url string) *Response { - return &Response{ +func Redirect(url string) *gus.Response { + return &gus.Response{ Status: StatusTemporaryRedirect, Meta: url, } } // PermanentRedirect builds a response with a permanent redirect. -func PermanentRedirect(url string) *Response { - return &Response{ +func PermanentRedirect(url string) *gus.Response { + return &gus.Response{ Status: StatusPermanentRedirect, Meta: url, } } // Failure builds a temporary failure response from an error. -func Failure(err error) *Response { - return &Response{ +func Failure(err error) *gus.Response { + return &gus.Response{ Status: StatusTemporaryFailure, Meta: err.Error(), } } // Unavailable build a "server unavailable" response. -func Unavailable(msg string) *Response { - return &Response{ +func Unavailable(msg string) *gus.Response { + return &gus.Response{ Status: StatusServerUnavailable, Meta: msg, } } // CGIError builds a "cgi error" response. -func CGIError(err string) *Response { - return &Response{ +func CGIError(err string) *gus.Response { + return &gus.Response{ Status: StatusCGIError, Meta: err, } } // ProxyError builds a proxy error response. -func ProxyError(msg string) *Response { - return &Response{ +func ProxyError(msg string) *gus.Response { + return &gus.Response{ Status: StatusProxyError, Meta: msg, } } // SlowDown builds a "slow down" response with the number of seconds until the resource is available. -func SlowDown(seconds int) *Response { - return &Response{ +func SlowDown(seconds int) *gus.Response { + return &gus.Response{ Status: StatusSlowDown, Meta: strconv.Itoa(seconds), } } // PermanentFailure builds a "permanent failure" from an error. -func PermanentFailure(err error) *Response { - return &Response{ +func PermanentFailure(err error) *gus.Response { + return &gus.Response{ Status: StatusPermanentFailure, Meta: err.Error(), } } // NotFound builds a "resource not found" response. -func NotFound(msg string) *Response { - return &Response{ +func NotFound(msg string) *gus.Response { + return &gus.Response{ Status: StatusNotFound, Meta: msg, } } // Gone builds a "resource gone" response. -func Gone(msg string) *Response { - return &Response{ +func Gone(msg string) *gus.Response { + return &gus.Response{ Status: StatusGone, Meta: msg, } } // RefuseProxy builds a "proxy request refused" response. -func RefuseProxy(msg string) *Response { - return &Response{ +func RefuseProxy(msg string) *gus.Response { + return &gus.Response{ Status: StatusProxyRequestRefused, Meta: msg, } } // BadRequest builds a "bad request" response. -func BadRequest(msg string) *Response { - return &Response{ +func BadRequest(msg string) *gus.Response { + return &gus.Response{ Status: StatusBadRequest, Meta: msg, } } // RequireCert builds a "client certificate required" response. -func RequireCert(msg string) *Response { - return &Response{ +func RequireCert(msg string) *gus.Response { + return &gus.Response{ Status: StatusClientCertificateRequired, Meta: msg, } } // CertAuthFailure builds a "certificate not authorized" response. -func CertAuthFailure(msg string) *Response { - return &Response{ +func CertAuthFailure(msg string) *gus.Response { + return &gus.Response{ Status: StatusCertificateNotAuthorized, Meta: msg, } } // CertInvalid builds a "client certificate not valid" response. -func CertInvalid(msg string) *Response { - return &Response{ +func CertInvalid(msg string) *gus.Response { + return &gus.Response{ Status: StatusCertificateNotValid, Meta: msg, } @@ -275,7 +257,7 @@ var InvalidResponseHeaderLine = errors.New("Invalid response header line.") // ParseResponse parses a complete gemini response from a reader. // // The reader must contain only one gemini response. -func ParseResponse(rdr io.Reader) (*Response, error) { +func ParseResponse(rdr io.Reader) (*gus.Response, error) { bufrdr := bufio.NewReader(rdr) hdrLine, err := bufrdr.ReadBytes('\n') @@ -295,53 +277,57 @@ func ParseResponse(rdr io.Reader) (*Response, error) { return nil, InvalidResponseHeaderLine } - return &Response{ - Status: Status(status), + return &gus.Response{ + Status: gus.Status(status), Meta: string(hdrLine[3:]), Body: bufrdr, }, nil } -// Read implements io.Reader for Response. -func (r *Response) Read(b []byte) (int, error) { - r.ensureReader() - return r.reader.Read(b) +type ResponseReader interface { + io.Reader + io.WriterTo + io.Closer } -// WriteTo implements io.WriterTo for Response. -func (r *Response) WriteTo(dst io.Writer) (int64, error) { - r.ensureReader() - return r.reader.(io.WriterTo).WriteTo(dst) +func NewResponseReader(response *gus.Response) ResponseReader { + return &responseReader{ Response: response } } -// Close implements io.Closer and ensures the body gets closed. -func (r *Response) Close() error { - if r != nil { - if cl, ok := r.Body.(io.Closer); ok { - return cl.Close() - } - } - return nil +type responseReader struct { + *gus.Response + reader io.Reader +} + +func (rdr *responseReader) Read(b []byte) (int, error) { + rdr.ensureReader() + return rdr.reader.Read(b) +} + +func (rdr *responseReader) WriteTo(dst io.Writer) (int64, error) { + rdr.ensureReader() + return rdr.reader.(io.WriterTo).WriteTo(dst) } -func (r *Response) ensureReader() { - if r.reader != nil { +func (rdr *responseReader) ensureReader() { + if rdr.reader != nil { return } - hdr := bytes.NewBuffer(r.headerLine()) - if r.Body != nil { - r.reader = io.MultiReader(hdr, r.Body) + hdr := bytes.NewBuffer(rdr.headerLine()) + if rdr.Body != nil { + rdr.reader = io.MultiReader(hdr, rdr.Body) } else { - r.reader = hdr + rdr.reader = hdr } } -func (r Response) headerLine() []byte { - buf := make([]byte, len(r.Meta)+5) - _ = strconv.AppendInt(buf[:0], int64(r.Status), 10) +func (rdr responseReader) headerLine() []byte { + meta := rdr.Meta.(string) + buf := make([]byte, len(meta)+5) + _ = strconv.AppendInt(buf[:0], int64(rdr.Status), 10) buf[2] = ' ' - copy(buf[3:], r.Meta) + copy(buf[3:], meta) buf[len(buf)-2] = '\r' buf[len(buf)-1] = '\n' return buf -- cgit v1.2.3