all repos — flounder @ 3d35721726896a728cca246d4fea18e300524ba2

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 {
 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		InsecureSkipTrust: true,
 42	}
 43
 44	if h := (url.URL{Host: req.Host}); h.Port() == "" {
 45		req.Host += ":1965"
 46	}
 47
 48	resp, err := client.Do(&req)
 49	if err != nil {
 50		w.WriteHeader(http.StatusBadGateway)
 51		fmt.Fprintf(w, "Gateway error: %v", err)
 52		return
 53	}
 54	defer resp.Body.Close()
 55
 56	switch resp.Status {
 57	case 10, 11:
 58		// TODO accept input
 59		w.WriteHeader(http.StatusInternalServerError)
 60		return
 61	case 20:
 62		break // OK
 63	case 30, 31:
 64		to, err := url.Parse(resp.Meta)
 65		if err != nil {
 66			w.WriteHeader(http.StatusBadGateway)
 67			w.Write([]byte(fmt.Sprintf("Gateway error: bad redirect %v", err)))
 68		}
 69		next := req.URL.ResolveReference(to)
 70		if next.Scheme != "gemini" {
 71			w.WriteHeader(http.StatusOK)
 72			w.Write([]byte(fmt.Sprintf("This page is redirecting you to %s", next.String())))
 73			return
 74		}
 75		next.Host = r.URL.Host
 76		next.Scheme = r.URL.Scheme
 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	htmlString := textToHTML(req.URL, gemini.ParseText(resp.Body))
115	if strings.HasSuffix(r.URL.Path, "/") {
116		r.URL.Path = path.Dir(r.URL.Path)
117	}
118	data := struct {
119		SiteBody  template.HTML
120		Favicon   string
121		PageTitle string
122		URI       *url.URL
123	}{template.HTML(htmlString), "", r.URL.String(), r.URL}
124
125	err = t.ExecuteTemplate(w, "user_page.html", data)
126	if err != nil {
127		w.WriteHeader(http.StatusInternalServerError)
128		fmt.Fprintf(w, "%v", err)
129		return
130	}
131}