/* paster -- paste service Copyright (C) 2021-2022 Sergey Matveev 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 . */ package main import ( "bufio" "bytes" "crypto/rand" "crypto/sha512" _ "embed" "encoding/base32" "encoding/hex" "flag" "fmt" "html/template" "io" "os" "strconv" ) 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() { 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") flag.PrintDefaults() } flag.Parse() if len(flag.Args()) == 0 { flag.Usage() os.Exit(1) } var fn string r := bufio.NewReader(os.Stdin) b, err := r.ReadByte() if err != nil { fatal(err.Error()) } if b != 'd' { fatal("bad bencode: no dictionary start") } buf := make([]byte, 21) ext := ".txt" var size uint64 AnotherKey: if _, err = io.ReadFull(r, buf[:3]); 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") } 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 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() 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 { goto Failed } if len(buf) == 0 { buf = append(buf, 0) } else { buf = buf[:1] } if _, err = mr.Read(buf); err != nil { goto Failed } if _, err = mw.Write(buf); err != nil { goto Failed } if (ext == ".txt" || ext == ".url") && buf[0] != '\n' { if err = bfd.WriteByte('\n'); err != nil { goto Failed } } if _, err = mr.Read(buf); err != nil { goto Failed } if buf[0] != 'e' { os.Remove(fn) fatal("bad bencode: no dictionary end") } if err = bfd.Flush(); err != nil { goto Failed } if err = fd.Close(); err != nil { goto Failed } if err = os.Rename(fn, fn[1:]); 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])) 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", os.Getenv("TCPREMOTEIP"), os.Getenv("TCPREMOTEPORT"), fn[1:], size, ) return Failed: os.Remove(fn) fatal(err.Error()) }