all repos — flounder @ 6a448532723ac8abf673b4964260d8f4a4ed2fb2

A small site builder for the Gemini protocol

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		PageTitle string
129		GeminiURI *url.URL
130		URI       *url.URL
131		Config    Config
132	}{template.HTML(htmlDoc.Content), htmlDoc.Title, req.URL, r.URL, c}
133
134	err = t.ExecuteTemplate(w, "user_page.html", data)
135	if err != nil {
136		w.WriteHeader(http.StatusInternalServerError)
137		fmt.Fprintf(w, "%v", err)
138		return
139	}
140}