AttachmentFilePolicy
in package
Policy for advert attachments (Bazaar / Comcat), which are written to the public webroot (`www/modules/{bazaar,comcat}/files/items/...`) with an extension taken from the file name.
- Extensions are an allowlist, not a deny-list: advert attachments are a narrow, known case
(documents, spreadsheets, images, archives, media). The file manager deny-list
(FileSystemRepository) fits
uploads/, which holds unknown legitimate types; this case has no such need. - An empty extension is rejected. A double extension (
shell.php.pdf) is harmless because the file is stored under a random name with the single last extension. - The content is read (
finfo), not the upload header: active content (HTML, SVG, XML, PHP, JS) is rejected even under an innocent extension, since the web server or browser may render it by content.
IMPORTANT: the authoritative check is the Api call (self::rejectionReason()) before any
write to disk. The dropzones (self::ALLOWED_EXTENSIONS) only give immediate feedback and
can be bypassed; without the Api check a .php file would land in a directory the root
www/.htaccess serves directly.
Table of Contents
Constants
- ALLOWED_EXTENSIONS : array<string|int, mixed> = [ // documents 'pdf', 'txt', 'rtf', 'doc', 'doc...
- Extensions without the dot, lowercase.
- REASON_CONTENT : string = 'content'
- REASON_EXTENSION : string = 'extension'
- ACTIVE_CONTENT_MIME : array<string|int, mixed> = ['text/html', 'application/xhtml+xml', 'image/s...
- MIME types detected from the content that must not appear in the public area whatever the extension. Prefix matches (`text/x-php`, `application/x-httpd-php`) are handled by {@see self::isActiveContent()}.
Methods
- extensionOf() : string
- isAllowedExtension() : bool
- rejectionReason() : string|null
- Returns `null` when the file may be stored, otherwise the rejection reason ({@see self::REASON_EXTENSION} / {@see self::REASON_CONTENT}).
- detectMime() : string
- isActiveContent() : bool
Constants
ALLOWED_EXTENSIONS
Extensions without the dot, lowercase.
public
array<string|int, mixed>
ALLOWED_EXTENSIONS
= [
// documents
'pdf',
'txt',
'rtf',
'doc',
'docx',
'odt',
// spreadsheets and presentations
'xls',
'xlsx',
'ods',
'csv',
'ppt',
'pptx',
'odp',
// raster images (SVG is active content)
'jpg',
'jpeg',
'png',
'gif',
'webp',
'heic',
// archives
'zip',
'rar',
'7z',
// media
'mp3',
'mp4',
'mov',
]
REASON_CONTENT
public
string
REASON_CONTENT
= 'content'
REASON_EXTENSION
public
string
REASON_EXTENSION
= 'extension'
ACTIVE_CONTENT_MIME
MIME types detected from the content that must not appear in the public area whatever the extension. Prefix matches (`text/x-php`, `application/x-httpd-php`) are handled by {@see self::isActiveContent()}.
private
array<string|int, mixed>
ACTIVE_CONTENT_MIME
= ['text/html', 'application/xhtml+xml', 'image/svg+xml', 'text/xml', 'application/xml', 'text/javascript', 'application/javascript', 'application/x-javascript', 'application/x-shockwave-flash']
Methods
extensionOf()
public
static extensionOf(string $fileName) : string
Parameters
- $fileName : string
Return values
stringisAllowedExtension()
public
static isAllowedExtension(string $extension) : bool
Parameters
- $extension : string
Return values
boolrejectionReason()
Returns `null` when the file may be stored, otherwise the rejection reason ({@see self::REASON_EXTENSION} / {@see self::REASON_CONTENT}).
public
static rejectionReason(string $fileName, string $content) : string|null
Parameters
- $fileName : string
- $content : string
Return values
string|nulldetectMime()
private
static detectMime(string $content) : string
Parameters
- $content : string
Return values
stringisActiveContent()
private
static isActiveContent(string $mime) : bool
Parameters
- $mime : string