Skip to content

Commit 54c771b

Browse files
committed
Add API call to download files and optionally not increasing counter, added Download API permission
1 parent 03fa4c2 commit 54c771b

File tree

11 files changed

+225
-19
lines changed

11 files changed

+225
-19
lines changed

internal/models/ApiKey.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,17 @@ const (
2222
ApiPermManageUsers
2323
// ApiPermManageLogs is the permission required for managing the log file PERM_MANAGE_LOGS
2424
ApiPermManageLogs
25-
// ApiPermManageFileRequests is the permission required for creating and managing file requests
25+
// ApiPermManageFileRequests is the permission required for creating and managing file requests PERM_MANAGE_FILE_REQUESTS
2626
ApiPermManageFileRequests
27+
// ApiPermDownload is the permission required for downloading stored files without increasing the counter PERM_DOWNLOAD
28+
ApiPermDownload
2729
)
2830

2931
// ApiPermNone means no permission granted
3032
const ApiPermNone ApiPermission = 0
3133

32-
// ApiPermAll means all permission granted
33-
const ApiPermAll ApiPermission = 511
34-
35-
// ApiPermDefault means all permission granted, except ApiPermApiMod, ApiPermManageUsers, ApiPermManageLogs and ApiPermReplace
36-
// This is the default for new API keys that are created from the UI
37-
const ApiPermDefault = ApiPermAll - ApiPermApiMod - ApiPermManageUsers - ApiPermReplace - ApiPermManageLogs - ApiPermManageFileRequests
34+
// ApiPermDefault the default for new API keys that are created from the UI
35+
const ApiPermDefault = ApiPermView + ApiPermUpload + ApiPermDelete + ApiPermEdit
3836

3937
// ApiKey contains data of a single api key
4038
type ApiKey struct {
@@ -72,6 +70,8 @@ func ApiPermissionFromString(permString string) (ApiPermission, error) {
7270
return ApiPermManageLogs, nil
7371
case "PERM_MANAGE_FILE_REQUESTS":
7472
return ApiPermManageFileRequests, nil
73+
case "PERM_DOWNLOAD":
74+
return ApiPermDownload, nil
7575
default:
7676
return 0, errors.New("invalid permission")
7777
}
@@ -145,6 +145,11 @@ func (key *ApiKey) HasPermissionManageFileRequests() bool {
145145
return key.HasPermission(ApiPermManageFileRequests)
146146
}
147147

148+
// HasPermissionDownload returns true if ApiPermDownload is granted
149+
func (key *ApiKey) HasPermissionDownload() bool {
150+
return key.HasPermission(ApiPermDownload)
151+
}
152+
148153
// ApiKeyOutput is the output used after a new key is created
149154
type ApiKeyOutput struct {
150155
Result string

internal/storage/FileServing.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -596,12 +596,14 @@ func GetFileByHotlink(id string) (models.File, bool) {
596596
}
597597

598598
// ServeFile subtracts a download allowance and serves the file to the browser
599-
func ServeFile(file models.File, w http.ResponseWriter, r *http.Request, forceDownload bool) {
600-
file.DownloadsRemaining = file.DownloadsRemaining - 1
601-
file.DownloadCount = file.DownloadCount + 1
602-
database.IncreaseDownloadCount(file.Id, !file.UnlimitedDownloads)
599+
func ServeFile(file models.File, w http.ResponseWriter, r *http.Request, forceDownload, increaseCounter bool) {
600+
if increaseCounter {
601+
file.DownloadsRemaining = file.DownloadsRemaining - 1
602+
file.DownloadCount = file.DownloadCount + 1
603+
database.IncreaseDownloadCount(file.Id, !file.UnlimitedDownloads)
604+
go sse.PublishDownloadCount(file)
605+
}
603606
logging.LogDownload(file, r, configuration.Get().SaveIp)
604-
go sse.PublishDownloadCount(file)
605607

606608
if !file.IsLocalStorage() {
607609
// If non-blocking, we are not setting a download complete status as there is no reliable way to

internal/webserver/Webserver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ func showHotlink(w http.ResponseWriter, r *http.Request) {
590590
_, _ = w.Write(imageExpiredPicture)
591591
return
592592
}
593-
storage.ServeFile(file, w, r, false)
593+
storage.ServeFile(file, w, r, false, true)
594594
}
595595

596596
// Checks if a file is associated with the GET parameter from the current URL
@@ -930,7 +930,7 @@ func serveFile(id string, isRootUrl bool, w http.ResponseWriter, r *http.Request
930930
return
931931
}
932932
}
933-
storage.ServeFile(savedFile, w, r, true)
933+
storage.ServeFile(savedFile, w, r, true, true)
934934
}
935935

936936
func requireLogin(next http.HandlerFunc, isUiCall, isPwChangeView bool) http.HandlerFunc {

internal/webserver/api/Api.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,29 @@ func apiListSingle(w http.ResponseWriter, r requestParser, user models.User) {
449449
_, _ = w.Write(result)
450450
}
451451

452+
func apiDownloadSingle(w http.ResponseWriter, r requestParser, user models.User) {
453+
request, ok := r.(*paramFilesDownloadSingle)
454+
if !ok {
455+
panic("invalid parameter passed")
456+
}
457+
file, ok := storage.GetFile(request.Id)
458+
if !ok {
459+
sendError(w, http.StatusNotFound, "File not found")
460+
return
461+
}
462+
if file.UserId != user.Id && !user.HasPermission(models.UserPermListOtherUploads) {
463+
sendError(w, http.StatusUnauthorized, "No permission to download file")
464+
return
465+
}
466+
storage.ServeFile(file, w, request.WebRequest, true, request.IncreaseCounter)
467+
config := configuration.Get()
468+
output, err := file.ToFileApiOutput(config.ServerUrl, config.IncludeFilename)
469+
helper.Check(err)
470+
result, err := json.Marshal(output)
471+
helper.Check(err)
472+
_, _ = w.Write(result)
473+
}
474+
452475
func apiUploadFile(w http.ResponseWriter, r requestParser, user models.User) {
453476
request, ok := r.(*paramFilesAdd)
454477
if !ok {

internal/webserver/api/routing.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ var routes = []apiRoute{
4242
execution: apiConfigInfo,
4343
RequestParser: nil,
4444
},
45+
{
46+
Url: "/files/download/",
47+
ApiPerm: models.ApiPermDownload,
48+
execution: apiDownloadSingle,
49+
HasWildcard: true,
50+
RequestParser: &paramFilesDownloadSingle{},
51+
},
4552
{
4653
Url: "/files/list",
4754
ApiPerm: models.ApiPermView,
@@ -231,6 +238,20 @@ func (p *paramFilesListSingle) ProcessParameter(r *http.Request) error {
231238
return nil
232239
}
233240

241+
type paramFilesDownloadSingle struct {
242+
Id string
243+
WebRequest *http.Request
244+
IncreaseCounter bool `header:"increaseCounter"`
245+
foundHeaders map[string]bool
246+
}
247+
248+
func (p *paramFilesDownloadSingle) ProcessParameter(r *http.Request) error {
249+
p.WebRequest = r
250+
url := parseRequestUrl(r)
251+
p.Id = strings.TrimPrefix(url, "/files/download/")
252+
return nil
253+
}
254+
234255
type paramFilesAdd struct {
235256
Request *http.Request
236257
}

internal/webserver/api/routingParsing.go

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/webserver/web/static/apidocumentation/openapi.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,65 @@
108108
}
109109
}
110110
},
111+
"/files/download/{id}": {
112+
"get": {
113+
"tags": [
114+
"files"
115+
],
116+
"summary": "Downloads file with optionally increasing the download counter",
117+
"description": "This API call downloads a file that is not expired and increasing its download counter is disabled by default. Returns 404 if an invalid/expired ID was passed. Requires API permission DOWNLOAD. To download files that were not uploaded by the user, the user needs to have the user permission LIST",
118+
"operationId": "downloadsingle",
119+
"parameters": [
120+
{
121+
"name": "id",
122+
"in": "path",
123+
"required": true,
124+
"schema": {
125+
"type": "string"
126+
},
127+
"description": "ID of file to be downloaded"
128+
},
129+
{
130+
"name": "increaseCounter",
131+
"in": "header",
132+
"required": false,
133+
"schema": {
134+
"type": "boolean"
135+
},
136+
"description": "Increase counter if set to true"
137+
}
138+
],
139+
"security": [
140+
{
141+
"apikey": [
142+
"DOWNLOAD"
143+
]
144+
}
145+
],
146+
"responses": {
147+
"200": {
148+
"description": "Operation successful",
149+
"content": {
150+
"application/octet-stream": {
151+
"schema": {
152+
"type": "file",
153+
"format": "binary"
154+
}
155+
}
156+
}
157+
},
158+
"400": {
159+
"description": "Invalid input"
160+
},
161+
"401": {
162+
"description": "Invalid API key provided for authentication or API key does not have the required permission"
163+
},
164+
"404": {
165+
"description": "Invalid ID provided or file has expired"
166+
}
167+
}
168+
}
169+
},
111170
"/files/list": {
112171
"get": {
113172
"tags": [

internal/webserver/web/static/js/admin_ui_api.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ function addRowApi(apiKey, publicId) {
203203
},
204204
{
205205
perm: 'PERM_UPLOAD',
206-
icon: 'bi-file-earmark-arrow-up',
206+
icon: 'bi-file-earmark-plus',
207207
granted: true,
208208
title: 'Upload'
209209
},
@@ -226,9 +226,15 @@ function addRowApi(apiKey, publicId) {
226226
title: 'Replace Uploads'
227227
},
228228
{
229-
perm: 'PERM_MANAGE_FILE_REQUESTS',
229+
perm: 'PERM_DOWNLOAD',
230230
icon: 'bi-box-arrow-in-down',
231231
granted: false,
232+
title: 'Download Files'
233+
},
234+
{
235+
perm: 'PERM_MANAGE_FILE_REQUESTS',
236+
icon: 'bi-file-earmark-arrow-up',
237+
granted: false,
232238
title: 'Manage File Requests'
233239
},
234240
{

internal/webserver/web/static/js/min/admin.min.15.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/webserver/web/templates/html_api.tmpl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<td class="prevent-select">
4646
<i id="perm_view_{{ .PublicId }}" class="bi bi-eye {{if not .HasPermissionView}}perm-notgranted{{else}}perm-granted{{end}}" title="List Uploads" onclick='changeApiPermission("{{ .PublicId }}","PERM_VIEW", "perm_view_{{ .PublicId }}");'></i>
4747

48-
<i id="perm_upload_{{ .PublicId }}" class="bi bi-file-earmark-arrow-up {{if not .HasPermissionUpload}}perm-notgranted{{else}}perm-granted{{end}}" title="Upload" onclick='changeApiPermission("{{ .PublicId }}","PERM_UPLOAD", "perm_upload_{{ .PublicId }}");'></i>
48+
<i id="perm_upload_{{ .PublicId }}" class="bi bi-file-earmark-plus {{if not .HasPermissionUpload}}perm-notgranted{{else}}perm-granted{{end}}" title="Upload" onclick='changeApiPermission("{{ .PublicId }}","PERM_UPLOAD", "perm_upload_{{ .PublicId }}");'></i>
4949

5050
<i id="perm_edit_{{ .PublicId }}" class="bi bi-pencil {{if not .HasPermissionEdit}}perm-notgranted{{else}}perm-granted{{end}}" title="Edit Uploads" onclick='changeApiPermission("{{ .PublicId }}","PERM_EDIT", "perm_edit_{{ .PublicId }}");'></i>
5151

@@ -54,7 +54,10 @@
5454

5555
<i id="perm_replace_{{ .PublicId }}" class="bi bi-recycle {{if not (index $.UserMap .UserId).HasPermissionReplace}}perm-unavailable perm-nochange{{ else }}{{if not .HasPermissionReplace}}perm-notgranted{{else}}perm-granted{{end}}{{end}}" title="Replace Uploads" onclick='changeApiPermission("{{ .PublicId }}","PERM_REPLACE", "perm_replace_{{ .PublicId }}");'></i>
5656

57-
<i id="perm_manage_file_requests_{{ .PublicId }}" class="bi bi-box-arrow-in-down {{if not (index $.UserMap .UserId).HasPermissionCreateFileRequests}}perm-unavailable perm-nochange{{ else }}{{if not .HasPermissionManageFileRequests}}perm-notgranted{{else}}perm-granted{{end}}{{end}}" title="Manage File Requests" onclick='changeApiPermission("{{ .PublicId }}","PERM_MANAGE_FILE_REQUESTS", "perm_manage_file_requests_{{ .PublicId }}");'></i>
57+
<i id="perm_download_{{ .PublicId }}" class="bi bi-box-arrow-in-down {{if not .HasPermissionDownload}}perm-notgranted{{else}}perm-granted{{end}}" title="Download Files" onclick='changeApiPermission("{{ .PublicId }}","PERM_DOWNLOAD", "perm_download_{{ .PublicId }}");'></i>
58+
59+
60+
<i id="perm_manage_file_requests_{{ .PublicId }}" class="bi bi-file-earmark-arrow-up {{if not (index $.UserMap .UserId).HasPermissionCreateFileRequests}}perm-unavailable perm-nochange{{ else }}{{if not .HasPermissionManageFileRequests}}perm-notgranted{{else}}perm-granted{{end}}{{end}}" title="Manage File Requests" onclick='changeApiPermission("{{ .PublicId }}","PERM_MANAGE_FILE_REQUESTS", "perm_manage_file_requests_{{ .PublicId }}");'></i>
5861

5962
<i id="perm_users_{{ .PublicId }}" class="bi bi-people {{if not (index $.UserMap .UserId).HasPermissionManageUsers}}perm-unavailable perm-nochange{{ else }}{{if not .HasPermissionManageUsers}}perm-notgranted{{else}}perm-granted{{end}}{{end}}" title="Manage Users" onclick='changeApiPermission("{{ .PublicId }}","PERM_MANAGE_USERS", "perm_users_{{ .PublicId }}");'></i>
6063

0 commit comments

Comments
 (0)