go-paste/paste/paste.go

189 lines
3.0 KiB
Go
Raw Normal View History

2023-10-19 17:05:41 -06:00
package paste
import (
"bufio"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"go.sour.is/paste/src/pkg/readutil"
"golang.org/x/sys/unix"
"sour.is/x/toolbox/log"
)
type Paste struct {
store string
}
func New(store string) (p *Paste, err error) {
p = &Paste{}
p.store = store
if !chkStore(p.store) {
err = fmt.Errorf("paste: store location [%s] does not exist or is not writable", p.store)
return
}
return
}
func chkStore(path string) bool {
file, err := os.Stat(path)
if err != nil && os.IsNotExist(err) {
err = os.MkdirAll(path, 0744)
if err != nil {
return false
}
file, err = os.Stat(path)
}
if err != nil {
return false
}
if !file.IsDir() {
return false
}
if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
return false
}
return true
}
func chkFile(path string) bool {
file, err := os.Stat(path)
if err != nil {
return false
}
if file.IsDir() {
return false
}
if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
return false
}
return true
}
func chkGone(path string) bool {
file, err := os.Stat(path)
if err != nil {
return true
}
if file.Size() == 0 {
return true
}
return false
}
func (p *Paste) Get(w io.Writer, id string) error {
fname := filepath.Join(p.store, id)
if !chkFile(fname) {
return ErrNotFound
}
if chkGone(fname) {
return ErrGone
}
f, err := os.Open(fname)
if err != nil {
return fmt.Errorf("%w: %w", ErrReadingContent, err)
}
defer f.Close()
pr := readutil.NewPreviewReader(f)
keep := true
scanner := bufio.NewScanner(pr)
for scanner.Scan() {
txt := scanner.Text()
log.Debug(txt)
// End of header found
if txt == "" {
break
}
if strings.HasPrefix(txt, "exp:") {
now := time.Now().Unix()
exp, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(txt, "exp:")), 10, 64)
if err != nil {
return fmt.Errorf("%w: %w", ErrReadingContent, err)
}
log.Debugf("%d > %d", now, exp)
if now > exp {
deleteFile(fname)
return ErrGone
}
}
if strings.HasPrefix(txt, "burn:") {
burn := strings.TrimSpace(strings.TrimPrefix(txt, "burn:"))
if burn == "true" {
keep = false
}
}
}
_, _ = io.Copy(w, pr.Drain())
if !keep {
deleteFile(fname)
}
return nil
}
func (p *Paste) Set(r io.ReadCloser) (string, error) {
defer r.Close()
rdr := io.LimitReader(r, 1048576)
s256 := sha256.New()
tmp, _ := os.CreateTemp(p.store, "arch-")
defer os.Remove(tmp.Name())
m := io.MultiWriter(s256, tmp)
if _, err := io.Copy(m, rdr); err != nil {
return "", fmt.Errorf("%w: %w", ErrBadInput, err)
}
tmp.Close()
id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:])
fname := filepath.Join(p.store, id)
_ = os.Rename(tmp.Name(), fname)
return id, nil
}
func deleteFile(path string) {
_ = ioutil.WriteFile(path, nil, 0644)
}
var (
ErrNotFound = errors.New("not found")
ErrGone = errors.New("gone")
ErrReadingContent = errors.New("reading content")
ErrBadInput = errors.New("bad input")
)