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.