WebP Express CloudHost.es Fix v0.25.9-cloudhost
✅ Fixed bulk conversion getting stuck on missing files ✅ Added robust error handling and timeout protection ✅ Improved JavaScript response parsing ✅ Added file existence validation ✅ Fixed missing PHP class imports ✅ Added comprehensive try-catch error recovery 🔧 Key fixes: - File existence checks before conversion attempts - 30-second timeout protection per file - Graceful handling of 500 errors and JSON parsing issues - Automatic continuation to next file on failures - Cache busting for JavaScript updates 🎯 Result: Bulk conversion now completes successfully even with missing files 🚀 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
99
vendor/rosell-dk/webp-convert-cloud-service/src/AccessCheck.php
vendored
Normal file
99
vendor/rosell-dk/webp-convert-cloud-service/src/AccessCheck.php
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace WebPConvertCloudService;
|
||||
|
||||
use \WebPConvertCloudService\WebPConvertCloudService;
|
||||
|
||||
class AccessCheck
|
||||
{
|
||||
|
||||
private static function accessDenied($msg)
|
||||
{
|
||||
WebPConvertCloudService::exitWithError(WebPConvertCloudService::ERROR_ACCESS_DENIED, $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an IP (ie "212.67.80.1") against a pattern (ie "212.*")
|
||||
*/
|
||||
private static function testIpPattern($ip, $pattern)
|
||||
{
|
||||
$regEx = '/^' . str_replace('*', '.*', $pattern) . '$/';
|
||||
|
||||
if (preg_match($regEx, $ip)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function runAccessChecks($options)
|
||||
{
|
||||
$accessOptions = $options['access'];
|
||||
|
||||
$onWhitelist = false;
|
||||
if (isset($accessOptions['whitelist']) && count($accessOptions['whitelist']) > 0) {
|
||||
foreach ($accessOptions['whitelist'] as $whitelistItem) {
|
||||
if (isset($whitelistItem['ip'])) {
|
||||
if (!self::testIpPattern($_SERVER['REMOTE_ADDR'], $whitelistItem['ip'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$onWhitelist = true;
|
||||
|
||||
if (!isset($whitelistItem['api-key']) || $whitelistItem['api-key'] == '') {
|
||||
// This item requires no api key
|
||||
// Access granted!
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['salt']) && isset($_POST['api-key-crypted'])) {
|
||||
if (CRYPT_BLOWFISH == 1) {
|
||||
// Strip off the first 28 characters (the first 6 are always "$2y$10$". The next 22 is the salt)
|
||||
$cryptedKey = substr(crypt($whitelistItem['api-key'], '$2y$10$' . $_POST['salt'] . '$'), 28);
|
||||
if ($_POST['api-key-crypted'] == $cryptedKey) {
|
||||
// Access granted!
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// trouble...
|
||||
}
|
||||
} else {
|
||||
$hashingRequired = (
|
||||
isset($whitelistItem['require-api-key-to-be-crypted-in-transfer']) &&
|
||||
$whitelistItem['require-api-key-to-be-crypted-in-transfer']
|
||||
);
|
||||
if (!$hashingRequired && isset($_POST['api-key'])) {
|
||||
if ($_POST['api-key'] == $whitelistItem['api-key']) {
|
||||
// Access granted!
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($onWhitelist) {
|
||||
if (isset($_POST['salt']) && isset($_POST['api-key-crypted'])) {
|
||||
self::accessDenied('Invalid api key');
|
||||
} else {
|
||||
if (isset($_POST['api-key'])) {
|
||||
self::accessDenied('Either api key is invalid, or you must crypt the api key');
|
||||
} else {
|
||||
if (isset($_POST['salt']) && isset($_POST['api-key-crypted'])) {
|
||||
self::accessDenied('You need to supply a valid api key');
|
||||
} else {
|
||||
if (!isset($_POST['api-key-crypted'])) {
|
||||
self::accessDenied('You need to supply an api key');
|
||||
} else {
|
||||
if (!isset($_POST['salt'])) {
|
||||
self::accessDenied('You must supply salt to go with you encripted api key');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self::accessDenied('Access denied');
|
||||
}
|
||||
}
|
||||
}
|
||||
139
vendor/rosell-dk/webp-convert-cloud-service/src/Serve.php
vendored
Normal file
139
vendor/rosell-dk/webp-convert-cloud-service/src/Serve.php
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace WebPConvertCloudService;
|
||||
|
||||
use \WebPConvertCloudService\WebPConvertCloudService;
|
||||
use \WebPConvert\WebPConvert;
|
||||
|
||||
class Serve
|
||||
{
|
||||
|
||||
private static function configurationError($msg)
|
||||
{
|
||||
WebPConvertCloudService::exitWithError(WebPConvertCloudService::ERROR_CONFIGURATION, $msg);
|
||||
}
|
||||
|
||||
private static function accessDenied($msg)
|
||||
{
|
||||
WebPConvertCloudService::exitWithError(WebPConvertCloudService::ERROR_ACCESS_DENIED, $msg);
|
||||
}
|
||||
|
||||
private static function runtimeError($msg)
|
||||
{
|
||||
WebPConvertCloudService::exitWithError(WebPConvertCloudService::ERROR_RUNTIME, $msg);
|
||||
}
|
||||
|
||||
public static function serve($options)
|
||||
{
|
||||
$uploaddir = $options['destination-dir'] ;
|
||||
|
||||
if (!is_dir($uploaddir)) {
|
||||
if (!@mkdir($uploaddir, 0775, true)) {
|
||||
self::configurationError('Could not create folder for converted files: ' . $uploaddir);
|
||||
}
|
||||
@chmod($uploaddir, 0775);
|
||||
}
|
||||
|
||||
/*
|
||||
if (!isset($_POST['hash'])) {
|
||||
self::accessDenied('Restricted access. Hash required, but missing');
|
||||
}*/
|
||||
|
||||
if (!isset($_FILES['file'])) {
|
||||
self::runtimeError('No file was supplied');
|
||||
}
|
||||
if (!isset($_FILES['file']['error'])) {
|
||||
self::runtimeError('Invalid parameters');
|
||||
}
|
||||
|
||||
if (is_array($_FILES['file']['error'])) {
|
||||
self::runtimeError('Cannot convert multiple files');
|
||||
}
|
||||
|
||||
switch ($_FILES['file']['error']) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
self::runtimeError('No file sent');
|
||||
break;
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
self::runtimeError('Exceeded filesize limit.');
|
||||
break;
|
||||
default:
|
||||
self::runtimeError('Unknown error.');
|
||||
}
|
||||
|
||||
if ($_FILES['file']['size'] == 0) {
|
||||
self::accessDenied('File size is zero. Perhaps exceeded filesize limit?');
|
||||
}
|
||||
// Undefined | Multiple Files | $_FILES Corruption Attack
|
||||
// If this request falls under any of them, treat it invalid.
|
||||
/*if ($_FILES['file']['size'] > 1000000) {
|
||||
throw new RuntimeException('Exceeded filesize limit.');
|
||||
}*/
|
||||
|
||||
// DO NOT TRUST $_FILES['upfile']['mime'] VALUE !!
|
||||
// Check MIME Type by yourself.
|
||||
if (function_exists('finfo_file') && (defined('FILEINFO_MIME_TYPE'))) {
|
||||
$r = finfo_open(FILEINFO_MIME_TYPE);
|
||||
if (false === $ext = array_search(
|
||||
finfo_file($r, $_FILES['file']['tmp_name']),
|
||||
array(
|
||||
'jpg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
),
|
||||
true
|
||||
)) {
|
||||
self::accessDenied('Invalid file format.');
|
||||
}
|
||||
} else {
|
||||
$ext = 'jpg'; // We set it to something, in case above fails.
|
||||
}
|
||||
|
||||
$uploadfile = $uploaddir . '/' . sha1_file($_FILES['file']['tmp_name']) . '.' . $ext;
|
||||
//echo $uploadfile;
|
||||
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
|
||||
// File is valid, and was successfully uploaded
|
||||
|
||||
$source = $uploadfile;
|
||||
|
||||
/*
|
||||
if (!empty($password)) {
|
||||
$hash = md5(md5_file($source) . $password);
|
||||
|
||||
if ($hash != $_POST['hash']) {
|
||||
self::accessDenied('Wrong password.');
|
||||
}
|
||||
}
|
||||
*/
|
||||
$destination = $uploadfile . '.webp';
|
||||
|
||||
if (isset($_POST['options'])) {
|
||||
// Merge in options in $_POST, overwriting the webp-convert options in config
|
||||
$convertOptionsInPost = (array) json_decode($_POST['options'], true);
|
||||
$convertOptions = array_merge($options['webp-convert'], $convertOptionsInPost);
|
||||
} else {
|
||||
$convertOptions = $options['webp-convert'];
|
||||
}
|
||||
|
||||
try {
|
||||
WebPConvert::convert($source, $destination, $convertOptions);
|
||||
header('Content-type: application/octet-stream');
|
||||
echo file_get_contents($destination);
|
||||
|
||||
unlink($source);
|
||||
unlink($destination);
|
||||
} catch (\Exception $e) {
|
||||
echo 'Conversion failed!';
|
||||
echo $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
// Possible file upload attack!
|
||||
self::configurationError('Failed to move uploaded file');
|
||||
|
||||
//echo 'Failed to move uploaded file';
|
||||
}
|
||||
}
|
||||
}
|
||||
113
vendor/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php
vendored
Normal file
113
vendor/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @link https://github.com/rosell-dk/webp-convert-cloud-service
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace WebPConvertCloudService;
|
||||
|
||||
use WebPConvertCloudService\Serve;
|
||||
use WebPConvertCloudService\AccessCheck;
|
||||
|
||||
class WebPConvertCloudService
|
||||
{
|
||||
public $options;
|
||||
|
||||
const ERROR_CONFIGURATION = 0;
|
||||
const ERROR_ACCESS_DENIED = 1;
|
||||
const ERROR_RUNTIME = 2;
|
||||
|
||||
/*
|
||||
example yaml:
|
||||
|
||||
destination-dir: '../conversions'
|
||||
access:
|
||||
allowed-hosts:
|
||||
- bitwise-it.dk
|
||||
allowed-ips:
|
||||
- 127.0.0.1
|
||||
secret: 'my dog is white'
|
||||
|
||||
whitelist:
|
||||
-
|
||||
label: 'rosell.dk'
|
||||
ip: 212.14.2.1
|
||||
secret: 'aoeuth8aoeuh'
|
||||
-
|
||||
label: 'public'
|
||||
secret: '9tita8hoetua'
|
||||
|
||||
webp-convert:
|
||||
quality: 80
|
||||
...
|
||||
*/
|
||||
/*
|
||||
public function loadConfig()
|
||||
{
|
||||
$configDir = __DIR__;
|
||||
|
||||
$parentFolders = explode('/', $configDir);
|
||||
$poppedFolders = [];
|
||||
|
||||
while (!(file_exists(implode('/', $parentFolders) . '/wpc-config.yaml')) && count($parentFolders) > 0) {
|
||||
array_unshift($poppedFolders, array_pop($parentFolders));
|
||||
}
|
||||
if (count($parentFolders) == 0) {
|
||||
self::exitWithError(
|
||||
WebPConvertCloudService::ERROR_SERVER_SETUP,
|
||||
'wpc-config.yaml not found in any parent folders.'
|
||||
);
|
||||
}
|
||||
$configFilePath = implode('/', $parentFolders) . '/wpc-config.yaml';
|
||||
|
||||
try {
|
||||
$options = \Spyc::YAMLLoad($configFilePath);
|
||||
} catch (\Exception $e) {
|
||||
self::exitWithError(WebPConvertCloudService::ERROR_SERVER_SETUP, 'Error parsing wpc-config.yaml.');
|
||||
}
|
||||
}*/
|
||||
|
||||
public static function exitWithError($errorCode, $msg)
|
||||
{
|
||||
$returnObject = [
|
||||
'success' => 0,
|
||||
'errorCode' => $errorCode,
|
||||
'errorMessage' => $msg,
|
||||
];
|
||||
echo json_encode($returnObject);
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function handleRequest($options)
|
||||
{
|
||||
//$this->options = static::loadConfig();
|
||||
if (!isset($options)) {
|
||||
self::exitWithError(self::ERROR_SERVER_SETUP, 'No options was supplied');
|
||||
}
|
||||
|
||||
$action = (isset($_POST['action']) ? $_POST['action'] : 'convert');
|
||||
|
||||
// Handle actions that does not require access check
|
||||
|
||||
switch ($action) {
|
||||
case 'api-version':
|
||||
echo '2';
|
||||
exit;
|
||||
}
|
||||
|
||||
AccessCheck::runAccessChecks($options);
|
||||
|
||||
// Handle actions that requires access check
|
||||
|
||||
switch ($action) {
|
||||
case 'check-access':
|
||||
echo "You have access!\n";
|
||||
break;
|
||||
case 'convert':
|
||||
Serve::serve($options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user