-/*
-uploader -- simplest form file uploader
-Copyright (C) 2018-2021 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
+// uploader -- simplest form file uploader
+// Copyright (C) 2018-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
"fmt"
"html/template"
"io"
- "io/ioutil"
"log"
"mime"
"net"
"go.cypherpunks.ru/recfile"
"go.cypherpunks.ru/tai64n/v2"
- "golang.org/x/crypto/blake2b"
"golang.org/x/net/netutil"
+ "lukechampine.com/blake3"
)
const (
Example command line usage:
<pre>
$ curl -F file=@somedata.tar.gpg [-F comment="optional comment"] http://.../upload/
-$ b2sum somedata.tar.gpg # to verify BLAKE2b-512 checksum
+$ b3sum somedata.tar.gpg # to verify BLAKE3-256 checksum
</pre>
<form enctype="multipart/form-data" action="/upload/" method="post">
<label for="file">File to upload:</label><br/>
Jobs sync.WaitGroup
)
-func notify(tai, filename string, size int64, comment string) {
- defer Jobs.Done()
- if *NotifyToAddr == "" {
- return
- }
- var rec bytes.Buffer
- w := recfile.NewWriter(&rec)
- w.WriteFields(
- recfile.Field{Name: "TAI64N", Value: tai},
- recfile.Field{Name: "Size", Value: strconv.FormatInt(size, 10)},
- recfile.Field{Name: "Filename", Value: filename},
- )
- w.WriteFieldMultiline("Comment", strings.Split(comment, "\n"))
- cmd := exec.Command(SendmailCmd, *NotifyToAddr)
- cmd.Stdin = io.MultiReader(
- strings.NewReader(fmt.Sprintf(
- `From: %s
-To: %s
-Subject: %s
-MIME-Version: 1.0
-Content-Type: text/plain; charset=utf-8
-Content-Transfer-Encoding: base64
-
-`,
- *NotifyFromAddr,
- *NotifyToAddr,
- mime.BEncoding.Encode("UTF-8", fmt.Sprintf("%s (%d KiB)", filename, size/1024)),
- )),
- strings.NewReader(base64.StdEncoding.EncodeToString(rec.Bytes())),
- )
- cmd.Run()
-}
-
func upload(w http.ResponseWriter, r *http.Request) {
log.Println(r.RemoteAddr, "connected")
if r.Method == http.MethodGet {
log.Println(r.RemoteAddr, "non file form field")
return
}
- h, err := blake2b.New512(nil)
- if err != nil {
- panic(err)
- }
t := time.Now()
ts := new(tai64n.TAI64N)
ts.FromTime(t)
return
}
fdBuf := bufio.NewWriterSize(fd, WriteBufSize)
+ h := blake3.New(32, nil)
mw := io.MultiWriter(fdBuf, h)
n, err := io.Copy(mw, p)
if err != nil {
log.Println(r.RemoteAddr, tai, fnOrig, n, sum, err)
return
}
+
var rec bytes.Buffer
wr := recfile.NewWriter(&rec)
if _, err = wr.WriteFields(
log.Println(r.RemoteAddr, tai, fnOrig, n, sum, err)
return
}
- io.Copy(w, &rec)
- log.Println(r.RemoteAddr, tai, fnOrig, n, sum)
+ if _, err = w.Write(rec.Bytes()); err == nil {
+ log.Println(r.RemoteAddr, tai, fnOrig, n, sum)
+ } else {
+ log.Println(r.RemoteAddr, tai, fnOrig, n, sum, err)
+ return
+ }
- rec.Reset()
- wr = recfile.NewWriter(&rec)
if _, err = wr.WriteFields(
recfile.Field{Name: "Filename", Value: fnOrig},
); err != nil {
return
}
- var comment []byte
+ var commentLines []string
p, err = mr.NextPart()
- if err != nil || p.FormName() != CommentFieldName {
- goto Notify
+ if err == nil && p.FormName() == CommentFieldName {
+ comment, err := io.ReadAll(p)
+ if err == nil && len(comment) > 0 {
+ commentLines = strings.Split(string(comment), "\n")
+ wr.WriteFieldMultiline("Comment", commentLines)
+ }
}
- comment, err = ioutil.ReadAll(p)
- if err != nil || len(comment) == 0 {
- goto Notify
+
+ os.WriteFile(tai+".rec", rec.Bytes(), os.FileMode(0666))
+ if *NotifyToAddr == "" {
+ return
}
- wr.WriteFieldMultiline("Comment", strings.Split(string(comment), "\n"))
+ cmd := exec.Command(SendmailCmd, *NotifyToAddr)
+ cmd.Stdin = io.MultiReader(
+ strings.NewReader(fmt.Sprintf(
+ `From: %s
+To: %s
+Subject: %s
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: base64
-Notify:
+`,
+ *NotifyFromAddr,
+ *NotifyToAddr,
+ mime.BEncoding.Encode("UTF-8", fmt.Sprintf("%s (%d KiB)", fnOrig, n/1024)),
+ )),
+ strings.NewReader(base64.StdEncoding.EncodeToString(rec.Bytes())),
+ )
Jobs.Add(1)
- go notify(tai, fnOrig, n, string(comment))
- ioutil.WriteFile(tai+".rec", rec.Bytes(), os.FileMode(0666))
+ go func() {
+ cmd.Run()
+ Jobs.Done()
+ }()
}
func main() {
ReadHeaderTimeout: 10 * time.Second,
MaxHeaderBytes: 10 * (1 << 10),
}
- s.SetKeepAlivesEnabled(false)
http.HandleFunc("/upload/", upload)
if *doUCSPI {
+ s.SetKeepAlivesEnabled(false)
ln := &UCSPI{}
s.ConnState = connStater
err := s.Serve(ln)