-/*
-paster -- paste service
-Copyright (C) 2021-2022 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
+// paster -- paste service
+// Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
"bufio"
"bytes"
"crypto/rand"
- "crypto/sha512"
_ "embed"
"encoding/base32"
"encoding/hex"
"html/template"
"io"
"os"
- "strconv"
+
+ "go.cypherpunks.ru/netstring/v2"
+ "lukechampine.com/blake3"
)
+const MaxExtLen = 9
+
var (
//go:embed asciicast.tmpl
ASCIICastHTMLTmplRaw string
maxSize := flag.Uint64("max-size", 1<<20, "Maximal upload size")
asciicastPath := flag.String("asciicast-path", "", "Generate HTMLs for .cast asciicasts, specify \"asciinema-player-v2.6.1\"")
flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: paster [options] URL [...]\n")
+ fmt.Fprintf(os.Stderr, "Usage: paster [options] URL [URL ...]\n")
flag.PrintDefaults()
}
flag.Parse()
flag.Usage()
os.Exit(1)
}
- var fn string
- r := bufio.NewReader(os.Stdin)
- b, err := r.ReadByte()
+ r := netstring.NewReader(os.Stdin)
+ size, err := r.Next()
if err != nil {
fatal(err.Error())
}
- if b != 'd' {
- fatal("bad bencode: no dictionary start")
+ if size > MaxExtLen {
+ fatal("too long extension length")
}
- buf := make([]byte, 21)
- ext := ".txt"
- var size uint64
-AnotherKey:
- if _, err = io.ReadFull(r, buf[:3]); err != nil {
+ data, err := io.ReadAll(r)
+ if err != nil {
fatal(err.Error())
}
- switch s := string(buf[:3]); s {
- case "1:e":
- if _, err = io.ReadFull(r, buf[:2]); err != nil {
- fatal(err.Error())
- }
- if buf[1] != ':' {
- fatal(`bad bencode: invalid "e" format`)
- }
- extLen, err := strconv.Atoi(string(buf[:1]))
- if err != nil {
- fatal(err.Error())
- }
- if _, err = io.ReadFull(r, buf[:extLen]); err != nil {
- fatal(err.Error())
- }
- ext = "." + string(buf[:extLen])
- goto AnotherKey
- case "1:v":
- n, err := r.Read(buf)
- if err != nil {
- fatal(err.Error())
- }
- i := bytes.IndexByte(buf[:n], ':')
- if i == -1 {
- fatal(`bad bencode: invalid "v" length`)
- }
- size, err = strconv.ParseUint(string(buf[:i]), 10, 64)
- if err != nil {
- fatal(err.Error())
- }
- if size == 0 {
- fatal("empty paste")
- }
- if size > *maxSize {
- fatal("too big")
- }
- buf = buf[i+1 : n]
- default:
- fatal("bad bencode: invalid key")
+ var ext string
+ if len(data) == 0 {
+ ext = ".txt"
+ } else {
+ ext = "." + string(data)
+ }
+ size, err = r.Next()
+ if err != nil {
+ fatal(err.Error())
+ }
+ if size == 0 {
+ fatal("empty paste")
+ }
+ if size > *maxSize {
+ fatal("too big")
}
rnd := make([]byte, 12)
if _, err = io.ReadFull(rand.Reader, rnd); err != nil {
fatal(err.Error())
}
- fn = "." + base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(rnd) +
- ext
+ fn := "." + base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(rnd) + ext
fd, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
if err != nil {
fatal(err.Error())
}
- h := sha512.New()
+ h := blake3.New(32, nil)
bfd := bufio.NewWriter(fd)
- mr := io.MultiReader(bytes.NewReader(buf), r)
mw := io.MultiWriter(bfd, h)
- if _, err = io.CopyN(mw, mr, int64(size-1)); err != nil {
+ buf := make([]byte, 1)
+ _, err = io.CopyN(mw, r, int64(size-1))
+ if err != nil {
goto Failed
}
- if _, err = mr.Read(buf[:1]); err != nil {
+ _, err = r.Read(buf)
+ if err != nil {
goto Failed
}
- if _, err = mw.Write(buf[:1]); err != nil {
+ _, err = mw.Write(buf)
+ if err != nil {
goto Failed
}
if (ext == ".txt" || ext == ".url") && buf[0] != '\n' {
- if err = bfd.WriteByte('\n'); err != nil {
+ err = bfd.WriteByte('\n')
+ if err != nil {
goto Failed
}
}
- if _, err = mr.Read(buf[:1]); err != nil {
- goto Failed
- }
- if buf[0] != 'e' {
- os.Remove(fn)
- fatal("bad bencode: no dictionary end")
- }
- if err = bfd.Flush(); err != nil {
+ err = bfd.Flush()
+ if err != nil {
goto Failed
}
- if err = fd.Close(); err != nil {
+ err = fd.Close()
+ if err != nil {
goto Failed
}
- if err = os.Rename(fn, fn[1:]); err != nil {
+ err = os.Rename(fn, fn[1:])
+ if err != nil {
goto Failed
}
for _, u := range flag.Args() {
fmt.Println(u + fn[1:])
}
- fmt.Println("SHA512/2:", hex.EncodeToString(h.Sum(nil)[:512/2/8]))
+ fmt.Println("BLAKE3-256:", hex.EncodeToString(h.Sum(nil)))
if ext == ".cast" && *asciicastPath != "" {
if err = asciicastHTML(*asciicastPath, fn[1:]); err != nil {
goto Failed