Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,30 @@ PLIKD_DATA_BACKEND_CONFIG='{"Directory":"/var/files"}' ./plikd

## Server Settings

| Parameter | Default | Description |
|-----------|---------|-------------|
| `ListenPort` | `8080` | HTTP server port |
| `ListenAddress` | `0.0.0.0` | HTTP server bind address |
| `MetricsPort` | `0` | Prometheus metrics port (0 = disabled) |
| `Path` | `""` | HTTP root path prefix |
| `SslEnabled` | `false` | Enable TLS |
| `SslCert` / `SslKey` | — | TLS certificate and key paths |
| `TlsVersion` | `tlsv10` | Minimum TLS version |
| `NoWebInterface` | `false` | Disable web UI |
| `PlikDomain` | `""` | Public webapp URL (e.g., `https://plik.example.com`). **Domain only — no path.** Used for OAuth redirects and CORS. Does **not** restrict downloads on its own — set `DownloadDomain` for that |
| `DownloadDomain` | `""` | Enforce download domain (e.g., `https://dl.plik.example.com`). **Domain only — no path.** UI/API blocking and CORS require `PlikDomain` too |
| `DownloadDomainAlias` | `[]` | Additional accepted download hosts |
| `AssumeHTTPS` | `false` | Enable HSTS + Secure cookies (auto-enabled from `SslEnabled` or HTTPS `PlikDomain`) |
| `SessionTimeout` | `365d` | Authentication session duration |
| `AbuseContact` | `""` | Abuse contact email shown in footer. `settings.json` `"footer"` takes precedence when set |
| `WebappDirectory` | `../webapp/dist` | Web UI static files directory |
| `ClientsDirectory` | `../clients` | CLI client binaries directory |
| `ChangelogDirectory` | `../changelog` | Release changelog directory |
| `SourceIpHeader` | `""` | Header for real IP behind proxy (e.g., `X-Forwarded-For`) |
| `UploadWhitelist` | `[]` | Restrict uploads to IP ranges (CIDR) |
| `EnableArchiveCompression` | `true` | Enable zip compression for archive downloads. Set to `false` to use `zip.Store` (no compression) to prevent CPU exhaustion on public instances. See [Security — Archive Compression](/guide/security#archive-compression) |
| Parameter | Default | Description |
|-----------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ListenPort` | `8080` | HTTP server port |
| `ListenAddress` | `0.0.0.0` | HTTP server bind address |
| `MetricsPort` | `0` | Prometheus metrics port (0 = disabled) |
| `Path` | `""` | HTTP root path prefix |
| `SslEnabled` | `false` | Enable TLS |
| `SslCert` / `SslKey` | — | TLS certificate and key paths |
| `TlsVersion` | `tlsv10` | Minimum TLS version |
| `NoWebInterface` | `false` | Disable web UI |
| `PlikDomain` | `""` | Public webapp URL (e.g., `https://plik.example.com`). **Domain only — no path.** Used for OAuth redirects and CORS. Does **not** restrict downloads on its own — set `DownloadDomain` for that |
| `DownloadDomain` | `""` | Enforce download domain (e.g., `https://dl.plik.example.com`). **Domain only — no path.** UI/API blocking and CORS require `PlikDomain` too |
| `DownloadDomainAlias` | `[]` | Additional accepted download hosts |
| `AssumeHTTPS` | `false` | Enable HSTS + Secure cookies (auto-enabled from `SslEnabled` or HTTPS `PlikDomain`) |
| `SessionTimeout` | `365d` | Authentication session duration |
| `AbuseContact` | `""` | Abuse contact email shown in footer. `settings.json` `"footer"` takes precedence when set |
| `WebappDirectory` | `../webapp/dist` | Web UI static files directory |
| `ClientsDirectory` | `../clients` | CLI client binaries directory |
| `ChangelogDirectory` | `../changelog` | Release changelog directory |
| `SourceIpHeader` | `""` | Header for real IP behind proxy (e.g., `X-Forwarded-For`) |
| `UploadWhitelist` | `[]` | Restrict uploads to IP ranges (CIDR) |
| `EnableArchiveCompression` | `true` | Enable zip compression for archive downloads. Set to `false` to use `zip.Store` (no compression) to prevent CPU exhaustion on public instances. See [Security — Archive Compression](/guide/security#archive-compression) |
| `UploadIDLength` | `16` | Length of ID that will be used to uniquely identify uploads. |
| `UploadIDinB32` | `false` | Whether to use lowercase Crockford Base32 for ease of manual copying Upload IDs. If false, uses Base62 instead |

## Limits

Expand Down
4 changes: 2 additions & 2 deletions server/cmd/fakedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func fakedb(cmd *cobra.Command, args []string) {
}

for u := range fakedbUploads {
upload := common.NewUpload()
upload := common.NewUpload(false, 16)
upload.User = user.ID
if len(user.Tokens) > 0 && rand.Intn(2) == 0 {
upload.Token = user.Tokens[rand.Intn(len(user.Tokens))].Token
Expand Down Expand Up @@ -202,7 +202,7 @@ func fakedb(cmd *cobra.Command, args []string) {

// Create anonymous uploads (no user, no token)
for i := range fakedbAnonUploads {
upload := common.NewUpload()
upload := common.NewUpload(false, 16)
upload.Comments = fmt.Sprintf("anonymous upload %d", i)
upload.RemoteIP = fmt.Sprintf("192.168.%d.%d", rand.Intn(256), rand.Intn(256))
ttl := []int{0, 3600, 86400, 604800}[rand.Intn(4)]
Expand Down
6 changes: 6 additions & 0 deletions server/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type Configuration struct {
SourceIPHeader string `json:"-"`
UploadWhitelist []string `json:"-"`

UploadIDLength int `json:"uploadIDLength"`
UploadIDinB32 bool `json:"uploadIDinB32"`

// Feature Flags
FeatureAuthentication string `json:"feature_authentication"`
FeatureLocalLogin string `json:"feature_local_login"`
Expand Down Expand Up @@ -157,6 +160,9 @@ func NewConfiguration() (config *Configuration) {
config.DefaultTTL = 2592000 // 30 days
config.MaxTTL = 2592000 // 30 days

config.UploadIDLength = 16
config.UploadIDinB32 = false

// Deprecated feature flags default values to ensure backward compatibility <1.3.6
// New FeatureFlags default values are defined in feature_flags.go initialization functions
config.OneShot = true
Expand Down
18 changes: 11 additions & 7 deletions server/common/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,21 @@ func IsValidE2EEScheme(scheme string) bool {
return slices.Contains(validE2EESchemes, scheme)
}

// NewUpload creates a new upload object
func NewUpload() (upload *Upload) {
// NewUpload creates a new upload object and populates the ID and Upload Token
func NewUpload(useB32 bool, len int) (upload *Upload) {
upload = &Upload{}
upload.GenerateID()
upload.GenerateID(len, useB32)
upload.GenerateUploadToken()
return upload
}

// GenerateID generate a new Upload ID and UploadToken
func (upload *Upload) GenerateID() {
upload.ID = GenerateRandomID(16)
// GenerateID generate a new Upload ID
func (upload *Upload) GenerateID(len int, useB32 bool) {
if useB32 {
upload.ID = GenerateB32ID(len)
} else {
upload.ID = GenerateRandomID(len)
}
}

// GenerateUploadToken generate a new UploadToken
Expand Down Expand Up @@ -136,7 +140,7 @@ func (upload *Upload) IsExpired() bool {
// InitializeForTests initialize upload for database insert without config checks and override for testing purpose
func (upload *Upload) InitializeForTests() {
if upload.ID == "" {
upload.GenerateID()
upload.GenerateID(16, false)
}

upload.ExtendExpirationDate()
Expand Down
9 changes: 8 additions & 1 deletion server/common/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import (
)

func TestNewUpload(t *testing.T) {
upload := NewUpload()
upload := NewUpload(false, 16)
require.NotNil(t, upload)
require.NotZero(t, upload.ID, "missing upload id")
require.NotZero(t, upload.UploadToken, "missing upload token")
}

func TestNewUploadB32(t *testing.T) {
upload := NewUpload(true, 16)
require.NotNil(t, upload)
require.NotZero(t, upload.ID, "missing upload id")
require.NotZero(t, upload.UploadToken, "missing upload token")
Expand Down
22 changes: 22 additions & 0 deletions server/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,28 @@ func GenerateRandomID(length int) string {
return string(result)
}

// Base32Charset is similar to Crockford's Base32 specification to avoid similar-looking characters.
const Base32Charset = "0123456789abcdefghjkmnpqrstvwxyz"

// GenerateB32ID generates a random upload ID of the specified length
// It uses crypto/rand.Read but without rejection sampling since 256 is already completely divisible by 32
func GenerateB32ID(length int) string {
if length == 0 {
return ""
}

result := make([]byte, length)

if _, err := rand.Read(result); err != nil {
panic(fmt.Sprintf("failed to generate upload ID: %s", err))
}
for i := range length {
result[i] = Base32Charset[result[i]%32]
}

return string(result)
}

// IsPlikWebapp checks if the request comes from the Plik web application
func IsPlikWebapp(req *http.Request) bool {
return req.Header.Get("X-ClientApp") == "web_client"
Expand Down
2 changes: 1 addition & 1 deletion server/context/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// CreateUpload from params and context (check configuration and default values, generate upload and file IDs, ... )
func (ctx *Context) CreateUpload(params *common.Upload) (upload *common.Upload, err error) {
upload = common.NewUpload()
upload = common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)

if ctx.GetSourceIP() != nil {
upload.RemoteIP = ctx.GetSourceIP().String()
Expand Down
4 changes: 2 additions & 2 deletions server/context/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ func testUploadSize(t *testing.T, ok bool) {
defer setupNewMetadataBackend(ctx)()

// Create
upload := common.NewUpload()
upload := common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)
upload.User = ctx.user.ID
file := upload.NewFile()
file.Status = common.FileUploaded
Expand Down Expand Up @@ -768,7 +768,7 @@ func testUserTotalSize(t *testing.T, ok bool) {
defer setupNewMetadataBackend(ctx)()

// Create
upload := common.NewUpload()
upload := common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)
upload.User = ctx.user.ID
file := upload.NewFile()
file.Status = common.FileUploaded
Expand Down
2 changes: 1 addition & 1 deletion server/handlers/add_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ func testAddFileMaxUserSize(t *testing.T, ok bool) {

ctx.SetUser(user)

upload := common.NewUpload()
upload := common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)
upload.User = user.ID
f := upload.NewFile()
f.Size = 1000
Expand Down
6 changes: 3 additions & 3 deletions server/handlers/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,15 +288,15 @@ func TestSearchUsersNotAdmin(t *testing.T) {
}

func createTestUploads(t *testing.T, ctx *context.Context) {
upload1 := common.NewUpload()
upload1 := common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)
upload1.Comments = "1"
f1 := upload1.NewFile()
f1.Status = common.FileUploaded
f1.Size = 1
err := ctx.GetMetadataBackend().CreateUpload(upload1)
require.NoError(t, err, "unable to create upload1")

upload2 := common.NewUpload()
upload2 := common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)
upload2.Comments = "2"
f2 := upload2.NewFile()
f2.Status = common.FileUploaded
Expand All @@ -305,7 +305,7 @@ func createTestUploads(t *testing.T, ctx *context.Context) {
err = ctx.GetMetadataBackend().CreateUpload(upload2)
require.NoError(t, err, "unable to create upload2")

upload3 := common.NewUpload()
upload3 := common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)
upload3.Comments = "3"
f3 := upload3.NewFile()
f3.Status = common.FileUploaded
Expand Down
2 changes: 1 addition & 1 deletion server/middleware/create_upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func testCreateUploadMaxUserSize(t *testing.T, ok bool) {

ctx.SetUser(user)

upload := common.NewUpload()
upload := common.NewUpload(ctx.GetConfig().UploadIDinB32, ctx.GetConfig().UploadIDLength)
upload.User = user.ID
f := upload.NewFile()
f.Size = 1024
Expand Down
3 changes: 3 additions & 0 deletions server/plikd.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ MaxFilePerUpload = 1000
DefaultTTLStr = "30d" # 30 days
MaxTTLStr = "30d" # 0 : No limit

UploadIDLength = 16 # Length of ID that will be used to uniquely identify uploads. Default is 16.
UploadIDinB32 = false # Whether to use lowercase Crockford Base32 for ease of manual copying. If false, uses Base62 instead. Default is false.

# Feature flags to enable/disable Plik features.
# - disabled : feature is always off
# - enabled : feature is opt-in
Expand Down