diff --git a/main.go b/main.go
new file mode 100755
index 0000000..0f58e9f
--- /dev/null
+++ b/main.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+ "html/template"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ router := gin.Default()
+ router.LoadHTMLGlob("templates/*")
+
+ router.GET("/", func(c *gin.Context) {
+ c.Redirect(302, "/"+randomAlliterateCombo())
+ })
+ router.GET("/:page", func(c *gin.Context) {
+ page := c.Param("page")
+ c.Redirect(302, "/"+page+"/edit")
+ })
+ router.GET("/:page/:command", handlePageRequest)
+ router.POST("/update", handlePageUpdate)
+ router.POST("/prime", handlePrime)
+ router.POST("/lock", handleLock)
+
+ router.Run(":8050")
+}
+
+func handlePageRequest(c *gin.Context) {
+ page := c.Param("page")
+ command := c.Param("command")
+ version := c.DefaultQuery("version", "ajksldfjl")
+ p := Open(page)
+ if p.IsPrimedForSelfDestruct && !p.IsLocked {
+ p.Update("*This page has now self-destructed.*\n\n" + p.Text.GetCurrent())
+ p.Erase()
+ }
+ if command == "erase" && !p.IsLocked {
+ p.Erase()
+ c.Redirect(302, "/"+page+"/edit")
+ }
+ 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 = MarkdownToHtml(rawText)
+ }
+ }
+ c.HTML(http.StatusOK, "index.html", gin.H{
+ "EditPage": command == "edit",
+ "ViewPage": command == "view",
+ "ListPage": command == "list",
+ "HistoryPage": command == "history",
+ "Page": p.Name,
+ "RenderedPage": template.HTML([]byte(rawHTML)),
+ "RawPage": rawText,
+ "Versions": p.Text.GetSnapshots(),
+ "IsLocked": p.IsLocked,
+ "IsEncrypted": p.IsEncrypted,
+ })
+}
+
+func handlePageUpdate(c *gin.Context) {
+ type QueryJSON struct {
+ Page string `json:"page"`
+ NewText string `json:"new_text"`
+ }
+ var json QueryJSON
+ if c.BindJSON(&json) != nil {
+ c.String(http.StatusBadRequest, "Problem binding keys")
+ return
+ }
+ log.Trace("Update: %v", json)
+ p := Open(json.Page)
+ if !p.IsLocked {
+ p.Update(json.NewText)
+ p.Save()
+ c.JSON(http.StatusOK, gin.H{"success": true, "message": "Saved"})
+ } else {
+ c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
+ }
+}
+
+func 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
+ }
+ log.Trace("Update: %v", json)
+ p := Open(json.Page)
+ p.IsPrimedForSelfDestruct = true
+ p.Save()
+ c.JSON(http.StatusOK, gin.H{"success": true})
+}
+
+func 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
+ }
+ p := Open(json.Page)
+ var message string
+ if p.IsLocked {
+ err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
+ if err2 != nil {
+ c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
+ return
+ }
+ p.IsLocked = false
+ message = "Unlocked"
+ } else {
+ p.IsLocked = true
+ p.PassphraseToUnlock = HashPassword(json.Passphrase)
+ message = "Locked"
+ }
+ p.Save()
+ c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
+}
diff --git a/page.go b/page.go
new file mode 100755
index 0000000..0b99bba
--- /dev/null
+++ b/page.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+ "encoding/base32"
+ "encoding/json"
+ "io/ioutil"
+ "os"
+ "path"
+
+ "github.com/microcosm-cc/bluemonday"
+ "github.com/russross/blackfriday"
+ "github.com/schollz/versionedtext"
+)
+
+var pathToData = "data"
+
+func init() {
+ os.MkdirAll(pathToData, 0755)
+}
+
+type Page struct {
+ Name string
+ Text versionedtext.VersionedText
+ RenderedPage string
+ IsLocked bool
+ PassphraseToUnlock string
+ IsEncrypted bool
+ IsPrimedForSelfDestruct bool
+}
+
+func Open(name string) (p *Page) {
+ p = new(Page)
+ p.Name = name
+ p.Text = versionedtext.NewVersionedText("")
+ p.Render()
+ bJSON, err := ioutil.ReadFile(path.Join(pathToData, encodeToBase32(name)+".json"))
+ if err != nil {
+ return
+ }
+ err = json.Unmarshal(bJSON, &p)
+ if err != nil {
+ p = new(Page)
+ }
+ return p
+}
+
+func (p *Page) Update(newText string) error {
+ p.Text.Update(newText)
+ p.Render()
+ return p.Save()
+}
+
+func (p *Page) Render() {
+ if p.IsEncrypted {
+ p.RenderedPage = "" + p.Text.GetCurrent() + "
"
+ return
+ }
+ p.RenderedPage = MarkdownToHtml(p.Text.GetCurrent())
+}
+
+func MarkdownToHtml(s string) string {
+ unsafe := blackfriday.MarkdownCommon([]byte(s))
+ pClean := bluemonday.UGCPolicy()
+ pClean.AllowElements("img")
+ pClean.AllowAttrs("alt").OnElements("img")
+ pClean.AllowAttrs("src").OnElements("img")
+ pClean.AllowAttrs("class").OnElements("a")
+ pClean.AllowAttrs("href").OnElements("a")
+ pClean.AllowAttrs("id").OnElements("a")
+ pClean.AllowDataURIImages()
+ html := pClean.SanitizeBytes(unsafe)
+ return string(html)
+}
+
+func (p *Page) Save() error {
+ bJSON, err := json.MarshalIndent(p, "", " ")
+ if err != nil {
+ return err
+ }
+ return ioutil.WriteFile(path.Join(pathToData, encodeToBase32(p.Name)+".json"), bJSON, 0755)
+}
+
+func (p *Page) Erase() error {
+ return os.Remove(path.Join(pathToData, encodeToBase32(p.Name)+".json"))
+}
+
+func encodeToBase32(s string) string {
+ return base32.StdEncoding.EncodeToString([]byte(s))
+}
+
+func decodeFromBase32(s string) (s2 string, err error) {
+ bString, err := base32.StdEncoding.DecodeString(s)
+ s2 = string(bString)
+ return
+}
diff --git a/page_test.go b/page_test.go
new file mode 100755
index 0000000..bf767b1
--- /dev/null
+++ b/page_test.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ // "fmt"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestGeneral(t *testing.T) {
+ defer os.RemoveAll("data")
+ p := Open("testpage")
+ err := p.Update("**bold**")
+ if err != nil {
+ t.Error(err)
+ }
+ if strings.TrimSpace(p.RenderedPage) != "
bold
" { + t.Errorf("Did not render: '%s'", p.RenderedPage) + } + err = p.Update("**bold** and *italic*") + if err != nil { + t.Error(err) + } + p.Save() + + p2 := Open("testpage") + if strings.TrimSpace(p2.RenderedPage) != "bold and italic
" { + t.Errorf("Did not render: '%s'", p2.RenderedPage) + } + +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..54c3ca3 --- /dev/null +++ b/utils.go @@ -0,0 +1,203 @@ +package main + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "io/ioutil" + "math/rand" + "net" + "strings" + "time" + + "golang.org/x/crypto/bcrypt" + + "github.com/jcelliott/lumber" + "github.com/schollz/cryptopasta" + "github.com/sergi/go-diff/diffmatchpatch" +) + +var animals []string +var adjectives []string +var aboutPageText string + +var log *lumber.ConsoleLogger + +func init() { + rand.Seed(time.Now().Unix()) + animalsText, _ := ioutil.ReadFile("./static/text/animals") + animals = strings.Split(string(animalsText), ",") + adjectivesText, _ := ioutil.ReadFile("./static/text/adjectives") + adjectives = strings.Split(string(adjectivesText), "\n") + log = lumber.NewConsoleLogger(lumber.TRACE) +} + +func randomAnimal() string { + return strings.Replace(strings.Title(animals[rand.Intn(len(animals)-1)]), " ", "", -1) +} + +func randomAdjective() string { + return strings.Replace(strings.Title(adjectives[rand.Intn(len(adjectives)-1)]), " ", "", -1) +} + +func randomAlliterateCombo() (combo string) { + combo = "" + // // first determine which names are taken from program data + // takenNames := []string{} + // err := db.View(func(tx *bolt.Tx) error { + // // Assume bucket exists and has keys + // b := tx.Bucket([]byte("programdata")) + // c := b.Cursor() + // for k, v := c.First(); k != nil; k, v = c.Next() { + // takenNames = append(takenNames, strings.ToLower(string(v))) + // } + // return nil + // }) + // if err != nil { + // panic(err) + // } + // fmt.Println(takenNames) + // generate random alliteration thats not been used + for { + animal := randomAnimal() + adjective := randomAdjective() + if animal[0] == adjective[0] { //&& stringInSlice(strings.ToLower(adjective+animal), takenNames) == false { + combo = adjective + animal + break + } + } + return +} + +// is there a string in a slice? +func stringInSlice(s string, strings []string) bool { + for _, k := range strings { + if s == k { + return true + } + } + return false +} + +// itob returns an 8-byte big endian representation of v. +func itob(v int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(v)) + return b +} + +func contentType(filename string) string { + switch { + case strings.Contains(filename, ".css"): + return "text/css" + case strings.Contains(filename, ".jpg"): + return "image/jpeg" + case strings.Contains(filename, ".png"): + return "image/png" + case strings.Contains(filename, ".js"): + return "application/javascript" + } + return "text/html" +} + +func diffRebuildtexts(diffs []diffmatchpatch.Diff) []string { + text := []string{"", ""} + for _, myDiff := range diffs { + if myDiff.Type != diffmatchpatch.DiffInsert { + text[0] += myDiff.Text + } + if myDiff.Type != diffmatchpatch.DiffDelete { + text[1] += myDiff.Text + } + } + return text +} + +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" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<