| Server IP : 121.121.20.254 / Your IP : 216.73.216.201 Web Server : Microsoft-IIS/10.0 System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64 User : IUSR ( 0) PHP Version : 8.3.28 Disable Function : NONE MySQL : ON | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : C:/inetpub/wwwroot/YTLStockManagement/wp-content/plugins/novamira/includes/ |
Upload File : |
<?php
// SPDX-FileCopyrightText: 2026 Ovation S.r.l. <dev@novamira.ai>
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
/**
* Temporary signed upload endpoint support.
*/
if (!defined('ABSPATH')) {
exit();
}
add_action('rest_api_init', callback: 'novamira_register_upload_route');
/**
* Register the REST endpoint used by signed uploads.
*/
function novamira_register_upload_route(): void
{
$route_namespace = 'novamira/v1';
$route = '/upload';
register_rest_route($route_namespace, $route, [
'methods' => ['POST', 'PUT'],
'callback' => 'novamira_handle_signed_upload',
'permission_callback' => '__return_true',
]);
}
/**
* Sign an upload-link payload.
*
* @param array<string, mixed> $payload Upload token payload.
* @return string|WP_Error
*/
function novamira_sign_upload_payload(array $payload): string|WP_Error
{
$json = wp_json_encode($payload, JSON_UNESCAPED_SLASHES);
if (!is_string($json)) {
return new WP_Error('upload_token_encode_failed', 'Could not encode upload token payload.');
}
$body = novamira_base64url_encode($json);
$signature = hash_hmac('sha256', $body, novamira_upload_token_secret(), binary: true);
return $body . '.' . novamira_base64url_encode($signature);
}
/**
* Verify an upload-link token and return its payload.
*
* @return array<string, mixed>|WP_Error
*/
function novamira_verify_upload_token(string $token): array|WP_Error
{
$parts = explode('.', $token, limit: 2);
if (count($parts) !== 2) {
return new WP_Error('invalid_upload_token', 'Invalid upload token.', ['status' => 401]);
}
[$body, $signature] = $parts;
$expected = novamira_base64url_encode(hash_hmac('sha256', $body, novamira_upload_token_secret(), binary: true));
if (!hash_equals($expected, $signature)) {
return new WP_Error('invalid_upload_token', 'Invalid upload token signature.', ['status' => 401]);
}
$json = novamira_base64url_decode($body);
if ($json === false) {
return new WP_Error('invalid_upload_token', 'Invalid upload token payload.', ['status' => 401]);
}
/** @var array<string, mixed>|null $decoded */
$decoded = json_decode($json, associative: true);
if (!is_array($decoded)) {
return new WP_Error('invalid_upload_token', 'Invalid upload token payload.', ['status' => 401]);
}
$payload = [
'path' => $decoded['path'] ?? null,
'expires_at' => $decoded['expires_at'] ?? null,
'max_bytes' => $decoded['max_bytes'] ?? null,
'overwrite' => $decoded['overwrite'] ?? null,
'create_directories' => $decoded['create_directories'] ?? null,
];
$expires_at = (int) $payload['expires_at'];
if ($expires_at < time()) {
return new WP_Error('upload_token_expired', 'Upload token has expired.', ['status' => 401]);
}
return $payload;
}
/**
* Handle a signed upload request.
*
* @return array|WP_Error
*/
function novamira_handle_signed_upload(WP_REST_Request $request)
{
if (!novamira_is_enabled()) {
return new WP_Error('novamira_disabled', 'Novamira abilities are disabled.', ['status' => 403]);
}
$token = novamira_get_upload_token_from_request($request);
if ($token === '') {
return new WP_Error('missing_upload_token', 'Missing upload token.', ['status' => 401]);
}
$payload = novamira_verify_upload_token($token);
if (is_wp_error($payload)) {
return $payload;
}
$destination = novamira_prepare_upload_destination($payload);
if (is_wp_error($destination)) {
return $destination;
}
$source = novamira_open_upload_source($request);
if (is_wp_error($source)) {
return $source;
}
$stream = $source['stream'];
$result = $destination['overwrite']
? novamira_overwrite_upload_stream(
source: $stream,
resolved: $destination['path'],
max_bytes: $destination['max_bytes'],
)
: novamira_create_upload_stream(
source: $stream,
resolved: $destination['path'],
max_bytes: $destination['max_bytes'],
);
fclose($stream);
if (is_wp_error($result)) {
return $result;
}
clearstatcache(clear_realpath_cache: true, filename: $destination['path']);
return [
'path' => $destination['path'],
'bytes_written' => $result['bytes_written'],
'created' => $result['created'],
'directories_created' => $destination['directories_created'],
'size' => filesize($destination['path']),
'source' => $source['source'],
'filename' => $source['filename'],
];
}
/**
* Resolve and validate the upload destination from a verified token payload.
*
* @param array<string, mixed> $payload Verified upload token payload.
* @return array{path: string, max_bytes: int, overwrite: bool, directories_created: array}|WP_Error
*/
function novamira_prepare_upload_destination(array $payload): array|WP_Error
{
if (!is_string($payload['path']) || $payload['path'] === '') {
return new WP_Error('invalid_upload_token', 'Upload token does not contain a valid path.', ['status' => 401]);
}
$resolved = novamira_resolve_path(path: $payload['path'], must_exist: false);
if (is_wp_error($resolved)) {
return $resolved;
}
$symlink_error = novamira_reject_final_path_symlink($resolved);
if (is_wp_error($symlink_error)) {
return $symlink_error;
}
// Sandbox enforcement is intentionally limited to PHP execution paths.
// Non-PHP uploads outside the sandbox are part of the filesystem ability model.
$sandbox_error = novamira_check_php_execution_sandbox($resolved);
if (is_wp_error($sandbox_error)) {
return $sandbox_error;
}
$parent_dir = dirname($resolved);
$directories_created = [];
if (!is_dir($parent_dir)) {
if ($payload['create_directories'] !== true) {
return new WP_Error('directory_not_found', sprintf('Parent directory does not exist: %s', $parent_dir));
}
$directories_created = novamira_ensure_parent_dir($parent_dir);
if (is_wp_error($directories_created)) {
return $directories_created;
}
}
if (!is_writable($parent_dir)) {
return new WP_Error('directory_not_writable', sprintf('Parent directory is not writable: %s', $parent_dir));
}
return [
'path' => $resolved,
'max_bytes' => max(1, (int) $payload['max_bytes']),
'overwrite' => $payload['overwrite'] === true,
'directories_created' => $directories_created,
];
}
/**
* Return the upload token from request headers.
*/
function novamira_get_upload_token_from_request(WP_REST_Request $request): string
{
return novamira_rest_header_token($request, header_name: 'x-novamira-upload-token');
}
/**
* Open the uploaded file stream, either from multipart/form-data or the raw request body.
*
* @return array{stream: resource, source: string, filename: string}|WP_Error
*/
function novamira_open_upload_source(WP_REST_Request $request): array|WP_Error
{
$file = novamira_get_multipart_upload_file($request);
if ($file !== null) {
return novamira_open_multipart_upload_source($file);
}
$stream = fopen('php://input', mode: 'rb');
if ($stream === false) {
return new WP_Error('upload_read_failed', 'Could not read upload request body.');
}
return [
'stream' => $stream,
'source' => 'raw',
'filename' => '',
];
}
/**
* Return a multipart file entry from the request, if present.
*
* @return array<array-key, mixed>|null
*/
function novamira_get_multipart_upload_file(WP_REST_Request $request): ?array
{
/** @var array<string, array<array-key, mixed>> $files */
$files = $request->get_file_params();
foreach ($files as $field => $candidate) {
if ($field === 'file' || count($files) === 1) {
return $candidate;
}
}
return null;
}
/**
* Open a multipart upload source stream.
*
* @param array<array-key, mixed> $file File entry from WP_REST_Request::get_file_params().
* @return array{stream: resource, source: string, filename: string}|WP_Error
*/
function novamira_open_multipart_upload_source(array $file): array|WP_Error
{
$error = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
if ($error !== UPLOAD_ERR_OK) {
return new WP_Error('upload_failed', novamira_upload_error_message($error));
}
$tmp_name = '';
if (array_key_exists('tmp_name', $file) && is_string($file['tmp_name'])) {
$tmp_name = $file['tmp_name'];
}
if ($tmp_name === '' || !is_uploaded_file($tmp_name)) {
return new WP_Error('invalid_upload', 'The multipart upload did not contain a valid uploaded file.');
}
$stream = fopen($tmp_name, mode: 'rb');
if ($stream === false) {
return new WP_Error('upload_read_failed', 'Could not read uploaded file.');
}
$name = '';
if (array_key_exists('name', $file) && is_string($file['name'])) {
$name = $file['name'];
}
return [
'stream' => $stream,
'source' => 'multipart',
'filename' => sanitize_file_name($name),
];
}
/**
* Write an upload stream to a new destination path.
*
* @param resource $source
* @return array{bytes_written: int, created: bool}|WP_Error
*/
function novamira_create_upload_stream($source, string $resolved, int $max_bytes): array|WP_Error
{
$target = fopen($resolved, mode: 'xb');
if ($target === false) {
if (file_exists($resolved)) {
return new WP_Error('file_exists', sprintf('Destination already exists: %s', $resolved));
}
return new WP_Error('upload_write_failed', sprintf('Could not open destination for writing: %s', $resolved));
}
$bytes_written = novamira_copy_limited_stream(source: $source, target: $target, max_bytes: $max_bytes);
fclose($target);
if (is_wp_error($bytes_written)) {
unlink($resolved);
return $bytes_written;
}
chmod(filename: $resolved, permissions: 0644);
return [
'bytes_written' => $bytes_written,
'created' => true,
];
}
/**
* Write an upload stream, replacing the destination path if it exists.
*
* @param resource $source
* @return array{bytes_written: int, created: bool}|WP_Error
*/
function novamira_overwrite_upload_stream($source, string $resolved, int $max_bytes): array|WP_Error
{
$created = !file_exists($resolved);
$temporary_path = tempnam(dirname($resolved), prefix: '.novamira-upload-');
if ($temporary_path === false) {
return new WP_Error('upload_temp_failed', sprintf(
'Could not create temporary upload file in: %s',
dirname($resolved),
));
}
$target = fopen($temporary_path, mode: 'wb');
if ($target === false) {
unlink($temporary_path);
return new WP_Error('upload_write_failed', sprintf('Could not open destination for writing: %s', $resolved));
}
$bytes_written = novamira_copy_limited_stream(source: $source, target: $target, max_bytes: $max_bytes);
fclose($target);
if (is_wp_error($bytes_written)) {
unlink($temporary_path);
return $bytes_written;
}
if (!rename($temporary_path, $resolved)) {
unlink($temporary_path);
return new WP_Error('upload_move_failed', sprintf('Could not move uploaded file into place: %s', $resolved));
}
chmod(filename: $resolved, permissions: 0644);
return [
'bytes_written' => $bytes_written,
'created' => $created,
];
}
/**
* Copy a stream while enforcing a byte limit.
*
* @param resource $source
* @param resource $target
* @return int|WP_Error
*/
function novamira_copy_limited_stream($source, $target, int $max_bytes): int|WP_Error
{
$bytes_written = 0;
while (!feof($source)) {
$chunk = fread($source, length: 1_048_576);
if ($chunk === false) {
return new WP_Error('upload_read_failed', 'Could not read upload stream.');
}
if ($chunk === '') {
continue;
}
$bytes_written += strlen($chunk);
if ($bytes_written > $max_bytes) {
return new WP_Error('upload_too_large', sprintf(
'Upload exceeds the signed URL limit of %d bytes.',
$max_bytes,
));
}
if (fwrite($target, $chunk) === false) {
return new WP_Error('upload_write_failed', 'Could not write upload stream.');
}
}
return $bytes_written;
}
/**
* Return a human-readable upload error message.
*/
function novamira_upload_error_message(int $error): string
{
return match ($error) {
UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Uploaded file exceeds the configured PHP upload size limit.',
UPLOAD_ERR_PARTIAL => 'The file was only partially uploaded.',
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
UPLOAD_ERR_NO_TMP_DIR => 'The server is missing a temporary upload directory.',
UPLOAD_ERR_CANT_WRITE => 'The server could not write the uploaded file to disk.',
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.',
default => 'The upload failed.',
};
}
/**
* Return the signing secret for upload URLs.
*/
function novamira_upload_token_secret(): string
{
return wp_salt('auth') . '|' . wp_salt('secure_auth') . '|novamira-upload-link';
}
/**
* Encode bytes with base64url.
*/
function novamira_base64url_encode(string $value): string
{
return rtrim(strtr(base64_encode($value), from: '+/', to: '-_'), characters: '=');
}
/**
* Decode base64url bytes.
*/
function novamira_base64url_decode(string $value): string|false
{
$padding = strlen($value) % 4;
if ($padding !== 0) {
$value .= str_repeat('=', 4 - $padding);
}
return base64_decode(strtr($value, from: '-_', to: '+/'), strict: true);
}