Skip to content

Commit cc59fff

Browse files
committed
Implemented API calls for uploading files as file request
1 parent c21a4f5 commit cc59fff

File tree

10 files changed

+622
-61
lines changed

10 files changed

+622
-61
lines changed

internal/models/FileRequest.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,10 @@ func (f *FileRequest) GetReadableTotalSize() string {
5555
func (f *FileRequest) GetFilesAsString() string {
5656
return strings.Join(f.FileIdList, ",")
5757
}
58+
59+
func (f *FileRequest) IsUnlimitedSize() bool {
60+
return f.MaxSize == 0
61+
}
62+
func (f *FileRequest) IsUnlimitedFiles() bool {
63+
return f.MaxFiles == 0
64+
}

internal/models/FileUpload.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package models
22

3-
// UploadRequest is used to set an upload request
4-
type UploadRequest struct {
3+
// UploadParameters is used to set parameters for a new upload
4+
type UploadParameters struct {
55
UserId int
66
AllowedDownloads int
77
Expiry int
@@ -13,4 +13,5 @@ type UploadRequest struct {
1313
IsEndToEndEncrypted bool
1414
Password string
1515
ExternalUrl string
16+
FileRequestId string
1617
}

internal/storage/FileServing.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ var ErrorInvalidPresign = errors.New("invalid presign")
5353
// NewFile creates a new file in the system. Called after an upload from the API has been completed. If a file with the same sha1 hash
5454
// already exists, it is deduplicated. This function gathers information about the file, creates an ID and saves
5555
// it into the global configuration. It is now only used by the API, the web UI uses NewFileFromChunk
56-
func NewFile(fileContent io.Reader, fileHeader *multipart.FileHeader, userId int, uploadRequest models.UploadRequest) (models.File, error) {
56+
func NewFile(fileContent io.Reader, fileHeader *multipart.FileHeader, userId int, uploadRequest models.UploadParameters) (models.File, error) {
5757
if !isAllowedFileSize(fileHeader.Size) {
5858
return models.File{}, ErrorFileTooLarge
5959
}
@@ -154,7 +154,7 @@ func GetUploadCounts() map[int]int {
154154
// NewFileFromChunk creates a new file in the system after a chunk upload has fully completed. If a file with the same sha1 hash
155155
// already exists, it is deduplicated. This function gathers information about the file, creates an ID and saves
156156
// it into the global configuration.
157-
func NewFileFromChunk(chunkId string, fileHeader chunking.FileHeader, userId int, uploadRequest models.UploadRequest) (models.File, error) {
157+
func NewFileFromChunk(chunkId string, fileHeader chunking.FileHeader, userId int, uploadRequest models.UploadParameters) (models.File, error) {
158158
file, err := chunking.GetFileByChunkId(chunkId)
159159
if err != nil {
160160
return models.File{}, err
@@ -291,7 +291,7 @@ func encryptChunkFile(file *os.File, metadata *models.File) (*os.File, error) {
291291
return tempFileEnc, nil
292292
}
293293

294-
func createNewMetaData(hash string, fileHeader chunking.FileHeader, userId int, uploadRequest models.UploadRequest) models.File {
294+
func createNewMetaData(hash string, fileHeader chunking.FileHeader, userId int, uploadRequest models.UploadParameters) models.File {
295295
file := models.File{
296296
Id: createNewId(),
297297
Name: fileHeader.Filename,
@@ -397,7 +397,7 @@ func isChangeRequested(parametersToChange, parameter int) bool {
397397
}
398398

399399
// DuplicateFile creates a copy of an existing file with new parameters
400-
func DuplicateFile(file models.File, parametersToChange int, newFileName string, fileParameters models.UploadRequest) (models.File, error) {
400+
func DuplicateFile(file models.File, parametersToChange int, newFileName string, fileParameters models.UploadParameters) (models.File, error) {
401401

402402
// apiDuplicateFile expects fileParameters.IsEndToEndEncrypted and fileParameters.RealSize not to be used,
403403
// change in apiDuplicateFile if using in this function!

internal/storage/FileServing_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,13 @@ func TestAddHotlink(t *testing.T) {
134134

135135
type testFile struct {
136136
File models.File
137-
Request models.UploadRequest
137+
Request models.UploadParameters
138138
Header multipart.FileHeader
139139
UserId int
140140
Content []byte
141141
}
142142

143-
func createRawTestFile(content []byte) (multipart.FileHeader, models.UploadRequest) {
143+
func createRawTestFile(content []byte) (multipart.FileHeader, models.UploadParameters) {
144144
os.Setenv("TZ", "UTC")
145145
mimeHeader := make(textproto.MIMEHeader)
146146
mimeHeader.Set("Content-Disposition", "form-data; name=\"file\"; filename=\"test.dat\"")
@@ -150,7 +150,7 @@ func createRawTestFile(content []byte) (multipart.FileHeader, models.UploadReque
150150
Header: mimeHeader,
151151
Size: int64(len(content)),
152152
}
153-
request := models.UploadRequest{
153+
request := models.UploadParameters{
154154
AllowedDownloads: 1,
155155
Expiry: 999,
156156
ExpiryTimestamp: 2147483600,
@@ -172,7 +172,7 @@ func createTestFile() (testFile, error) {
172172
}, err
173173
}
174174

175-
func createTestChunk() (string, chunking.FileHeader, models.UploadRequest, error) {
175+
func createTestChunk() (string, chunking.FileHeader, models.UploadParameters, error) {
176176
content := []byte("This is a file for chunk testing purposes")
177177
header, request := createRawTestFile(content)
178178
chunkId := helper.GenerateRandomString(15)
@@ -183,7 +183,7 @@ func createTestChunk() (string, chunking.FileHeader, models.UploadRequest, error
183183
}
184184
err := os.WriteFile("test/data/chunk-"+chunkId, content, 0600)
185185
if err != nil {
186-
return "", chunking.FileHeader{}, models.UploadRequest{}, err
186+
return "", chunking.FileHeader{}, models.UploadParameters{}, err
187187
}
188188
return chunkId, fileheader, request, nil
189189
}
@@ -241,7 +241,7 @@ func TestNewFile(t *testing.T) {
241241
Header: mimeHeader,
242242
Size: int64(20) * 1024 * 1024,
243243
}
244-
request = models.UploadRequest{
244+
request = models.UploadParameters{
245245
AllowedDownloads: 1,
246246
Expiry: 999,
247247
ExpiryTimestamp: 2147483600,
@@ -274,7 +274,7 @@ func TestNewFile(t *testing.T) {
274274
Header: mimeHeader,
275275
Size: int64(50) * 1024 * 1024,
276276
}
277-
request = models.UploadRequest{
277+
request = models.UploadParameters{
278278
AllowedDownloads: 1,
279279
Expiry: 999,
280280
ExpiryTimestamp: 2147483600,
@@ -332,7 +332,7 @@ func TestNewFile(t *testing.T) {
332332
Header: mimeHeader,
333333
Size: int64(20) * 1024 * 1024,
334334
}
335-
request = models.UploadRequest{
335+
request = models.UploadParameters{
336336
AllowedDownloads: 1,
337337
Expiry: 999,
338338
ExpiryTimestamp: 2147483600,
@@ -445,7 +445,7 @@ func TestDuplicateFile(t *testing.T) {
445445
retrievedFile.DownloadCount = 5
446446
database.SaveMetaData(retrievedFile)
447447

448-
newFile, err := DuplicateFile(retrievedFile, 0, "123", models.UploadRequest{})
448+
newFile, err := DuplicateFile(retrievedFile, 0, "123", models.UploadParameters{})
449449
test.IsNil(t, err)
450450
test.IsEqualInt(t, newFile.DownloadCount, 0)
451451
test.IsEqualInt(t, newFile.DownloadsRemaining, 1)
@@ -455,7 +455,7 @@ func TestDuplicateFile(t *testing.T) {
455455
test.IsEqualBool(t, newFile.UnlimitedTime, false)
456456
test.IsEqualString(t, newFile.Name, "test.dat")
457457

458-
uploadRequest := models.UploadRequest{
458+
uploadRequest := models.UploadParameters{
459459
AllowedDownloads: 5,
460460
Expiry: 5,
461461
ExpiryTimestamp: 200000,

internal/webserver/api/Api.go

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/forceu/gokapi/internal/logging"
1616
"github.com/forceu/gokapi/internal/models"
1717
"github.com/forceu/gokapi/internal/storage"
18+
"github.com/forceu/gokapi/internal/storage/chunking"
1819
"github.com/forceu/gokapi/internal/storage/filerequest"
1920
"github.com/forceu/gokapi/internal/webserver/authentication/users"
2021
"github.com/forceu/gokapi/internal/webserver/fileupload"
@@ -334,42 +335,86 @@ func apiChunkAdd(w http.ResponseWriter, r requestParser, _ models.User) {
334335
if !ok {
335336
panic("invalid parameter passed")
336337
}
337-
maxUpload := int64(configuration.Get().MaxFileSizeMB) * 1024 * 1024
338-
if request.Request.ContentLength > maxUpload {
339-
sendError(w, http.StatusBadRequest, storage.ErrorFileTooLarge.Error())
338+
statusCode, errString := processNewChunk(w, request, configuration.Get().MaxFileSizeMB)
339+
if statusCode != http.StatusOK {
340+
sendError(w, statusCode, errString)
341+
}
342+
}
343+
344+
func apiChunkUploadRequestAdd(w http.ResponseWriter, r requestParser, _ models.User) {
345+
request, ok := r.(*paramChunkUploadRequestAdd)
346+
if !ok {
347+
panic("invalid parameter passed")
348+
}
349+
fileRequest, ok, status, errorMsg := checkFileRequestAndApiKey(request.FileRequestId, request.ApiKey)
350+
if !ok {
351+
sendError(w, status, errorMsg)
340352
return
341353
}
354+
maxUpload := configuration.Get().MaxFileSizeMB
355+
if !fileRequest.IsUnlimitedSize() {
356+
if (fileRequest.MaxSize) < maxUpload {
357+
maxUpload = fileRequest.MaxSize
358+
}
359+
}
360+
statusCode, errString := processNewChunk(w, request, maxUpload)
361+
if statusCode != http.StatusOK {
362+
sendError(w, statusCode, errString)
363+
}
364+
}
342365

343-
request.Request.Body = http.MaxBytesReader(w, request.Request.Body, maxUpload)
344-
err := fileupload.ProcessNewChunk(w, request.Request, true)
366+
func checkFileRequestAndApiKey(fileRequestId, apiKey string) (models.FileRequest, bool, int, string) {
367+
fileRequest, ok := filerequest.Get(fileRequestId)
368+
if !ok {
369+
return models.FileRequest{}, false, http.StatusNotFound, "FileRequest does not exist with the given ID"
370+
}
371+
if fileRequest.ApiKey != apiKey {
372+
return models.FileRequest{}, false, http.StatusUnauthorized, "Invalid API key"
373+
}
374+
return fileRequest, true, 0, ""
375+
}
376+
377+
type chunkParams interface {
378+
GetRequest() *http.Request
379+
}
380+
381+
func processNewChunk(w http.ResponseWriter, request chunkParams, maxFileSizeMb int) (int, string) {
382+
maxUpload := int64(maxFileSizeMb) * 1024 * 1024
383+
if request.GetRequest().ContentLength > maxUpload {
384+
return http.StatusBadRequest, storage.ErrorFileTooLarge.Error()
385+
}
386+
387+
request.GetRequest().Body = http.MaxBytesReader(w, request.GetRequest().Body, maxUpload)
388+
err := fileupload.ProcessNewChunk(w, request.GetRequest(), true)
345389
if err != nil {
346-
sendError(w, http.StatusBadRequest, err.Error())
347-
return
390+
return http.StatusBadRequest, err.Error()
348391
}
392+
return http.StatusOK, ""
349393
}
350394

351395
func apiChunkComplete(w http.ResponseWriter, r requestParser, user models.User) {
352396
request, ok := r.(*paramChunkComplete)
353397
if !ok {
354398
panic("invalid parameter passed")
355399
}
400+
uploadParams := fileupload.CreateUploadConfig(request.AllowedDownloads,
401+
request.ExpiryDays,
402+
request.Password,
403+
request.UnlimitedTime,
404+
request.UnlimitedDownloads,
405+
request.IsE2E,
406+
request.FileSize,
407+
"")
356408
if request.IsNonBlocking {
357-
go doBlockingPartCompleteChunk(nil, request, user)
409+
go doBlockingPartCompleteChunk(nil, request.Uuid, request.FileHeader, user, uploadParams)
358410
_, _ = io.WriteString(w, "{\"result\":\"OK\"}")
359411
return
360412
}
361-
doBlockingPartCompleteChunk(w, request, user)
413+
go doBlockingPartCompleteChunk(w, request.Uuid, request.FileHeader, user, uploadParams)
362414
}
363415

364-
func doBlockingPartCompleteChunk(w http.ResponseWriter, request *paramChunkComplete, user models.User) {
365-
uploadRequest := fileupload.CreateUploadConfig(request.AllowedDownloads,
366-
request.ExpiryDays,
367-
request.Password,
368-
request.UnlimitedTime,
369-
request.UnlimitedDownloads,
370-
request.IsE2E,
371-
request.FileSize)
372-
file, err := fileupload.CompleteChunk(request.Uuid, request.FileHeader, user.Id, uploadRequest)
416+
func doBlockingPartCompleteChunk(w http.ResponseWriter, uuid string, fileHeader chunking.FileHeader, user models.User, uploadParameters models.UploadParameters) {
417+
file, err := fileupload.CompleteChunk(uuid, fileHeader, user.Id, uploadParameters)
373418
if err != nil {
374419
sendError(w, http.StatusBadRequest, err.Error())
375420
return
@@ -378,6 +423,27 @@ func doBlockingPartCompleteChunk(w http.ResponseWriter, request *paramChunkCompl
378423
outputFileJson(w, file)
379424
}
380425

426+
func apiChunkUploadRequestComplete(w http.ResponseWriter, r requestParser, user models.User) {
427+
request, ok := r.(*paramChunkUploadRequestComplete)
428+
if !ok {
429+
panic("invalid parameter passed")
430+
}
431+
fileRequest, ok, status, errorMsg := checkFileRequestAndApiKey(request.FileRequestId, request.ApiKey)
432+
if !ok {
433+
sendError(w, status, errorMsg)
434+
return
435+
}
436+
uploadParams := fileupload.CreateUploadConfig(0,
437+
0, "", true, true,
438+
false, request.FileSize, fileRequest.Id)
439+
if request.IsNonBlocking {
440+
go doBlockingPartCompleteChunk(nil, request.Uuid, request.FileHeader, user, uploadParams)
441+
_, _ = io.WriteString(w, "{\"result\":\"OK\"}")
442+
return
443+
}
444+
go doBlockingPartCompleteChunk(w, request.Uuid, request.FileHeader, user, uploadParams)
445+
}
446+
381447
func apiVersionInfo(w http.ResponseWriter, _ requestParser, _ models.User) {
382448
type versionInfo struct {
383449
Version string
@@ -565,7 +631,8 @@ func apiDuplicateFile(w http.ResponseWriter, r requestParser, user models.User)
565631
request.UnlimitedTime,
566632
request.UnlimitedDownloads,
567633
false, // is not being used by storage.DuplicateFile
568-
0) // is not being used by storage.DuplicateFile
634+
0, // is not being used by storage.DuplicateFile
635+
"")
569636
newFile, err := storage.DuplicateFile(file, request.RequestedChanges, request.FileName, uploadRequest)
570637
if err != nil {
571638
sendError(w, http.StatusInternalServerError, err.Error())
@@ -954,7 +1021,7 @@ func isAuthorisedForApi(r *http.Request, routing apiRoute) (models.User, bool) {
9541021
return models.User{}, false
9551022
}
9561023
// Returns false if a public upload key is used for non-public api call or vice versa
957-
if routing.UsesPublicUploadApiKey != apiKey.IsUploadRequestKey() {
1024+
if routing.IsFileRequestApi != apiKey.IsUploadRequestKey() {
9581025
return models.User{}, false
9591026
}
9601027
return user, true

0 commit comments

Comments
 (0)