aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/server
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/server')
-rw-r--r--cmd/server/main.go119
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
+}