189 lines
3.0 KiB
Go
189 lines
3.0 KiB
Go
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")
|
|
)
|