Skip to content

Commit

Permalink
Added feature for editing uploaded files #103, updated Bootstrap to 5.3
Browse files Browse the repository at this point in the history
  • Loading branch information
Forceu committed Nov 29, 2023
1 parent d24ea97 commit a130357
Show file tree
Hide file tree
Showing 22 changed files with 585 additions and 127 deletions.
9 changes: 7 additions & 2 deletions internal/models/Api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ const (
ApiPermUpload
ApiPermDelete
ApiPermApiMod
ApiPermEdit
)

const ApiPermNone = 0
const ApiPermAllNoApiMod = 7
const ApiPermAll = 15
const ApiPermAllNoApiMod = 23
const ApiPermAll = 31

// ApiKey contains data of a single api key
type ApiKey struct {
Expand Down Expand Up @@ -49,3 +50,7 @@ func (key *ApiKey) HasPermissionDelete() bool {
func (key *ApiKey) HasPermissionApiMod() bool {
return key.HasPermission(ApiPermApiMod)
}

func (key *ApiKey) HasPermissionEdit() bool {
return key.HasPermission(ApiPermEdit)
}
8 changes: 6 additions & 2 deletions internal/storage/FileServing.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ func encryptChunkFile(file *os.File, metadata *models.File) (*os.File, error) {
return tempFileEnc, nil
}

func FormatTimestamp(timestamp int64) string {
return time.Unix(timestamp, 0).Format("2006-01-02 15:04")
}

func createNewMetaData(hash string, fileHeader chunking.FileHeader, uploadRequest models.UploadRequest) models.File {
file := models.File{
Id: createNewId(),
Expand All @@ -273,7 +277,7 @@ func createNewMetaData(hash string, fileHeader chunking.FileHeader, uploadReques
SizeBytes: fileHeader.Size,
ContentType: fileHeader.ContentType,
ExpireAt: uploadRequest.ExpiryTimestamp,
ExpireAtString: time.Unix(uploadRequest.ExpiryTimestamp, 0).Format("2006-01-02 15:04"),
ExpireAtString: FormatTimestamp(uploadRequest.ExpiryTimestamp),
DownloadsRemaining: uploadRequest.AllowedDownloads,
UnlimitedTime: uploadRequest.UnlimitedTime,
UnlimitedDownloads: uploadRequest.UnlimitedDownload,
Expand Down Expand Up @@ -348,7 +352,7 @@ func DuplicateFile(file models.File, parametersToChange int, newFileName string,

if changeExpiry {
newFile.ExpireAt = fileParameters.ExpiryTimestamp
newFile.ExpireAtString = time.Unix(fileParameters.ExpiryTimestamp, 0).Format("2006-01-02 15:04")
newFile.ExpireAtString = FormatTimestamp(fileParameters.ExpiryTimestamp)
newFile.UnlimitedTime = fileParameters.UnlimitedTime
}
if changeDownloads {
Expand Down
99 changes: 83 additions & 16 deletions internal/webserver/api/Api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,58 @@ func Process(w http.ResponseWriter, r *http.Request, maxMemory int) {
deleteFile(w, request)
case "/files/duplicate":
duplicateFile(w, request)
case "/files/modify":
editFile(w, request)
case "/auth/friendlyname":
changeFriendlyName(w, request)
case "/auth/modifypermission":
case "/auth/modify":
modifyApiPermission(w, request)
default:
sendError(w, http.StatusBadRequest, "Invalid request")
}
}

func editFile(w http.ResponseWriter, request apiRequest) {
file, ok := database.GetMetaDataById(request.filemodInfo.id)
if !ok {
sendError(w, http.StatusBadRequest, "Invalid file ID provided.")
return
}
if request.filemodInfo.downloads != "" {
dowloadsInt, err := strconv.Atoi(request.filemodInfo.downloads)
if err != nil {
sendError(w, http.StatusBadRequest, "Invalid download count provided.")
return
}
if dowloadsInt != 0 {
file.DownloadsRemaining = dowloadsInt
file.UnlimitedDownloads = false
} else {
file.UnlimitedDownloads = true
}
}
if request.filemodInfo.expiry != "" {
expiryInt, err := strconv.ParseInt(request.filemodInfo.expiry, 10, 64)
if err != nil {
sendError(w, http.StatusBadRequest, "Invalid expiry timestamp provided.")
return
}
if expiryInt != 0 {
file.ExpireAt = expiryInt
file.ExpireAtString = storage.FormatTimestamp(expiryInt)
file.UnlimitedTime = false
} else {
file.UnlimitedTime = true
}
}

if !request.filemodInfo.originalPassword {
file.PasswordHash = configuration.HashPassword(request.filemodInfo.password, true)
}
database.SaveMetaData(file)
outputFileInfo(w, file)
}

func getApiPermissionRequired(requestUrl string) (uint8, bool) {
switch requestUrl {
case "/chunk/add":
Expand All @@ -58,10 +101,12 @@ func getApiPermissionRequired(requestUrl string) (uint8, bool) {
case "/files/delete":
return models.ApiPermDelete, true
case "/files/duplicate":
return models.ApiPermUpload | models.ApiPermView, true
return models.ApiPermUpload, true
case "/files/modify":
return models.ApiPermEdit, true
case "/auth/friendlyname":
return models.ApiPermApiMod, true
case "/auth/modifypermission":
case "/auth/modify":
return models.ApiPermApiMod, true
default:
return models.ApiPermNone, false
Expand Down Expand Up @@ -93,7 +138,7 @@ func modifyApiPermission(w http.ResponseWriter, request apiRequest) {
if !isValidKeyForEditing(w, request) {
return
}
if request.apiInfo.permission < models.ApiPermView || request.apiInfo.permission > models.ApiPermApiMod {
if request.apiInfo.permission < models.ApiPermView || request.apiInfo.permission > models.ApiPermEdit {
sendError(w, http.StatusBadRequest, "Invalid permission sent")
return
}
Expand Down Expand Up @@ -142,14 +187,15 @@ func changeFriendlyName(w http.ResponseWriter, request apiRequest) {
func deleteFile(w http.ResponseWriter, request apiRequest) {
ok := storage.DeleteFile(request.fileInfo.id, true)
if !ok {
sendError(w, http.StatusBadRequest, "Invalid id provided.")
sendError(w, http.StatusBadRequest, "Invalid file 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())
return
}

request.request.Body = http.MaxBytesReader(w, request.request.Body, maxUpload)
Expand All @@ -162,12 +208,12 @@ func chunkComplete(w http.ResponseWriter, request apiRequest) {
err := request.request.ParseForm()
if err != nil {
sendError(w, http.StatusBadRequest, err.Error())
return
}
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
}
}

Expand All @@ -190,6 +236,7 @@ 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())
return
}

request.request.Body = http.MaxBytesReader(w, request.request.Body, maxUpload)
Expand Down Expand Up @@ -221,7 +268,11 @@ func duplicateFile(w http.ResponseWriter, request apiRequest) {
sendError(w, http.StatusBadRequest, err.Error())
return
}
publicOutput, err := newFile.ToFileApiOutput()
outputFileInfo(w, newFile)
}

func outputFileInfo(w http.ResponseWriter, file models.File) {
publicOutput, err := file.ToFileApiOutput()
helper.Check(err)
result, err := json.Marshal(publicOutput)
helper.Check(err)
Expand All @@ -231,7 +282,8 @@ func duplicateFile(w http.ResponseWriter, request apiRequest) {
func isAuthorisedForApi(w http.ResponseWriter, request apiRequest) bool {
perm, ok := getApiPermissionRequired(request.requestUrl)
if !ok {
sendError(w, http.StatusUnauthorized, "Unauthorized")
sendError(w, http.StatusBadRequest, "Invalid request")
return false
}
if IsValidApiKey(request.apiKey, true, perm) || sessionmanager.IsValidSession(w, request.request) {
return true
Expand All @@ -240,19 +292,19 @@ func isAuthorisedForApi(w http.ResponseWriter, request apiRequest) bool {
return false
}

// TODO investigate superfluous response.WriteHeader call from github.com/forceu/gokapi/internal/webserver/api.sendError (Api.go:244)
// Probably from new API permission system
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
apiKey string
requestUrl string
request *http.Request
fileInfo fileInfo
apiInfo apiInfo
filemodInfo filemodInfo
}

func (a *apiRequest) parseUploadRequest() error {
Expand Down Expand Up @@ -290,6 +342,13 @@ type apiInfo struct {
permission uint8
grantPermission bool
}
type filemodInfo struct {
id string
downloads string
expiry string
password string
originalPassword bool
}

func parseRequest(r *http.Request) apiRequest {
permission := models.ApiPermNone
Expand All @@ -302,12 +361,21 @@ func parseRequest(r *http.Request) apiRequest {
permission = models.ApiPermDelete
case "PERM_API_MOD":
permission = models.ApiPermApiMod
case "PERM_EDIT":
permission = models.ApiPermEdit
}
return apiRequest{
apiKey: r.Header.Get("apikey"),
requestUrl: strings.Replace(r.URL.String(), "/api", "", 1),
request: r,
fileInfo: fileInfo{id: r.Header.Get("id")},
filemodInfo: filemodInfo{
id: r.Header.Get("id"),
downloads: r.Header.Get("allowedDownloads"),
expiry: r.Header.Get("expiryTimestamp"),
password: r.Header.Get("password"),
originalPassword: r.Header.Get("originalPassword") == "true",
},
apiInfo: apiInfo{
friendlyName: r.Header.Get("friendlyName"),
apiKeyToModify: r.Header.Get("apiKeyToModify"),
Expand Down Expand Up @@ -384,8 +452,7 @@ func IsValidApiKey(key string, modifyTime bool, requiredPermission uint8) bool {
savedKey.LastUsed = time.Now().Unix()
database.UpdateTimeApiKey(savedKey)
}
result := savedKey.HasPermission(requiredPermission)
return result
return savedKey.HasPermission(requiredPermission)
}
return false
}
6 changes: 3 additions & 3 deletions internal/webserver/api/Api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestProcess(t *testing.T) {
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
w, r = test.GetRecorder("GET", "/api/invalid", nil, nil, nil)
Process(w, r, maxMemory)
test.ResponseBodyContains(t, w, "Unauthorized")
test.ResponseBodyContains(t, w, "Invalid request")
w, r = test.GetRecorder("GET", "/api/invalid", nil, []test.Header{{
Name: "apikey",
Value: "validkey",
Expand Down Expand Up @@ -138,7 +138,7 @@ func TestDeleteFile(t *testing.T) {
Value: "validkey",
}}, nil)
Process(w, r, maxMemory)
test.ResponseBodyContains(t, w, "Invalid id provided.")
test.ResponseBodyContains(t, w, "Invalid file ID provided")
w, r = test.GetRecorder("GET", "/api/files/delete", nil, []test.Header{{
Name: "apikey",
Value: "validkey",
Expand All @@ -148,7 +148,7 @@ func TestDeleteFile(t *testing.T) {
},
}, nil)
Process(w, r, maxMemory)
test.ResponseBodyContains(t, w, "Invalid id provided.")
test.ResponseBodyContains(t, w, "Invalid file ID provided")
file, ok := database.GetMetaDataById("jpLXGJKigM4hjtA6T6sN2")
test.IsEqualBool(t, ok, true)
test.IsEqualString(t, file.Id, "jpLXGJKigM4hjtA6T6sN2")
Expand Down
Loading

0 comments on commit a130357

Please sign in to comment.