1/*
2 * Copyright © 2020 A Bunch Tell LLC.
3 *
4 * This file is part of WriteFreely.
5 *
6 * WriteFreely is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License, included
8 * in the LICENSE file in this source code package.
9 */
10
11package writefreely
12
13import (
14	"bytes"
15	"fmt"
16	"io"
17	"net/url"
18	"regexp"
19	"strings"
20
21	"git.mills.io/prologic/go-gopher"
22	"github.com/writeas/web-core/log"
23)
24
25func initGopher(apper Apper) {
26	handler := NewWFHandler(apper)
27
28	gopher.HandleFunc("/", handler.Gopher(handleGopher))
29	log.Info("Serving on gopher://localhost:%d", apper.App().Config().Server.GopherPort)
30	gopher.ListenAndServe(fmt.Sprintf(":%d", apper.App().Config().Server.GopherPort), nil)
31}
32
33// Utility function to strip the URL from the hostname provided by app.cfg.App.Host
34func stripHostProtocol(app *App) string {
35	u, err := url.Parse(app.cfg.App.Host)
36	if err != nil {
37		// Fall back to host, with scheme stripped
38		return string(regexp.MustCompile("^.*://").ReplaceAll([]byte(app.cfg.App.Host), []byte("")))
39	}
40	return u.Hostname()
41}
42
43func handleGopher(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
44	parts := strings.Split(r.Selector, "/")
45	if app.cfg.App.SingleUser {
46		if parts[1] != "" {
47			return handleGopherCollectionPost(app, w, r)
48		}
49		return handleGopherCollection(app, w, r)
50	}
51
52	// Show all public collections (a gopher Reader view, essentially)
53	if len(parts) == 3 {
54		return handleGopherCollection(app, w, r)
55	}
56
57	w.WriteInfo(fmt.Sprintf("Welcome to %s", app.cfg.App.SiteName))
58
59	colls, err := app.db.GetPublicCollections(app.cfg.App.Host)
60	if err != nil {
61		return err
62	}
63
64	for _, c := range *colls {
65		w.WriteItem(&gopher.Item{
66			Host:        stripHostProtocol(app),
67			Port:        app.cfg.Server.GopherPort,
68			Type:        gopher.DIRECTORY,
69			Description: c.DisplayTitle(),
70			Selector:    "/" + c.Alias + "/",
71		})
72	}
73	return w.End()
74}
75
76func handleGopherCollection(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
77	var collAlias, slug string
78	var c *Collection
79	var err error
80	var baseSel = "/"
81
82	parts := strings.Split(r.Selector, "/")
83	if app.cfg.App.SingleUser {
84		// sanity check
85		slug = parts[1]
86		if slug != "" {
87			return handleGopherCollectionPost(app, w, r)
88		}
89
90		c, err = app.db.GetCollectionByID(1)
91		if err != nil {
92			return err
93		}
94	} else {
95		collAlias = parts[1]
96		slug = parts[2]
97		if slug != "" {
98			return handleGopherCollectionPost(app, w, r)
99		}
100
101		c, err = app.db.GetCollection(collAlias)
102		if err != nil {
103			return err
104		}
105		baseSel = "/" + c.Alias + "/"
106	}
107	c.hostName = app.cfg.App.Host
108
109	w.WriteInfo(c.DisplayTitle())
110	if c.Description != "" {
111		w.WriteInfo(c.Description)
112	}
113
114	posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false)
115	if err != nil {
116		return err
117	}
118
119	for _, p := range *posts {
120		w.WriteItem(&gopher.Item{
121			Port:        app.cfg.Server.GopherPort,
122			Host:        stripHostProtocol(app),
123			Type:        gopher.FILE,
124			Description: p.CreatedDate() + " - " + p.DisplayTitle(),
125			Selector:    baseSel + p.Slug.String,
126		})
127	}
128	return w.End()
129}
130
131func handleGopherCollectionPost(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
132	var collAlias, slug string
133	var c *Collection
134	var err error
135
136	parts := strings.Split(r.Selector, "/")
137	if app.cfg.App.SingleUser {
138		slug = parts[1]
139		c, err = app.db.GetCollectionByID(1)
140		if err != nil {
141			return err
142		}
143	} else {
144		collAlias = parts[1]
145		slug = parts[2]
146		c, err = app.db.GetCollection(collAlias)
147		if err != nil {
148			return err
149		}
150	}
151	c.hostName = app.cfg.App.Host
152
153	p, err := app.db.GetPost(slug, c.ID)
154	if err != nil {
155		return err
156	}
157
158	b := bytes.Buffer{}
159	if p.Title.String != "" {
160		b.WriteString(p.Title.String + "\n")
161	}
162	b.WriteString(p.DisplayDate + "\n\n")
163	b.WriteString(p.Content)
164	io.Copy(w, &b)
165
166	return w.End()
167}
168