Add display picture support and error code refactor

This commit is contained in:
netkas 2024-12-24 15:05:35 -05:00
parent 85bdff7d3c
commit 738f8a455c
7 changed files with 523 additions and 286 deletions

View file

@ -37,7 +37,7 @@
*/
public function getUserDisplayImagesPath(): string
{
return $this->userDisplayImagesPath;
return $this->path . DIRECTORY_SEPARATOR . $this->userDisplayImagesPath;
}
/**

View file

@ -0,0 +1,69 @@
<?php
namespace Socialbox\Classes\StandardMethods;
use Exception;
use Socialbox\Abstracts\Method;
use Socialbox\Classes\Configuration;
use Socialbox\Classes\Utilities;
use Socialbox\Enums\Flags\SessionFlags;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\StandardException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\RegisteredPeerManager;
use Socialbox\Managers\SessionManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\RpcRequest;
class SettingsSetDisplayPicture extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
if(!$rpcRequest->containsParameter('image'))
{
return $rpcRequest->produceError(StandardError::RPC_INVALID_ARGUMENTS, "Missing 'image' parameter");
}
if(strlen($rpcRequest->getParameter('image')) > Configuration::getStorageConfiguration()->getUserDisplayImagesMaxSize())
{
return $rpcRequest->produceError(StandardError::RPC_INVALID_ARGUMENTS, "Image size exceeds the maximum allowed size of " . Configuration::getStorageConfiguration()->getUserDisplayImagesMaxSize() . " bytes");
}
try
{
$decodedImage = base64_decode($rpcRequest->getParameter('image'));
if($decodedImage === false)
{
return $rpcRequest->produceError(StandardError::BAD_REQUEST, "Failed to decode JPEG image base64 data");
}
$sanitizedImage = Utilities::resizeImage(Utilities::sanitizeJpeg($decodedImage), 126, 126);
}
catch(Exception $e)
{
throw new StandardException('Failed to process JPEG image: ' . $e->getMessage(), StandardError::BAD_REQUEST, $e);
}
try
{
// Set the password
RegisteredPeerManager::updateDisplayPicture($request->getPeer(), $sanitizedImage);
// Remove the SET_DISPLAY_PICTURE flag
SessionManager::removeFlags($request->getSessionUuid(), [SessionFlags::SET_DISPLAY_PICTURE]);
// Check & update the session flow
SessionManager::updateFlow($request->getSession());
}
catch(Exception $e)
{
throw new StandardException('Failed to update display picture: ' . $e->getMessage(), StandardError::INTERNAL_SERVER_ERROR, $e);
}
return $rpcRequest->produceResponse(true);
}
}

View file

@ -1,287 +1,367 @@
<?php
namespace Socialbox\Classes;
namespace Socialbox\Classes;
use InvalidArgumentException;
use JsonException;
use RuntimeException;
use Socialbox\Enums\StandardHeaders;
use Throwable;
use Exception;
use InvalidArgumentException;
use JsonException;
use RuntimeException;
use Socialbox\Enums\StandardHeaders;
use Throwable;
class Utilities
{
/**
* Decodes a JSON string into an associative array, throws an exception if the JSON is invalid
*
* @param string $json The JSON string to decode
* @return array The decoded associative array
* @throws InvalidArgumentException If the JSON is invalid
*/
public static function jsonDecode(string $json): array
class Utilities
{
$decoded = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE)
/**
* Decodes a JSON string into an associative array, throws an exception if the JSON is invalid
*
* @param string $json The JSON string to decode
* @return array The decoded associative array
* @throws InvalidArgumentException If the JSON is invalid
*/
public static function jsonDecode(string $json): array
{
throw match (json_last_error())
$decoded = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE)
{
JSON_ERROR_DEPTH => new InvalidArgumentException("JSON decoding failed: Maximum stack depth exceeded"),
JSON_ERROR_STATE_MISMATCH => new InvalidArgumentException("JSON decoding failed: Underflow or the modes mismatch"),
JSON_ERROR_CTRL_CHAR => new InvalidArgumentException("JSON decoding failed: Unexpected control character found"),
JSON_ERROR_SYNTAX => new InvalidArgumentException("JSON decoding failed: Syntax error, malformed JSON"),
JSON_ERROR_UTF8 => new InvalidArgumentException("JSON decoding failed: Malformed UTF-8 characters, possibly incorrectly encoded"),
default => new InvalidArgumentException("JSON decoding failed: Unknown error"),
};
}
return $decoded;
}
public static function jsonEncode(mixed $data): string
{
try
{
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
}
catch(JsonException $e)
{
throw new InvalidArgumentException("Failed to encode json input", $e);
}
}
/**
* Encodes the given data in Base64.
*
* @param string $data The data to be encoded.
* @return string The Base64 encoded string.
* @throws InvalidArgumentException if the encoding fails.
*/
public static function base64encode(string $data): string
{
$encoded = base64_encode($data);
if (!$encoded)
{
throw new InvalidArgumentException('Failed to encode data in Base64');
}
return $encoded;
}
/**
* Decodes a Base64 encoded string.
*
* @param string $data The Base64 encoded data to be decoded.
* @return string The decoded data.
* @throws InvalidArgumentException If decoding fails.
*/
public static function base64decode(string $data): string
{
$decoded = base64_decode($data, true);
if ($decoded === false)
{
throw new InvalidArgumentException('Failed to decode data from Base64');
}
return $decoded;
}
/**
* Returns the request headers as an associative array
*
* @return array
*/
public static function getRequestHeaders(): array
{
// Check if function getallheaders() exists
if (function_exists('getallheaders'))
{
$headers = getallheaders();
}
else
{
// Fallback for servers where getallheaders() is not available
$headers = [];
foreach ($_SERVER as $key => $value)
{
if (str_starts_with($key, 'HTTP_'))
throw match (json_last_error())
{
// Convert header names to the normal HTTP format
$headers[str_replace('_', '-', strtolower(substr($key, 5)))] = $value;
JSON_ERROR_DEPTH => new InvalidArgumentException("JSON decoding failed: Maximum stack depth exceeded"),
JSON_ERROR_STATE_MISMATCH => new InvalidArgumentException("JSON decoding failed: Underflow or the modes mismatch"),
JSON_ERROR_CTRL_CHAR => new InvalidArgumentException("JSON decoding failed: Unexpected control character found"),
JSON_ERROR_SYNTAX => new InvalidArgumentException("JSON decoding failed: Syntax error, malformed JSON"),
JSON_ERROR_UTF8 => new InvalidArgumentException("JSON decoding failed: Malformed UTF-8 characters, possibly incorrectly encoded"),
default => new InvalidArgumentException("JSON decoding failed: Unknown error"),
};
}
return $decoded;
}
public static function jsonEncode(mixed $data): string
{
try
{
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
}
catch(JsonException $e)
{
throw new InvalidArgumentException("Failed to encode json input", $e);
}
}
/**
* Encodes the given data in Base64.
*
* @param string $data The data to be encoded.
* @return string The Base64 encoded string.
* @throws InvalidArgumentException if the encoding fails.
*/
public static function base64encode(string $data): string
{
$encoded = base64_encode($data);
if (!$encoded)
{
throw new InvalidArgumentException('Failed to encode data in Base64');
}
return $encoded;
}
/**
* Decodes a Base64 encoded string.
*
* @param string $data The Base64 encoded data to be decoded.
* @return string The decoded data.
* @throws InvalidArgumentException If decoding fails.
*/
public static function base64decode(string $data): string
{
$decoded = base64_decode($data, true);
if ($decoded === false)
{
throw new InvalidArgumentException('Failed to decode data from Base64');
}
return $decoded;
}
/**
* Returns the request headers as an associative array
*
* @return array
*/
public static function getRequestHeaders(): array
{
// Check if function getallheaders() exists
if (function_exists('getallheaders'))
{
$headers = getallheaders();
}
else
{
// Fallback for servers where getallheaders() is not available
$headers = [];
foreach ($_SERVER as $key => $value)
{
if (str_starts_with($key, 'HTTP_'))
{
// Convert header names to the normal HTTP format
$headers[str_replace('_', '-', strtolower(substr($key, 5)))] = $value;
}
}
}
if($headers === false)
{
throw new RuntimeException('Failed to get request headers');
}
return $headers;
}
/**
* Converts a Throwable object into a formatted string.
*
* @param Throwable $e The throwable to be converted into a string.
* @return string The formatted string representation of the throwable, including the exception class, message, file, line, and stack trace.
*/
public static function throwableToString(Throwable $e): string
{
return sprintf(
"%s: %s in %s:%d\nStack trace:\n%s",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
);
}
/**
* Generates a formatted header string.
*
* @param StandardHeaders $header The standard header object.
* @param string $value The header value to be associated with the standard header.
* @return string The formatted header string.
*/
public static function generateHeader(StandardHeaders $header, string $value): string
{
return $header->value . ': ' . $value;
}
/**
* Generates a random string of specified length using the provided character set.
*
* @param int $int The length of the random string to be generated.
* @param string $string The character set to use for generating the random string.
* @return string The generated random string.
*/
public static function randomString(int $int, string $string): string
{
$characters = str_split($string);
$randomString = '';
for ($i = 0; $i < $int; $i++)
{
$randomString .= $characters[array_rand($characters)];
}
return $randomString;
}
/**
* Generates a random CRC32 hash.
*
* @return string The generated CRC32 hash as a string.
*/
public static function randomCrc32(): string
{
return hash('crc32b', uniqid());
}
/**
* Sanitizes a file name by removing any characters that are not alphanumeric, hyphen, or underscore.
*
* @param string $name The file name to be sanitized.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $name): string
{
return preg_replace('/[^a-zA-Z0-9-_]/', '', $name);
}
/**
* Sanitizes a Base64-encoded JPEG image by validating its data, decoding it,
* and re-encoding it to ensure it conforms to the JPEG format.
*
* @param string $data The Base64-encoded string potentially containing a JPEG image,
* optionally prefixed with "data:image/...;base64,".
* @return string A sanitized and re-encoded JPEG image as a binary string.
* @throws InvalidArgumentException If the input data is not valid Base64,
* does not represent an image, or is not in the JPEG format.
*/
public static function sanitizeJpeg(string $data): string
{
// Temporarily load the decoded data as an image
$tempResource = imagecreatefromstring($data);
// Validate that the decoded data is indeed an image
if ($tempResource === false)
{
throw new InvalidArgumentException("The data does not represent a valid image.");
}
// Validate MIME type using getimagesizefromstring
$imageInfo = getimagesizefromstring($data);
if ($imageInfo === false || $imageInfo['mime'] !== 'image/jpeg')
{
imagedestroy($tempResource); // Cleanup resources
throw new InvalidArgumentException("The image is not a valid JPEG format.");
}
// Capture the re-encoded image in memory and return it as a string
ob_start(); // Start output buffering
$saveResult = imagejpeg($tempResource, null, 100); // Max quality, save to output buffer
imagedestroy($tempResource); // Free up memory resources
if (!$saveResult)
{
ob_end_clean(); // Clean the output buffer if encoding failed
throw new InvalidArgumentException("Failed to encode the sanitized image.");
}
// Return the sanitized jpeg image as the result
return ob_get_clean();
}
/**
* Resizes an image to a specified width and height while maintaining its aspect ratio.
* The resized image is centered on a black background matching the target dimensions.
*
* @param string $data The binary data of the source image.
* @param int $width The desired width of the resized image.
* @param int $height The desired height of the resized image.
* @return string The binary data of the resized image in PNG format.
* @throws InvalidArgumentException If the source image cannot be created from the provided data.
* @throws Exception If image processing fails during resizing.
*/
public static function resizeImage(string $data, int $width, int $height): string
{
try
{
// Create image resource from binary data
$sourceImage = imagecreatefromstring($data);
if (!$sourceImage)
{
throw new InvalidArgumentException("Failed to create image from provided data");
}
// Get original dimensions
$sourceWidth = imagesx($sourceImage);
$sourceHeight = imagesy($sourceImage);
// Calculate aspect ratios
$sourceRatio = $sourceWidth / $sourceHeight;
$targetRatio = $width / $height;
// Initialize dimensions for scaling
$scaleWidth = $width;
$scaleHeight = $height;
// Calculate scaling dimensions to maintain aspect ratio
if ($sourceRatio > $targetRatio)
{
// Source image is wider - scale by width
$scaleHeight = $width / $sourceRatio;
}
else
{
// Source image is taller - scale by height
$scaleWidth = $height * $sourceRatio;
}
// Create target image with desired dimensions
$targetImage = imagecreatetruecolor($width, $height);
if (!$targetImage)
{
throw new Exception("Failed to create target image");
}
// Fill background with black
$black = imagecolorallocate($targetImage, 0, 0, 0);
imagefill($targetImage, 0, 0, $black);
// Calculate padding to center the scaled image
$paddingX = ($width - $scaleWidth) / 2;
$paddingY = ($height - $scaleHeight) / 2;
// Enable alpha blending
imagealphablending($targetImage, true);
imagesavealpha($targetImage, true);
// Resize and copy the image with high-quality resampling
if (!imagecopyresampled($targetImage, $sourceImage, (int)$paddingX, (int)$paddingY, 0, 0, (int)$scaleWidth, (int)$scaleHeight, $sourceWidth, $sourceHeight))
{
throw new Exception("Failed to resize image");
}
// Start output buffering
ob_start();
// Output image as PNG (you can modify this to support other formats)
imagepng($targetImage);
// Return the image data
return ob_get_clean();
}
finally
{
if (isset($sourceImage))
{
imagedestroy($sourceImage);
}
if (isset($targetImage))
{
imagedestroy($targetImage);
}
}
}
if($headers === false)
/**
* Converts an array into a serialized string by joining the elements with a comma.
*
* @param array $list An array of elements that need to be converted to a comma-separated string.
* @return string A string representation of the array elements, joined by commas.
*/
public static function serializeList(array $list): string
{
throw new RuntimeException('Failed to get request headers');
return implode(',', $list);
}
return $headers;
}
/**
* Converts a Throwable object into a formatted string.
*
* @param Throwable $e The throwable to be converted into a string.
* @return string The formatted string representation of the throwable, including the exception class, message, file, line, and stack trace.
*/
public static function throwableToString(Throwable $e): string
{
return sprintf(
"%s: %s in %s:%d\nStack trace:\n%s",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
);
}
/**
* Generates a formatted header string.
*
* @param StandardHeaders $header The standard header object.
* @param string $value The header value to be associated with the standard header.
* @return string The formatted header string.
*/
public static function generateHeader(StandardHeaders $header, string $value): string
{
return $header->value . ': ' . $value;
}
/**
* Generates a random string of specified length using the provided character set.
*
* @param int $int The length of the random string to be generated.
* @param string $string The character set to use for generating the random string.
* @return string The generated random string.
*/
public static function randomString(int $int, string $string): string
{
$characters = str_split($string);
$randomString = '';
for ($i = 0; $i < $int; $i++)
/**
* Converts a serialized string into an array by splitting the string at each comma.
*
* @param string $list A comma-separated string that needs to be converted to an array.
* @return array An array of string values obtained by splitting the input string.
*/
public static function unserializeList(string $list): array
{
$randomString .= $characters[array_rand($characters)];
return explode(',', $list);
}
return $randomString;
}
/**
* Generates a random CRC32 hash.
*
* @return string The generated CRC32 hash as a string.
*/
public static function randomCrc32(): string
{
return hash('crc32b', uniqid());
}
/**
* Sanitizes a file name by removing any characters that are not alphanumeric, hyphen, or underscore.
*
* @param string $name The file name to be sanitized.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $name): string
{
return preg_replace('/[^a-zA-Z0-9-_]/', '', $name);
}
/**
* Sanitizes a Base64-encoded JPEG image by validating its data, decoding it,
* and re-encoding it to ensure it conforms to the JPEG format.
*
* @param string $data The Base64-encoded string potentially containing a JPEG image,
* optionally prefixed with "data:image/...;base64,".
* @return string A sanitized and re-encoded JPEG image as a binary string.
* @throws InvalidArgumentException If the input data is not valid Base64,
* does not represent an image, or is not in the JPEG format.
*/
public static function sanitizeBase64Jpeg(string $data): string
{
// Detect and strip the potential "data:image/...;base64," prefix, if present
if (str_contains($data, ','))
/**
* Checks if the given HTTP response code indicates success or failure.
*
* @param int $responseCode The HTTP response code to check.
* @return bool True if the response code indicates success, false otherwise.
*/
public static function isSuccessCodes(int $responseCode): bool
{
[, $data] = explode(',', $data, 2);
return $responseCode >= 200 && $responseCode < 300;
}
// Decode the Base64 string
$decodedData = base64_decode($data, true);
// Check if decoding succeeded
if ($decodedData === false)
{
throw new InvalidArgumentException("Invalid Base64 data.");
}
// Temporarily load the decoded data as an image
$tempResource = imagecreatefromstring($decodedData);
// Validate that the decoded data is indeed an image
if ($tempResource === false)
{
throw new InvalidArgumentException("The Base64 data does not represent a valid image.");
}
// Validate MIME type using getimagesizefromstring
$imageInfo = getimagesizefromstring($decodedData);
if ($imageInfo === false || $imageInfo['mime'] !== 'image/jpeg')
{
imagedestroy($tempResource); // Cleanup resources
throw new InvalidArgumentException("The image is not a valid JPEG format.");
}
// Capture the re-encoded image in memory and return it as a string
ob_start(); // Start output buffering
$saveResult = imagejpeg($tempResource, null, 100); // Max quality, save to output buffer
imagedestroy($tempResource); // Free up memory resources
if (!$saveResult)
{
ob_end_clean(); // Clean the output buffer if encoding failed
throw new InvalidArgumentException("Failed to encode the sanitized image.");
}
// Return the sanitized jpeg image as the result
return ob_get_clean();
}
/**
* Converts an array into a serialized string by joining the elements with a comma.
*
* @param array $list An array of elements that need to be converted to a comma-separated string.
* @return string A string representation of the array elements, joined by commas.
*/
public static function serializeList(array $list): string
{
return implode(',', $list);
}
/**
* Converts a serialized string into an array by splitting the string at each comma.
*
* @param string $list A comma-separated string that needs to be converted to an array.
* @return array An array of string values obtained by splitting the input string.
*/
public static function unserializeList(string $list): array
{
return explode(',', $list);
}
/**
* Checks if the given HTTP response code indicates success or failure.
*
* @param int $responseCode The HTTP response code to check.
* @return bool True if the response code indicates success, false otherwise.
*/
public static function isSuccessCodes(int $responseCode): bool
{
return $responseCode >= 200 && $responseCode < 300;
}
}
}