Add new flags for robots.txt and URL rewrites
This commit is contained in:
parent
c36b3b6cb0
commit
32c87fbc39
2 changed files with 124 additions and 66 deletions
20
README.md
20
README.md
|
@ -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.
|
Geminispace, but it defaults to a specific domain.
|
||||||
|
|
||||||
[http]: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
|
[http]: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
|
||||||
[gemini]: https://gemini.circumlunar.space/
|
[gemini]: https://geminiprotocol.net/
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go build
|
$ 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
|
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
|
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.
|
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
|
relative link, for instance `-e /main.css` will serve `main.css` from the root
|
||||||
of the proxied Gemini capsule.
|
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
|
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.
|
for all external URLs, that is al URLs whose host does not match the proxied site.
|
||||||
|
|
||||||
## "kineto"?
|
## "kineto"?
|
||||||
|
|
170
main.go
170
main.go
|
@ -108,28 +108,45 @@ var gemtextPage = template.Must(template.
|
||||||
|
|
||||||
u = ctx.URL.ResolveReference(u)
|
u = ctx.URL.ResolveReference(u)
|
||||||
|
|
||||||
if u.Scheme == "" || u.Scheme == "gemini" {
|
if u.Scheme != "" && u.Scheme != "gemini" {
|
||||||
if u.Host != ctx.Root.Host {
|
return template.URL(u.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.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 = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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/%s/%s%s", p.Path, u.Scheme, u.Host, u.Path)
|
||||||
|
u.Scheme = p.Scheme
|
||||||
|
u.Host = p.Host
|
||||||
|
|
||||||
|
return template.URL(u.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = fmt.Sprintf("/x/%s%s", u.Host, u.Path)
|
||||||
|
u.Scheme = ""
|
||||||
|
u.Host = ""
|
||||||
|
|
||||||
return template.URL(u.String())
|
return template.URL(u.String())
|
||||||
},
|
},
|
||||||
"safeCSS": func(s string) template.CSS {
|
"safeCSS": func(s string) template.CSS {
|
||||||
|
@ -383,19 +400,27 @@ input:focus {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const defaultRobots = `
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
`
|
||||||
|
|
||||||
type GemtextContext struct {
|
type GemtextContext struct {
|
||||||
CSS string
|
CSS string
|
||||||
ExternalCSS bool
|
ExternalCSS bool
|
||||||
External bool
|
External bool
|
||||||
Lines []gemini.Line
|
Lines []gemini.Line
|
||||||
Pre int
|
Pre int
|
||||||
Resp *gemini.Response
|
Resp *gemini.Response
|
||||||
Title string
|
Title string
|
||||||
Lang string
|
Lang string
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
Root *url.URL
|
Root *url.URL
|
||||||
UsePortal bool
|
UsePortal bool
|
||||||
Portal string
|
Portal string
|
||||||
|
DisableExternalProxy bool
|
||||||
|
UseRewrite bool
|
||||||
|
Rewrite string
|
||||||
}
|
}
|
||||||
|
|
||||||
type InputContext struct {
|
type InputContext struct {
|
||||||
|
@ -427,8 +452,9 @@ func createAnchor(heading string) string {
|
||||||
return strings.ToLower(anchor.String())
|
return strings.ToLower(anchor.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func proxyGemini(req gemini.Request, external bool, root *url.URL,
|
func proxyGemini(req gemini.Request, external bool, root *url.URL, w http.ResponseWriter,
|
||||||
w http.ResponseWriter, r *http.Request, css string, externalCSS bool, usePortal bool, portal string) {
|
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)
|
ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -524,16 +550,19 @@ func proxyGemini(req gemini.Request, external bool, root *url.URL,
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "text/html")
|
w.Header().Add("Content-Type", "text/html")
|
||||||
gemctx := &GemtextContext{
|
gemctx := &GemtextContext{
|
||||||
CSS: css,
|
CSS: css,
|
||||||
ExternalCSS: externalCSS,
|
ExternalCSS: externalCSS,
|
||||||
External: external,
|
External: external,
|
||||||
Resp: resp,
|
Resp: resp,
|
||||||
Title: req.URL.Host + " " + req.URL.Path,
|
Title: req.URL.Host + " " + req.URL.Path,
|
||||||
Lang: lang,
|
Lang: lang,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
Root: root,
|
Root: root,
|
||||||
UsePortal: usePortal,
|
UsePortal: usePortal,
|
||||||
Portal: portal,
|
Portal: portal,
|
||||||
|
DisableExternalProxy: disableExternalProxy,
|
||||||
|
UseRewrite: useRewrite,
|
||||||
|
Rewrite: rewrite,
|
||||||
}
|
}
|
||||||
|
|
||||||
var title bool
|
var title bool
|
||||||
|
@ -557,21 +586,40 @@ func proxyGemini(req gemini.Request, external bool, root *url.URL,
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
bind string = ":8080"
|
bind string = ":8080"
|
||||||
css string = defaultCSS
|
css string = defaultCSS
|
||||||
external bool = false
|
external bool = false
|
||||||
usePortal bool = false
|
usePortal bool = false
|
||||||
portal string = ""
|
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 {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
switch opt.Option {
|
switch opt.Option {
|
||||||
|
case 'P':
|
||||||
|
disableExternalProxy = true
|
||||||
|
case 'R':
|
||||||
|
disableRobots = true
|
||||||
case 'b':
|
case 'b':
|
||||||
bind = opt.Value
|
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':
|
case 's':
|
||||||
external = false
|
external = false
|
||||||
cssContent, err := os.ReadFile(opt.Value)
|
cssContent, err := os.ReadFile(opt.Value)
|
||||||
|
@ -580,12 +628,6 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("Error opening custom CSS from '%s': %v", opt.Value, err)
|
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,10 +667,12 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path == "/robots.txt" {
|
if !disableRobots {
|
||||||
w.WriteHeader(http.StatusOK)
|
if r.URL.Path == "/robots.txt" {
|
||||||
w.Write([]byte("User-agent: *\nDisallow: /\n"))
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
w.Write([]byte(defaultRobots))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req := gemini.Request{}
|
req := gemini.Request{}
|
||||||
|
@ -637,7 +681,7 @@ func main() {
|
||||||
req.URL.Host = root.Host
|
req.URL.Host = root.Host
|
||||||
req.URL.Path = r.URL.Path
|
req.URL.Path = r.URL.Path
|
||||||
req.URL.RawQuery = r.URL.RawQuery
|
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) {
|
http.Handle("/x/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -646,7 +690,7 @@ func main() {
|
||||||
path = append(path, "")
|
path = append(path, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
if usePortal && path[2] != root.Host {
|
if path[2] != root.Host && (usePortal || disableExternalProxy) {
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
w.Write([]byte("404 Not found"))
|
w.Write([]byte("404 Not found"))
|
||||||
return
|
return
|
||||||
|
@ -678,7 +722,7 @@ func main() {
|
||||||
req.URL.Path = "/" + path[3]
|
req.URL.Path = "/" + path[3]
|
||||||
req.URL.RawQuery = r.URL.RawQuery
|
req.URL.RawQuery = r.URL.RawQuery
|
||||||
log.Printf("%s (external) %s%s", r.Method, r.URL.Host, r.URL.Path)
|
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)
|
log.Printf("HTTP server listening on %s", bind)
|
||||||
|
|
Loading…
Reference in a new issue