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