1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
package gemini
import (
"fmt"
"net/url"
"strings"
)
const (
URLMaxLength = 1024
MetaMaxLength = 1024
)
// Gemini status codes as defined in the Gemini spec Appendix 1.
const (
StatusInput = 10
StatusSensitiveInput = 11
StatusSuccess = 20
StatusRedirect = 30
StatusRedirectTemporary = 30
StatusRedirectPermanent = 31
StatusTemporaryFailure = 40
StatusUnavailable = 41
StatusCGIError = 42
StatusProxyError = 43
StatusSlowDown = 44
StatusPermanentFailure = 50
StatusNotFound = 51
StatusGone = 52
StatusProxyRequestRefused = 53
StatusBadRequest = 59
StatusClientCertificateRequired = 60
StatusCertificateNotAuthorised = 61
StatusCertificateNotValid = 62
)
var statusText = map[int]string{
StatusInput: "Input",
StatusSensitiveInput: "Sensitive Input",
StatusSuccess: "Success",
// StatusRedirect: "Redirect - Temporary"
StatusRedirectTemporary: "Redirect - Temporary",
StatusRedirectPermanent: "Redirect - Permanent",
StatusTemporaryFailure: "Temporary Failure",
StatusUnavailable: "Server Unavailable",
StatusCGIError: "CGI Error",
StatusProxyError: "Proxy Error",
StatusSlowDown: "Slow Down",
StatusPermanentFailure: "Permanent Failure",
StatusNotFound: "Not Found",
StatusGone: "Gone",
StatusProxyRequestRefused: "Proxy Request Refused",
StatusBadRequest: "Bad Request",
StatusClientCertificateRequired: "Client Certificate Required",
StatusCertificateNotAuthorised: "Certificate Not Authorised",
StatusCertificateNotValid: "Certificate Not Valid",
}
// StatusText returns a text for the Gemini status code. It returns the empty
// string if the code is unknown.
func StatusText(code int) string {
return statusText[code]
}
// SimplifyStatus simplify the response status by ommiting the detailed second digit of the status code.
func SimplifyStatus(status int) int {
return (status / 10) * 10
}
// IsStatusValid checks whether an int status is covered by the spec.
// Note that:
// A client SHOULD deal with undefined status codes
// between '10' and '69' per the default action of the initial digit.
func IsStatusValid(status int) bool {
_, found := statusText[status]
return found
}
// StatusInRange returns true if the status has a valid first digit.
// This means it can be handled even if it's not defined by the spec,
// because it has a known category
func StatusInRange(status int) bool {
if status < 10 || status > 69 {
return false
}
return true
}
// CleanStatus returns the status code as is, unless it's invalid but still in range
// Then it returns the status code with the second digit zeroed. So 51 returns 51,
// but 22 returns 20.
//
// This corresponds with the spec:
// A client SHOULD deal with undefined status codes
// between '10' and '69' per the default action of the initial digit.
func CleanStatus(status int) int {
// All the functions come together!
if !IsStatusValid(status) && StatusInRange(status) {
return SimplifyStatus(status)
}
return status
}
// QueryEscape provides URL query escaping in a way that follows the Gemini spec.
// It is the same as url.PathEscape, but it also replaces the +, because Gemini
// requires percent-escaping for queries.
func QueryEscape(query string) string {
return strings.ReplaceAll(url.PathEscape(query), "+", "%2B")
}
// QueryUnescape is the same as url.PathUnescape
func QueryUnescape(query string) (string, error) {
return url.PathUnescape(query)
}
type Error struct {
Err error
Status int
}
func (e Error) Error() string {
return fmt.Sprintf("Status %d: %v", e.Status, e.Err)
}
func (e Error) Unwrap() error {
return e.Err
}
|