summaryrefslogtreecommitdiff
path: root/handlers.go
diff options
context:
space:
mode:
Diffstat (limited to 'handlers.go')
-rw-r--r--handlers.go201
1 files changed, 201 insertions, 0 deletions
diff --git a/handlers.go b/handlers.go
new file mode 100644
index 0000000..587af68
--- /dev/null
+++ b/handlers.go
@@ -0,0 +1,201 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "mime"
+ "net/url"
+ "strings"
+
+ "tildegit.org/tjp/sliderule"
+ "tildegit.org/tjp/sliderule/gemini"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gopher"
+ "tildegit.org/tjp/sliderule/gopher/gophermap"
+)
+
+func docType(u *url.URL, response *sliderule.Response) string {
+ _, gopherType := gopherURL(u)
+ switch gopherType {
+ case gopher.MenuType:
+ return "text/x-gophermap"
+ case gopher.TextFileType, gopher.ErrorType, gopher.InfoMessageType:
+ return "text/plain"
+ case gopher.MacBinHexType,
+ gopher.DosBinType,
+ gopher.BinaryFileType,
+ gopher.ImageFileType,
+ gopher.MovieFileType,
+ gopher.SoundFileType,
+ gopher.DocumentType:
+ return "application/octet-stream"
+ case gopher.UuencodedType:
+ return "text/x-uuencode"
+ case gopher.GifFileType:
+ return "image/gif"
+ case gopher.BitmapType:
+ return "image/bmp"
+ case gopher.PngImageFileType:
+ return "image/png"
+ case gopher.HTMLType:
+ return "text/html"
+ case gopher.RtfDocumentType:
+ return "application/rtf"
+ case gopher.WavSoundFileType:
+ return "audio/wav"
+ case gopher.PdfDocumentType:
+ return "application/pdf"
+ case gopher.XmlDocumentType:
+ return "application/xml"
+ }
+
+ if u.Scheme == "gemini" {
+ if response.Status == gemini.StatusSuccess {
+ mtype, _, err := mime.ParseMediaType(response.Meta.(string))
+ if err == nil {
+ return mtype
+ }
+ }
+ }
+
+ return "text/plain"
+}
+
+func parseDoc(doctype string, body []byte, conf *Config) (string, []Link, error) {
+ switch doctype {
+ case "text/x-gophermap":
+ return parseGophermapDoc(body, conf.SoftWrap)
+ case "text/gemini":
+ return parseGemtextDoc(body, conf.SoftWrap)
+ }
+
+ return string(body), nil, nil
+}
+
+func parseGophermapDoc(body []byte, softWrap int) (string, []Link, error) {
+ var b strings.Builder
+ var l []Link
+ mapdoc, err := gophermap.Parse(bytes.NewBuffer(body))
+ if err != nil {
+ return "", nil, err
+ }
+
+ i := 0
+ for _, item := range mapdoc {
+ switch item.Type {
+ case gopher.InfoMessageType:
+ for _, line := range fold(item.Display, softWrap) {
+ if _, err := b.WriteString(" " + line + "\n"); err != nil {
+ return "", nil, err
+ }
+ }
+ default:
+ l = append(l, Link{
+ Text: item.Display,
+ Target: fmtGopherURL(item.Type, item.Selector, item.Hostname, item.Port),
+ })
+ if _, err := b.WriteString(fmt.Sprintf("[%d]%s %s\n", i, linkSpaces(i), item.Display)); err != nil {
+ return "", nil, err
+ }
+ i += 1
+ }
+ }
+
+ return b.String(), l, nil
+}
+
+func parseGemtextDoc(body []byte, softWrap int) (string, []Link, error) {
+ var b strings.Builder
+ var l []Link
+ gemdoc, err := gemtext.Parse(bytes.NewBuffer(body))
+ if err != nil {
+ return "", nil, err
+ }
+
+ i := 0
+ for _, item := range gemdoc {
+ switch item.Type() {
+ case gemtext.LineTypeLink:
+ ll := item.(gemtext.LinkLine)
+ u, err := url.Parse(ll.URL())
+ if err != nil {
+ return "", nil, err
+ }
+ l = append(l, Link{
+ Text: ll.Label(),
+ Target: u,
+ })
+ if _, err := b.WriteString(fmt.Sprintf("[%d]%s %s\n", i, linkSpaces(i), ll.Label())); err != nil {
+ return "", nil, err
+ }
+ i += 1
+ default:
+ for _, line := range fold(item.String(), softWrap) {
+ if _, err := b.WriteString(" " + line + "\n"); err != nil {
+ return "", nil, err
+ }
+ }
+ }
+ }
+
+ return b.String(), l, nil
+}
+
+func fold(line string, width int) []string {
+ rs := []rune(strings.TrimSuffix(line, "\n"))
+ if len(rs) == 0 {
+ return []string{""}
+ }
+
+ var b []string
+outer:
+ for len(rs) > 0 {
+ if len(rs) <= width {
+ b = append(b, string(rs))
+ break
+ }
+
+ w := width
+ for rs[w] != ' ' && w > 0 {
+ w -= 1
+ }
+ if w == 0 {
+ for i := width + 1; i < len(rs); i += 1 {
+ if rs[i] == ' ' {
+ b = append(b, string(rs[:i]))
+ rs = rs[i+1:]
+ continue outer
+ }
+ }
+ b = append(b, string(rs))
+ break outer
+ }
+
+ b = append(b, string(rs[:w]))
+ rs = rs[w+1:]
+ }
+
+ return b
+}
+
+func fmtGopherURL(itemtype sliderule.Status, selector, hostname, port string) *url.URL {
+ if port != "70" {
+ hostname += ":" + port
+ }
+ return &url.URL{
+ Scheme: "gopher",
+ Host: hostname,
+ Path: "/" + string(byte(itemtype)) + selector,
+ }
+}
+
+func linkSpaces(i int) string {
+ switch {
+ case i < 10:
+ return " "
+ case i < 100:
+ return " "
+ default:
+ return ""
+ }
+}