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
|
// Package gemini implements the Gemini protocol.
package gemini
import (
"crypto/tls"
"errors"
"io"
"net/url"
"strconv"
)
// Status codes.
const (
StatusInput = 10
StatusSensitiveInput = 11
StatusSuccess = 20
StatusRedirectTemporary = 30
StatusRedirectPermanent = 31
StatusTemporaryFailure = 40
StatusServerUnavailable = 41
StatusCGIError = 42
StatusProxyError = 43
StatusSlowDown = 44
StatusPermanentFailure = 50
StatusNotFound = 51
StatusGone = 52
StatusProxyRequestRefused = 53
StatusBadRequest = 59
StatusClientCertificateRequired = 60
StatusCertificateNotAuthorised = 61
StatusCertificateNotValid = 62
)
// Status code categories.
const (
StatusClassInput = 1
StatusClassSuccess = 2
StatusClassRedirect = 3
StatusClassTemporaryFailure = 4
StatusClassPermanentFailure = 5
StatusClassClientCertificateRequired = 6
)
// Errors.
var (
ErrProtocol = errors.New("Protocol error")
ErrInvalidURL = errors.New("Invalid URL")
)
// Request is a Gemini request.
//
// A Request can optionally be configured with a client certificate. Example:
//
// req := NewRequest(url)
// cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
// if err != nil {
// panic(err)
// }
// req.Certificates = append(req.Certificates, cert)
//
type Request struct {
Host string // host or host:port
URL *url.URL // the requested URL
Certificates []tls.Certificate // client certificates
}
// NewRequest returns a new request. The host is inferred from the provided url.
func NewRequest(rawurl string) (*Request, error) {
u, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
// UserInfo is invalid
if u.User != nil {
return nil, ErrInvalidURL
}
return &Request{
Host: u.Host,
URL: u,
}, nil
}
// NewProxyRequest returns a new request using the provided host.
func NewProxyRequest(host, rawurl string) (*Request, error) {
u, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
// UserInfo is invalid
if u.User != nil {
return nil, ErrInvalidURL
}
return &Request{
Host: host,
URL: u,
}, nil
}
// Write writes the Gemini request to the provided io.Writer.
func (r *Request) Write(w io.Writer) error {
request := r.URL.String() + "\r\n"
_, err := w.Write([]byte(request))
return err
}
// Response is a Gemini response.
type Response struct {
Status int
Meta string
Body []byte
}
// Write writes the Gemini response header and body to the provided io.Writer.
func (r *Response) Write(w io.Writer) error {
header := strconv.Itoa(r.Status) + " " + r.Meta + "\r\n"
if _, err := w.Write([]byte(header)); err != nil {
return err
}
// Only write the response body on success
if r.Status/10 == StatusClassSuccess {
if _, err := w.Write(r.Body); err != nil {
return err
}
}
return nil
}
|