diff options
Diffstat (limited to 'cmd/server')
| -rw-r--r-- | cmd/server/main.go | 119 |
1 files changed, 102 insertions, 17 deletions
diff --git a/cmd/server/main.go b/cmd/server/main.go index 5998fb7..58dc87d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "context" "crypto/hmac" "crypto/rand" @@ -19,6 +20,7 @@ import ( "net/http" "net/url" "os" + "os/exec" "path" "strings" "sync" @@ -29,7 +31,6 @@ import ( "cloud.google.com/go/storage" "git.toothrot.net/i.dis.band/internal" secretmanager2 "google.golang.org/genproto/googleapis/cloud/secretmanager/v1" - "gopkg.in/gographics/imagick.v2/imagick" ) func main() { @@ -130,6 +131,16 @@ func upload(b *storage.BucketHandle) http.Handler { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } + tmp, err := ioutil.TempFile("", "disband") + if err != nil { + log.Printf("ioutil.TempFile(%q, %q) = _, %v", "", "disband", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + defer func() { + tmp.Close() + log.Printf("os.Remove(%q) = %v", tmp.Name(), os.Remove(tmp.Name())) + }() file, head, err := r.FormFile("file") if err != nil { log.Printf("r.FormFile(%q) = _, %v", "file", err) @@ -141,36 +152,52 @@ func upload(b *storage.BucketHandle) http.Handler { http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed) return } - img, err := ioutil.ReadAll(file) + _, err = io.Copy(tmp, file) if err != nil { - log.Printf("ioutil.ReadAll(file) = _, %v", err) + log.Printf("io.Copy(%v, %v) = _, %v", tmp, file, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + tmp.Close() + file.Close() + r.MultipartForm.RemoveAll() + cmd := exec.Command("exiftool", "-all=", "--icc_profile:all", "-overwrite_original_in_place", tmp.Name()) + var stdOutErr bytes.Buffer + cmd.Stdout = &stdOutErr + cmd.Stderr = &stdOutErr + if err := cmd.Start(); err != nil { + log.Printf("cmd.Start() = %v", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - imagick.Initialize() - defer imagick.Terminate() - wand := imagick.NewMagickWand() - defer wand.Destroy() - if err := wand.ReadImageBlob(img); err != nil { - log.Printf("wand.ReadImageBlob(img) = %v", err) + if err := WaitOrStop(r.Context(), cmd, os.Kill, 100*time.Millisecond); err != nil { + log.Printf("WaitOrStop(_, %v, %s) = %v", cmd, 100*time.Millisecond, err) + log.Printf("Output: %s", stdOutErr.String()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - if err := wand.StripImage(); err != nil { - log.Printf("wand.StripImage() = %v", err) + if code := cmd.ProcessState.ExitCode(); code != 0 { + log.Printf("cmd.ProcessState.ExitCode() = %d", code) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } + ct := head.Header.Get("content-type") ext, err := mime.ExtensionsByType(ct) if err != nil { ext = []string{path.Ext(head.Filename)} } - outname := fmt.Sprintf("%s%s", filename(), ext[len(ext)-1]) - log.Printf("trying to write %q", outname) - fw := b.Object(outname).NewWriter(r.Context()) + outName := fmt.Sprintf("%s%s", filename(), ext[len(ext)-1]) + log.Printf("trying to write %q", outName) + fw := b.Object(outName).NewWriter(r.Context()) fw.ContentType = ct - n, err := fw.Write(wand.GetImageBlob()) + file, err = os.Open(tmp.Name()) + if err != nil { + log.Printf("os.Open(%q) = _, %v", tmp.Name(), err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + n, err := io.Copy(fw, file) if err != nil || n == 0{ log.Printf("fw.Write(wand.GetImageBlob()) = %d, %v", n, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -181,7 +208,7 @@ func upload(b *storage.BucketHandle) http.Handler { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - u := url.URL{Scheme: "https", Host: "i.dis.band", Path: outname} + u := url.URL{Scheme: "https", Host: "i.dis.band", Path: outName} fmt.Fprintln(w, u.String()) }) } @@ -223,7 +250,7 @@ func image(b *storage.BucketHandle, next http.Handler) http.Handler { }) } -func home(w http.ResponseWriter, r *http.Request) { +func home(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("hello")) } @@ -242,3 +269,61 @@ func fileServerHandler(fs fs.FS, next http.Handler) http.Handler { s.ServeHTTP(w, r) }) } + +// WaitOrStop waits for the already-started command cmd by calling its Wait method. +// +// If cmd does not return before ctx is done, WaitOrStop sends it the given interrupt signal. +// If killDelay is positive, WaitOrStop waits that additional period for Wait to return before sending os.Kill. +func WaitOrStop(ctx context.Context, cmd *exec.Cmd, interrupt os.Signal, killDelay time.Duration) error { + if cmd.Process == nil { + panic("WaitOrStop called with a nil cmd.Process — missing Start call?") + } + if interrupt == nil { + panic("WaitOrStop requires a non-nil interrupt signal") + } + + errc := make(chan error) + go func() { + select { + case errc <- nil: + return + case <-ctx.Done(): + } + + err := cmd.Process.Signal(interrupt) + if err == nil { + err = ctx.Err() // Report ctx.Err() as the reason we interrupted. + } else if err.Error() == "os: process already finished" { + errc <- nil + return + } + + if killDelay > 0 { + timer := time.NewTimer(killDelay) + select { + // Report ctx.Err() as the reason we interrupted the process... + case errc <- ctx.Err(): + timer.Stop() + return + // ...but after killDelay has elapsed, fall back to a stronger signal. + case <-timer.C: + } + + // Wait still hasn't returned. + // Kill the process harder to make sure that it exits. + // + // Ignore any error: if cmd.Process has already terminated, we still + // want to send ctx.Err() (or the error from the Interrupt call) + // to properly attribute the signal that may have terminated it. + _ = cmd.Process.Kill() + } + + errc <- err + }() + + waitErr := cmd.Wait() + if interruptErr := <-errc; interruptErr != nil { + return interruptErr + } + return waitErr +} |
