diff --git a/Makefile b/Makefile index 72c494d..28e5909 100644 --- a/Makefile +++ b/Makefile @@ -5,49 +5,48 @@ VERSION=$(shell git describe) LDFLAGS=-ldflags "-X main.version=${VERSION}" .PHONY: build -build: bindata.go +build: server/bindata.go go build ${LDFLAGS} STATICFILES := $(wildcard static/*) TEMPLATES := $(wildcard templates/*) -bindata.go: $(STATICFILES) $(TEMPLATES) - go-bindata -tags '!debug' static/... templates/... +server/bindata.go: $(STATICFILES) $(TEMPLATES) + go-bindata -tags '!debug' -o server/bindata.go static/... templates/... go fmt -bindata-debug.go: $(STATICFILES) $(TEMPLATES) - go-bindata -tags 'debug' -o bindata-debug.go -debug static/... templates/... +server/bindata-debug.go: $(STATICFILES) $(TEMPLATES) + go-bindata -tags 'debug' -o server/bindata-debug.go -debug static/... templates/... go fmt - .PHONY: devel -devel: bindata-debug.go +devel: server/bindata-debug.go go build -tags debug .PHONY: quick -quick: bindata.go +quick: server/bindata.go go build .PHONY: linuxarm -linuxarm: bindata.go +linuxarm: server/bindata.go env GOOS=linux GOARCH=arm go build ${LDFLAGS} -o dist/cowyo_linux_arm #cd dist && upx --brute cowyo_linux_arm .PHONY: linux32 -linux32: bindata.go +linux32: server/bindata.go env GOOS=linux GOARCH=386 go build ${LDFLAGS} -o dist/cowyo_linux_32bit #cd dist && upx --brute cowyo_linux_32bit .PHONY: linux64 -linux64: bindata.go +linux64: server/bindata.go env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_linux_amd64 .PHONY: windows -windows: bindata.go +windows: server/bindata.go env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_windows_amd64.exe #cd dist && upx --brute cowyo_windows_amd64.exe .PHONY: osx -osx: bindata.go +osx: server/bindata.go env GOOS=darwin GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_osx_amd64 #cd dist && upx --brute cowyo_osx_amd64 diff --git a/cmd/herdyo/herdyo.go b/cmd/herdyo/herdyo.go new file mode 100644 index 0000000..0f175a9 --- /dev/null +++ b/cmd/herdyo/herdyo.go @@ -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) + } + }))) +} diff --git a/main.go b/main.go index 5cf4831..d40e79e 100755 --- a/main.go +++ b/main.go @@ -2,10 +2,14 @@ package main import ( "fmt" + "net" "os" "time" - "gopkg.in/urfave/cli.v1" + "github.com/jcelliott/lumber" + "github.com/schollz/cowyo/server" + + cli "gopkg.in/urfave/cli.v1" ) var version string @@ -19,9 +23,7 @@ func main() { app.Version = version app.Compiled = time.Now() app.Action = func(c *cli.Context) error { - if !c.GlobalBool("debug") { - turnOffDebugger() - } + pathToData = c.GlobalString("data") os.MkdirAll(pathToData, 0755) 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")) } - allowFileUploads = c.GlobalBool("allow-file-uploads") - maxUploadMB = c.GlobalUint("max-upload-mb") - - serve( + server.Serve( + pathToData, c.GlobalString("host"), c.GlobalString("port"), c.GlobalString("cert"), @@ -58,6 +58,9 @@ func main() { c.GlobalString("access-code"), c.GlobalBool("allow-insecure-markup"), hotTemplateReloading, + c.GlobalBool("allow-file-uploads"), + c.GlobalUint("max-upload-mb"), + logger(c.GlobalBool("debug")), ) return nil } @@ -150,9 +153,6 @@ func main() { Aliases: []string{"m"}, Usage: "migrate from the old cowyo", Action: func(c *cli.Context) error { - if !c.GlobalBool("debug") { - turnOffDebugger() - } pathToData = c.GlobalString("data") pathToOldData := c.GlobalString("olddata") if len(pathToOldData) == 0 { @@ -164,12 +164,43 @@ func main() { fmt.Printf("Can not find '%s', does it exist?", pathToOldData) return nil } - migrate(pathToOldData, pathToData) + server.Migrate(pathToOldData, pathToData, logger(c.GlobalBool("debug"))) return nil }, }, } 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) } diff --git a/bindata-debug.go b/server/bindata-debug.go similarity index 99% rename from bindata-debug.go rename to server/bindata-debug.go index f21fd75..5288697 100644 --- a/bindata-debug.go +++ b/server/bindata-debug.go @@ -51,7 +51,7 @@ // +build debug -package main +package server import ( "fmt" diff --git a/bindata.go b/server/bindata.go similarity index 99% rename from bindata.go rename to server/bindata.go index c3851b1..b6b50be 100644 --- a/bindata.go +++ b/server/bindata.go @@ -51,7 +51,7 @@ // +build !debug -package main +package server import ( "bytes" diff --git a/debug.go b/server/debug.go similarity index 80% rename from debug.go rename to server/debug.go index 28dead5..900744c 100644 --- a/debug.go +++ b/server/debug.go @@ -1,6 +1,6 @@ // +build debug -package main +package server func init() { hotTemplateReloading = true diff --git a/handlers.go b/server/handlers.go similarity index 81% rename from handlers.go rename to server/handlers.go index e490e56..4b83b30 100755 --- a/handlers.go +++ b/server/handlers.go @@ -1,4 +1,4 @@ -package main +package server import ( "crypto/sha256" @@ -12,27 +12,47 @@ import ( "path" "strconv" "strings" + "sync" "time" - // "github.com/gin-contrib/static" secretRequired "github.com/danielheath/gin-teeny-security" "github.com/gin-contrib/multitemplate" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" + "github.com/jcelliott/lumber" "github.com/schollz/cowyo/encrypt" ) const minutesToUnlock = 10.0 -var customCSS []byte -var defaultLock string -var debounceTime int -var diaryMode bool -var allowFileUploads bool -var maxUploadMB uint -var needSitemapUpdate = true +type Site struct { + PathToData string + Css []byte + DefaultPage string + DefaultPassword string + Debounce int + 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, port, crt_path, @@ -47,31 +67,73 @@ func serve( secretCode string, allowInsecure 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) } else { gin.SetMode(gin.ReleaseMode) } router := gin.Default() - router.SetFuncMap(template.FuncMap{ - "sniffContentType": sniffContentType, + "sniffContentType": s.sniffContentType, }) - if hotTemplateReloading { + if s.HotTemplateReloading { router.LoadHTMLGlob("templates/*.tmpl") } else { - router.HTMLRender = loadTemplates("index.tmpl") + router.HTMLRender = s.loadTemplates("index.tmpl") } - store := sessions.NewCookieStore([]byte(secret)) - router.Use(sessions.Sessions("mysession", store)) - if secretCode != "" { + router.Use(sessions.Sessions(s.PathToData, s.SessionStore)) + if s.SecretCode != "" { cfg := &secretRequired.Config{ - Secret: secretCode, + Secret: s.SecretCode, Path: "/login/", RequireAuth: func(c *gin.Context) bool { page := c.Param("page") @@ -82,7 +144,7 @@ func serve( } if page != "" && cmd == "/read" { - p := Open(page) + p := s.Open(page) fmt.Printf("p: '%+v'\n", p) if p != nil && p.IsPublished { return false // Published pages don't require auth. @@ -96,67 +158,39 @@ func serve( // router.Use(static.Serve("/static/", static.LocalFile("./static", true))) router.GET("/", func(c *gin.Context) { - if defaultPage != "" { - c.Redirect(302, "/"+defaultPage+"/read") + if s.DefaultPage != "" { + c.Redirect(302, "/"+s.DefaultPage+"/read") } else { c.Redirect(302, "/"+randomAlliterateCombo()) } }) - router.POST("/uploads", handleUpload) + router.POST("/uploads", s.handleUpload) router.GET("/:page", func(c *gin.Context) { page := c.Param("page") c.Redirect(302, "/"+page+"/") }) - router.GET("/:page/*command", handlePageRequest) - router.POST("/update", handlePageUpdate) - router.POST("/relinquish", handlePageRelinquish) // relinquish returns the page no matter what (and destroys if nessecary) - router.POST("/exists", handlePageExists) - router.POST("/prime", handlePrime) - router.POST("/lock", handleLock) - router.POST("/publish", handlePublish) - router.POST("/encrypt", handleEncrypt) - router.DELETE("/oldlist", handleClearOldListItems) - router.DELETE("/listitem", deleteListItem) + router.GET("/:page/*command", s.handlePageRequest) + router.POST("/update", s.handlePageUpdate) + 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) + router.POST("/lock", s.handleLock) + 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 - go 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 + go s.thread_SiteMap() // Allow iframe/scripts in markup? - allowInsecureHtml = allowInsecure - - if TLS { - http.ListenAndServeTLS(host+":"+port, crt_path, key_path, router) - } else { - panic(router.Run(host + ":" + port)) - } + allowInsecureHtml = s.AllowInsecure + return router } -func loadTemplates(list ...string) multitemplate.Render { +func (s *Site) loadTemplates(list ...string) multitemplate.Render { r := multitemplate.New() for _, x := range list { @@ -166,7 +200,7 @@ func loadTemplates(list ...string) multitemplate.Render { } tmplMessage, err := template.New(x).Funcs(template.FuncMap{ - "sniffContentType": sniffContentType, + "sniffContentType": s.sniffContentType, }).Parse(string(templateString)) if err != nil { panic(err) @@ -185,14 +219,14 @@ func pageIsLocked(p *Page, c *gin.Context) bool { return !unlocked } -func handlePageRelinquish(c *gin.Context) { +func (s *Site) handlePageRelinquish(c *gin.Context) { type QueryJSON struct { Page string `json:"page"` } var json QueryJSON err := c.BindJSON(&json) if err != nil { - log.Trace(err.Error()) + s.Logger.Trace(err.Error()) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"}) return } @@ -201,7 +235,7 @@ func handlePageRelinquish(c *gin.Context) { return } message := "Relinquished" - p := Open(json.Page) + p := s.Open(json.Page) name := p.Meta if name == "" { name = json.Page @@ -239,26 +273,26 @@ func getSetSessionID(c *gin.Context) (sid string) { return sid } -func thread_SiteMap() { +func (s *Site) thread_SiteMap() { for { - if needSitemapUpdate { - log.Info("Generating sitemap...") - needSitemapUpdate = false - ioutil.WriteFile(path.Join(pathToData, "sitemap.xml"), []byte(generateSiteMap()), 0644) - log.Info("..finished generating sitemap") + if !s.sitemapUpToDate { + s.Logger.Info("Generating sitemap...") + s.sitemapUpToDate = true + ioutil.WriteFile(path.Join(s.PathToData, "sitemap.xml"), []byte(s.generateSiteMap()), 0644) + s.Logger.Info("..finished generating sitemap") } time.Sleep(time.Second) } } -func generateSiteMap() (sitemap string) { - files, _ := ioutil.ReadDir(pathToData) +func (s *Site) generateSiteMap() (sitemap string) { + files, _ := ioutil.ReadDir(s.PathToData) lastEdited := make([]string, len(files)) names := make([]string, len(files)) i := 0 for _, f := range files { names[i] = DecodeFileName(f.Name()) - p := Open(names[i]) + p := s.Open(names[i]) if p.IsPublished { lastEdited[i] = time.Unix(p.Text.LastEditTime()/1000000000, 0).Format("2006-01-02") i++ @@ -281,12 +315,12 @@ func generateSiteMap() (sitemap string) { return } -func handlePageRequest(c *gin.Context) { +func (s *Site) handlePageRequest(c *gin.Context) { page := c.Param("page") command := c.Param("command") 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 { c.Data(http.StatusInternalServerError, contentType("sitemap.xml"), []byte("")) } else { @@ -302,7 +336,7 @@ func handlePageRequest(c *gin.Context) { filename := page + command var data []byte if filename == "static/css/custom.css" { - data = customCSS + data = s.Css } else { var errAssset error data, errAssset = Asset(filename) @@ -314,7 +348,7 @@ func handlePageRequest(c *gin.Context) { return } else if page == "uploads" { if len(command) == 0 || command == "/" || command == "/edit" { - if !allowFileUploads { + if !s.Fileuploads { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server")) return } @@ -323,7 +357,7 @@ func handlePageRequest(c *gin.Context) { if !strings.HasSuffix(command, ".upload") { command = command + ".upload" } - pathname := path.Join(pathToData, command) + pathname := path.Join(s.PathToData, command) if allowInsecureHtml { c.Header( @@ -343,7 +377,7 @@ func handlePageRequest(c *gin.Context) { } } - p := Open(page) + p := s.Open(page) if len(command) < 2 { if p.IsPublished { c.Redirect(302, "/"+page+"/read") @@ -354,9 +388,9 @@ func handlePageRequest(c *gin.Context) { } // use the default lock - if defaultLock != "" && p.IsNew() { + if s.defaultLock() != "" && p.IsNew() { p.IsLocked = true - p.PassphraseToUnlock = defaultLock + p.PassphraseToUnlock = s.defaultLock() } version := c.DefaultQuery("version", "ajksldfjl") @@ -430,12 +464,12 @@ func handlePageRequest(c *gin.Context) { var DirectoryEntries []os.FileInfo if page == "ls" { command = "/view" - DirectoryEntries = DirectoryList() + DirectoryEntries = s.DirectoryList() } if page == "uploads" { command = "/view" var err error - DirectoryEntries, err = UploadList() + DirectoryEntries, err = s.UploadList() if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return @@ -474,14 +508,14 @@ func handlePageRequest(c *gin.Context) { "HasDotInName": strings.Contains(page, "."), "RecentlyEdited": getRecentlyEdited(page, c), "IsPublished": p.IsPublished, - "CustomCSS": len(customCSS) > 0, - "Debounce": debounceTime, - "DiaryMode": diaryMode, + "CustomCSS": len(s.Css) > 0, + "Debounce": s.Debounce, + "DiaryMode": s.Diary, "Date": time.Now().Format("2006-01-02"), "UnixTime": time.Now().Unix(), "ChildPageNames": p.ChildPageNames(), - "AllowFileUploads": allowFileUploads, - "MaxUploadMB": maxUploadMB, + "AllowFileUploads": s.Fileuploads, + "MaxUploadMB": s.MaxUploadSize, }) } @@ -517,18 +551,18 @@ func getRecentlyEdited(title string, c *gin.Context) []string { return editedThingsWithoutCurrent[:i] } -func handlePageExists(c *gin.Context) { +func (s *Site) handlePageExists(c *gin.Context) { type QueryJSON struct { Page string `json:"page"` } var json QueryJSON err := c.BindJSON(&json) 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}) return } - p := Open(json.Page) + p := s.Open(json.Page) if len(p.Text.GetCurrent()) > 0 { c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " found", "exists": true}) } 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 { Page string `json:"page"` NewText string `json:"new_text"` @@ -549,7 +583,7 @@ func handlePageUpdate(c *gin.Context) { var json QueryJSON err := c.BindJSON(&json) if err != nil { - log.Trace(err.Error()) + s.Logger.Trace(err.Error()) c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"}) return } @@ -561,8 +595,8 @@ func handlePageUpdate(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"}) return } - log.Trace("Update: %v", json) - p := Open(json.Page) + s.Logger.Trace("Update: %v", json) + p := s.Open(json.Page) var ( message string sinceLastEdit = time.Since(p.LastEditTime()) @@ -592,14 +626,14 @@ func handlePageUpdate(c *gin.Context) { p.Save() message = "Saved" if p.IsPublished { - needSitemapUpdate = true + s.sitemapUpToDate = false } success = true } 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 { Page string `json:"page"` } @@ -608,8 +642,8 @@ func handlePrime(c *gin.Context) { c.String(http.StatusBadRequest, "Problem binding keys") return } - log.Trace("Update: %v", json) - p := Open(json.Page) + s.Logger.Trace("Update: %v", json) + p := s.Open(json.Page) if pageIsLocked(p, c) { c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"}) return @@ -622,7 +656,7 @@ func handlePrime(c *gin.Context) { 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 { Page string `json:"page"` Passphrase string `json:"passphrase"` @@ -633,10 +667,10 @@ func handleLock(c *gin.Context) { c.String(http.StatusBadRequest, "Problem binding keys") return } - p := Open(json.Page) - if defaultLock != "" && p.IsNew() { + p := s.Open(json.Page) + if s.defaultLock() != "" && p.IsNew() { p.IsLocked = true // IsLocked was replaced by variable wrt Context - p.PassphraseToUnlock = defaultLock + p.PassphraseToUnlock = s.defaultLock() } if p.IsEncrypted { @@ -679,7 +713,7 @@ func handleLock(c *gin.Context) { 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 { Page string `json:"page"` Publish bool `json:"publish"` @@ -690,7 +724,7 @@ func handlePublish(c *gin.Context) { c.String(http.StatusBadRequest, "Problem binding keys") return } - p := Open(json.Page) + p := s.Open(json.Page) p.IsPublished = json.Publish p.Save() message := "Published" @@ -700,8 +734,8 @@ func handlePublish(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true, "message": message}) } -func handleUpload(c *gin.Context) { - if !allowFileUploads { +func (s *Site) handleUpload(c *gin.Context) { + if !s.Fileuploads { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server")) return } @@ -722,7 +756,7 @@ func handleUpload(c *gin.Context) { newName := "sha256-" + encodeBytesToBase32(h.Sum(nil)) // 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 { c.AbortWithError(http.StatusInternalServerError, err) return @@ -739,7 +773,7 @@ func handleUpload(c *gin.Context) { return } -func handleEncrypt(c *gin.Context) { +func (s *Site) handleEncrypt(c *gin.Context) { type QueryJSON struct { Page string `json:"page"` Passphrase string `json:"passphrase"` @@ -750,12 +784,12 @@ func handleEncrypt(c *gin.Context) { c.String(http.StatusBadRequest, "Problem binding keys") return } - p := Open(json.Page) + p := s.Open(json.Page) if pageIsLocked(p, c) { c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"}) return } - q := Open(json.Page) + q := s.Open(json.Page) var message string if p.IsEncrypted { decrypted, err2 := encrypt.DecryptString(p.Text.GetCurrent(), json.Passphrase) @@ -764,7 +798,7 @@ func handleEncrypt(c *gin.Context) { return } q.Erase() - q = Open(json.Page) + q = s.Open(json.Page) q.Update(decrypted) q.IsEncrypted = false q.IsLocked = p.IsLocked @@ -774,7 +808,7 @@ func handleEncrypt(c *gin.Context) { currentText := p.Text.GetCurrent() encrypted, _ := encrypt.EncryptString(currentText, json.Passphrase) q.Erase() - q = Open(json.Page) + q = s.Open(json.Page) q.Update(encrypted) q.IsEncrypted = true q.IsLocked = p.IsLocked @@ -785,11 +819,11 @@ func handleEncrypt(c *gin.Context) { 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")) page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname") if err == nil { - p := Open(page) + p := s.Open(page) _, listItems := reorderList(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 { Page string `json:"page"` } @@ -830,7 +864,7 @@ func handleClearOldListItems(c *gin.Context) { c.String(http.StatusBadRequest, "Problem binding keys") return } - p := Open(json.Page) + p := s.Open(json.Page) if p.IsEncrypted { c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"}) return diff --git a/listify.go b/server/listify.go similarity index 99% rename from listify.go rename to server/listify.go index 5fb2dfd..8c76173 100644 --- a/listify.go +++ b/server/listify.go @@ -1,4 +1,4 @@ -package main +package server import ( "html/template" diff --git a/migrate.go b/server/migrate.go similarity index 66% rename from migrate.go rename to server/migrate.go index b91f355..b01dc9d 100755 --- a/migrate.go +++ b/server/migrate.go @@ -1,22 +1,25 @@ -package main +package server import ( "fmt" "io/ioutil" "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) if len(files) == 0 { return err } + s := Site{PathToData: pathToData, Logger: lumber.NewConsoleLogger(lumber.TRACE)} for _, f := range files { if f.Mode().IsDir() { continue } fmt.Printf("Migrating %s", f.Name()) - p := Open(f.Name()) + p := s.Open(f.Name()) bData, err := ioutil.ReadFile(path.Join(pathToOldData, f.Name())) if err != nil { return err diff --git a/page.go b/server/page.go similarity index 81% rename from page.go rename to server/page.go index 1f265bd..94719b5 100755 --- a/page.go +++ b/server/page.go @@ -1,4 +1,4 @@ -package main +package server import ( "encoding/json" @@ -9,7 +9,6 @@ import ( "regexp" "sort" "strings" - "sync" "time" "github.com/schollz/versionedtext" @@ -17,6 +16,8 @@ import ( // Page is the basic struct type Page struct { + Site *Site + Name string Text versionedtext.VersionedText Meta string @@ -37,12 +38,13 @@ func (p Page) LastEditUnixTime() int64 { return p.Text.LastEditTime() / 1000000000 } -func Open(name string) (p *Page) { +func (s *Site) Open(name string) (p *Page) { p = new(Page) + p.Site = s p.Name = name p.Text = versionedtext.NewVersionedText("") 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 { return } @@ -88,12 +90,12 @@ func (d DirectoryEntry) Sys() interface{} { return nil } -func DirectoryList() []os.FileInfo { - files, _ := ioutil.ReadDir(pathToData) +func (s *Site) DirectoryList() []os.FileInfo { + files, _ := ioutil.ReadDir(s.PathToData) entries := make([]os.FileInfo, len(files)) for i, f := range files { name := DecodeFileName(f.Name()) - p := Open(name) + p := s.Open(name) entries[i] = DirectoryEntry{ Path: name, Length: len(p.Text.GetCurrent()), @@ -109,8 +111,8 @@ type UploadEntry struct { os.FileInfo } -func UploadList() ([]os.FileInfo, error) { - paths, err := filepath.Glob(path.Join(pathToData, "sha256*")) +func (s *Site) UploadList() ([]os.FileInfo, error) { + paths, err := filepath.Glob(path.Join(s.PathToData, "sha256*")) if err != nil { return nil, err } @@ -161,21 +163,19 @@ func (p *Page) Render() { p.RenderedPage = MarkdownToHtml(p.Text.GetCurrent()) } -var saveMut = sync.Mutex{} - func (p *Page) Save() error { - saveMut.Lock() - defer saveMut.Unlock() + p.Site.saveMut.Lock() + defer p.Site.saveMut.Unlock() bJSON, err := json.MarshalIndent(p, "", " ") if err != nil { 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 { prefix := strings.ToLower(p.Name + ": ") - files, err := filepath.Glob(path.Join(pathToData, "*")) + files, err := filepath.Glob(path.Join(p.Site.PathToData, "*")) if err != nil { panic("Filepath pattern cannot be malformed") } @@ -194,12 +194,12 @@ func (p *Page) ChildPageNames() []string { } 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 { - log.Trace("Erasing " + p.Name) - return os.Remove(path.Join(pathToData, encodeToBase32(strings.ToLower(p.Name))+".json")) + p.Site.Logger.Trace("Erasing " + p.Name) + return os.Remove(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json")) } func (p *Page) Published() bool { diff --git a/page_test.go b/server/page_test.go similarity index 78% rename from page_test.go rename to server/page_test.go index 1816e55..abccea0 100755 --- a/page_test.go +++ b/server/page_test.go @@ -1,4 +1,4 @@ -package main +package server import ( "os" @@ -10,24 +10,25 @@ func TestListFiles(t *testing.T) { pathToData = "testdata" os.MkdirAll(pathToData, 0755) defer os.RemoveAll(pathToData) - p := Open("testpage") + s := Site{PathToData: pathToData} + p := s.Open("testpage") p.Update("Some data") - p = Open("testpage2") + p = s.Open("testpage2") p.Update("A different bunch of data") - p = Open("testpage3") + p = s.Open("testpage3") p.Update("Not much else") - n := DirectoryList() + n := s.DirectoryList() if len(n) != 3 { t.Error("Expected three directory entries") t.FailNow() } - if n[0].Name != "testpage" { + if n[0].Name() != "testpage" { t.Error("Expected testpage to be first") } - if n[1].Name != "testpage2" { + if n[1].Name() != "testpage2" { t.Error("Expected testpage2 to be second") } - if n[2].Name != "testpage3" { + if n[2].Name() != "testpage3" { t.Error("Expected testpage3 to be last") } } @@ -36,7 +37,8 @@ func TestGeneral(t *testing.T) { pathToData = "testdata" os.MkdirAll(pathToData, 0755) defer os.RemoveAll(pathToData) - p := Open("testpage") + s := Site{PathToData: pathToData} + p := s.Open("testpage") err := p.Update("**bold**") if err != nil { t.Error(err) @@ -50,12 +52,12 @@ func TestGeneral(t *testing.T) { } p.Save() - p2 := Open("testpage") + p2 := s.Open("testpage") if strings.TrimSpace(p2.RenderedPage) != "

bold and italic

" { t.Errorf("Did not render: '%s'", p2.RenderedPage) } - p3 := Open("testpage: childpage") + p3 := s.Open("testpage: childpage") err = p3.Update("**child content**") if err != nil { t.Error(err) diff --git a/utils.go b/server/utils.go similarity index 85% rename from utils.go rename to server/utils.go index 5f50635..3c4bda7 100644 --- a/utils.go +++ b/server/utils.go @@ -1,18 +1,16 @@ -package main +package server import ( "encoding/base32" "encoding/binary" "encoding/hex" "math/rand" - "net" "net/http" "os" "path" "strings" "time" - "github.com/jcelliott/lumber" "github.com/microcosm-cc/bluemonday" "github.com/russross/blackfriday" "github.com/shurcooL/github_flavored_markdown" @@ -24,19 +22,12 @@ var adjectives []string var aboutPageText string var allowInsecureHtml bool -var log *lumber.ConsoleLogger - func init() { rand.Seed(time.Now().Unix()) animalsText, _ := Asset("static/text/animals") animals = strings.Split(string(animalsText), ",") adjectivesText, _ := Asset("static/text/adjectives") adjectives = strings.Split(string(adjectivesText), "\n") - log = lumber.NewConsoleLogger(lumber.TRACE) -} - -func turnOffDebugger() { - log = lumber.NewConsoleLogger(lumber.WARN) } func randomAnimal() string { @@ -94,8 +85,8 @@ func contentType(filename string) string { return "text/html" } -func sniffContentType(name string) (string, error) { - file, err := os.Open(path.Join(pathToData, name)) +func (s *Site) sniffContentType(name string) (string, error) { + file, err := os.Open(path.Join(s.PathToData, name)) if err != nil { return "", err @@ -113,11 +104,6 @@ func sniffContentType(name string) (string, error) { 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()) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -146,24 +132,6 @@ func RandStringBytesMaskImprSrc(n int) string { 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. // https://github.com/gtank/cryptopasta/blob/master/hash.go 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 func exists(path string) bool { _, err := os.Stat(path) - if err == nil { - return true - } - if os.IsNotExist(err) { - return false - } - return true + return !os.IsNotExist(err) } func MarkdownToHtml(s string) string { diff --git a/utils_test.go b/server/utils_test.go similarity index 96% rename from utils_test.go rename to server/utils_test.go index 81d79c2..d52f74d 100755 --- a/utils_test.go +++ b/server/utils_test.go @@ -1,4 +1,4 @@ -package main +package server import ( "testing" @@ -23,7 +23,6 @@ func TestReverseList(t *testing.T) { func TestHashing(t *testing.T) { p := HashPassword("1234") - log.Debug(p) err := CheckPasswordHash("1234", p) if err != nil { t.Errorf("Should be correct password")