Remove sqlite database
We don't need the sqlite database anymore
This commit is contained in:
parent
9d39765ab2
commit
b24bfa39e7
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -26,3 +26,5 @@ __debug_bin
|
|||
*.db
|
||||
bins
|
||||
*.toml
|
||||
pasta_test
|
||||
/pastas
|
||||
|
|
|
@ -9,7 +9,10 @@ go:
|
|||
dist:
|
||||
- bionic
|
||||
|
||||
script:
|
||||
before_script:
|
||||
- sudo apt-get -y install jq
|
||||
- make requirements
|
||||
|
||||
script:
|
||||
- make
|
||||
- make test
|
||||
|
|
|
@ -10,5 +10,5 @@ RUN mkdir /app
|
|||
RUN mkdir /data
|
||||
WORKDIR /data
|
||||
COPY --from=build-env /app/pastad /app/pastad
|
||||
CMD /app/pastad
|
||||
ENTRYPOINT /app/pastad
|
||||
VOLUME ["/data"]
|
||||
|
|
12
Makefile
12
Makefile
|
@ -2,13 +2,17 @@ default: all
|
|||
all: pasta pastad
|
||||
|
||||
requirements:
|
||||
go get github.com/mattn/go-sqlite3
|
||||
go get github.com/BurntSushi/toml
|
||||
|
||||
pasta: cmd/pasta/*.go
|
||||
pasta: cmd/pasta/pasta.go
|
||||
go build $^
|
||||
pastad: cmd/pastad/*.go
|
||||
pastad: cmd/pastad/pastad.go cmd/pastad/storage.go
|
||||
go build $^
|
||||
|
||||
test:
|
||||
test: pastad
|
||||
go test ./...
|
||||
# TODO: This syntax is horrible :-)
|
||||
bash -c 'cd test && ./test.sh'
|
||||
|
||||
docker: Dockerfile pasta pastad
|
||||
docker build . -t feldspaten.org/pasta
|
||||
|
|
|
@ -16,6 +16,10 @@ Then create a `pastad.toml` file using the provided example (`pastad.toml.exampl
|
|||
|
||||
### Docker
|
||||
|
||||
make docker
|
||||
|
||||
Or manually:
|
||||
|
||||
docker build . -t feldspaten.org/pasta # Build docker container
|
||||
|
||||
Create or run the container with
|
||||
|
@ -23,5 +27,5 @@ Create or run the container with
|
|||
docker container create --name pasta -p 8199:8199 -v ABSOLUTE_PATH_TO_DATA_DIR:/data feldspaten.org/pasta
|
||||
docker container run --name pasta -p 8199:8199 -v ABSOLUTE_PATH_TO_DATA_DIR:/data feldspaten.org/pasta
|
||||
|
||||
The container needs a `data` directory with a valid `pastad.toml` (See the [example file](pastad.toml.example))
|
||||
The container needs a `data` directory with a valid `pastad.toml` (See the [example file](pastad.toml.example), otherwise default values will be used).
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"flag"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
@ -19,12 +20,27 @@ type Config struct {
|
|||
|
||||
var cf Config
|
||||
|
||||
func FileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cf.RemoteHost = "http://localhost:8199"
|
||||
// Load configuration file if possible (swallow errors)
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
configFile := homeDir + "/.pasta.toml"
|
||||
toml.DecodeFile(configFile, &cf)
|
||||
if FileExists(configFile) {
|
||||
if _, err := toml.DecodeFile(configFile, &cf); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "config-toml file parse error: %s %s\n", configFile, err)
|
||||
}
|
||||
}
|
||||
// Parse program arguments
|
||||
flag.StringVar(&cf.RemoteHost, "r", cf.RemoteHost, "Specify remote host")
|
||||
flag.Parse()
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
// Push to server
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -18,21 +17,20 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaseUrl string `toml:"BaseURL"` // Instance base URL
|
||||
DbFile string `toml:"Database"` // SQLite3 database
|
||||
BinDir string `toml:"BinsDir"` // dir where bins are stored
|
||||
PastaDir string `toml:"PastaDir"` // dir where pasta are stored
|
||||
BindAddr string `toml:"BindAddress"`
|
||||
MaxBinSize int64 `toml:"MaxBinSize"` // Max bin size in bytes
|
||||
BinCharacters int `toml:"BinCharacters"`
|
||||
}
|
||||
|
||||
var cf Config
|
||||
var bowl PastaBowl
|
||||
|
||||
func ExtractBinName(path string) string {
|
||||
func ExtractPastaId(path string) string {
|
||||
i := strings.LastIndex(path, "/")
|
||||
if i < 0 {
|
||||
return path
|
||||
|
@ -41,96 +39,96 @@ func ExtractBinName(path string) string {
|
|||
}
|
||||
}
|
||||
|
||||
func SendBin(id string, w http.ResponseWriter) error {
|
||||
filename := fmt.Sprintf("%s/%s", cf.BinDir, id)
|
||||
file, err := os.OpenFile(filename, os.O_RDONLY, 0400)
|
||||
func SendPasta(id string, w http.ResponseWriter) error {
|
||||
pasta, err := bowl.GetPasta(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := bowl.GetPastaReader(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
stat, _ := file.Stat()
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(pasta.Size, 10))
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, err = io.Copy(w, file)
|
||||
return err
|
||||
}
|
||||
|
||||
func ReceiveBin(r *http.Request) (Bin, error) {
|
||||
func ReceivePasta(r *http.Request) (Pasta, error) {
|
||||
var err error
|
||||
reader := r.Body
|
||||
buf := make([]byte, 4096)
|
||||
bin := Bin{Id: ""}
|
||||
var size int64
|
||||
pasta := Pasta{Id: ""}
|
||||
defer reader.Close()
|
||||
|
||||
// TODO: Use suggested ID from http header if present
|
||||
|
||||
bin.Id, err = GenerateRandomBinId(cf.BinCharacters)
|
||||
if err != nil {
|
||||
log.Fatalf("Server error while generating random bin: %s", err)
|
||||
bin.Id = ""
|
||||
return bin, err
|
||||
pasta.Id = bowl.GenerateRandomBinId(cf.BinCharacters)
|
||||
if err = bowl.InsertPasta(&pasta); err != nil {
|
||||
return pasta, err
|
||||
}
|
||||
filename := fmt.Sprintf("%s/%s", cf.BinDir, bin.Id)
|
||||
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0640)
|
||||
// Append contents to file
|
||||
file, err := os.OpenFile(pasta.Filename, os.O_RDWR|os.O_APPEND, 0640)
|
||||
if err != nil {
|
||||
return bin, err
|
||||
file.Close()
|
||||
return pasta, err
|
||||
}
|
||||
defer file.Close()
|
||||
for size < cf.MaxBinSize {
|
||||
pasta.Size = 0
|
||||
for pasta.Size < cf.MaxBinSize {
|
||||
n, err := reader.Read(buf)
|
||||
if (err == nil || err == io.EOF) && n > 0 {
|
||||
if _, err = file.Write(buf[:n]); err != nil {
|
||||
log.Fatalf("Write error while receiving bin: %s", err)
|
||||
return bin, err
|
||||
return pasta, err
|
||||
}
|
||||
size += int64(n)
|
||||
pasta.Size += int64(n)
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Fatalf("Receive error while receiving bin: %s", err)
|
||||
return bin, err
|
||||
return pasta, err
|
||||
}
|
||||
}
|
||||
if size >= cf.MaxBinSize {
|
||||
if pasta.Size >= cf.MaxBinSize {
|
||||
log.Println("Max size exceeded while receiving bin")
|
||||
return bin, errors.New("Bin size exceeded")
|
||||
return pasta, errors.New("Bin size exceeded")
|
||||
}
|
||||
if size == 0 {
|
||||
return bin, nil
|
||||
if pasta.Size == 0 {
|
||||
// This is invalid
|
||||
file.Close()
|
||||
bowl.DeletePasta(pasta.Id)
|
||||
pasta.Id = ""
|
||||
pasta.Filename = ""
|
||||
pasta.Token = ""
|
||||
pasta.ExpireDate = 0
|
||||
return pasta, nil
|
||||
}
|
||||
|
||||
file.Sync()
|
||||
file.Close()
|
||||
bin.CreationDate = time.Now().Unix()
|
||||
bin.Size = size
|
||||
err = InsertBin(bin)
|
||||
if err != nil {
|
||||
log.Fatalf("Database while receiving bin: %s", err)
|
||||
os.Remove(filename)
|
||||
bin.Id = ""
|
||||
return bin, err
|
||||
}
|
||||
return bin, nil
|
||||
return pasta, file.Sync()
|
||||
}
|
||||
|
||||
func handlerPost(w http.ResponseWriter, r *http.Request) {
|
||||
bin, err := ReceiveBin(r)
|
||||
pasta, err := ReceivePasta(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Server error")
|
||||
log.Printf("Receive error: %s", err)
|
||||
return
|
||||
} else {
|
||||
if bin.Id == "" {
|
||||
if pasta.Id == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Empty content"))
|
||||
w.Write([]byte("Empty pasta"))
|
||||
} else {
|
||||
log.Printf("Received bin %s (%d bytes) from %s", bin.Id, bin.Size, r.RemoteAddr)
|
||||
log.Printf("Received bin %s (%d bytes) from %s", pasta.Id, pasta.Size, r.RemoteAddr)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
url := fmt.Sprintf("%s/%s", cf.BaseUrl, bin.Id)
|
||||
w.Write([]byte(url))
|
||||
url := fmt.Sprintf("%s/%s", cf.BaseUrl, pasta.Id)
|
||||
// Dont use json package, the reply is simple enough to build it on-the-fly
|
||||
reply := fmt.Sprintf("{\"url\":\"%s\",\"token\":\"%s\"}", url, pasta.Token)
|
||||
w.Write([]byte(reply))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,24 +136,24 @@ func handlerPost(w http.ResponseWriter, r *http.Request) {
|
|||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
// Check if bin ID is given
|
||||
binId := ExtractBinName(r.URL.Path)
|
||||
if binId == "" {
|
||||
id := ExtractPastaId(r.URL.Path)
|
||||
if id == "" {
|
||||
fmt.Fprintf(w, "<!doctype html><html><head>")
|
||||
fmt.Fprintf(w, "<body>Stupid simple pastebin service</body>")
|
||||
fmt.Fprintf(w, "</html>")
|
||||
} else {
|
||||
bin, err := FetchBin(binId)
|
||||
pasta, err := bowl.GetPasta(id)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Database error")
|
||||
log.Fatalf("Database error: %s", err)
|
||||
fmt.Fprintf(w, "Storage error")
|
||||
log.Fatalf("Storage error: %s", err)
|
||||
return
|
||||
}
|
||||
if bin.Id == "" {
|
||||
fmt.Fprintf(w, "No such bin: %s", binId)
|
||||
if pasta.Id == "" {
|
||||
fmt.Fprintf(w, "No such pasta: %s", id)
|
||||
} else {
|
||||
if err = SendBin(bin.Id, w); err != nil {
|
||||
log.Printf("Error sending bin %s: %s", bin.Id, err)
|
||||
if err = SendPasta(pasta.Id, w); err != nil {
|
||||
log.Printf("Error sending pasta %s: %s", pasta.Id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,51 +165,43 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func handlerPrivacy(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "<!doctype html><html><head>")
|
||||
fmt.Fprintf(w, "<body><h1>Privacy</h1><p>When fetching bins no data is collected</p><p>When pasting a bin, the pasted content is stored and your IP address is logged for debugging and abuse prevention purposes</p></body>")
|
||||
fmt.Fprintf(w, "</html>")
|
||||
}
|
||||
|
||||
func handlerHealth(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "OK")
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
configFile := "pastad.toml"
|
||||
// Set defaults
|
||||
cf.BaseUrl = "http://localhost:8199"
|
||||
cf.DbFile = "pasta.db"
|
||||
cf.BinDir = "bins/"
|
||||
cf.PastaDir = "pastas/"
|
||||
cf.BindAddr = "127.0.0.1:8199"
|
||||
cf.MaxBinSize = 1024 * 1024 * 25 // Default max size: 25 MB
|
||||
cf.BinCharacters = 8 // Note: Never use less than 8 characters!
|
||||
rand.Seed(time.Now().Unix())
|
||||
if _, err := toml.DecodeFile("pastad.toml", &cf); err != nil {
|
||||
fmt.Printf("Error loading configuration file: %s\n", err)
|
||||
os.Exit(1)
|
||||
fmt.Println("Starting pasta server ... ")
|
||||
if FileExists(configFile) {
|
||||
if _, err := toml.DecodeFile(configFile, &cf); err != nil {
|
||||
fmt.Printf("Error loading configuration file: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Config file '%s' not found\n", configFile)
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if cf.BinCharacters < 8 {
|
||||
log.Println("Warning: Using less than 8 bin characters is recommended and might lead to unintended side-effects")
|
||||
}
|
||||
os.Mkdir(cf.BinDir, os.ModePerm)
|
||||
if cf.PastaDir == "" {
|
||||
cf.PastaDir = "."
|
||||
}
|
||||
bowl.Directory = cf.PastaDir
|
||||
os.Mkdir(bowl.Directory, os.ModePerm)
|
||||
|
||||
// Database
|
||||
log.Printf("Database initialization: %s", cf.DbFile)
|
||||
db, err = sql.Open("sqlite3", cf.DbFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = DbInitialize(db); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer db.Close()
|
||||
// Setup webserver
|
||||
log.Printf("Webserver initialization: http://%s", cf.BindAddr)
|
||||
http.HandleFunc("/", handler)
|
||||
http.HandleFunc("/privacy", handlerPrivacy)
|
||||
http.HandleFunc("/health", handlerHealth)
|
||||
log.Println("Startup completed")
|
||||
log.Printf("Up and running: http://%s", cf.BindAddr)
|
||||
log.Printf("Startup completed. Serving http://%s", cf.BindAddr)
|
||||
log.Fatal(http.ListenAndServe(cf.BindAddr, nil))
|
||||
}
|
||||
|
|
|
@ -1,64 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
type Bin struct {
|
||||
Id string
|
||||
Owner string
|
||||
CreationDate int64
|
||||
ExpireDate int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
func DbInitialize(db *sql.DB) error {
|
||||
_, err := db.Exec("CREATE TABLE IF NOT EXISTS `bins` (`id` VARCHAR(64) PRIMARY KEY, `owner` VARCHAR(64), `createdate` int, `expiredate` int, `size` int);")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FetchBin(id string) (Bin, error) {
|
||||
bin := Bin{Id: ""}
|
||||
stmt, err := db.Prepare("SELECT `id`,`owner`,`createdate`,`expiredate`,`size` FROM `bins` WHERE `id` = ? LIMIT 1;")
|
||||
if err != nil {
|
||||
return bin, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.Query(id)
|
||||
if err != nil {
|
||||
return bin, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
rows.Scan(&bin.Id, &bin.Owner, &bin.CreationDate, &bin.ExpireDate, &bin.Size)
|
||||
}
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
func InsertBin(bin Bin) error {
|
||||
stmt, err := db.Prepare("INSERT INTO `bins`(`id`,`owner`,`createdate`,`expiredate`,`size`) VALUES (?,?,?,?,?);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec(bin.Id, bin.Owner, bin.CreationDate, bin.ExpireDate, bin.Size)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteBin(id string) error {
|
||||
stmt, err := db.Prepare("DELETE FROM `bins` WHERE `id` = ?;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec(id)
|
||||
return err
|
||||
type Pasta struct {
|
||||
Id string
|
||||
Token string
|
||||
Filename string
|
||||
ExpireDate int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
func RandomString(n int) string {
|
||||
|
@ -70,15 +28,160 @@ func RandomString(n int) string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
func GenerateRandomBinId(n int) (string, error) {
|
||||
func FileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
/* PastaBowl is the main storage instance */
|
||||
type PastaBowl struct {
|
||||
Directory string // Directory where the pastas are
|
||||
}
|
||||
|
||||
func (bowl *PastaBowl) filename(id string) string {
|
||||
return fmt.Sprintf("%s/%s", bowl.Directory, id)
|
||||
}
|
||||
|
||||
func (bowl *PastaBowl) Exists(id string) bool {
|
||||
return FileExists(bowl.filename(id))
|
||||
}
|
||||
|
||||
// get pasta metadata
|
||||
func (bowl *PastaBowl) GetPasta(id string) (Pasta, error) {
|
||||
pasta := Pasta{Id: "", Filename: bowl.filename(id)}
|
||||
stat, err := os.Stat(bowl.filename(id))
|
||||
if err != nil {
|
||||
// Does not exists results in empty pasta result
|
||||
if !os.IsExist(err) {
|
||||
return pasta, nil
|
||||
}
|
||||
return pasta, err
|
||||
}
|
||||
pasta.Size = stat.Size()
|
||||
file, err := os.OpenFile(pasta.Filename, os.O_RDONLY, 0400)
|
||||
if err != nil {
|
||||
return pasta, err
|
||||
}
|
||||
defer file.Close()
|
||||
// Read metadata (until "---")
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
if err = scanner.Err(); err != nil {
|
||||
return pasta, err
|
||||
}
|
||||
line := scanner.Text()
|
||||
pasta.Size -= int64(len(line) + 1)
|
||||
if line == "---" {
|
||||
break
|
||||
}
|
||||
// Parse metadata (name: value)
|
||||
i := strings.Index(line, ":")
|
||||
if i <= 0 {
|
||||
continue
|
||||
}
|
||||
name, value := strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:])
|
||||
if name == "token" {
|
||||
pasta.Token = value
|
||||
} else if name == "expire" {
|
||||
pasta.ExpireDate, _ = strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
|
||||
}
|
||||
// All good
|
||||
pasta.Id = id
|
||||
return pasta, nil
|
||||
}
|
||||
|
||||
func (bowl *PastaBowl) getPastaFile(id string, flag int) (*os.File, error) {
|
||||
filename := bowl.filename(id)
|
||||
file, err := os.OpenFile(filename, flag, 0640)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, 1)
|
||||
c := 0 // Counter
|
||||
for {
|
||||
n, err := file.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
file.Close()
|
||||
return nil, err
|
||||
}
|
||||
file.Close()
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
if buf[0] == '-' {
|
||||
c++
|
||||
} else if buf[0] == '\n' {
|
||||
if c >= 3 {
|
||||
return file, nil
|
||||
}
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
// This should never occur
|
||||
file.Close()
|
||||
return nil, errors.New("Unexpected end of block")
|
||||
}
|
||||
|
||||
// Get the file instance to the pasta content (read-only)
|
||||
func (bowl *PastaBowl) GetPastaReader(id string) (*os.File, error) {
|
||||
return bowl.getPastaFile(id, os.O_RDONLY)
|
||||
}
|
||||
|
||||
// Get the file instance to the pasta content (read-only)
|
||||
func (bowl *PastaBowl) GetPastaWriter(id string) (*os.File, error) {
|
||||
return bowl.getPastaFile(id, os.O_RDWR)
|
||||
}
|
||||
|
||||
// Prepare a pasta file to be written. Id and Token will be set, if not already done
|
||||
func (bowl *PastaBowl) InsertPasta(pasta *Pasta) error {
|
||||
if pasta.Id == "" {
|
||||
// TODO: Use crypto rand
|
||||
pasta.Id = bowl.GenerateRandomBinId(8) // Use default length here
|
||||
}
|
||||
if pasta.Token == "" {
|
||||
// TODO: Use crypto rand
|
||||
pasta.Token = RandomString(16)
|
||||
}
|
||||
pasta.Filename = bowl.filename(pasta.Id)
|
||||
file, err := os.OpenFile(pasta.Filename, os.O_RDWR|os.O_CREATE, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err := file.Write([]byte(fmt.Sprintf("token:%s\n", pasta.Token))); err != nil {
|
||||
return err
|
||||
}
|
||||
if pasta.ExpireDate > 0 {
|
||||
if _, err := file.Write([]byte(fmt.Sprintf("expire:%d\n", pasta.ExpireDate))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := file.Write([]byte("---\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
return file.Sync()
|
||||
}
|
||||
|
||||
func (bowl *PastaBowl) DeletePasta(id string) error {
|
||||
if !bowl.Exists(id) {
|
||||
return nil
|
||||
}
|
||||
return os.Remove(bowl.filename(id))
|
||||
}
|
||||
|
||||
func (bowl *PastaBowl) GenerateRandomBinId(n int) string {
|
||||
for {
|
||||
id := RandomString(n)
|
||||
bin, err := FetchBin(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if bin.Id == "" {
|
||||
return id, nil
|
||||
if !bowl.Exists(id) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,91 +1,245 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const testFilename = "test.db"
|
||||
var testBowl PastaBowl
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
|
||||
// Initialisation
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
os.Remove(testFilename)
|
||||
db, err = sql.Open("sqlite3", testFilename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = DbInitialize(db); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer db.Close()
|
||||
testBowl.Directory = "pasta_test"
|
||||
os.Mkdir(testBowl.Directory, os.ModePerm)
|
||||
defer os.RemoveAll(testBowl.Directory)
|
||||
// Run tests
|
||||
ret := m.Run()
|
||||
os.Exit(ret)
|
||||
}
|
||||
|
||||
func TestBins(t *testing.T) {
|
||||
func TestMetadata(t *testing.T) {
|
||||
var err error
|
||||
var bin Bin
|
||||
bin1 := Bin{Id: "a", Owner: "user1", CreationDate: 1, ExpireDate: 1000, Size: 10}
|
||||
bin2 := Bin{Id: "b", Owner: "user2", CreationDate: 2, ExpireDate: 2000, Size: 50}
|
||||
var pasta, p1, p2, p3 Pasta
|
||||
|
||||
if err = InsertBin(bin1); err != nil {
|
||||
t.Fatalf("Error inserting bin 1: %s", err)
|
||||
if err = testBowl.InsertPasta(&p1); err != nil {
|
||||
t.Fatalf("Error inserting pasta 1: %s", err)
|
||||
return
|
||||
}
|
||||
if err = InsertBin(bin2); err != nil {
|
||||
t.Fatalf("Error inserting bin 2: %s", err)
|
||||
if p1.Id == "" {
|
||||
t.Fatal("Pasta 1 id not set")
|
||||
return
|
||||
}
|
||||
if bin, err = FetchBin(bin1.Id); err != nil {
|
||||
t.Fatalf("Error getting bin 1: %s", err)
|
||||
return
|
||||
} else if bin != bin1 {
|
||||
t.Fatal("Bin 1 mismatch", err)
|
||||
if p1.Token == "" {
|
||||
t.Fatal("Pasta 1 id not set")
|
||||
return
|
||||
}
|
||||
if bin, err = FetchBin(bin2.Id); err != nil {
|
||||
t.Fatalf("Error getting bin 2: %s", err)
|
||||
return
|
||||
} else if bin != bin2 {
|
||||
t.Fatal("Bin 2 mismatch", err)
|
||||
if err = testBowl.InsertPasta(&p2); err != nil {
|
||||
t.Fatalf("Error inserting pasta 2: %s", err)
|
||||
return
|
||||
}
|
||||
if err = DeleteBin(bin1.Id); err != nil {
|
||||
t.Fatalf("Error deleting bin 1: %s", err)
|
||||
// Insert pasta with given ID and Token
|
||||
p3Id := testBowl.GenerateRandomBinId(12)
|
||||
p3Token := RandomString(20)
|
||||
p3.Id = p3Id
|
||||
p3.Token = p3Token
|
||||
if err = testBowl.InsertPasta(&p3); err != nil {
|
||||
t.Fatalf("Error inserting pasta 3: %s", err)
|
||||
return
|
||||
}
|
||||
if bin, err = FetchBin(bin1.Id); err != nil {
|
||||
t.Fatalf("Error getting bin 1 after delete: %s", err)
|
||||
return
|
||||
} else if bin.Id != "" {
|
||||
t.Fatal("Bin 1 still exists after delete", err)
|
||||
if p3.Id != p3Id {
|
||||
t.Fatal("Pasta 3 id mismatch")
|
||||
return
|
||||
}
|
||||
// Check if bin2 is still existing
|
||||
if bin, err = FetchBin(bin2.Id); err != nil {
|
||||
t.Fatalf("Error getting bin 2: %s", err)
|
||||
return
|
||||
} else if bin != bin2 {
|
||||
t.Fatal("Bin 2 mismatch after deleting", err)
|
||||
if p3.Token != p3Token {
|
||||
t.Fatal("Pasta 3 id mismatch")
|
||||
return
|
||||
}
|
||||
// Now delete bin2 as well
|
||||
if err = DeleteBin(bin2.Id); err != nil {
|
||||
t.Fatalf("Error deleting bin 1: %s", err)
|
||||
|
||||
pasta, err = testBowl.GetPasta(p1.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 1: %s", err)
|
||||
return
|
||||
}
|
||||
if bin, err = FetchBin(bin2.Id); err != nil {
|
||||
t.Fatalf("Error getting bin 2 after delete: %s", err)
|
||||
if pasta != p1 {
|
||||
t.Fatal("Pasta 1 mismatch")
|
||||
return
|
||||
} else if bin.Id != "" {
|
||||
t.Fatal("Bin 2 still exists after delete", err)
|
||||
}
|
||||
pasta, err = testBowl.GetPasta(p2.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 2: %s", err)
|
||||
return
|
||||
}
|
||||
if pasta != p2 {
|
||||
t.Fatal("Pasta 2 mismatch")
|
||||
return
|
||||
}
|
||||
pasta, err = testBowl.GetPasta(p3.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 3: %s", err)
|
||||
return
|
||||
}
|
||||
if pasta != p3 {
|
||||
t.Fatal("Pasta 3 mismatch")
|
||||
return
|
||||
}
|
||||
|
||||
if err = testBowl.DeletePasta(p1.Id); err != nil {
|
||||
t.Fatalf("Error deleting pasta 1: %s", err)
|
||||
}
|
||||
pasta, err = testBowl.GetPasta(p1.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 1 (after delete): %s", err)
|
||||
return
|
||||
}
|
||||
if pasta.Id != "" {
|
||||
t.Fatal("Pasta 1 exists after delete")
|
||||
return
|
||||
}
|
||||
// Ensure pasta 2 and 3 are not affected if we delete pasta 1
|
||||
pasta, err = testBowl.GetPasta(p2.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 2 after deleting pasta 1: %s", err)
|
||||
return
|
||||
}
|
||||
if pasta != p2 {
|
||||
t.Fatal("Pasta 2 mismatch after deleting pasta 1")
|
||||
return
|
||||
}
|
||||
pasta, err = testBowl.GetPasta(p3.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 3 after deleting pasta 1: %s", err)
|
||||
return
|
||||
}
|
||||
if pasta != p3 {
|
||||
t.Fatal("Pasta 3 mismatch after deleteing pasta 1")
|
||||
return
|
||||
}
|
||||
// Delete also pasta 2
|
||||
if err = testBowl.DeletePasta(p2.Id); err != nil {
|
||||
t.Fatalf("Error deleting pasta 2: %s", err)
|
||||
}
|
||||
pasta, err = testBowl.GetPasta(p2.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 2 (after delete): %s", err)
|
||||
return
|
||||
}
|
||||
if pasta.Id != "" {
|
||||
t.Fatal("Pasta 2 exists after delete")
|
||||
return
|
||||
}
|
||||
pasta, err = testBowl.GetPasta(p3.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta 3 after deleting pasta 2: %s", err)
|
||||
return
|
||||
}
|
||||
if pasta != p3 {
|
||||
t.Fatal("Pasta 3 mismatch after deleting pasta 2")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlobs(t *testing.T) {
|
||||
var err error
|
||||
var p1, p2 Pasta
|
||||
|
||||
// Contents
|
||||
testString1 := RandomString(4096 * 8)
|
||||
testString2 := RandomString(4096 * 8)
|
||||
|
||||
if err = testBowl.InsertPasta(&p1); err != nil {
|
||||
t.Fatalf("Error inserting pasta 1: %s", err)
|
||||
return
|
||||
}
|
||||
file, err := testBowl.GetPastaWriter(p1.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta file 1: %s", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err = file.Write([]byte(testString1)); err != nil {
|
||||
t.Fatalf("Error writing to pasta file 1: %s", err)
|
||||
return
|
||||
}
|
||||
if err = file.Close(); err != nil {
|
||||
t.Fatalf("Error closing pasta file 1: %s", err)
|
||||
return
|
||||
}
|
||||
if err = testBowl.InsertPasta(&p2); err != nil {
|
||||
t.Fatalf("Error inserting pasta 2: %s", err)
|
||||
return
|
||||
}
|
||||
file, err = testBowl.GetPastaWriter(p2.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta file 2: %s", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err = file.Write([]byte(testString2)); err != nil {
|
||||
t.Fatalf("Error writing to pasta file 2: %s", err)
|
||||
return
|
||||
}
|
||||
if err = file.Close(); err != nil {
|
||||
t.Fatalf("Error closing pasta file 2: %s", err)
|
||||
return
|
||||
}
|
||||
// Fetch contents now
|
||||
file, err = testBowl.GetPastaReader(p1.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta reader 1: %s", err)
|
||||
return
|
||||
}
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading pasta 1: %s", err)
|
||||
return
|
||||
}
|
||||
if testString1 != string(buf) {
|
||||
t.Fatal("Mismatch: pasta 1 contents")
|
||||
t.Logf("Bytes: Read %d, Expected %d", len(buf), len(([]byte(testString1))))
|
||||
return
|
||||
}
|
||||
// Same for pasta 2
|
||||
file, err = testBowl.GetPastaReader(p2.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta reader 2: %s", err)
|
||||
return
|
||||
}
|
||||
buf, err = ioutil.ReadAll(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading pasta 2: %s", err)
|
||||
return
|
||||
}
|
||||
if testString2 != string(buf) {
|
||||
t.Fatal("Mismatch: pasta 2 contents")
|
||||
t.Logf("Bytes: Read %d, Expected %d", len(buf), len(([]byte(testString2))))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if pasta 1 can be deleted and the contents of pasta 2 are still OK afterwards
|
||||
if err = testBowl.DeletePasta(p1.Id); err != nil {
|
||||
t.Fatalf("Error deleting pasta 1: %s", err)
|
||||
}
|
||||
file, err = testBowl.GetPastaReader(p2.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pasta reader 2: %s", err)
|
||||
return
|
||||
}
|
||||
buf, err = ioutil.ReadAll(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading pasta 2: %s", err)
|
||||
return
|
||||
}
|
||||
if testString2 != string(buf) {
|
||||
t.Fatal("Mismatch: pasta 2 contents")
|
||||
t.Logf("Bytes: Read %d, Expected %d", len(buf), len(([]byte(testString2))))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
BaseURL = "http://localhost:8199"
|
||||
Database = "pastad.db"
|
||||
BinsDir = "bins"
|
||||
PastaDir = "bins"
|
||||
BindAddress = ":8199"
|
||||
MaxBinSize = 26214400 # 5 MB
|
||||
BinCharacters = 8
|
||||
|
|
12
test/test.sh
12
test/test.sh
|
@ -7,20 +7,26 @@ function cleanup() {
|
|||
rm -f testfile
|
||||
rm -f testfile2
|
||||
kill %1
|
||||
rm -rf pasta_test
|
||||
rm -f pasta.json
|
||||
}
|
||||
|
||||
set -e
|
||||
trap cleanup EXIT
|
||||
|
||||
./pastad &
|
||||
../pastad &
|
||||
sleep 1 # Don't do sleep you lazy :-)
|
||||
echo "Testfile 123" > testfile
|
||||
link=`./pasta < testfile`
|
||||
../pasta -r http://localhost:8199 < testfile > pasta.json
|
||||
cat pasta.json
|
||||
link=`jq -r .url pasta.json`
|
||||
echo $link
|
||||
curl -o testfile2 $link
|
||||
diff testfile testfile2
|
||||
echo "Testfile matches"
|
||||
echo "Testfile 123456789" > testfile
|
||||
link=`./pasta < testfile`
|
||||
../pasta -r http://localhost:8199 < testfile > pasta.json
|
||||
link=`jq -r .url pasta.json`
|
||||
curl -o testfile2 $link
|
||||
diff testfile testfile2
|
||||
echo "Testfile 2 matches"
|
||||
|
|
Loading…
Reference in a new issue