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 Former-commit-id: 2693cca428a2f42aa85ee42a136250f69555c16f [formerly 3d64052514a02869b91e732cae7a9e07bcbee9f4] [formerly d3935ab9d29fecfefa33d018122693b9acf5391d [formerly 27666aef65cc6c8809f8cfa379750a54d13482e2 [formerly7e723e65b5
]]] Former-commit-id: 3b635ad54eef64b2b5b867d8ae7bf7d40921e773 [formerly 3488934a111431706a6c48372d79f552ac706eae] Former-commit-id: 4ed997a716a81a28cbb15ca61047d14dff3b5be8 Former-commit-id:b54feaf247
This commit is contained in:
commit
616b3cea68
19
README.md
19
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
|
||||
|
||||
<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.
|
||||
|
||||
|
37
db.go
37
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()
|
||||
|
64
main.go
64
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)
|
||||
|
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
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
<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.
|
||||
|
||||
**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