Add new flags for robots.txt and URL rewrites

This commit is contained in:
Erick Ruiz de Chavez 2025-01-10 07:24:45 -05:00
parent c36b3b6cb0
commit 32c87fbc39
2 changed files with 124 additions and 66 deletions

View file

@ -6,15 +6,24 @@ can proxy to any domain in order to facilitate linking to the rest of
Geminispace, but it defaults to a specific domain.
[http]: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
[gemini]: https://gemini.circumlunar.space/
[gemini]: https://geminiprotocol.net/
## Usage
```
$ go build
$ ./kineto [-b 127.0.0.1:8080] [-s style.css] [-e style.css] [-p https://portal.mozz.us] gemini://example.org
$ ./kineto [-P] [-R] [-b 127.0.0.1:8080] [-r /learn-more-about-gemini] [-s style.css] [-e style.css] [-p https://portal.mozz.us] gemini://example.org
```
The -P argument is optional and allows you to completely disable proxying
and URL rewriting external gemini URLs. The user visiting your site will need
to have a browser or browser extension that understands how to handle
gemini:// links.
The -R argument is optional and allows you to completely disables serving a
custom robots.txt response. When enabled the request will be proxied to your
gemini site.
The -b argument is optional and allows you to bind to an arbitrary address; by
default kineto will bind to `:8080`. You should set up some external reverse
proxy like nginx to forward traffic to this port and add TLS.
@ -30,8 +39,13 @@ 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 -r argument is optional and allows you rewrite external gemini links to a
custom URL. Use this if you have external gemini links but instead of proxying
them you prefer to invite your visitors to learn more about the Gemini protocol
and download a Gemini browser. This can be a relative or absolute URL.
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/`
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"?

90
main.go
View file

@ -108,27 +108,44 @@ var gemtextPage = template.Must(template.
u = ctx.URL.ResolveReference(u)
if u.Scheme == "" || u.Scheme == "gemini" {
if u.Host != ctx.Root.Host {
if u.Scheme != "" && u.Scheme != "gemini" {
return template.URL(u.String())
}
if u.Host == ctx.Root.Host {
u.Scheme = ""
u.Host = ""
return template.URL(u.String())
}
if ctx.DisableExternalProxy {
return template.URL(u.String())
}
if ctx.UseRewrite {
r, err := url.Parse(ctx.Rewrite)
if err != nil {
return template.URL("error")
}
return template.URL(r.String())
}
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.Path = fmt.Sprintf("%s/%s/%s%s", p.Path, u.Scheme, u.Host, u.Path)
u.Scheme = p.Scheme
u.Host = p.Host
} else {
return template.URL(u.String())
}
u.Path = fmt.Sprintf("/x/%s%s", u.Host, u.Path)
u.Scheme = ""
u.Host = ""
}
} else {
u.Scheme = ""
u.Host = ""
}
}
return template.URL(u.String())
},
@ -383,6 +400,11 @@ input:focus {
}
`
const defaultRobots = `
User-agent: *
Disallow: /
`
type GemtextContext struct {
CSS string
ExternalCSS bool
@ -396,6 +418,9 @@ type GemtextContext struct {
Root *url.URL
UsePortal bool
Portal string
DisableExternalProxy bool
UseRewrite bool
Rewrite string
}
type InputContext struct {
@ -427,8 +452,9 @@ func createAnchor(heading string) string {
return strings.ToLower(anchor.String())
}
func proxyGemini(req gemini.Request, external bool, root *url.URL,
w http.ResponseWriter, r *http.Request, css string, externalCSS bool, usePortal bool, portal string) {
func proxyGemini(req gemini.Request, external bool, root *url.URL, w http.ResponseWriter,
r *http.Request, css string, externalCSS bool, usePortal bool, portal string, disableExternalProxy bool,
useRewrite bool, rewrite string) {
ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
defer cancel()
@ -534,6 +560,9 @@ func proxyGemini(req gemini.Request, external bool, root *url.URL,
Root: root,
UsePortal: usePortal,
Portal: portal,
DisableExternalProxy: disableExternalProxy,
UseRewrite: useRewrite,
Rewrite: rewrite,
}
var title bool
@ -562,16 +591,35 @@ func main() {
external bool = false
usePortal bool = false
portal string = ""
useRewrite bool = false
rewrite string = ""
disableExternalProxy bool = false
disableRobots bool = false
)
opts, optind, err := getopt.Getopts(os.Args, "b:c:s:e:p:")
opts, optind, err := getopt.Getopts(os.Args, "PRb:c:e:p:r:s:")
if err != nil {
log.Fatal(err)
}
for _, opt := range opts {
switch opt.Option {
case 'P':
disableExternalProxy = true
case 'R':
disableRobots = true
case 'b':
bind = opt.Value
case 'e':
external = true
css = opt.Value
case 'p':
usePortal = true
portal = opt.Value
case 'r':
useRewrite = true
rewrite = opt.Value
case 's':
external = false
cssContent, err := os.ReadFile(opt.Value)
@ -580,12 +628,6 @@ func main() {
} else {
log.Fatalf("Error opening custom CSS from '%s': %v", opt.Value, err)
}
case 'e':
external = true
css = opt.Value
case 'p':
usePortal = true
portal = opt.Value
}
}
@ -625,11 +667,13 @@ func main() {
return
}
if !disableRobots {
if r.URL.Path == "/robots.txt" {
w.WriteHeader(http.StatusOK)
w.Write([]byte("User-agent: *\nDisallow: /\n"))
w.Write([]byte(defaultRobots))
return
}
}
req := gemini.Request{}
req.URL = &url.URL{}
@ -637,7 +681,7 @@ 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, usePortal, portal)
proxyGemini(req, false, root, w, r, css, external, usePortal, portal, disableExternalProxy, useRewrite, rewrite)
}))
http.Handle("/x/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -646,7 +690,7 @@ func main() {
path = append(path, "")
}
if usePortal && path[2] != root.Host {
if path[2] != root.Host && (usePortal || disableExternalProxy) {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("404 Not found"))
return
@ -678,7 +722,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, usePortal, portal)
proxyGemini(req, true, root, w, r, css, external, usePortal, portal, disableExternalProxy, useRewrite, rewrite)
}))
log.Printf("HTTP server listening on %s", bind)