Add portal flag

Proxing all small web through /x/ might not be desirable. In this commit I am adding a new argument, -p, that allows proxing all external requests using a third party proxy, like Portal (https://portal.mozz.us).

By proding a value for this parameter any request made to `/x/` whose host does not match the root host will be rejected.
This commit is contained in:
Erick Ruiz de Chavez 2025-01-08 08:29:17 -05:00
parent 857f8c97eb
commit e4781e7f37
3 changed files with 53 additions and 15 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
kineto

View file

@ -12,7 +12,7 @@ Geminispace, but it defaults to a specific domain.
```
$ go build
$ ./kineto [-b 127.0.0.1:8080] [-s style.css] [-e style.css] gemini://example.org
$ ./kineto [-b 127.0.0.1:8080] [-s style.css] [-e style.css] [-p https://portal.mozz.us] gemini://example.org
```
The -b argument is optional and allows you to bind to an arbitrary address; by
@ -30,6 +30,10 @@ in a `<style>` block like with the -s flag. The given stylesheet can be a
relative link, for instance `-e /main.css` will serve `main.css` from the root
of the proxied Gemini capsule.
The -p argument is optional and allows you to specify a custom Portal to proxy
external gemini requests. This option will effectively disable the use of `/x/`
for all external URLs, that is al URLs whose host does not match the proxied site.
## "kineto"?
It's named after the Contraves-Goerz Kineto Tracking Mount, which is used by

61
main.go
View file

@ -102,18 +102,35 @@ var gemtextPage = template.Must(template.
},
"url": func(ctx *GemtextContext, s string) template.URL {
u, err := url.Parse(s)
if err != nil {
return template.URL("error")
}
u = ctx.URL.ResolveReference(u)
if u.Scheme == "" || u.Scheme == "gemini" {
if u.Host != ctx.Root.Host {
u.Path = fmt.Sprintf("/x/%s%s", u.Host, u.Path)
if ctx.UsePortal {
p, err := url.Parse(ctx.Portal)
if err != nil {
return template.URL("error")
}
p = ctx.URL.ResolveReference(p)
u.Path = fmt.Sprintf("%s/gemini/%s%s", p.Path, u.Host, u.Path)
u.Scheme = p.Scheme
u.Host = p.Host
} else {
u.Path = fmt.Sprintf("/x/%s%s", u.Host, u.Path)
u.Scheme = ""
u.Host = ""
}
} else {
u.Scheme = ""
u.Host = ""
}
u.Scheme = ""
u.Host = ""
}
return template.URL(u.String())
},
"safeCSS": func(s string) template.CSS {
@ -378,6 +395,8 @@ type GemtextContext struct {
Lang string
URL *url.URL
Root *url.URL
UsePortal bool
Portal string
}
type InputContext struct {
@ -410,7 +429,7 @@ func createAnchor(heading string) string {
}
func proxyGemini(req gemini.Request, external bool, root *url.URL,
w http.ResponseWriter, r *http.Request, css string, externalCSS bool) {
w http.ResponseWriter, r *http.Request, css string, externalCSS bool, usePortal bool, portal string) {
ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
defer cancel()
@ -514,6 +533,8 @@ func proxyGemini(req gemini.Request, external bool, root *url.URL,
Lang: lang,
URL: req.URL,
Root: root,
UsePortal: usePortal,
Portal: portal,
}
var title bool
@ -537,12 +558,14 @@ func proxyGemini(req gemini.Request, external bool, root *url.URL,
func main() {
var (
bind string = ":8080"
css string = defaultCSS
external bool = false
bind string = ":8080"
css string = defaultCSS
external bool = false
usePortal bool = false
portal string = ""
)
opts, optind, err := getopt.Getopts(os.Args, "b:c:s:e:")
opts, optind, err := getopt.Getopts(os.Args, "b:c:s:e:p:")
if err != nil {
log.Fatal(err)
}
@ -561,6 +584,9 @@ func main() {
case 'e':
external = true
css = opt.Value
case 'p':
usePortal = true
portal = opt.Value
}
}
@ -612,10 +638,21 @@ func main() {
req.URL.Host = root.Host
req.URL.Path = r.URL.Path
req.URL.RawQuery = r.URL.RawQuery
proxyGemini(req, false, root, w, r, css, external)
proxyGemini(req, false, root, w, r, css, external, usePortal, portal)
}))
http.Handle("/x/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := strings.SplitN(r.URL.Path, "/", 4)
if len(path) != 4 {
path = append(path, "")
}
if usePortal && path[2] != root.Host {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("404 Not found"))
return
}
if r.Method == "POST" {
r.ParseForm()
if q, ok := r.Form["q"]; !ok {
@ -635,10 +672,6 @@ func main() {
return
}
path := strings.SplitN(r.URL.Path, "/", 4)
if len(path) != 4 {
path = append(path, "")
}
req := gemini.Request{}
req.URL = &url.URL{}
req.URL.Scheme = "gemini"
@ -646,7 +679,7 @@ func main() {
req.URL.Path = "/" + path[3]
req.URL.RawQuery = r.URL.RawQuery
log.Printf("%s (external) %s%s", r.Method, r.URL.Host, r.URL.Path)
proxyGemini(req, true, root, w, r, css, external)
proxyGemini(req, true, root, w, r, css, external, usePortal, portal)
}))
log.Printf("HTTP server listening on %s", bind)