summaryrefslogtreecommitdiff
path: root/src/http
diff options
context:
space:
mode:
authorBrian Picciano <mediocregopher@gmail.com>2023-04-15 20:47:50 +0200
committerBrian Picciano <mediocregopher@gmail.com>2023-04-15 20:57:39 +0200
commit68f3215df6e2e4f345076dd5b20b9bf5867353cf (patch)
tree0f858dbaebbfeb3a88497adf4a3e0b16ca7cc26c /src/http
parentb5e1103caf5e8225176ba74a6f4f0a9b2bde7192 (diff)
Add support for http loading files from an asset archive
Diffstat (limited to 'src/http')
-rw-r--r--src/http/assets.go189
1 files changed, 152 insertions, 37 deletions
diff --git a/src/http/assets.go b/src/http/assets.go
index 9c6c67e..5b26a2e 100644
--- a/src/http/assets.go
+++ b/src/http/assets.go
@@ -2,23 +2,27 @@ package http
import (
"bytes"
+ "compress/gzip"
"errors"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
+ "io/fs"
"net/http"
"path/filepath"
"strings"
+ "time"
"github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil"
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
+ "github.com/omeid/go-tarfs"
"golang.org/x/image/draw"
)
-func isImgResizable(id string) bool {
- switch strings.ToLower(filepath.Ext(id)) {
+func isImgResizable(path string) bool {
+ switch strings.ToLower(filepath.Ext(path)) {
case ".jpg", ".jpeg", ".png":
return true
default:
@@ -84,62 +88,173 @@ func (a *api) managePostAssetsHandler() http.Handler {
})
}
-func (a *api) getPostAssetHandler() http.Handler {
+type postAssetArchiveInfo struct {
+ path string
+ id string
+ subPath string
+ isGzipped bool
+}
- return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+func extractPostAssetArchiveInfo(path string) (postAssetArchiveInfo, bool) {
- id := filepath.Base(r.URL.Path)
+ var info postAssetArchiveInfo
- maxWidth, err := apiutil.StrToInt(r.FormValue("w"), 0)
- if err != nil {
- apiutil.BadRequest(rw, r, fmt.Errorf("invalid w parameter: %w", err))
- return
- }
+ info.path = strings.TrimPrefix(path, "/")
- buf := new(bytes.Buffer)
+ info.id, info.subPath, _ = strings.Cut(info.path, "/")
- err = a.params.PostAssetStore.Get(id, buf)
+ switch {
- if errors.Is(err, post.ErrAssetNotFound) {
- http.Error(rw, "Asset not found", 404)
- return
- } else if err != nil {
+ case strings.HasSuffix(info.id, ".tar.gz"),
+ strings.HasSuffix(info.id, ".tgz"):
+ info.isGzipped = true
+
+ case strings.HasSuffix(info.id, ".tar"):
+ // ok
+
+ default:
+ // unsupported
+ return postAssetArchiveInfo{}, false
+ }
+
+ return info, true
+}
+
+func (a *api) writePostAsset(
+ rw http.ResponseWriter,
+ r *http.Request,
+ path string,
+ from io.ReadSeeker,
+) {
+
+ maxWidth, err := apiutil.StrToInt(r.FormValue("w"), 0)
+ if err != nil {
+ apiutil.BadRequest(rw, r, fmt.Errorf("invalid w parameter: %w", err))
+ return
+ }
+
+ if maxWidth == 0 {
+ http.ServeContent(rw, r, path, time.Time{}, from)
+ return
+ }
+
+ if !isImgResizable(path) {
+ apiutil.BadRequest(rw, r, fmt.Errorf("cannot resize asset %q", path))
+ return
+ }
+
+ resizedBuf := new(bytes.Buffer)
+
+ if err := resizeImage(resizedBuf, from, float64(maxWidth)); err != nil {
+ apiutil.InternalServerError(
+ rw, r,
+ fmt.Errorf(
+ "resizing image %q to size %d: %w",
+ path, maxWidth, err,
+ ),
+ )
+ }
+
+ http.ServeContent(
+ rw, r, path, time.Time{}, bytes.NewReader(resizedBuf.Bytes()),
+ )
+}
+
+func (a *api) handleGetPostAssetArchive(
+ rw http.ResponseWriter,
+ r *http.Request,
+ info postAssetArchiveInfo,
+) {
+
+ buf := new(bytes.Buffer)
+
+ err := a.params.PostAssetStore.Get(info.id, buf)
+
+ if errors.Is(err, post.ErrAssetNotFound) {
+ http.Error(rw, "asset not found", 404)
+ return
+ } else if err != nil {
+ apiutil.InternalServerError(
+ rw, r,
+ fmt.Errorf("fetching archive asset with id %q: %w", info.id, err),
+ )
+ return
+ }
+
+ var from io.Reader = buf
+
+ if info.isGzipped {
+
+ if from, err = gzip.NewReader(from); err != nil {
apiutil.InternalServerError(
- rw, r, fmt.Errorf("fetching asset with id %q: %w", id, err),
+ rw, r,
+ fmt.Errorf("decompressing archive asset with id %q: %w", info.id, err),
)
return
}
+ }
- if maxWidth == 0 {
+ tarFS, err := tarfs.New(from)
- if _, err := io.Copy(rw, buf); err != nil {
- apiutil.InternalServerError(
- rw, r,
- fmt.Errorf(
- "copying asset with id %q to response writer: %w",
- id, err,
- ),
- )
- }
+ if err != nil {
+ apiutil.InternalServerError(
+ rw, r,
+ fmt.Errorf("reading archive asset with id %q as fs: %w", info.id, err),
+ )
+ return
+ }
- return
- }
+ f, err := tarFS.Open(info.subPath)
+
+ if errors.Is(err, fs.ErrExist) {
+ http.Error(rw, "Asset not found", 404)
+ return
- if !isImgResizable(id) {
- apiutil.BadRequest(rw, r, fmt.Errorf("cannot resize file %q", id))
+ } else if err != nil {
+
+ apiutil.InternalServerError(
+ rw, r,
+ fmt.Errorf(
+ "opening path %q from archive asset with id %q as fs: %w",
+ info.subPath, info.id, err,
+ ),
+ )
+ return
+ }
+
+ defer f.Close()
+
+ a.writePostAsset(rw, r, info.path, f)
+}
+
+func (a *api) getPostAssetHandler() http.Handler {
+
+ return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+
+ archiveInfo, ok := extractPostAssetArchiveInfo(r.URL.Path)
+
+ if ok {
+ a.handleGetPostAssetArchive(rw, r, archiveInfo)
return
}
- if err := resizeImage(rw, buf, float64(maxWidth)); err != nil {
+ id := filepath.Base(r.URL.Path)
+
+ buf := new(bytes.Buffer)
+
+ err := a.params.PostAssetStore.Get(id, buf)
+
+ if errors.Is(err, post.ErrAssetNotFound) {
+ http.Error(rw, "Asset not found", 404)
+ return
+ } else if err != nil {
apiutil.InternalServerError(
- rw, r,
- fmt.Errorf(
- "resizing image with id %q to size %d: %w",
- id, maxWidth, err,
- ),
+ rw, r, fmt.Errorf("fetching asset with id %q: %w", id, err),
)
+ return
}
+ a.writePostAsset(rw, r, id, bytes.NewReader(buf.Bytes()))
})
}