Files
Gokapi/internal/models/FileList.go
2025-05-25 17:07:06 +02:00

157 lines
8.7 KiB
Go

package models
import (
"encoding/json"
"fmt"
"github.com/jinzhu/copier"
"net/url"
)
// File is a struct used for saving information about an uploaded file
type File struct {
Id string `json:"Id" redis:"Id"` // The internal ID of the file
Name string `json:"Name" redis:"Name"` // The filename. Will be 'Encrypted file' for end-to-end encrypted files
Size string `json:"Size" redis:"Size"` // Filesize in a human-readable format
SHA1 string `json:"SHA1" redis:"SHA1"` // The hash of the file, used for deduplication
PasswordHash string `json:"PasswordHash" redis:"PasswordHash"` // The hash of the password (if the file is password protected)
HotlinkId string `json:"HotlinkId" redis:"HotlinkId"` // If file is a picture file and can be hotlinked, this is the ID for the hotlink
ContentType string `json:"ContentType" redis:"ContentType"` // The MIME type for the file
AwsBucket string `json:"AwsBucket" redis:"AwsBucket"` // If the file is stored in the cloud, this is the bucket that is being used
ExpireAtString string `json:"ExpireAtString" redis:"ExpireAtString"` // Time expiry in a human-readable format in local time
ExpireAt int64 `json:"ExpireAt" redis:"ExpireAt"` // UTC timestamp of file expiry
SizeBytes int64 `json:"SizeBytes" redis:"SizeBytes"` // Filesize in bytes
UploadDate int64 `json:"UploadDate" redis:"UploadDate"` // UTC timestamp of upload time
DownloadsRemaining int `json:"DownloadsRemaining" redis:"DownloadsRemaining"` // The remaining downloads for this file
DownloadCount int `json:"DownloadCount" redis:"DownloadCount"` // The amount of times the file has been downloaded
UserId int `json:"UserId" redis:"UserId"` // The user ID of the uploader
Encryption EncryptionInfo `json:"Encryption" redis:"-"` // If the file is encrypted, this stores all info for decrypting
UnlimitedDownloads bool `json:"UnlimitedDownloads" redis:"UnlimitedDownloads"` // True if the uploader did not limit the downloads
UnlimitedTime bool `json:"UnlimitedTime" redis:"UnlimitedTime"` // True if the uploader did not limit the time
InternalRedisEncryption []byte `redis:"EncryptionRedis"` // This field is an internal field, used to store the EncryptionInfo in a Redis Hashmap
}
// FileApiOutput will be displayed for public outputs from the ID, hiding sensitive information
type FileApiOutput struct {
Id string `json:"Id"` // The internal ID of the file
Name string `json:"Name"` // The filename. Will be 'Encrypted file' for end-to-end encrypted files
Size string `json:"Size"` // Filesize in a human-readable format
HotlinkId string `json:"HotlinkId"` // If file is a picture file and can be hotlinked, this is the ID for the hotlink
ContentType string `json:"ContentType"` // The MIME type for the file
ExpireAtString string `json:"ExpireAtString"` // Time expiry in a human-readable format in local time
UrlDownload string `json:"UrlDownload"` // The public download URL for the file
UrlHotlink string `json:"UrlHotlink"` // The public hotlink URL for the file
UploadDate int64 `json:"UploadDate"` // UTC timestamp of upload time
ExpireAt int64 `json:"ExpireAt"` // "UTC timestamp of file expiry
SizeBytes int64 `json:"SizeBytes"` // Filesize in bytes
DownloadsRemaining int `json:"DownloadsRemaining"` // The remaining downloads for this file
DownloadCount int `json:"DownloadCount"` // The amount of times the file has been downloaded
UnlimitedDownloads bool `json:"UnlimitedDownloads"` // True if the uploader did not limit the downloads
UnlimitedTime bool `json:"UnlimitedTime"` // True if the uploader did not limit the time
RequiresClientSideDecryption bool `json:"RequiresClientSideDecryption"` // True if the file has to be decrypted client-side
IsEncrypted bool `json:"IsEncrypted"` // True if the file is encrypted
IsEndToEndEncrypted bool `json:"IsEndToEndEncrypted"` // True if the file is end-to-end encrypted
IsPasswordProtected bool `json:"IsPasswordProtected"` // True if a password has to be entered before downloading the file
IsSavedOnLocalStorage bool `json:"IsSavedOnLocalStorage"` // True if the file does not use cloud storage
UploaderId int `json:"UploaderId"` // The user ID of the uploader
}
// EncryptionInfo holds information about the encryption used on the file
type EncryptionInfo struct {
IsEncrypted bool `json:"IsEncrypted" redis:"IsEncrypted"`
IsEndToEndEncrypted bool `json:"IsEndToEndEncrypted" redis:"IsEndToEndEncrypted"`
DecryptionKey []byte `json:"DecryptionKey" redis:"DecryptionKey"`
Nonce []byte `json:"Nonce" redis:"Nonce"`
}
// IsLocalStorage returns true if the file is not stored on a remote storage
func (f *File) IsLocalStorage() bool {
return f.AwsBucket == ""
}
// ToFileApiOutput returns a json object without sensitive information
func (f *File) ToFileApiOutput(serverUrl string, useFilenameInUrl bool) (FileApiOutput, error) {
var result FileApiOutput
err := copier.Copy(&result, &f)
if err != nil {
return FileApiOutput{}, err
}
result.IsPasswordProtected = f.PasswordHash != ""
result.IsEncrypted = f.Encryption.IsEncrypted
result.IsSavedOnLocalStorage = f.AwsBucket == ""
if f.Encryption.IsEndToEndEncrypted || f.RequiresClientDecryption() {
result.RequiresClientSideDecryption = true
}
result.IsEndToEndEncrypted = f.Encryption.IsEndToEndEncrypted
result.UrlHotlink = getHotlinkUrl(result, serverUrl, useFilenameInUrl)
result.UrlDownload = getDownloadUrl(result, serverUrl, useFilenameInUrl)
result.UploaderId = f.UserId
return result, nil
}
func getDownloadUrl(input FileApiOutput, serverUrl string, useFilename bool) string {
if useFilename {
return serverUrl + "d/" + input.Id + "/" + url.PathEscape(input.Name)
}
return serverUrl + "d?id=" + input.Id
}
func getHotlinkUrl(input FileApiOutput, serverUrl string, useFilename bool) string {
if input.RequiresClientSideDecryption || input.IsPasswordProtected {
return ""
}
if input.HotlinkId != "" {
return serverUrl + "h/" + input.HotlinkId
}
if useFilename {
return serverUrl + "dh/" + input.Id + "/" + url.PathEscape(input.Name)
}
return serverUrl + "downloadFile?id=" + input.Id
}
// ToJsonResult converts the file info to a json String used for returning a result for an upload
func (f *File) ToJsonResult(serverUrl string, includeFilename bool) string {
info, err := f.ToFileApiOutput(serverUrl, includeFilename)
if err != nil {
return errorAsJson(err)
}
byteOutput, err := json.Marshal(Result{
Result: "OK",
IncludeFilename: includeFilename,
FileInfo: info,
})
if err != nil {
return errorAsJson(err)
}
return string(byteOutput)
}
// RequiresClientDecryption checks if the file needs to be decrypted by the client
// (if remote storage or end-to-end encryption)
func (f *File) RequiresClientDecryption() bool {
if !f.Encryption.IsEncrypted {
return false
}
return !f.IsLocalStorage() || f.Encryption.IsEndToEndEncrypted
}
func errorAsJson(err error) string {
fmt.Println(err)
return "{\"Result\":\"error\",\"ErrorMessage\":\"" + err.Error() + "\"}"
}
// Result is the struct used for the result after an upload
// swagger:model UploadResult
type Result struct {
Result string `json:"Result"`
FileInfo FileApiOutput `json:"FileInfo"`
IncludeFilename bool `json:"IncludeFilename"`
}
// DownloadStatus contains current downloads, so they do not get removed during cleanup
type DownloadStatus struct {
Id string
FileId string
ExpireAt int64
}