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:
6
vendor/rosell-dk/webp-convert-cloud-service/.gitignore
vendored
Executable file
6
vendor/rosell-dk/webp-convert-cloud-service/.gitignore
vendored
Executable file
@@ -0,0 +1,6 @@
|
||||
.php_cs.cache
|
||||
composer.lock
|
||||
composer.phar
|
||||
config.yaml
|
||||
/vendor
|
||||
/conversions
|
||||
19
vendor/rosell-dk/webp-convert-cloud-service/.php_cs.dist
vendored
Normal file
19
vendor/rosell-dk/webp-convert-cloud-service/.php_cs.dist
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->exclude('tests')
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
$config = PhpCsFixer\Config::create();
|
||||
$config
|
||||
->setRules([
|
||||
'@PSR2' => true,
|
||||
'array_syntax' => [
|
||||
'syntax' => 'short',
|
||||
],
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
|
||||
return $config;
|
||||
9
vendor/rosell-dk/webp-convert-cloud-service/LICENSE
vendored
Normal file
9
vendor/rosell-dk/webp-convert-cloud-service/LICENSE
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Bjørn Rosell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
88
vendor/rosell-dk/webp-convert-cloud-service/README.md
vendored
Normal file
88
vendor/rosell-dk/webp-convert-cloud-service/README.md
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
# WebPConvert Cloud Service
|
||||
|
||||
This library allows you to set up your own WebP conversion cloud service. This way you can have a cloud converter for free. You won't have to worry about licenses expiring or being stolen and abused. And you will be completely in control of it (and downtime)
|
||||
|
||||
After setting up the cloud service, you will be able to use it to convert jpeg and png images into webp images. You can do that, using [webp-convert](https://github.com/rosell-dk/webp-convert/), or one of its implementations, such as the Wordpress plugin, [WebP Express](https://github.com/rosell-dk/webp-express/).
|
||||
|
||||
Alternatively to installing this library, you could install Wordpress and the the *WebP Express* plugin mentioned above. *WebP Express* can be configured to act as a conversion service. The plugin actually uses this library to achieve that functionality.
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Require the library with composer
|
||||
```text
|
||||
composer require rosell-dk/webp-convert-cloud-service
|
||||
```
|
||||
|
||||
### 2. Create a script, which calls the library with configuration options
|
||||
|
||||
Here is an example to get started with:
|
||||
|
||||
```php
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use \WebPConvertCloudService\WebPConvertCloudService;
|
||||
|
||||
$options = [
|
||||
// Set dir for storing converted images temporarily
|
||||
// (make sure to create that dir, with permissions for web server to write)
|
||||
'destination-dir' => '../conversions',
|
||||
|
||||
// Set acccess restrictions
|
||||
'access' => [
|
||||
'whitelist' => [
|
||||
[
|
||||
'ip' => '*',
|
||||
'api-key' => 'my dog is white',
|
||||
'require-api-key-to-be-crypted-in-transfer' => false
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
// Optionally set webp-convert options
|
||||
'webp-convert' => [
|
||||
'converters' => ['cwebp', 'gd', 'imagick'],
|
||||
'converter-options' => [
|
||||
'cwebp' => [
|
||||
'try-common-system-paths' => true,
|
||||
'try-supplied-binary-for-os' => true,
|
||||
'use-nice' => true
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$wpc = new WebPConvertCloudService();
|
||||
$wpc->handleRequest($options);
|
||||
?>
|
||||
|
||||
```
|
||||
|
||||
### 3. Test if it works
|
||||
|
||||
You can call the API with curl.
|
||||
First thing you might do is to test if service is available.
|
||||
You can do that by asking for the api-version, as this does not require any authorization:
|
||||
|
||||
```
|
||||
curl --form action="api-version" http://wpc.example.com/wpc.php
|
||||
```
|
||||
|
||||
Next, you can test access.
|
||||
If you have set *require-api-key-to-be-crypted-in-transfer* to `false`, you can test access like this:
|
||||
|
||||
```
|
||||
curl --form action="check-access" --form api-key="my dog is white" http://wpc.example.com/wpc.php
|
||||
```
|
||||
|
||||
Finally, you can make a test conversion like this.
|
||||
First, place a file *test.jpg* in your current dir, then run:
|
||||
```
|
||||
curl --form action="convert" --form api-key="my dog is white" --form file=@test.jpg http://wpc.example.com/wpc.php > test.webp
|
||||
```
|
||||
If you get a corrupt file, then it is probably because the output contains an error message. To see it, run the above command again, but remove the piping of the output to a file.
|
||||
|
||||
You will probably not need to know more of the API. But in case you do, check out [docs/api.md](https://github.com/rosell-dk/webp-convert-cloud-service/blob/master/docs/api.md)
|
||||
|
||||
## Mad Scientist-ware
|
||||
If you enjoy this software, feel free to conduct some secret experiments and go mad. If you like.
|
||||
58
vendor/rosell-dk/webp-convert-cloud-service/composer.json
vendored
Normal file
58
vendor/rosell-dk/webp-convert-cloud-service/composer.json
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "rosell-dk/webp-convert-cloud-service",
|
||||
"description": "Cloud service for converting JPEG & PNG to WebP",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"keywords": ["webp", "images", "cwebp", "imagick", "gd", "jpg2webp", "png2webp", "jpg", "png", "image conversion"],
|
||||
"scripts": {
|
||||
"ci": [
|
||||
"@test",
|
||||
"@phpcs-all",
|
||||
"@composer validate --no-check-all --strict"
|
||||
],
|
||||
"cs-fix-all": [
|
||||
"php-cs-fixer fix src"
|
||||
],
|
||||
"cs-fix": "php-cs-fixer fix",
|
||||
"cs-dry": "php-cs-fixer fix --dry-run --diff",
|
||||
"test": "phpunit tests/",
|
||||
"phpcs": "phpcs --standard=PSR2",
|
||||
"phpcs-all": "phpcs --standard=PSR2 src",
|
||||
"phpcbf": "phpcbf --standard=PSR2"
|
||||
},
|
||||
"extra": {
|
||||
"scripts-descriptions": {
|
||||
"ci": "Run tests before CI",
|
||||
"phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
|
||||
"phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
|
||||
"cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
|
||||
"cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
|
||||
"test": "Launches the preconfigured PHPUnit"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "WebPConvertCloudService\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "WebPConvertCloudService\\Tests\\": "tests/" }
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bjørn Rosell",
|
||||
"homepage": "https://www.bitwise-it.dk/contact",
|
||||
"role": "Project Author"
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.11",
|
||||
"phpunit/phpunit": "5.7.27",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"require": {
|
||||
"rosell-dk/webp-convert": "^2.0.0"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
|
||||
}
|
||||
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