gemini-browser

A text-based gemini browser
git clone git://git.laack.co/gemini-browser.git
Log | Files | Refs | README

gemini.go (3781B)


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