1
0
Fork 0
upl/handlers-s3.go

310 lines
7.1 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/gorilla/mux"
gonanoid "github.com/matoous/go-nanoid/v2"
)
func formatKey(prefix, filename string) string {
for strings.Contains(prefix, "{random}") {
random := gonanoid.MustGenerate(idAlphabet, 16)
prefix = strings.Replace(prefix, "{random}", random, 1)
}
return prefix + filename
}
/* createMultipartUpload */
type createMultipartUploadReq struct {
Filename string `json:"filename"`
Type string `json:"type"`
Metadata createMultipartUploadReqMetadata `json:"metadata"`
}
func (r createMultipartUploadReq) validate() error {
if r.Filename == "" {
return errors.New("invalid filename")
} else if r.Type == "" {
return errors.New("invalid content type")
}
return nil
}
type createMultipartUploadReqMetadata struct {
Name string `json:"name"`
Type string `json:"type"`
}
type createMultipartUploadRes struct {
Key string `json:"key"`
UploadID string `json:"uploadId"`
}
func handleCreateMultipartUpload(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
cred, err := getCredential(vars["id"])
if err != nil {
errorResponse(w, req, err)
return
}
r := createMultipartUploadReq{}
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&r); err != nil {
errorResponse(w, req, fmt.Errorf("%w: %s", errBadRequest, err))
return
}
if err := r.validate(); err != nil {
errorResponse(w, req, err)
return
}
// Derive the object key
key := formatKey(cred.Prefix, r.Filename)
// Ensure that the file does not exist
err = headObject(key, cred)
if !errors.Is(err, errNotFound) {
errorResponse(w, req, fmt.Errorf("%w: the provided key exists", errConflict))
}
result, err := initiateMultipartUpload(key, cred)
if err != nil {
errorResponse(w, req, err)
return
}
encoder := json.NewEncoder(w)
encoder.Encode(createMultipartUploadRes{
Key: key,
UploadID: result.UploadID,
})
}
/* getUploadedParts */
type getUploadedPartsRes []part
func handleGetUploadedParts(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
cred, err := getCredential(vars["id"])
if err != nil {
errorResponse(w, req, err)
return
}
uploadID := vars["uploadID"]
key := req.URL.Query().Get("key")
if uploadID == "" || key == "" {
errorResponse(w, req, fmt.Errorf("%w", errBadRequest))
return
}
parts := make(getUploadedPartsRes, 0)
var nextPartNumberMarker uint32
for {
page, err := listParts(key, uploadID, cred, nextPartNumberMarker)
if err != nil {
errorResponse(w, req, err)
return
}
parts = append(parts, page.Parts...)
nextPartNumberMarker = page.NextPartNumberMarker
if !page.IsTruncated {
break
}
}
encoder := json.NewEncoder(w)
encoder.Encode(getUploadedPartsRes(parts))
}
/* batchSignPartsUpload */
type batchSignPartsUploadRes struct {
PresignedURLs map[string]string `json:"presignedUrls"`
}
func handleBatchSignPartsUpload(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
cred, err := getCredential(vars["id"])
if err != nil {
errorResponse(w, req, err)
return
}
uploadID := vars["uploadID"]
key := req.URL.Query().Get("key")
partNumbers := req.URL.Query().Get("partNumbers")
if uploadID == "" || key == "" || partNumbers == "" {
errorResponse(w, req, fmt.Errorf("%w", errBadRequest))
return
}
partNumbersArray := strings.Split(partNumbers, ",")
partNumbersParsed := make([]uint16, 0, len(partNumbersArray))
for _, partNumber := range partNumbersArray {
n, err := strconv.ParseUint(partNumber, 10, 16)
if n < 1 || n > 10000 || err != nil {
errorResponse(w, req, fmt.Errorf("%w: invalid part number", errBadRequest))
return
}
partNumbersParsed = append(partNumbersParsed, uint16(n))
}
presignedURLs := make(map[string]string, len(partNumbersParsed))
for _, partNumber := range partNumbersParsed {
params := make(url.Values)
params.Add("partNumber", strconv.FormatUint(uint64(partNumber), 10))
params.Add("uploadId", uploadID)
unsignedReq, err := http.NewRequest(http.MethodPut, cred.Endpoint+"/"+key+"?"+params.Encode(), nil)
if err != nil {
errorResponse(w, req, fmt.Errorf("%w: %s", errInternalServerError, err))
return
}
signedReq := preSign(unsignedReq, cred)
presignedURLs[strconv.FormatUint(uint64(partNumber), 10)] = signedReq.URL.String()
}
encoder := json.NewEncoder(w)
encoder.Encode(batchSignPartsUploadRes{
PresignedURLs: presignedURLs,
})
}
/* signPartUpload */
type signPartUploadRes struct {
URL string `json:"url"`
}
func handleSignPartUpload(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
cred, err := getCredential(vars["id"])
if err != nil {
errorResponse(w, req, err)
return
}
uploadID := vars["uploadID"]
key := req.URL.Query().Get("key")
partNumber, err := strconv.ParseUint(vars["uploadPart"], 10, 16)
if uploadID == "" || key == "" {
errorResponse(w, req, fmt.Errorf("%w", errBadRequest))
return
}
if partNumber < 1 || partNumber > 10000 || err != nil {
errorResponse(w, req, fmt.Errorf("%w: invalid part number", errBadRequest))
return
}
params := make(url.Values)
params.Add("partNumber", strconv.FormatUint(partNumber, 10))
params.Add("uploadId", uploadID)
unsignedReq, err := http.NewRequest(http.MethodPut, cred.Endpoint+"/"+key+"?"+params.Encode(), nil)
if err != nil {
errorResponse(w, req, fmt.Errorf("%w: %s", errInternalServerError, err))
return
}
signedReq := preSign(unsignedReq, cred)
encoder := json.NewEncoder(w)
encoder.Encode(signPartUploadRes{
URL: signedReq.URL.String(),
})
}
/* completeMultipartUpload */
type completeMultipartUploadReq struct {
Parts []completePart `json:"parts"`
}
func (r completeMultipartUploadReq) validate() error {
for _, part := range r.Parts {
if err := part.validate(); err != nil {
return err
}
}
return nil
}
func handleCompleteMultipartUpload(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
cred, err := getCredential(vars["id"])
if err != nil {
errorResponse(w, req, err)
return
}
uploadID := vars["uploadID"]
key := req.URL.Query().Get("key")
if uploadID == "" || key == "" {
errorResponse(w, req, fmt.Errorf("%w", errBadRequest))
return
}
r := completeMultipartUploadReq{}
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&r); err != nil {
errorResponse(w, req, fmt.Errorf("%w: %s", errBadRequest, err))
return
}
if err := r.validate(); err != nil {
errorResponse(w, req, err)
return
}
result, err := completeMultipartUpload(key, uploadID, r.Parts, cred)
if err != nil {
errorResponse(w, req, err)
return
}
encoder := json.NewEncoder(w)
encoder.Encode(result)
}
/* abortMultipartUpload */
func handleAbortMultipartUpload(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
cred, err := getCredential(vars["id"])
if err != nil {
errorResponse(w, req, err)
return
}
uploadID := vars["uploadID"]
key := req.URL.Query().Get("key")
if uploadID == "" || key == "" {
errorResponse(w, req, fmt.Errorf("%w", errBadRequest))
return
}
err = abortMultipartUpload(key, uploadID, cred)
if err != nil {
errorResponse(w, req, err)
return
}
}