mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
Merge pull request #40 from schollz/upload
CLI support for uploading/downloading via curl
This commit is contained in:
commit
7e723e65b5
19
README.md
19
README.md
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# [cowyo.com](http://cowyo.com/)
|
# [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.
|
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
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
**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
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
**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.
|
**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.
|
||||||
|
|
||||||
|
37
db.go
37
db.go
@ -43,43 +43,6 @@ type WikiData struct {
|
|||||||
Locked string
|
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) {
|
func getCurrentText(title string, version int) (string, []versionsInfo, bool, time.Duration, bool, string) {
|
||||||
Open(RuntimeArgs.DatabaseLocation)
|
Open(RuntimeArgs.DatabaseLocation)
|
||||||
defer Close()
|
defer Close()
|
||||||
|
64
main.go
64
main.go
@ -1,12 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
@ -38,10 +35,8 @@ var RuntimeArgs struct {
|
|||||||
}
|
}
|
||||||
var VersionNum string
|
var VersionNum string
|
||||||
|
|
||||||
const _24K = (1 << 20) * 24
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
VersionNum = "0.94"
|
VersionNum = "0.95"
|
||||||
// _, executableFile, _, _ := runtime.Caller(0) // get full path of this file
|
// _, executableFile, _, _ := runtime.Caller(0) // get full path of this file
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
databaseFile := path.Join(cwd, "data.db")
|
databaseFile := path.Join(cwd, "data.db")
|
||||||
@ -107,61 +102,8 @@ Options:`)
|
|||||||
r.GET("/", newNote)
|
r.GET("/", newNote)
|
||||||
r.HEAD("/", func(c *gin.Context) { c.Status(200) })
|
r.HEAD("/", func(c *gin.Context) { c.Status(200) })
|
||||||
r.GET("/:title", editNote)
|
r.GET("/:title", editNote)
|
||||||
r.PUT("/:title", func(c *gin.Context) {
|
r.PUT("/:title", putFile)
|
||||||
filename := c.Param("title")
|
r.PUT("/", putFile)
|
||||||
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.GET("/:title/*option", everythingElse)
|
r.GET("/:title/*option", everythingElse)
|
||||||
r.POST("/:title/*option", encryptionRoute)
|
r.POST("/:title/*option", encryptionRoute)
|
||||||
r.DELETE("/listitem", deleteListItem)
|
r.DELETE("/listitem", deleteListItem)
|
||||||
|
25
ratelimiter.go
Executable file
25
ratelimiter.go
Executable file
@ -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
|
||||||
|
}
|
||||||
|
}
|
122
routes.go
122
routes.go
@ -1,9 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -18,6 +21,65 @@ import (
|
|||||||
"github.com/russross/blackfriday"
|
"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 {
|
type EncryptionPost struct {
|
||||||
Text string `form:"text" json:"text" binding:"required"`
|
Text string `form:"text" json:"text" binding:"required"`
|
||||||
Password string `form:"password" json:"password" 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 {
|
} else if strings.ToLower(title) == "help" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||||
c.Redirect(302, "/Help/view")
|
c.Redirect(302, "/Help/view")
|
||||||
} else {
|
} else {
|
||||||
locked, _ := hasPassword(title)
|
version := c.DefaultQuery("version", "-1")
|
||||||
if locked {
|
versionNum, _ := strconv.Atoi(version)
|
||||||
c.Redirect(302, "/"+title+"/view")
|
currentText, versions, currentVersion, totalTime, encrypted, locked := getCurrentText(title, versionNum)
|
||||||
} else {
|
if strings.Contains(c.Request.Header.Get("User-Agent"), "curl/") {
|
||||||
version := c.DefaultQuery("version", "-1")
|
c.Data(200, "text/plain", []byte(currentText))
|
||||||
versionNum, _ := strconv.Atoi(version)
|
return
|
||||||
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,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# CowYo
|
# CowYo
|
||||||
## Collections of Organized Words You Open
|
## 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).
|
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
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
**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
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
**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.
|
**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.
|
**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.
|
||||||
|
Loading…
Reference in New Issue
Block a user