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