proxy.go (view raw)
1// Copied from https://git.sr.ht/~sircmpwn/kineto/tree/master/item/main.go
2package main
3
4import (
5 "fmt"
6 "html/template"
7 "io"
8 "mime"
9 "net/http"
10 "net/url"
11 "path"
12 "strings"
13 "time"
14
15 "git.sr.ht/~adnano/go-gemini"
16)
17
18func proxyGemini(w http.ResponseWriter, r *http.Request) {
19 if r.Method != "GET" {
20 w.WriteHeader(http.StatusMethodNotAllowed)
21 w.Write([]byte("404 Not found"))
22 return
23 }
24 var spath []string
25 if r.URL.Path == "/" {
26 http.Redirect(w, r, "gemini.circumlunar.space", http.StatusSeeOther)
27 return
28 } else {
29 spath = strings.SplitN(r.URL.Path, "/", 3)
30 }
31 req := gemini.Request{}
32 var err error
33 req.Host = spath[1]
34 if len(spath) > 2 {
35 req.URL, err = url.Parse(fmt.Sprintf("gemini://%s/%s", spath[1], spath[2]))
36 } else {
37 req.URL, err = url.Parse(fmt.Sprintf("gemini://%s/", spath[1]))
38 }
39 client := gemini.Client{
40 Timeout: 60 * time.Second,
41 }
42
43 if h := (url.URL{Host: req.Host}); h.Port() == "" {
44 req.Host += ":1965"
45 }
46
47 resp, err := client.Do(&req)
48 if err != nil {
49 w.WriteHeader(http.StatusBadGateway)
50 fmt.Fprintf(w, "Gateway error: %v", err)
51 return
52 }
53 defer resp.Body.Close()
54
55 switch resp.Status {
56 case 10, 11:
57 // TODO accept input
58 w.WriteHeader(http.StatusInternalServerError)
59 return
60 case 20:
61 break // OK
62 case 30, 31:
63 to, err := url.Parse(resp.Meta)
64 if err != nil {
65 w.WriteHeader(http.StatusBadGateway)
66 w.Write([]byte(fmt.Sprintf("Gateway error: bad redirect %v", err)))
67 }
68 next := req.URL.ResolveReference(to)
69 if next.Scheme != "gemini" {
70 w.WriteHeader(http.StatusOK)
71 w.Write([]byte(fmt.Sprintf("This page is redirecting you to %s", next.String())))
72 return
73 }
74 next.Host = r.URL.Host
75 next.Scheme = r.URL.Scheme
76 next.Path = fmt.Sprintf("/%s/%s", req.URL.Host, next)
77 w.Header().Add("Location", next.String())
78 w.WriteHeader(http.StatusFound)
79 w.Write([]byte("Redirecting to " + next.String()))
80 return
81 case 40, 41, 42, 43, 44:
82 w.WriteHeader(http.StatusServiceUnavailable)
83 fmt.Fprintf(w, "The remote server returned %d: %s", resp.Status, resp.Meta)
84 return
85 case 50, 51:
86 w.WriteHeader(http.StatusNotFound)
87 fmt.Fprintf(w, "The remote server returned %d: %s", resp.Status, resp.Meta)
88 return
89 case 52, 53, 59:
90 w.WriteHeader(http.StatusServiceUnavailable)
91 fmt.Fprintf(w, "The remote server returned %d: %s", resp.Status, resp.Meta)
92 return
93 default:
94 w.WriteHeader(http.StatusNotImplemented)
95 fmt.Fprintf(w, "Proxy does not understand Gemini response status %d", resp.Status)
96 return
97 }
98
99 m, _, err := mime.ParseMediaType(resp.Meta)
100 if err != nil {
101 w.WriteHeader(http.StatusBadGateway)
102 w.Write([]byte(fmt.Sprintf("Gateway error: %d %s: %v",
103 resp.Status, resp.Meta, err)))
104 return
105 }
106
107 if m != "text/gemini" {
108 w.Header().Add("Content-Type", resp.Meta)
109 io.Copy(w, resp.Body)
110 return
111 }
112
113 w.Header().Add("Content-Type", "text/html")
114 parse, _ := gemini.ParseText(resp.Body)
115 htmlString := textToHTML(req.URL, parse)
116 if strings.HasSuffix(r.URL.Path, "/") {
117 r.URL.Path = path.Dir(r.URL.Path)
118 }
119 data := struct {
120 SiteBody template.HTML
121 Favicon string
122 PageTitle string
123 URI *url.URL
124 Config Config
125 }{template.HTML(htmlString), "", r.URL.String(), req.URL, c}
126
127 err = t.ExecuteTemplate(w, "user_page.html", data)
128 if err != nil {
129 w.WriteHeader(http.StatusInternalServerError)
130 fmt.Fprintf(w, "%v", err)
131 return
132 }
133}