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 if r.URL.Path == "/robots.txt" {
29 temp := c.TemplatesDirectory
30 http.ServeFile(w, r, path.Join(temp, "proxy-robots.txt"))
31 return
32 } else {
33 spath = strings.SplitN(r.URL.Path, "/", 3)
34 }
35 req := gemini.Request{}
36 var err error
37 req.Host = spath[1]
38 if len(spath) > 2 {
39 req.URL, err = url.Parse(fmt.Sprintf("gemini://%s/%s", spath[1], spath[2]))
40 } else {
41 req.URL, err = url.Parse(fmt.Sprintf("gemini://%s/", spath[1]))
42 }
43 req.URL.RawQuery = r.URL.RawQuery
44 client := gemini.Client{
45 Timeout: 60 * time.Second,
46 }
47
48 if h := (url.URL{Host: req.Host}); h.Port() == "" {
49 req.Host += ":1965"
50 }
51
52 resp, err := client.Do(&req)
53 if err != nil {
54 w.WriteHeader(http.StatusBadGateway)
55 fmt.Fprintf(w, "Gateway error: %v", err)
56 return
57 }
58 defer resp.Body.Close()
59
60 switch resp.Status {
61 case 10, 11:
62 // TODO accept input
63 w.WriteHeader(http.StatusInternalServerError)
64 return
65 case 20:
66 break // OK
67 case 30, 31:
68 to, err := url.Parse(resp.Meta)
69 if err != nil {
70 w.WriteHeader(http.StatusBadGateway)
71 w.Write([]byte(fmt.Sprintf("Gateway error: bad redirect %v", err)))
72 }
73 next := req.URL.ResolveReference(to)
74 if next.Scheme != "gemini" {
75 w.WriteHeader(http.StatusOK)
76 w.Write([]byte(fmt.Sprintf("This page is redirecting you to %s", next.String())))
77 return
78 }
79 next.Host = r.URL.Host
80 next.Scheme = r.URL.Scheme
81 next.Path = fmt.Sprintf("/%s/%s", req.URL.Host, next)
82 w.Header().Add("Location", next.String())
83 w.WriteHeader(http.StatusFound)
84 w.Write([]byte("Redirecting to " + next.String()))
85 return
86 case 40, 41, 42, 43, 44:
87 w.WriteHeader(http.StatusServiceUnavailable)
88 fmt.Fprintf(w, "The remote server returned %d: %s", resp.Status, resp.Meta)
89 return
90 case 50, 51:
91 w.WriteHeader(http.StatusNotFound)
92 fmt.Fprintf(w, "The remote server returned %d: %s", resp.Status, resp.Meta)
93 return
94 case 52, 53, 59:
95 w.WriteHeader(http.StatusServiceUnavailable)
96 fmt.Fprintf(w, "The remote server returned %d: %s", resp.Status, resp.Meta)
97 return
98 default:
99 w.WriteHeader(http.StatusNotImplemented)
100 fmt.Fprintf(w, "Proxy does not understand Gemini response status %d", resp.Status)
101 return
102 }
103
104 m, _, err := mime.ParseMediaType(resp.Meta)
105 if err != nil {
106 w.WriteHeader(http.StatusBadGateway)
107 w.Write([]byte(fmt.Sprintf("Gateway error: %d %s: %v",
108 resp.Status, resp.Meta, err)))
109 return
110 }
111
112 _, raw := r.URL.Query()["raw"]
113 acceptsGemini := strings.Contains(r.Header.Get("Accept"), "text/gemini")
114 if m != "text/gemini" || raw || acceptsGemini {
115 w.Header().Add("Content-Type", resp.Meta)
116 io.Copy(w, resp.Body)
117 return
118 }
119
120 w.Header().Add("Content-Type", "text/html")
121 parse, _ := gemini.ParseText(resp.Body)
122 htmlDoc := textToHTML(req.URL, parse)
123 if strings.HasSuffix(r.URL.Path, "/") {
124 r.URL.Path = path.Dir(r.URL.Path)
125 }
126 data := struct {
127 SiteBody template.HTML
128 Favicon string
129 PageTitle string
130 GeminiURI *url.URL
131 URI *url.URL
132 Config Config
133 }{template.HTML(htmlDoc.Content), "", htmlDoc.Title, req.URL, r.URL, c}
134
135 err = t.ExecuteTemplate(w, "user_page.html", data)
136 if err != nil {
137 w.WriteHeader(http.StatusInternalServerError)
138 fmt.Fprintf(w, "%v", err)
139 return
140 }
141}