]> Cypherpunks.ru repositories - gocheese.git/blob - upload.go
-auth-required option and optional :ro per-user attribute
[gocheese.git] / upload.go
1 // GoCheese -- Python private package repository and caching proxy
2 // Copyright (C) 2019-2024 Sergey Matveev <stargrave@stargrave.org>
3 //               2019-2024 Elena Balakhonova <balakhonova_e@riseup.net>
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 package main
18
19 import (
20         "bufio"
21         "bytes"
22         "crypto/sha256"
23         "encoding/hex"
24         "io"
25         "log"
26         "net/http"
27         "os"
28         "path/filepath"
29         "regexp"
30         "strings"
31         "time"
32
33         "go.cypherpunks.ru/recfile"
34 )
35
36 var NormalizationRe = regexp.MustCompilePOSIX("[-_.]+")
37
38 func serveUpload(w http.ResponseWriter, r *http.Request) {
39         user := r.Context().Value(CtxUserKey).(*User)
40         if user == nil {
41                 log.Println(r.RemoteAddr, "unauthorised")
42                 http.Error(w, "unauthorised", http.StatusUnauthorized)
43                 return
44         }
45         if user.ro {
46                 log.Println(r.RemoteAddr, "ro user", user.name)
47                 http.Error(w, "unauthorised", http.StatusUnauthorized)
48                 return
49         }
50
51         // Form parsing
52         var err error
53         if err = r.ParseMultipartForm(1 << 20); err != nil {
54                 http.Error(w, err.Error(), http.StatusBadRequest)
55                 return
56         }
57         pkgNames, exists := r.MultipartForm.Value["name"]
58         if !exists || len(pkgNames) != 1 {
59                 http.Error(w, "single name is expected in request", http.StatusBadRequest)
60                 return
61         }
62         pkgName := strings.ToLower(NormalizationRe.ReplaceAllString(pkgNames[0], "-"))
63         dirPath := filepath.Join(Root, pkgName)
64         now := time.Now().UTC()
65
66         var digestSHA256Expected []byte
67         if digestSHA256ExpectedHex, exists := r.MultipartForm.Value["sha256_digest"]; exists {
68                 digestSHA256Expected, err = hex.DecodeString(digestSHA256ExpectedHex[0])
69                 if err != nil {
70                         http.Error(w, "bad sha256_digest: "+err.Error(), http.StatusBadRequest)
71                         return
72                 }
73         }
74         var digestBLAKE2b256Expected []byte
75         if digestBLAKE2b256ExpectedHex, exists := r.MultipartForm.Value["blake2_256_digest"]; exists {
76                 digestBLAKE2b256Expected, err = hex.DecodeString(digestBLAKE2b256ExpectedHex[0])
77                 if err != nil {
78                         http.Error(w, "bad blake2_256_digest: "+err.Error(), http.StatusBadRequest)
79                         return
80                 }
81         }
82
83         // Checking is it internal package
84         if _, err = os.Stat(filepath.Join(dirPath, InternalFlag)); err != nil {
85                 log.Println(r.RemoteAddr, "non-internal package", pkgName)
86                 http.Error(w, "unknown internal package", http.StatusUnauthorized)
87                 return
88         }
89
90         for _, file := range r.MultipartForm.File["content"] {
91                 filename := file.Filename
92                 log.Println(r.RemoteAddr, "put", filename, "by", user.name)
93                 path := filepath.Join(dirPath, filename)
94                 if _, err = os.Stat(path); err == nil {
95                         log.Println(r.RemoteAddr, filename, "already exists")
96                         http.Error(w, "already exists", http.StatusBadRequest)
97                         return
98                 }
99                 if !mkdirForPkg(w, r, pkgName) {
100                         return
101                 }
102                 src, err := file.Open()
103                 if err != nil {
104                         log.Println("error", r.RemoteAddr, filename, err)
105                         http.Error(w, err.Error(), http.StatusInternalServerError)
106                         return
107                 }
108                 defer src.Close()
109                 dst, err := TempFile(dirPath)
110                 if err != nil {
111                         log.Println("error", r.RemoteAddr, filename, err)
112                         http.Error(w, err.Error(), http.StatusInternalServerError)
113                         return
114                 }
115                 dstBuf := bufio.NewWriter(dst)
116                 hasherSHA256 := sha256.New()
117                 hasherBLAKE2b256 := blake2b256New()
118                 wr := io.MultiWriter(hasherSHA256, hasherBLAKE2b256, dst)
119                 if _, err = io.Copy(wr, src); err != nil {
120                         log.Println("error", r.RemoteAddr, filename, err)
121                         os.Remove(dst.Name())
122                         dst.Close()
123                         http.Error(w, err.Error(), http.StatusInternalServerError)
124                         return
125                 }
126                 if err = dstBuf.Flush(); err != nil {
127                         log.Println("error", r.RemoteAddr, filename, err)
128                         os.Remove(dst.Name())
129                         dst.Close()
130                         http.Error(w, err.Error(), http.StatusInternalServerError)
131                         return
132                 }
133                 if !NoSync {
134                         if err = dst.Sync(); err != nil {
135                                 log.Println("error", r.RemoteAddr, filename, err)
136                                 os.Remove(dst.Name())
137                                 dst.Close()
138                                 http.Error(w, err.Error(), http.StatusInternalServerError)
139                                 return
140                         }
141                 }
142                 dst.Close()
143
144                 digestSHA256 := hasherSHA256.Sum(nil)
145                 digestBLAKE2b256 := hasherBLAKE2b256.Sum(nil)
146                 if digestSHA256Expected != nil {
147                         if bytes.Equal(digestSHA256Expected, digestSHA256) {
148                                 log.Println(r.RemoteAddr, filename, "good SHA256 checksum received")
149                         } else {
150                                 log.Println(r.RemoteAddr, filename, "bad SHA256 checksum received")
151                                 http.Error(w, "bad sha256 checksum", http.StatusBadRequest)
152                                 os.Remove(dst.Name())
153                                 return
154                         }
155                 }
156                 if digestBLAKE2b256Expected != nil {
157                         if bytes.Equal(digestBLAKE2b256Expected, digestBLAKE2b256) {
158                                 log.Println(r.RemoteAddr, filename, "good BLAKE2b-256 checksum received")
159                         } else {
160                                 log.Println(r.RemoteAddr, filename, "bad BLAKE2b-256 checksum received")
161                                 http.Error(w, "bad blake2b_256 checksum", http.StatusBadRequest)
162                                 os.Remove(dst.Name())
163                                 return
164                         }
165                 }
166
167                 if err = os.Rename(dst.Name(), path); err != nil {
168                         log.Println("error", r.RemoteAddr, path, err)
169                         http.Error(w, err.Error(), http.StatusInternalServerError)
170                         return
171                 }
172                 if err = DirSync(dirPath); err != nil {
173                         log.Println("error", r.RemoteAddr, dirPath, err)
174                         http.Error(w, err.Error(), http.StatusInternalServerError)
175                         return
176                 }
177                 if err = WriteFileSync(dirPath, path+"."+HashAlgoSHA256, digestSHA256, now); err != nil {
178                         log.Println("error", r.RemoteAddr, path+"."+HashAlgoSHA256, err)
179                         http.Error(w, err.Error(), http.StatusInternalServerError)
180                         return
181                 }
182                 if err = WriteFileSync(dirPath, path+"."+HashAlgoBLAKE2b256, digestBLAKE2b256, now); err != nil {
183                         log.Println("error", r.RemoteAddr, path+"."+HashAlgoBLAKE2b256, err)
184                         http.Error(w, err.Error(), http.StatusInternalServerError)
185                         return
186                 }
187         }
188
189         var buf bytes.Buffer
190         wr := recfile.NewWriter(&buf)
191         for _, m := range MDFormToRecField {
192                 formField, recField := m[0], m[1]
193                 if vs, exists := r.MultipartForm.Value[formField]; exists {
194                         for _, v := range vs {
195                                 lines := strings.Split(v, "\n")
196                                 if len(lines) > 1 {
197                                         _, err = wr.WriteFieldMultiline(recField, lines)
198                                 } else {
199                                         _, err = wr.WriteFields(recfile.Field{
200                                                 Name:  recField,
201                                                 Value: lines[0],
202                                         })
203                                 }
204                                 if err != nil {
205                                         log.Fatal(err)
206                                 }
207                         }
208                 }
209         }
210         path := filepath.Join(dirPath, MDFile)
211         if err = WriteFileSync(dirPath, path, buf.Bytes(), now); err != nil {
212                 log.Println("error", r.RemoteAddr, path, err)
213                 http.Error(w, err.Error(), http.StatusInternalServerError)
214                 return
215         }
216 }