diff --git a/go.mod b/go.mod index 05df28b2..cfe5a510 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/maruel/natural v0.0.0-20180416170133-dbcb3e2e8cf1 github.com/mholt/archiver v3.1.1+incompatible github.com/mitchellh/go-homedir v1.1.0 + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nwaples/rardecode v1.0.0 // indirect github.com/pelletier/go-toml v1.6.0 github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 // indirect diff --git a/go.sum b/go.sum index 6ce903fc..538a382d 100644 --- a/go.sum +++ b/go.sum @@ -143,6 +143,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= diff --git a/http/preview.go b/http/preview.go new file mode 100644 index 00000000..05666703 --- /dev/null +++ b/http/preview.go @@ -0,0 +1,126 @@ +package http + +import ( + "bytes" + "errors" + "fmt" + "github.com/filebrowser/filebrowser/v2/files" + "github.com/nfnt/resize" + "image" + "image/gif" + "image/jpeg" + "image/png" + "io" + "mime" + "net/http" + "net/url" +) + +var compressHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + if !d.user.Perm.Download { + return http.StatusAccepted, nil + } + + file, err := files.NewFileInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: r.URL.Path, + Modify: d.user.Perm.Modify, + Expand: true, + Checker: d, + }) + if err != nil { + return errToStatus(err), err + } + + if file.IsDir || file.Type != "image" { + return http.StatusNotFound, nil + } + + return compressFileHandler(w, r, file) +}) + +func compressFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo) (int, error) { + fd, err := file.Fs.Open(file.Path) + if err != nil { + return http.StatusInternalServerError, err + } + defer fd.Close() + + if r.URL.Query().Get("inline") == "true" { + w.Header().Set("Content-Disposition", "inline") + } else { + // As per RFC6266 section 4.3 + w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(file.Name)) + } + + buf, err := compressImageHandler(file, fd) + if err != nil { + return errToStatus(err), err + } + w.Header().Add("Content-Length", fmt.Sprintf("%d", buf.Len())) + w.Header().Add("Content-Type", mime.TypeByExtension(file.Extension)) + io.Copy(w, buf) + return 0, nil +} + +func compressImageHandler(file *files.FileInfo, fd io.Reader) (*bytes.Buffer, error) { + var ( + buf *bytes.Buffer + m image.Image + err error + ) + + switch file.Extension { + case ".jpg", ".jpeg": + buf, m, err = compressImage(jpeg.Decode, fd) + if err != nil { + return nil, err + } + err = jpeg.Encode(buf, m, nil) + break + case ".png": + buf, m, err = compressImage(png.Decode, fd) + if err != nil { + return nil, err + } + err = png.Encode(buf, m) + break + case ".gif": + buf, m, err = compressImage(gif.Decode, fd) + if err != nil { + return nil, err + } + err = gif.Encode(buf, m, nil) + break + default: + return nil, errors.New("extension is not supported") + } + if err != nil { + return nil, err + } + return buf, nil +} + +const maxSize = 1080 + +func compressImage(decode func(r io.Reader) (image.Image, error), fd io.Reader) (*bytes.Buffer, image.Image, error) { + img, err := decode(fd) + if err != nil { + return nil, nil, err + } + buf := bytes.NewBuffer([]byte{}) + width := img.Bounds().Dx() + height := img.Bounds().Dy() + if width > maxSize && width > height { + width = maxSize + height = 0 + } else if height > maxSize && height > width { + width = 0 + height = maxSize + } else { + width = 0 + height = 0 + } + m := resize.Resize(uint(width), uint(height), img, resize.Lanczos3) + return buf, m, nil +}