aboutsummaryrefslogtreecommitdiffstats
path: root/internal/port/gemini.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/port/gemini.go')
-rw-r--r--internal/port/gemini.go176
1 files changed, 64 insertions, 112 deletions
diff --git a/internal/port/gemini.go b/internal/port/gemini.go
index b10da7d..0d8292c 100644
--- a/internal/port/gemini.go
+++ b/internal/port/gemini.go
@@ -1,7 +1,6 @@
1package port 1package port
2 2
3import ( 3import (
4 "bufio"
5 "bytes" 4 "bytes"
6 "fmt" 5 "fmt"
7 "html/template" 6 "html/template"
@@ -10,7 +9,6 @@ import (
10 "mime" 9 "mime"
11 "net/http" 10 "net/http"
12 "net/url" 11 "net/url"
13 "regexp"
14 "strings" 12 "strings"
15 13
16 "golang.org/x/net/html/charset" 14 "golang.org/x/net/html/charset"
@@ -21,129 +19,83 @@ import (
21 "github.com/temoto/robotstxt" 19 "github.com/temoto/robotstxt"
22) 20)
23 21
24type SectionType byte 22type GeminiTemplateVariables struct {
25
26const (
27 RAW_TEXT = SectionType(0)
28 REFLOW_TEXT = SectionType(1)
29 LINK = SectionType(2)
30)
31
32type Section struct {
33 Type SectionType
34 Text string
35 URL template.URL
36}
37
38type templateVariables struct {
39 Title string 23 Title string
40 URI string 24 URL string
41 Assets AssetList 25 Assets AssetList
42 Sections []Section 26 Sections []GeminiSection
27 Nav []GeminiNavItem
43} 28}
44 29
45var ( 30type GeminiNavItem struct {
46 TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m") 31 Label string
47) 32 URL string
33}
48 34
49func resolveURI(uri string, baseURL *url.URL) (resolvedURI string) { 35type GeminiSection struct {
36 Type libgemini.GeminiDocSectionType
37 Text string
38 URL template.URL
39 Items []string
40}
41
42func resolveURL(uri string, baseURL *url.URL) (resolvedURL string) {
50 if strings.HasPrefix(uri, "//") { 43 if strings.HasPrefix(uri, "//") {
51 resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "//") 44 resolvedURL = "/gemini/" + strings.TrimPrefix(uri, "//")
52 } else if strings.HasPrefix(uri, "gemini://") { 45 } else if strings.HasPrefix(uri, "gemini://") {
53 resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "gemini://") 46 resolvedURL = "/gemini/" + strings.TrimPrefix(uri, "gemini://")
54 } else if strings.HasPrefix(uri, "gopher://") { 47 } else if strings.HasPrefix(uri, "gopher://") {
55 resolvedURI = "/gopher/" + strings.TrimPrefix(uri, "gopher://") 48 resolvedURL = "/gopher/" + strings.TrimPrefix(uri, "gopher://")
56 } else { 49 } else {
57 url, err := url.Parse(uri) 50 url, err := url.Parse(uri)
58 if err != nil { 51 if err != nil {
59 return "" 52 return ""
60 } 53 }
61 adjustedURI := baseURL.ResolveReference(url) 54 adjustedURL := baseURL.ResolveReference(url)
62 path := adjustedURI.Path 55 path := adjustedURL.Path
63 if !strings.HasPrefix(path, "/") { 56 if !strings.HasPrefix(path, "/") {
64 path = "/" + path 57 path = "/" + path
65 } 58 }
66 if adjustedURI.Scheme == "gemini" { 59 if adjustedURL.Scheme == "gemini" {
67 resolvedURI = "/gemini/" + adjustedURI.Host + path 60 resolvedURL = "/gemini/" + adjustedURL.Host + path
68 } else if adjustedURI.Scheme == "gopher" { 61 } else if adjustedURL.Scheme == "gopher" {
69 resolvedURI = "/gopher/" + adjustedURI.Host + path 62 resolvedURL = "/gopher/" + adjustedURL.Host + path
70 } else { 63 } else {
71 resolvedURI = adjustedURI.String() 64 resolvedURL = adjustedURL.String()
72 } 65 }
73 } 66 }
74 67
75 return 68 return
76} 69}
77 70
78func parseGeminiDocument(body *bytes.Buffer, uri string, hostport string) (sections []Section) { 71func parseGeminiDocument(body *bytes.Buffer, uri string, hostport string) (sections []GeminiSection) {
79 baseURL, err := url.Parse(fmt.Sprintf( 72 baseURL, err := url.Parse(fmt.Sprintf(
80 "gemini://%s/%s", 73 "gemini://%s/%s",
81 hostport, 74 hostport,
82 uri, 75 uri,
83 )) 76 ))
84 if err != nil { 77 if err != nil {
85 return []Section{} 78 return
86 }
87
88 skipSection := true
89
90 section := Section{
91 Type: RAW_TEXT,
92 } 79 }
93 80
94 scanner := bufio.NewScanner(body) 81 unpreppedSections := libgemini.ParseGeminiDocument(body)
95 82
96 for scanner.Scan() { 83 for _, section := range unpreppedSections {
97 line := strings.Trim(scanner.Text(), "\r\n") 84 if section.Type != libgemini.LINK {
98 line = TermEscapeSGRPattern.ReplaceAllString(line, "") 85 sections = append(sections, GeminiSection{
99 86 Type: section.Type,
100 linkMatch := libgemini.LinkPattern.FindStringSubmatch(line) 87 Text: section.Text,
101 if len(linkMatch) != 0 && linkMatch[0] != "" { 88 URL: template.URL(section.URL),
102 curType := section.Type 89 Items: section.Items,
103
104 if !skipSection {
105 sections = append(sections, section)
106 }
107
108 label := linkMatch[2]
109 if label == "" {
110 label = linkMatch[1]
111 }
112
113 sections = append(sections, Section{
114 Type: LINK,
115 Text: label,
116 URL: template.URL(resolveURI(linkMatch[1], baseURL)),
117 }) 90 })
118
119 skipSection = false
120 section = Section{
121 Type: curType,
122 }
123 } else {
124 reflowModeMatch := libgemini.ReflowModePattern.FindStringSubmatch(line)
125 if len(reflowModeMatch) != 0 {
126 newType := RAW_TEXT
127 if section.Type == RAW_TEXT {
128 newType = REFLOW_TEXT
129 }
130
131 if !skipSection {
132 sections = append(sections, section)
133 }
134
135 skipSection = false
136 section = Section{
137 Type: newType,
138 }
139 } else {
140 section.Text = section.Text + "\n" + line
141 }
142 } 91 }
143 }
144 92
145 if !skipSection { 93 sections = append(sections, GeminiSection{
146 sections = append(sections, section) 94 Type: section.Type,
95 Text: section.Text,
96 URL: template.URL(resolveURL(section.URL, baseURL)),
97 Items: section.Items,
98 })
147 } 99 }
148 100
149 return 101 return
@@ -176,12 +128,12 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
176 128
177 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) 129 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
178 if err != nil { 130 if err != nil {
179 if e := tpl.Execute(w, templateVariables{ 131 if e := tpl.Execute(w, GeminiTemplateVariables{
180 Title: title, 132 Title: title,
181 URI: hostport, 133 URL: hostport,
182 Assets: assetList, 134 Assets: assetList,
183 Sections: []Section{{ 135 Sections: []GeminiSection{{
184 Type: RAW_TEXT, 136 Type: libgemini.RAW_TEXT,
185 Text: fmt.Sprintf("Error: %s", err), 137 Text: fmt.Sprintf("Error: %s", err),
186 }}, 138 }},
187 }); e != nil { 139 }); e != nil {
@@ -205,12 +157,12 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
205 ) 157 )
206 158
207 if err != nil { 159 if err != nil {
208 if e := tpl.Execute(w, templateVariables{ 160 if e := tpl.Execute(w, GeminiTemplateVariables{
209 Title: title, 161 Title: title,
210 URI: fmt.Sprintf("%s/%s", hostport, uri), 162 URL: fmt.Sprintf("%s/%s", hostport, uri),
211 Assets: assetList, 163 Assets: assetList,
212 Sections: []Section{{ 164 Sections: []GeminiSection{{
213 Type: RAW_TEXT, 165 Type: libgemini.RAW_TEXT,
214 Text: fmt.Sprintf("Error: %s", err), 166 Text: fmt.Sprintf("Error: %s", err),
215 }}, 167 }},
216 }); e != nil { 168 }); e != nil {
@@ -227,12 +179,12 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
227 uri, 179 uri,
228 )) 180 ))
229 if err != nil { 181 if err != nil {
230 if e := tpl.Execute(w, templateVariables{ 182 if e := tpl.Execute(w, GeminiTemplateVariables{
231 Title: title, 183 Title: title,
232 URI: fmt.Sprintf("%s/%s", hostport, uri), 184 URL: fmt.Sprintf("%s/%s", hostport, uri),
233 Assets: assetList, 185 Assets: assetList,
234 Sections: []Section{{ 186 Sections: []GeminiSection{{
235 Type: RAW_TEXT, 187 Type: libgemini.RAW_TEXT,
236 Text: fmt.Sprintf("Error: %s", err), 188 Text: fmt.Sprintf("Error: %s", err),
237 }}, 189 }},
238 }); e != nil { 190 }); e != nil {
@@ -242,17 +194,17 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
242 return 194 return
243 } 195 }
244 196
245 http.Redirect(w, req, resolveURI(res.Header.Meta, baseURL), http.StatusFound) 197 http.Redirect(w, req, resolveURL(res.Header.Meta, baseURL), http.StatusFound)
246 return 198 return
247 } 199 }
248 200
249 if int(res.Header.Status/10) != 2 { 201 if int(res.Header.Status/10) != 2 {
250 if err := tpl.Execute(w, templateVariables{ 202 if err := tpl.Execute(w, GeminiTemplateVariables{
251 Title: title, 203 Title: title,
252 URI: fmt.Sprintf("%s/%s", hostport, uri), 204 URL: fmt.Sprintf("%s/%s", hostport, uri),
253 Assets: assetList, 205 Assets: assetList,
254 Sections: []Section{{ 206 Sections: []GeminiSection{{
255 Type: RAW_TEXT, 207 Type: libgemini.RAW_TEXT,
256 Text: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), 208 Text: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta),
257 }}, 209 }},
258 }); err != nil { 210 }); err != nil {
@@ -277,20 +229,20 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
277 writer.Close() 229 writer.Close()
278 } 230 }
279 231
280 var sections []Section 232 var sections []GeminiSection
281 233
282 if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) { 234 if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) {
283 sections = parseGeminiDocument(buf, uri, hostport) 235 sections = parseGeminiDocument(buf, uri, hostport)
284 } else { 236 } else {
285 sections = append(sections, Section{ 237 sections = append(sections, GeminiSection{
286 Type: RAW_TEXT, 238 Type: libgemini.RAW_TEXT,
287 Text: buf.String(), 239 Text: buf.String(),
288 }) 240 })
289 } 241 }
290 242
291 if err := tpl.Execute(w, templateVariables{ 243 if err := tpl.Execute(w, GeminiTemplateVariables{
292 Title: title, 244 Title: title,
293 URI: fmt.Sprintf("%s/%s", hostport, uri), 245 URL: fmt.Sprintf("%s/%s", hostport, uri),
294 Assets: assetList, 246 Assets: assetList,
295 Sections: sections, 247 Sections: sections,
296 }); err != nil { 248 }); err != nil {