summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Picciano <mediocregopher@gmail.com>2023-01-23 16:02:35 +0100
committerBrian Picciano <mediocregopher@gmail.com>2023-01-23 16:02:35 +0100
commitc4520f2c84c0d4555bdb02f4ec7b2d1a8bdefca2 (patch)
tree04c343906811c87283d88ae131ac6441265db73f
parentaba69d432959f3c1edc78b1ec2e0891120af5ae2 (diff)
Automatically bridge gemini links to a gateway on http site
-rw-r--r--flake.nix1
-rw-r--r--src/cmd/load-test-data/test-data.yml8
-rw-r--r--src/gmi/gemtext.go27
-rw-r--r--src/gmi/gemtext_test.go17
-rw-r--r--src/http/http.go11
-rw-r--r--src/http/posts.go6
6 files changed, 60 insertions, 10 deletions
diff --git a/flake.nix b/flake.nix
index 801ebdb..3a1eb3e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -43,6 +43,7 @@
export MEDIOCRE_BLOG_HTTP_PUBLIC_URL="$MEDIOCRE_BLOG_ML_PUBLIC_URL"
export MEDIOCRE_BLOG_HTTP_LISTEN_PROTO="tcp"
export MEDIOCRE_BLOG_HTTP_LISTEN_ADDR=":4000"
+ export MEDIOCRE_BLOG_HTTP_GEMINI_GATEWAY_URL="https://nightfall.city/x/"
# http auth
# (password is "bar". This should definitely be changed for prod.)
diff --git a/src/cmd/load-test-data/test-data.yml b/src/cmd/load-test-data/test-data.yml
index a3fff9d..36e009f 100644
--- a/src/cmd/load-test-data/test-data.yml
+++ b/src/cmd/load-test-data/test-data.yml
@@ -104,6 +104,12 @@ published_posts:
Edgy.
+ => / Here's a link within the site
+
+ => gemini://mediocregopher.com And here's a link to a gemini capsule
+
+ => https://mediocregopher.com And here's a link to an https site
+
#### Side-note
Did you know that the terms "cyberspace" and "matrix" are attributable to a book from 1984 called _Neuromancer_?
@@ -117,8 +123,6 @@ published_posts:
This has been a great post.
- => / Here's a link outa here!
-
- id: empty-markdown-test
title: Empty Markdown Test
description:
diff --git a/src/gmi/gemtext.go b/src/gmi/gemtext.go
index a1136bd..3e5f6fc 100644
--- a/src/gmi/gemtext.go
+++ b/src/gmi/gemtext.go
@@ -5,8 +5,9 @@ import (
"errors"
"fmt"
"io"
- "log"
+ "net/url"
"path"
+ "path/filepath"
"regexp"
"strings"
)
@@ -25,7 +26,10 @@ var linkRegexp = regexp.MustCompile(`^=>\s+(\S+)\s*(.*?)\s*$`)
// GemtextToMarkdown reads a gemtext formatted body from the Reader and writes
// the markdown version of that body to the Writer.
-func GemtextToMarkdown(dst io.Writer, src io.Reader) error {
+//
+// gmiGateway, if given, is used for all `gemini://` links. The `gemini://`
+// prefix will be stripped, and replaced with the given URL.
+func GemtextToMarkdown(dst io.Writer, src io.Reader, gmiGateway *url.URL) error {
bufSrc := bufio.NewReader(src)
@@ -40,7 +44,20 @@ func GemtextToMarkdown(dst io.Writer, src io.Reader) error {
if match := linkRegexp.FindStringSubmatch(line); len(match) > 0 {
- isImg := hasImgExt(match[1])
+ u, err := url.Parse(match[1])
+ if err != nil {
+ return fmt.Errorf("link to invalid url %q: %w", match[1], err)
+ }
+
+ if u.Scheme == "gemini" && gmiGateway != nil {
+
+ newU := *gmiGateway
+ newU.Path = filepath.Join(newU.Path, u.Host, u.Path)
+ newU.RawQuery = u.RawQuery
+ u = &newU
+ }
+
+ isImg := hasImgExt(u.Path)
descr := match[2]
@@ -52,9 +69,7 @@ func GemtextToMarkdown(dst io.Writer, src io.Reader) error {
descr = "Link"
}
- log.Printf("descr:%q", descr)
-
- line = fmt.Sprintf("[%s](%s)\n", descr, match[1])
+ line = fmt.Sprintf("[%s](%s)\n", descr, u.String())
if isImg {
line = "!" + line
diff --git a/src/gmi/gemtext_test.go b/src/gmi/gemtext_test.go
index 23cb97f..75da9df 100644
--- a/src/gmi/gemtext_test.go
+++ b/src/gmi/gemtext_test.go
@@ -2,6 +2,7 @@ package gmi
import (
"bytes"
+ "net/url"
"strconv"
"testing"
@@ -10,6 +11,8 @@ import (
func TestGemtextToMarkdown(t *testing.T) {
+ gmiGateway, _ := url.Parse("https://gateway.com/x/")
+
tests := []struct {
in, exp string
}{
@@ -37,13 +40,25 @@ func TestGemtextToMarkdown(t *testing.T) {
in: "=> img.png description is here ",
exp: "![description is here](img.png)\n",
},
+ {
+ in: "=> gemini://somewhere.com/foo Somewhere",
+ exp: "[Somewhere](https://gateway.com/x/somewhere.com/foo)\n",
+ },
+ {
+ in: "=> gemini://somewhere.com:420/foo Somewhere",
+ exp: "[Somewhere](https://gateway.com/x/somewhere.com:420/foo)\n",
+ },
+ {
+ in: "=> gemini://somewhere.com:420/foo?bar=baz Somewhere",
+ exp: "[Somewhere](https://gateway.com/x/somewhere.com:420/foo?bar=baz)\n",
+ },
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
got := new(bytes.Buffer)
- err := GemtextToMarkdown(got, bytes.NewBufferString(test.in))
+ err := GemtextToMarkdown(got, bytes.NewBufferString(test.in), gmiGateway)
assert.NoError(t, err)
assert.Equal(t, test.exp, got.String())
})
diff --git a/src/http/http.go b/src/http/http.go
index da39374..98cdde3 100644
--- a/src/http/http.go
+++ b/src/http/http.go
@@ -56,12 +56,17 @@ type Params struct {
// AuthRatelimit indicates how much time must pass between subsequent auth
// attempts.
AuthRatelimit time.Duration
+
+ // GeminiGatewayURL will be used to translate links for `gemini://` into
+ // `http(s)://`. See gmi.GemtextToMarkdown.
+ GeminiGatewayURL *url.URL
}
// SetupCfg implement the cfg.Cfger interface.
func (p *Params) SetupCfg(cfg *cfg.Cfg) {
publicURLStr := cfg.String("http-public-url", "http://localhost:4000", "URL this service is accessible at")
+ geminiGatewayURLStr := cfg.String("http-gemini-gateway-url", "", "Optional URL to prefix to all gemini:// links, to make them accessible over https")
cfg.StringVar(&p.ListenProto, "http-listen-proto", "tcp", "Protocol to listen for HTTP requests with")
cfg.StringVar(&p.ListenAddr, "http-listen-addr", ":4000", "Address/unix socket path to listen for HTTP requests on")
@@ -87,6 +92,12 @@ func (p *Params) SetupCfg(cfg *cfg.Cfg) {
return fmt.Errorf("parsing -http-public-url: %w", err)
}
+ if *geminiGatewayURLStr != "" {
+ if p.GeminiGatewayURL, err = url.Parse(*geminiGatewayURLStr); err != nil {
+ return fmt.Errorf("parsing -http-gemini-gateway-url: %w", err)
+ }
+ }
+
return nil
})
}
diff --git a/src/http/posts.go b/src/http/posts.go
index cae8f43..bb1c899 100644
--- a/src/http/posts.go
+++ b/src/http/posts.go
@@ -96,7 +96,11 @@ func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload,
prevBodyBuf := bodyBuf
bodyBuf = new(bytes.Buffer)
- if err := gmi.GemtextToMarkdown(bodyBuf, prevBodyBuf); err != nil {
+ err := gmi.GemtextToMarkdown(
+ bodyBuf, prevBodyBuf, a.params.GeminiGatewayURL,
+ )
+
+ if err != nil {
return postTplPayload{}, fmt.Errorf("converting gemtext to markdown: %w", err)
}
}