]> Sergey Matveev's repositories - paster.git/blobdiff - main.go
Trivial style fixes
[paster.git] / main.go
diff --git a/main.go b/main.go
index 9cb209a6d3c9d5acece3083d151f2ba646b69689..17957374a6e4bbf799f3930798b1a6dc7b2f3404 100644 (file)
--- a/main.go
+++ b/main.go
@@ -1,19 +1,17 @@
-/*
-paster -- paste service
-Copyright (C) 2021 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
 
@@ -21,166 +19,153 @@ import (
        "bufio"
        "bytes"
        "crypto/rand"
-       "crypto/sha512"
+       _ "embed"
        "encoding/base32"
        "encoding/hex"
-       "errors"
        "flag"
        "fmt"
+       "html/template"
        "io"
-       "log"
        "os"
-       "strconv"
-       "time"
+
+       "go.cypherpunks.ru/netstring/v2"
+       "lukechampine.com/blake3"
+)
+
+const MaxExtLen = 9
+
+var (
+       //go:embed asciicast.tmpl
+       ASCIICastHTMLTmplRaw string
+       ASCIICastHTMLTmpl    = template.Must(template.New("asciicast").Parse(
+               ASCIICastHTMLTmplRaw,
+       ))
 )
 
+func fatal(s string) {
+       fmt.Println(s)
+       os.Exit(1)
+}
+
+func asciicastHTML(playerPath, cast string) error {
+       var buf bytes.Buffer
+       err := ASCIICastHTMLTmpl.Execute(&buf, struct {
+               PlayerPath string
+               Cast       string
+       }{
+               PlayerPath: playerPath,
+               Cast:       cast,
+       })
+       if err != nil {
+               return err
+       }
+       fn := cast + ".html"
+       fd, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
+       if err != nil {
+               return err
+       }
+       if _, err = fd.Write(buf.Bytes()); err != nil {
+               os.Remove(fn)
+               return err
+       }
+       return fd.Close()
+}
+
 func main() {
-       maxSecs := flag.Uint("max-secs", 60, "Maximal time of aliveness (0=disable)")
        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()
-       log.SetFlags(0)
-       log.SetOutput(os.Stdout)
        if len(flag.Args()) == 0 {
                flag.Usage()
                os.Exit(1)
        }
-       if *maxSecs > 0 {
-               go func() {
-                       time.Sleep(time.Duration(*maxSecs) * time.Second)
-                       log.Fatalln(errors.New("max aliveness time is reached"))
-               }()
+       r := netstring.NewReader(os.Stdin)
+       size, err := r.Next()
+       if err != nil {
+               fatal(err.Error())
        }
-       r := bufio.NewReader(os.Stdin)
-       b, err := r.ReadByte()
+       if size > MaxExtLen {
+               fatal("too long extension length")
+       }
+       data, err := io.ReadAll(r)
        if err != nil {
-               log.Fatalln(err)
-       }
-       if b != 'd' {
-               log.Fatalln(errors.New("bad bencode: no dictionary start"))
-       }
-       buf := make([]byte, 21)
-       ext := ".txt"
-       var size uint64
-AnotherKey:
-       if _, err = io.ReadFull(r, buf[:3]); err != nil {
-               log.Fatalln(err)
-       }
-       switch s := string(buf[:3]); s {
-       case "1:e":
-               if _, err = io.ReadFull(r, buf[:2]); err != nil {
-                       log.Fatalln(err)
-               }
-               extLen := 0
-               switch s = string(buf[:2]); s {
-               case "1:":
-                       extLen = 1
-               case "2:":
-                       extLen = 2
-               case "3:":
-                       extLen = 3
-               case "4:":
-                       extLen = 4
-               case "5:":
-                       extLen = 5
-               case "6:":
-                       extLen = 6
-               case "7:":
-                       extLen = 7
-               case "8:":
-                       extLen = 8
-               case "9:":
-                       extLen = 9
-               default:
-                       log.Fatalln(errors.New("bad bencode: invalid \"e\" length"))
-               }
-               if _, err = io.ReadFull(r, buf[:extLen]); err != nil {
-                       log.Fatalln(err)
-               }
-               ext = "." + string(buf[:extLen])
-               goto AnotherKey
-       case "1:v":
-               n, err := r.Read(buf)
-               if err != nil {
-                       log.Fatalln(err)
-               }
-               i := bytes.IndexByte(buf[:n], ':')
-               if i == -1 {
-                       log.Fatalln(errors.New("bad bencode: invalid \"v\" length"))
-               }
-               size, err = strconv.ParseUint(string(buf[:i]), 10, 64)
-               if err != nil {
-                       log.Fatalln(err)
-               }
-               if size == 0 {
-                       log.Fatalln(errors.New("empty paste"))
-               }
-               if size > *maxSize {
-                       log.Fatalln(errors.New("too big"))
-               }
-               buf = buf[i+1 : n]
-       default:
-               log.Fatalln(errors.New("bad bencode: invalid key"))
+               fatal(err.Error())
+       }
+       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 {
-               log.Fatalln(err)
+               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 {
-               log.Fatalln(err)
+               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 {
-               os.Remove(fn)
-               log.Fatalln(err)
+       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 {
-               os.Remove(fn)
-               log.Fatalln(err)
+       _, err = r.Read(buf)
+       if err != nil {
+               goto Failed
        }
-       if _, err = mw.Write(buf[:1]); err != nil {
-               os.Remove(fn)
-               log.Fatalln(err)
+       _, err = mw.Write(buf)
+       if err != nil {
+               goto Failed
        }
        if (ext == ".txt" || ext == ".url") && buf[0] != '\n' {
-               if err = bfd.WriteByte('\n'); err != nil {
-                       os.Remove(fn)
-                       log.Fatalln(err)
+               err = bfd.WriteByte('\n')
+               if err != nil {
+                       goto Failed
                }
        }
-       if _, err = mr.Read(buf[:1]); err != nil {
-               os.Remove(fn)
-               log.Fatalln(err)
-       }
-       if buf[0] != 'e' {
-               os.Remove(fn)
-               log.Fatalln(errors.New("bad bencode: no dictionary end"))
-       }
-       if err = bfd.Flush(); err != nil {
-               os.Remove(fn)
-               log.Fatalln(err)
+       err = bfd.Flush()
+       if err != nil {
+               goto Failed
        }
-       if err = fd.Close(); err != nil {
-               os.Remove(fn)
-               log.Fatalln(err)
+       err = fd.Close()
+       if err != nil {
+               goto Failed
        }
-       if err = os.Rename(fn, fn[1:]); err != nil {
-               os.Remove(fn)
-               log.Fatalln(err)
+       err = os.Rename(fn, fn[1:])
+       if err != nil {
+               goto Failed
        }
        for _, u := range flag.Args() {
                fmt.Println(u + fn[1:])
        }
-       fmt.Println(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
+               }
+               for _, u := range flag.Args() {
+                       fmt.Println(u + fn[1:] + ".html")
+               }
+       }
        fmt.Fprintf(
                os.Stderr,
                "[%s]:%s %s %d\n",
@@ -188,4 +173,8 @@ AnotherKey:
                os.Getenv("TCPREMOTEPORT"),
                fn[1:], size,
        )
+       return
+Failed:
+       os.Remove(fn)
+       fatal(err.Error())
 }