UploadedItem
in package
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:
- The call is internal (
$internalCall, framework state from InternalCallContext::isActive(), set only byApiBridge::internalCall()independently of RBAC, never a payload value). - The path lies inside the dropzone root after
realpath()(DropzoneTempRoot). - It is a regular file, not a symlink or folder, and the path has no
... An externalsaveadditionally stripspathfrommetaat 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
KEY_DATA
public
string
KEY_DATA
= 'data'
KEY_PATH
public
string
KEY_PATH
= 'path'
REASON_INVALID
public
string
REASON_INVALID
= 'invalid'
REASON_NO_ROOT
public
string
REASON_NO_ROOT
= 'noRoot'
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
boolisBlank()
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
boolpathRejection()
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|nullread()
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);
nullmeanspathis 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 passfalse - $maxBytes : int|null = null
-
size ceiling; for
pathit is checked viafilesize()before reading
Return values
string|nullresolvePath()
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|nullstripPaths()
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: arraythe 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]