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") )