diff --git a/README.md b/README.md index 27a5ffc..5551905 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # [cowyo.com](http://cowyo.com/) -[![Version 0.94](https://img.shields.io/badge/version-0.94-brightgreen.svg)]() [![Go Report Card](https://goreportcard.com/badge/github.com/schollz/cowyo)](https://goreportcard.com/report/github.com/schollz/cowyo) [![Join the chat at https://gitter.im/schollz/cowyo](https://badges.gitter.im/schollz/cowyo.svg)](https://gitter.im/schollz/cowyo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Version 0.95](https://img.shields.io/badge/version-0.95-brightgreen.svg)]() [![Go Report Card](https://goreportcard.com/badge/github.com/schollz/cowyo)](https://goreportcard.com/report/github.com/schollz/cowyo) [![Join the chat at https://gitter.im/schollz/cowyo](https://badges.gitter.im/schollz/cowyo.svg)](https://gitter.im/schollz/cowyo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) This is a self-contained notepad webserver that makes sharing easy and _fast_. The most important feature here is *simplicity*. There are many other features as well including versioning, page locking, self-destructing messages, encryption, math support, and listifying. Read on to learn more about the features. @@ -44,6 +44,23 @@ This is a self-contained notepad webserver that makes sharing easy and _fast_. T
+**CLI support**. Want to upload/download from a server? Its super easy. Upload/download files like this: +```bash +$ echo "Hello, world!" > hi.txt +$ curl --upload-file hi.txt cowyo.com + File uploaded to http://cowyo.com/hi.txt +$ curl cowyo.com/test.txt + Hello, world! +``` +or just skip the file-creation step, +```bash +$ echo "Wow, so easy" | curl --upload-file "-" cowyo.com + File uploaded to http://cowyo.com/CautiousCommonLoon +$ curl cowyo.com/CautiousCommonLoon + Wow, so easy +``` + +
**Keyboard Shortcuts**. Quickly transition between Edit/View/List by using `Ctl+Shift+E` to Edit, `Ctl+Shift+Z` to View, and `Ctl+Shift+L` to Listify. diff --git a/db.go b/db.go index 0f243da..4d31bb8 100644 --- a/db.go +++ b/db.go @@ -43,43 +43,6 @@ type WikiData struct { Locked string } -func hasPassword(title string) (bool, error) { - title = strings.ToLower(title) - if !open { - Open(RuntimeArgs.DatabaseLocation) - defer Close() - } - hasPassword := false - err := db.View(func(tx *bolt.Tx) error { - var err error - b := tx.Bucket([]byte("datas")) - if b == nil { - return fmt.Errorf("db must be opened before loading") - } - k := []byte(title) - val := b.Get(k) - if val == nil { - return nil - } - var p WikiData - err = p.decode(val) - if err != nil { - return err - } - for _, line := range strings.Split(p.CurrentText, "\n") { - if strings.Contains(line, "<") == true && strings.Contains(line, ">") == true && strings.Contains(line, "user") == true && strings.Contains(line, "password") == true && strings.Contains(line, "public") == true { - hasPassword = true - } - } - return nil - }) - if err != nil { - fmt.Printf("Could not get WikiData: %s", err) - return false, err - } - return hasPassword, nil -} - func getCurrentText(title string, version int) (string, []versionsInfo, bool, time.Duration, bool, string) { Open(RuntimeArgs.DatabaseLocation) defer Close() diff --git a/main.go b/main.go index cccab19..6663546 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,9 @@ package main import ( - "bytes" "flag" "fmt" - "io" "io/ioutil" - "log" "os" "path" @@ -38,10 +35,8 @@ var RuntimeArgs struct { } var VersionNum string -const _24K = (1 << 20) * 24 - func main() { - VersionNum = "0.94" + VersionNum = "0.95" // _, executableFile, _, _ := runtime.Caller(0) // get full path of this file cwd, _ := os.Getwd() databaseFile := path.Join(cwd, "data.db") @@ -107,61 +102,8 @@ Options:`) r.GET("/", newNote) r.HEAD("/", func(c *gin.Context) { c.Status(200) }) r.GET("/:title", editNote) - r.PUT("/:title", func(c *gin.Context) { - filename := c.Param("title") - fmt.Println(filename) - fmt.Println(c.Request.Body) - fmt.Println(c.Request.ContentLength) - fmt.Println(c.Request.Header) - contentLength := c.Request.ContentLength - var reader io.Reader - reader = c.Request.Body - if contentLength == -1 { - // queue file to disk, because s3 needs content length - var err error - var f io.Reader - - f = reader - - var b bytes.Buffer - - n, err := io.CopyN(&b, f, _24K+1) - if err != nil && err != io.EOF { - log.Printf("%s", err.Error()) - } - - if n > _24K { - file, err := ioutil.TempFile("./", "transfer-") - if err != nil { - log.Printf("%s", err.Error()) - } - - defer file.Close() - - n, err = io.Copy(file, io.MultiReader(&b, f)) - if err != nil { - os.Remove(file.Name()) - log.Printf("%s", err.Error()) - } - - reader, err = os.Open(file.Name()) - } else { - reader = bytes.NewReader(b.Bytes()) - } - - contentLength = n - } - buf := new(bytes.Buffer) - buf.ReadFrom(reader) - fmt.Println("---------------") - fmt.Println(buf.String()) - fmt.Println("---------------") - fmt.Println(c.ContentType()) - fmt.Println(c.Request.Header) - fmt.Println("---------------") - p := WikiData{filename, "", []string{}, []string{}, false, ""} - p.save(buf.String()) - }) + r.PUT("/:title", putFile) + r.PUT("/", putFile) r.GET("/:title/*option", everythingElse) r.POST("/:title/*option", encryptionRoute) r.DELETE("/listitem", deleteListItem) diff --git a/ratelimiter.go b/ratelimiter.go new file mode 100755 index 0000000..35b070f --- /dev/null +++ b/ratelimiter.go @@ -0,0 +1,25 @@ +package main + +import "time" + +var bannedIPs []string + +func init() { + go clearBannedIPs() +} + +func clearBannedIPs() { + for { + bannedIPs = []string{} + time.Sleep(3 * time.Minute) + } +} + +func isIPBanned(ip string) bool { + if stringInSlice(ip, bannedIPs) { + return true + } else { + bannedIPs = append(bannedIPs, ip) + return false + } +} diff --git a/routes.go b/routes.go index d471c28..6b44aa3 100644 --- a/routes.go +++ b/routes.go @@ -1,9 +1,12 @@ package main import ( + "bytes" "fmt" "html/template" + "io" "io/ioutil" + "log" "net/http" "os" "path" @@ -18,6 +21,65 @@ import ( "github.com/russross/blackfriday" ) +const _24K = (1 << 20) * 24 + +func putFile(c *gin.Context) { + if isIPBanned(c.ClientIP()) { + c.Data(200, "text/plain", []byte("You are rate limited to 20 requests/hour.")) + return + } + filename := c.Param("title") + if len(filename) == 0 { + filename = randomAlliterateCombo() + } + contentLength := c.Request.ContentLength + var reader io.Reader + reader = c.Request.Body + if contentLength == -1 { + // queue file to disk, because s3 needs content length + var err error + var f io.Reader + + f = reader + + var b bytes.Buffer + + n, err := io.CopyN(&b, f, _24K+1) + if err != nil && err != io.EOF { + log.Printf("%s", err.Error()) + } + + if n > _24K { + file, err := ioutil.TempFile("./", "transfer-") + if err != nil { + log.Printf("%s", err.Error()) + } + + defer file.Close() + + n, err = io.Copy(file, io.MultiReader(&b, f)) + if err != nil { + os.Remove(file.Name()) + log.Printf("%s", err.Error()) + } + + reader, err = os.Open(file.Name()) + } else { + reader = bytes.NewReader(b.Bytes()) + } + + contentLength = n + } + buf := new(bytes.Buffer) + buf.ReadFrom(reader) + // p := WikiData{filename, "", []string{}, []string{}, false, ""} + // p.save(buf.String()) + var p WikiData + p.load(strings.ToLower(filename)) + p.save(buf.String()) + c.Data(200, "text/plain", []byte("File uploaded to http://"+RuntimeArgs.ExternalIP+"/"+filename)) +} + type EncryptionPost struct { Text string `form:"text" json:"text" binding:"required"` Password string `form:"password" json:"password" binding:"required"` @@ -158,37 +220,37 @@ func editNote(c *gin.Context) { } else if strings.ToLower(title) == "help" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true { c.Redirect(302, "/Help/view") } else { - locked, _ := hasPassword(title) - if locked { - c.Redirect(302, "/"+title+"/view") - } else { - version := c.DefaultQuery("version", "-1") - versionNum, _ := strconv.Atoi(version) - currentText, versions, currentVersion, totalTime, encrypted, locked := getCurrentText(title, versionNum) - if encrypted || len(locked) > 0 { - c.Redirect(302, "/"+title+"/view") - } - if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") { - c.Redirect(302, "/"+title+"/view") - } - numRows := len(strings.Split(currentText, "\n")) + 10 - totalTimeString := totalTime.String() - if totalTime.Seconds() < 1 { - totalTimeString = "< 1 s" - } - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "Title": title, - "WikiName": RuntimeArgs.WikiName, - "ExternalIP": RuntimeArgs.ExternalIP, - "CurrentText": currentText, - "NumRows": numRows, - "Versions": versions, - "TotalTime": totalTimeString, - "SocketType": RuntimeArgs.Socket, - "NoEdit": !currentVersion, - }) - + version := c.DefaultQuery("version", "-1") + versionNum, _ := strconv.Atoi(version) + currentText, versions, currentVersion, totalTime, encrypted, locked := getCurrentText(title, versionNum) + if strings.Contains(c.Request.Header.Get("User-Agent"), "curl/") { + c.Data(200, "text/plain", []byte(currentText)) + return } + if encrypted || len(locked) > 0 { + c.Redirect(302, "/"+title+"/view") + return + } + if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") { + c.Redirect(302, "/"+title+"/view") + return + } + numRows := len(strings.Split(currentText, "\n")) + 10 + totalTimeString := totalTime.String() + if totalTime.Seconds() < 1 { + totalTimeString = "< 1 s" + } + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "Title": title, + "WikiName": RuntimeArgs.WikiName, + "ExternalIP": RuntimeArgs.ExternalIP, + "CurrentText": currentText, + "NumRows": numRows, + "Versions": versions, + "TotalTime": totalTimeString, + "SocketType": RuntimeArgs.Socket, + "NoEdit": !currentVersion, + }) } } diff --git a/templates/aboutpage.md b/templates/aboutpage.md index e83432f..c988814 100644 --- a/templates/aboutpage.md +++ b/templates/aboutpage.md @@ -2,7 +2,7 @@ # CowYo ## Collections of Organized Words You Open -![Version 0.94](https://img.shields.io/badge/version-0.94-brightgreen.svg) +![Version 0.95](https://img.shields.io/badge/version-0.95-brightgreen.svg) This is a self-contained wiki webserver that makes sharing easy and _fast_. The most important feature here is *simplicity*. There are many other features as well including versioning, page locking, self-destructing messages, encryption, math support, and listifying. Read on to learn more about the features. **CowYo** is also [Open Source](https://github.com/schollz/cowyo). @@ -45,6 +45,24 @@ This is a self-contained wiki webserver that makes sharing easy and _fast_. The
+**CLI support**. Want to upload/download from a server? Its super easy. Upload/download files like this: +```bash +$ echo "Hello, world!" > hi.txt +$ curl --upload-file hi.txt cowyo.com + File uploaded to http://cowyo.com/hi.txt +$ curl cowyo.com/test.txt + Hello, world! +``` +or just skip the file-creation step, +```bash +$ echo "Wow, so easy" | curl --upload-file "-" cowyo.com + File uploaded to http://cowyo.com/CautiousCommonLoon +$ curl cowyo.com/CautiousCommonLoon + Wow, so easy +``` + +
+ **Keyboard Shortcuts**. Quickly transition between Edit/View/List by using `Ctl+Shift+E` to Edit, `Ctl+Shift+Z` to View, and `Ctl+Shift+L` to Listify. **Admin controls**. The Admin can view/delete all the documents by setting the `-a YourAdminKey` when starting the program. Then the admin has access to the `/ls/YourAdminKey` to view and delete any of the pages.