bluebell/upload/upload.go

122 lines
2.9 KiB
Go
Raw Normal View History

2023-07-22 00:59:45 +01:00
package upload
import (
"archive/tar"
"compress/gzip"
2024-08-16 16:48:50 +01:00
"context"
2023-07-22 00:59:45 +01:00
"fmt"
2024-08-16 16:48:50 +01:00
"github.com/1f349/bluebell/database"
"github.com/dustin/go-humanize"
2023-07-22 00:59:45 +01:00
"github.com/julienschmidt/httprouter"
"github.com/spf13/afero"
"io"
2023-08-21 00:27:54 +01:00
"io/fs"
2023-07-22 00:59:45 +01:00
"net/http"
"os"
"path/filepath"
2024-08-16 16:48:50 +01:00
"strings"
2023-07-22 00:59:45 +01:00
)
2024-08-16 16:48:50 +01:00
type sitesQueries interface {
GetSiteBySlug(ctx context.Context, slug string) (database.Site, error)
GetSiteByDomain(ctx context.Context, domain string) (database.Site, error)
2023-07-22 00:59:45 +01:00
}
2024-08-16 16:48:50 +01:00
func New(storage afero.Fs, db sitesQueries) *Handler {
return &Handler{storage, db}
}
const maxFileSize = 1 * humanize.GiByte
2023-07-22 00:59:45 +01:00
type Handler struct {
storageFs afero.Fs
2024-08-16 16:48:50 +01:00
db sitesQueries
2023-07-22 00:59:45 +01:00
}
2024-08-16 16:48:50 +01:00
func (h *Handler) Handle(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
2023-08-21 00:27:54 +01:00
q := req.URL.Query()
site := q.Get("site")
branch := q.Get("branch")
2024-08-16 16:48:50 +01:00
site = strings.ReplaceAll(site, "*", "")
siteConf, err := h.db.GetSiteByDomain(req.Context(), "*"+site)
if err != nil {
http.Error(rw, "", http.StatusNotFound)
2023-08-21 00:27:54 +01:00
return
}
if "Bearer "+siteConf.Token != req.Header.Get("Authorization") {
http.Error(rw, "403 Forbidden", http.StatusForbidden)
return
}
2023-07-22 00:59:45 +01:00
fileData, fileHeader, err := req.FormFile("upload")
if err != nil {
http.Error(rw, "Missing file upload", http.StatusBadRequest)
return
}
// if file is bigger than 1GiB
2024-08-16 16:48:50 +01:00
if fileHeader.Size > maxFileSize {
2023-07-22 00:59:45 +01:00
http.Error(rw, "File too big", http.StatusBadRequest)
return
}
err = h.extractTarGzUpload(fileData, site, branch)
if err != nil {
http.Error(rw, fmt.Sprintf("Invalid upload: %s", err), http.StatusBadRequest)
return
}
rw.WriteHeader(http.StatusAccepted)
}
func (h *Handler) extractTarGzUpload(fileData io.Reader, site, branch string) error {
2023-08-21 00:27:54 +01:00
siteBranchPath := filepath.Join(site, branch)
err := h.storageFs.Rename(siteBranchPath, siteBranchPath+".old")
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to save an old copy of the site: %w", err)
}
err = h.storageFs.MkdirAll(siteBranchPath, fs.ModePerm)
if err != nil {
return fmt.Errorf("failed to make site directory: %w", err)
}
branchFs := afero.NewBasePathFs(h.storageFs, siteBranchPath)
2023-07-22 00:59:45 +01:00
// decompress gzip wrapper
gzipReader, err := gzip.NewReader(fileData)
if err != nil {
return fmt.Errorf("invalid gzip file: %w", err)
}
// parse tar encoding
tarReader := tar.NewReader(gzipReader)
for {
next, err := tarReader.Next()
if err == io.EOF {
2023-08-21 00:27:54 +01:00
// finished reading tar, exit now
2023-07-22 00:59:45 +01:00
break
}
if err != nil {
return fmt.Errorf("invalid tar archive: %w", err)
}
2023-08-21 00:27:54 +01:00
err = branchFs.MkdirAll(filepath.Dir(next.Name), fs.ModePerm)
2023-07-22 00:59:45 +01:00
if err != nil {
return fmt.Errorf("failed to make directory tree: %w", err)
}
2023-08-21 00:27:54 +01:00
create, err := branchFs.Create(next.Name)
2023-07-22 00:59:45 +01:00
if err != nil {
return fmt.Errorf("failed to create output file: '%s': %w", next.Name, err)
}
_, err = io.Copy(create, tarReader)
if err != nil {
return fmt.Errorf("failed to copy from archive to output file: '%s': %w", next.Name, err)
}
}
return nil
}