aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/libgemini
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/libgemini')
-rw-r--r--pkg/libgemini/libgemini.go145
1 files changed, 145 insertions, 0 deletions
diff --git a/pkg/libgemini/libgemini.go b/pkg/libgemini/libgemini.go
new file mode 100644
index 0000000..303490c
--- /dev/null
+++ b/pkg/libgemini/libgemini.go
@@ -0,0 +1,145 @@
1package libgemini
2
3import (
4 "bufio"
5 "crypto/tls"
6 "errors"
7 "fmt"
8 "io"
9 "mime"
10 "net"
11 "net/url"
12 "regexp"
13 "strconv"
14 "strings"
15)
16
17const (
18 CRLF = "\r\n"
19)
20
21const (
22 STATUS_INPUT = 10
23 STATUS_SUCCESS = 20
24 STATUS_SUCCESS_CERT = 21
25 STATUS_REDIRECT_TEMP = 30
26 STATUS_REDIRECT_PERM = 31
27 STATUS_TEMP_FAILURE = 40
28 STATUS_SERVER_UNAVAILABLE = 41
29 STATUS_CGI_ERROR = 42
30 STATUS_PROXY_ERROR = 43
31 STATUS_SLOW_DOWN = 44
32 STATUS_PERM_FAILURE = 50
33 STATUS_NOT_FOUND = 51
34 STATUS_GONE = 52
35 STATUS_PROXY_REFUSED = 53
36 STATUS_BAD_REQUEST = 59
37 STATUS_CLIENT_CERT_EXPIRED = 60
38 STATUS_TRANSIENT_CERT_REQUEST = 61
39 STATUS_AUTH_CERT_REQUIRED = 62
40 STATUS_CERT_REJECTED = 63
41 STATUS_FUTURE_CERT_REJECTED = 64
42 STATUS_EXPIRED_CERT_REJECTED = 65
43)
44
45const (
46 MIME_GEMINI = "text/gemini"
47 DEFAULT_MIME = MIME_GEMINI
48 DEFAULT_CHARSET = "utf-8"
49)
50
51var (
52 HeaderPattern = regexp.MustCompile("^(\\d\\d)[ \\t]+(.*)$")
53 LinkPattern = regexp.MustCompile("^=>[ \\t]*([^ \\t]+)(?:[ \\t]+(.*))?$")
54)
55
56type Header struct {
57 Status int
58 Meta string
59}
60
61type Response struct {
62 Header *Header
63 Body io.Reader
64}
65
66func Get(uri string) (*Response, error) {
67 u, err := url.Parse(uri)
68 if err != nil {
69 return nil, err
70 }
71
72 if u.Scheme != "gemini" {
73 return nil, errors.New("invalid scheme for uri")
74 }
75
76 host := u.Hostname()
77 port := u.Port()
78
79 if port == "" {
80 port = "1965"
81 }
82
83 conn, err := tls.Dial("tcp", net.JoinHostPort(host, port), &tls.Config{
84 MinVersion: tls.VersionTLS12,
85 InsecureSkipVerify: true,
86 })
87 if err != nil {
88 return nil, err
89 }
90
91 _, err = conn.Write([]byte(u.String() + CRLF))
92 if err != nil {
93 conn.Close()
94 return nil, err
95 }
96
97 reader := bufio.NewReader(conn)
98
99 line, _, err := reader.ReadLine()
100 if err != nil {
101 conn.Close()
102 return nil, err
103 }
104
105 header, err := ParseHeader(string(line))
106 if err != nil {
107 conn.Close()
108 return nil, err
109 }
110
111 return &Response{
112 Header: header,
113 Body: reader,
114 }, nil
115}
116
117func ParseHeader(line string) (header *Header, err error) {
118 matches := HeaderPattern.FindStringSubmatch(line)
119
120 status, err := strconv.Atoi(matches[1])
121 if err != nil {
122 return nil, err
123 }
124
125 meta := matches[2]
126
127 if int(status/10) == 2 {
128 mediaType, params, err := mime.ParseMediaType(meta)
129
130 if err != nil {
131 meta = fmt.Sprintf("%s;charset=%s", DEFAULT_MIME, DEFAULT_CHARSET)
132 } else if strings.HasPrefix(mediaType, "text/") {
133 if _, ok := params["charset"]; !ok {
134 meta += ";charset=" + DEFAULT_CHARSET
135 }
136 }
137 }
138
139 header = &Header{
140 Status: status,
141 Meta: meta,
142 }
143
144 return
145}