Files
Gokapi/internal/webserver/api/Api.go

390 lines
11 KiB
Go

package api
import (
"encoding/json"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/storage"
"github.com/forceu/gokapi/internal/webserver/authentication/sessionmanager"
"github.com/forceu/gokapi/internal/webserver/fileupload"
"net/http"
"strconv"
"strings"
"time"
)
// Process parses the request and executes the API call or returns an error message to the sender
func Process(w http.ResponseWriter, r *http.Request, maxMemory int) {
w.Header().Set("cache-control", "no-store")
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
request := parseRequest(r)
if !isAuthorisedForApi(w, request) {
return
}
switch request.requestUrl {
case "/chunk/add":
chunkAdd(w, request)
case "/chunk/complete":
chunkComplete(w, request)
case "/files/list":
list(w)
case "/files/add":
upload(w, request, maxMemory)
case "/files/delete":
deleteFile(w, request)
case "/files/duplicate":
duplicateFile(w, request)
case "/auth/friendlyname":
changeFriendlyName(w, request)
case "/auth/modifypermission":
modifyApiPermission(w, request)
default:
sendError(w, http.StatusBadRequest, "Invalid request")
}
}
func getApiPermissionRequired(requestUrl string) (uint8, bool) {
switch requestUrl {
case "/chunk/add":
return models.ApiPermUpload, true
case "/chunk/complete":
return models.ApiPermUpload, true
case "/files/list":
return models.ApiPermView, true
case "/files/add":
return models.ApiPermUpload, true
case "/files/delete":
return models.ApiPermDelete, true
case "/files/duplicate":
return models.ApiPermUpload | models.ApiPermView, true
case "/auth/friendlyname":
return models.ApiPermApiMod, true
case "/auth/modifypermission":
return models.ApiPermApiMod, true
default:
return models.ApiPermNone, false
}
}
// DeleteKey deletes the selected API key
func DeleteKey(id string) bool {
if !IsValidApiKey(id, false, models.ApiPermNone) {
return false
}
database.DeleteApiKey(id)
return true
}
// NewKey generates a new API key
func NewKey() string {
newKey := models.ApiKey{
Id: helper.GenerateRandomString(30),
FriendlyName: "Unnamed key",
LastUsed: 0,
Permissions: models.ApiPermAllNoApiMod,
}
database.SaveApiKey(newKey)
return newKey.Id
}
func modifyApiPermission(w http.ResponseWriter, request apiRequest) {
if !isValidKeyForEditing(w, request) {
return
}
if request.apiInfo.permission < models.ApiPermView || request.apiInfo.permission > models.ApiPermApiMod {
sendError(w, http.StatusBadRequest, "Invalid permission sent")
return
}
key, ok := database.GetApiKey(request.apiInfo.apiKeyToModify)
if !ok {
sendError(w, http.StatusInternalServerError, "Could not modify API key")
return
}
if request.apiInfo.grantPermission && !key.HasPermission(request.apiInfo.permission) {
key.SetPermission(request.apiInfo.permission)
database.SaveApiKey(key)
return
}
if !request.apiInfo.grantPermission && key.HasPermission(request.apiInfo.permission) {
key.RemovePermission(request.apiInfo.permission)
database.SaveApiKey(key)
}
}
func isValidKeyForEditing(w http.ResponseWriter, request apiRequest) bool {
if !IsValidApiKey(request.apiInfo.apiKeyToModify, false, models.ApiPermNone) {
sendError(w, http.StatusBadRequest, "Invalid api key provided.")
return false
}
return true
}
func changeFriendlyName(w http.ResponseWriter, request apiRequest) {
if !isValidKeyForEditing(w, request) {
return
}
if request.apiInfo.friendlyName == "" {
request.apiInfo.friendlyName = "Unnamed key"
}
key, ok := database.GetApiKey(request.apiInfo.apiKeyToModify)
if !ok {
sendError(w, http.StatusInternalServerError, "Could not modify API key")
return
}
if key.FriendlyName != request.apiInfo.friendlyName {
key.FriendlyName = request.apiInfo.friendlyName
database.SaveApiKey(key)
}
}
func deleteFile(w http.ResponseWriter, request apiRequest) {
ok := storage.DeleteFile(request.fileInfo.id, true)
if !ok {
sendError(w, http.StatusBadRequest, "Invalid id provided.")
}
}
func chunkAdd(w http.ResponseWriter, request apiRequest) {
maxUpload := int64(configuration.Get().MaxFileSizeMB) * 1024 * 1024
if request.request.ContentLength > maxUpload {
sendError(w, http.StatusBadRequest, storage.ErrorFileTooLarge.Error())
}
request.request.Body = http.MaxBytesReader(w, request.request.Body, maxUpload)
err := fileupload.ProcessNewChunk(w, request.request, true)
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
}
}
func chunkComplete(w http.ResponseWriter, request apiRequest) {
err := request.request.ParseForm()
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
}
request.request.Form.Set("chunkid", request.request.Form.Get("uuid"))
err = fileupload.CompleteChunk(w, request.request, true)
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
return
}
}
func list(w http.ResponseWriter) {
var validFiles []models.FileApiOutput
timeNow := time.Now().Unix()
for _, element := range database.GetAllMetadata() {
if !storage.IsExpiredFile(element, timeNow) {
file, err := element.ToFileApiOutput()
helper.Check(err)
validFiles = append(validFiles, file)
}
}
result, err := json.Marshal(validFiles)
helper.Check(err)
_, _ = w.Write(result)
}
func upload(w http.ResponseWriter, request apiRequest, maxMemory int) {
maxUpload := int64(configuration.Get().MaxFileSizeMB) * 1024 * 1024
if request.request.ContentLength > maxUpload {
sendError(w, http.StatusBadRequest, storage.ErrorFileTooLarge.Error())
}
request.request.Body = http.MaxBytesReader(w, request.request.Body, maxUpload)
err := fileupload.Process(w, request.request, false, maxMemory)
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
return
}
}
func duplicateFile(w http.ResponseWriter, request apiRequest) {
err := request.parseForm()
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
return
}
file, ok := storage.GetFile(request.fileInfo.id)
if !ok {
sendError(w, http.StatusBadRequest, "Invalid id provided.")
return
}
err = request.parseUploadRequest()
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
return
}
newFile, err := storage.DuplicateFile(file, request.fileInfo.paramsToChange, request.fileInfo.filename, request.fileInfo.uploadRequest)
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
return
}
publicOutput, err := newFile.ToFileApiOutput()
helper.Check(err)
result, err := json.Marshal(publicOutput)
helper.Check(err)
_, _ = w.Write(result)
}
func isAuthorisedForApi(w http.ResponseWriter, request apiRequest) bool {
perm, ok := getApiPermissionRequired(request.requestUrl)
if !ok {
sendError(w, http.StatusUnauthorized, "Unauthorized")
}
if IsValidApiKey(request.apiKey, true, perm) || sessionmanager.IsValidSession(w, request.request) {
return true
}
sendError(w, http.StatusUnauthorized, "Unauthorized")
return false
}
func sendError(w http.ResponseWriter, errorInt int, errorMessage string) {
w.WriteHeader(errorInt)
_, _ = w.Write([]byte("{\"Result\":\"error\",\"ErrorMessage\":\"" + errorMessage + "\"}"))
}
type apiRequest struct {
apiKey string
requestUrl string
request *http.Request
fileInfo fileInfo
apiInfo apiInfo
}
func (a *apiRequest) parseUploadRequest() error {
uploadRequest, paramsToChange, filename, err := apiRequestToUploadRequest(a.request)
if err != nil {
return err
}
a.fileInfo.uploadRequest = uploadRequest
a.fileInfo.paramsToChange = paramsToChange
a.fileInfo.filename = filename
return nil
}
func (a *apiRequest) parseForm() error {
err := a.request.ParseForm()
if err != nil {
return err
}
if a.request.Form.Get("id") != "" {
a.fileInfo.id = a.request.Form.Get("id")
}
return nil
}
type fileInfo struct {
id string // apiRequest.parseForm() needs to be called first if id is encoded in form
uploadRequest models.UploadRequest // apiRequest.parseUploadRequest() needs to be called first
paramsToChange int // apiRequest.parseUploadRequest() needs to be called first
filename string // apiRequest.parseUploadRequest() needs to be called first
}
type apiInfo struct {
friendlyName string
apiKeyToModify string
permission uint8
grantPermission bool
}
func parseRequest(r *http.Request) apiRequest {
permission := models.ApiPermNone
switch r.Header.Get("permission") {
case "PERM_VIEW":
permission = models.ApiPermView
case "PERM_UPLOAD":
permission = models.ApiPermUpload
case "PERM_DELETE":
permission = models.ApiPermDelete
case "PERM_API_MOD":
permission = models.ApiPermApiMod
}
return apiRequest{
apiKey: r.Header.Get("apikey"),
requestUrl: strings.Replace(r.URL.String(), "/api", "", 1),
request: r,
fileInfo: fileInfo{id: r.Header.Get("id")},
apiInfo: apiInfo{
friendlyName: r.Header.Get("friendlyName"),
apiKeyToModify: r.Header.Get("apiKeyToModify"),
permission: uint8(permission),
grantPermission: r.Header.Get("permissionModifier") == "GRANT"},
}
}
func apiRequestToUploadRequest(request *http.Request) (models.UploadRequest, int, string, error) {
paramsToChange := 0
allowedDownloads := 0
daysExpiry := 0
unlimitedTime := false
unlimitedDownloads := false
password := ""
fileName := ""
err := request.ParseForm()
if err != nil {
return models.UploadRequest{}, 0, "", err
}
if request.Form.Get("allowedDownloads") != "" {
paramsToChange = paramsToChange | storage.ParamDownloads
allowedDownloads, err = strconv.Atoi(request.Form.Get("allowedDownloads"))
if err != nil {
return models.UploadRequest{}, 0, "", err
}
if allowedDownloads == 0 {
unlimitedDownloads = true
}
}
if request.Form.Get("expiryDays") != "" {
paramsToChange = paramsToChange | storage.ParamExpiry
daysExpiry, err = strconv.Atoi(request.Form.Get("expiryDays"))
if err != nil {
return models.UploadRequest{}, 0, "", err
}
if daysExpiry == 0 {
unlimitedTime = true
}
}
if strings.ToLower(request.Form.Get("originalPassword")) != "true" {
paramsToChange = paramsToChange | storage.ParamPassword
password = request.Form.Get("password")
}
if request.Form.Get("filename") != "" {
paramsToChange = paramsToChange | storage.ParamName
fileName = request.Form.Get("filename")
}
return models.UploadRequest{
AllowedDownloads: allowedDownloads,
Expiry: daysExpiry,
UnlimitedTime: unlimitedTime,
UnlimitedDownload: unlimitedDownloads,
Password: password,
ExpiryTimestamp: time.Now().Add(time.Duration(daysExpiry) * time.Hour * 24).Unix(),
}, paramsToChange, fileName, nil
}
// IsValidApiKey checks if the API key provides is valid. If modifyTime is true, it also automatically updates
// the lastUsed timestamp
func IsValidApiKey(key string, modifyTime bool, requiredPermission uint8) bool {
if key == "" {
return false
}
savedKey, ok := database.GetApiKey(key)
if ok && savedKey.Id != "" {
if modifyTime {
savedKey.LastUsed = time.Now().Unix()
database.UpdateTimeApiKey(savedKey)
}
result := savedKey.HasPermission(requiredPermission)
return result
}
return false
}