1
0
mirror of https://github.com/schollz/cowyo.git synced 2023-08-10 21:13:00 +03:00

Merge pull request #133 from DanielHeath/master

Make cowyo separately mountable
This commit is contained in:
Zack 2018-04-30 02:19:23 -07:00 committed by GitHub
commit 180a28cda6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 299 additions and 227 deletions

View File

@ -5,49 +5,48 @@ VERSION=$(shell git describe)
LDFLAGS=-ldflags "-X main.version=${VERSION}" LDFLAGS=-ldflags "-X main.version=${VERSION}"
.PHONY: build .PHONY: build
build: bindata.go build: server/bindata.go
go build ${LDFLAGS} go build ${LDFLAGS}
STATICFILES := $(wildcard static/*) STATICFILES := $(wildcard static/*)
TEMPLATES := $(wildcard templates/*) TEMPLATES := $(wildcard templates/*)
bindata.go: $(STATICFILES) $(TEMPLATES) server/bindata.go: $(STATICFILES) $(TEMPLATES)
go-bindata -tags '!debug' static/... templates/... go-bindata -tags '!debug' -o server/bindata.go static/... templates/...
go fmt go fmt
bindata-debug.go: $(STATICFILES) $(TEMPLATES) server/bindata-debug.go: $(STATICFILES) $(TEMPLATES)
go-bindata -tags 'debug' -o bindata-debug.go -debug static/... templates/... go-bindata -tags 'debug' -o server/bindata-debug.go -debug static/... templates/...
go fmt go fmt
.PHONY: devel .PHONY: devel
devel: bindata-debug.go devel: server/bindata-debug.go
go build -tags debug go build -tags debug
.PHONY: quick .PHONY: quick
quick: bindata.go quick: server/bindata.go
go build go build
.PHONY: linuxarm .PHONY: linuxarm
linuxarm: bindata.go linuxarm: server/bindata.go
env GOOS=linux GOARCH=arm go build ${LDFLAGS} -o dist/cowyo_linux_arm env GOOS=linux GOARCH=arm go build ${LDFLAGS} -o dist/cowyo_linux_arm
#cd dist && upx --brute cowyo_linux_arm #cd dist && upx --brute cowyo_linux_arm
.PHONY: linux32 .PHONY: linux32
linux32: bindata.go linux32: server/bindata.go
env GOOS=linux GOARCH=386 go build ${LDFLAGS} -o dist/cowyo_linux_32bit env GOOS=linux GOARCH=386 go build ${LDFLAGS} -o dist/cowyo_linux_32bit
#cd dist && upx --brute cowyo_linux_32bit #cd dist && upx --brute cowyo_linux_32bit
.PHONY: linux64 .PHONY: linux64
linux64: bindata.go linux64: server/bindata.go
env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_linux_amd64 env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_linux_amd64
.PHONY: windows .PHONY: windows
windows: bindata.go windows: server/bindata.go
env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_windows_amd64.exe env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_windows_amd64.exe
#cd dist && upx --brute cowyo_windows_amd64.exe #cd dist && upx --brute cowyo_windows_amd64.exe
.PHONY: osx .PHONY: osx
osx: bindata.go osx: server/bindata.go
env GOOS=darwin GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_osx_amd64 env GOOS=darwin GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_osx_amd64
#cd dist && upx --brute cowyo_osx_amd64 #cd dist && upx --brute cowyo_osx_amd64

42
cmd/herdyo/herdyo.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"net/http"
"strings"
"github.com/gin-contrib/sessions"
"github.com/schollz/cowyo/server"
)
func main() {
store := sessions.NewCookieStore([]byte("secret"))
first := server.Site{
PathToData: "site1",
Debounce: 500,
SessionStore: store,
AllowInsecure: true,
HotTemplateReloading: true,
Fileuploads: true,
MaxUploadSize: 2,
}.Router()
second := server.Site{
PathToData: "site2",
Debounce: 500,
SessionStore: store,
AllowInsecure: true,
HotTemplateReloading: true,
Fileuploads: true,
MaxUploadSize: 2,
}.Router()
panic(http.ListenAndServe("localhost:8000", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.Host, "first") {
first.ServeHTTP(rw, r)
} else if strings.HasPrefix(r.Host, "second") {
second.ServeHTTP(rw, r)
} else {
http.NotFound(rw, r)
}
})))
}

55
main.go
View File

@ -2,10 +2,14 @@ package main
import ( import (
"fmt" "fmt"
"net"
"os" "os"
"time" "time"
"gopkg.in/urfave/cli.v1" "github.com/jcelliott/lumber"
"github.com/schollz/cowyo/server"
cli "gopkg.in/urfave/cli.v1"
) )
var version string var version string
@ -19,9 +23,7 @@ func main() {
app.Version = version app.Version = version
app.Compiled = time.Now() app.Compiled = time.Now()
app.Action = func(c *cli.Context) error { app.Action = func(c *cli.Context) error {
if !c.GlobalBool("debug") {
turnOffDebugger()
}
pathToData = c.GlobalString("data") pathToData = c.GlobalString("data")
os.MkdirAll(pathToData, 0755) os.MkdirAll(pathToData, 0755)
host := c.GlobalString("host") host := c.GlobalString("host")
@ -40,10 +42,8 @@ func main() {
fmt.Printf("\nRunning cowyo server (version %s) at http://%s:%s\n\n", version, host, c.GlobalString("port")) fmt.Printf("\nRunning cowyo server (version %s) at http://%s:%s\n\n", version, host, c.GlobalString("port"))
} }
allowFileUploads = c.GlobalBool("allow-file-uploads") server.Serve(
maxUploadMB = c.GlobalUint("max-upload-mb") pathToData,
serve(
c.GlobalString("host"), c.GlobalString("host"),
c.GlobalString("port"), c.GlobalString("port"),
c.GlobalString("cert"), c.GlobalString("cert"),
@ -58,6 +58,9 @@ func main() {
c.GlobalString("access-code"), c.GlobalString("access-code"),
c.GlobalBool("allow-insecure-markup"), c.GlobalBool("allow-insecure-markup"),
hotTemplateReloading, hotTemplateReloading,
c.GlobalBool("allow-file-uploads"),
c.GlobalUint("max-upload-mb"),
logger(c.GlobalBool("debug")),
) )
return nil return nil
} }
@ -150,9 +153,6 @@ func main() {
Aliases: []string{"m"}, Aliases: []string{"m"},
Usage: "migrate from the old cowyo", Usage: "migrate from the old cowyo",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if !c.GlobalBool("debug") {
turnOffDebugger()
}
pathToData = c.GlobalString("data") pathToData = c.GlobalString("data")
pathToOldData := c.GlobalString("olddata") pathToOldData := c.GlobalString("olddata")
if len(pathToOldData) == 0 { if len(pathToOldData) == 0 {
@ -164,12 +164,43 @@ func main() {
fmt.Printf("Can not find '%s', does it exist?", pathToOldData) fmt.Printf("Can not find '%s', does it exist?", pathToOldData)
return nil return nil
} }
migrate(pathToOldData, pathToData) server.Migrate(pathToOldData, pathToData, logger(c.GlobalBool("debug")))
return nil return nil
}, },
}, },
} }
app.Run(os.Args) app.Run(os.Args)
}
// GetLocalIP returns the local ip address
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
bestIP := ""
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return bestIP
}
// exists returns whether the given file or directory exists or not
func exists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func logger(debug bool) *lumber.ConsoleLogger {
if !debug {
return lumber.NewConsoleLogger(lumber.WARN)
}
return lumber.NewConsoleLogger(lumber.TRACE)
} }

View File

@ -51,7 +51,7 @@
// +build debug // +build debug
package main package server
import ( import (
"fmt" "fmt"

View File

@ -51,7 +51,7 @@
// +build !debug // +build !debug
package main package server
import ( import (
"bytes" "bytes"

View File

@ -1,6 +1,6 @@
// +build debug // +build debug
package main package server
func init() { func init() {
hotTemplateReloading = true hotTemplateReloading = true

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"crypto/sha256" "crypto/sha256"
@ -12,27 +12,47 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
// "github.com/gin-contrib/static"
secretRequired "github.com/danielheath/gin-teeny-security" secretRequired "github.com/danielheath/gin-teeny-security"
"github.com/gin-contrib/multitemplate" "github.com/gin-contrib/multitemplate"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jcelliott/lumber"
"github.com/schollz/cowyo/encrypt" "github.com/schollz/cowyo/encrypt"
) )
const minutesToUnlock = 10.0 const minutesToUnlock = 10.0
var customCSS []byte type Site struct {
var defaultLock string PathToData string
var debounceTime int Css []byte
var diaryMode bool DefaultPage string
var allowFileUploads bool DefaultPassword string
var maxUploadMB uint Debounce int
var needSitemapUpdate = true Diary bool
SessionStore sessions.Store
SecretCode string
AllowInsecure bool
HotTemplateReloading bool
Fileuploads bool
MaxUploadSize uint
Logger *lumber.ConsoleLogger
func serve( saveMut sync.Mutex
sitemapUpToDate bool // TODO this makes everything use a pointer
}
func (s *Site) defaultLock() string {
if s.DefaultPassword == "" {
return ""
}
return HashPassword(s.DefaultPassword)
}
func Serve(
filepathToData,
host, host,
port, port,
crt_path, crt_path,
@ -47,31 +67,73 @@ func serve(
secretCode string, secretCode string,
allowInsecure bool, allowInsecure bool,
hotTemplateReloading bool, hotTemplateReloading bool,
fileuploads bool,
maxUploadSize uint,
logger *lumber.ConsoleLogger,
) { ) {
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))
}
if hotTemplateReloading { router := Site{
filepathToData,
customCSS,
defaultPage,
defaultPassword,
debounce,
diary,
sessions.NewCookieStore([]byte(secret)),
secretCode,
allowInsecure,
hotTemplateReloading,
fileuploads,
maxUploadSize,
logger,
sync.Mutex{},
false,
}.Router()
if TLS {
http.ListenAndServeTLS(host+":"+port, crt_path, key_path, router)
} else {
panic(router.Run(host + ":" + port))
}
}
func (s Site) Router() *gin.Engine {
if s.Logger == nil {
s.Logger = lumber.NewConsoleLogger(lumber.TRACE)
}
if s.HotTemplateReloading {
gin.SetMode(gin.DebugMode) gin.SetMode(gin.DebugMode)
} else { } else {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
router := gin.Default() router := gin.Default()
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
"sniffContentType": sniffContentType, "sniffContentType": s.sniffContentType,
}) })
if hotTemplateReloading { if s.HotTemplateReloading {
router.LoadHTMLGlob("templates/*.tmpl") router.LoadHTMLGlob("templates/*.tmpl")
} else { } else {
router.HTMLRender = loadTemplates("index.tmpl") router.HTMLRender = s.loadTemplates("index.tmpl")
} }
store := sessions.NewCookieStore([]byte(secret)) router.Use(sessions.Sessions(s.PathToData, s.SessionStore))
router.Use(sessions.Sessions("mysession", store)) if s.SecretCode != "" {
if secretCode != "" {
cfg := &secretRequired.Config{ cfg := &secretRequired.Config{
Secret: secretCode, Secret: s.SecretCode,
Path: "/login/", Path: "/login/",
RequireAuth: func(c *gin.Context) bool { RequireAuth: func(c *gin.Context) bool {
page := c.Param("page") page := c.Param("page")
@ -82,7 +144,7 @@ func serve(
} }
if page != "" && cmd == "/read" { if page != "" && cmd == "/read" {
p := Open(page) p := s.Open(page)
fmt.Printf("p: '%+v'\n", p) fmt.Printf("p: '%+v'\n", p)
if p != nil && p.IsPublished { if p != nil && p.IsPublished {
return false // Published pages don't require auth. return false // Published pages don't require auth.
@ -96,67 +158,39 @@ func serve(
// router.Use(static.Serve("/static/", static.LocalFile("./static", true))) // router.Use(static.Serve("/static/", static.LocalFile("./static", true)))
router.GET("/", func(c *gin.Context) { router.GET("/", func(c *gin.Context) {
if defaultPage != "" { if s.DefaultPage != "" {
c.Redirect(302, "/"+defaultPage+"/read") c.Redirect(302, "/"+s.DefaultPage+"/read")
} else { } else {
c.Redirect(302, "/"+randomAlliterateCombo()) c.Redirect(302, "/"+randomAlliterateCombo())
} }
}) })
router.POST("/uploads", handleUpload) router.POST("/uploads", s.handleUpload)
router.GET("/:page", func(c *gin.Context) { router.GET("/:page", func(c *gin.Context) {
page := c.Param("page") page := c.Param("page")
c.Redirect(302, "/"+page+"/") c.Redirect(302, "/"+page+"/")
}) })
router.GET("/:page/*command", handlePageRequest) router.GET("/:page/*command", s.handlePageRequest)
router.POST("/update", handlePageUpdate) router.POST("/update", s.handlePageUpdate)
router.POST("/relinquish", handlePageRelinquish) // relinquish returns the page no matter what (and destroys if nessecary) router.POST("/relinquish", s.handlePageRelinquish) // relinquish returns the page no matter what (and destroys if nessecary)
router.POST("/exists", handlePageExists) router.POST("/exists", s.handlePageExists)
router.POST("/prime", handlePrime) router.POST("/prime", s.handlePrime)
router.POST("/lock", handleLock) router.POST("/lock", s.handleLock)
router.POST("/publish", handlePublish) router.POST("/publish", s.handlePublish)
router.POST("/encrypt", handleEncrypt) router.POST("/encrypt", s.handleEncrypt)
router.DELETE("/oldlist", handleClearOldListItems) router.DELETE("/oldlist", s.handleClearOldListItems)
router.DELETE("/listitem", deleteListItem) router.DELETE("/listitem", s.deleteListItem)
// start long-processes as threads // start long-processes as threads
go thread_SiteMap() go s.thread_SiteMap()
// collect custom CSS
if len(cssFile) > 0 {
var errRead error
customCSS, errRead = ioutil.ReadFile(cssFile)
if errRead != nil {
fmt.Println(errRead.Error())
return
}
fmt.Printf("Loaded CSS file, %d bytes\n", len(customCSS))
}
// lock all pages automatically
if defaultPassword != "" {
fmt.Println("running with locked pages")
defaultLock = HashPassword(defaultPassword)
}
// set the debounce time
debounceTime = debounce
// set diary mode
diaryMode = diary
// Allow iframe/scripts in markup? // Allow iframe/scripts in markup?
allowInsecureHtml = allowInsecure allowInsecureHtml = s.AllowInsecure
return router
if TLS {
http.ListenAndServeTLS(host+":"+port, crt_path, key_path, router)
} else {
panic(router.Run(host + ":" + port))
}
} }
func loadTemplates(list ...string) multitemplate.Render { func (s *Site) loadTemplates(list ...string) multitemplate.Render {
r := multitemplate.New() r := multitemplate.New()
for _, x := range list { for _, x := range list {
@ -166,7 +200,7 @@ func loadTemplates(list ...string) multitemplate.Render {
} }
tmplMessage, err := template.New(x).Funcs(template.FuncMap{ tmplMessage, err := template.New(x).Funcs(template.FuncMap{
"sniffContentType": sniffContentType, "sniffContentType": s.sniffContentType,
}).Parse(string(templateString)) }).Parse(string(templateString))
if err != nil { if err != nil {
panic(err) panic(err)
@ -185,14 +219,14 @@ func pageIsLocked(p *Page, c *gin.Context) bool {
return !unlocked return !unlocked
} }
func handlePageRelinquish(c *gin.Context) { func (s *Site) handlePageRelinquish(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
} }
var json QueryJSON var json QueryJSON
err := c.BindJSON(&json) err := c.BindJSON(&json)
if err != nil { if err != nil {
log.Trace(err.Error()) s.Logger.Trace(err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
return return
} }
@ -201,7 +235,7 @@ func handlePageRelinquish(c *gin.Context) {
return return
} }
message := "Relinquished" message := "Relinquished"
p := Open(json.Page) p := s.Open(json.Page)
name := p.Meta name := p.Meta
if name == "" { if name == "" {
name = json.Page name = json.Page
@ -239,26 +273,26 @@ func getSetSessionID(c *gin.Context) (sid string) {
return sid return sid
} }
func thread_SiteMap() { func (s *Site) thread_SiteMap() {
for { for {
if needSitemapUpdate { if !s.sitemapUpToDate {
log.Info("Generating sitemap...") s.Logger.Info("Generating sitemap...")
needSitemapUpdate = false s.sitemapUpToDate = true
ioutil.WriteFile(path.Join(pathToData, "sitemap.xml"), []byte(generateSiteMap()), 0644) ioutil.WriteFile(path.Join(s.PathToData, "sitemap.xml"), []byte(s.generateSiteMap()), 0644)
log.Info("..finished generating sitemap") s.Logger.Info("..finished generating sitemap")
} }
time.Sleep(time.Second) time.Sleep(time.Second)
} }
} }
func generateSiteMap() (sitemap string) { func (s *Site) generateSiteMap() (sitemap string) {
files, _ := ioutil.ReadDir(pathToData) files, _ := ioutil.ReadDir(s.PathToData)
lastEdited := make([]string, len(files)) lastEdited := make([]string, len(files))
names := make([]string, len(files)) names := make([]string, len(files))
i := 0 i := 0
for _, f := range files { for _, f := range files {
names[i] = DecodeFileName(f.Name()) names[i] = DecodeFileName(f.Name())
p := Open(names[i]) p := s.Open(names[i])
if p.IsPublished { if p.IsPublished {
lastEdited[i] = time.Unix(p.Text.LastEditTime()/1000000000, 0).Format("2006-01-02") lastEdited[i] = time.Unix(p.Text.LastEditTime()/1000000000, 0).Format("2006-01-02")
i++ i++
@ -281,12 +315,12 @@ func generateSiteMap() (sitemap string) {
return return
} }
func handlePageRequest(c *gin.Context) { func (s *Site) handlePageRequest(c *gin.Context) {
page := c.Param("page") page := c.Param("page")
command := c.Param("command") command := c.Param("command")
if page == "sitemap.xml" { if page == "sitemap.xml" {
siteMap, err := ioutil.ReadFile(path.Join(pathToData, "sitemap.xml")) siteMap, err := ioutil.ReadFile(path.Join(s.PathToData, "sitemap.xml"))
if err != nil { if err != nil {
c.Data(http.StatusInternalServerError, contentType("sitemap.xml"), []byte("")) c.Data(http.StatusInternalServerError, contentType("sitemap.xml"), []byte(""))
} else { } else {
@ -302,7 +336,7 @@ func handlePageRequest(c *gin.Context) {
filename := page + command filename := page + command
var data []byte var data []byte
if filename == "static/css/custom.css" { if filename == "static/css/custom.css" {
data = customCSS data = s.Css
} else { } else {
var errAssset error var errAssset error
data, errAssset = Asset(filename) data, errAssset = Asset(filename)
@ -314,7 +348,7 @@ func handlePageRequest(c *gin.Context) {
return return
} else if page == "uploads" { } else if page == "uploads" {
if len(command) == 0 || command == "/" || command == "/edit" { if len(command) == 0 || command == "/" || command == "/edit" {
if !allowFileUploads { if !s.Fileuploads {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server")) c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
return return
} }
@ -323,7 +357,7 @@ func handlePageRequest(c *gin.Context) {
if !strings.HasSuffix(command, ".upload") { if !strings.HasSuffix(command, ".upload") {
command = command + ".upload" command = command + ".upload"
} }
pathname := path.Join(pathToData, command) pathname := path.Join(s.PathToData, command)
if allowInsecureHtml { if allowInsecureHtml {
c.Header( c.Header(
@ -343,7 +377,7 @@ func handlePageRequest(c *gin.Context) {
} }
} }
p := Open(page) p := s.Open(page)
if len(command) < 2 { if len(command) < 2 {
if p.IsPublished { if p.IsPublished {
c.Redirect(302, "/"+page+"/read") c.Redirect(302, "/"+page+"/read")
@ -354,9 +388,9 @@ func handlePageRequest(c *gin.Context) {
} }
// use the default lock // use the default lock
if defaultLock != "" && p.IsNew() { if s.defaultLock() != "" && p.IsNew() {
p.IsLocked = true p.IsLocked = true
p.PassphraseToUnlock = defaultLock p.PassphraseToUnlock = s.defaultLock()
} }
version := c.DefaultQuery("version", "ajksldfjl") version := c.DefaultQuery("version", "ajksldfjl")
@ -430,12 +464,12 @@ func handlePageRequest(c *gin.Context) {
var DirectoryEntries []os.FileInfo var DirectoryEntries []os.FileInfo
if page == "ls" { if page == "ls" {
command = "/view" command = "/view"
DirectoryEntries = DirectoryList() DirectoryEntries = s.DirectoryList()
} }
if page == "uploads" { if page == "uploads" {
command = "/view" command = "/view"
var err error var err error
DirectoryEntries, err = UploadList() DirectoryEntries, err = s.UploadList()
if err != nil { if err != nil {
c.AbortWithError(http.StatusInternalServerError, err) c.AbortWithError(http.StatusInternalServerError, err)
return return
@ -474,14 +508,14 @@ func handlePageRequest(c *gin.Context) {
"HasDotInName": strings.Contains(page, "."), "HasDotInName": strings.Contains(page, "."),
"RecentlyEdited": getRecentlyEdited(page, c), "RecentlyEdited": getRecentlyEdited(page, c),
"IsPublished": p.IsPublished, "IsPublished": p.IsPublished,
"CustomCSS": len(customCSS) > 0, "CustomCSS": len(s.Css) > 0,
"Debounce": debounceTime, "Debounce": s.Debounce,
"DiaryMode": diaryMode, "DiaryMode": s.Diary,
"Date": time.Now().Format("2006-01-02"), "Date": time.Now().Format("2006-01-02"),
"UnixTime": time.Now().Unix(), "UnixTime": time.Now().Unix(),
"ChildPageNames": p.ChildPageNames(), "ChildPageNames": p.ChildPageNames(),
"AllowFileUploads": allowFileUploads, "AllowFileUploads": s.Fileuploads,
"MaxUploadMB": maxUploadMB, "MaxUploadMB": s.MaxUploadSize,
}) })
} }
@ -517,18 +551,18 @@ func getRecentlyEdited(title string, c *gin.Context) []string {
return editedThingsWithoutCurrent[:i] return editedThingsWithoutCurrent[:i]
} }
func handlePageExists(c *gin.Context) { func (s *Site) handlePageExists(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
} }
var json QueryJSON var json QueryJSON
err := c.BindJSON(&json) err := c.BindJSON(&json)
if err != nil { if err != nil {
log.Trace(err.Error()) s.Logger.Trace(err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON", "exists": false}) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON", "exists": false})
return return
} }
p := Open(json.Page) p := s.Open(json.Page)
if len(p.Text.GetCurrent()) > 0 { if len(p.Text.GetCurrent()) > 0 {
c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " found", "exists": true}) c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " found", "exists": true})
} else { } else {
@ -537,7 +571,7 @@ func handlePageExists(c *gin.Context) {
} }
func handlePageUpdate(c *gin.Context) { func (s *Site) handlePageUpdate(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
NewText string `json:"new_text"` NewText string `json:"new_text"`
@ -549,7 +583,7 @@ func handlePageUpdate(c *gin.Context) {
var json QueryJSON var json QueryJSON
err := c.BindJSON(&json) err := c.BindJSON(&json)
if err != nil { if err != nil {
log.Trace(err.Error()) s.Logger.Trace(err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
return return
} }
@ -561,8 +595,8 @@ func handlePageUpdate(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"})
return return
} }
log.Trace("Update: %v", json) s.Logger.Trace("Update: %v", json)
p := Open(json.Page) p := s.Open(json.Page)
var ( var (
message string message string
sinceLastEdit = time.Since(p.LastEditTime()) sinceLastEdit = time.Since(p.LastEditTime())
@ -592,14 +626,14 @@ func handlePageUpdate(c *gin.Context) {
p.Save() p.Save()
message = "Saved" message = "Saved"
if p.IsPublished { if p.IsPublished {
needSitemapUpdate = true s.sitemapUpToDate = false
} }
success = true success = true
} }
c.JSON(http.StatusOK, gin.H{"success": success, "message": message, "unix_time": time.Now().Unix()}) c.JSON(http.StatusOK, gin.H{"success": success, "message": message, "unix_time": time.Now().Unix()})
} }
func handlePrime(c *gin.Context) { func (s *Site) handlePrime(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
} }
@ -608,8 +642,8 @@ func handlePrime(c *gin.Context) {
c.String(http.StatusBadRequest, "Problem binding keys") c.String(http.StatusBadRequest, "Problem binding keys")
return return
} }
log.Trace("Update: %v", json) s.Logger.Trace("Update: %v", json)
p := Open(json.Page) p := s.Open(json.Page)
if pageIsLocked(p, c) { if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return return
@ -622,7 +656,7 @@ func handlePrime(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"}) c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"})
} }
func handleLock(c *gin.Context) { func (s *Site) handleLock(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
Passphrase string `json:"passphrase"` Passphrase string `json:"passphrase"`
@ -633,10 +667,10 @@ func handleLock(c *gin.Context) {
c.String(http.StatusBadRequest, "Problem binding keys") c.String(http.StatusBadRequest, "Problem binding keys")
return return
} }
p := Open(json.Page) p := s.Open(json.Page)
if defaultLock != "" && p.IsNew() { if s.defaultLock() != "" && p.IsNew() {
p.IsLocked = true // IsLocked was replaced by variable wrt Context p.IsLocked = true // IsLocked was replaced by variable wrt Context
p.PassphraseToUnlock = defaultLock p.PassphraseToUnlock = s.defaultLock()
} }
if p.IsEncrypted { if p.IsEncrypted {
@ -679,7 +713,7 @@ func handleLock(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "message": message}) c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
} }
func handlePublish(c *gin.Context) { func (s *Site) handlePublish(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
Publish bool `json:"publish"` Publish bool `json:"publish"`
@ -690,7 +724,7 @@ func handlePublish(c *gin.Context) {
c.String(http.StatusBadRequest, "Problem binding keys") c.String(http.StatusBadRequest, "Problem binding keys")
return return
} }
p := Open(json.Page) p := s.Open(json.Page)
p.IsPublished = json.Publish p.IsPublished = json.Publish
p.Save() p.Save()
message := "Published" message := "Published"
@ -700,8 +734,8 @@ func handlePublish(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "message": message}) c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
} }
func handleUpload(c *gin.Context) { func (s *Site) handleUpload(c *gin.Context) {
if !allowFileUploads { if !s.Fileuploads {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server")) c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
return return
} }
@ -722,7 +756,7 @@ func handleUpload(c *gin.Context) {
newName := "sha256-" + encodeBytesToBase32(h.Sum(nil)) newName := "sha256-" + encodeBytesToBase32(h.Sum(nil))
// Replaces any existing version, but sha256 collisions are rare as anything. // Replaces any existing version, but sha256 collisions are rare as anything.
outfile, err := os.Create(path.Join(pathToData, newName+".upload")) outfile, err := os.Create(path.Join(s.PathToData, newName+".upload"))
if err != nil { if err != nil {
c.AbortWithError(http.StatusInternalServerError, err) c.AbortWithError(http.StatusInternalServerError, err)
return return
@ -739,7 +773,7 @@ func handleUpload(c *gin.Context) {
return return
} }
func handleEncrypt(c *gin.Context) { func (s *Site) handleEncrypt(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
Passphrase string `json:"passphrase"` Passphrase string `json:"passphrase"`
@ -750,12 +784,12 @@ func handleEncrypt(c *gin.Context) {
c.String(http.StatusBadRequest, "Problem binding keys") c.String(http.StatusBadRequest, "Problem binding keys")
return return
} }
p := Open(json.Page) p := s.Open(json.Page)
if pageIsLocked(p, c) { if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return return
} }
q := Open(json.Page) q := s.Open(json.Page)
var message string var message string
if p.IsEncrypted { if p.IsEncrypted {
decrypted, err2 := encrypt.DecryptString(p.Text.GetCurrent(), json.Passphrase) decrypted, err2 := encrypt.DecryptString(p.Text.GetCurrent(), json.Passphrase)
@ -764,7 +798,7 @@ func handleEncrypt(c *gin.Context) {
return return
} }
q.Erase() q.Erase()
q = Open(json.Page) q = s.Open(json.Page)
q.Update(decrypted) q.Update(decrypted)
q.IsEncrypted = false q.IsEncrypted = false
q.IsLocked = p.IsLocked q.IsLocked = p.IsLocked
@ -774,7 +808,7 @@ func handleEncrypt(c *gin.Context) {
currentText := p.Text.GetCurrent() currentText := p.Text.GetCurrent()
encrypted, _ := encrypt.EncryptString(currentText, json.Passphrase) encrypted, _ := encrypt.EncryptString(currentText, json.Passphrase)
q.Erase() q.Erase()
q = Open(json.Page) q = s.Open(json.Page)
q.Update(encrypted) q.Update(encrypted)
q.IsEncrypted = true q.IsEncrypted = true
q.IsLocked = p.IsLocked q.IsLocked = p.IsLocked
@ -785,11 +819,11 @@ func handleEncrypt(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "message": message}) c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
} }
func deleteListItem(c *gin.Context) { func (s *Site) deleteListItem(c *gin.Context) {
lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None")) lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname") page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname")
if err == nil { if err == nil {
p := Open(page) p := s.Open(page)
_, listItems := reorderList(p.Text.GetCurrent()) _, listItems := reorderList(p.Text.GetCurrent())
newText := p.Text.GetCurrent() newText := p.Text.GetCurrent()
@ -820,7 +854,7 @@ func deleteListItem(c *gin.Context) {
} }
} }
func handleClearOldListItems(c *gin.Context) { func (s *Site) handleClearOldListItems(c *gin.Context) {
type QueryJSON struct { type QueryJSON struct {
Page string `json:"page"` Page string `json:"page"`
} }
@ -830,7 +864,7 @@ func handleClearOldListItems(c *gin.Context) {
c.String(http.StatusBadRequest, "Problem binding keys") c.String(http.StatusBadRequest, "Problem binding keys")
return return
} }
p := Open(json.Page) p := s.Open(json.Page)
if p.IsEncrypted { if p.IsEncrypted {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
return return

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"html/template" "html/template"

View File

@ -1,22 +1,25 @@
package main package server
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path" "path"
"github.com/jcelliott/lumber"
) )
func migrate(pathToOldData, pathToData string) error { func Migrate(pathToOldData, pathToData string, logger *lumber.ConsoleLogger) error {
files, err := ioutil.ReadDir(pathToOldData) files, err := ioutil.ReadDir(pathToOldData)
if len(files) == 0 { if len(files) == 0 {
return err return err
} }
s := Site{PathToData: pathToData, Logger: lumber.NewConsoleLogger(lumber.TRACE)}
for _, f := range files { for _, f := range files {
if f.Mode().IsDir() { if f.Mode().IsDir() {
continue continue
} }
fmt.Printf("Migrating %s", f.Name()) fmt.Printf("Migrating %s", f.Name())
p := Open(f.Name()) p := s.Open(f.Name())
bData, err := ioutil.ReadFile(path.Join(pathToOldData, f.Name())) bData, err := ioutil.ReadFile(path.Join(pathToOldData, f.Name()))
if err != nil { if err != nil {
return err return err

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"encoding/json" "encoding/json"
@ -9,7 +9,6 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
"github.com/schollz/versionedtext" "github.com/schollz/versionedtext"
@ -17,6 +16,8 @@ import (
// Page is the basic struct // Page is the basic struct
type Page struct { type Page struct {
Site *Site
Name string Name string
Text versionedtext.VersionedText Text versionedtext.VersionedText
Meta string Meta string
@ -37,12 +38,13 @@ func (p Page) LastEditUnixTime() int64 {
return p.Text.LastEditTime() / 1000000000 return p.Text.LastEditTime() / 1000000000
} }
func Open(name string) (p *Page) { func (s *Site) Open(name string) (p *Page) {
p = new(Page) p = new(Page)
p.Site = s
p.Name = name p.Name = name
p.Text = versionedtext.NewVersionedText("") p.Text = versionedtext.NewVersionedText("")
p.Render() p.Render()
bJSON, err := ioutil.ReadFile(path.Join(pathToData, encodeToBase32(strings.ToLower(name))+".json")) bJSON, err := ioutil.ReadFile(path.Join(s.PathToData, encodeToBase32(strings.ToLower(name))+".json"))
if err != nil { if err != nil {
return return
} }
@ -88,12 +90,12 @@ func (d DirectoryEntry) Sys() interface{} {
return nil return nil
} }
func DirectoryList() []os.FileInfo { func (s *Site) DirectoryList() []os.FileInfo {
files, _ := ioutil.ReadDir(pathToData) files, _ := ioutil.ReadDir(s.PathToData)
entries := make([]os.FileInfo, len(files)) entries := make([]os.FileInfo, len(files))
for i, f := range files { for i, f := range files {
name := DecodeFileName(f.Name()) name := DecodeFileName(f.Name())
p := Open(name) p := s.Open(name)
entries[i] = DirectoryEntry{ entries[i] = DirectoryEntry{
Path: name, Path: name,
Length: len(p.Text.GetCurrent()), Length: len(p.Text.GetCurrent()),
@ -109,8 +111,8 @@ type UploadEntry struct {
os.FileInfo os.FileInfo
} }
func UploadList() ([]os.FileInfo, error) { func (s *Site) UploadList() ([]os.FileInfo, error) {
paths, err := filepath.Glob(path.Join(pathToData, "sha256*")) paths, err := filepath.Glob(path.Join(s.PathToData, "sha256*"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -161,21 +163,19 @@ func (p *Page) Render() {
p.RenderedPage = MarkdownToHtml(p.Text.GetCurrent()) p.RenderedPage = MarkdownToHtml(p.Text.GetCurrent())
} }
var saveMut = sync.Mutex{}
func (p *Page) Save() error { func (p *Page) Save() error {
saveMut.Lock() p.Site.saveMut.Lock()
defer saveMut.Unlock() defer p.Site.saveMut.Unlock()
bJSON, err := json.MarshalIndent(p, "", " ") bJSON, err := json.MarshalIndent(p, "", " ")
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(path.Join(pathToData, encodeToBase32(strings.ToLower(p.Name))+".json"), bJSON, 0644) return ioutil.WriteFile(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"), bJSON, 0644)
} }
func (p *Page) ChildPageNames() []string { func (p *Page) ChildPageNames() []string {
prefix := strings.ToLower(p.Name + ": ") prefix := strings.ToLower(p.Name + ": ")
files, err := filepath.Glob(path.Join(pathToData, "*")) files, err := filepath.Glob(path.Join(p.Site.PathToData, "*"))
if err != nil { if err != nil {
panic("Filepath pattern cannot be malformed") panic("Filepath pattern cannot be malformed")
} }
@ -194,12 +194,12 @@ func (p *Page) ChildPageNames() []string {
} }
func (p *Page) IsNew() bool { func (p *Page) IsNew() bool {
return !exists(path.Join(pathToData, encodeToBase32(strings.ToLower(p.Name))+".json")) return !exists(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"))
} }
func (p *Page) Erase() error { func (p *Page) Erase() error {
log.Trace("Erasing " + p.Name) p.Site.Logger.Trace("Erasing " + p.Name)
return os.Remove(path.Join(pathToData, encodeToBase32(strings.ToLower(p.Name))+".json")) return os.Remove(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"))
} }
func (p *Page) Published() bool { func (p *Page) Published() bool {

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"os" "os"
@ -10,24 +10,25 @@ func TestListFiles(t *testing.T) {
pathToData = "testdata" pathToData = "testdata"
os.MkdirAll(pathToData, 0755) os.MkdirAll(pathToData, 0755)
defer os.RemoveAll(pathToData) defer os.RemoveAll(pathToData)
p := Open("testpage") s := Site{PathToData: pathToData}
p := s.Open("testpage")
p.Update("Some data") p.Update("Some data")
p = Open("testpage2") p = s.Open("testpage2")
p.Update("A different bunch of data") p.Update("A different bunch of data")
p = Open("testpage3") p = s.Open("testpage3")
p.Update("Not much else") p.Update("Not much else")
n := DirectoryList() n := s.DirectoryList()
if len(n) != 3 { if len(n) != 3 {
t.Error("Expected three directory entries") t.Error("Expected three directory entries")
t.FailNow() t.FailNow()
} }
if n[0].Name != "testpage" { if n[0].Name() != "testpage" {
t.Error("Expected testpage to be first") t.Error("Expected testpage to be first")
} }
if n[1].Name != "testpage2" { if n[1].Name() != "testpage2" {
t.Error("Expected testpage2 to be second") t.Error("Expected testpage2 to be second")
} }
if n[2].Name != "testpage3" { if n[2].Name() != "testpage3" {
t.Error("Expected testpage3 to be last") t.Error("Expected testpage3 to be last")
} }
} }
@ -36,7 +37,8 @@ func TestGeneral(t *testing.T) {
pathToData = "testdata" pathToData = "testdata"
os.MkdirAll(pathToData, 0755) os.MkdirAll(pathToData, 0755)
defer os.RemoveAll(pathToData) defer os.RemoveAll(pathToData)
p := Open("testpage") s := Site{PathToData: pathToData}
p := s.Open("testpage")
err := p.Update("**bold**") err := p.Update("**bold**")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -50,12 +52,12 @@ func TestGeneral(t *testing.T) {
} }
p.Save() p.Save()
p2 := Open("testpage") p2 := s.Open("testpage")
if strings.TrimSpace(p2.RenderedPage) != "<p><strong>bold</strong> and <em>italic</em></p>" { if strings.TrimSpace(p2.RenderedPage) != "<p><strong>bold</strong> and <em>italic</em></p>" {
t.Errorf("Did not render: '%s'", p2.RenderedPage) t.Errorf("Did not render: '%s'", p2.RenderedPage)
} }
p3 := Open("testpage: childpage") p3 := s.Open("testpage: childpage")
err = p3.Update("**child content**") err = p3.Update("**child content**")
if err != nil { if err != nil {
t.Error(err) t.Error(err)

View File

@ -1,18 +1,16 @@
package main package server
import ( import (
"encoding/base32" "encoding/base32"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"math/rand" "math/rand"
"net"
"net/http" "net/http"
"os" "os"
"path" "path"
"strings" "strings"
"time" "time"
"github.com/jcelliott/lumber"
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
"github.com/shurcooL/github_flavored_markdown" "github.com/shurcooL/github_flavored_markdown"
@ -24,19 +22,12 @@ var adjectives []string
var aboutPageText string var aboutPageText string
var allowInsecureHtml bool var allowInsecureHtml bool
var log *lumber.ConsoleLogger
func init() { func init() {
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
animalsText, _ := Asset("static/text/animals") animalsText, _ := Asset("static/text/animals")
animals = strings.Split(string(animalsText), ",") animals = strings.Split(string(animalsText), ",")
adjectivesText, _ := Asset("static/text/adjectives") adjectivesText, _ := Asset("static/text/adjectives")
adjectives = strings.Split(string(adjectivesText), "\n") adjectives = strings.Split(string(adjectivesText), "\n")
log = lumber.NewConsoleLogger(lumber.TRACE)
}
func turnOffDebugger() {
log = lumber.NewConsoleLogger(lumber.WARN)
} }
func randomAnimal() string { func randomAnimal() string {
@ -94,8 +85,8 @@ func contentType(filename string) string {
return "text/html" return "text/html"
} }
func sniffContentType(name string) (string, error) { func (s *Site) sniffContentType(name string) (string, error) {
file, err := os.Open(path.Join(pathToData, name)) file, err := os.Open(path.Join(s.PathToData, name))
if err != nil { if err != nil {
return "", err return "", err
@ -113,11 +104,6 @@ func sniffContentType(name string) (string, error) {
return http.DetectContentType(buffer), nil return http.DetectContentType(buffer), nil
} }
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
log.Debug("%s took %s", name, elapsed)
}
var src = rand.NewSource(time.Now().UnixNano()) var src = rand.NewSource(time.Now().UnixNano())
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
@ -146,24 +132,6 @@ func RandStringBytesMaskImprSrc(n int) string {
return string(b) return string(b)
} }
// GetLocalIP returns the local ip address
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
bestIP := ""
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return bestIP
}
// HashPassword generates a bcrypt hash of the password using work factor 14. // HashPassword generates a bcrypt hash of the password using work factor 14.
// https://github.com/gtank/cryptopasta/blob/master/hash.go // https://github.com/gtank/cryptopasta/blob/master/hash.go
func HashPassword(password string) string { func HashPassword(password string) string {
@ -185,13 +153,7 @@ func CheckPasswordHash(password, hashedString string) error {
// exists returns whether the given file or directory exists or not // exists returns whether the given file or directory exists or not
func exists(path string) bool { func exists(path string) bool {
_, err := os.Stat(path) _, err := os.Stat(path)
if err == nil { return !os.IsNotExist(err)
return true
}
if os.IsNotExist(err) {
return false
}
return true
} }
func MarkdownToHtml(s string) string { func MarkdownToHtml(s string) string {

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"testing" "testing"
@ -23,7 +23,6 @@ func TestReverseList(t *testing.T) {
func TestHashing(t *testing.T) { func TestHashing(t *testing.T) {
p := HashPassword("1234") p := HashPassword("1234")
log.Debug(p)
err := CheckPasswordHash("1234", p) err := CheckPasswordHash("1234", p)
if err != nil { if err != nil {
t.Errorf("Should be correct password") t.Errorf("Should be correct password")