X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=main.go;h=720ee4f123ff94da11bdecb9c2544807a2995518;hb=HEAD;hp=030cb1fea071af242ed2f46b8d5ced154c9210bc;hpb=96d49430705d94a7e28126ccf19c56645c6e517e;p=uploader.git diff --git a/main.go b/main.go index 030cb1f..720ee4f 100644 --- a/main.go +++ b/main.go @@ -1,19 +1,17 @@ -/* -uploader -- simplest form file uploader -Copyright (C) 2018-2020 Sergey Matveev - -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 . -*/ +// uploader -- simplest form file uploader +// Copyright (C) 2018-2024 Sergey Matveev +// +// 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 . package main @@ -26,7 +24,6 @@ import ( "fmt" "html/template" "io" - "io/ioutil" "log" "mime" "net" @@ -35,12 +32,13 @@ import ( "os/exec" "strconv" "strings" + "sync" "time" "go.cypherpunks.ru/recfile" - "go.cypherpunks.ru/tai64n" - "golang.org/x/crypto/blake2b" + "go.cypherpunks.ru/tai64n/v2" "golang.org/x/net/netutil" + "lukechampine.com/blake3" ) const ( @@ -54,11 +52,10 @@ const ( var ( Index = template.Must(template.New("index").Parse(` Upload -
 Example command line usage:
-
-    $ curl -F file=@somedata.tar.gpg [-F comment="optional comment"] http://.../upload/
-    $ b2sum -a blake2b somedata.tar.gpg # to verify checksum
+
+$ curl -F file=@somedata.tar.gpg [-F comment="optional comment"] http://.../upload/
+$ b3sum somedata.tar.gpg # to verify BLAKE3-256 checksum
 

@@ -69,40 +66,9 @@ Example command line usage:
`)) NotifyFromAddr *string NotifyToAddr *string + Jobs sync.WaitGroup ) -func notify(tai, filename string, size int64, comment string) { - 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 { @@ -125,21 +91,18 @@ func upload(w http.ResponseWriter, r *http.Request) { 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) - tai64n.FromTime(t, ts) - tai := ts.Encode()[1:] + ts.FromTime(t) + tai := tai64n.Encode(ts[:])[1:] fnOrig := p.FileName() - fd, err := os.OpenFile(tai+".part", os.O_WRONLY|os.O_CREATE, 0600) + fd, err := os.OpenFile(tai+".part", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) if err != nil { log.Println(r.RemoteAddr, tai, fnOrig, err) 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 { @@ -170,6 +133,7 @@ func upload(w http.ResponseWriter, r *http.Request) { log.Println(r.RemoteAddr, tai, fnOrig, n, sum, err) return } + var rec bytes.Buffer wr := recfile.NewWriter(&rec) if _, err = wr.WriteFields( @@ -180,41 +144,95 @@ func upload(w http.ResponseWriter, r *http.Request) { log.Println(r.RemoteAddr, tai, fnOrig, n, sum, err) return } - io.Copy(w, &rec) - log.Println(r.RemoteAddr, tai, fnOrig, n, sum) - p, err = mr.NextPart() - if err != nil || p.FormName() != CommentFieldName { - go notify(fnOrig, tai, n, "") + 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 } - comment, err := ioutil.ReadAll(p) - if err != nil || len(comment) == 0 { - go notify(tai, fnOrig, n, "") + + if _, err = wr.WriteFields( + recfile.Field{Name: "Filename", Value: fnOrig}, + ); err != nil { + log.Println(r.RemoteAddr, tai, fnOrig, n, sum, err) + return + } + + var commentLines []string + p, err = mr.NextPart() + 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) + } + } + + os.WriteFile(tai+".rec", rec.Bytes(), os.FileMode(0666)) + if *NotifyToAddr == "" { return } - ioutil.WriteFile(tai+".txt", comment, os.FileMode(0600)) - go notify(tai, fnOrig, n, string(comment)) + 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)", fnOrig, n/1024)), + )), + strings.NewReader(base64.StdEncoding.EncodeToString(rec.Bytes())), + ) + Jobs.Add(1) + go func() { + cmd.Run() + Jobs.Done() + }() } func main() { + doUCSPI := flag.Bool("ucspi", false, "Work as UCSPI-TCP service") bind := flag.String("bind", "[::]:8086", "Address to bind to") conns := flag.Int("conns", 2, "Maximal number of connections") - NotifyFromAddr = flag.String("notify-from", "uploader@example.com", "Address notifications are send to") - NotifyToAddr = flag.String("notify-to", "", "Address notifications are send from") + NotifyFromAddr = flag.String("notify-from", "uploader@example.com", "Address notifications are sent to") + NotifyToAddr = flag.String("notify-to", "", "Address notifications are sent from") flag.Parse() + log.SetFlags(log.Lshortfile) + if !*doUCSPI { + log.SetOutput(os.Stdout) + } if len(*NotifyFromAddr) == 0 && len(*NotifyToAddr) > 0 { log.Fatalln("notify-from address can not be empty, if notify-to is set") } - ln, err := net.Listen("tcp", *bind) - if err != nil { - log.Fatalln(err) - } - log.Println("listening", *bind) - ln = netutil.LimitListener(ln, *conns) s := &http.Server{ ReadHeaderTimeout: 10 * time.Second, MaxHeaderBytes: 10 * (1 << 10), } http.HandleFunc("/upload/", upload) - s.Serve(ln) + if *doUCSPI { + s.SetKeepAlivesEnabled(false) + ln := &UCSPI{} + s.ConnState = connStater + err := s.Serve(ln) + if _, ok := err.(UCSPIAlreadyAccepted); !ok { + log.Fatalln(err) + } + Jobs.Wait() + } else { + ln, err := net.Listen("tcp", *bind) + if err != nil { + log.Fatalln(err) + } + log.Println("listening", *bind) + ln = netutil.LimitListener(ln, *conns) + if err = s.Serve(ln); err != nil { + log.Fatalln(err) + } + } }