cowyo/server/handlers.go

888 lines
23 KiB
Go
Raw Normal View History

package server
import (
"crypto/sha256"
2017-10-15 16:49:40 +03:00
"fmt"
"html/template"
"io"
2017-10-15 16:49:40 +03:00
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
2018-04-28 12:32:48 +03:00
"sync"
"time"
2018-01-21 04:09:16 +03:00
secretRequired "github.com/danielheath/gin-teeny-security"
"github.com/gin-contrib/multitemplate"
2017-09-29 15:48:32 +03:00
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/jcelliott/lumber"
"github.com/schollz/cowyo/encrypt"
)
2018-03-12 01:46:29 +03:00
const minutesToUnlock = 10.0
2018-04-28 04:42:51 +03:00
type Site struct {
2018-04-30 13:19:11 +03:00
PathToData string
Css []byte
DefaultPage string
DefaultPassword string
Debounce int
Diary bool
SessionStore sessions.Store
SecretCode string
AllowInsecure bool
Fileuploads bool
MaxUploadSize uint
Logger *lumber.ConsoleLogger
MaxDocumentSize uint // in runes; about a 10mb limit by default
2018-04-28 12:32:48 +03:00
saveMut sync.Mutex
2018-04-28 05:19:43 +03:00
sitemapUpToDate bool // TODO this makes everything use a pointer
2018-04-28 04:42:51 +03:00
}
2018-04-28 05:19:43 +03:00
func (s *Site) defaultLock() string {
2018-04-28 05:16:10 +03:00
if s.DefaultPassword == "" {
return ""
}
return HashPassword(s.DefaultPassword)
}
2018-04-30 13:19:11 +03:00
var hotTemplateReloading bool
func Serve(
filepathToData,
2018-01-18 12:50:13 +03:00
host,
port,
crt_path,
key_path string,
TLS bool,
cssFile string,
defaultPage string,
defaultPassword string,
debounce int,
diary bool,
secret string,
2018-01-21 04:09:16 +03:00
secretCode string,
allowInsecure bool,
fileuploads bool,
maxUploadSize uint,
maxDocumentSize uint,
logger *lumber.ConsoleLogger,
2018-01-18 12:50:13 +03:00
) {
var customCSS []byte
// collect custom CSS
if len(cssFile) > 0 {
var errRead error
customCSS, errRead = ioutil.ReadFile(cssFile)
if errRead != nil {
fmt.Println(errRead)
return
}
fmt.Printf("Loaded CSS file, %d bytes\n", len(customCSS))
}
router := Site{
PathToData: filepathToData,
Css: customCSS,
DefaultPage: defaultPage,
DefaultPassword: defaultPassword,
Debounce: debounce,
Diary: diary,
SessionStore: sessions.NewCookieStore([]byte(secret)),
SecretCode: secretCode,
AllowInsecure: allowInsecure,
Fileuploads: fileuploads,
MaxUploadSize: maxUploadSize,
Logger: logger,
MaxDocumentSize: maxDocumentSize,
}.Router()
if TLS {
http.ListenAndServeTLS(host+":"+port, crt_path, key_path, router)
} else {
panic(router.Run(host + ":" + port))
}
2018-04-28 04:42:51 +03:00
}
func (s Site) Router() *gin.Engine {
2018-04-28 05:30:13 +03:00
if s.Logger == nil {
s.Logger = lumber.NewConsoleLogger(lumber.TRACE)
}
2018-04-30 13:19:11 +03:00
if hotTemplateReloading {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
2018-01-21 04:09:16 +03:00
router := gin.Default()
router.SetFuncMap(template.FuncMap{
2018-04-28 12:29:35 +03:00
"sniffContentType": s.sniffContentType,
})
2018-04-30 13:19:11 +03:00
if hotTemplateReloading {
router.LoadHTMLGlob("templates/*.tmpl")
} else {
2018-04-28 12:29:35 +03:00
router.HTMLRender = s.loadTemplates("index.tmpl")
}
router.Use(sessions.Sessions(s.PathToData, s.SessionStore))
2018-04-28 04:42:51 +03:00
if s.SecretCode != "" {
2018-02-07 07:09:01 +03:00
cfg := &secretRequired.Config{
2018-04-28 04:42:51 +03:00
Secret: s.SecretCode,
2018-02-07 07:09:01 +03:00
Path: "/login/",
RequireAuth: func(c *gin.Context) bool {
page := c.Param("page")
cmd := c.Param("command")
if page == "sitemap.xml" || page == "favicon.ico" || page == "static" || page == "uploads" {
return false // no auth for these
2018-02-07 07:09:01 +03:00
}
if page != "" && cmd == "/read" {
2018-04-28 05:30:13 +03:00
p := s.Open(page)
2018-02-07 07:09:01 +03:00
fmt.Printf("p: '%+v'\n", p)
if p != nil && p.IsPublished {
return false // Published pages don't require auth.
}
}
return true
},
}
router.Use(cfg.Middleware)
2018-01-21 04:09:16 +03:00
}
// router.Use(static.Serve("/static/", static.LocalFile("./static", true)))
router.GET("/", func(c *gin.Context) {
2018-04-28 04:42:51 +03:00
if s.DefaultPage != "" {
c.Redirect(302, "/"+s.DefaultPage+"/read")
2017-11-04 13:21:51 +03:00
} else {
c.Redirect(302, "/"+randomAlliterateCombo())
}
})
2018-04-28 05:13:19 +03:00
router.POST("/uploads", s.handleUpload)
router.GET("/:page", func(c *gin.Context) {
page := c.Param("page")
c.Redirect(302, "/"+page+"/")
})
router.GET("/:page/*command", s.handlePageRequest)
2018-04-28 05:19:43 +03:00
router.POST("/update", s.handlePageUpdate)
2018-04-28 05:30:13 +03:00
router.POST("/relinquish", s.handlePageRelinquish) // relinquish returns the page no matter what (and destroys if nessecary)
router.POST("/exists", s.handlePageExists)
router.POST("/prime", s.handlePrime)
2018-04-28 05:16:10 +03:00
router.POST("/lock", s.handleLock)
2018-04-28 05:30:13 +03:00
router.POST("/publish", s.handlePublish)
router.POST("/encrypt", s.handleEncrypt)
router.DELETE("/oldlist", s.handleClearOldListItems)
router.DELETE("/listitem", s.deleteListItem)
// start long-processes as threads
2018-04-28 05:19:43 +03:00
go s.thread_SiteMap()
2018-01-21 04:09:16 +03:00
// Allow iframe/scripts in markup?
2018-04-28 04:42:51 +03:00
allowInsecureHtml = s.AllowInsecure
return router
}
2018-04-28 12:29:35 +03:00
func (s *Site) loadTemplates(list ...string) multitemplate.Render {
r := multitemplate.New()
for _, x := range list {
templateString, err := Asset("templates/" + x)
if err != nil {
panic(err)
}
tmplMessage, err := template.New(x).Funcs(template.FuncMap{
2018-04-28 12:29:35 +03:00
"sniffContentType": s.sniffContentType,
}).Parse(string(templateString))
if err != nil {
panic(err)
}
r.Add(x, tmplMessage)
}
return r
}
2018-03-12 01:46:29 +03:00
func pageIsLocked(p *Page, c *gin.Context) bool {
// it is easier to reason about when the page is actually unlocked
var unlocked = !p.IsLocked ||
(p.IsLocked && p.UnlockedFor == getSetSessionID(c))
return !unlocked
}
2018-04-28 05:30:13 +03:00
func (s *Site) handlePageRelinquish(c *gin.Context) {
2017-06-28 06:32:38 +03:00
type QueryJSON struct {
Page string `json:"page"`
}
var json QueryJSON
err := c.BindJSON(&json)
if err != nil {
2018-04-28 05:30:13 +03:00
s.Logger.Trace(err.Error())
2017-06-28 06:32:38 +03:00
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
return
}
if len(json.Page) == 0 {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"})
return
}
message := "Relinquished"
2018-04-28 05:30:13 +03:00
p := s.Open(json.Page)
2017-08-17 17:36:07 +03:00
name := p.Meta
if name == "" {
name = json.Page
}
2017-06-28 06:32:38 +03:00
text := p.Text.GetCurrent()
2018-03-12 01:46:29 +03:00
isLocked := pageIsLocked(p, c)
2017-06-28 19:10:32 +03:00
isEncrypted := p.IsEncrypted
destroyed := p.IsPrimedForSelfDestruct
2018-03-12 01:46:29 +03:00
if !isLocked && p.IsPrimedForSelfDestruct {
2017-06-28 06:32:38 +03:00
p.Erase()
message = "Relinquished and erased"
}
c.JSON(http.StatusOK, gin.H{"success": true,
"name": name,
"message": message,
"text": text,
"locked": isLocked,
"encrypted": isEncrypted,
"destroyed": destroyed})
2017-06-28 06:32:38 +03:00
}
2018-03-12 01:46:29 +03:00
func getSetSessionID(c *gin.Context) (sid string) {
var (
session = sessions.Default(c)
v = session.Get("sid")
)
if v != nil {
sid = v.(string)
}
if v == nil || sid == "" {
sid = RandStringBytesMaskImprSrc(8)
session.Set("sid", sid)
session.Save()
}
return sid
}
2018-04-28 05:19:43 +03:00
func (s *Site) thread_SiteMap() {
for {
2018-04-28 05:19:43 +03:00
if !s.sitemapUpToDate {
2018-04-28 05:30:13 +03:00
s.Logger.Info("Generating sitemap...")
2018-04-28 05:19:43 +03:00
s.sitemapUpToDate = true
2018-04-28 12:29:35 +03:00
ioutil.WriteFile(path.Join(s.PathToData, "sitemap.xml"), []byte(s.generateSiteMap()), 0644)
2018-04-28 05:30:13 +03:00
s.Logger.Info("..finished generating sitemap")
}
time.Sleep(time.Second)
}
}
2018-04-28 05:30:13 +03:00
func (s *Site) generateSiteMap() (sitemap string) {
2018-04-28 12:29:35 +03:00
files, _ := ioutil.ReadDir(s.PathToData)
2017-10-15 16:49:40 +03:00
lastEdited := make([]string, len(files))
names := make([]string, len(files))
i := 0
for _, f := range files {
names[i] = DecodeFileName(f.Name())
2018-04-28 05:30:13 +03:00
p := s.Open(names[i])
2017-10-15 16:49:40 +03:00
if p.IsPublished {
lastEdited[i] = time.Unix(p.Text.LastEditTime()/1000000000, 0).Format("2006-01-02")
i++
}
}
names = names[:i]
lastEdited = lastEdited[:i]
sitemap = ""
2017-10-15 16:49:40 +03:00
for i := range names {
sitemap += fmt.Sprintf(`
<url>
<loc>{{ .Request.Host }}/%s/read</loc>
<lastmod>%s</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
`, names[i], lastEdited[i])
2017-10-15 16:49:40 +03:00
}
sitemap += "</urlset>"
return
}
2017-11-02 15:27:39 +03:00
2018-04-28 05:19:43 +03:00
func (s *Site) handlePageRequest(c *gin.Context) {
page := c.Param("page")
2017-10-15 16:58:43 +03:00
command := c.Param("command")
2017-10-15 16:49:40 +03:00
if page == "sitemap.xml" {
2018-04-28 12:29:35 +03:00
siteMap, err := ioutil.ReadFile(path.Join(s.PathToData, "sitemap.xml"))
if err != nil {
c.Data(http.StatusInternalServerError, contentType("sitemap.xml"), []byte(""))
} else {
fmt.Fprintln(c.Writer, `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`)
template.Must(template.New("sitemap").Parse(string(siteMap))).Execute(c.Writer, c)
}
2017-10-15 16:49:40 +03:00
return
2017-10-15 16:58:43 +03:00
} else if page == "favicon.ico" {
data, _ := Asset("/static/img/cowyo/favicon.ico")
c.Data(http.StatusOK, contentType("/static/img/cowyo/favicon.ico"), data)
return
2017-10-15 16:58:43 +03:00
} else if page == "static" {
filename := page + command
var data []byte
if filename == "static/css/custom.css" {
data = s.Css
} else {
var errAssset error
data, errAssset = Asset(filename)
if errAssset != nil {
c.String(http.StatusInternalServerError, "Could not find data")
}
}
c.Data(http.StatusOK, contentType(filename), data)
return
} else if page == "uploads" {
if len(command) == 0 || command == "/" || command == "/edit" {
2018-04-28 05:13:19 +03:00
if !s.Fileuploads {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
return
}
} else {
command = command[1:]
if !strings.HasSuffix(command, ".upload") {
command = command + ".upload"
}
2018-04-28 12:29:35 +03:00
pathname := path.Join(s.PathToData, command)
if allowInsecureHtml {
c.Header(
"Content-Disposition",
`inline; filename="`+c.DefaultQuery("filename", "upload")+`"`,
)
} else {
// Prevent malicious html uploads by forcing type to plaintext and 'download-instead-of-view'
c.Header("Content-Type", "text/plain")
c.Header(
"Content-Disposition",
`attachment; filename="`+c.DefaultQuery("filename", "upload")+`"`,
)
}
c.File(pathname)
return
}
}
2018-04-28 05:30:13 +03:00
p := s.Open(page)
2017-10-15 16:58:43 +03:00
if len(command) < 2 {
2017-11-02 15:27:39 +03:00
if p.IsPublished {
c.Redirect(302, "/"+page+"/read")
} else {
c.Redirect(302, "/"+page+"/edit")
}
2017-10-15 16:58:43 +03:00
return
}
// use the default lock
2018-04-28 05:16:10 +03:00
if s.defaultLock() != "" && p.IsNew() {
p.IsLocked = true
2018-04-28 05:16:10 +03:00
p.PassphraseToUnlock = s.defaultLock()
}
2018-03-12 01:46:29 +03:00
version := c.DefaultQuery("version", "ajksldfjl")
isLocked := pageIsLocked(p, c)
// Disallow anything but viewing locked/encrypted pages
2018-03-12 01:46:29 +03:00
if (p.IsEncrypted || isLocked) &&
(command[0:2] != "/v" && command[0:2] != "/r") {
c.Redirect(302, "/"+page+"/view")
return
}
// Destroy page if it is opened and primed
2018-03-12 01:46:29 +03:00
if p.IsPrimedForSelfDestruct && !isLocked && !p.IsEncrypted {
2018-02-18 23:04:23 +03:00
p.Update("<center><em>This page has self-destructed. You cannot return to it.</em></center>\n\n" + p.Text.GetCurrent())
p.Erase()
if p.IsPublished {
command = "/read"
} else {
command = "/view"
}
}
if command == "/erase" {
2018-03-12 01:46:29 +03:00
if !isLocked && !p.IsEncrypted {
p.Erase()
c.Redirect(302, "/"+page+"/edit")
} else {
c.Redirect(302, "/"+page+"/view")
}
return
}
rawText := p.Text.GetCurrent()
rawHTML := p.RenderedPage
// Check to see if an old version is requested
versionInt, versionErr := strconv.Atoi(version)
if versionErr == nil && versionInt > 0 {
versionText, err := p.Text.GetPreviousByTimestamp(int64(versionInt))
if err == nil {
rawText = versionText
rawHTML = GithubMarkdownToHTML(rawText)
}
}
// Get history
var versionsInt64 []int64
var versionsChangeSums []int
var versionsText []string
if command[0:2] == "/h" {
versionsInt64, versionsChangeSums = p.Text.GetMajorSnapshotsAndChangeSums(60) // get snapshots 60 seconds apart
versionsText = make([]string, len(versionsInt64))
for i, v := range versionsInt64 {
versionsText[i] = time.Unix(v/1000000000, 0).Format("Mon Jan 2 15:04:05 MST 2006")
}
versionsText = reverseSliceString(versionsText)
versionsInt64 = reverseSliceInt64(versionsInt64)
versionsChangeSums = reverseSliceInt(versionsChangeSums)
}
2017-11-13 21:32:32 +03:00
if len(command) > 3 && command[0:3] == "/ra" {
c.Writer.Header().Set("Content-Type", "text/plain")
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Data(200, contentType(p.Name), []byte(rawText))
return
}
var DirectoryEntries []os.FileInfo
if page == "ls" {
command = "/view"
2018-04-28 05:30:13 +03:00
DirectoryEntries = s.DirectoryList()
}
if page == "uploads" {
command = "/view"
var err error
2018-04-28 12:29:35 +03:00
DirectoryEntries, err = s.UploadList()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
// swap out /view for /read if it is published
if p.IsPublished {
rawHTML = strings.Replace(rawHTML, "/view", "/read", -1)
}
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"EditPage": command[0:2] == "/e", // /edit
"ViewPage": command[0:2] == "/v", // /view
"ListPage": command[0:2] == "/l", // /list
"HistoryPage": command[0:2] == "/h", // /history
2017-11-02 15:27:39 +03:00
"ReadPage": command[0:2] == "/r", // /history
"DontKnowPage": command[0:2] != "/e" &&
command[0:2] != "/v" &&
command[0:2] != "/l" &&
2018-01-29 14:33:11 +03:00
command[0:2] != "/r" &&
command[0:2] != "/h",
"DirectoryPage": page == "ls" || page == "uploads",
"UploadPage": page == "uploads",
"DirectoryEntries": DirectoryEntries,
"Page": page,
"RenderedPage": template.HTML([]byte(rawHTML)),
"RawPage": rawText,
"Versions": versionsInt64,
"VersionsText": versionsText,
"VersionsChangeSums": versionsChangeSums,
2018-03-12 01:46:29 +03:00
"IsLocked": isLocked,
"IsEncrypted": p.IsEncrypted,
"ListItems": renderList(rawText),
"Route": "/" + page + command,
"HasDotInName": strings.Contains(page, "."),
2017-09-29 15:48:32 +03:00
"RecentlyEdited": getRecentlyEdited(page, c),
2017-10-15 16:49:40 +03:00
"IsPublished": p.IsPublished,
"CustomCSS": len(s.Css) > 0,
2018-04-28 05:12:10 +03:00
"Debounce": s.Debounce,
"DiaryMode": s.Diary,
"Date": time.Now().Format("2006-01-02"),
"UnixTime": time.Now().Unix(),
2018-01-31 05:53:37 +03:00
"ChildPageNames": p.ChildPageNames(),
2018-04-28 05:13:19 +03:00
"AllowFileUploads": s.Fileuploads,
2018-04-28 05:10:47 +03:00
"MaxUploadMB": s.MaxUploadSize,
})
}
2017-09-29 15:48:32 +03:00
func getRecentlyEdited(title string, c *gin.Context) []string {
session := sessions.Default(c)
var recentlyEdited string
v := session.Get("recentlyEdited")
editedThings := []string{}
if v == nil {
recentlyEdited = title
} else {
editedThings = strings.Split(v.(string), "|||")
if !stringInSlice(title, editedThings) {
recentlyEdited = v.(string) + "|||" + title
} else {
recentlyEdited = v.(string)
}
}
session.Set("recentlyEdited", recentlyEdited)
session.Save()
editedThingsWithoutCurrent := make([]string, len(editedThings))
i := 0
for _, thing := range editedThings {
if thing == title {
continue
}
2017-09-29 19:21:10 +03:00
if strings.Contains(thing, "icon-") {
continue
}
2017-09-29 15:48:32 +03:00
editedThingsWithoutCurrent[i] = thing
i++
}
return editedThingsWithoutCurrent[:i]
}
2018-04-28 05:30:13 +03:00
func (s *Site) handlePageExists(c *gin.Context) {
2017-07-05 19:49:06 +03:00
type QueryJSON struct {
Page string `json:"page"`
}
var json QueryJSON
err := c.BindJSON(&json)
if err != nil {
2018-04-28 05:30:13 +03:00
s.Logger.Trace(err.Error())
2017-07-05 19:49:06 +03:00
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON", "exists": false})
return
}
2018-04-28 05:30:13 +03:00
p := s.Open(json.Page)
2017-07-05 19:49:06 +03:00
if len(p.Text.GetCurrent()) > 0 {
c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " found", "exists": true})
} else {
c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " not found", "exists": false})
}
}
2018-04-28 05:19:43 +03:00
func (s *Site) handlePageUpdate(c *gin.Context) {
type QueryJSON struct {
2017-06-28 06:32:38 +03:00
Page string `json:"page"`
NewText string `json:"new_text"`
FetchedAt int64 `json:"fetched_at"`
2017-06-28 06:32:38 +03:00
IsEncrypted bool `json:"is_encrypted"`
IsPrimed bool `json:"is_primed"`
2017-08-17 17:36:07 +03:00
Meta string `json:"meta"`
}
var json QueryJSON
2017-06-28 06:32:38 +03:00
err := c.BindJSON(&json)
if err != nil {
2018-04-28 05:30:13 +03:00
s.Logger.Trace(err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
return
}
if uint(len(json.NewText)) > s.MaxDocumentSize {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Too much"})
return
}
2017-06-28 06:32:38 +03:00
if len(json.Page) == 0 {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"})
return
}
2018-04-28 05:30:13 +03:00
s.Logger.Trace("Update: %v", json)
p := s.Open(json.Page)
2018-03-12 01:46:29 +03:00
var (
message string
sinceLastEdit = time.Since(p.LastEditTime())
)
2017-07-05 19:49:06 +03:00
success := false
2018-03-12 01:46:29 +03:00
if pageIsLocked(p, c) {
if sinceLastEdit < minutesToUnlock {
message = "This page is being edited by someone else"
} else {
// here what might have happened is that two people unlock without
// editing thus they both suceeds but only one is able to edit
message = "Locked, must unlock first"
}
} else if p.IsEncrypted {
2017-06-28 06:32:38 +03:00
message = "Encrypted, must decrypt first"
} else if json.FetchedAt > 0 && p.LastEditUnixTime() > json.FetchedAt {
message = "Refusing to overwrite others work"
} else {
2017-08-17 17:36:07 +03:00
p.Meta = json.Meta
p.Update(json.NewText)
2017-06-28 06:32:38 +03:00
if json.IsEncrypted {
p.IsEncrypted = true
}
if json.IsPrimed {
p.IsPrimedForSelfDestruct = true
}
2017-08-17 17:44:18 +03:00
p.Save()
message = "Saved"
if p.IsPublished {
2018-04-28 05:19:43 +03:00
s.sitemapUpToDate = false
}
2017-07-05 19:49:06 +03:00
success = true
}
c.JSON(http.StatusOK, gin.H{"success": success, "message": message, "unix_time": time.Now().Unix()})
}
2018-04-28 05:30:13 +03:00
func (s *Site) handlePrime(c *gin.Context) {
type QueryJSON struct {
Page string `json:"page"`
}
var json QueryJSON
if c.BindJSON(&json) != nil {
c.String(http.StatusBadRequest, "Problem binding keys")
return
}
2018-04-28 05:30:13 +03:00
s.Logger.Trace("Update: %v", json)
p := s.Open(json.Page)
2018-03-12 01:46:29 +03:00
if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return
} else if p.IsEncrypted {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
return
}
p.IsPrimedForSelfDestruct = true
p.Save()
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"})
}
2018-04-28 05:19:43 +03:00
func (s *Site) handleLock(c *gin.Context) {
type QueryJSON struct {
Page string `json:"page"`
Passphrase string `json:"passphrase"`
}
var json QueryJSON
if c.BindJSON(&json) != nil {
c.String(http.StatusBadRequest, "Problem binding keys")
return
}
2018-04-28 05:30:13 +03:00
p := s.Open(json.Page)
2018-04-28 05:16:10 +03:00
if s.defaultLock() != "" && p.IsNew() {
2018-03-12 01:46:29 +03:00
p.IsLocked = true // IsLocked was replaced by variable wrt Context
2018-04-28 05:16:10 +03:00
p.PassphraseToUnlock = s.defaultLock()
}
if p.IsEncrypted {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
return
}
2018-03-12 01:46:29 +03:00
var (
message string
sessionID = getSetSessionID(c)
sinceLastEdit = time.Since(p.LastEditTime())
)
// both lock/unlock ends here on locked&timeout combination
if p.IsLocked &&
p.UnlockedFor != sessionID &&
p.UnlockedFor != "" &&
sinceLastEdit.Minutes() < minutesToUnlock {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": fmt.Sprintf("This page is being edited by someone else! Will unlock automatically %2.0f minutes after the last change.", minutesToUnlock-sinceLastEdit.Minutes()),
})
return
}
if !pageIsLocked(p, c) {
p.IsLocked = true
p.PassphraseToUnlock = HashPassword(json.Passphrase)
p.UnlockedFor = ""
message = "Locked"
} else {
err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
if err2 != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
return
}
2018-03-12 01:46:29 +03:00
p.UnlockedFor = sessionID
message = "Unlocked only for you"
}
p.Save()
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
2017-10-15 16:49:40 +03:00
}
2018-04-28 05:30:13 +03:00
func (s *Site) handlePublish(c *gin.Context) {
2017-10-15 16:49:40 +03:00
type QueryJSON struct {
Page string `json:"page"`
Publish bool `json:"publish"`
}
var json QueryJSON
if c.BindJSON(&json) != nil {
c.String(http.StatusBadRequest, "Problem binding keys")
return
}
2018-04-28 05:30:13 +03:00
p := s.Open(json.Page)
2017-10-15 16:49:40 +03:00
p.IsPublished = json.Publish
p.Save()
message := "Published"
if !p.IsPublished {
message = "Unpublished"
}
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}
2018-04-28 05:19:43 +03:00
func (s *Site) handleUpload(c *gin.Context) {
2018-04-28 05:13:19 +03:00
if !s.Fileuploads {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
return
}
file, info, err := c.Request.FormFile("file")
defer file.Close()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
h := sha256.New()
if _, err := io.Copy(h, file); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
newName := "sha256-" + encodeBytesToBase32(h.Sum(nil))
// Replaces any existing version, but sha256 collisions are rare as anything.
2018-04-28 12:29:35 +03:00
outfile, err := os.Create(path.Join(s.PathToData, newName+".upload"))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
file.Seek(0, io.SeekStart)
_, err = io.Copy(outfile, file)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Header("Location", "/uploads/"+newName+"?filename="+url.QueryEscape(info.Filename))
return
}
2018-04-28 05:30:13 +03:00
func (s *Site) handleEncrypt(c *gin.Context) {
type QueryJSON struct {
Page string `json:"page"`
Passphrase string `json:"passphrase"`
}
var json QueryJSON
if c.BindJSON(&json) != nil {
c.String(http.StatusBadRequest, "Problem binding keys")
return
}
2018-04-28 05:30:13 +03:00
p := s.Open(json.Page)
2018-03-12 01:46:29 +03:00
if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return
}
2018-04-28 05:30:13 +03:00
q := s.Open(json.Page)
var message string
if p.IsEncrypted {
decrypted, err2 := encrypt.DecryptString(p.Text.GetCurrent(), json.Passphrase)
if err2 != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong password"})
return
}
q.Erase()
2018-04-28 05:30:13 +03:00
q = s.Open(json.Page)
q.Update(decrypted)
q.IsEncrypted = false
q.IsLocked = p.IsLocked
q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
message = "Decrypted"
} else {
currentText := p.Text.GetCurrent()
encrypted, _ := encrypt.EncryptString(currentText, json.Passphrase)
q.Erase()
2018-04-28 05:30:13 +03:00
q = s.Open(json.Page)
q.Update(encrypted)
q.IsEncrypted = true
q.IsLocked = p.IsLocked
q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
message = "Encrypted"
}
q.Save()
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}
2018-04-28 05:30:13 +03:00
func (s *Site) deleteListItem(c *gin.Context) {
lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname")
if err == nil {
2018-04-28 05:30:13 +03:00
p := s.Open(page)
_, listItems := reorderList(p.Text.GetCurrent())
newText := p.Text.GetCurrent()
for i, lineString := range listItems {
// fmt.Println(i, lineString, lineNum)
if i+1 == lineNum {
// fmt.Println("MATCHED")
if strings.Contains(lineString, "~~") == false {
// fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
} else {
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
}
p.Update(newText)
break
}
}
c.JSON(200, gin.H{
"success": true,
"message": "Done.",
})
} else {
c.JSON(200, gin.H{
"success": false,
"message": err.Error(),
})
}
}
2018-04-28 05:30:13 +03:00
func (s *Site) handleClearOldListItems(c *gin.Context) {
type QueryJSON struct {
Page string `json:"page"`
}
var json QueryJSON
if c.BindJSON(&json) != nil {
c.String(http.StatusBadRequest, "Problem binding keys")
return
}
2018-04-28 05:30:13 +03:00
p := s.Open(json.Page)
if p.IsEncrypted {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
return
}
2018-03-12 01:46:29 +03:00
if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return
}
lines := strings.Split(p.Text.GetCurrent(), "\n")
newLines := make([]string, len(lines))
newLinesI := 0
for _, line := range lines {
if strings.Count(line, "~~") != 2 {
newLines[newLinesI] = line
newLinesI++
}
}
p.Update(strings.Join(newLines[0:newLinesI], "\n"))
p.Save()
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Cleared"})
}