126 lines
2.7 KiB
Go
126 lines
2.7 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/gofrs/uuid"
|
||
|
"github.com/pkg/errors"
|
||
|
"github.com/sirupsen/logrus"
|
||
|
)
|
||
|
|
||
|
const dirCreateMode = 0o750
|
||
|
|
||
|
func deriveFile(r *http.Request) (string, error) {
|
||
|
absRoot, err := filepath.Abs(cfg.RootDir)
|
||
|
if err != nil {
|
||
|
return "", errors.Wrap(err, "getting absolute root")
|
||
|
}
|
||
|
|
||
|
file := filepath.Clean(path.Join(absRoot, strings.TrimLeft(r.URL.Path, "/")))
|
||
|
if !strings.HasPrefix(file, path.Join(absRoot, "")) {
|
||
|
return "", errors.New("break-out attempt")
|
||
|
}
|
||
|
|
||
|
return file, nil
|
||
|
}
|
||
|
|
||
|
func genericHTTPError(w http.ResponseWriter, reqID string, err error, desc string) {
|
||
|
logrus.WithField("req_id", reqID).WithError(err).Error(desc)
|
||
|
http.Error(w, fmt.Sprintf("something went wrong: %s", reqID), http.StatusInternalServerError)
|
||
|
}
|
||
|
|
||
|
func handleDelete(w http.ResponseWriter, r *http.Request) {
|
||
|
reqID := uuid.Must(uuid.NewV4()).String()
|
||
|
|
||
|
f, err := deriveFile(r)
|
||
|
if err != nil {
|
||
|
genericHTTPError(w, reqID, err, "deriving file for request")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
stat, err := os.Stat(f)
|
||
|
if err != nil {
|
||
|
if errors.Is(err, fs.ErrNotExist) {
|
||
|
w.WriteHeader(http.StatusNoContent)
|
||
|
return
|
||
|
}
|
||
|
genericHTTPError(w, reqID, err, "stating file")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if stat.IsDir() {
|
||
|
genericHTTPError(w, reqID, errors.New("is directory"), "refusing to delete dir")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = os.Remove(f); err != nil {
|
||
|
genericHTTPError(w, reqID, err, "deleting file")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusNoContent)
|
||
|
}
|
||
|
|
||
|
func handleGet(w http.ResponseWriter, r *http.Request) {
|
||
|
reqID := uuid.Must(uuid.NewV4()).String()
|
||
|
|
||
|
f, err := deriveFile(r)
|
||
|
if err != nil {
|
||
|
genericHTTPError(w, reqID, err, "deriving file for request")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
stat, err := os.Stat(f)
|
||
|
if err != nil {
|
||
|
if errors.Is(err, fs.ErrNotExist) {
|
||
|
http.Error(w, "not found", http.StatusNotFound)
|
||
|
return
|
||
|
}
|
||
|
genericHTTPError(w, reqID, err, "stating file")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if stat.IsDir() {
|
||
|
genericHTTPError(w, reqID, errors.New("is directory"), "refusing to get dir")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
http.ServeFile(w, r, f)
|
||
|
}
|
||
|
|
||
|
func handlePut(w http.ResponseWriter, r *http.Request) {
|
||
|
reqID := uuid.Must(uuid.NewV4()).String()
|
||
|
|
||
|
f, err := deriveFile(r)
|
||
|
if err != nil {
|
||
|
genericHTTPError(w, reqID, err, "deriving file for request")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = os.MkdirAll(path.Dir(f), dirCreateMode); err != nil {
|
||
|
genericHTTPError(w, reqID, err, "ensuring directory for file")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
out, err := os.Create(f)
|
||
|
if err != nil {
|
||
|
genericHTTPError(w, reqID, err, "creating file")
|
||
|
return
|
||
|
}
|
||
|
defer out.Close()
|
||
|
|
||
|
if _, err = io.Copy(out, r.Body); err != nil {
|
||
|
genericHTTPError(w, reqID, err, "copying file contents")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusCreated)
|
||
|
}
|