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