Add basic file upload support
This commit is contained in:
parent
eefc11bd0d
commit
7a6397bf17
|
@ -35,6 +35,7 @@ func Configure() *gin.Engine {
|
||||||
// These routes use signed URLs to validate access to the resource being requested.
|
// These routes use signed URLs to validate access to the resource being requested.
|
||||||
router.GET("/download/backup", getDownloadBackup)
|
router.GET("/download/backup", getDownloadBackup)
|
||||||
router.GET("/download/file", getDownloadFile)
|
router.GET("/download/file", getDownloadFile)
|
||||||
|
router.POST("/upload/file", postServerUploadFiles)
|
||||||
|
|
||||||
// This route is special it sits above all of the other requests because we are
|
// This route is special it sits above all of the other requests because we are
|
||||||
// using a JWT to authorize access to it, therefore it needs to be publicly
|
// using a JWT to authorize access to it, therefore it needs to be publicly
|
||||||
|
|
|
@ -3,13 +3,18 @@ package router
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -170,7 +175,7 @@ func postServerDeleteFiles(c *gin.Context) {
|
||||||
|
|
||||||
if len(data.Files) == 0 {
|
if len(data.Files) == 0 {
|
||||||
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
||||||
"error": "No files were specififed for deletion.",
|
"error": "No files were specified for deletion.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -277,3 +282,90 @@ func postServerCompressFiles(c *gin.Context) {
|
||||||
Mimetype: "application/tar+gzip",
|
Mimetype: "application/tar+gzip",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func postServerUploadFiles(c *gin.Context) {
|
||||||
|
token := tokens.UploadPayload{}
|
||||||
|
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
|
||||||
|
TrackedError(err).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := GetServer(token.ServerUuid)
|
||||||
|
if s == nil || !token.IsUniqueRequest() {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
|
"error": "The requested resource was not found on this server.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.Filesystem.HasSpaceAvailable() {
|
||||||
|
c.AbortWithStatusJSON(http.StatusConflict, gin.H{
|
||||||
|
"error": "This server does not have enough available disk space to accept any file uploads.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
form, err := c.MultipartForm()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Failed to get multipart form.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range form.File {
|
||||||
|
log.Debug(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers, ok := form.File["files"]
|
||||||
|
if !ok {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotModified, gin.H{
|
||||||
|
"error": "No files were attached to the request.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make sure directory is safe.
|
||||||
|
directory := c.Query("directory")
|
||||||
|
|
||||||
|
for _, header := range headers {
|
||||||
|
// TODO: Make sure header#Filename is clean.
|
||||||
|
p, err := s.Filesystem.SafePath(filepath.Join(directory, header.Filename))
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug(p)
|
||||||
|
|
||||||
|
// We run this in a different method so I can use defer without any of
|
||||||
|
// the consequences caused by calling it in a loop.
|
||||||
|
if err := handleFileUpload(p, s, header); err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader) error {
|
||||||
|
_, err := s.Filesystem.Stat(header.Filename)
|
||||||
|
if err == nil {
|
||||||
|
// TODO: Figure out how to better handle this
|
||||||
|
|
||||||
|
// This means the file exists, not 100% sure what to do in this situation, but for now we will skip the file.
|
||||||
|
return nil
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := header.Open()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if err := s.Filesystem.Writefile(p, file); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
type BackupPayload struct {
|
type BackupPayload struct {
|
||||||
jwt.Payload
|
jwt.Payload
|
||||||
|
|
||||||
ServerUuid string `json:"server_uuid"`
|
ServerUuid string `json:"server_uuid"`
|
||||||
BackupUuid string `json:"backup_uuid"`
|
BackupUuid string `json:"backup_uuid"`
|
||||||
UniqueId string `json:"unique_id"`
|
UniqueId string `json:"unique_id"`
|
||||||
|
|
25
router/tokens/upload.go
Normal file
25
router/tokens/upload.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UploadPayload struct {
|
||||||
|
jwt.Payload
|
||||||
|
|
||||||
|
ServerUuid string `json:"server_uuid"`
|
||||||
|
UniqueId string `json:"unique_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the JWT payload.
|
||||||
|
func (p *UploadPayload) GetPayload() *jwt.Payload {
|
||||||
|
return &p.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if this JWT is valid for the given request cycle. If the
|
||||||
|
// unique ID passed in the token has already been seen before this will
|
||||||
|
// return false. This allows us to use this JWT as a one-time token that
|
||||||
|
// validates all of the request.
|
||||||
|
func (p *UploadPayload) IsUniqueRequest() bool {
|
||||||
|
return getTokenStore().IsValidToken(p.UniqueId)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user