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