]> Cypherpunks.ru repositories - gocheese.git/blob - upload.go
Fatal(err) is enough
[gocheese.git] / upload.go
1 /*
2 GoCheese -- Python private package repository and caching proxy
3 Copyright (C) 2019-2023 Sergey Matveev <stargrave@stargrave.org>
4               2019-2023 Elena Balakhonova <balakhonova_e@riseup.net>
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, version 3 of the License.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package main
20
21 import (
22         "bufio"
23         "bytes"
24         "crypto/sha256"
25         "encoding/hex"
26         "io"
27         "log"
28         "net/http"
29         "os"
30         "path/filepath"
31         "regexp"
32         "strings"
33         "time"
34
35         "go.cypherpunks.ru/recfile"
36 )
37
38 var NormalizationRe = regexp.MustCompilePOSIX("[-_.]+")
39
40 func serveUpload(w http.ResponseWriter, r *http.Request) {
41         // Authentication
42         username, password, ok := r.BasicAuth()
43         if !ok {
44                 log.Println(r.RemoteAddr, "unauthenticated", username)
45                 http.Error(w, "unauthenticated", http.StatusUnauthorized)
46                 return
47         }
48         PasswordsM.RLock()
49         auther, ok := Passwords[username]
50         PasswordsM.RUnlock()
51         if !ok || !auther.Auth(password) {
52                 log.Println(r.RemoteAddr, "unauthenticated", username)
53                 http.Error(w, "unauthenticated", http.StatusUnauthorized)
54                 return
55         }
56
57         // Form parsing
58         var err error
59         if err = r.ParseMultipartForm(1 << 20); err != nil {
60                 http.Error(w, err.Error(), http.StatusBadRequest)
61                 return
62         }
63         pkgNames, exists := r.MultipartForm.Value["name"]
64         if !exists || len(pkgNames) != 1 {
65                 http.Error(w, "single name is expected in request", http.StatusBadRequest)
66                 return
67         }
68         pkgName := strings.ToLower(NormalizationRe.ReplaceAllString(pkgNames[0], "-"))
69         dirPath := filepath.Join(Root, pkgName)
70         now := time.Now().UTC()
71
72         var digestSHA256Expected []byte
73         if digestSHA256ExpectedHex, exists := r.MultipartForm.Value["sha256_digest"]; exists {
74                 digestSHA256Expected, err = hex.DecodeString(digestSHA256ExpectedHex[0])
75                 if err != nil {
76                         http.Error(w, "bad sha256_digest: "+err.Error(), http.StatusBadRequest)
77                         return
78                 }
79         }
80         var digestBLAKE2b256Expected []byte
81         if digestBLAKE2b256ExpectedHex, exists := r.MultipartForm.Value["blake2_256_digest"]; exists {
82                 digestBLAKE2b256Expected, err = hex.DecodeString(digestBLAKE2b256ExpectedHex[0])
83                 if err != nil {
84                         http.Error(w, "bad blake2_256_digest: "+err.Error(), http.StatusBadRequest)
85                         return
86                 }
87         }
88
89         // Checking is it internal package
90         if _, err = os.Stat(filepath.Join(dirPath, InternalFlag)); err != nil {
91                 log.Println(r.RemoteAddr, "non-internal package", pkgName)
92                 http.Error(w, "unknown internal package", http.StatusUnauthorized)
93                 return
94         }
95
96         for _, file := range r.MultipartForm.File["content"] {
97                 filename := file.Filename
98                 log.Println(r.RemoteAddr, "put", filename, "by", username)
99                 path := filepath.Join(dirPath, filename)
100                 if _, err = os.Stat(path); err == nil {
101                         log.Println(r.RemoteAddr, filename, "already exists")
102                         http.Error(w, "already exists", http.StatusBadRequest)
103                         return
104                 }
105                 if !mkdirForPkg(w, r, pkgName) {
106                         return
107                 }
108                 src, err := file.Open()
109                 if err != nil {
110                         log.Println("error", r.RemoteAddr, filename, err)
111                         http.Error(w, err.Error(), http.StatusInternalServerError)
112                         return
113                 }
114                 defer src.Close()
115                 dst, err := TempFile(dirPath)
116                 if err != nil {
117                         log.Println("error", r.RemoteAddr, filename, err)
118                         http.Error(w, err.Error(), http.StatusInternalServerError)
119                         return
120                 }
121                 dstBuf := bufio.NewWriter(dst)
122                 hasherSHA256 := sha256.New()
123                 hasherBLAKE2b256 := blake2b256New()
124                 wr := io.MultiWriter(hasherSHA256, hasherBLAKE2b256, dst)
125                 if _, err = io.Copy(wr, src); err != nil {
126                         log.Println("error", r.RemoteAddr, filename, err)
127                         os.Remove(dst.Name())
128                         dst.Close()
129                         http.Error(w, err.Error(), http.StatusInternalServerError)
130                         return
131                 }
132                 if err = dstBuf.Flush(); err != nil {
133                         log.Println("error", r.RemoteAddr, filename, err)
134                         os.Remove(dst.Name())
135                         dst.Close()
136                         http.Error(w, err.Error(), http.StatusInternalServerError)
137                         return
138                 }
139                 if !NoSync {
140                         if err = dst.Sync(); err != nil {
141                                 log.Println("error", r.RemoteAddr, filename, err)
142                                 os.Remove(dst.Name())
143                                 dst.Close()
144                                 http.Error(w, err.Error(), http.StatusInternalServerError)
145                                 return
146                         }
147                 }
148                 dst.Close()
149
150                 digestSHA256 := hasherSHA256.Sum(nil)
151                 digestBLAKE2b256 := hasherBLAKE2b256.Sum(nil)
152                 if digestSHA256Expected != nil {
153                         if bytes.Equal(digestSHA256Expected, digestSHA256) {
154                                 log.Println(r.RemoteAddr, filename, "good SHA256 checksum received")
155                         } else {
156                                 log.Println(r.RemoteAddr, filename, "bad SHA256 checksum received")
157                                 http.Error(w, "bad sha256 checksum", http.StatusBadRequest)
158                                 os.Remove(dst.Name())
159                                 return
160                         }
161                 }
162                 if digestBLAKE2b256Expected != nil {
163                         if bytes.Equal(digestBLAKE2b256Expected, digestBLAKE2b256) {
164                                 log.Println(r.RemoteAddr, filename, "good BLAKE2b-256 checksum received")
165                         } else {
166                                 log.Println(r.RemoteAddr, filename, "bad BLAKE2b-256 checksum received")
167                                 http.Error(w, "bad blake2b_256 checksum", http.StatusBadRequest)
168                                 os.Remove(dst.Name())
169                                 return
170                         }
171                 }
172
173                 if err = os.Rename(dst.Name(), path); err != nil {
174                         log.Println("error", r.RemoteAddr, path, err)
175                         http.Error(w, err.Error(), http.StatusInternalServerError)
176                         return
177                 }
178                 if err = DirSync(dirPath); err != nil {
179                         log.Println("error", r.RemoteAddr, dirPath, err)
180                         http.Error(w, err.Error(), http.StatusInternalServerError)
181                         return
182                 }
183                 if err = WriteFileSync(dirPath, path+"."+HashAlgoSHA256, digestSHA256, now); err != nil {
184                         log.Println("error", r.RemoteAddr, path+"."+HashAlgoSHA256, err)
185                         http.Error(w, err.Error(), http.StatusInternalServerError)
186                         return
187                 }
188                 if err = WriteFileSync(dirPath, path+"."+HashAlgoBLAKE2b256, digestBLAKE2b256, now); err != nil {
189                         log.Println("error", r.RemoteAddr, path+"."+HashAlgoBLAKE2b256, err)
190                         http.Error(w, err.Error(), http.StatusInternalServerError)
191                         return
192                 }
193         }
194
195         var buf bytes.Buffer
196         wr := recfile.NewWriter(&buf)
197         for _, m := range MDFormToRecField {
198                 formField, recField := m[0], m[1]
199                 if vs, exists := r.MultipartForm.Value[formField]; exists {
200                         for _, v := range vs {
201                                 lines := strings.Split(v, "\n")
202                                 if len(lines) > 1 {
203                                         _, err = wr.WriteFieldMultiline(recField, lines)
204                                 } else {
205                                         _, err = wr.WriteFields(recfile.Field{
206                                                 Name:  recField,
207                                                 Value: lines[0],
208                                         })
209                                 }
210                                 if err != nil {
211                                         log.Fatal(err)
212                                 }
213                         }
214                 }
215         }
216         path := filepath.Join(dirPath, MDFile)
217         if err = WriteFileSync(dirPath, path, buf.Bytes(), now); err != nil {
218                 log.Println("error", r.RemoteAddr, path, err)
219                 http.Error(w, err.Error(), http.StatusInternalServerError)
220                 return
221         }
222 }