CoolCMS API

UploadedItem
in package

FinalYes

The single reader of an uploaded item's content on the Api side.

Forms send only a reference to the file in the dropzone temporary folder ({name, path}) instead of all files as base64 ({name, data}), which would exceed memory_limit with 60-80 MB of photos and end in a 500. The Api reads each file here, one at a time, right before processing.

An item carries either data (base64; external client, classic upload) or path. When the path key is present it alone decides and data is never used as a fallback, so a rejected path cannot pass through another channel.

IMPORTANT: path reads a file on the server, so it is accepted only when:

  1. The call is internal ($internalCall, framework state from InternalCallContext::isActive(), set only by ApiBridge::internalCall() independently of RBAC, never a payload value).
  2. The path lies inside the dropzone root after realpath() (DropzoneTempRoot).
  3. It is a regular file, not a symlink or folder, and the path has no ... An external save additionally strips path from meta at the boundary (SaveTrait::stripExternalUploadPaths()). Without these checks a client could read any file of the server.

Static and stateless (like AttachmentFilePolicy); logging of rejections is left to the caller via self::pathRejection().

Table of Contents

Constants

KEY_DATA  : string = 'data'
KEY_PATH  : string = 'path'
REASON_INVALID  : string = 'invalid'
REASON_NO_ROOT  : string = 'noRoot'
REASON_NOT_FILE  : string = 'notFile'
REASON_NOT_INTERNAL  : string = 'notInternal'
REASON_OUTSIDE_ROOT  : string = 'outsideRoot'

Methods

hasPath()  : bool
Returns whether the item carries a file reference (valid or not).
isBlank()  : bool
Returns whether the item has no content: no `path`, and `data` missing or empty. The caller discards it (no file, no DB row) and the save does not fail because of it. Empty content (a `path` file of 0 bytes) is checked by the caller after reading (`=== ''`).
pathRejection()  : string|null
Returns why {@see self::resolvePath()} would reject the path (`REASON_*`), `null` when it is fine. The caller writes it to the `auth` log channel; the reader logs nothing itself.
read()  : string|null
Returns the item content, or `null` when it cannot (or must not) be read.
resolvePath()  : string|null
Returns the absolute (real) path of the item file, or `null` meaning do not touch the file.
stripPaths()  : array{0: array, 1: int}
Recursively removes the `path` key from all nested items (external boundary of `save`).
inspectPath()  : array{0: ?string, 1: ?string}

Constants

REASON_NOT_FILE

public string REASON_NOT_FILE = 'notFile'

REASON_NOT_INTERNAL

public string REASON_NOT_INTERNAL = 'notInternal'

REASON_OUTSIDE_ROOT

public string REASON_OUTSIDE_ROOT = 'outsideRoot'

Methods

hasPath()

Returns whether the item carries a file reference (valid or not).

public static hasPath(array<string|int, mixed> $item) : bool
Parameters
$item : array<string|int, mixed>
Return values
bool

isBlank()

Returns whether the item has no content: no `path`, and `data` missing or empty. The caller discards it (no file, no DB row) and the save does not fail because of it. Empty content (a `path` file of 0 bytes) is checked by the caller after reading (`=== ''`).

public static isBlank(array<string|int, mixed> $item) : bool
Parameters
$item : array<string|int, mixed>
Return values
bool

pathRejection()

Returns why {@see self::resolvePath()} would reject the path (`REASON_*`), `null` when it is fine. The caller writes it to the `auth` log channel; the reader logs nothing itself.

public static pathRejection(mixed $path, string|null $dropzoneRoot, bool $internalCall) : string|null
Parameters
$path : mixed
$dropzoneRoot : string|null
$internalCall : bool
Return values
string|null

read()

Returns the item content, or `null` when it cannot (or must not) be read.

public static read(array<string|int, mixed> $item, string|null $dropzoneRoot, bool $internalCall[, bool $strictBase64 = true ][, int|null $maxBytes = null ]) : string|null
Parameters
$item : array<string|int, mixed>

payload item ({name, data} / {name, path})

$dropzoneRoot : string|null

dropzone root (DropzoneTempRoot); null means path is not accepted

$internalCall : bool

the call came internally through the bridge (see the class docblock)

$strictBase64 : bool = true

base64_decode(..., strict); callers that used to decode loosely pass false

$maxBytes : int|null = null

size ceiling; for path it is checked via filesize() before reading

Return values
string|null

resolvePath()

Returns the absolute (real) path of the item file, or `null` meaning do not touch the file.

public static resolvePath(mixed $path, string|null $dropzoneRoot, bool $internalCall) : string|null

The rejection reason comes from self::pathRejection().

Parameters
$path : mixed
$dropzoneRoot : string|null
$internalCall : bool
Return values
string|null

stripPaths()

Recursively removes the `path` key from all nested items (external boundary of `save`).

public static stripPaths(array<string|int, mixed> $value) : array{0: array, 1: int}
Parameters
$value : array<string|int, mixed>
Return values
array{0: array, 1: int} —

the cleaned array and the number of removed keys

inspectPath()

private static inspectPath(mixed $path, string|null $dropzoneRoot, bool $internalCall) : array{0: ?string, 1: ?string}
Parameters
$path : mixed
$dropzoneRoot : string|null
$internalCall : bool
Return values
array{0: ?string, 1: ?string} —

[real path or null, rejection reason or null]

On this page

Search results