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:
2025-09-23 10:22:32 +02:00
commit 37cf714058
553 changed files with 55249 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
# Grant All Crash Testing
This library used to have a class for crash-testing specific .htaccess rules commonly used in an attempt to grant access to specific files. Such directives are however "dangerous" to use because it is not uncommon that the server has been configured not to allow authorization directives like "Order" and "Require" and even set up to go fatal.
I removed the class, as I found it a bit too specialized.
Here is the `.htaccess` it was testing:
```
# This .htaccess is here in order to test if it results in a 500 Internal Server Error.
# .htaccess files can result in 500 Internal Server Error when they contain directives that has
# not been allowed for the directory it is in (that stuff is controlled with "AllowOverride" or
# "AllowOverrideList" in httpd.conf)
#
# The use case of a .htaccess file like the one tested here would be an attempt to override
# meassurements taken to prevent access. As an example, in Wordpress, there are security plugins
# which puts "Require all denied" into .htaccess files in certain directories in order to strengthen
# security. Such security meassurements could even be applied to the plugins directory, as plugins
# normally should not need PHPs to be requested directly. But of course, there are cases where plugin
# authors need to anyway and thus find themselves counterfighting the security plugin with an .htaccess
# like this. But in doing so, they run the risk of the 500 Internal Server Error. There are standard
# setups out there which not only does not allow "Require" directives, but are configured to go fatal
# about it.
#
# The following directives is used in this .htaccess file:
# - Require (Override: AuthConfig)
# - Order (Override: Limit)
# - FilesMatch (Override: All)
# - IfModule (Override: All)
# FilesMatch should usually be used in this use case, as you would not want to be granting more access
# than you need
<FilesMatch "ping\.txt$">
<IfModule !mod_authz_core.c>
Order deny,allow
Allow from all
</IfModule>
<IfModule mod_authz_core.c>
Require all granted
</IfModule>
</FilesMatch>
```

View File

@@ -0,0 +1,238 @@
Which name is best?
```php
if ($hct->rewriteWorks()) {
}
if ($hct->addTypeWorks()) {
}
if ($hct->serverSignatureWorks()) {
}
if ($hct->contentDigestWorks()) {
}
$hct->rewriteWorks();
$hct->canRewrite();
$hct->rewrite()
$hct->isRewriteWorking();
$hct->canUseRewrite();
$hct->hasRewrite()
$hct->mayRewrite()
$hct->doesRewriteWork();
$hct->rewriting();
$hct->rewritingWorks();
$hct->RewriteRule();
$hct->rewriteSupported();
$hct->rewriteWorks();
$hct->testRewriting();
$hct->test('RewriteRule');
$hct->runTest(new RewriteTester());
$hct->runTest()->RewriteRule();
$hct->canDoRewrite();
$hct->haveRewrite();
$hct->rewriteAvail();
$hct->isRewriteAvailable();
$hct->isRewriteAccessible();
$hct->isRewriteOperative();
$hct->isRewriteOperational();
$hct->isRewriteFunctional();
$hct->isRewritePossible();
$hct->isRewriteOk();
$hct->isRewriteFlying();
$hct->rewritePasses();
if ($hct->canRewrite()) {
}
if ($hct->rewriteWorks()) {
}
if ($hct->rewriteOk()) {
}
if ($hct->rewriteQM()) {
}
if ($hct->rewriteNA()) {
}
// --------------
$hct->canAddType();
$hct->addTypeWorks();
$hct->addTypeQM();
$hct->canUseAddType();
$hct->doesAddTypeWork();
$hct->addType();
$hct->AddType();
$hct->addTypeSupported();
$hct->addTypeWorks();
$hct->addTypeLive();
$hct->addTypeYes();
$hct->addTypeFF(); // fully functional
$hct->testAddType();
$hct->test('AddType');
$hct->run(new AddTypeTester());
$hct->runTest('AddType');
$hct->runTest()->AddType();
$hct->runTest(\HtaccessCapabilityTester\AddType);
$hct->canIUse('AddType');
// ------------------
if ($hct->canContentDigest()) {
}
if ($hct->contentDigestWorks()) {
}
if ($hct->contentDigestOk()) {
}
if ($hct->contentDigestFF()) {
}
$hct->canContentDigest();
$hct->contentDigestWorks();
$hct->canUseContentDigest();
$hct->doesContentDigestWork();
$hct->contentDigest();
$hct->ContentDigest();
// ---------------------
if ($hct->serverSignatureWorks()) {
}
if ($hct->canSetServerSignature()) {
}
if ($hct->testServerSignature()) {
}
if ($hct->doesServerSignatureWork()) {
}
if ($hct->isServerSignatureAllowed()) {
}
if ($hct->isServerSignatureWorking()) {
}
// --------------------
$hct->modRewriteLoaded();
$hct->moduleLoaded('rewrite');
$hct->testModuleLoaded('rewrite');
$hct->modLoaded('rewrite');
// --------------------
$hct->doesThisCrash();
$hct->kaput();
$hct->ooo();
$hct->na();
```
# IDEA:
```yaml
subdir: rewrite
files:
- filename: '.htaccess'
content: |
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^0\.txt$ 1\.txt [L]
</IfModule>
- filename: '0.txt'
content: '0'
- filename: '1.txt'
content: '1'
request:
url: '0.txt'
interpretation:
- [success, body, equals, '1']
- [failure, body, equals, '0']
- [interprete500, status-code, equals, '500'] # inconclusive if innocent also crashes, otherwise failure
- [inconclusive, status-code, equals, '403']
- if: [status-code, equals, '500']
then:
- if: [doesInnocentCrash()]
then: inconclusive
else: failure
- [inconclusive]
```
or:
```yaml
interpretation:
- [success, body, equals, '1']
- [failure, body, equals, '0']
- [handle-errors] # Standard error handling (403, 404, 500)
```
```php
[
'interpretation' => [
[
'if' => ['body', 'equals', '1'],
'then' => ['success']
],
[
'if' => ['body', 'equals', '0'],
'then' => ['failure', 'no-effect']
],
[
'if' => ['status-code', 'equals', '500'],
'then' => 'handle500()'
],
[
'if' => ['status-code', 'equals', '500'],
'then' => 'handle500()'
]
]
```
```yaml
```
crashTestInnocent
handle500:
returns "failure" if innocent request succeeds
returns "inconclusive" if innocent request fails
handle403:
if innocent request also 403, all requests probably does
returns "failure" if innocent request succeeds

View File

@@ -0,0 +1,54 @@
## More examples of what you can test:
```php
require 'vendor/autoload.php';
use HtaccessCapabilityTester\HtaccessCapabilityTester;
$hct = new HtaccessCapabilityTester($baseDir, $baseUrl);
$rulesToCrashTest = <<<'EOD'
<ifModule mod_rewrite.c>
RewriteEngine On
</ifModule>
EOD;
if ($hct->crashTest($rulesToCrashTest)) {
// The rules at least did not cause requests to anything in the folder to "crash".
// (even simple rules like the above can make the server respond with a
// 500 Internal Server Error - see "docs/TheManyWaysOfHtaccessFailure.md")
}
if ($hct->addTypeWorks()) {
// AddType directive works
}
if ($hct->headerSetWorks()) {
// "Header set" works
}
if ($hct->requestHeaderWorks()) {
// "RequestHeader set" works
}
// Note that the tests returns null if they are inconclusive
$testResult = $hct->htaccessEnabled();
if (is_null($testResult)) {
// Inconclusive!
// Perhaps a 403 Forbidden?
// You can get a bit textual insight by using:
// $hct->infoFromLastTest
}
// Also note that an exception will be thrown if test files cannot be created.
// You might want to wrap your call in a try-catch statement.
try {
if ($hct->requestHeaderWorks()) {
// "RequestHeader set" works
}
} catch (\Exception $e) {
// Probably permission problems.
// We should probably notify someone
}
```

View File

@@ -0,0 +1,85 @@
# Running your own custom tests using the *customTest* method
A typical test s mentioned, a test has three phases:
1. Writing the test files to the directory in question
2. Doing a request (in advanced cases, more)
3. Interpreting the request
So, in order for *customTest()*, it needs to know. 1) What files are needed? 2) Which file should be requested? 3) How should the response be interpreted?
Here is a definition which can be used for implementing the *headerSetWorks()* functionality yourself. It's in YAML because it is more readable like this.
<details><summary><u>Click here to see the PHP example</u></summary>
<p><br>
<b>PHP example</b>
```php
<?php
require 'vendor/autoload.php';
use HtaccessCapabilityTester\HtaccessCapabilityTester;
$hct = new HtaccessCapabilityTester($baseDir, $baseUrl);
$htaccessFile = <<<'EOD'
<IfModule mod_headers.c>
Header set X-Response-Header-Test: test
</IfModule>
EOD;
$test = [
'subdir' => 'header-set',
'files' => [
['.htaccess', $htaccessFile],
['request-me.txt', "hi"],
],
'request' => 'request-me.txt',
'interpretation' => [
['success', 'headers', 'contains-key-value', 'X-Response-Header-Test', 'test'],
// the next three mappings are actually not necessary, as customTest() does standard
// error handling automatically (can be turned off)
['failure', 'status-code', 'equals', '500'],
['inconclusive', 'status-code', 'equals', '403'],
['inconclusive', 'status-code', 'equals', '404'],
]
];
if ($hct->customTest($test)) {
// setting a header in the .htaccess works!
}
```
</p>
</details>
```yaml
subdir: header-set
files:
- filename: '.htaccess'
content: |
<IfModule mod_headers.c>
Header set X-Response-Header-Test: test
</IfModule>
- filename: 'request-me.txt'
content: 'hi'
request:
url: 'request-me.txt'
interpretation:
- [success, headers, contains-key-value, 'X-Response-Header-Test', 'test']
- [failure, status-code, equals, '500'] # actually not needed (part of standard error handling)
- [inconclusive, status-code, equals, '403'] # actually not needed (part of standard error handling)
- [inconclusive, status-code, equals, '404'] # actually not needed (part of standard error handling)
- [failure]
```
In fact, this is more or less how this library implements it.
The test definition has the following sub-definitions:
- *subdir*: Defines which subdir the test files should reside in
- *files*: Defines the files for the test (filename and content)
- *request*: Defines which file that should be requested
- *interpretation*: Defines how to interprete the response. It consists of a list of mappings is read from the top until one of the conditions is met. The first line for example translates to "Map to success if the body of the response equals '1'". If none of the conditions are met, the result is automatically mapped to 'inconclusive'.
For more info, look in the API (below). For real examples, check out the classes in the "Testers" dir - most of them are defined in this "language"

View File

@@ -0,0 +1,30 @@
# The many ways of .htaccess failure
If you have written any `.htaccess` files, you are probably comfortable with the "IfModule" tag and the concept that some directives are not available, unless a certain module has been loaded. So, to make your .htaccess failproof, you wrapped those directives in an IfModule tag. Failproof? Wrong!
Meet the [AllowOverride](https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride) and [AllowOverrideList](https://httpd.apache.org/docs/2.4/mod/core.html#allowoverridelist) directives. These fellows effectively controls which directives that are allowed in .htaccess files. It is not a global setting, but something that can be configured per directory. If you are on a specialized host for some CMS, it could very well be that the allowed directives is limited and set to different things in the directories, ie the plugin and media directories.
The settings of AllowOverride and AllowOverrideList can produce three kinds of failures:
1. The .htaccess is skipped altogether. This happens when nothing is allowed (when both AllowOverride and AllowOverrideList are set to None)
2. The forbidden tags are ignored. This happens if the "Nonfatal" setting for AllowOverride is set to "All" or "Override"
3. All requests to the folder/subfolder containing an .htaccess file with forbidden directive results in a 500 Internal Server Error. This happens if the "Nonfatal" option isn't set (or is set to "Unknown"). The IfModule directive does not prevent this from happening.
So, no, using IfModule tags does not make the .htaccess failproof.
Fortunately, the core directives can only be made forbidden in what I take to be a very rare setting: By setting AllowOverride to None and AllowOverrideList to a list, which doesn't include the core directives. So at least, it will be rare to that the IfModule directive itself is forbidden and thereby can cause 500 Internal Server Error.
Besides these cases, there is of course also the authorization directives.
The sysadmin might have placed something like this in the virtual host configuration:
```
<Directory /var/www/your-site/media/ >
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
</Directory>
```
This isn't really a .htaccess failure, but it is an obstacle too. Especially with regards to this library. As we have seen, the capabilities of a .htaccess in one folder is not neccessarily the same in another folder, so we often want to place the .htaccess test files in a subdir to the directory that the real .htaccess files are going to reside. However, if phps aren't allowed to be run there, we can't. Unless of course, the test can be made not to rely on a receiving test.php script. A great amount of effort has been done to avoid resorting to PHP when possible.

View File

@@ -0,0 +1,5 @@
### Running your own test
It is not to define your own test by extending the "AbstractTester" class. You can use the code in one of the provided testers as a template (ie `RequestHeaderTester.php`).
### Using another library for making the HTTP request
This library simply uses `file_get_contents` to make HTTP requests. It can however be set to use another library. Use the `setHttpRequestor` method for that. The requester must implement `HttpRequesterInterface` interface, which simply consists of a single method: `makeHttpRequest($url)`

View File

@@ -0,0 +1,31 @@
"test.php" will either result in "0", "1" or an error.
The tester class then makes a HTTP to `test.php` and examines the response in order to answer the question: *Is "RequestHeader" available and does it work?* It should be clear by inspecting the code above that if `mod_headers` is loaded and `RequestHeader` is allowed in the `.htaccess`, the response of `test.php` will be "1". And if `mod_headers` isn't loaded, the response will be "0". There is however other possibilities. The server can be configured to completely ignore `.htaccess` files (when `AllowOverride` is set to *None* and `AllowOverrideList` is set to *None*). In that case, we will also get a "0", which is appropriate, as this would also mean a "no" to the "available and working?" question. Also, the `RequestHeader` directive might have been disallowed. Exactly which directives that are allowed in an `.htaccess` depends on the configuration of the virtual host and can be set up differently per directory. What happens then, if the directive is forbidden? One of two things. Depending on the "NonFatal" option on the "AllowOverride" directive, Apache will either go fatal on forbidden directives or ignore them. In this case, the ignored directive will result in a "0", which is appropriate. "Going fatal" means responding with a *500 Internal Server Error*. So the tester class must interpret such response as a "no" to the "available and working?" question. Other errors are possible. For example *404 Not Found*. In that case, the problem is probably that the test was set up wrong and throwing an Exception is appropriate. How about *429 Too Many Requests*? It would mean that the test could not be run *at this time* and an inconclusive answer would seem appropriate. However, you could also argue that as the test failed its purpose (being conclusive), an Exception is appropriate. Throwing exceptions allows users to handle the various cases differently, which is nice. So we go with Exceptions. *403 Forbidden*? I'm unsure. Often, the directory will be forbidden for all users, and then a "no" seems appropriate, however, it could be that it is just forbidden for some users, and in that case, an inconclusive answer seems more suitable - which means throwing an Exception. TODO: decide on this. Summing up: "1" => true, "0" => false, Error => false or throw Exception, depending on the error.
Here is another example, on how
For example, the following two files can be used for answering the question: Is mod_env loaded?
**.htaccess**
```
<IfModule mod_setenvif.c>
ServerSignature On
</IfModule>
<IfModule !mod_setenvif.c>
ServerSignature Off
</IfModule>
```
**test.php**
```
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
echo 1;
} else {
echo 0;
}
```
I'm a bit proud of this one. The two directives used (`ServerSignature` and `IfModule`) are both part of core. So these will be available, unless Apache is configured to ignore `.htaccess` files altogether in the given directory. The rarely used `ServerSignature` directive has an even more rarely known side-effect: It sets a server variable. By making a request to `test.php`, we
directive is part of core, as is And as you see, the template can easily be modified to test for whatever module. can easily be used to test whatever `ServerSignature` is the only directive that is part of core