]> Sergey Matveev's repositories - paster.git/blob - main.go
Fix possible empty buf
[paster.git] / main.go
1 /*
2 paster -- paste service
3 Copyright (C) 2021-2022 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package main
19
20 import (
21         "bufio"
22         "bytes"
23         "crypto/rand"
24         "crypto/sha512"
25         _ "embed"
26         "encoding/base32"
27         "encoding/hex"
28         "flag"
29         "fmt"
30         "html/template"
31         "io"
32         "os"
33         "strconv"
34 )
35
36 var (
37         //go:embed asciicast.tmpl
38         ASCIICastHTMLTmplRaw string
39         ASCIICastHTMLTmpl    = template.Must(template.New("asciicast").Parse(
40                 ASCIICastHTMLTmplRaw,
41         ))
42 )
43
44 func fatal(s string) {
45         fmt.Println(s)
46         os.Exit(1)
47 }
48
49 func asciicastHTML(playerPath, cast string) error {
50         var buf bytes.Buffer
51         err := ASCIICastHTMLTmpl.Execute(&buf, struct {
52                 PlayerPath string
53                 Cast       string
54         }{
55                 PlayerPath: playerPath,
56                 Cast:       cast,
57         })
58         if err != nil {
59                 return err
60         }
61         fn := cast + ".html"
62         fd, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
63         if err != nil {
64                 return err
65         }
66         if _, err = fd.Write(buf.Bytes()); err != nil {
67                 os.Remove(fn)
68                 return err
69         }
70         return fd.Close()
71 }
72
73 func main() {
74         maxSize := flag.Uint64("max-size", 1<<20, "Maximal upload size")
75         asciicastPath := flag.String("asciicast-path", "", "Generate HTMLs for .cast asciicasts, specify \"asciinema-player-v2.6.1\"")
76         flag.Usage = func() {
77                 fmt.Fprintf(os.Stderr, "Usage: paster [options] URL [...]\n")
78                 flag.PrintDefaults()
79         }
80         flag.Parse()
81         if len(flag.Args()) == 0 {
82                 flag.Usage()
83                 os.Exit(1)
84         }
85         var fn string
86         r := bufio.NewReader(os.Stdin)
87         b, err := r.ReadByte()
88         if err != nil {
89                 fatal(err.Error())
90         }
91         if b != 'd' {
92                 fatal("bad bencode: no dictionary start")
93         }
94         buf := make([]byte, 21)
95         ext := ".txt"
96         var size uint64
97 AnotherKey:
98         if _, err = io.ReadFull(r, buf[:3]); err != nil {
99                 fatal(err.Error())
100         }
101         switch s := string(buf[:3]); s {
102         case "1:e":
103                 if _, err = io.ReadFull(r, buf[:2]); err != nil {
104                         fatal(err.Error())
105                 }
106                 if buf[1] != ':' {
107                         fatal(`bad bencode: invalid "e" format`)
108                 }
109                 extLen, err := strconv.Atoi(string(buf[:1]))
110                 if err != nil {
111                         fatal(err.Error())
112                 }
113                 if _, err = io.ReadFull(r, buf[:extLen]); err != nil {
114                         fatal(err.Error())
115                 }
116                 ext = "." + string(buf[:extLen])
117                 goto AnotherKey
118         case "1:v":
119                 n, err := r.Read(buf)
120                 if err != nil {
121                         fatal(err.Error())
122                 }
123                 i := bytes.IndexByte(buf[:n], ':')
124                 if i == -1 {
125                         fatal(`bad bencode: invalid "v" length`)
126                 }
127                 size, err = strconv.ParseUint(string(buf[:i]), 10, 64)
128                 if err != nil {
129                         fatal(err.Error())
130                 }
131                 if size == 0 {
132                         fatal("empty paste")
133                 }
134                 if size > *maxSize {
135                         fatal("too big")
136                 }
137                 buf = buf[i+1 : n]
138         default:
139                 fatal("bad bencode: invalid key")
140         }
141         rnd := make([]byte, 12)
142         if _, err = io.ReadFull(rand.Reader, rnd); err != nil {
143                 fatal(err.Error())
144         }
145         fn = "." + base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(rnd) + ext
146         fd, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
147         if err != nil {
148                 fatal(err.Error())
149         }
150         h := sha512.New()
151         bfd := bufio.NewWriter(fd)
152         mr := io.MultiReader(bytes.NewReader(buf), r)
153         mw := io.MultiWriter(bfd, h)
154         if _, err = io.CopyN(mw, mr, int64(size-1)); err != nil {
155                 goto Failed
156         }
157         if len(buf) == 0 {
158                 buf = append(buf, 0)
159         } else {
160                 buf = buf[:1]
161         }
162         if _, err = mr.Read(buf); err != nil {
163                 goto Failed
164         }
165         if _, err = mw.Write(buf); err != nil {
166                 goto Failed
167         }
168         if (ext == ".txt" || ext == ".url") && buf[0] != '\n' {
169                 if err = bfd.WriteByte('\n'); err != nil {
170                         goto Failed
171                 }
172         }
173         if _, err = mr.Read(buf); err != nil {
174                 goto Failed
175         }
176         if buf[0] != 'e' {
177                 os.Remove(fn)
178                 fatal("bad bencode: no dictionary end")
179         }
180         if err = bfd.Flush(); err != nil {
181                 goto Failed
182         }
183         if err = fd.Close(); err != nil {
184                 goto Failed
185         }
186         if err = os.Rename(fn, fn[1:]); err != nil {
187                 goto Failed
188         }
189         for _, u := range flag.Args() {
190                 fmt.Println(u + fn[1:])
191         }
192         fmt.Println("SHA512/2:", hex.EncodeToString(h.Sum(nil)[:512/2/8]))
193         if ext == ".cast" && *asciicastPath != "" {
194                 if err = asciicastHTML(*asciicastPath, fn[1:]); err != nil {
195                         goto Failed
196                 }
197                 for _, u := range flag.Args() {
198                         fmt.Println(u + fn[1:] + ".html")
199                 }
200         }
201         fmt.Fprintf(
202                 os.Stderr,
203                 "[%s]:%s %s %d\n",
204                 os.Getenv("TCPREMOTEIP"),
205                 os.Getenv("TCPREMOTEPORT"),
206                 fn[1:], size,
207         )
208         return
209 Failed:
210         os.Remove(fn)
211         fatal(err.Error())
212 }