diff options
Diffstat (limited to 'pkg/libgemini')
-rw-r--r-- | pkg/libgemini/libgemini.go | 145 |
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 @@ | |||
1 | package libgemini | ||
2 | |||
3 | import ( | ||
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 | |||
17 | const ( | ||
18 | CRLF = "\r\n" | ||
19 | ) | ||
20 | |||
21 | const ( | ||
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 | |||
45 | const ( | ||
46 | MIME_GEMINI = "text/gemini" | ||
47 | DEFAULT_MIME = MIME_GEMINI | ||
48 | DEFAULT_CHARSET = "utf-8" | ||
49 | ) | ||
50 | |||
51 | var ( | ||
52 | HeaderPattern = regexp.MustCompile("^(\\d\\d)[ \\t]+(.*)$") | ||
53 | LinkPattern = regexp.MustCompile("^=>[ \\t]*([^ \\t]+)(?:[ \\t]+(.*))?$") | ||
54 | ) | ||
55 | |||
56 | type Header struct { | ||
57 | Status int | ||
58 | Meta string | ||
59 | } | ||
60 | |||
61 | type Response struct { | ||
62 | Header *Header | ||
63 | Body io.Reader | ||
64 | } | ||
65 | |||
66 | func 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 | |||
117 | func 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 | } | ||