CoolCMS API

FileSystemRepository
in package

FinalYes

Filesystem repository of the uploads folder (`%appWwwDir%uploads/`).

All paths are relative to the root. The class strictly prevents escaping the root (path traversal, symlinks): the admin never receives a real server path and sees nothing outside uploads/.

Produces the value objects FtpFile / FtpFolder. All real filesystem work (scandir, mkdir, rename, unlink, read/write) happens here only.

Table of Contents

Constants

DANGEROUS_NAME_SEGMENTS  : array<string|int, mixed> = ['php', 'php3', 'php4', 'php5', 'php7', 'php8',...
Deny-list of executable and server configuration extensions.

Properties

$config  : Config
$svgSanitizer  : SvgSanitizer

Methods

__construct()  : mixed
createFolder()  : Result
Creates the folder `$name` in the relative folder `$relParent` (the name is sanitized).
delete()  : bool
Deletes a file or (recursively) a folder inside the root.
listFolder()  : FtpFile>}
Lists a folder: subfolders and files (value objects with relative paths).
read()  : array{name: string, content: string, contentType: string}|null
Reads a file for download (proxy): returns the name, base64 content and MIME type.
rename()  : bool
Renames a file or folder to `$newName` (in the same folder).
sanitizeAll()  : Result
Walks the whole uploads tree and renames files and folders with a non-conforming name to a sanitized form (diacritics and spaces become a safe shape); a collision gets a `-N` suffix.
writeFile()  : Result
Writes an uploaded file into a relative folder under the sanitized `$name` (diacritics and spaces removed, extension kept).
deleteDir()  : bool
Deletes a folder recursively (called only on a verified path inside the root).
isSafeFileName()  : bool
Rejects names whose file the server could execute or read as configuration.
logUnsafeFileName()  : void
Logs a rejected attempt with a dangerous name (Tracy channel `security`).
resolve()  : string|null
Safely resolves a relative path to an absolute one inside the root.
root()  : string
Absolute uploads root (realpath, no trailing slash); empty means unavailable.
safeName()  : string|null
Whether the name is a safe file/folder name (basename only, no slashes, `..` or control characters).
sanitize()  : string|null
Sanitizes a name for the filesystem and URLs.
sanitizeDir()  : array{renamed: int, skipped: int}
Sanitizes a folder recursively, bottom-up: the content first, then the folder itself.
uniqueTarget()  : string
Returns a free (non-colliding) target name in a folder; on a collision appends `-1`, `-2`, .
withinRoot()  : bool
Whether the absolute path is inside the root (or is the root itself).

Constants

DANGEROUS_NAME_SEGMENTS

Deny-list of executable and server configuration extensions.

private array<string|int, mixed> DANGEROUS_NAME_SEGMENTS = ['php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps', 'pht', 'phtml', 'phar', 'cgi', 'pl', 'py', 'sh', 'bash', 'htaccess', 'htpasswd', 'ini', 'exe', 'dll', 'so']

A .php file in uploads/ could be served directly by Apache. An allowlist would break unknown legitimate upload types, so the deny-list covers only scripts and sensitive configuration files and does not restrict regular uploads.

Properties

Methods

createFolder()

Creates the folder `$name` in the relative folder `$relParent` (the name is sanitized).

public createFolder(string $relParent, string $name) : Result

IMPORTANT: returns a Result, which is always truthy; callers must ask ->isSuccess(). The failure reason is carried by ResultCode; it does not reach the wire {success} of the Api presenter, which stays unchanged.

Parameters
$relParent : string
$name : string
Return values
Result

delete()

Deletes a file or (recursively) a folder inside the root.

public delete(string $rel) : bool
Parameters
$rel : string
Return values
bool

listFolder()

Lists a folder: subfolders and files (value objects with relative paths).

public listFolder(string $rel) : FtpFile>}
Parameters
$rel : string
Return values
FtpFile>}

read()

Reads a file for download (proxy): returns the name, base64 content and MIME type.

public read(string $rel) : array{name: string, content: string, contentType: string}|null

Returns null when the path is not a valid file inside the root. The real path never leaves the class.

Parameters
$rel : string
Return values
array{name: string, content: string, contentType: string}|null

rename()

Renames a file or folder to `$newName` (in the same folder).

public rename(string $rel, string $newName) : bool

A file keeps its original extension: the admin renames only the base of the name.

Parameters
$rel : string
$newName : string
Return values
bool

sanitizeAll()

Walks the whole uploads tree and renames files and folders with a non-conforming name to a sanitized form (diacritics and spaces become a safe shape); a collision gets a `-N` suffix.

public sanitizeAll() : Result

Returns a Result: success carries data = ['renamed' => int, 'skipped' => int]. An unknown uploads root (missing configuration) is the failure ResultCode::INVALID_CONFIGURATION, not a silent zero.

Return values
Result

writeFile()

Writes an uploaded file into a relative folder under the sanitized `$name` (diacritics and spaces removed, extension kept).

public writeFile(string $relFolder, string $name, string $bytes) : Result

IMPORTANT: returns a Result, see createFolder().

Parameters
$relFolder : string
$name : string
$bytes : string
Return values
Result

deleteDir()

Deletes a folder recursively (called only on a verified path inside the root).

private deleteDir(string $abs) : bool
Parameters
$abs : string
Return values
bool

isSafeFileName()

Rejects names whose file the server could execute or read as configuration.

private isSafeFileName(string $name) : bool

Checks every dot segment of the name, not only the last one: Apache AddHandler/AddType matches shell.php.jpg too (the PHP handler runs although the name looks like an image). Hidden files (.htaccess, .user.ini) are rejected by the leading dot regardless of the deny-list.

Parameters
$name : string
Return values
bool

logUnsafeFileName()

Logs a rejected attempt with a dangerous name (Tracy channel `security`).

private logUnsafeFileName(string $action, string $name) : void

A logger failure (unconfigured directory, full disk) must not break the rejection itself: the caller always gets false, just without a record.

Parameters
$action : string
$name : string

resolve()

Safely resolves a relative path to an absolute one inside the root.

private resolve(string $rel, bool $mustExist) : string|null

$mustExist requires an existing target (realpath); otherwise the parent and the last segment are validated. Returns null on any attempt to escape the root or on invalid input.

Parameters
$rel : string
$mustExist : bool
Return values
string|null

root()

Absolute uploads root (realpath, no trailing slash); empty means unavailable.

private root() : string
Return values
string

safeName()

Whether the name is a safe file/folder name (basename only, no slashes, `..` or control characters).

private safeName(string $name) : string|null
Parameters
$name : string
Return values
string|null

sanitize()

Sanitizes a name for the filesystem and URLs.

private sanitize(string $name, bool $isFile) : string|null

Transliterates diacritics, turns spaces into - and keeps only [a-zA-Z0-9-] (plus the dot that separates the extension for files). A file keeps its extension (webalized separately, lowercase). Returns null when no usable name remains after cleaning.

Parameters
$name : string
$isFile : bool
Return values
string|null

sanitizeDir()

Sanitizes a folder recursively, bottom-up: the content first, then the folder itself.

private sanitizeDir(string $dirAbs) : array{renamed: int, skipped: int}
Parameters
$dirAbs : string
Return values
array{renamed: int, skipped: int}

uniqueTarget()

Returns a free (non-colliding) target name in a folder; on a collision appends `-1`, `-2`, .

private uniqueTarget(string $dirAbs, string $name) : string

..

Parameters
$dirAbs : string
$name : string
Return values
string

withinRoot()

Whether the absolute path is inside the root (or is the root itself).

private withinRoot(string $abs, string $root) : bool
Parameters
$abs : string
$root : string
Return values
bool
On this page

Search results