]> Sergey Matveev's repositories - paster.git/blob - main.go
139b5366d31f9caf6c75b001de1f671ab365877b
[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         "encoding/base32"
26         "encoding/hex"
27         "flag"
28         "fmt"
29         "io"
30         "os"
31         "strconv"
32 )
33
34 func fatal(s string) {
35         fmt.Println(s)
36         os.Exit(1)
37 }
38
39 func main() {
40         maxSize := flag.Uint64("max-size", 1<<20, "Maximal upload size")
41         flag.Usage = func() {
42                 fmt.Fprintf(os.Stderr, "Usage: paster [options] URL [...]\n")
43                 flag.PrintDefaults()
44         }
45         flag.Parse()
46         if len(flag.Args()) == 0 {
47                 flag.Usage()
48                 os.Exit(1)
49         }
50         var fn string
51         r := bufio.NewReader(os.Stdin)
52         b, err := r.ReadByte()
53         if err != nil {
54                 fatal(err.Error())
55         }
56         if b != 'd' {
57                 fatal("bad bencode: no dictionary start")
58         }
59         buf := make([]byte, 21)
60         ext := ".txt"
61         var size uint64
62 AnotherKey:
63         if _, err = io.ReadFull(r, buf[:3]); err != nil {
64                 fatal(err.Error())
65         }
66         switch s := string(buf[:3]); s {
67         case "1:e":
68                 if _, err = io.ReadFull(r, buf[:2]); err != nil {
69                         fatal(err.Error())
70                 }
71                 if buf[1] != ':' {
72                         fatal(`bad bencode: invalid "e" format`)
73                 }
74                 extLen, err := strconv.Atoi(string(buf[:1]))
75                 if err != nil {
76                         fatal(err.Error())
77                 }
78                 if _, err = io.ReadFull(r, buf[:extLen]); err != nil {
79                         fatal(err.Error())
80                 }
81                 ext = "." + string(buf[:extLen])
82                 goto AnotherKey
83         case "1:v":
84                 n, err := r.Read(buf)
85                 if err != nil {
86                         fatal(err.Error())
87                 }
88                 i := bytes.IndexByte(buf[:n], ':')
89                 if i == -1 {
90                         fatal(`bad bencode: invalid "v" length`)
91                 }
92                 size, err = strconv.ParseUint(string(buf[:i]), 10, 64)
93                 if err != nil {
94                         fatal(err.Error())
95                 }
96                 if size == 0 {
97                         fatal("empty paste")
98                 }
99                 if size > *maxSize {
100                         fatal("too big")
101                 }
102                 buf = buf[i+1 : n]
103         default:
104                 fatal("bad bencode: invalid key")
105         }
106         rnd := make([]byte, 12)
107         if _, err = io.ReadFull(rand.Reader, rnd); err != nil {
108                 fatal(err.Error())
109         }
110         fn = "." + base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(rnd) +
111                 ext
112         fd, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
113         if err != nil {
114                 fatal(err.Error())
115         }
116         h := sha512.New()
117         bfd := bufio.NewWriter(fd)
118         mr := io.MultiReader(bytes.NewReader(buf), r)
119         mw := io.MultiWriter(bfd, h)
120         if _, err = io.CopyN(mw, mr, int64(size-1)); err != nil {
121                 goto Failed
122         }
123         if _, err = mr.Read(buf[:1]); err != nil {
124                 goto Failed
125         }
126         if _, err = mw.Write(buf[:1]); err != nil {
127                 goto Failed
128         }
129         if (ext == ".txt" || ext == ".url") && buf[0] != '\n' {
130                 if err = bfd.WriteByte('\n'); err != nil {
131                         goto Failed
132                 }
133         }
134         if _, err = mr.Read(buf[:1]); err != nil {
135                 goto Failed
136         }
137         if buf[0] != 'e' {
138                 os.Remove(fn)
139                 fatal("bad bencode: no dictionary end")
140         }
141         if err = bfd.Flush(); err != nil {
142                 goto Failed
143         }
144         if err = fd.Close(); err != nil {
145                 goto Failed
146         }
147         if err = os.Rename(fn, fn[1:]); err != nil {
148                 goto Failed
149         }
150         for _, u := range flag.Args() {
151                 fmt.Println(u + fn[1:])
152         }
153         fmt.Println(hex.EncodeToString(h.Sum(nil)[:512/2/8]))
154         fmt.Fprintf(
155                 os.Stderr,
156                 "[%s]:%s %s %d\n",
157                 os.Getenv("TCPREMOTEIP"),
158                 os.Getenv("TCPREMOTEPORT"),
159                 fn[1:], size,
160         )
161         return
162 Failed:
163         os.Remove(fn)
164         fatal(err.Error())
165 }