Version Description
(released: 24 Sep 2020) * You can now set cache control header in CDN friendly mode too * The code for testing what actually works in .htaccess files on the server setup has been moved to a new library: htaccess-capability-tester. It has been strengthened in the process. * Improved diagnosing in the "Live test" buttons * Simplified the logic for adding "Vary header" in the .htaccess residing in the cache dir. The logic no longer depends on the Apache module "mod_envif" being installed. mod_envif has Apache "Base" status, which means it is very rarely missing, so I decided not to trigger automatically updating of the .htaccess rules. To apply the change, you must click the button that forces .htaccess regeneration * The plugin has a folder called "wod" which contains php scripts for converting an image to webp. This is used for the function that rely on redirect magic to trigger conversion ("Enable redirection to converter?" and "Create webp files upon request?"). The .htaccess file in the "wod" folder in the plugin dir contains directives for modifying access (in order to counterfight rules placed by security plugins for disallows scripts to be run directly). However if these directives has been disallowed in the server setup, any request to a file in the folder will result in a 500 internal server error. To circumvent this, a "wod2" folder has been added, which contains the same scripts, but without the .htaccess. Upon saving, WebP Express now automatically checks which one works, and points to that in the .htaccess rules. * Bugfix: webp mime type was not registred in .htaccess in "CDN friendly" mode. This is a minor fix so I decided not to update the .htaccess automatically. To apply it, you must click the button that forces .htaccess regeneration. * Bugfix: Bulk convert failed to load the list when there were filenames containing non-unicode characters * Added a new way to support me. I'm on GitHub Sponsors!
For more info, see the closed issues on the 0.18.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/33?closed=1
Release Info
Developer | rosell.dk |
Plugin | WebP Express |
Version | 0.18.0 |
Comparing to | |
See all releases |
Code changes from version 0.17.5 to 0.18.0
- .github/FUNDING.yml +2 -0
- BACKERS.md +29 -13
- README.md +78 -14
- README.txt +26 -6
- changelog.txt +13 -0
- composer.json +2 -1
- composer.lock +61 -1
- htaccess-capability-tests/.htaccess +0 -6
- htaccess-capability-tests/has-mod-header/.htaccess +0 -5
- htaccess-capability-tests/has-mod-header/test.php +0 -9
- htaccess-capability-tests/has-mod-rewrite/.htaccess +0 -11
- htaccess-capability-tests/has-mod-rewrite/1.php +0 -2
- htaccess-capability-tests/has-mod-rewrite/test.php +0 -2
- htaccess-capability-tests/pass-server-var-through-header/.htaccess +0 -15
- htaccess-capability-tests/pass-server-var-through-header/test.php +0 -9
- htaccess-capability-tests/pass-through-environment-var/.htaccess +0 -13
- htaccess-capability-tests/pass-through-environment-var/test.php +0 -26
- htaccess-capability-tests/urls.txt +0 -12
- lib/classes/BulkConvert.php +5 -54
- lib/classes/CachePurge.php +1 -1
- lib/classes/CapabilityTest.php +0 -1
- lib/classes/Config.php +8 -14
- lib/classes/Convert.php +2 -2
- lib/classes/ConvertHelperIndependent.php +1 -1
- lib/classes/ConvertLog.php +1 -1
- lib/classes/HTAccess.php +0 -86
- lib/classes/HTAccessCapabilityTestRunner.php +184 -0
- lib/classes/HTAccessRules.php +275 -32
- lib/classes/Paths.php +34 -3
- lib/classes/PlatformInfo.php +2 -1
- lib/classes/PluginActivate.php +0 -2
- lib/classes/SelfTest.php +3 -3
- lib/classes/SelfTestHelper.php +200 -6
- lib/classes/SelfTestRedirectToConverter.php +23 -9
- lib/classes/SelfTestRedirectToExisting.php +4 -8
- lib/classes/SelfTestRedirectToWebPRealizer.php +11 -0
- lib/classes/WPHttpRequester.php +28 -0
- lib/migrate/migrate7.php +0 -4
- lib/options/css/webp-express-options-page.css +1 -0
- lib/options/enqueue_scripts.php +1 -1
- lib/options/js/0.16.0/bulk-convert.js +74 -42
- lib/options/js/0.16.0/self-test.js +4 -0
- lib/options/options/alter-html/alter-html-options.inc +1 -1
- lib/options/options/general/cache-control.inc +15 -8
- lib/options/options/general/general.inc +1 -1
- lib/options/options/operation-mode.inc +1 -3
- lib/options/options/redirection-rules/enable-redirection-to-converter.inc +2 -2
- lib/options/options/redirection-rules/redirection-rules.inc +14 -4
- lib/options/page-messages.php +124 -39
- lib/options/submit.php +17 -19
- vendor/composer/autoload_classmap.php +24 -0
- vendor/composer/autoload_psr4.php +1 -0
- vendor/composer/autoload_static.php +32 -0
- vendor/composer/installed.json +62 -0
- vendor/rosell-dk/htaccess-capability-tester/.github/FUNDING.yml +2 -0
- vendor/rosell-dk/htaccess-capability-tester/.travis.yml +58 -0
- vendor/rosell-dk/htaccess-capability-tester/LICENSE +674 -0
- vendor/rosell-dk/htaccess-capability-tester/README.md +705 -0
- vendor/rosell-dk/htaccess-capability-tester/composer.json +72 -0
- vendor/rosell-dk/htaccess-capability-tester/docs/GrantAllCrashTesting.md +41 -0
- vendor/rosell-dk/htaccess-capability-tester/docs/Ideas.md +238 -0
- vendor/rosell-dk/htaccess-capability-tester/docs/MoreExamples.md +54 -0
- vendor/rosell-dk/htaccess-capability-tester/docs/TheManyWaysOfHtaccessFailure.md +30 -0
- vendor/rosell-dk/htaccess-capability-tester/docs/Usage.md +5 -0
- vendor/rosell-dk/htaccess-capability-tester/docs/interpreting.md +31 -0
- vendor/rosell-dk/htaccess-capability-tester/phpunit.xml.dist +39 -0
- vendor/rosell-dk/htaccess-capability-tester/src/HtaccessCapabilityTester.php +334 -0
- vendor/rosell-dk/htaccess-capability-tester/src/HttpRequesterInterface.php +13 -0
- vendor/rosell-dk/htaccess-capability-tester/src/HttpResponse.php +76 -0
- vendor/rosell-dk/htaccess-capability-tester/src/SimpleHttpRequester.php +46 -0
- vendor/rosell-dk/htaccess-capability-tester/src/SimpleTestFileLineUpper.php +99 -0
- vendor/rosell-dk/htaccess-capability-tester/src/TestFilesLineUpperInterface.php +20 -0
- vendor/rosell-dk/htaccess-capability-tester/src/TestResult.php +39 -0
- vendor/rosell-dk/htaccess-capability-tester/src/TestResultCache.php +81 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/AbstractTester.php +191 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/AddTypeTester.php +44 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/ContentDigestTester.php +54 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/CrashTester.php +87 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/CustomTester.php +228 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/DirectoryIndexTester.php +48 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/HeaderSetTester.php +43 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/Helpers/ResponseInterpreter.php +169 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/HtaccessEnabledTester.php +108 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/InnocentRequestTester.php +38 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/ModuleLoadedTester.php +369 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughEnvTester.php +81 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTester.php +65 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/RequestHeaderTester.php +59 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/RewriteTester.php +93 -0
- vendor/rosell-dk/htaccess-capability-tester/src/Testers/ServerSignatureTester.php +93 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/FakeServer.php +170 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Helper.php +18 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/HtaccessCapabilityTesterTest.php +142 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/HttpResponseTest.php +51 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/AddTypeTesterTest.php +94 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/BasisTestCase.php +72 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ContentDigestTesterTest.php +127 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/CrashTesterTest.php +106 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/DirectoryIndexTesterTest.php +100 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/HeaderSetTesterTest.php +99 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/HtaccessEnabledTesterTest.php +116 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/InnocentRequestTesterTest.php +54 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ModuleLoadedTesterTest.php +249 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/PassInfoFromRewriteToScriptThroughEnvTesterTest.php +143 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTesterTest.php +129 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/RequestHeaderTesterTest.php +123 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/RewriteTesterTest.php +98 -0
- vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ServerSignatureTesterTest.php +148 -0
- webp-express.php +1 -1
- wod/.htaccess +1 -1
- wod/ping.php +1 -0
- wod/ping.txt +1 -0
- wod2/ping.php +1 -0
- wod2/ping.txt +1 -0
- wod2/webp-on-demand.php +2 -0
- wod2/webp-realizer.php +2 -0
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
ko_fi: rosell
|
2 |
+
patreon: rosell
|
@@ -1,27 +1,43 @@
|
|
1 |
|
2 |
# Backers
|
3 |
|
4 |
-
WebP Express is an MIT-licensed open source project. It is free and always will be.
|
5 |
|
6 |
-
How is it financed then? Well, it isn't exactly. However, some people choose to support the development by buying
|
7 |
|
8 |
-
To become a backer, yourself, [go to my
|
9 |
|
10 |
-
|
11 |
-
## Backers via Patron
|
12 |
-
|
13 |
-
| Name | Date | Message (max 70 chars, plain text only) |
|
14 |
-
| --------------------- | ---------- | ----------------------------------------------------------------------- |
|
15 |
-
| Tammy Valgardson | 2018-12-27 | |
|
16 |
-
| Max Kreminsky | 2019-08-02 | |
|
17 |
-
|
18 |
-
<sub>I reserve the right to disallow inappropriate messages. No Trump hooting or bashing here, please. And don't be aggressive, obscene or anything unpleasant. But I don't have to point that out, do I?</sub>
|
19 |
|
20 |
## Generous backers via Patron
|
21 |
|
22 |
-
Generous backers will get their names listed here
|
23 |
|
24 |
There are no generous backers yet. [Be the first!](https://www.patreon.com/rosell)
|
25 |
|
26 |
<sub>
|
27 |
I reserve the right to disallow inappropriate messages and links. No xxx sites or anything freaky or fishy, please. You may however advertise non-freaky-or-fishy things, if you wish. Just remember the audience. No point in trying to sell shoes here</sub>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
|
2 |
# Backers
|
3 |
|
4 |
+
WebP Express is an MIT-licensed open source project. It is free and always will be.
|
5 |
|
6 |
+
How is it financed then? Well, it isn't exactly. However, some people choose to support the development by buying me a cup of coffee, and some go even further, by becoming backers. Backers are nice folks making recurring monthly donations, and by doing this, they give me an excuse to put more work into the plugin than I really should.
|
7 |
|
8 |
+
To become a backer, yourself, [go to my GitHub sponsors page](https://github.com/sponsors/rosell-dk)
|
9 |
|
10 |
+
PS: I just started using GitHub Sponsors instead of patreon. I'm keeping [my patreon page]((https://www.patreon.com/rosell)), but might change it to support some other project. I'm for example very curious about the nature of reality and if we might be living in a computer simulation, and I might want to write a book about it one day.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
## Generous backers via Patron
|
13 |
|
14 |
+
Generous backers will get their names listed here.
|
15 |
|
16 |
There are no generous backers yet. [Be the first!](https://www.patreon.com/rosell)
|
17 |
|
18 |
<sub>
|
19 |
I reserve the right to disallow inappropriate messages and links. No xxx sites or anything freaky or fishy, please. You may however advertise non-freaky-or-fishy things, if you wish. Just remember the audience. No point in trying to sell shoes here</sub>
|
20 |
+
|
21 |
+
|
22 |
+
## Active backers via Patron
|
23 |
+
|
24 |
+
| Name | Since date |
|
25 |
+
| ---------------------- | -------------- |
|
26 |
+
| Max Kreminsky | 2019-08-02 |
|
27 |
+
| [Mathieu Gollain-Dupont](https://www.linkedin.com/in/mathieu-gollain-dupont-9938a4a/) | 2020-08-26 |
|
28 |
+
| Nodeflame | 2019-10-31 |
|
29 |
+
| Ruben Solvang | 2020-01-08 |
|
30 |
+
|
31 |
+
|
32 |
+
Hi-scores:
|
33 |
+
|
34 |
+
| Name | Life time contribution |
|
35 |
+
| ------------------------ | ------------------------ |
|
36 |
+
| Tammy Valgardson | $90 |
|
37 |
+
| Max Kreminsky | $65 |
|
38 |
+
| Ruben Solvang | $14 |
|
39 |
+
| Dmitry Verzjikovsky | $5 |
|
40 |
+
|
41 |
+
## Former backers - I'm still grateful :)
|
42 |
+
- Dmitry Verzjikovsky
|
43 |
+
- Tammy Valgardson
|
@@ -10,7 +10,7 @@ But well, it is developed ([here on github](https://github.com/rosell-dk/webp-ex
|
|
10 |
**News: I have added the vendor folder to the repo. To install the plugin here from github, you can simply download the zip and unzip it in your plugin folder**
|
11 |
|
12 |
## Description
|
13 |
-
|
14 |
|
15 |
### The image converter
|
16 |
The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
|
@@ -30,13 +30,14 @@ The plugin implements the "WebP On Demand" solution described [here](https://git
|
|
30 |
- [WebP Convert Cloud Service](https://github.com/rosell-dk/webp-convert-cloud-service): For the Web Service functionality
|
31 |
- [DOM Util for WebP](https://github.com/rosell-dk/dom-util-for-webp): For the Alter HTML functionality
|
32 |
- [Image MimeType Guesser](https://github.com/rosell-dk/image-mime-type-guesser): For detecting mime types of images.
|
|
|
33 |
|
34 |
### Benefits
|
35 |
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
|
36 |
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
|
37 |
- Better ranking in Google searches (performance is taken into account by Google)
|
38 |
- Less bandwidth consumption - makes a huge difference in the parts of the world where the internet is slow and costly (you know, ~80% of the world population lives under these circumstances).
|
39 |
-
- Currently ~
|
40 |
- It's great for the environment too! Reducing network traffic reduces electricity consumption which reduces CO2 emissions.
|
41 |
|
42 |
### Recent news
|
@@ -88,22 +89,27 @@ If you do not have quality detection working, you can try one of the following:
|
|
88 |
- Use "Remote WebP Express" converter to connect to a site, that *does* have quality detection working
|
89 |
- If you have cwebp converter available, you can configure it to aim for a certain reduction, rather than using the quality parameter. Set this to for example 50%, or even 45%.
|
90 |
|
91 |
-
### Verifying that it works
|
92 |
-
|
|
|
|
|
|
|
93 |
|
94 |
-
|
95 |
|
96 |
-
|
97 |
|
98 |
-
|
|
|
|
|
|
|
|
|
99 |
- Right-click the page and choose "Inspect"
|
100 |
- Click the "Network" tab
|
101 |
- Reload the page
|
102 |
- Find a jpeg or png image in the list. In the "type" column, it should say "webp"
|
103 |
|
104 |
-
|
105 |
-
|
106 |
-
You can also append `?debug` after any image url, in order to run a conversion, and see the conversion report. Also, if you append `?reconvert` after an image url, you will force a reconversion of the image.
|
107 |
|
108 |
### Notes
|
109 |
|
@@ -375,6 +381,13 @@ Here are rules if you need to *replace* the file extension with ".webp" rather t
|
|
375 |
### I am on a Windows server
|
376 |
Good news! It should work now, thanks to a guy that calls himself lwxbr. At least on XAMPP 7.3.1, Windows 10. https://github.com/rosell-dk/webp-express/pull/213.
|
377 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
378 |
### I am using Jetpack
|
379 |
If you install Jetpack and enable the "Speed up image load times" then Jetpack will alter the HTML such that images are pointed to their CDN.
|
380 |
|
@@ -644,20 +657,44 @@ RewriteRule . - [L]
|
|
644 |
```
|
645 |
If you got any further questions, look at, or comment on [this topic](https://wordpress.org/support/topic/can-i-make-an-exception-for-specific-post-image/)
|
646 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
647 |
### Update failed and cannot reinstall
|
648 |
The 0.17.0 release contained binaries with dots in their filenames, which caused the unpacking during update to fail on a few systems. This failure could leave an incomplete installation. With important files missing - such as the main plugin file - Wordpress no longer registers that the plugin is there (it is missing from the list). However, the folder is there in the file system and trying to install WebP Express again fails because Wordpress complains about just that. The solution is to remove the "webp-express" folder in "plugins" manually (via ftp or a plugin, such as File Manager) and then install WebP Express anew. The setting will be intact. The filenames that caused the trouble where fixed in 0.17.2.
|
649 |
|
650 |
### When is feature X coming? / Roadmap
|
651 |
No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
|
652 |
|
653 |
-
Here are my current plans ahead: 0.
|
654 |
|
655 |
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
|
656 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
657 |
## Changes in 0.17.5
|
658 |
*(released: 11 Aug 2020)*
|
659 |
|
660 |
-
* Fixed "Path is outside resolved document root" in a certain symlinked configuration. Thanks to @
|
661 |
* Added content filtering hooks for several third party plugins including ACF and WooCommerce Product Images. With this change, the "Use content filtering hooks" in Alter HTML works in more scenarios, which means there are fewer scenarios where you have to resort to the slower "The complete page" option. Thanks to alextuan for providing the contribution
|
662 |
* Fixed problems with Alter HTML when migrating: Absolute paths were cached in the database and the cache was only updated upon saving settings. The paths are not cached anymore (recalculating these on each page load is not a performance problem)
|
663 |
|
@@ -833,5 +870,32 @@ For more info, see the closed issues on the 0.9.0 milestone on the github reposi
|
|
833 |
## Supporting WebP Express
|
834 |
Bread on the table don't come for free, even though this plugin does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to continue putting effort into this plugin:
|
835 |
|
836 |
-
- [Buy me a
|
837 |
-
- [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
**News: I have added the vendor folder to the repo. To install the plugin here from github, you can simply download the zip and unzip it in your plugin folder**
|
11 |
|
12 |
## Description
|
13 |
+
About 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
|
14 |
|
15 |
### The image converter
|
16 |
The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
|
30 |
- [WebP Convert Cloud Service](https://github.com/rosell-dk/webp-convert-cloud-service): For the Web Service functionality
|
31 |
- [DOM Util for WebP](https://github.com/rosell-dk/dom-util-for-webp): For the Alter HTML functionality
|
32 |
- [Image MimeType Guesser](https://github.com/rosell-dk/image-mime-type-guesser): For detecting mime types of images.
|
33 |
+
- [HTAccess Capability Tester](https://github.com/rosell-dk/htaccess-capability-tester): For testing .htaccess capabilities in a given directory, using live tests
|
34 |
|
35 |
### Benefits
|
36 |
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
|
37 |
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
|
38 |
- Better ranking in Google searches (performance is taken into account by Google)
|
39 |
- Less bandwidth consumption - makes a huge difference in the parts of the world where the internet is slow and costly (you know, ~80% of the world population lives under these circumstances).
|
40 |
+
- Currently ~86% of all traffic, and ~78% of mobile browsing traffic are done with browsers supporting webp. With Apple finally on board (Safari 14.0), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
|
41 |
- It's great for the environment too! Reducing network traffic reduces electricity consumption which reduces CO2 emissions.
|
42 |
|
43 |
### Recent news
|
89 |
- Use "Remote WebP Express" converter to connect to a site, that *does* have quality detection working
|
90 |
- If you have cwebp converter available, you can configure it to aim for a certain reduction, rather than using the quality parameter. Set this to for example 50%, or even 45%.
|
91 |
|
92 |
+
### Verifying that it works (in "Varied image responses" mode)
|
93 |
+
1. Make sure at least one of the conversion methods are working. It should have a green checkmark next to it.
|
94 |
+
2. If you haven't saved yet, click "Save settings". This will put redirection rules into .htaccess files in the relevant directories (typically in uploads, themes and wp-content/webp-express/webp-images, depending on the "Scope" setting)
|
95 |
+
3. I assume that you checked at least one of the checkboxes in the .htaccess rules section - otherwise you might as well change to "CDN friendly" mode. The first
|
96 |
+
4. Click the "Live test" buttons to see that the enabled rules actually are working. If they are not, it *could* be that the server needs a little time to recognize the changed rules.
|
97 |
|
98 |
+
The live tests are quite thorough and I recommend them over a manual test. However, it doesn't hurt to do a manual inspection too.
|
99 |
|
100 |
+
*Doing a manual inspection*
|
101 |
|
102 |
+
Note that when WebP Express is serving varied image responses, the image URLs *still points to the jpg/png*. If the URL is visited using a browser that supports webp, however, the response will be a webp image. So there is a mismatch between the file extension (the filename ends with "jpg" or "png") and the file type. But luckily, the browser does not rely on the extension to determine the file type, it only looks at the Content-Type response header.
|
103 |
+
|
104 |
+
To verify that the plugin is working (without clicking the test button), do the following:
|
105 |
+
|
106 |
+
- Open the page in a browser that supports webp, ie Google Chrome
|
107 |
- Right-click the page and choose "Inspect"
|
108 |
- Click the "Network" tab
|
109 |
- Reload the page
|
110 |
- Find a jpeg or png image in the list. In the "type" column, it should say "webp"
|
111 |
|
112 |
+
You can also look at the headers. When WebP Express has redirected to an existing webp, there will be a "X-WebP-Express" header with the following value: "Redirected directly to existing webp". If there isn't (and you have checked "Enable redirection to converter"), you should see a "WebP-Convert-Log" header (WebP-Express uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) for conversions).
|
|
|
|
|
113 |
|
114 |
### Notes
|
115 |
|
381 |
### I am on a Windows server
|
382 |
Good news! It should work now, thanks to a guy that calls himself lwxbr. At least on XAMPP 7.3.1, Windows 10. https://github.com/rosell-dk/webp-express/pull/213.
|
383 |
|
384 |
+
### I am on a Litespeed server
|
385 |
+
You do not have to do anything special for it to work on a Litespeed server. You should be able to use WebP Express in any operation mode. For best performance, I however recommend that use the *LiteSpeed Cache* plugin for page caching.
|
386 |
+
|
387 |
+
LiteSpeed Cache can be set up to maintain separate page caches for browsers that supports webp and browsers that don't. Through this functionality it is possible to use "Alter HTML" with the option "Replace image URLs" and "Only do the replacements in webp enabled browsers" mode.
|
388 |
+
|
389 |
+
The setup was kindly shared and explained in detail by [@ribeiroeder](https://github.com/ribeiroeder) [here](https://github.com/rosell-dk/webp-express/issues/433)
|
390 |
+
|
391 |
### I am using Jetpack
|
392 |
If you install Jetpack and enable the "Speed up image load times" then Jetpack will alter the HTML such that images are pointed to their CDN.
|
393 |
|
657 |
```
|
658 |
If you got any further questions, look at, or comment on [this topic](https://wordpress.org/support/topic/can-i-make-an-exception-for-specific-post-image/)
|
659 |
|
660 |
+
### Alter HTML only replaces some of the images
|
661 |
+
|
662 |
+
If you are wondering why Alter HTML are missing some images, it can be due to one of the following reasons:
|
663 |
+
|
664 |
+
- WebP Express doesn't convert external images, only images on your server. Alter HTML will therefore not alter URLS that unless they point to your server or a CDN that you have added in the *CDN hostnames* section
|
665 |
+
- WebP Express has a "Scope" option, which for example can be set to "Uploads and themes". Only images that resides within the selected scope are replaced with webp.
|
666 |
+
- If you have selected `<picture>` tags syntax, only images inserted with `<img>`-tags will be replaced (CSS images will not be replaced). Additionally, the `<img>`-tag must have a "src" attribute or a commonly used data attribute for lazyloading (such as “data-src” or “data-lazy-src”)
|
667 |
+
- If you have set the "How to replace" option to "Use content filtering hooks", images inserted with some third party plugins/themes might not be replaced. To overcome that, change that setting to "The complete page".
|
668 |
+
- The image might have been inserted with javascript. WebP Express doesn't changSome plugins might insert the
|
669 |
+
|
670 |
### Update failed and cannot reinstall
|
671 |
The 0.17.0 release contained binaries with dots in their filenames, which caused the unpacking during update to fail on a few systems. This failure could leave an incomplete installation. With important files missing - such as the main plugin file - Wordpress no longer registers that the plugin is there (it is missing from the list). However, the folder is there in the file system and trying to install WebP Express again fails because Wordpress complains about just that. The solution is to remove the "webp-express" folder in "plugins" manually (via ftp or a plugin, such as File Manager) and then install WebP Express anew. The setting will be intact. The filenames that caused the trouble where fixed in 0.17.2.
|
672 |
|
673 |
### When is feature X coming? / Roadmap
|
674 |
No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
|
675 |
|
676 |
+
Here are my current plans ahead: 0.18 will probably be a file manager-like interface for converting / bulk converting / viewing conversion logs / comparing original vs webp visually - kind of a merge of current "test converter" and "bulk conversion" interfaces, and with an addition of a file explorer. 0.19 might allow excluding certain files and folders. 0.20 could be supporting Save-Data header in Varied Image Responses mode (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.21 might be displaying rules for NGINX. 0.21 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript library does not (currently) support srcset attributes, which is why I moved this item down the priority list. We need srcset to be supported for the feature to be useful. 0.21 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
|
677 |
|
678 |
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
|
679 |
|
680 |
+
## Changes in 0.18.0
|
681 |
+
*(released: 24 Sep 2020)*
|
682 |
+
* You can now set cache control header in CDN friendly mode too
|
683 |
+
* The code for testing what actually works in .htaccess files on the server setup has been moved to a new library: [htaccess-capability-tester](https://github.com/rosell-dk/htaccess-capability-tester). It has been strengthened in the process.
|
684 |
+
* Improved diagnosing in the "Live test" buttons
|
685 |
+
* Simplified the logic for adding "Vary header" in the .htaccess residing in the cache dir. The logic no longer depends on the Apache module "mod_envif" being installed. mod_envif has Apache "Base" status, which means it is very rarely missing, so I decided not to trigger automatically updating of the .htaccess rules. To apply the change, you must click the button that forces .htaccess regeneration
|
686 |
+
* The plugin has a folder called "wod" which contains php scripts for converting an image to webp. This is used for the function that rely on redirect magic to trigger conversion ("Enable redirection to converter?" and "Create webp files upon request?"). The .htaccess file in the "wod" folder in the plugin dir contains directives for modifying access (in order to counterfight rules placed by security plugins for disallows scripts to be run directly). However if these directives has been disallowed in the server setup, any request to a file in the folder will result in a 500 internal server error. To circumvent this, a "wod2" folder has been added, which contains the same scripts, but without the .htaccess. Upon saving, WebP Express now automatically checks which one works, and points to that in the .htaccess rules.
|
687 |
+
* Bugfix: webp mime type was not registred in .htaccess in "CDN friendly" mode. This is a minor fix so I decided not to update the .htaccess automatically. To apply it, you must click the button that forces .htaccess regeneration.
|
688 |
+
* Bugfix: Bulk convert failed to load the list when there were filenames containing non-unicode characters
|
689 |
+
* Added a new way to support me. I'm on [GitHub Sponsors](https://github.com/sponsors/rosell-dk)!
|
690 |
+
|
691 |
+
For more info, see the closed issues on the 0.18.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/33?closed=1
|
692 |
+
|
693 |
+
|
694 |
## Changes in 0.17.5
|
695 |
*(released: 11 Aug 2020)*
|
696 |
|
697 |
+
* Fixed "Path is outside resolved document root" in a certain symlinked configuration. Thanks to @spiderPan for providing the fix.
|
698 |
* Added content filtering hooks for several third party plugins including ACF and WooCommerce Product Images. With this change, the "Use content filtering hooks" in Alter HTML works in more scenarios, which means there are fewer scenarios where you have to resort to the slower "The complete page" option. Thanks to alextuan for providing the contribution
|
699 |
* Fixed problems with Alter HTML when migrating: Absolute paths were cached in the database and the cache was only updated upon saving settings. The paths are not cached anymore (recalculating these on each page load is not a performance problem)
|
700 |
|
870 |
## Supporting WebP Express
|
871 |
Bread on the table don't come for free, even though this plugin does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to continue putting effort into this plugin:
|
872 |
|
873 |
+
- [Buy me a coffee](https://ko-fi.com/rosell)
|
874 |
+
- [Buy me coffee on a regular basis](https://github.com/sponsors/rosell-dk) and help ensuring my coffee supplies doesn't run dry.
|
875 |
+
|
876 |
+
|
877 |
+
**Persons / Companies currently backing the project via patreon - Thanks!**
|
878 |
+
|
879 |
+
- Max Kreminsky
|
880 |
+
- Nodeflame
|
881 |
+
- [Mathieu Gollain-Dupont](https://www.linkedin.com/in/mathieu-gollain-dupont-9938a4a/)
|
882 |
+
- Ruben Solvang
|
883 |
+
|
884 |
+
**Persons who contributed with coffee within the last 30 days:**
|
885 |
+
|
886 |
+
| Name | Date | Message |
|
887 |
+
| ---------------------- | -------------- | ------------ |
|
888 |
+
| Anon | 2020-08-18 | - |
|
889 |
+
| Eder Ribeiro | 2020-08-08 | Hello Bjørn I believe that it is a fantastic solution and that it deserves maximum support to keep getting better! If you can, check out my configuration tip, https://github.com/rosell-dk/webp-express/issues/433 |
|
890 |
+
| Christian | 2020-08-05 | Merci pour votre plugin. Exceptionnel et gratuit. |
|
891 |
+
|
892 |
+
|
893 |
+
**Persons who contributed with extra generously amounts of coffee / lifetime backing (>50$) - thanks!:**
|
894 |
+
|
895 |
+
| Name | Amount | Message |
|
896 |
+
| ---------------------- | -----------| --------- |
|
897 |
+
| Justin - BigScoots | $105 | Such an amazing plugin for so many reasons, thank you! |
|
898 |
+
| Sebastian | $99 | WebP for Wordpress – what a great plugin! Thank you! |
|
899 |
+
| Tammy Lee | $90 | |
|
900 |
+
| Max Kreminsky | $65 | |
|
901 |
+
| Steven Sullivan | $51 | Thank you for such a wonderful plugin. |
|
@@ -4,7 +4,7 @@ Donate link: https://ko-fi.com/rosell
|
|
4 |
Tags: webp, images, performance
|
5 |
Requires at least: 4.0
|
6 |
Tested up to: 5.5
|
7 |
-
Stable tag: 0.
|
8 |
Requires PHP: 5.6
|
9 |
License: GPLv3
|
10 |
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
@@ -126,17 +126,19 @@ Do not simply remove the plugin without deactivating it first. Deactivation take
|
|
126 |
Bread on the table don't come for free, even though this plugin does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to continue wasting time on this plugin:
|
127 |
|
128 |
* [Buy me a Coffee](https://ko-fi.com/rosell)
|
129 |
-
* [
|
130 |
|
131 |
== Supporters of WebP Express ==
|
132 |
|
133 |
-
**Persons currently backing the project via patreon - Thanks!**
|
134 |
|
135 |
* Max Kreminsky
|
136 |
* [Mathieu Gollain-Dupont](https://www.linkedin.com/in/mathieu-gollain-dupont-9938a4a/)
|
137 |
|
138 |
**Persons who contributed with coffee within the last 30 days (updated biweekly) - Thanks! **
|
139 |
|
|
|
|
|
140 |
* RobMoore
|
141 |
* Anon
|
142 |
* Eder Ribeiro
|
@@ -150,7 +152,6 @@ Bread on the table don't come for free, even though this plugin does, and always
|
|
150 |
* Max Kreminsky ($65)
|
151 |
* Steven Sullivan ($51)
|
152 |
|
153 |
-
|
154 |
== Frequently Asked Questions ==
|
155 |
|
156 |
= How do I verify that the plugin is working? =
|
@@ -722,8 +723,11 @@ Here are my current plans ahead: 0.18 will probably be a file manager-like inter
|
|
722 |
|
723 |
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
|
724 |
|
725 |
-
=
|
726 |
-
|
|
|
|
|
|
|
727 |
|
728 |
== Screenshots ==
|
729 |
|
@@ -731,6 +735,19 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacof
|
|
731 |
|
732 |
== Changelog ==
|
733 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
734 |
= 0.17.5 =
|
735 |
*(released: 11 Aug 2020)*
|
736 |
* Fixed "Path is outside resolved document root" in a certain symlinked configuration. Thanks to @spiderPan on github for providing the fix.
|
@@ -1161,6 +1178,9 @@ For older releases, check out changelog.txt
|
|
1161 |
|
1162 |
== Upgrade Notice ==
|
1163 |
|
|
|
|
|
|
|
1164 |
= 0.17.5 =
|
1165 |
* Various fixes, mainly by community.
|
1166 |
|
4 |
Tags: webp, images, performance
|
5 |
Requires at least: 4.0
|
6 |
Tested up to: 5.5
|
7 |
+
Stable tag: 0.18.0
|
8 |
Requires PHP: 5.6
|
9 |
License: GPLv3
|
10 |
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
126 |
Bread on the table don't come for free, even though this plugin does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to continue wasting time on this plugin:
|
127 |
|
128 |
* [Buy me a Coffee](https://ko-fi.com/rosell)
|
129 |
+
* [Buy me coffee on a regular basis](https://github.com/sponsors/rosell-dk) and help ensuring my coffee supplies doesn't run dry.
|
130 |
|
131 |
== Supporters of WebP Express ==
|
132 |
|
133 |
+
**Persons currently backing the project via GitHub Sponsors or patreon - Thanks!**
|
134 |
|
135 |
* Max Kreminsky
|
136 |
* [Mathieu Gollain-Dupont](https://www.linkedin.com/in/mathieu-gollain-dupont-9938a4a/)
|
137 |
|
138 |
**Persons who contributed with coffee within the last 30 days (updated biweekly) - Thanks! **
|
139 |
|
140 |
+
* Katja
|
141 |
+
* Devashish Datt Mamgain
|
142 |
* RobMoore
|
143 |
* Anon
|
144 |
* Eder Ribeiro
|
152 |
* Max Kreminsky ($65)
|
153 |
* Steven Sullivan ($51)
|
154 |
|
|
|
155 |
== Frequently Asked Questions ==
|
156 |
|
157 |
= How do I verify that the plugin is working? =
|
723 |
|
724 |
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
|
725 |
|
726 |
+
= Can I buy you a cup of coffee? =
|
727 |
+
You sure can! To do so, [go here!](https://ko-fi.com/rosell). If payment doesn't work for your country, [try here instead](https://buymeacoff.ee/rosell).
|
728 |
+
|
729 |
+
If you want to make sure that my coffee supplies don't run dry, you can even buy me a cup of coffee on a regular basis [by going here](https://github.com/sponsors/rosell-dk)
|
730 |
+
|
731 |
|
732 |
== Screenshots ==
|
733 |
|
735 |
|
736 |
== Changelog ==
|
737 |
|
738 |
+
= 0.18.0 =
|
739 |
+
*(released: 24 Sep 2020)*
|
740 |
+
* You can now set cache control header in CDN friendly mode too
|
741 |
+
* The code for testing what actually works in .htaccess files on the server setup has been moved to a new library: [htaccess-capability-tester](https://github.com/rosell-dk/htaccess-capability-tester). It has been strengthened in the process.
|
742 |
+
* Improved diagnosing in the "Live test" buttons
|
743 |
+
* Simplified the logic for adding "Vary header" in the .htaccess residing in the cache dir. The logic no longer depends on the Apache module "mod_envif" being installed. mod_envif has Apache "Base" status, which means it is very rarely missing, so I decided not to trigger automatically updating of the .htaccess rules. To apply the change, you must click the button that forces .htaccess regeneration
|
744 |
+
* The plugin has a folder called "wod" which contains php scripts for converting an image to webp. This is used for the function that rely on redirect magic to trigger conversion ("Enable redirection to converter?" and "Create webp files upon request?"). The .htaccess file in the "wod" folder in the plugin dir contains directives for modifying access (in order to counterfight rules placed by security plugins for disallows scripts to be run directly). However if these directives has been disallowed in the server setup, any request to a file in the folder will result in a 500 internal server error. To circumvent this, a "wod2" folder has been added, which contains the same scripts, but without the .htaccess. Upon saving, WebP Express now automatically checks which one works, and points to that in the .htaccess rules.
|
745 |
+
* Bugfix: webp mime type was not registred in .htaccess in "CDN friendly" mode. This is a minor fix so I decided not to update the .htaccess automatically. To apply it, you must click the button that forces .htaccess regeneration.
|
746 |
+
* Bugfix: Bulk convert failed to load the list when there were filenames containing non-unicode characters
|
747 |
+
* Added a new way to support me. I'm on [GitHub Sponsors](https://github.com/sponsors/rosell-dk)!
|
748 |
+
|
749 |
+
For more info, see the closed issues on the 0.18.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/33?closed=1
|
750 |
+
|
751 |
= 0.17.5 =
|
752 |
*(released: 11 Aug 2020)*
|
753 |
* Fixed "Path is outside resolved document root" in a certain symlinked configuration. Thanks to @spiderPan on github for providing the fix.
|
1178 |
|
1179 |
== Upgrade Notice ==
|
1180 |
|
1181 |
+
= 0.18.0 =
|
1182 |
+
* General maintenance and improvements
|
1183 |
+
|
1184 |
= 0.17.5 =
|
1185 |
* Various fixes, mainly by community.
|
1186 |
|
@@ -1,3 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
= 0.17.5 =
|
2 |
*(released: 11 Aug 2020)*
|
3 |
* Fixed "Path is outside resolved document root" in a certain symlinked configuration. Thanks to @spiderPan on github for providing the fix.
|
1 |
+
= 0.18.0 =
|
2 |
+
*(released: 24 Sep 2020)*
|
3 |
+
* You can now set cache control header in CDN friendly mode too
|
4 |
+
* The code for testing what actually works in .htaccess files on the server setup has been moved to a new library: [htaccess-capability-tester](https://github.com/rosell-dk/htaccess-capability-tester). It has been strengthened in the process.
|
5 |
+
* Improved diagnosing in the "Live test" buttons
|
6 |
+
* Simplified the logic for adding "Vary header" in the .htaccess residing in the cache dir. The logic no longer depends on the Apache module "mod_envif" being installed. mod_envif has Apache "Base" status, which means it is very rarely missing, so I decided not to trigger automatically updating of the .htaccess rules. To apply the change, you must click the button that forces .htaccess regeneration
|
7 |
+
* The plugin has a folder called "wod" which contains php scripts for converting an image to webp. This is used for the function that rely on redirect magic to trigger conversion ("Enable redirection to converter?" and "Create webp files upon request?"). The .htaccess file in the "wod" folder in the plugin dir contains directives for modifying access (in order to counterfight rules placed by security plugins for disallows scripts to be run directly). However if these directives has been disallowed in the server setup, any request to a file in the folder will result in a 500 internal server error. To circumvent this, a "wod2" folder has been added, which contains the same scripts, but without the .htaccess. Upon saving, WebP Express now automatically checks which one works, and points to that in the .htaccess rules.
|
8 |
+
* Bugfix: webp mime type was not registred in .htaccess in "CDN friendly" mode. This is a minor fix so I decided not to update the .htaccess automatically. To apply it, you must click the button that forces .htaccess regeneration.
|
9 |
+
* Bugfix: Bulk convert failed to load the list when there were filenames containing non-unicode characters
|
10 |
+
* Added a new way to support me. I'm on [GitHub Sponsors](https://github.com/sponsors/rosell-dk)!
|
11 |
+
|
12 |
+
For more info, see the closed issues on the 0.18.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/33?closed=1
|
13 |
+
|
14 |
= 0.17.5 =
|
15 |
*(released: 11 Aug 2020)*
|
16 |
* Fixed "Path is outside resolved document root" in a certain symlinked configuration. Thanks to @spiderPan on github for providing the fix.
|
@@ -7,7 +7,8 @@
|
|
7 |
"composer/installers": "^1.0.0",
|
8 |
"rosell-dk/webp-convert": "^2.3.1",
|
9 |
"rosell-dk/webp-convert-cloud-service": "^2.0.0",
|
10 |
-
"rosell-dk/dom-util-for-webp": "^0.4.0"
|
|
|
11 |
},
|
12 |
"require-dev": {
|
13 |
},
|
7 |
"composer/installers": "^1.0.0",
|
8 |
"rosell-dk/webp-convert": "^2.3.1",
|
9 |
"rosell-dk/webp-convert-cloud-service": "^2.0.0",
|
10 |
+
"rosell-dk/dom-util-for-webp": "^0.4.0",
|
11 |
+
"rosell-dk/htaccess-capability-tester": "^0.8.0"
|
12 |
},
|
13 |
"require-dev": {
|
14 |
},
|
@@ -4,7 +4,7 @@
|
|
4 |
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
5 |
"This file is @generated automatically"
|
6 |
],
|
7 |
-
"content-hash": "
|
8 |
"packages": [
|
9 |
{
|
10 |
"name": "composer/installers",
|
@@ -191,6 +191,66 @@
|
|
191 |
],
|
192 |
"time": "2020-02-02T11:16:27+00:00"
|
193 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
{
|
195 |
"name": "rosell-dk/image-mime-type-guesser",
|
196 |
"version": "0.3",
|
4 |
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
5 |
"This file is @generated automatically"
|
6 |
],
|
7 |
+
"content-hash": "b2a148dedbe8b5edc157f97c824af67f",
|
8 |
"packages": [
|
9 |
{
|
10 |
"name": "composer/installers",
|
191 |
],
|
192 |
"time": "2020-02-02T11:16:27+00:00"
|
193 |
},
|
194 |
+
{
|
195 |
+
"name": "rosell-dk/htaccess-capability-tester",
|
196 |
+
"version": "0.8",
|
197 |
+
"source": {
|
198 |
+
"type": "git",
|
199 |
+
"url": "https://github.com/rosell-dk/htaccess-capability-tester.git",
|
200 |
+
"reference": "3556f664c5533643c0cb0beceaf1947abd7aed7a"
|
201 |
+
},
|
202 |
+
"dist": {
|
203 |
+
"type": "zip",
|
204 |
+
"url": "https://api.github.com/repos/rosell-dk/htaccess-capability-tester/zipball/3556f664c5533643c0cb0beceaf1947abd7aed7a",
|
205 |
+
"reference": "3556f664c5533643c0cb0beceaf1947abd7aed7a",
|
206 |
+
"shasum": ""
|
207 |
+
},
|
208 |
+
"require": {
|
209 |
+
"php": "^5.6 | ^7.0"
|
210 |
+
},
|
211 |
+
"require-dev": {
|
212 |
+
"phpunit/php-code-coverage": "dev-4.0-dev as 4.0.4",
|
213 |
+
"phpunit/phpunit": "^5.7",
|
214 |
+
"squizlabs/php_codesniffer": "3.*"
|
215 |
+
},
|
216 |
+
"suggest": {
|
217 |
+
"php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
|
218 |
+
},
|
219 |
+
"type": "library",
|
220 |
+
"extra": {
|
221 |
+
"scripts-descriptions": {
|
222 |
+
"ci": "Run tests before CI",
|
223 |
+
"phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
|
224 |
+
"phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
|
225 |
+
"cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
|
226 |
+
"cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
|
227 |
+
"test": "Launches the preconfigured PHPUnit"
|
228 |
+
}
|
229 |
+
},
|
230 |
+
"autoload": {
|
231 |
+
"psr-4": {
|
232 |
+
"HtaccessCapabilityTester\\": "src/"
|
233 |
+
}
|
234 |
+
},
|
235 |
+
"notification-url": "https://packagist.org/downloads/",
|
236 |
+
"license": [
|
237 |
+
"MIT"
|
238 |
+
],
|
239 |
+
"authors": [
|
240 |
+
{
|
241 |
+
"name": "Bjørn Rosell",
|
242 |
+
"homepage": "https://www.bitwise-it.dk/contact",
|
243 |
+
"role": "Project Author"
|
244 |
+
}
|
245 |
+
],
|
246 |
+
"description": "Test the capabilities of .htaccess files on the server using live tests",
|
247 |
+
"keywords": [
|
248 |
+
".htaccess",
|
249 |
+
"apache",
|
250 |
+
"litespeed"
|
251 |
+
],
|
252 |
+
"time": "2020-09-23T10:46:34+00:00"
|
253 |
+
},
|
254 |
{
|
255 |
"name": "rosell-dk/image-mime-type-guesser",
|
256 |
"version": "0.3",
|
@@ -1,6 +0,0 @@
|
|
1 |
-
<IfModule mod_rewrite.c>
|
2 |
-
#RewriteEngine on
|
3 |
-
#RewriteCond %{REQUEST_URI} !request-uri-usable
|
4 |
-
#RewriteRule ^(.*)$ request-uri-usable/$1
|
5 |
-
|
6 |
-
</IfModule>
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,5 +0,0 @@
|
|
1 |
-
<IfModule mod_headers.c>
|
2 |
-
# Certain hosts seem to strip non-standard request headers,
|
3 |
-
# so we use a standard one to avoid a false negative
|
4 |
-
RequestHeader set User-Agent "webp-express-test"
|
5 |
-
</IfModule>
|
|
|
|
|
|
|
|
|
|
@@ -1,9 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
//echo '<pre>'. print_r($_SERVER, 1) . '</pre>';
|
4 |
-
|
5 |
-
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
6 |
-
echo $_SERVER['HTTP_USER_AGENT'] == 'webp-express-test' ? 1 : 0;
|
7 |
-
} else {
|
8 |
-
echo 0;
|
9 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,11 +0,0 @@
|
|
1 |
-
<IfModule mod_rewrite.c>
|
2 |
-
|
3 |
-
# Testing for mod_rewrite
|
4 |
-
# -----------------------
|
5 |
-
# If mod_rewrite is enabled, redirect to 1.php, which returns "1".
|
6 |
-
# If mod_rewrite is disabled, the rewriting fails, and we end at test.php, which always returns 0.
|
7 |
-
|
8 |
-
RewriteEngine On
|
9 |
-
RewriteRule ^test\.php$ 1.php [L]
|
10 |
-
|
11 |
-
</IfModule>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,2 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
echo '1';
|
|
|
|
@@ -1,2 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
echo '0';
|
|
|
|
@@ -1,15 +0,0 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
<IfModule mod_rewrite.c>
|
5 |
-
RewriteEngine On
|
6 |
-
|
7 |
-
# PASSTHROUGHHEADERing if we can pass environment variable through request header
|
8 |
-
# We pass document root, because that can easily be checked by the script
|
9 |
-
|
10 |
-
<IfModule mod_headers.c>
|
11 |
-
RequestHeader set PASSTHROUGHHEADER "%{PASSTHROUGHHEADER}e" env=PASSTHROUGHHEADER
|
12 |
-
</IfModule>
|
13 |
-
RewriteRule ^test\.php$ - [E=PASSTHROUGHHEADER:%{DOCUMENT_ROOT},L]
|
14 |
-
|
15 |
-
</IfModule>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,9 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
//echo '<pre>'. print_r($_SERVER, 1) . '</pre>';
|
4 |
-
|
5 |
-
if (isset($_SERVER['HTTP_PASSTHROUGHHEADER'])) {
|
6 |
-
echo ($_SERVER['HTTP_PASSTHROUGHHEADER'] == $_SERVER['DOCUMENT_ROOT'] ? 1 : 0);
|
7 |
-
exit;
|
8 |
-
}
|
9 |
-
echo '0';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,13 +0,0 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
<IfModule mod_rewrite.c>
|
6 |
-
|
7 |
-
# Testing if we can pass environment variable from .htaccess to script in a RewriteRule
|
8 |
-
# We pass document root, because that can easily be checked by the script
|
9 |
-
|
10 |
-
RewriteEngine On
|
11 |
-
RewriteRule ^test\.php$ - [E=PASSTHROUGHENV:%{DOCUMENT_ROOT},L]
|
12 |
-
|
13 |
-
</IfModule>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,26 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
/**
|
4 |
-
* Get environment variable set with mod_rewrite module
|
5 |
-
* Return false if the environment variable isn't found
|
6 |
-
*/
|
7 |
-
function getEnvPassedInRewriteRule($envName) {
|
8 |
-
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
|
9 |
-
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
10 |
-
// We simply look for an environment variable that ends with what we are looking for.
|
11 |
-
// (so make sure to make it unique)
|
12 |
-
$len = strlen($envName);
|
13 |
-
foreach ($_SERVER as $key => $item) {
|
14 |
-
if (substr($key, -$len) == $envName) {
|
15 |
-
return $item;
|
16 |
-
}
|
17 |
-
}
|
18 |
-
return false;
|
19 |
-
}
|
20 |
-
|
21 |
-
$result = getEnvPassedInRewriteRule('PASSTHROUGHENV');
|
22 |
-
if ($result === false) {
|
23 |
-
echo '0';
|
24 |
-
exit;
|
25 |
-
}
|
26 |
-
echo ($result == $_SERVER['DOCUMENT_ROOT'] ? '1' : '0');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,12 +0,0 @@
|
|
1 |
-
We 0:
|
2 |
-
|
3 |
-
http://we0/wp-content-moved/webp-express/capability-tests/has-mod-rewrite/test.php
|
4 |
-
http://we0/wp-content-moved/webp-express/capability-tests/pass-through-environment-var/test.php
|
5 |
-
http://we0/wp-content-moved/webp-express/capability-tests/pass-server-var-through-header/test.php
|
6 |
-
http://we0/wp-content-moved/webp-express/capability-tests/request-uri-usable/test.php
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
We 7:
|
11 |
-
------
|
12 |
-
http://we7/wp-content/webp-express/capability-tests/request-uri-usable/test.php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -8,7 +8,6 @@ class BulkConvert
|
|
8 |
public static function getList($config)
|
9 |
{
|
10 |
|
11 |
-
|
12 |
/*
|
13 |
isUploadDirMovedOutOfWPContentDir
|
14 |
isUploadDirMovedOutOfAbsPath
|
@@ -16,7 +15,6 @@ class BulkConvert
|
|
16 |
isPluginDirMovedOutOfWpContent
|
17 |
isWPContentDirMovedOutOfAbsPath */
|
18 |
|
19 |
-
|
20 |
$listOptions = [
|
21 |
//'root' => Paths::getUploadDirAbs(),
|
22 |
'ext' => $config['destination-extension'],
|
@@ -32,8 +30,6 @@ class BulkConvert
|
|
32 |
]
|
33 |
];
|
34 |
|
35 |
-
//$dirs = $config['scope'];
|
36 |
-
|
37 |
$rootIds = Paths::filterOutSubRoots($config['scope']);
|
38 |
|
39 |
$groups = [];
|
@@ -44,54 +40,6 @@ class BulkConvert
|
|
44 |
];
|
45 |
}
|
46 |
|
47 |
-
/*
|
48 |
-
if (in_array('index', $config['scope'])) {
|
49 |
-
if (Paths::isWPContentDirMovedOutOfAbsPath()) {
|
50 |
-
|
51 |
-
}
|
52 |
-
} elseif (in_array('wp-content', $config['scope'])) {
|
53 |
-
$dirs[] = 'wp-content';
|
54 |
-
if (in_array('uploads', $config['scope']) && Paths::isUploadDirMovedOutOfWPContentDir()) {
|
55 |
-
$dirs[] = 'uploads';
|
56 |
-
}
|
57 |
-
if (in_array('plugins', $config['scope']) && Paths::isPluginDirMovedOutOfWpContent()) {
|
58 |
-
$dirs[] = 'plugins';
|
59 |
-
}
|
60 |
-
// ps: themes is always below wp-content
|
61 |
-
} else {
|
62 |
-
if (in_array('uploads', $config['scope'])) {
|
63 |
-
$dirs[] = 'uploads';
|
64 |
-
}
|
65 |
-
if (in_array('plugins', $config['scope'])) {
|
66 |
-
$dirs[] = 'plugins';
|
67 |
-
}
|
68 |
-
}
|
69 |
-
|
70 |
-
/*
|
71 |
-
if (Paths::isUploadDirMovedOutOfWPContentDir()) {
|
72 |
-
$groups[] = [
|
73 |
-
'groupName' => 'uploads',
|
74 |
-
'root' => Paths::getUploadDirAbs(),
|
75 |
-
];
|
76 |
-
}
|
77 |
-
|
78 |
-
$groups[] = [
|
79 |
-
'groupName' => 'themes',
|
80 |
-
'root' => Paths::getThemesDirAbs(),
|
81 |
-
];
|
82 |
-
|
83 |
-
$groups[] = [
|
84 |
-
'groupName' => 'wp-content',
|
85 |
-
'root' => Paths::getContentDirAbs(),
|
86 |
-
];
|
87 |
-
|
88 |
-
if (Paths::isPluginDirMovedOutOfWpContent()) {
|
89 |
-
$groups[] = [
|
90 |
-
'groupName' => 'plugins',
|
91 |
-
'root' => Paths::getPluginDirAbs(),
|
92 |
-
];
|
93 |
-
}*/
|
94 |
-
|
95 |
foreach ($groups as $i => &$group) {
|
96 |
$listOptions['root'] = $group['root'];
|
97 |
/*
|
@@ -187,7 +135,8 @@ class BulkConvert
|
|
187 |
}
|
188 |
|
189 |
if ($addThis) {
|
190 |
-
|
|
|
191 |
}
|
192 |
}
|
193 |
}
|
@@ -224,12 +173,14 @@ class BulkConvert
|
|
224 |
public static function processAjaxListUnconvertedFiles()
|
225 |
{
|
226 |
if (!check_ajax_referer('webpexpress-ajax-list-unconverted-files-nonce', 'nonce', false)) {
|
227 |
-
wp_send_json_error('
|
228 |
wp_die();
|
229 |
}
|
230 |
|
231 |
$config = Config::loadConfigAndFix();
|
232 |
$arr = self::getList($config);
|
|
|
|
|
233 |
echo json_encode($arr, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
|
234 |
wp_die();
|
235 |
}
|
8 |
public static function getList($config)
|
9 |
{
|
10 |
|
|
|
11 |
/*
|
12 |
isUploadDirMovedOutOfWPContentDir
|
13 |
isUploadDirMovedOutOfAbsPath
|
15 |
isPluginDirMovedOutOfWpContent
|
16 |
isWPContentDirMovedOutOfAbsPath */
|
17 |
|
|
|
18 |
$listOptions = [
|
19 |
//'root' => Paths::getUploadDirAbs(),
|
20 |
'ext' => $config['destination-extension'],
|
30 |
]
|
31 |
];
|
32 |
|
|
|
|
|
33 |
$rootIds = Paths::filterOutSubRoots($config['scope']);
|
34 |
|
35 |
$groups = [];
|
40 |
];
|
41 |
}
|
42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
foreach ($groups as $i => &$group) {
|
44 |
$listOptions['root'] = $group['root'];
|
45 |
/*
|
135 |
}
|
136 |
|
137 |
if ($addThis) {
|
138 |
+
// utf8_encode the filename so json_encode won't fail later on (#445)
|
139 |
+
$results[] = substr($relDir . "/", 2) . utf8_encode($filename); // (we cut the leading "./" off with substr)
|
140 |
}
|
141 |
}
|
142 |
}
|
173 |
public static function processAjaxListUnconvertedFiles()
|
174 |
{
|
175 |
if (!check_ajax_referer('webpexpress-ajax-list-unconverted-files-nonce', 'nonce', false)) {
|
176 |
+
wp_send_json_error('The security nonce has expired. You need to reload the settings page (press F5) and try again)');
|
177 |
wp_die();
|
178 |
}
|
179 |
|
180 |
$config = Config::loadConfigAndFix();
|
181 |
$arr = self::getList($config);
|
182 |
+
|
183 |
+
// TODO: Handle json failure (false)
|
184 |
echo json_encode($arr, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
|
185 |
wp_die();
|
186 |
}
|
@@ -152,7 +152,7 @@ class CachePurge
|
|
152 |
{
|
153 |
|
154 |
if (!check_ajax_referer('webpexpress-ajax-purge-cache-nonce', 'nonce', false)) {
|
155 |
-
wp_send_json_error('
|
156 |
wp_die();
|
157 |
}
|
158 |
|
152 |
{
|
153 |
|
154 |
if (!check_ajax_referer('webpexpress-ajax-purge-cache-nonce', 'nonce', false)) {
|
155 |
+
wp_send_json_error('The security nonce has expired. You need to reload the settings page (press F5) and try again)');
|
156 |
wp_die();
|
157 |
}
|
158 |
|
@@ -100,5 +100,4 @@ class CapabilityTest
|
|
100 |
return self::runTest('pass-server-var-through-header');
|
101 |
}
|
102 |
|
103 |
-
|
104 |
}
|
100 |
return self::runTest('pass-server-var-through-header');
|
101 |
}
|
102 |
|
|
|
103 |
}
|
@@ -2,15 +2,6 @@
|
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
-
use \WebPExpress\ConvertersHelper;
|
6 |
-
use \WebPExpress\FileHelper;
|
7 |
-
use \WebPExpress\HTAccess;
|
8 |
-
use \WebPExpress\Messenger;
|
9 |
-
use \WebPExpress\Paths;
|
10 |
-
use \WebPExpress\State;
|
11 |
-
use \WebPExpress\TestRun;
|
12 |
-
use \WebPExpress\Option;
|
13 |
-
|
14 |
class Config
|
15 |
{
|
16 |
|
@@ -267,9 +258,12 @@ class Config
|
|
267 |
public static function runAndStoreCapabilityTests(&$config)
|
268 |
{
|
269 |
$config['base-htaccess-on-these-capability-tests'] = [
|
270 |
-
'passThroughHeaderWorking' =>
|
271 |
-
'passThroughEnvWorking' =>
|
272 |
-
'modHeaderWorking' =>
|
|
|
|
|
|
|
273 |
];
|
274 |
}
|
275 |
|
@@ -646,10 +640,10 @@ class Config
|
|
646 |
if ($forceRuleUpdating) {
|
647 |
$rewriteRulesNeedsUpdate = true;
|
648 |
} else {
|
649 |
-
$rewriteRulesNeedsUpdate =
|
650 |
}
|
651 |
|
652 |
-
if (!isset($config['base-htaccess-on-these-capability-tests'])) {
|
653 |
self::runAndStoreCapabilityTests($config);
|
654 |
}
|
655 |
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
class Config
|
6 |
{
|
7 |
|
258 |
public static function runAndStoreCapabilityTests(&$config)
|
259 |
{
|
260 |
$config['base-htaccess-on-these-capability-tests'] = [
|
261 |
+
'passThroughHeaderWorking' => HTAccessCapabilityTestRunner::passThroughHeaderWorking(),
|
262 |
+
'passThroughEnvWorking' => HTAccessCapabilityTestRunner::passThroughEnvWorking(),
|
263 |
+
'modHeaderWorking' => HTAccessCapabilityTestRunner::modHeaderWorking(),
|
264 |
+
//'grantAllAllowed' => HTAccessCapabilityTestRunner::grantAllAllowed(),
|
265 |
+
'canRunTestScriptInWOD' => HTAccessCapabilityTestRunner::canRunTestScriptInWOD(),
|
266 |
+
'canRunTestScriptInWOD2' => HTAccessCapabilityTestRunner::canRunTestScriptInWOD2(),
|
267 |
];
|
268 |
}
|
269 |
|
640 |
if ($forceRuleUpdating) {
|
641 |
$rewriteRulesNeedsUpdate = true;
|
642 |
} else {
|
643 |
+
$rewriteRulesNeedsUpdate = HTAccessRules::doesRewriteRulesNeedUpdate($config);
|
644 |
}
|
645 |
|
646 |
+
if (!isset($config['base-htaccess-on-these-capability-tests']) || $rewriteRulesNeedsUpdate) {
|
647 |
self::runAndStoreCapabilityTests($config);
|
648 |
}
|
649 |
|
@@ -179,12 +179,12 @@ class Convert
|
|
179 |
|
180 |
if (!check_ajax_referer('webpexpress-ajax-convert-nonce', 'nonce', false)) {
|
181 |
//if (true) {
|
182 |
-
//wp_send_json_error('
|
183 |
//wp_die();
|
184 |
|
185 |
$result = [
|
186 |
'success' => false,
|
187 |
-
'msg' => '
|
188 |
'stop' => true
|
189 |
];
|
190 |
|
179 |
|
180 |
if (!check_ajax_referer('webpexpress-ajax-convert-nonce', 'nonce', false)) {
|
181 |
//if (true) {
|
182 |
+
//wp_send_json_error('The security nonce has expired. You need to reload the settings page (press F5) and try again)');
|
183 |
//wp_die();
|
184 |
|
185 |
$result = [
|
186 |
'success' => false,
|
187 |
+
'msg' => 'The security nonce has expired. You need to reload the settings page (press F5) and try again)',
|
188 |
'stop' => true
|
189 |
];
|
190 |
|
@@ -571,7 +571,7 @@ APACHE
|
|
571 |
$text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
|
572 |
|
573 |
// TODO: Put version number somewhere else. Ie \WebPExpress\VersionNumber::version
|
574 |
-
$text = 'WebP Express 0.
|
575 |
|
576 |
$logFile = self::getLogFilename($source, $logDir);
|
577 |
|
571 |
$text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
|
572 |
|
573 |
// TODO: Put version number somewhere else. Ie \WebPExpress\VersionNumber::version
|
574 |
+
$text = 'WebP Express 0.18.0. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
|
575 |
|
576 |
$logFile = self::getLogFilename($source, $logDir);
|
577 |
|
@@ -10,7 +10,7 @@ class ConvertLog
|
|
10 |
public static function processAjaxViewLog()
|
11 |
{
|
12 |
if (!check_ajax_referer('webpexpress-ajax-view-log-nonce', 'nonce', false)) {
|
13 |
-
wp_send_json_error('
|
14 |
wp_die();
|
15 |
}
|
16 |
|
10 |
public static function processAjaxViewLog()
|
11 |
{
|
12 |
if (!check_ajax_referer('webpexpress-ajax-view-log-nonce', 'nonce', false)) {
|
13 |
+
wp_send_json_error('The security nonce has expired. You need to reload the settings page (press F5) and try again)');
|
14 |
wp_die();
|
15 |
}
|
16 |
|
@@ -10,93 +10,7 @@ use \WebPExpress\State;
|
|
10 |
|
11 |
class HTAccess
|
12 |
{
|
13 |
-
public static function arePathsUsedInHTAccessOutdated() {
|
14 |
-
if (!Config::isConfigFileThere()) {
|
15 |
-
// this properly means that rewrite rules have never been generated
|
16 |
-
return false;
|
17 |
-
}
|
18 |
-
|
19 |
-
$pathsGoingToBeUsedInHtaccess = [
|
20 |
-
'wod-url-path' => Paths::getWodUrlPath(),
|
21 |
-
];
|
22 |
-
|
23 |
-
$config = Config::loadConfig();
|
24 |
-
if ($config === false) {
|
25 |
-
// corrupt or not readable
|
26 |
-
return true;
|
27 |
-
}
|
28 |
-
|
29 |
-
foreach ($config['paths-used-in-htaccess'] as $prop => $value) {
|
30 |
-
if (isset($pathsGoingToBeUsedInHtaccess[$prop])) {
|
31 |
-
if ($value != $pathsGoingToBeUsedInHtaccess[$prop]) {
|
32 |
-
return true;
|
33 |
-
}
|
34 |
-
}
|
35 |
-
}
|
36 |
-
}
|
37 |
-
|
38 |
-
public static function doesRewriteRulesNeedUpdate($newConfig) {
|
39 |
-
if (!Config::isConfigFileThere()) {
|
40 |
-
// this properly means that rewrite rules have never been generated
|
41 |
-
return true;
|
42 |
-
}
|
43 |
-
|
44 |
-
$oldConfig = Config::loadConfig();
|
45 |
-
if ($oldConfig === false) {
|
46 |
-
// corrupt or not readable
|
47 |
-
return true;
|
48 |
-
}
|
49 |
|
50 |
-
$propsToCompare = [
|
51 |
-
'forward-query-string' => true,
|
52 |
-
'image-types' => 1,
|
53 |
-
'redirect-to-existing-in-htaccess' => false,
|
54 |
-
'only-redirect-to-converter-on-cache-miss' => false,
|
55 |
-
'success-response' => 'converted',
|
56 |
-
'cache-control' => 'no-header',
|
57 |
-
'cache-control-custom' => 'public, max-age:3600',
|
58 |
-
'cache-control-max-age' => 'one-week',
|
59 |
-
'cache-control-public' => true,
|
60 |
-
'enable-redirection-to-webp-realizer' => false,
|
61 |
-
'enable-redirection-to-converter' => true,
|
62 |
-
'destination-folder' => 'separate',
|
63 |
-
'destination-extension' => 'append',
|
64 |
-
'destination-structure' => 'doc-root',
|
65 |
-
'scope' => ['themes', 'uploads']
|
66 |
-
];
|
67 |
-
|
68 |
-
/*
|
69 |
-
if (isset($newConfig['redirect-to-existing-in-htaccess']) && $newConfig['redirect-to-existing-in-htaccess']) {
|
70 |
-
$propsToCompare['destination-folder'] = 'separate';
|
71 |
-
$propsToCompare['destination-extension'] = 'append';
|
72 |
-
$propsToCompare['destination-structure'] = 'doc-root';
|
73 |
-
}*/
|
74 |
-
|
75 |
-
foreach ($propsToCompare as $prop => $behaviourBeforeIntroduced) {
|
76 |
-
if (!isset($newConfig[$prop])) {
|
77 |
-
continue;
|
78 |
-
}
|
79 |
-
if (!isset($oldConfig[$prop])) {
|
80 |
-
// Do not trigger .htaccess update if the new value results
|
81 |
-
// in same old behaviour (before this option was introduced)
|
82 |
-
if ($newConfig[$prop] == $behaviourBeforeIntroduced) {
|
83 |
-
continue;
|
84 |
-
} else {
|
85 |
-
// Otherwise DO trigger .htaccess update
|
86 |
-
return true;
|
87 |
-
}
|
88 |
-
}
|
89 |
-
if ($newConfig[$prop] != $oldConfig[$prop]) {
|
90 |
-
return true;
|
91 |
-
}
|
92 |
-
}
|
93 |
-
|
94 |
-
if (!isset($oldConfig['paths-used-in-htaccess'])) {
|
95 |
-
return true;
|
96 |
-
}
|
97 |
-
|
98 |
-
return self::arePathsUsedInHTAccessOutdated();
|
99 |
-
}
|
100 |
|
101 |
/**
|
102 |
* Must be parsed ie "wp-content", "index", etc. Not real dirs
|
10 |
|
11 |
class HTAccess
|
12 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
/**
|
16 |
* Must be parsed ie "wp-content", "index", etc. Not real dirs
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
This functionality will be moved to a separate project.
|
4 |
+
|
5 |
+
Btw:
|
6 |
+
Seems someone else got similar idea:
|
7 |
+
http://christian.roy.name/blog/detecting-modrewrite-using-php
|
8 |
+
*/
|
9 |
+
namespace WebPExpress;
|
10 |
+
|
11 |
+
use \WebPExpress\FileHelper;
|
12 |
+
use \WebPExpress\Paths;
|
13 |
+
|
14 |
+
use \HtaccessCapabilityTester\HtaccessCapabilityTester;
|
15 |
+
|
16 |
+
include_once WEBPEXPRESS_PLUGIN_DIR . '/vendor/autoload.php';
|
17 |
+
|
18 |
+
class HTAccessCapabilityTestRunner
|
19 |
+
{
|
20 |
+
|
21 |
+
public static $cachedResults;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Tests if a test script responds with "pong"
|
25 |
+
*/
|
26 |
+
private static function canRunPingPongTestScript($url)
|
27 |
+
{
|
28 |
+
$response = wp_remote_get($url, ['timeout' => 10]);
|
29 |
+
//echo '<pre>' . print_r($response, true) . '</pre>';
|
30 |
+
if (wp_remote_retrieve_response_code($response) != '200') {
|
31 |
+
return false;
|
32 |
+
}
|
33 |
+
$body = wp_remote_retrieve_body($response);
|
34 |
+
return ($body == 'pong');
|
35 |
+
}
|
36 |
+
|
37 |
+
private static function runNamedTest($testName)
|
38 |
+
{
|
39 |
+
switch ($testName) {
|
40 |
+
case 'canRunTestScriptInWOD':
|
41 |
+
$url = Paths::getWebPExpressPluginUrl() . '/wod/ping.php';
|
42 |
+
return self::canRunPingPongTestScript($url);
|
43 |
+
|
44 |
+
case 'canRunTestScriptInWOD2':
|
45 |
+
$url = Paths::getWebPExpressPluginUrl() . '/wod2/ping.php';
|
46 |
+
return self::canRunPingPongTestScript($url);
|
47 |
+
|
48 |
+
case 'htaccessEnabled':
|
49 |
+
return self::runTestInWebPExpressContentDir('htaccessEnabled');
|
50 |
+
|
51 |
+
case 'modHeadersLoaded':
|
52 |
+
return self::runTestInWebPExpressContentDir('modHeadersLoaded');
|
53 |
+
|
54 |
+
case 'modHeaderWorking':
|
55 |
+
return self::runTestInWebPExpressContentDir('headerSetWorks');
|
56 |
+
|
57 |
+
case 'modRewriteWorking':
|
58 |
+
return self::runTestInWebPExpressContentDir('rewriteWorks');
|
59 |
+
|
60 |
+
case 'passThroughEnvWorking':
|
61 |
+
return self::runTestInWebPExpressContentDir('passingInfoFromRewriteToScriptThroughEnvWorks');
|
62 |
+
|
63 |
+
case 'passThroughHeaderWorking':
|
64 |
+
// pretend it fails because .htaccess rules aren't currently generated correctly
|
65 |
+
return false;
|
66 |
+
return self::runTestInWebPExpressContentDir('passingInfoFromRewriteToScriptThroughRequestHeaderWorks');
|
67 |
+
|
68 |
+
case 'grantAllAllowed':
|
69 |
+
return self::runTestInWebPExpressContentDir('grantAllCrashTester');
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
private static function runOrGetCached($testName)
|
74 |
+
{
|
75 |
+
if (!isset(self::$cachedResults)) {
|
76 |
+
self::$cachedResults = [];
|
77 |
+
}
|
78 |
+
if (!isset(self::$cachedResults[$testName])) {
|
79 |
+
self::$cachedResults[$testName] = self::runNamedTest($testName);
|
80 |
+
}
|
81 |
+
return self::$cachedResults[$testName];
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Run one of the htaccess capability tests.
|
86 |
+
* Three possible outcomes: true, false or null (null if request fails)
|
87 |
+
*/
|
88 |
+
private static function runTestInWebPExpressContentDir($testName)
|
89 |
+
{
|
90 |
+
$baseDir = Paths::getWebPExpressContentDirAbs() . '/htaccess-capability-tests';
|
91 |
+
$baseUrl = Paths::getContentUrl() . '/webp-express/htaccess-capability-tests';
|
92 |
+
|
93 |
+
$hct = new HtaccessCapabilityTester($baseDir, $baseUrl);
|
94 |
+
$hct->setHttpRequester(new WPHttpRequester());
|
95 |
+
|
96 |
+
try {
|
97 |
+
switch ($testName) {
|
98 |
+
case 'htaccessEnabled':
|
99 |
+
return $hct->htaccessEnabled();
|
100 |
+
case 'rewriteWorks':
|
101 |
+
return $hct->rewriteWorks();
|
102 |
+
case 'addTypeWorks':
|
103 |
+
return $hct->addTypeWorks();
|
104 |
+
case 'modHeadersLoaded':
|
105 |
+
return $hct->moduleLoaded('headers');
|
106 |
+
case 'headerSetWorks':
|
107 |
+
return $hct->headerSetWorks();
|
108 |
+
case 'requestHeaderWorks':
|
109 |
+
return $hct->requestHeaderWorks();
|
110 |
+
case 'passingInfoFromRewriteToScriptThroughRequestHeaderWorks':
|
111 |
+
return $hct->passingInfoFromRewriteToScriptThroughRequestHeaderWorks();
|
112 |
+
case 'passingInfoFromRewriteToScriptThroughEnvWorks':
|
113 |
+
return $hct->passingInfoFromRewriteToScriptThroughEnvWorks();
|
114 |
+
case 'grantAllCrashTester':
|
115 |
+
$rules = <<<'EOD'
|
116 |
+
<FilesMatch "(webp-on-demand\.php|webp-realizer\.php|ping\.php|ping\.txt)$">
|
117 |
+
<IfModule !mod_authz_core.c>
|
118 |
+
Order deny,allow
|
119 |
+
Allow from all
|
120 |
+
</IfModule>
|
121 |
+
<IfModule mod_authz_core.c>
|
122 |
+
Require all granted
|
123 |
+
</IfModule>
|
124 |
+
</FilesMatch>
|
125 |
+
EOD;
|
126 |
+
return $hct->crashTest($rules, 'grant-all');
|
127 |
+
}
|
128 |
+
|
129 |
+
} catch (\Exception $e) {
|
130 |
+
return null;
|
131 |
+
}
|
132 |
+
//error_log('test: ' . $testName . ':' . (($testResult === true) ? 'ok' : ($testResult === false ? 'failed' : 'hm')));
|
133 |
+
|
134 |
+
throw new \Exception('Unknown test:' . $testName);
|
135 |
+
}
|
136 |
+
|
137 |
+
|
138 |
+
public static function modRewriteWorking()
|
139 |
+
{
|
140 |
+
return self::runOrGetCached('modRewriteWorking');
|
141 |
+
}
|
142 |
+
|
143 |
+
public static function htaccessEnabled()
|
144 |
+
{
|
145 |
+
return self::runOrGetCached('htaccessEnabled');
|
146 |
+
}
|
147 |
+
|
148 |
+
public static function modHeadersLoaded()
|
149 |
+
{
|
150 |
+
return self::runOrGetCached('modHeadersLoaded');
|
151 |
+
}
|
152 |
+
|
153 |
+
public static function modHeaderWorking()
|
154 |
+
{
|
155 |
+
return self::runOrGetCached('modHeaderWorking');
|
156 |
+
}
|
157 |
+
|
158 |
+
public static function passThroughEnvWorking()
|
159 |
+
{
|
160 |
+
return self::runOrGetCached('passThroughEnvWorking');
|
161 |
+
}
|
162 |
+
|
163 |
+
public static function passThroughHeaderWorking()
|
164 |
+
{
|
165 |
+
return self::runOrGetCached('passThroughHeaderWorking');
|
166 |
+
}
|
167 |
+
|
168 |
+
public static function grantAllAllowed()
|
169 |
+
{
|
170 |
+
return self::runOrGetCached('grantAllAllowed');
|
171 |
+
}
|
172 |
+
|
173 |
+
public static function canRunTestScriptInWOD()
|
174 |
+
{
|
175 |
+
return self::runOrGetCached('canRunTestScriptInWOD');
|
176 |
+
}
|
177 |
+
|
178 |
+
public static function canRunTestScriptInWOD2()
|
179 |
+
{
|
180 |
+
return self::runOrGetCached('canRunTestScriptInWOD2');
|
181 |
+
}
|
182 |
+
|
183 |
+
|
184 |
+
}
|
@@ -20,11 +20,145 @@ class HTAccessRules
|
|
20 |
private static $passThroughHeaderDefinitelyAvailable;
|
21 |
private static $passThroughEnvVarDefinitelyUnavailable;
|
22 |
private static $passThroughEnvVarDefinitelyAvailable;
|
|
|
|
|
23 |
private static $capTests;
|
24 |
-
private static $
|
25 |
private static $dirContainsSourceImages;
|
26 |
private static $dirContainsWebPImages;
|
|
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
/**
|
30 |
* @return string Info in comments.
|
@@ -34,6 +168,7 @@ class HTAccessRules
|
|
34 |
|
35 |
return "# The rules below is a result of many parameters, including the following:\n" .
|
36 |
"#\n# WebP Express options:\n" .
|
|
|
37 |
"# - Redirection to existing webp: " .
|
38 |
(self::$config['redirect-to-existing-in-htaccess'] ? 'enabled' : 'disabled') . "\n" .
|
39 |
"# - Redirection to converter: " .
|
@@ -45,6 +180,7 @@ class HTAccessRules
|
|
45 |
"# - Destination extension: " . self::$config['destination-extension'] . "\n" .
|
46 |
"# - Destination structure: " . self::$config['destination-structure'] . (((self::$config['destination-structure'] == 'doc-root') && (!self::$useDocRootForStructuringCacheDir)) ? ' (overruled!)' : '') . "\n" .
|
47 |
"# - Image types: " . str_replace('?', '', implode(', ', self::$fileExtensions)) . "\n" .
|
|
|
48 |
|
49 |
"#\n# Wordpress/Server configuration:\n" .
|
50 |
'# - Document root availablity: ' . Paths::docRootStatusText() . "\n" .
|
@@ -166,18 +302,18 @@ class HTAccessRules
|
|
166 |
if (self::$config['destination-extension'] == 'append') {
|
167 |
$rules .= " RewriteCond %{REQUEST_FILENAME}.webp -f\n";
|
168 |
//$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp -f\n";
|
169 |
-
$rules .= " RewriteRule ^/?(.*)\.(" . self::$fileExt . ")$ $1.$2.webp [NC,T=image/webp,E=EXISTING:1," . (self::$
|
170 |
} else {
|
171 |
// extension: set to webp
|
172 |
|
173 |
//$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . self::$htaccessDirRelToDocRoot . "/$1.webp -f\n";
|
174 |
-
//$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . self::$fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1," . (self::$
|
175 |
|
176 |
// Got these new rules here: https://www.digitalocean.com/community/tutorials/how-to-create-and-serve-webp-images-to-speed-up-your-website
|
177 |
// (but are they actually better than the ones we use for append?)
|
178 |
$rules .= " RewriteCond %{REQUEST_URI} (?i)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
179 |
$rules .= " RewriteCond %{DOCUMENT_ROOT}%1\.webp -f\n";
|
180 |
-
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ %1\.webp [T=image/webp,E=EXISTING:1," . (self::$
|
181 |
|
182 |
// Instead of using REQUEST_URI, I can use REQUEST_FILENAME and remove DOCUMENT_ROOT
|
183 |
// I suppose REQUEST_URI is what was requested (ie "/wp-content/uploads/image.jpg").
|
@@ -193,7 +329,7 @@ class HTAccessRules
|
|
193 |
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
194 |
$rules .= " RewriteCond %1" . ($appendWebP ? "%2" : "") . "\.webp -f\n";
|
195 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ %1" . ($appendWebP ? "%2" : "") .
|
196 |
-
"\.webp [T=image/webp,E=EXISTING:1," . (self::$
|
197 |
|
198 |
}
|
199 |
|
@@ -218,7 +354,7 @@ class HTAccessRules
|
|
218 |
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
219 |
$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp -f\n";
|
220 |
$rules .= " RewriteRule ^/?(.+)\.(" . self::$fileExt . ")$ /" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot .
|
221 |
-
"/$1.$2.webp [NC,T=image/webp,E=EXISTING:1," . (self::$
|
222 |
|
223 |
} else {
|
224 |
// Make sure source image exists
|
@@ -241,7 +377,7 @@ class HTAccessRules
|
|
241 |
$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
242 |
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
243 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath .
|
244 |
-
" [T=image/webp,E=EXISTING:1," . (self::$
|
245 |
}
|
246 |
|
247 |
//$rules .= " RewriteRule ^\/?(.*)\.(" . self::$fileExt . ")$ /" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
|
@@ -303,7 +439,7 @@ class HTAccessRules
|
|
303 |
|
304 |
$rewriteRuleStart = '^/?(.+)';
|
305 |
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(webp)$ " .
|
306 |
-
"/" .
|
307 |
((count($params) > 0) ? "?" . implode('&', $params) : '') .
|
308 |
" [" . implode(',', $flags) . "]\n\n";
|
309 |
} else {
|
@@ -340,7 +476,7 @@ class HTAccessRules
|
|
340 |
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
341 |
|
342 |
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "\.webp$ " .
|
343 |
-
"/" .
|
344 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
345 |
" [" . implode(',', $flags) . "]\n";
|
346 |
|
@@ -378,7 +514,7 @@ class HTAccessRules
|
|
378 |
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
379 |
|
380 |
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "\.webp$ " .
|
381 |
-
"/" .
|
382 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
383 |
" [" . implode(',', $flags) . "]\n\n";
|
384 |
*/
|
@@ -420,7 +556,7 @@ class HTAccessRules
|
|
420 |
}
|
421 |
|
422 |
$rules .= " RewriteRule (?i).*\.webp$ " .
|
423 |
-
"/" .
|
424 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
425 |
" [" . implode(',', $flags) . "]\n\n";
|
426 |
*/
|
@@ -496,7 +632,7 @@ class HTAccessRules
|
|
496 |
// TODO: When $rewriteRuleStart is empty, we don't need the .*, do we? - test
|
497 |
$rewriteRuleStart = '^/?(.+)';
|
498 |
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . self::$fileExt . ")$ " .
|
499 |
-
"/" .
|
500 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
501 |
" [" . implode(',', $flags) . "]\n";
|
502 |
|
@@ -537,7 +673,7 @@ class HTAccessRules
|
|
537 |
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
538 |
|
539 |
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "$ " .
|
540 |
-
"/" .
|
541 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
542 |
" [" . implode(',', $flags) . "]\n";
|
543 |
|
@@ -573,7 +709,7 @@ class HTAccessRules
|
|
573 |
$params[] = 'xsource-rel-to-root-id=x%2' . (self::$appendWebP ? "%3" : "");
|
574 |
|
575 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " .
|
576 |
-
"/" .
|
577 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
578 |
" [" . implode(',', $flags) . "]\n";
|
579 |
*/
|
@@ -598,7 +734,7 @@ class HTAccessRules
|
|
598 |
self::$htaccessDirAbs . "/)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
599 |
|
600 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " .
|
601 |
-
"/" .
|
602 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
603 |
" [" . implode(',', $flags) . "]\n";
|
604 |
|
@@ -606,7 +742,7 @@ class HTAccessRules
|
|
606 |
// $urlPath = '/' . Paths::getUrlPathById(self::$htaccessDir) . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
607 |
//$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
608 |
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
609 |
-
//$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath ." [T=image/webp,E=EXISTING:1," . (self::$
|
610 |
*/
|
611 |
|
612 |
|
@@ -621,7 +757,7 @@ class HTAccessRules
|
|
621 |
$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
622 |
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
623 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath .
|
624 |
-
" [T=image/webp,E=EXISTING:1," . (self::$
|
625 |
*/
|
626 |
|
627 |
/*
|
@@ -659,6 +795,7 @@ class HTAccessRules
|
|
659 |
|
660 |
// Fix config.
|
661 |
$defaults = [
|
|
|
662 |
'enable-redirection-to-converter' => true,
|
663 |
'forward-query-string' => true,
|
664 |
'image-types' => 1,
|
@@ -673,16 +810,42 @@ class HTAccessRules
|
|
673 |
$config = array_merge($defaults, $config);
|
674 |
|
675 |
if (!isset($config['base-htaccess-on-these-capability-tests'])) {
|
676 |
-
$config['base-htaccess-on-these-capability-tests'] = Config::runAndStoreCapabilityTests();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
677 |
}
|
|
|
678 |
self::$config = $config;
|
679 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
680 |
$capTests = self::$config['base-htaccess-on-these-capability-tests'];
|
|
|
|
|
681 |
self::$modHeaderDefinitelyUnavailable = ($capTests['modHeaderWorking'] === false);
|
682 |
self::$passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
|
683 |
self::$passThroughHeaderDefinitelyAvailable = ($capTests['passThroughHeaderWorking'] === true);
|
684 |
-
self::$
|
685 |
-
self::$
|
|
|
|
|
686 |
self::$capTests = $capTests;
|
687 |
|
688 |
self::$imageTypes = self::$config['image-types'];
|
@@ -703,6 +866,21 @@ class HTAccessRules
|
|
703 |
self::$appendWebP = !$setWebPExt;
|
704 |
}
|
705 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
706 |
public static function addVaryHeaderEnvRules($indent = 0)
|
707 |
{
|
708 |
$rules = [];
|
@@ -743,13 +921,44 @@ class HTAccessRules
|
|
743 |
return implode("\n", $rules);
|
744 |
}
|
745 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
746 |
// https://stackoverflow.com/questions/34124819/mod-rewrite-set-custom-header-through-htaccess
|
|
|
|
|
|
|
|
|
|
|
|
|
747 |
public static function generateHTAccessRulesFromConfigObj($config, $htaccessDir = 'index', $dirContainsSourceImages = true, $dirContainsWebPImages = true)
|
748 |
{
|
749 |
self::setInternalProperties($config, $htaccessDir);
|
750 |
self::$dirContainsSourceImages = $dirContainsSourceImages;
|
751 |
self::$dirContainsWebPImages = $dirContainsWebPImages;
|
752 |
|
|
|
753 |
if (
|
754 |
(!self::$config['enable-redirection-to-converter']) &&
|
755 |
(!self::$config['redirect-to-existing-in-htaccess']) &&
|
@@ -757,22 +966,44 @@ class HTAccessRules
|
|
757 |
) {
|
758 |
return '# WebP Express does not need to write any rules (it has not been set up to redirect to converter, nor' .
|
759 |
' to existing webp, and the "convert non-existing webp-files upon request" option has not been enabled)';
|
760 |
-
}
|
761 |
|
762 |
if (self::$imageTypes == 0) {
|
763 |
return '# WebP Express disabled (no image types has been choosen to be converted/redirected)';
|
764 |
}
|
765 |
|
766 |
-
self::$
|
767 |
if (self::$modHeaderDefinitelyUnavailable) {
|
768 |
-
self::$
|
769 |
}
|
770 |
|
771 |
/* Build rules */
|
772 |
$rules = '';
|
773 |
$rules .= self::infoRules();
|
774 |
|
775 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
776 |
$rules .= "# Rules for handling requests for source images\n";
|
777 |
$rules .= "# ---------------------------------------------\n\n";
|
778 |
$rules .= "<IfModule mod_rewrite.c>\n" .
|
@@ -786,11 +1017,8 @@ class HTAccessRules
|
|
786 |
$rules .= self::webpOnDemandRules();
|
787 |
}
|
788 |
|
789 |
-
//if (self::$
|
790 |
-
if (
|
791 |
-
(self::$config['redirect-to-existing-in-htaccess']) ||
|
792 |
-
(self::$config['enable-redirection-to-converter'])
|
793 |
-
) {
|
794 |
$rules .= " # Make sure that browsers which does not support webp also gets the Vary:Accept header\n" .
|
795 |
" # when requesting images that would be redirected to webp on browsers that does.\n";
|
796 |
|
@@ -807,10 +1035,10 @@ class HTAccessRules
|
|
807 |
" </IfModule>\n\n";
|
808 |
*/
|
809 |
|
810 |
-
//self::$
|
811 |
|
812 |
/*
|
813 |
-
if (self::$
|
814 |
if ($dirContainsWebPImages) {
|
815 |
$rules .= self::addVaryHeaderEnvRules(2);
|
816 |
}
|
@@ -835,7 +1063,22 @@ class HTAccessRules
|
|
835 |
(self::$config['redirect-to-existing-in-htaccess'])
|
836 |
) {
|
837 |
}*/
|
838 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
839 |
|
840 |
$rules .= "\n# Register webp mime type \n";
|
841 |
$rules .= "<IfModule mod_mime.c>\n";
|
20 |
private static $passThroughHeaderDefinitelyAvailable;
|
21 |
private static $passThroughEnvVarDefinitelyUnavailable;
|
22 |
private static $passThroughEnvVarDefinitelyAvailable;
|
23 |
+
private static $canDefinitelyRunTestScriptInWOD;
|
24 |
+
private static $canDefinitelyRunTestScriptInWOD2;
|
25 |
private static $capTests;
|
26 |
+
private static $setAddVaryEnvInRedirect;
|
27 |
private static $dirContainsSourceImages;
|
28 |
private static $dirContainsWebPImages;
|
29 |
+
private static $alterHtmlEnabled;
|
30 |
|
31 |
+
private static function trueFalseNullString($var)
|
32 |
+
{
|
33 |
+
if ($var === true) {
|
34 |
+
return 'yes';
|
35 |
+
}
|
36 |
+
if ($var === false) {
|
37 |
+
return 'no';
|
38 |
+
}
|
39 |
+
return 'could not be determined';
|
40 |
+
}
|
41 |
+
|
42 |
+
public static function arePathsUsedInHTAccessOutdated() {
|
43 |
+
if (!Config::isConfigFileThere()) {
|
44 |
+
// this properly means that rewrite rules have never been generated
|
45 |
+
return false;
|
46 |
+
}
|
47 |
+
|
48 |
+
$oldConfig = Config::loadConfig();
|
49 |
+
if ($oldConfig === false) {
|
50 |
+
// corrupt or not readable
|
51 |
+
return true;
|
52 |
+
}
|
53 |
+
|
54 |
+
return self::arePathsUsedInHTAccessOutdated2($oldConfig);
|
55 |
+
}
|
56 |
+
|
57 |
+
private static function arePathsUsedInHTAccessOutdated2($oldConfig)
|
58 |
+
{
|
59 |
+
if (!isset($oldConfig['paths-used-in-htaccess'])) {
|
60 |
+
return true;
|
61 |
+
}
|
62 |
+
|
63 |
+
$pathsGoingToBeUsedInHtaccess = [
|
64 |
+
'wod-url-path' => Paths::getWodUrlPath(),
|
65 |
+
];
|
66 |
+
foreach ($oldConfig['paths-used-in-htaccess'] as $prop => $value) {
|
67 |
+
if (isset($pathsGoingToBeUsedInHtaccess[$prop])) {
|
68 |
+
if ($value != $pathsGoingToBeUsedInHtaccess[$prop]) {
|
69 |
+
return true;
|
70 |
+
}
|
71 |
+
}
|
72 |
+
}
|
73 |
+
return false;
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Decides if .htaccess rules needs to be updated.
|
78 |
+
*
|
79 |
+
* The result is positive under these circumstances:
|
80 |
+
* - If there is no existing config.json (it must mean that there are no rules, and so they need "updating")
|
81 |
+
* - If existing config.json is corrupt or not readable
|
82 |
+
* - The new config.json is compared to the old. If any of the properties that will affect the .htaccess has
|
83 |
+
* changed, well, it needs updating. Also, if there is a new property, it needs update, unless the
|
84 |
+
* value of that new property would not have any effect
|
85 |
+
* - If the url path to the "wod" folder has changed (actually, to wod/webp-on-demand, but if one of these changes, so does the other)
|
86 |
+
* - TODO: Should we not also compare (some of) the capability tests?
|
87 |
+
* - TODO: Should we not also compare some paths, ie. Paths::getContentDirRelToPluginDir(), which is used in
|
88 |
+
* HTAccessRules::webpRealizerRules()
|
89 |
+
* - TODO: Some changes would not really require regeneration. We could do a more fine-grained
|
90 |
+
* check. For example, many changes matters not (ie "wod-url-path") if redirection to
|
91 |
+
* both webp-realizer and webp-on-demand are disabled.
|
92 |
+
*
|
93 |
+
*/
|
94 |
+
public static function doesRewriteRulesNeedUpdate($newConfig) {
|
95 |
+
if (!Config::isConfigFileThere()) {
|
96 |
+
// this properly means that rewrite rules have never been generated
|
97 |
+
return true;
|
98 |
+
}
|
99 |
+
|
100 |
+
$oldConfig = Config::loadConfig();
|
101 |
+
if ($oldConfig === false) {
|
102 |
+
// corrupt or not readable
|
103 |
+
return true;
|
104 |
+
}
|
105 |
+
|
106 |
+
// $propsToCompare is set like this:
|
107 |
+
// Keys: properties that should trigger .htaccess update if changed
|
108 |
+
// Values: The behaviour before that property was intruduced
|
109 |
+
$propsToCompare = [
|
110 |
+
'forward-query-string' => true,
|
111 |
+
'image-types' => 1,
|
112 |
+
'redirect-to-existing-in-htaccess' => false,
|
113 |
+
'only-redirect-to-converter-on-cache-miss' => false,
|
114 |
+
'success-response' => 'converted',
|
115 |
+
'cache-control' => 'no-header',
|
116 |
+
'cache-control-custom' => 'public, max-age:3600',
|
117 |
+
'cache-control-max-age' => 'one-week',
|
118 |
+
'cache-control-public' => true,
|
119 |
+
'enable-redirection-to-webp-realizer' => false,
|
120 |
+
'enable-redirection-to-converter' => true,
|
121 |
+
'destination-folder' => 'separate',
|
122 |
+
'destination-extension' => 'append',
|
123 |
+
'destination-structure' => 'doc-root',
|
124 |
+
'scope' => ['themes', 'uploads']
|
125 |
+
];
|
126 |
+
|
127 |
+
// If one of the props have changed, we need to update.
|
128 |
+
// And we also need update if one of them doesn't exist in the old config, unless
|
129 |
+
// the new property/value has same effect as before the property was introduced.
|
130 |
+
foreach ($propsToCompare as $prop => $behaviourBeforeIntroduced) {
|
131 |
+
if (!isset($newConfig[$prop])) {
|
132 |
+
continue;
|
133 |
+
}
|
134 |
+
if (!isset($oldConfig[$prop])) {
|
135 |
+
// Do not trigger .htaccess update if the new value results
|
136 |
+
// in same old behaviour (before this option was introduced)
|
137 |
+
if ($newConfig[$prop] == $behaviourBeforeIntroduced) {
|
138 |
+
continue;
|
139 |
+
} else {
|
140 |
+
// Otherwise DO trigger .htaccess update
|
141 |
+
return true;
|
142 |
+
}
|
143 |
+
}
|
144 |
+
if ($newConfig[$prop] != $oldConfig[$prop]) {
|
145 |
+
return true;
|
146 |
+
}
|
147 |
+
}
|
148 |
+
|
149 |
+
/*
|
150 |
+
if (isset($newConfig['redirect-to-existing-in-htaccess']) && $newConfig['redirect-to-existing-in-htaccess']) {
|
151 |
+
$propsToCompare['destination-folder'] = 'separate';
|
152 |
+
$propsToCompare['destination-extension'] = 'append';
|
153 |
+
$propsToCompare['destination-structure'] = 'doc-root';
|
154 |
+
}*/
|
155 |
+
|
156 |
+
|
157 |
+
if (self::arePathsUsedInHTAccessOutdated2($oldConfig)) {
|
158 |
+
return true;
|
159 |
+
}
|
160 |
+
return false;
|
161 |
+
}
|
162 |
|
163 |
/**
|
164 |
* @return string Info in comments.
|
168 |
|
169 |
return "# The rules below is a result of many parameters, including the following:\n" .
|
170 |
"#\n# WebP Express options:\n" .
|
171 |
+
"# - Operation mode: " . self::$config['operation-mode'] . "\n" .
|
172 |
"# - Redirection to existing webp: " .
|
173 |
(self::$config['redirect-to-existing-in-htaccess'] ? 'enabled' : 'disabled') . "\n" .
|
174 |
"# - Redirection to converter: " .
|
180 |
"# - Destination extension: " . self::$config['destination-extension'] . "\n" .
|
181 |
"# - Destination structure: " . self::$config['destination-structure'] . (((self::$config['destination-structure'] == 'doc-root') && (!self::$useDocRootForStructuringCacheDir)) ? ' (overruled!)' : '') . "\n" .
|
182 |
"# - Image types: " . str_replace('?', '', implode(', ', self::$fileExtensions)) . "\n" .
|
183 |
+
'# - Alter HTML enabled?: ' . (self::$alterHtmlEnabled ? 'yes' : 'no') . "\n" .
|
184 |
|
185 |
"#\n# Wordpress/Server configuration:\n" .
|
186 |
'# - Document root availablity: ' . Paths::docRootStatusText() . "\n" .
|
302 |
if (self::$config['destination-extension'] == 'append') {
|
303 |
$rules .= " RewriteCond %{REQUEST_FILENAME}.webp -f\n";
|
304 |
//$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp -f\n";
|
305 |
+
$rules .= " RewriteRule ^/?(.*)\.(" . self::$fileExt . ")$ $1.$2.webp [NC,T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
306 |
} else {
|
307 |
// extension: set to webp
|
308 |
|
309 |
//$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . self::$htaccessDirRelToDocRoot . "/$1.webp -f\n";
|
310 |
+
//$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . self::$fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
311 |
|
312 |
// Got these new rules here: https://www.digitalocean.com/community/tutorials/how-to-create-and-serve-webp-images-to-speed-up-your-website
|
313 |
// (but are they actually better than the ones we use for append?)
|
314 |
$rules .= " RewriteCond %{REQUEST_URI} (?i)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
315 |
$rules .= " RewriteCond %{DOCUMENT_ROOT}%1\.webp -f\n";
|
316 |
+
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ %1\.webp [T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
317 |
|
318 |
// Instead of using REQUEST_URI, I can use REQUEST_FILENAME and remove DOCUMENT_ROOT
|
319 |
// I suppose REQUEST_URI is what was requested (ie "/wp-content/uploads/image.jpg").
|
329 |
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
330 |
$rules .= " RewriteCond %1" . ($appendWebP ? "%2" : "") . "\.webp -f\n";
|
331 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ %1" . ($appendWebP ? "%2" : "") .
|
332 |
+
"\.webp [T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
333 |
|
334 |
}
|
335 |
|
354 |
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
355 |
$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp -f\n";
|
356 |
$rules .= " RewriteRule ^/?(.+)\.(" . self::$fileExt . ")$ /" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot .
|
357 |
+
"/$1.$2.webp [NC,T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
358 |
|
359 |
} else {
|
360 |
// Make sure source image exists
|
377 |
$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
378 |
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
379 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath .
|
380 |
+
" [T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
381 |
}
|
382 |
|
383 |
//$rules .= " RewriteRule ^\/?(.*)\.(" . self::$fileExt . ")$ /" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
|
439 |
|
440 |
$rewriteRuleStart = '^/?(.+)';
|
441 |
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(webp)$ " .
|
442 |
+
"/" . self::getWebPRealizerUrlPath() .
|
443 |
((count($params) > 0) ? "?" . implode('&', $params) : '') .
|
444 |
" [" . implode(',', $flags) . "]\n\n";
|
445 |
} else {
|
476 |
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
477 |
|
478 |
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "\.webp$ " .
|
479 |
+
"/" . self::getWebPRealizerUrlPath() .
|
480 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
481 |
" [" . implode(',', $flags) . "]\n";
|
482 |
|
514 |
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
515 |
|
516 |
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "\.webp$ " .
|
517 |
+
"/" . self::getWebPRealizerUrlPath() .
|
518 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
519 |
" [" . implode(',', $flags) . "]\n\n";
|
520 |
*/
|
556 |
}
|
557 |
|
558 |
$rules .= " RewriteRule (?i).*\.webp$ " .
|
559 |
+
"/" . self::getWebPRealizerUrlPath() .
|
560 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
561 |
" [" . implode(',', $flags) . "]\n\n";
|
562 |
*/
|
632 |
// TODO: When $rewriteRuleStart is empty, we don't need the .*, do we? - test
|
633 |
$rewriteRuleStart = '^/?(.+)';
|
634 |
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . self::$fileExt . ")$ " .
|
635 |
+
"/" . self::getWodUrlPath() .
|
636 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
637 |
" [" . implode(',', $flags) . "]\n";
|
638 |
|
673 |
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
674 |
|
675 |
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "$ " .
|
676 |
+
"/" . self::getWodUrlPath() .
|
677 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
678 |
" [" . implode(',', $flags) . "]\n";
|
679 |
|
709 |
$params[] = 'xsource-rel-to-root-id=x%2' . (self::$appendWebP ? "%3" : "");
|
710 |
|
711 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " .
|
712 |
+
"/" . self::getWodUrlPath() .
|
713 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
714 |
" [" . implode(',', $flags) . "]\n";
|
715 |
*/
|
734 |
self::$htaccessDirAbs . "/)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
735 |
|
736 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " .
|
737 |
+
"/" . self::getWodUrlPath() .
|
738 |
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
739 |
" [" . implode(',', $flags) . "]\n";
|
740 |
|
742 |
// $urlPath = '/' . Paths::getUrlPathById(self::$htaccessDir) . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
743 |
//$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
744 |
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
745 |
+
//$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath ." [T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
746 |
*/
|
747 |
|
748 |
|
757 |
$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
758 |
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
759 |
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath .
|
760 |
+
" [T=image/webp,E=EXISTING:1," . (self::$setAddVaryEnvInRedirect ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
761 |
*/
|
762 |
|
763 |
/*
|
795 |
|
796 |
// Fix config.
|
797 |
$defaults = [
|
798 |
+
'operation-mode' => 'varied-image-responses',
|
799 |
'enable-redirection-to-converter' => true,
|
800 |
'forward-query-string' => true,
|
801 |
'image-types' => 1,
|
810 |
$config = array_merge($defaults, $config);
|
811 |
|
812 |
if (!isset($config['base-htaccess-on-these-capability-tests'])) {
|
813 |
+
$config['base-htaccess-on-these-capability-tests'] = Config::runAndStoreCapabilityTests($config);
|
814 |
+
}
|
815 |
+
// We currently accept that the following capability tests might not
|
816 |
+
// have been run (we did not want to force recreation of .htaccess because of these)
|
817 |
+
// - "modHeaderWorking"
|
818 |
+
// - "canRunTestScriptInWOD"
|
819 |
+
// - "canRunTestScriptInWOD2"
|
820 |
+
if (!isset($config['base-htaccess-on-these-capability-tests']['modHeaderWorking'])) {
|
821 |
+
$config['base-htaccess-on-these-capability-tests']['modHeaderWorking'] = HTAccessCapabilityTestRunner::modHeaderWorking();
|
822 |
+
}
|
823 |
+
if (!isset($config['base-htaccess-on-these-capability-tests']['canRunTestScriptInWOD'])) {
|
824 |
+
$config['base-htaccess-on-these-capability-tests']['canRunTestScriptInWOD'] = HTAccessCapabilityTestRunner::canRunTestScriptInWOD();
|
825 |
+
}
|
826 |
+
if (!isset($config['base-htaccess-on-these-capability-tests']['canRunTestScriptInWOD2'])) {
|
827 |
+
$config['base-htaccess-on-these-capability-tests']['canRunTestScriptInWOD2'] = HTAccessCapabilityTestRunner::canRunTestScriptInWOD2();
|
828 |
}
|
829 |
+
|
830 |
self::$config = $config;
|
831 |
|
832 |
+
|
833 |
+
if (!isset($config['alter-html']['enabled'])) {
|
834 |
+
self::$alterHtmlEnabled = false;
|
835 |
+
} else {
|
836 |
+
self::$alterHtmlEnabled = true;
|
837 |
+
}
|
838 |
+
|
839 |
$capTests = self::$config['base-htaccess-on-these-capability-tests'];
|
840 |
+
|
841 |
+
|
842 |
self::$modHeaderDefinitelyUnavailable = ($capTests['modHeaderWorking'] === false);
|
843 |
self::$passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
|
844 |
self::$passThroughHeaderDefinitelyAvailable = ($capTests['passThroughHeaderWorking'] === true);
|
845 |
+
self::$canDefinitelyRunTestScriptInWOD = ($capTests['canRunTestScriptInWOD'] === true);
|
846 |
+
self::$canDefinitelyRunTestScriptInWOD2 = ($capTests['canRunTestScriptInWOD2'] === true);
|
847 |
+
|
848 |
+
|
849 |
self::$capTests = $capTests;
|
850 |
|
851 |
self::$imageTypes = self::$config['image-types'];
|
866 |
self::$appendWebP = !$setWebPExt;
|
867 |
}
|
868 |
|
869 |
+
public static function addVaryHeaderRules()
|
870 |
+
{
|
871 |
+
$rules = [
|
872 |
+
'# Add "Vary: Accept" header in order to make proxies aware that the response varies depending',
|
873 |
+
'# on the "accept" request header (which is the one browsers use to signal if they support webp).',
|
874 |
+
'# In this folder, there are only webp files, so there is no need for any other logic than the ',
|
875 |
+
'# check which ensures that mod_headers is available.',
|
876 |
+
'<IfModule mod_headers.c>',
|
877 |
+
' Header append "Vary" "Accept"',
|
878 |
+
'</IfModule>',
|
879 |
+
''
|
880 |
+
];
|
881 |
+
return implode("\n", $rules);
|
882 |
+
}
|
883 |
+
|
884 |
public static function addVaryHeaderEnvRules($indent = 0)
|
885 |
{
|
886 |
$rules = [];
|
921 |
return implode("\n", $rules);
|
922 |
}
|
923 |
|
924 |
+
private static function getWodUrlPath()
|
925 |
+
{
|
926 |
+
// We prefer the "wod" folder over "wod2" (when it works), simply because it is older
|
927 |
+
// and we should not change things without having a good reason.
|
928 |
+
if (self::$canDefinitelyRunTestScriptInWOD) {
|
929 |
+
return Paths::getWodUrlPath();
|
930 |
+
}
|
931 |
+
|
932 |
+
// We however prefer the "wod2" folder when "wod" does not work, even if
|
933 |
+
// "wod2" doesn't work either. Why? Less things can go wrong in "wod2", so trying to fix
|
934 |
+
// it should be more straight forward.
|
935 |
+
return Paths::getWod2UrlPath();
|
936 |
+
}
|
937 |
+
|
938 |
+
private static function getWebPRealizerUrlPath()
|
939 |
+
{
|
940 |
+
// We prefer the "wod" folder over "wod2", simply because it is older
|
941 |
+
// and we should not change things without having a good reason.
|
942 |
+
if (self::$canDefinitelyRunTestScriptInWOD) {
|
943 |
+
return Paths::getWebPRealizerUrlPath();
|
944 |
+
}
|
945 |
+
return Paths::getWebPRealizer2UrlPath();
|
946 |
+
}
|
947 |
+
|
948 |
// https://stackoverflow.com/questions/34124819/mod-rewrite-set-custom-header-through-htaccess
|
949 |
+
/**
|
950 |
+
*
|
951 |
+
* PS: $config has a property "base-htaccess-on-these-capability-tests", which will be used.
|
952 |
+
* make sure that this is up-to-date before calling this method.
|
953 |
+
* It is updated with $config->runAndStoreCapabilityTests()
|
954 |
+
*/
|
955 |
public static function generateHTAccessRulesFromConfigObj($config, $htaccessDir = 'index', $dirContainsSourceImages = true, $dirContainsWebPImages = true)
|
956 |
{
|
957 |
self::setInternalProperties($config, $htaccessDir);
|
958 |
self::$dirContainsSourceImages = $dirContainsSourceImages;
|
959 |
self::$dirContainsWebPImages = $dirContainsWebPImages;
|
960 |
|
961 |
+
/*
|
962 |
if (
|
963 |
(!self::$config['enable-redirection-to-converter']) &&
|
964 |
(!self::$config['redirect-to-existing-in-htaccess']) &&
|
966 |
) {
|
967 |
return '# WebP Express does not need to write any rules (it has not been set up to redirect to converter, nor' .
|
968 |
' to existing webp, and the "convert non-existing webp-files upon request" option has not been enabled)';
|
969 |
+
}*/
|
970 |
|
971 |
if (self::$imageTypes == 0) {
|
972 |
return '# WebP Express disabled (no image types has been choosen to be converted/redirected)';
|
973 |
}
|
974 |
|
975 |
+
self::$setAddVaryEnvInRedirect = self::$config['redirect-to-existing-in-htaccess'];
|
976 |
if (self::$modHeaderDefinitelyUnavailable) {
|
977 |
+
self::$setAddVaryEnvInRedirect = false;
|
978 |
}
|
979 |
|
980 |
/* Build rules */
|
981 |
$rules = '';
|
982 |
$rules .= self::infoRules();
|
983 |
|
984 |
+
$variedImageResponses =
|
985 |
+
(self::$config['redirect-to-existing-in-htaccess']) ||
|
986 |
+
(self::$config['enable-redirection-to-converter']);
|
987 |
+
|
988 |
+
$addVaryHeaderUsingModHeader = $variedImageResponses;
|
989 |
+
|
990 |
+
/*
|
991 |
+
TODO: (#447)
|
992 |
+
We should not add the "Header append" code if it is disallowed
|
993 |
+
in the server config (ie if "FileInfo" isn't in the AllowOverride list)
|
994 |
+
Why? Well, it will result in 500 internal error on the image requests
|
995 |
+
(or errors in the log, if configured to "NonFatal")
|
996 |
+
.. But this requires a bit of effort, as it might be that it is allowed
|
997 |
+
in some dirs but not in others.
|
998 |
+
If mod_headers simply isn't installed, the system behaves fine (thanks to
|
999 |
+
the "IfModule" directive. So we should actually add the code, when that is
|
1000 |
+
the case (as the server setting might change for the better)
|
1001 |
+
|
1002 |
+
if (self::$modHeaderDefinitelyUnavailable) {
|
1003 |
+
//$addVaryHeaderUsingModHeader = false;
|
1004 |
+
}*/
|
1005 |
+
|
1006 |
+
if ($dirContainsSourceImages && $variedImageResponses) {
|
1007 |
$rules .= "# Rules for handling requests for source images\n";
|
1008 |
$rules .= "# ---------------------------------------------\n\n";
|
1009 |
$rules .= "<IfModule mod_rewrite.c>\n" .
|
1017 |
$rules .= self::webpOnDemandRules();
|
1018 |
}
|
1019 |
|
1020 |
+
//if (self::$setAddVaryEnvInRedirect) {
|
1021 |
+
if ($addVaryHeaderUsingModHeader) {
|
|
|
|
|
|
|
1022 |
$rules .= " # Make sure that browsers which does not support webp also gets the Vary:Accept header\n" .
|
1023 |
" # when requesting images that would be redirected to webp on browsers that does.\n";
|
1024 |
|
1035 |
" </IfModule>\n\n";
|
1036 |
*/
|
1037 |
|
1038 |
+
//self::$setAddVaryEnvInRedirect = (self::$config['enable-redirection-to-converter'] && (self::$config['success-response'] == 'converted')) || (self::$config['redirect-to-existing-in-htaccess']);
|
1039 |
|
1040 |
/*
|
1041 |
+
if (self::$setAddVaryEnvInRedirect) {
|
1042 |
if ($dirContainsWebPImages) {
|
1043 |
$rules .= self::addVaryHeaderEnvRules(2);
|
1044 |
}
|
1063 |
(self::$config['redirect-to-existing-in-htaccess'])
|
1064 |
) {
|
1065 |
}*/
|
1066 |
+
if ($addVaryHeaderUsingModHeader) {
|
1067 |
+
if (!$dirContainsSourceImages && !self::$alterHtmlEnabled) {
|
1068 |
+
// Simple rules for Vary:Accept #444
|
1069 |
+
// These can be used when we are sure that the webp in this folder is never
|
1070 |
+
// requested directly.
|
1071 |
+
// The simple rules are prefered when possible because they are more robust
|
1072 |
+
// and doesn't depend on mod_setenvif
|
1073 |
+
|
1074 |
+
// TODO: Use simple rules if it can be proved that mod_setenvif isn't working
|
1075 |
+
$rules .= self::addVaryHeaderRules();
|
1076 |
+
} else {
|
1077 |
+
// Advanced rules, which ensures that direct requests for webps doesn't get
|
1078 |
+
// Vary:Accept header added
|
1079 |
+
$rules .= self::addVaryHeaderEnvRules();
|
1080 |
+
}
|
1081 |
+
}
|
1082 |
|
1083 |
$rules .= "\n# Register webp mime type \n";
|
1084 |
$rules .= "<IfModule mod_mime.c>\n";
|
@@ -690,15 +690,46 @@ APACHE
|
|
690 |
return self::getUrlPathFromUrl(self::getWebPExpressPluginUrl());
|
691 |
}
|
692 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
693 |
public static function getWodUrlPath()
|
694 |
{
|
695 |
-
return
|
696 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
697 |
}
|
698 |
|
699 |
public static function getWebPRealizerUrlPath()
|
700 |
{
|
701 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
702 |
}
|
703 |
|
704 |
public static function getWebServiceUrl()
|
690 |
return self::getUrlPathFromUrl(self::getWebPExpressPluginUrl());
|
691 |
}
|
692 |
|
693 |
+
public static function getWodFolderUrlPath()
|
694 |
+
{
|
695 |
+
return
|
696 |
+
self::getWebPExpressPluginUrlPath() .
|
697 |
+
'/wod';
|
698 |
+
}
|
699 |
+
|
700 |
+
public static function getWod2FolderUrlPath()
|
701 |
+
{
|
702 |
+
return
|
703 |
+
self::getWebPExpressPluginUrlPath() .
|
704 |
+
'/wod2';
|
705 |
+
}
|
706 |
+
|
707 |
public static function getWodUrlPath()
|
708 |
{
|
709 |
+
return
|
710 |
+
self::getWodFolderUrlPath() .
|
711 |
+
'/webp-on-demand.php';
|
712 |
+
}
|
713 |
+
|
714 |
+
public static function getWod2UrlPath()
|
715 |
+
{
|
716 |
+
return
|
717 |
+
self::getWod2FolderUrlPath() .
|
718 |
+
'/webp-on-demand.php';
|
719 |
}
|
720 |
|
721 |
public static function getWebPRealizerUrlPath()
|
722 |
{
|
723 |
+
return
|
724 |
+
self::getWodFolderUrlPath() .
|
725 |
+
'/webp-realizer.php';
|
726 |
+
}
|
727 |
+
|
728 |
+
public static function getWebPRealizer2UrlPath()
|
729 |
+
{
|
730 |
+
return
|
731 |
+
self::getWod2FolderUrlPath() .
|
732 |
+
'/webp-realizer.php';
|
733 |
}
|
734 |
|
735 |
public static function getWebServiceUrl()
|
@@ -67,7 +67,8 @@ class PlatformInfo
|
|
67 |
// https://stackoverflow.com/questions/9021425/how-to-check-if-mod-rewrite-is-enabled-in-php
|
68 |
// But it does not seem to return all modules in my php-fpm setup.
|
69 |
|
70 |
-
// Currently we got no more tools
|
|
|
71 |
return null;
|
72 |
|
73 |
}
|
67 |
// https://stackoverflow.com/questions/9021425/how-to-check-if-mod-rewrite-is-enabled-in-php
|
68 |
// But it does not seem to return all modules in my php-fpm setup.
|
69 |
|
70 |
+
// Currently we got no more tools in this function...
|
71 |
+
// you might want to take a look at the "htaccess_capability_tester" library...
|
72 |
return null;
|
73 |
|
74 |
}
|
@@ -116,7 +116,5 @@ class PluginActivate
|
|
116 |
'<a href="' . Paths::getSettingsUrl() . '">configure it here</a>.'
|
117 |
);
|
118 |
|
119 |
-
// While not necessary, lets get those tests copied right away. Some servers are a bit slow to pick up on changes in the filesystem
|
120 |
-
CapabilityTest::copyCapabilityTestsToWpContent();
|
121 |
}
|
122 |
}
|
116 |
'<a href="' . Paths::getSettingsUrl() . '">configure it here</a>.'
|
117 |
);
|
118 |
|
|
|
|
|
119 |
}
|
120 |
}
|
@@ -43,8 +43,8 @@ class SelfTest
|
|
43 |
/*
|
44 |
$result = [];
|
45 |
$result[] = '# Redirection tests';
|
46 |
-
$modRewriteWorking =
|
47 |
-
$modHeaderWorking =
|
48 |
|
49 |
if (($modRewriteWorking === false) && ($modHeaderWorking)) {
|
50 |
//$result[] = 'mod_rewrite is not working';
|
@@ -80,7 +80,7 @@ class SelfTest
|
|
80 |
public static function processAjax()
|
81 |
{
|
82 |
if (!check_ajax_referer('webpexpress-ajax-self-test-nonce', 'nonce', false)) {
|
83 |
-
wp_send_json_error('
|
84 |
wp_die();
|
85 |
}
|
86 |
|
43 |
/*
|
44 |
$result = [];
|
45 |
$result[] = '# Redirection tests';
|
46 |
+
$modRewriteWorking = HTAccessCapabilityTestRunner::modRewriteWorking();
|
47 |
+
$modHeaderWorking = HTAccessCapabilityTestRunner::modHeaderWorking();
|
48 |
|
49 |
if (($modRewriteWorking === false) && ($modHeaderWorking)) {
|
50 |
//$result[] = 'mod_rewrite is not working';
|
80 |
public static function processAjax()
|
81 |
{
|
82 |
if (!check_ajax_referer('webpexpress-ajax-self-test-nonce', 'nonce', false)) {
|
83 |
+
wp_send_json_error('The security nonce has expired. You need to reload the settings page (press F5) and try again)');
|
84 |
wp_die();
|
85 |
}
|
86 |
|
@@ -292,6 +292,45 @@ class SelfTestHelper
|
|
292 |
return false;
|
293 |
}
|
294 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
public static function hasCacheControlOrExpiresHeader($headers)
|
296 |
{
|
297 |
if (isset($headers['cache-control'])) {
|
@@ -424,6 +463,8 @@ class SelfTestHelper
|
|
424 |
//$log[] = 'Image types: ' . ;
|
425 |
//$log[] = '';
|
426 |
$log[] = '(To view all configuration, take a look at the config file, which is stored in *' . Paths::getConfigFileName() . '*)';
|
|
|
|
|
427 |
return $log;
|
428 |
}
|
429 |
|
@@ -448,8 +489,10 @@ class SelfTestHelper
|
|
448 |
public static function rulesInImageRoot($config, $imageRootId)
|
449 |
{
|
450 |
$log = [];
|
451 |
-
$log[] = '#### WebP rules in *' . $imageRootId . '*:';
|
452 |
$file = Paths::getAbsDirById($imageRootId) . '/.htaccess';
|
|
|
|
|
|
|
453 |
if (!HTAccess::haveWeRulesInThisHTAccess($file)) {
|
454 |
$log[] = '**NONE!**{: .warn}';
|
455 |
} else {
|
@@ -477,6 +520,7 @@ class SelfTestHelper
|
|
477 |
public static function allInfo($config)
|
478 |
{
|
479 |
$log = [];
|
|
|
480 |
$log = array_merge($log, self::systemInfo());
|
481 |
$log = array_merge($log, self::wordpressInfo());
|
482 |
$log = array_merge($log, self::configInfo($config));
|
@@ -491,18 +535,26 @@ class SelfTestHelper
|
|
491 |
{
|
492 |
$capTests = $config['base-htaccess-on-these-capability-tests'];
|
493 |
$log = [];
|
494 |
-
$log[] = '#### Live tests of .htaccess capabilities:';
|
|
|
|
|
|
|
495 |
/*$log[] = 'Exactly what you can do in a *.htaccess* depends on the server setup. WebP Express ' .
|
496 |
'makes some live tests to verify if a certain feature in fact works. This is done by creating ' .
|
497 |
'test files (*.htaccess* files and php files) in a dir inside the content dir and running these. ' .
|
498 |
'These test results are used when creating the rewrite rules. Here are the results:';*/
|
499 |
|
500 |
// $log[] = '';
|
501 |
-
$log[] = '-
|
502 |
-
$log[] = '-
|
|
|
|
|
|
|
|
|
|
|
|
|
503 |
/*$log[] = '- pass variable from *.htaccess* to script through header working?: ' .
|
504 |
self::trueFalseNullString($capTests['passThroughHeaderWorking']);*/
|
505 |
-
$log[] = '- passing variables from *.htaccess* to PHP script through environment variable working?: ' . self::trueFalseNullString($capTests['passThroughEnvWorking']);
|
506 |
return $log;
|
507 |
}
|
508 |
|
@@ -540,7 +592,7 @@ class SelfTestHelper
|
|
540 |
return $log;
|
541 |
}
|
542 |
|
543 |
-
$modRewriteWorking =
|
544 |
if ($modRewriteWorking !== null) {
|
545 |
$log[] = 'Running a special designed capability test to test if rewriting works with *.htaccess* files';
|
546 |
}
|
@@ -592,4 +644,146 @@ class SelfTestHelper
|
|
592 |
$log = array_merge($log, self::allInfo($config));
|
593 |
return $log;
|
594 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
595 |
}
|
292 |
return false;
|
293 |
}
|
294 |
|
295 |
+
/**
|
296 |
+
* @param string $rule existing|webp-on-demand|webp-realizer
|
297 |
+
*/
|
298 |
+
public static function diagnoseNoVaryHeader($rootId, $rule)
|
299 |
+
{
|
300 |
+
$log = [];
|
301 |
+
$log[] = '**However, we did not receive a Vary:Accept header. ' .
|
302 |
+
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
303 |
+
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
304 |
+
|
305 |
+
$log[] = 'Too technical? ';
|
306 |
+
$log[] = 'Here is an explanation of what this means: ' .
|
307 |
+
'Some companies have set up proxies which caches resources. This way, if employee A have downloaded an ' .
|
308 |
+
'image and employee B requests it, the proxy can deliver the image directly to employee B without needing to ' .
|
309 |
+
'send a request to the server. ' .
|
310 |
+
'This is clever, but it can go wrong. If B for some reason is meant to get another image than A, it will not ' .
|
311 |
+
'happen, as the server does not get the request. That is where the Vary header comes in. It tells the proxy ' .
|
312 |
+
'that the image is dependent upon something. In this case, we need to signal proxies that the image depends upon ' .
|
313 |
+
'the "Accept" header, as this is the one browsers use to tell the server if it accepts webps or not. ' .
|
314 |
+
'We do that using the "Vary:Accept" header. However - it is missing :( ' .
|
315 |
+
'Which means that employees at (larger) companies might experience problems if some are using browsers ' .
|
316 |
+
'that supports webp and others are using browsers that does not. Worst case is that the request to an image ' .
|
317 |
+
'is done with a browser that supports webp, as this will cache the webp in the proxy, and deliver webps to ' .
|
318 |
+
'all employees - even to those who uses browsers that does not support webp. These employees will get blank images.';
|
319 |
+
|
320 |
+
if ($rule == 'existing') {
|
321 |
+
$log[] = 'So, what should you do? **I would recommend that you either try to fix the problem with the missing Vary:Accept ' .
|
322 |
+
'header or change to "CDN friendly" mode.**{: .warn}';
|
323 |
+
} elseif ($rule == 'webp-on-demand') {
|
324 |
+
$log[] = 'So, what should you do? **I would recommend that you either try to fix the problem with the missing Vary:Accept ' .
|
325 |
+
'header or disable the "Enable redirection to converter?" option and use another way to get the images converted - ie ' .
|
326 |
+
'Bulk Convert or Convert on Upload**{: .warn}';
|
327 |
+
}
|
328 |
+
|
329 |
+
|
330 |
+
|
331 |
+
return $log;
|
332 |
+
}
|
333 |
+
|
334 |
public static function hasCacheControlOrExpiresHeader($headers)
|
335 |
{
|
336 |
if (isset($headers['cache-control'])) {
|
463 |
//$log[] = 'Image types: ' . ;
|
464 |
//$log[] = '';
|
465 |
$log[] = '(To view all configuration, take a look at the config file, which is stored in *' . Paths::getConfigFileName() . '*)';
|
466 |
+
//$log[] = '- Config file: (config.json)';
|
467 |
+
//$log[] = "'''\n" . json_encode($config, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT) . "\n'''\n";
|
468 |
return $log;
|
469 |
}
|
470 |
|
489 |
public static function rulesInImageRoot($config, $imageRootId)
|
490 |
{
|
491 |
$log = [];
|
|
|
492 |
$file = Paths::getAbsDirById($imageRootId) . '/.htaccess';
|
493 |
+
$log[] = '#### WebP rules in *' .
|
494 |
+
($imageRootId == 'cache' ? 'webp image cache' : $imageRootId) . '*:';
|
495 |
+
$log[] = 'File: ' . $file;
|
496 |
if (!HTAccess::haveWeRulesInThisHTAccess($file)) {
|
497 |
$log[] = '**NONE!**{: .warn}';
|
498 |
} else {
|
520 |
public static function allInfo($config)
|
521 |
{
|
522 |
$log = [];
|
523 |
+
|
524 |
$log = array_merge($log, self::systemInfo());
|
525 |
$log = array_merge($log, self::wordpressInfo());
|
526 |
$log = array_merge($log, self::configInfo($config));
|
535 |
{
|
536 |
$capTests = $config['base-htaccess-on-these-capability-tests'];
|
537 |
$log = [];
|
538 |
+
$log[] = '#### Live tests of .htaccess capabilities / system configuration:';
|
539 |
+
$log[] = 'Unless noted otherwise, the tests are run in *wp-content/webp-express/htaccess-capability-tester*. ';
|
540 |
+
$log[] = 'WebPExpress currently treats the results as they neccessarily applies to all scopes (upload, themes, etc), ';
|
541 |
+
$log[] = 'but note that a server might be configured to have mod_rewrite disallowed in some folders and allowed in others.';
|
542 |
/*$log[] = 'Exactly what you can do in a *.htaccess* depends on the server setup. WebP Express ' .
|
543 |
'makes some live tests to verify if a certain feature in fact works. This is done by creating ' .
|
544 |
'test files (*.htaccess* files and php files) in a dir inside the content dir and running these. ' .
|
545 |
'These test results are used when creating the rewrite rules. Here are the results:';*/
|
546 |
|
547 |
// $log[] = '';
|
548 |
+
$log[] = '- .htaccess files enabled?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::htaccessEnabled());
|
549 |
+
$log[] = '- mod_rewrite working?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::modRewriteWorking());
|
550 |
+
$log[] = '- mod_headers loaded?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::modHeadersLoaded());
|
551 |
+
$log[] = '- mod_headers working (header set): ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::modHeaderWorking());
|
552 |
+
$log[] = '- passing variables from *.htaccess* to PHP script through environment variable working?: ' . self::trueFalseNullString($capTests['passThroughEnvWorking']);
|
553 |
+
$log[] = '- Can run php test file in plugins/webp-express/wod/ ?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::canRunTestScriptInWOD());
|
554 |
+
$log[] = '- Can run php test file in plugins/webp-express/wod2/ ?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::canRunTestScriptInWOD2());
|
555 |
+
$log[] = '- Directives for granting access like its done in wod/.htaccess allowed?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::grantAllAllowed());
|
556 |
/*$log[] = '- pass variable from *.htaccess* to script through header working?: ' .
|
557 |
self::trueFalseNullString($capTests['passThroughHeaderWorking']);*/
|
|
|
558 |
return $log;
|
559 |
}
|
560 |
|
592 |
return $log;
|
593 |
}
|
594 |
|
595 |
+
$modRewriteWorking = HTAccessCapabilityTestRunner::modRewriteWorking();
|
596 |
if ($modRewriteWorking !== null) {
|
597 |
$log[] = 'Running a special designed capability test to test if rewriting works with *.htaccess* files';
|
598 |
}
|
644 |
$log = array_merge($log, self::allInfo($config));
|
645 |
return $log;
|
646 |
}
|
647 |
+
|
648 |
+
public static function diagnoseWod403or500($config, $rootId, $responseCode)
|
649 |
+
{
|
650 |
+
$log = [];
|
651 |
+
|
652 |
+
$htaccessRules = SelfTestHelper::rulesInImageRoot($config, $rootId);
|
653 |
+
$rulesText = implode('', $htaccessRules);
|
654 |
+
$rulesPointsToWod = (strpos($rulesText, '/wod/') > 0);
|
655 |
+
$rulesPointsToWod2 = (strpos($rulesText, '/wod2/') !== false);
|
656 |
+
|
657 |
+
$log[] = '';
|
658 |
+
$log[] = '**diagnosing**';
|
659 |
+
$canRunTestScriptInWod = HTAccessCapabilityTestRunner::canRunTestScriptInWOD();
|
660 |
+
$canRunTestScriptInWod2 = HTAccessCapabilityTestRunner::canRunTestScriptInWOD2();
|
661 |
+
$canRunInAnyWod = ($canRunTestScriptInWod || $canRunTestScriptInWod2);
|
662 |
+
|
663 |
+
$responsePingPhp = wp_remote_get(Paths::getPluginsUrl() . '/webp-express/wod/ping.php', ['timeout' => 7]);
|
664 |
+
$pingPhpResponseCode = wp_remote_retrieve_response_code($responsePingPhp);
|
665 |
+
|
666 |
+
$responsePingText = wp_remote_get(Paths::getPluginsUrl() . '/webp-express/wod/ping.txt', ['timeout' => 7]);
|
667 |
+
$pingTextResponseCode = wp_remote_retrieve_response_code($responsePingText);
|
668 |
+
|
669 |
+
if ($responseCode == 500) {
|
670 |
+
$log[] = 'The response was a *500 Internal Server Error*. There can be different reasons for that. ' .
|
671 |
+
'Lets dig a bit deeper...';
|
672 |
+
}
|
673 |
+
|
674 |
+
$log[] = 'Examining where the *.htaccess* rules in the ' . $rootId . ' folder points to. ';
|
675 |
+
|
676 |
+
if ($rulesPointsToWod) {
|
677 |
+
$log[] = 'They point to **wod**/webp-on-demand.php';
|
678 |
+
} elseif ($rulesPointsToWod2) {
|
679 |
+
$log[] = 'They point to **wod2**/webp-on-demand.php';
|
680 |
+
} else {
|
681 |
+
$log[] = '**There are no redirect rule to *webp-on-demand.php* in the .htaccess!**{: .warn}';
|
682 |
+
$log[] = 'Here is the rules:';
|
683 |
+
$log = array_merge($log, $htaccessRules);
|
684 |
+
}
|
685 |
+
|
686 |
+
if ($rulesPointsToWod) {
|
687 |
+
$log[] = 'Requesting simple test script "wod/ping.php"... ' .
|
688 |
+
'Result: ' . ($pingPhpResponseCode == '200' ? 'ok' : 'failed (response code: ' . $pingPhpResponseCode . ')');
|
689 |
+
//'Result: ' . ($canRunTestScriptInWod ? 'ok' : 'failed');
|
690 |
+
|
691 |
+
if ($canRunTestScriptInWod) {
|
692 |
+
if ($responseCode == '500') {
|
693 |
+
$log[] = '';
|
694 |
+
$log[] = '**As the test script works, it would seem that the explanation for the 500 internal server ' .
|
695 |
+
'error is that the PHP script (webp-on-demand.php) crashes. ' .
|
696 |
+
'You can help me by enabling debugging and post the error on the support forum on Wordpress ' .
|
697 |
+
'(https://wordpress.org/support/plugin/webp-express/), or create an issue on github ' .
|
698 |
+
'(https://github.com/rosell-dk/webp-express/issues)**';
|
699 |
+
$log[] = '';
|
700 |
+
}
|
701 |
+
} else {
|
702 |
+
$log[] = 'Requesting simple test file "wod/ping.txt". ' .
|
703 |
+
'Result: ' . ($pingTextResponseCode == '200' ? 'ok' : 'failed (response code: ' . $pingTextResponseCode . ')');
|
704 |
+
|
705 |
+
if ($canRunTestScriptInWod2) {
|
706 |
+
if ($responseCode == 500) {
|
707 |
+
if ($pingTextResponseCode == '500') {
|
708 |
+
$log[] = 'The problem appears to be that the *.htaccess* placed in *plugins/webp-express/wod/.htaccess*' .
|
709 |
+
' contains auth directives ("Allow" and "Request") and your server is set up to go fatal about it. ' .
|
710 |
+
'Luckily, it seems that running scripts in the "wod2" folder works. ' .
|
711 |
+
'**What you need to do is simply to click the "Save settings and force new .htacess rules"' .
|
712 |
+
' button. WebP Express wil then change the .htaccess rules to point to the "wod2" folder**';
|
713 |
+
} else {
|
714 |
+
$log[] = 'The problem appears to be running PHP scripts in the "wod". ' .
|
715 |
+
'Luckily, it seems that running scripts in the "wod2" folder works ' .
|
716 |
+
'(it has probably something to do with the *.htaccess* file placed in "wod"). ' .
|
717 |
+
'**What you need to do is simply to click the "Save settings and force new .htacess rules"' .
|
718 |
+
' button. WebP Express wil then change the .htaccess rules to point to the "wod2" folder**';
|
719 |
+
}
|
720 |
+
} elseif ($responseCode == 403) {
|
721 |
+
$log[] = 'The problem appears to be running PHP scripts in the "wod". ' .
|
722 |
+
'Luckily, it seems that running scripts in the "wod2" folder works ' .
|
723 |
+
'(it could perhaps have something to do with the *.htaccess* file placed in "wod", ' .
|
724 |
+
'although it ought not result in a 403). **What you need to do is simply to click the "Save settings and force new .htacess rules"' .
|
725 |
+
' button. WebP Express wil then change the .htaccess rules to point to the "wod2" folder**';
|
726 |
+
}
|
727 |
+
|
728 |
+
return $log;
|
729 |
+
}
|
730 |
+
}
|
731 |
+
}
|
732 |
+
|
733 |
+
$log[] = 'Requesting simple test script "wod2/ping.php". Result: ' . ($canRunTestScriptInWod2 ? 'ok' : 'failed');
|
734 |
+
$responsePingText2 = wp_remote_get(Paths::getPluginsUrl() . '/webp-express/wod2/ping.txt', ['timeout' => 7]);
|
735 |
+
$pingTextResponseCode2 = wp_remote_retrieve_response_code($responsePingText2);
|
736 |
+
$log[] = 'Requesting simple test file "wod2/ping.txt". ' .
|
737 |
+
'Result: ' . ($pingTextResponseCode == '200' ? 'ok' : 'failed (response code: ' . $pingTextResponseCode2 . ')');
|
738 |
+
|
739 |
+
if ($rulesPointsToWod2) {
|
740 |
+
if ($canRunTestScriptInWod2) {
|
741 |
+
if ($responseCode == '500') {
|
742 |
+
$log[] = '';
|
743 |
+
$log[] = '**As the test script works, it would seem that the explanation for the 500 internal server ' .
|
744 |
+
'error is that the PHP script (webp-on-demand.php) crashes. ' .
|
745 |
+
'You can help me by enabling debugging and post the error on the support forum on Wordpress ' .
|
746 |
+
'(https://wordpress.org/support/plugin/webp-express/), or create an issue on github ' .
|
747 |
+
'(https://github.com/rosell-dk/webp-express/issues)**';
|
748 |
+
$log[] = '';
|
749 |
+
}
|
750 |
+
} else {
|
751 |
+
if ($canRunTestScriptInWod) {
|
752 |
+
$log[] = '';
|
753 |
+
$log[] = 'The problem appears to be running PHP scripts in the "wod2" folder. ' .
|
754 |
+
'Luckily, it seems that running scripts in the "wod" folder works ' .
|
755 |
+
'**What you need to do is simply to click the "Save settings and force new .htacess rules"' .
|
756 |
+
' button. WebP Express wil then change the .htaccess rules to point to the "wod" folder**';
|
757 |
+
$log[] = '';
|
758 |
+
} else {
|
759 |
+
if ($responseCode == 500) {
|
760 |
+
|
761 |
+
if ($pingTextResponseCode2 == '500') {
|
762 |
+
$log[] = 'All our requests results in 500 Internal Error. Even ' .
|
763 |
+
'the request to plugins/webp-express/wod2/ping.txt. ' .
|
764 |
+
'Surprising!';
|
765 |
+
} else {
|
766 |
+
$log[] = 'The internal server error happens for php files, but not txt files. ' .
|
767 |
+
'It could be the result of a restrictive server configuration or the works of a security plugin. ' .
|
768 |
+
'Try to examine the .htaccess file in the plugins folder and its parent folders. ' .
|
769 |
+
'Or try to look in the httpd.conf. Look for the "AllowOverride" and the "AllowOverrideList" directives. ';
|
770 |
+
}
|
771 |
+
|
772 |
+
//$log[] = 'We get *500 Internal Server Error*';
|
773 |
+
/*
|
774 |
+
It can for example be that the *.htaccess* ' .
|
775 |
+
'in the ' . $rootId . ' folder (or a parent folder) contains directives that the server either ' .
|
776 |
+
'doesnt support or has not allowed (using AllowOverride in ie httpd.conf). It could also be that the redirect succeded, ' .
|
777 |
+
'but the *.htaccess* in the folder of the script (or a parent folder) results in such problems. Also, ' .
|
778 |
+
'it could be that the script (webp-on-demand.php) for some reason fails.';
|
779 |
+
|
780 |
+
*/
|
781 |
+
}
|
782 |
+
}
|
783 |
+
|
784 |
+
|
785 |
+
}
|
786 |
+
}
|
787 |
+
return $log;
|
788 |
+
}
|
789 |
}
|
@@ -18,6 +18,15 @@ class SelfTestRedirectToConverter extends SelfTestRedirectAbstract
|
|
18 |
$createdTestFiles = false;
|
19 |
$noWarningsYet = true;
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
// Copy test image (jpeg)
|
22 |
list($subResult, $success, $sourceFileName) = SelfTestHelper::copyTestImageToRoot($rootId, $imageType);
|
23 |
$log = array_merge($log, $subResult);
|
@@ -45,7 +54,18 @@ class SelfTestRedirectToConverter extends SelfTestRedirectAbstract
|
|
45 |
//$log[count($log) - 1] .= '. FAILED';
|
46 |
$log[] = 'The request FAILED';
|
47 |
//$log = array_merge($log, $remoteGetLog);
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
//$log[count($log) - 1] .= '. FAILED';
|
50 |
return [false, $log, $createdTestFiles];
|
51 |
}
|
@@ -110,10 +130,7 @@ class SelfTestRedirectToConverter extends SelfTestRedirectAbstract
|
|
110 |
}
|
111 |
|
112 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
113 |
-
$log
|
114 |
-
$log[] = '**Warning: We did not receive a Vary:Accept header. ' .
|
115 |
-
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
116 |
-
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
117 |
$noWarningsYet = false;
|
118 |
}
|
119 |
if (!SelfTestHelper::hasCacheControlOrExpiresHeader($headers)) {
|
@@ -188,10 +205,7 @@ class SelfTestRedirectToConverter extends SelfTestRedirectAbstract
|
|
188 |
$log[] = 'Alrighty. We got the ' . $imageType . '. **Great!**{: .ok}.';
|
189 |
|
190 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
191 |
-
$log
|
192 |
-
$log[] = '**We did not receive a Vary:Accept header. ' .
|
193 |
-
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
194 |
-
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
195 |
$noWarningsYet = false;
|
196 |
}
|
197 |
|
18 |
$createdTestFiles = false;
|
19 |
$noWarningsYet = true;
|
20 |
|
21 |
+
$htaccessFile = Paths::getAbsDirById($rootId) . '/.htaccess';
|
22 |
+
if (!FileHelper::fileExists($htaccessFile)) {
|
23 |
+
$log[] = '**Warning: There is no .htaccess file in the ' . $rootId . ' folder!**{: .warn} (did you save settings yet?)';
|
24 |
+
$noWarningsYet = false;
|
25 |
+
} elseif (!HTAccess::haveWeRulesInThisHTAccess($htaccessFile)) {
|
26 |
+
$log[] = '**Warning: There are no WebP Express rules in the .htaccess file in the ' . $rootId . ' folder!**{: .warn}';
|
27 |
+
$noWarningsYet = false;
|
28 |
+
}
|
29 |
+
|
30 |
// Copy test image (jpeg)
|
31 |
list($subResult, $success, $sourceFileName) = SelfTestHelper::copyTestImageToRoot($rootId, $imageType);
|
32 |
$log = array_merge($log, $subResult);
|
54 |
//$log[count($log) - 1] .= '. FAILED';
|
55 |
$log[] = 'The request FAILED';
|
56 |
//$log = array_merge($log, $remoteGetLog);
|
57 |
+
|
58 |
+
if (isset($results[0]['response']['code'])) {
|
59 |
+
$responseCode = $results[0]['response']['code'];
|
60 |
+
if (($responseCode == 500) || ($responseCode == 403)) {
|
61 |
+
|
62 |
+
$log = array_merge($log, SelfTestHelper::diagnoseWod403or500($this->config, $rootId, $responseCode));
|
63 |
+
|
64 |
+
//$log[] = 'or that there is an .htaccess file in the ';
|
65 |
+
}
|
66 |
+
// $log[] = print_r($results[0]['response']['code'], true);
|
67 |
+
}
|
68 |
+
//$log[] = 'The test cannot be completed';
|
69 |
//$log[count($log) - 1] .= '. FAILED';
|
70 |
return [false, $log, $createdTestFiles];
|
71 |
}
|
130 |
}
|
131 |
|
132 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
133 |
+
$log = array_merge($log, SelfTestHelper::diagnoseNoVaryHeader($rootId, 'webp-on-demand'));
|
|
|
|
|
|
|
134 |
$noWarningsYet = false;
|
135 |
}
|
136 |
if (!SelfTestHelper::hasCacheControlOrExpiresHeader($headers)) {
|
205 |
$log[] = 'Alrighty. We got the ' . $imageType . '. **Great!**{: .ok}.';
|
206 |
|
207 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
208 |
+
$log = array_merge($log, SelfTestHelper::diagnoseNoVaryHeader($rootId, 'webp-on-demand'));
|
|
|
|
|
|
|
209 |
$noWarningsYet = false;
|
210 |
}
|
211 |
|
@@ -134,13 +134,12 @@ class SelfTestRedirectToExisting extends SelfTestRedirectAbstract
|
|
134 |
} else {
|
135 |
$log[] = 'Alrighty. We got a webp. Just what we wanted. **Great!**{: .ok}';
|
136 |
}
|
|
|
137 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
138 |
-
$log
|
139 |
-
$log[] = '**Warning: We did not receive a Vary:Accept header. ' .
|
140 |
-
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
141 |
-
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
142 |
$noWarningsYet = false;
|
143 |
}
|
|
|
144 |
if (!SelfTestHelper::hasCacheControlOrExpiresHeader($headers)) {
|
145 |
$log[] = '**Notice: No cache-control or expires header has been set. ' .
|
146 |
'It is recommended to do so. Set it nice and big once you are sure the webps have a good quality/compression compromise.**{: .warn}';
|
@@ -212,10 +211,7 @@ class SelfTestRedirectToExisting extends SelfTestRedirectAbstract
|
|
212 |
$log[] = 'Alrighty. We got the ' . $imageType . '. **Great!**{: .ok}.';
|
213 |
|
214 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
215 |
-
$log
|
216 |
-
$log[] = '**We did not receive a Vary:Accept header. ' .
|
217 |
-
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
218 |
-
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
219 |
$noWarningsYet = false;
|
220 |
}
|
221 |
|
134 |
} else {
|
135 |
$log[] = 'Alrighty. We got a webp. Just what we wanted. **Great!**{: .ok}';
|
136 |
}
|
137 |
+
|
138 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
139 |
+
$log = array_merge($log, SelfTestHelper::diagnoseNoVaryHeader($rootId, 'existing'));
|
|
|
|
|
|
|
140 |
$noWarningsYet = false;
|
141 |
}
|
142 |
+
|
143 |
if (!SelfTestHelper::hasCacheControlOrExpiresHeader($headers)) {
|
144 |
$log[] = '**Notice: No cache-control or expires header has been set. ' .
|
145 |
'It is recommended to do so. Set it nice and big once you are sure the webps have a good quality/compression compromise.**{: .warn}';
|
211 |
$log[] = 'Alrighty. We got the ' . $imageType . '. **Great!**{: .ok}.';
|
212 |
|
213 |
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
214 |
+
$log = array_merge($log, SelfTestHelper::diagnoseNoVaryHeader($rootId, 'existing'));
|
|
|
|
|
|
|
215 |
$noWarningsYet = false;
|
216 |
}
|
217 |
|
@@ -81,6 +81,17 @@ class SelfTestRedirectToWebPRealizer extends SelfTestRedirectAbstract
|
|
81 |
|
82 |
$log[] = 'The test **failed**{: .error}';
|
83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
$log[] = 'Why did it fail? It could either be that the redirection rule did not trigger ' .
|
85 |
'or it could be that the PHP script could not locate a source image corresponding to the destination URL. ' .
|
86 |
'Currently, this analysis cannot dertermine which was the case and it cannot be helpful ' .
|
81 |
|
82 |
$log[] = 'The test **failed**{: .error}';
|
83 |
|
84 |
+
if (isset($results[0]['response']['code'])) {
|
85 |
+
$responseCode = $results[0]['response']['code'];
|
86 |
+
if (($responseCode == 500) || ($responseCode == 403)) {
|
87 |
+
|
88 |
+
$log = array_merge($log, SelfTestHelper::diagnoseWod403or500($this->config, $rootId, $responseCode));
|
89 |
+
return [false, $log, $createdTestFiles];
|
90 |
+
//$log[] = 'or that there is an .htaccess file in the ';
|
91 |
+
}
|
92 |
+
// $log[] = print_r($results[0]['response']['code'], true);
|
93 |
+
}
|
94 |
+
|
95 |
$log[] = 'Why did it fail? It could either be that the redirection rule did not trigger ' .
|
96 |
'or it could be that the PHP script could not locate a source image corresponding to the destination URL. ' .
|
97 |
'Currently, this analysis cannot dertermine which was the case and it cannot be helpful ' .
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\HttpRequesterInterface;
|
6 |
+
use \HtaccessCapabilityTester\HttpResponse;
|
7 |
+
|
8 |
+
class WPHttpRequester implements HttpRequesterInterface
|
9 |
+
{
|
10 |
+
/**
|
11 |
+
* Make a HTTP request to a URL.
|
12 |
+
*
|
13 |
+
* @param string $url The URL to make the HTTP request to
|
14 |
+
*
|
15 |
+
* @return HttpResponse A HttpResponse object, which simply contains body, status code
|
16 |
+
* and response headers
|
17 |
+
*/
|
18 |
+
public function makeHTTPRequest($url) {
|
19 |
+
$response = wp_remote_get($url, ['timeout' => 10]);
|
20 |
+
//echo '<pre>' . print_r($response, true) . '</pre>';
|
21 |
+
|
22 |
+
$body = wp_remote_retrieve_body($response);
|
23 |
+
$statusCode = wp_remote_retrieve_response_code($response);
|
24 |
+
$headersMap = wp_remote_retrieve_headers($response)->getAll();
|
25 |
+
|
26 |
+
return new HttpResponse($body, $statusCode, $headersMap);
|
27 |
+
}
|
28 |
+
}
|
@@ -2,7 +2,6 @@
|
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
-
use \WebPExpress\CapabilityTest;
|
6 |
use \WebPExpress\Config;
|
7 |
use \WebPExpress\HTAccess;
|
8 |
use \WebPExpress\Messenger;
|
@@ -76,9 +75,6 @@ function webpexpress_migrate7() {
|
|
76 |
// PSST: When creating new migration files, remember to update WEBPEXPRESS_MIGRATION_VERSION in admin.php
|
77 |
Option::updateOption('webp-express-migration-version', '7');
|
78 |
|
79 |
-
|
80 |
-
CapabilityTest::copyCapabilityTestsToWpContent();
|
81 |
-
|
82 |
// Not completely sure if this could fail miserably, so commented out.
|
83 |
// We should probably do it in upcoming migrations
|
84 |
// \WebPExpress\KeepEwwwSubscriptionAlive::keepAliveIfItIsTime($config);
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
|
|
5 |
use \WebPExpress\Config;
|
6 |
use \WebPExpress\HTAccess;
|
7 |
use \WebPExpress\Messenger;
|
75 |
// PSST: When creating new migration files, remember to update WEBPEXPRESS_MIGRATION_VERSION in admin.php
|
76 |
Option::updateOption('webp-express-migration-version', '7');
|
77 |
|
|
|
|
|
|
|
78 |
// Not completely sure if this could fail miserably, so commented out.
|
79 |
// We should probably do it in upcoming migrations
|
80 |
// \WebPExpress\KeepEwwwSubscriptionAlive::keepAliveIfItIsTime($config);
|
@@ -315,6 +315,7 @@
|
|
315 |
font-family: sans-serif;
|
316 |
position: relative;
|
317 |
font-style: normal;
|
|
|
318 |
}
|
319 |
|
320 |
select + .help,
|
315 |
font-family: sans-serif;
|
316 |
position: relative;
|
317 |
font-style: normal;
|
318 |
+
pointer-events: all;
|
319 |
}
|
320 |
|
321 |
select + .help,
|
@@ -5,7 +5,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
|
|
5 |
use \WebPExpress\Paths;
|
6 |
use \WebPExpress\Config;
|
7 |
|
8 |
-
$ver = '
|
9 |
$jsDir = 'js/0.16.0'; // We change dir when it is critical that no-one gets the cached version (there is a plugin that strips version strings out there...)
|
10 |
|
11 |
if (!function_exists('webp_express_add_inline_script')) {
|
5 |
use \WebPExpress\Paths;
|
6 |
use \WebPExpress\Config;
|
7 |
|
8 |
+
$ver = '6'; // note: Minimum 1
|
9 |
$jsDir = 'js/0.16.0'; // We change dir when it is critical that no-one gets the cached version (there is a plugin that strips version strings out there...)
|
10 |
|
11 |
if (!function_exists('webp_express_add_inline_script')) {
|
@@ -8,48 +8,80 @@ function openBulkConvertPopup() {
|
|
8 |
'action': 'list_unconverted_files',
|
9 |
'nonce' : window.webpExpress['ajax-nonces']['list-unconverted-files'],
|
10 |
};
|
11 |
-
jQuery.
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
});
|
54 |
}
|
55 |
|
8 |
'action': 'list_unconverted_files',
|
9 |
'nonce' : window.webpExpress['ajax-nonces']['list-unconverted-files'],
|
10 |
};
|
11 |
+
jQuery.ajax({
|
12 |
+
type: "POST",
|
13 |
+
url: ajaxurl,
|
14 |
+
data: data,
|
15 |
+
dataType: 'text',
|
16 |
+
timeout: 30000,
|
17 |
+
error: function (jqXHR, status, errorThrown) {
|
18 |
+
html = '<h1>Error: ' + status + '</h1>';
|
19 |
+
html += errorThrown;
|
20 |
+
document.getElementById('bulkconvertcontent').innerHTML = html;
|
21 |
+
},
|
22 |
+
success: function(response) {
|
23 |
+
if ((typeof response == 'object') && (response['success'] == false)) {
|
24 |
+
html = '<h1>Error</h1>';
|
25 |
+
if (response['data'] && ((typeof response['data']) == 'string')) {
|
26 |
+
html += webpexpress_escapeHTML(response['data']);
|
27 |
+
}
|
28 |
+
document.getElementById('bulkconvertcontent').innerHTML = html;
|
29 |
+
return;
|
30 |
+
}
|
31 |
+
if (response == '') {
|
32 |
+
html = '<h1>Error</h1>';
|
33 |
+
html += '<p>Could not fetch list of files to convert. The server returned nothing (which is unexpected - ' +
|
34 |
+
'it is not simply because there are no files to convert.)</p>';
|
35 |
+
document.getElementById('bulkconvertcontent').innerHTML = html;
|
36 |
+
return;
|
37 |
+
}
|
38 |
+
var responseObj;
|
39 |
+
try {
|
40 |
+
responseObj = JSON.parse(response);
|
41 |
+
} catch (e) {
|
42 |
+
html = '<h1>Error</h1>';
|
43 |
+
html += '<p>The ajax call did not return valid JSON, as expected.</p>';
|
44 |
+
html += '<p>Check the javascript console to see what was returned.</p>';
|
45 |
+
console.log('The ajax call did not return valid JSON, as expected');
|
46 |
+
console.log('Here is what was received:');
|
47 |
+
console.log(response);
|
48 |
+
document.getElementById('bulkconvertcontent').innerHTML = html;
|
49 |
+
return;
|
50 |
+
}
|
51 |
+
var bulkInfo = {
|
52 |
+
'groups': responseObj,
|
53 |
+
'groupPointer': 0,
|
54 |
+
'filePointer': 0,
|
55 |
+
'paused': false,
|
56 |
+
'webpTotalFilesize': 0,
|
57 |
+
'orgTotalFilesize': 0,
|
58 |
+
};
|
59 |
+
window.webpexpress_bulkconvert = bulkInfo;
|
60 |
+
|
61 |
+
// count files
|
62 |
+
var numFiles = 0;
|
63 |
+
for (var i=0; i<bulkInfo.groups.length; i++) {
|
64 |
+
numFiles += bulkInfo.groups[i].files.length;
|
65 |
+
}
|
66 |
+
|
67 |
+
//console.log(JSON.parse(response));
|
68 |
+
var html = '';
|
69 |
+
if (numFiles == 0) {
|
70 |
+
html += '<p>There are no unconverted files</p>';
|
71 |
+
} else {
|
72 |
+
html += '<div>'
|
73 |
+
html += '<p>There are ' + numFiles + ' unconverted files.</p>';
|
74 |
+
html += '<p><i>Note that in a typical setup, you will have redirect rules which trigger conversion when needed, ' +
|
75 |
+
'and thus you have no need for bulk conversion. In fact, in that case, you should probably not bulk convert ' +
|
76 |
+
'because bulk conversion will also convert images and thumbnails which are not in use, and thus take up ' +
|
77 |
+
'more disk space than necessary. The bulk conversion feature was only added in order to make the plugin usable even when ' +
|
78 |
+
'there are problems with redirects (ie on Nginx in case you do not have access to the config or on Microsoft IIS). ' +
|
79 |
+
'</i></p><br>';
|
80 |
+
html += '<button onclick="startBulkConversion()" class="button button-primary" type="button">Start conversion</button>';
|
81 |
+
html += '</div>';
|
82 |
+
}
|
83 |
+
document.getElementById('bulkconvertcontent').innerHTML = html;
|
84 |
+
}
|
85 |
});
|
86 |
}
|
87 |
|
@@ -56,6 +56,10 @@ WebPExpress.SelfTest = {
|
|
56 |
line = '<h4>' + line.substr(5) + '</h4>';
|
57 |
}
|
58 |
|
|
|
|
|
|
|
|
|
59 |
// Empty line
|
60 |
if (line == '') {
|
61 |
line = '<br>';
|
56 |
line = '<h4>' + line.substr(5) + '</h4>';
|
57 |
}
|
58 |
|
59 |
+
if (line.substr(0, 15) == ''''') {
|
60 |
+
line = '<pre>' + line.substr(15) + '</pre>';
|
61 |
+
}
|
62 |
+
|
63 |
// Empty line
|
64 |
if (line == '') {
|
65 |
line = '<br>';
|
@@ -187,7 +187,7 @@ namespace WebPExpress;
|
|
187 |
'Also, in order to protect from memory problems, the HTML altering is ' .
|
188 |
'bypassed when the HTML is larger than 600 kb. ' .
|
189 |
'However, not all themes and plugins uses the standard hooks, so in some ' .
|
190 |
-
'cases, you will have to resort to the second option.
|
191 |
'hooks has been added for some popular plugins, such as <i>ACF</i> and ' .
|
192 |
'<i>Woo Commerce Product Image</i>. ' .
|
193 |
'<p><i>Use content filtering hooks</i> currently supports the following hooks: ' .
|
187 |
'Also, in order to protect from memory problems, the HTML altering is ' .
|
188 |
'bypassed when the HTML is larger than 600 kb. ' .
|
189 |
'However, not all themes and plugins uses the standard hooks, so in some ' .
|
190 |
+
'cases, you will have to resort to the second option. As of 0.17.5, ' .
|
191 |
'hooks has been added for some popular plugins, such as <i>ACF</i> and ' .
|
192 |
'<i>Woo Commerce Product Image</i>. ' .
|
193 |
'<p><i>Use content filtering hooks</i> currently supports the following hooks: ' .
|
@@ -8,14 +8,21 @@ $cacheControlPublic = $config['cache-control-public'];
|
|
8 |
|
9 |
<tr id="cache_control_div">
|
10 |
<th scope="row">Cache-Control header <?php
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
'
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
}
|
20 |
?>
|
21 |
</th>
|
8 |
|
9 |
<tr id="cache_control_div">
|
10 |
<th scope="row">Cache-Control header <?php
|
11 |
+
switch ($config['operation-mode']) {
|
12 |
+
case 'no-conversion':
|
13 |
+
echo helpIcon('<p>Optionally set cache-control header for the internally redirected images ' .
|
14 |
+
'(recommended!)</p>');
|
15 |
+
break;
|
16 |
+
case 'cdn-friendly':
|
17 |
+
echo helpIcon('<p>Optionally set cache-control header for webp images');
|
18 |
+
break;
|
19 |
+
default:
|
20 |
+
echo helpIcon('<p>Controls the cache-control header on successful conversion and direct redirection to converted ' .
|
21 |
+
'image in .htaccess. In case of convert failure, headers will be sent to prevent caching.</p>' .
|
22 |
+
'<p>PS: In order to set <i>stale-while-revalidate</i> and <i>stale-if-error directives<i>, you must ' .
|
23 |
+
'currently choose "Custom". <a target="_blank" href="https://www.fastly.com/blog/stale-while-revalidate-stale-if-error-available-today">It is a good idea to set these</a>.' .
|
24 |
+
'</p>');
|
25 |
+
break;
|
26 |
}
|
27 |
?>
|
28 |
</th>
|
@@ -24,7 +24,7 @@
|
|
24 |
|
25 |
// TODO:
|
26 |
// Perhaps also show cache control in cdn-friendly mode?
|
27 |
-
if (($config['operation-mode'] == 'tweaked') || ($config['operation-mode'] == 'varied-image-responses')) {
|
28 |
include_once 'cache-control.inc';
|
29 |
}
|
30 |
|
24 |
|
25 |
// TODO:
|
26 |
// Perhaps also show cache control in cdn-friendly mode?
|
27 |
+
if (($config['operation-mode'] == 'tweaked') || ($config['operation-mode'] == 'varied-image-responses') || ($config['operation-mode'] == 'cdn-friendly')) {
|
28 |
include_once 'cache-control.inc';
|
29 |
}
|
30 |
|
@@ -21,9 +21,7 @@ $operationMode = $config['operation-mode'];
|
|
21 |
<?php if ($config['operation-mode'] == 'varied-image-responses') : ?>
|
22 |
<p><div class="p">
|
23 |
<i>In the "Varied image responses" mode, WebP Express creates redirection rules for images, such that a request for a jpeg will
|
24 |
-
result in a webp – but only if the request comes from a webp-enabled browser.
|
25 |
-
If a webp already exists, it is served immediately. Otherwise it is converted and then served.
|
26 |
-
Note that not all CDN's handles varied responses well (see FAQ).
|
27 |
</i>
|
28 |
</div></p>
|
29 |
<?php endif; ?>
|
21 |
<?php if ($config['operation-mode'] == 'varied-image-responses') : ?>
|
22 |
<p><div class="p">
|
23 |
<i>In the "Varied image responses" mode, WebP Express creates redirection rules for images, such that a request for a jpeg will
|
24 |
+
result in a webp – but only if the request comes from a webp-enabled browser. Note that not all CDN's handles varied responses well (see FAQ).
|
|
|
|
|
25 |
</i>
|
26 |
</div></p>
|
27 |
<?php endif; ?>
|
@@ -2,12 +2,12 @@
|
|
2 |
<th scope="row">
|
3 |
Enable redirection to converter?
|
4 |
<?php echo helpIcon(
|
5 |
-
'<p>This will add rules in the <i>.htaccess</i> that redirects images (jpeg/png) to the conversion script ' .
|
6 |
'for browsers that supports webp.</p>' .
|
7 |
'<p>If the script detects that the webp already exists, and it is smaller and newer than the original, the ' .
|
8 |
'webp is served directly. Otherwise the original image is converted and served.</p>' .
|
9 |
'<p>The redirect rule is placed below the rule that redirects directly to existing ' .
|
10 |
-
'webp files.</p>'
|
11 |
); ?>
|
12 |
</th>
|
13 |
<td>
|
2 |
<th scope="row">
|
3 |
Enable redirection to converter?
|
4 |
<?php echo helpIcon(
|
5 |
+
'<p>This will add rules in the <i>.htaccess</i> that redirects images (jpeg/png) to the conversion script ("webp-on-demand.php") ' .
|
6 |
'for browsers that supports webp.</p>' .
|
7 |
'<p>If the script detects that the webp already exists, and it is smaller and newer than the original, the ' .
|
8 |
'webp is served directly. Otherwise the original image is converted and served.</p>' .
|
9 |
'<p>The redirect rule is placed below the rule that redirects directly to existing ' .
|
10 |
+
'webp files, which means that conversion will only be triggered once.</p>'
|
11 |
); ?>
|
12 |
</th>
|
13 |
<td>
|
@@ -1,16 +1,26 @@
|
|
1 |
<fieldset class="block">
|
2 |
-
<?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
<h2>Redirecting jpeg/png to existing webp (varied image response)</h2>
|
4 |
<p>
|
5 |
Enabling this adds rules to the <i>.htaccess</i> which internally redirects jpg/pngs to webp
|
6 |
and sets the <i>Vary:Accept</i> response header.
|
7 |
<i>Beware that special attention is needed if you are using a CDN (see FAQ).</i>
|
8 |
</p>
|
9 |
-
<?php elseif ($config['operation-mode'] == 'cdn-friendly') : ?>
|
10 |
-
<h2><i>.htaccess</i> rules for webp generation</h2>
|
11 |
<?php else : ?>
|
|
|
12 |
<h3>Redirection rules</h3>
|
13 |
-
<div><i>The options here
|
|
|
14 |
echo helpIcon('Note: The general options also affects the rules.');
|
15 |
?></i></div>
|
16 |
<?php endif; ?>
|
1 |
<fieldset class="block">
|
2 |
+
<?php if ($config['operation-mode'] == 'varied-image-responses') : ?>
|
3 |
+
<h2><i>.htaccess</i> rules for webp generation</h2>
|
4 |
+
<p>
|
5 |
+
Usually, in this operation mode, you would enable the two first options, which effectively
|
6 |
+
enables the varied image responses (such that a request for a jpeg/png will result in a webp on browsers that supports webp).
|
7 |
+
The first option makes this happen for images that are already converted. The second option makes it happen even for
|
8 |
+
images that has not yet been converted, by redirecting the request to the converter, which converts, saves and delivers.</p>
|
9 |
+
<p>The third option is only relevant if you are using Alter HTML as well as serving varied image responses.</p>
|
10 |
+
<?php elseif ($config['operation-mode'] == 'cdn-friendly') : ?>
|
11 |
+
<h2><i>.htaccess</i> rules for webp generation</h2>
|
12 |
+
<?php elseif ($config['operation-mode'] == 'no-conversion') : ?>
|
13 |
<h2>Redirecting jpeg/png to existing webp (varied image response)</h2>
|
14 |
<p>
|
15 |
Enabling this adds rules to the <i>.htaccess</i> which internally redirects jpg/pngs to webp
|
16 |
and sets the <i>Vary:Accept</i> response header.
|
17 |
<i>Beware that special attention is needed if you are using a CDN (see FAQ).</i>
|
18 |
</p>
|
|
|
|
|
19 |
<?php else : ?>
|
20 |
+
If a webp already exists, it is served immediately. Otherwise it is converted and then served.
|
21 |
<h3>Redirection rules</h3>
|
22 |
+
<div><i>The options here creates redirection rules in the .htaccess. The two first are used to If you are planning to serve You do not have to enable any of them,
|
23 |
+
as you can rely solely on altering enable any of them, you Disabling The options here affects the rules created in the .htaccess. <?php
|
24 |
echo helpIcon('Note: The general options also affects the rules.');
|
25 |
?></i></div>
|
26 |
<?php endif; ?>
|
@@ -2,17 +2,20 @@
|
|
2 |
|
3 |
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
|
4 |
|
5 |
-
use \WebPExpress\
|
6 |
use \WebPExpress\Config;
|
7 |
use \WebPExpress\ConvertersHelper;
|
8 |
use \WebPExpress\DismissableMessages;
|
9 |
use \WebPExpress\FileHelper;
|
10 |
use \WebPExpress\HTAccess;
|
|
|
11 |
use \WebPExpress\Messenger;
|
12 |
use \WebPExpress\Paths;
|
13 |
use \WebPExpress\PlatformInfo;
|
14 |
use \WebPExpress\State;
|
15 |
|
|
|
|
|
16 |
if (!(State::getState('configured', false))) {
|
17 |
include __DIR__ . "/page-welcome.php";
|
18 |
|
@@ -22,21 +25,16 @@ if (!(State::getState('configured', false))) {
|
|
22 |
|
23 |
}
|
24 |
|
|
|
25 |
|
26 |
/*
|
27 |
-
if (
|
28 |
echo 'mod rewrite works. that is nice';
|
29 |
}*/
|
30 |
|
31 |
-
/*if (
|
32 |
//echo 'nice!';
|
33 |
}*/
|
34 |
-
/*
|
35 |
-
if (CapabilityTest::copyCapabilityTestsToWpContent()) {
|
36 |
-
echo 'copy ok!';
|
37 |
-
} else {
|
38 |
-
echo 'copy failed!';
|
39 |
-
}*/
|
40 |
|
41 |
// Dissmiss page messages for which the condition no longer applies
|
42 |
if ($config['image-types'] != 1) {
|
@@ -56,33 +54,6 @@ DismissableMessages::printMessages();
|
|
56 |
$firstActiveAndWorkingConverterId = ConvertersHelper::getFirstWorkingAndActiveConverterId($config);
|
57 |
$workingIds = ConvertersHelper::getWorkingConverterIds($config);
|
58 |
|
59 |
-
if ($config['redirect-to-existing-in-htaccess']) {
|
60 |
-
if (PlatformInfo::isApacheOrLiteSpeed() && isset($config['base-htaccess-on-these-capability-tests']['modHeaderWorking']) && ($config['base-htaccess-on-these-capability-tests']['modHeaderWorking'] == false)) {
|
61 |
-
Messenger::printMessage(
|
62 |
-
'warning',
|
63 |
-
'It seems your server setup does not support headers in <i>.htaccess</i>. You should either fix this (install <i>mod_headers</i>) <i>or</i> ' .
|
64 |
-
'deactivate the "Enable direct redirection to existing converted images?" option. Otherwise the <i>Vary:Accept</i> header ' .
|
65 |
-
'will not be added and this can result in problems for users behind proxy servers (ie used in larger companies)'
|
66 |
-
);
|
67 |
-
}
|
68 |
-
}
|
69 |
-
|
70 |
-
$anyRedirectionToConverterEnabled = (($config['enable-redirection-to-converter']) || ($config['enable-redirection-to-webp-realizer']));
|
71 |
-
$anyRedirectionEnabled = ($anyRedirectionToConverterEnabled || $config['redirect-to-existing-in-htaccess']);
|
72 |
-
|
73 |
-
if ($anyRedirectionEnabled) {
|
74 |
-
if (PlatformInfo::definitelyNotGotModRewrite()) {
|
75 |
-
Messenger::printMessage(
|
76 |
-
'error',
|
77 |
-
"Rewriting isn't enabled on your server. ' .
|
78 |
-
'Currently, the only way to make WebP Express generate webp files is with rewriting. '
|
79 |
-
'If you got the webp files through other means, you can use CDN friendly mode and disable the rewrites. ' .
|
80 |
-
'Or perhaps you want to enable rewriting? Tell your host or system administrator to enable the 'mod_rewrite' module. ' .
|
81 |
-
'If you are on a shared host, chances are that mod_rewrite can be turned on in your control panel."
|
82 |
-
);
|
83 |
-
}
|
84 |
-
}
|
85 |
-
|
86 |
$cacheEnablerActivated = in_array('cache-enabler/cache-enabler.php', get_option('active_plugins', []));
|
87 |
if ($cacheEnablerActivated) {
|
88 |
$cacheEnablerSettings = get_option('cache-enabler', []);
|
@@ -222,11 +193,125 @@ if (Config::isConfigFileThere()) {
|
|
222 |
}
|
223 |
|
224 |
} else {
|
225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
226 |
Messenger::printMessage(
|
227 |
'warning',
|
228 |
-
|
229 |
-
'needs updating! (click <i>Save settings</i> to do so)<br>'
|
230 |
);
|
231 |
}
|
232 |
}
|
2 |
|
3 |
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
|
4 |
|
5 |
+
use \WebPExpress\HTAccessCapabilityTestRunner;
|
6 |
use \WebPExpress\Config;
|
7 |
use \WebPExpress\ConvertersHelper;
|
8 |
use \WebPExpress\DismissableMessages;
|
9 |
use \WebPExpress\FileHelper;
|
10 |
use \WebPExpress\HTAccess;
|
11 |
+
use \WebPExpress\HTAccessRules;
|
12 |
use \WebPExpress\Messenger;
|
13 |
use \WebPExpress\Paths;
|
14 |
use \WebPExpress\PlatformInfo;
|
15 |
use \WebPExpress\State;
|
16 |
|
17 |
+
// TODO: Move most of this file into a ProblemDetector class (SystemHealth)
|
18 |
+
|
19 |
if (!(State::getState('configured', false))) {
|
20 |
include __DIR__ . "/page-welcome.php";
|
21 |
|
25 |
|
26 |
}
|
27 |
|
28 |
+
$storedCapTests = $config['base-htaccess-on-these-capability-tests'];
|
29 |
|
30 |
/*
|
31 |
+
if (HTAccessCapabilityTestRunner::modRewriteWorking()) {
|
32 |
echo 'mod rewrite works. that is nice';
|
33 |
}*/
|
34 |
|
35 |
+
/*if (HTAccessCapabilityTestRunner::modHeaderWorking() === true) {
|
36 |
//echo 'nice!';
|
37 |
}*/
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
// Dissmiss page messages for which the condition no longer applies
|
40 |
if ($config['image-types'] != 1) {
|
54 |
$firstActiveAndWorkingConverterId = ConvertersHelper::getFirstWorkingAndActiveConverterId($config);
|
55 |
$workingIds = ConvertersHelper::getWorkingConverterIds($config);
|
56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
$cacheEnablerActivated = in_array('cache-enabler/cache-enabler.php', get_option('active_plugins', []));
|
58 |
if ($cacheEnablerActivated) {
|
59 |
$cacheEnablerSettings = get_option('cache-enabler', []);
|
193 |
}
|
194 |
|
195 |
} else {
|
196 |
+
|
197 |
+
if ($config['redirect-to-existing-in-htaccess']) {
|
198 |
+
if (PlatformInfo::isApacheOrLiteSpeed() && !(HTAccessCapabilityTestRunner::modHeaderWorking())) {
|
199 |
+
Messenger::printMessage(
|
200 |
+
'warning',
|
201 |
+
'It seems your server setup does not support headers in <i>.htaccess</i>. You should either fix this (install <i>mod_headers</i>) <i>or</i> ' .
|
202 |
+
'deactivate the "Enable direct redirection to existing converted images?" option. Otherwise the <i>Vary:Accept</i> header ' .
|
203 |
+
'will not be added and this can result in problems for users behind proxy servers (ie used in larger companies)'
|
204 |
+
);
|
205 |
+
}
|
206 |
+
}
|
207 |
+
|
208 |
+
$anyRedirectionToConverterEnabled = (($config['enable-redirection-to-converter']) || ($config['enable-redirection-to-webp-realizer']));
|
209 |
+
$anyRedirectionEnabled = ($anyRedirectionToConverterEnabled || $config['redirect-to-existing-in-htaccess']);
|
210 |
+
|
211 |
+
if ($anyRedirectionEnabled) {
|
212 |
+
if (PlatformInfo::isApacheOrLiteSpeed() && PlatformInfo::definitelyNotGotModRewrite()) {
|
213 |
+
Messenger::printMessage(
|
214 |
+
'warning',
|
215 |
+
"Rewriting isn't enabled on your server. " .
|
216 |
+
'You must either switch to "CDN friendly" mode or enable rewriting. ' .
|
217 |
+
"Tell your host or system administrator to enable the 'mod_rewrite' module. " .
|
218 |
+
'If you are on a shared host, chances are that mod_rewrite can be turned on in your control panel.'
|
219 |
+
);
|
220 |
+
}
|
221 |
+
}
|
222 |
+
|
223 |
+
if ($anyRedirectionToConverterEnabled) {
|
224 |
+
$canRunInWod = HTAccessCapabilityTestRunner::canRunTestScriptInWOD();
|
225 |
+
$canRunInWod2 = HTAccessCapabilityTestRunner::canRunTestScriptInWOD2();
|
226 |
+
if (!$canRunInWod && !$canRunInWod2) {
|
227 |
+
$turnedOn = [];
|
228 |
+
if ($config['enable-redirection-to-converter']) {
|
229 |
+
$turnedOn[] = '"Enable redirection to converter"';
|
230 |
+
}
|
231 |
+
if ($config['enable-redirection-to-webp-realizer']) {
|
232 |
+
$turnedOn[] = '"Create webp files upon request?""';
|
233 |
+
}
|
234 |
+
Messenger::printMessage(
|
235 |
+
'warning',
|
236 |
+
'<p>You have turned on ' . implode(' and ', $turnedOn) .
|
237 |
+
'. However, ' . (count($turnedOn) == 2 ? 'these features' : 'this feature') .
|
238 |
+
' does not work on your current server settings / wordpress setup, ' .
|
239 |
+
' because the PHP scripts in the plugin folder (in the "wod" and "wod2" subfolders) fails to run ' .
|
240 |
+
' when requested directly. You can try to fix the problem or simply turn ' .
|
241 |
+
(count($turnedOn) == 2 ? 'them' : 'it') .
|
242 |
+
' off and rely on "Convert on upload" and "Bulk Convert" to get the images converted.</p>' .
|
243 |
+
'<p>If you are going to try to solve the problem, you need at least one of the following pages ' .
|
244 |
+
'to display "pong": ' .
|
245 |
+
'<a href="' . Paths::getWebPExpressPluginUrl() . '/wod/ping.php" target="_blank">wod-test</a>' .
|
246 |
+
' or <a href="' . Paths::getWebPExpressPluginUrl() . '/wod2/ping.php" target="_blank">wod2-test</a>' .
|
247 |
+
'. The problem will typically be found in the server configuration or a security plugin. ' .
|
248 |
+
'If one of the links results in a 403 Permission denied, look out for "deny" and "denied" in ' .
|
249 |
+
'httpd.conf, /etc/apache/sites-enabled/your-site.conf and in parent .htaccess files.' .
|
250 |
+
'</p>.'
|
251 |
+
);
|
252 |
+
}
|
253 |
+
// We currently allow the "canRunTestScriptInWOD" test not to be stored,
|
254 |
+
// If it is not stored, it means .htaccess files are pointing to "wod"
|
255 |
+
// PS: the logic of where it is stored happens in HTAccessRules::getWodUrlPath
|
256 |
+
// - we mimic it here.
|
257 |
+
$pointingToWod = true; // true = pointing to "wod", false = pointing to "wod2"
|
258 |
+
$hasWODTestBeenRun = isset($storedCapTests['canRunTestScriptInWOD']);
|
259 |
+
if ($hasWODTestBeenRun && !($storedCapTests['canRunTestScriptInWOD'])) {
|
260 |
+
$pointingToWod = false;
|
261 |
+
}
|
262 |
+
$canOnlyRunInWod = $canRunInWod && !$canRunInWod2;
|
263 |
+
if ($canOnlyRunInWod && !$pointingToWod) {
|
264 |
+
Messenger::printMessage(
|
265 |
+
'warning',
|
266 |
+
'The conversion script cannot currently be run. ' .
|
267 |
+
'However, simply click "Save settings <b>and force new .htaccess rules</b>" to fix it. ' .
|
268 |
+
'(this will point to the script in the "wod" folder rather than "wod2")'
|
269 |
+
);
|
270 |
+
}
|
271 |
+
|
272 |
+
$canOnlyRunInWod2 = $canRunInWod2 && !$canRunInWod;
|
273 |
+
if ($canOnlyRunInWod2 && $pointingToWod) {
|
274 |
+
Messenger::printMessage(
|
275 |
+
'warning',
|
276 |
+
'The conversion script cannot currently be run. ' .
|
277 |
+
'However, simply click "Save settings <b>and force new .htaccess rules</b>" to fix it. ' .
|
278 |
+
'(this will point to the script in the "wod2" folder rather than "wod")'
|
279 |
+
);
|
280 |
+
}
|
281 |
+
|
282 |
+
}
|
283 |
+
|
284 |
+
if (HTAccessRules::arePathsUsedInHTAccessOutdated()) {
|
285 |
+
|
286 |
+
$pathsGoingToBeUsedInHtaccess = [
|
287 |
+
'wod-url-path' => Paths::getWodUrlPath(),
|
288 |
+
];
|
289 |
+
|
290 |
+
$config2 = Config::loadConfig();
|
291 |
+
if ($config2 === false) {
|
292 |
+
Messenger::printMessage(
|
293 |
+
'warning',
|
294 |
+
'Warning: Config file cannot be loaded. Perhaps clicking ' .
|
295 |
+
'<i>Save settings</i> will solve it<br>'
|
296 |
+
);
|
297 |
+
}
|
298 |
+
|
299 |
+
$warningMsg = 'Warning: Wordpress paths have changed since the last time the Rewrite Rules was generated. The rules ' .
|
300 |
+
'needs updating! (click <i>Save settings</i> to do so)<br><br>' .
|
301 |
+
'The following have changed:<br>';
|
302 |
+
|
303 |
+
foreach ($config2['paths-used-in-htaccess'] as $prop => $value) {
|
304 |
+
if (isset($pathsGoingToBeUsedInHtaccess[$prop])) {
|
305 |
+
if ($value != $pathsGoingToBeUsedInHtaccess[$prop]) {
|
306 |
+
$warningMsg .= '- ' . $prop . '(was: ' . $value . '- but is now: ' . $pathsGoingToBeUsedInHtaccess[$prop] . ')<br>';
|
307 |
+
}
|
308 |
+
}
|
309 |
+
}
|
310 |
+
|
311 |
+
|
312 |
Messenger::printMessage(
|
313 |
'warning',
|
314 |
+
$warningMsg
|
|
|
315 |
);
|
316 |
}
|
317 |
}
|
@@ -3,11 +3,11 @@
|
|
3 |
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
|
4 |
|
5 |
use \WebPExpress\CacheMover;
|
6 |
-
use \WebPExpress\CapabilityTest;
|
7 |
use \WebPExpress\Config;
|
8 |
use \WebPExpress\ConvertersHelper;
|
9 |
use \WebPExpress\DismissableMessages;
|
10 |
use \WebPExpress\HTAccess;
|
|
|
11 |
use \WebPExpress\Messenger;
|
12 |
use \WebPExpress\PathHelper;
|
13 |
use \WebPExpress\Paths;
|
@@ -369,6 +369,7 @@ $sanitized = [
|
|
369 |
]),
|
370 |
'cache-control-max-age' => webpexpress_getSanitizedChooseFromSet('cache-control-max-age', 'one-hour', [
|
371 |
'one-second',
|
|
|
372 |
'one-hour',
|
373 |
'one-day',
|
374 |
'one-week',
|
@@ -491,23 +492,19 @@ $config = array_merge($config, [
|
|
491 |
'forward-query-string' => true,
|
492 |
]);
|
493 |
|
494 |
-
// Set options that are available in all operation modes, except the "CDN friendly" mode
|
495 |
-
if ($sanitized['operation-mode'] != 'cdn-friendly') {
|
496 |
-
$config['cache-control'] = $sanitized['cache-control'];
|
497 |
-
switch ($sanitized['cache-control']) {
|
498 |
-
case 'no-header':
|
499 |
-
break;
|
500 |
-
case 'set':
|
501 |
-
$config['cache-control-max-age'] = $sanitized['cache-control-max-age'];
|
502 |
-
$config['cache-control-public'] = ($sanitized['cache-control-public'] == 'public');
|
503 |
-
break;
|
504 |
-
case 'custom':
|
505 |
-
$config['cache-control-custom'] = $sanitized['cache-control-custom'];
|
506 |
-
break;
|
507 |
-
}
|
508 |
-
}
|
509 |
-
|
510 |
// Set options that are available in ALL operation modes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
511 |
|
512 |
// Alter HTML
|
513 |
$config['alter-html'] = [];
|
@@ -684,8 +681,9 @@ if ($sanitized['operation-mode'] != $sanitized['change-operation-mode']) {
|
|
684 |
}
|
685 |
}
|
686 |
|
687 |
-
// If we are going to save .htaccess, run and store capability tests first
|
688 |
-
|
|
|
689 |
Config::runAndStoreCapabilityTests($config);
|
690 |
}
|
691 |
|
3 |
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
|
4 |
|
5 |
use \WebPExpress\CacheMover;
|
|
|
6 |
use \WebPExpress\Config;
|
7 |
use \WebPExpress\ConvertersHelper;
|
8 |
use \WebPExpress\DismissableMessages;
|
9 |
use \WebPExpress\HTAccess;
|
10 |
+
use \WebPExpress\HTAccessRules;
|
11 |
use \WebPExpress\Messenger;
|
12 |
use \WebPExpress\PathHelper;
|
13 |
use \WebPExpress\Paths;
|
369 |
]),
|
370 |
'cache-control-max-age' => webpexpress_getSanitizedChooseFromSet('cache-control-max-age', 'one-hour', [
|
371 |
'one-second',
|
372 |
+
'one-minute',
|
373 |
'one-hour',
|
374 |
'one-day',
|
375 |
'one-week',
|
492 |
'forward-query-string' => true,
|
493 |
]);
|
494 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
495 |
// Set options that are available in ALL operation modes
|
496 |
+
$config['cache-control'] = $sanitized['cache-control'];
|
497 |
+
switch ($sanitized['cache-control']) {
|
498 |
+
case 'no-header':
|
499 |
+
break;
|
500 |
+
case 'set':
|
501 |
+
$config['cache-control-max-age'] = $sanitized['cache-control-max-age'];
|
502 |
+
$config['cache-control-public'] = ($sanitized['cache-control-public'] == 'public');
|
503 |
+
break;
|
504 |
+
case 'custom':
|
505 |
+
$config['cache-control-custom'] = $sanitized['cache-control-custom'];
|
506 |
+
break;
|
507 |
+
}
|
508 |
|
509 |
// Alter HTML
|
510 |
$config['alter-html'] = [];
|
681 |
}
|
682 |
}
|
683 |
|
684 |
+
// If we are going to save .htaccess, run and store capability tests first
|
685 |
+
// (we should only store results when .htaccess is updated as well)
|
686 |
+
if ($sanitized['force'] || HTAccessRules::doesRewriteRulesNeedUpdate($config)) {
|
687 |
Config::runAndStoreCapabilityTests($config);
|
688 |
}
|
689 |
|
@@ -104,6 +104,30 @@ return array(
|
|
104 |
'Composer\\Installers\\ZikulaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
|
105 |
'DOMUtilForWebP\\ImageUrlReplacer' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
|
106 |
'DOMUtilForWebP\\PictureTags' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
'ImageMimeTypeGuesser\\Detectors\\AbstractDetector' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php',
|
108 |
'ImageMimeTypeGuesser\\Detectors\\ExifImageType' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php',
|
109 |
'ImageMimeTypeGuesser\\Detectors\\FInfo' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php',
|
104 |
'Composer\\Installers\\ZikulaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
|
105 |
'DOMUtilForWebP\\ImageUrlReplacer' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
|
106 |
'DOMUtilForWebP\\PictureTags' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
|
107 |
+
'HtaccessCapabilityTester\\HtaccessCapabilityTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/HtaccessCapabilityTester.php',
|
108 |
+
'HtaccessCapabilityTester\\HttpRequesterInterface' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/HttpRequesterInterface.php',
|
109 |
+
'HtaccessCapabilityTester\\HttpResponse' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/HttpResponse.php',
|
110 |
+
'HtaccessCapabilityTester\\SimpleHttpRequester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/SimpleHttpRequester.php',
|
111 |
+
'HtaccessCapabilityTester\\SimpleTestFileLineUpper' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/SimpleTestFileLineUpper.php',
|
112 |
+
'HtaccessCapabilityTester\\TestFilesLineUpperInterface' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/TestFilesLineUpperInterface.php',
|
113 |
+
'HtaccessCapabilityTester\\TestResult' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/TestResult.php',
|
114 |
+
'HtaccessCapabilityTester\\TestResultCache' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/TestResultCache.php',
|
115 |
+
'HtaccessCapabilityTester\\Testers\\AbstractTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/AbstractTester.php',
|
116 |
+
'HtaccessCapabilityTester\\Testers\\AddTypeTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/AddTypeTester.php',
|
117 |
+
'HtaccessCapabilityTester\\Testers\\ContentDigestTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/ContentDigestTester.php',
|
118 |
+
'HtaccessCapabilityTester\\Testers\\CrashTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/CrashTester.php',
|
119 |
+
'HtaccessCapabilityTester\\Testers\\CustomTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/CustomTester.php',
|
120 |
+
'HtaccessCapabilityTester\\Testers\\DirectoryIndexTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/DirectoryIndexTester.php',
|
121 |
+
'HtaccessCapabilityTester\\Testers\\HeaderSetTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/HeaderSetTester.php',
|
122 |
+
'HtaccessCapabilityTester\\Testers\\Helpers\\ResponseInterpreter' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/Helpers/ResponseInterpreter.php',
|
123 |
+
'HtaccessCapabilityTester\\Testers\\HtaccessEnabledTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/HtaccessEnabledTester.php',
|
124 |
+
'HtaccessCapabilityTester\\Testers\\InnocentRequestTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/InnocentRequestTester.php',
|
125 |
+
'HtaccessCapabilityTester\\Testers\\ModuleLoadedTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/ModuleLoadedTester.php',
|
126 |
+
'HtaccessCapabilityTester\\Testers\\PassInfoFromRewriteToScriptThroughEnvTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughEnvTester.php',
|
127 |
+
'HtaccessCapabilityTester\\Testers\\PassInfoFromRewriteToScriptThroughRequestHeaderTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTester.php',
|
128 |
+
'HtaccessCapabilityTester\\Testers\\RequestHeaderTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/RequestHeaderTester.php',
|
129 |
+
'HtaccessCapabilityTester\\Testers\\RewriteTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/RewriteTester.php',
|
130 |
+
'HtaccessCapabilityTester\\Testers\\ServerSignatureTester' => $vendorDir . '/rosell-dk/htaccess-capability-tester/src/Testers/ServerSignatureTester.php',
|
131 |
'ImageMimeTypeGuesser\\Detectors\\AbstractDetector' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php',
|
132 |
'ImageMimeTypeGuesser\\Detectors\\ExifImageType' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php',
|
133 |
'ImageMimeTypeGuesser\\Detectors\\FInfo' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php',
|
@@ -9,6 +9,7 @@ return array(
|
|
9 |
'WebPConvert\\' => array($vendorDir . '/rosell-dk/webp-convert/src'),
|
10 |
'WebPConvertCloudService\\' => array($vendorDir . '/rosell-dk/webp-convert-cloud-service/src'),
|
11 |
'ImageMimeTypeGuesser\\' => array($vendorDir . '/rosell-dk/image-mime-type-guesser/src'),
|
|
|
12 |
'DOMUtilForWebP\\' => array($vendorDir . '/rosell-dk/dom-util-for-webp/src'),
|
13 |
'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
|
14 |
);
|
9 |
'WebPConvert\\' => array($vendorDir . '/rosell-dk/webp-convert/src'),
|
10 |
'WebPConvertCloudService\\' => array($vendorDir . '/rosell-dk/webp-convert-cloud-service/src'),
|
11 |
'ImageMimeTypeGuesser\\' => array($vendorDir . '/rosell-dk/image-mime-type-guesser/src'),
|
12 |
+
'HtaccessCapabilityTester\\' => array($vendorDir . '/rosell-dk/htaccess-capability-tester/src'),
|
13 |
'DOMUtilForWebP\\' => array($vendorDir . '/rosell-dk/dom-util-for-webp/src'),
|
14 |
'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
|
15 |
);
|
@@ -16,6 +16,10 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
|
|
16 |
array (
|
17 |
'ImageMimeTypeGuesser\\' => 21,
|
18 |
),
|
|
|
|
|
|
|
|
|
19 |
'D' =>
|
20 |
array (
|
21 |
'DOMUtilForWebP\\' => 15,
|
@@ -39,6 +43,10 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
|
|
39 |
array (
|
40 |
0 => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src',
|
41 |
),
|
|
|
|
|
|
|
|
|
42 |
'DOMUtilForWebP\\' =>
|
43 |
array (
|
44 |
0 => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src',
|
@@ -148,6 +156,30 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
|
|
148 |
'Composer\\Installers\\ZikulaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
|
149 |
'DOMUtilForWebP\\ImageUrlReplacer' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
|
150 |
'DOMUtilForWebP\\PictureTags' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
'ImageMimeTypeGuesser\\Detectors\\AbstractDetector' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php',
|
152 |
'ImageMimeTypeGuesser\\Detectors\\ExifImageType' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php',
|
153 |
'ImageMimeTypeGuesser\\Detectors\\FInfo' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php',
|
16 |
array (
|
17 |
'ImageMimeTypeGuesser\\' => 21,
|
18 |
),
|
19 |
+
'H' =>
|
20 |
+
array (
|
21 |
+
'HtaccessCapabilityTester\\' => 25,
|
22 |
+
),
|
23 |
'D' =>
|
24 |
array (
|
25 |
'DOMUtilForWebP\\' => 15,
|
43 |
array (
|
44 |
0 => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src',
|
45 |
),
|
46 |
+
'HtaccessCapabilityTester\\' =>
|
47 |
+
array (
|
48 |
+
0 => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src',
|
49 |
+
),
|
50 |
'DOMUtilForWebP\\' =>
|
51 |
array (
|
52 |
0 => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src',
|
156 |
'Composer\\Installers\\ZikulaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
|
157 |
'DOMUtilForWebP\\ImageUrlReplacer' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
|
158 |
'DOMUtilForWebP\\PictureTags' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
|
159 |
+
'HtaccessCapabilityTester\\HtaccessCapabilityTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/HtaccessCapabilityTester.php',
|
160 |
+
'HtaccessCapabilityTester\\HttpRequesterInterface' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/HttpRequesterInterface.php',
|
161 |
+
'HtaccessCapabilityTester\\HttpResponse' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/HttpResponse.php',
|
162 |
+
'HtaccessCapabilityTester\\SimpleHttpRequester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/SimpleHttpRequester.php',
|
163 |
+
'HtaccessCapabilityTester\\SimpleTestFileLineUpper' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/SimpleTestFileLineUpper.php',
|
164 |
+
'HtaccessCapabilityTester\\TestFilesLineUpperInterface' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/TestFilesLineUpperInterface.php',
|
165 |
+
'HtaccessCapabilityTester\\TestResult' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/TestResult.php',
|
166 |
+
'HtaccessCapabilityTester\\TestResultCache' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/TestResultCache.php',
|
167 |
+
'HtaccessCapabilityTester\\Testers\\AbstractTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/AbstractTester.php',
|
168 |
+
'HtaccessCapabilityTester\\Testers\\AddTypeTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/AddTypeTester.php',
|
169 |
+
'HtaccessCapabilityTester\\Testers\\ContentDigestTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/ContentDigestTester.php',
|
170 |
+
'HtaccessCapabilityTester\\Testers\\CrashTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/CrashTester.php',
|
171 |
+
'HtaccessCapabilityTester\\Testers\\CustomTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/CustomTester.php',
|
172 |
+
'HtaccessCapabilityTester\\Testers\\DirectoryIndexTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/DirectoryIndexTester.php',
|
173 |
+
'HtaccessCapabilityTester\\Testers\\HeaderSetTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/HeaderSetTester.php',
|
174 |
+
'HtaccessCapabilityTester\\Testers\\Helpers\\ResponseInterpreter' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/Helpers/ResponseInterpreter.php',
|
175 |
+
'HtaccessCapabilityTester\\Testers\\HtaccessEnabledTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/HtaccessEnabledTester.php',
|
176 |
+
'HtaccessCapabilityTester\\Testers\\InnocentRequestTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/InnocentRequestTester.php',
|
177 |
+
'HtaccessCapabilityTester\\Testers\\ModuleLoadedTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/ModuleLoadedTester.php',
|
178 |
+
'HtaccessCapabilityTester\\Testers\\PassInfoFromRewriteToScriptThroughEnvTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughEnvTester.php',
|
179 |
+
'HtaccessCapabilityTester\\Testers\\PassInfoFromRewriteToScriptThroughRequestHeaderTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTester.php',
|
180 |
+
'HtaccessCapabilityTester\\Testers\\RequestHeaderTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/RequestHeaderTester.php',
|
181 |
+
'HtaccessCapabilityTester\\Testers\\RewriteTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/RewriteTester.php',
|
182 |
+
'HtaccessCapabilityTester\\Testers\\ServerSignatureTester' => __DIR__ . '/..' . '/rosell-dk/htaccess-capability-tester/src/Testers/ServerSignatureTester.php',
|
183 |
'ImageMimeTypeGuesser\\Detectors\\AbstractDetector' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php',
|
184 |
'ImageMimeTypeGuesser\\Detectors\\ExifImageType' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php',
|
185 |
'ImageMimeTypeGuesser\\Detectors\\FInfo' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php',
|
@@ -188,6 +188,68 @@
|
|
188 |
"replace"
|
189 |
]
|
190 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
{
|
192 |
"name": "rosell-dk/image-mime-type-guesser",
|
193 |
"version": "0.3",
|
188 |
"replace"
|
189 |
]
|
190 |
},
|
191 |
+
{
|
192 |
+
"name": "rosell-dk/htaccess-capability-tester",
|
193 |
+
"version": "0.8",
|
194 |
+
"version_normalized": "0.8.0.0",
|
195 |
+
"source": {
|
196 |
+
"type": "git",
|
197 |
+
"url": "https://github.com/rosell-dk/htaccess-capability-tester.git",
|
198 |
+
"reference": "3556f664c5533643c0cb0beceaf1947abd7aed7a"
|
199 |
+
},
|
200 |
+
"dist": {
|
201 |
+
"type": "zip",
|
202 |
+
"url": "https://api.github.com/repos/rosell-dk/htaccess-capability-tester/zipball/3556f664c5533643c0cb0beceaf1947abd7aed7a",
|
203 |
+
"reference": "3556f664c5533643c0cb0beceaf1947abd7aed7a",
|
204 |
+
"shasum": ""
|
205 |
+
},
|
206 |
+
"require": {
|
207 |
+
"php": "^5.6 | ^7.0"
|
208 |
+
},
|
209 |
+
"require-dev": {
|
210 |
+
"phpunit/php-code-coverage": "dev-4.0-dev as 4.0.4",
|
211 |
+
"phpunit/phpunit": "^5.7",
|
212 |
+
"squizlabs/php_codesniffer": "3.*"
|
213 |
+
},
|
214 |
+
"suggest": {
|
215 |
+
"php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
|
216 |
+
},
|
217 |
+
"time": "2020-09-23T10:46:34+00:00",
|
218 |
+
"type": "library",
|
219 |
+
"extra": {
|
220 |
+
"scripts-descriptions": {
|
221 |
+
"ci": "Run tests before CI",
|
222 |
+
"phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
|
223 |
+
"phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
|
224 |
+
"cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
|
225 |
+
"cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
|
226 |
+
"test": "Launches the preconfigured PHPUnit"
|
227 |
+
}
|
228 |
+
},
|
229 |
+
"installation-source": "dist",
|
230 |
+
"autoload": {
|
231 |
+
"psr-4": {
|
232 |
+
"HtaccessCapabilityTester\\": "src/"
|
233 |
+
}
|
234 |
+
},
|
235 |
+
"notification-url": "https://packagist.org/downloads/",
|
236 |
+
"license": [
|
237 |
+
"MIT"
|
238 |
+
],
|
239 |
+
"authors": [
|
240 |
+
{
|
241 |
+
"name": "Bjørn Rosell",
|
242 |
+
"homepage": "https://www.bitwise-it.dk/contact",
|
243 |
+
"role": "Project Author"
|
244 |
+
}
|
245 |
+
],
|
246 |
+
"description": "Test the capabilities of .htaccess files on the server using live tests",
|
247 |
+
"keywords": [
|
248 |
+
".htaccess",
|
249 |
+
"apache",
|
250 |
+
"litespeed"
|
251 |
+
]
|
252 |
+
},
|
253 |
{
|
254 |
"name": "rosell-dk/image-mime-type-guesser",
|
255 |
"version": "0.3",
|
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
ko_fi: rosell
|
2 |
+
patreon: rosell
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
language: php
|
2 |
+
os: linux
|
3 |
+
|
4 |
+
matrix:
|
5 |
+
fast_finish: true
|
6 |
+
include:
|
7 |
+
- name: "PHP 7.4, Xenial"
|
8 |
+
php: 7.4
|
9 |
+
dist: xenial
|
10 |
+
env:
|
11 |
+
- PHPSTAN=1
|
12 |
+
- UPLOADCOVERAGE=0
|
13 |
+
- name: "PHP 7.3, Xenial"
|
14 |
+
php: 7.3
|
15 |
+
dist: xenial
|
16 |
+
env:
|
17 |
+
- PHPSTAN=1
|
18 |
+
- UPLOADCOVERAGE=1
|
19 |
+
- name: "PHP 7.2, Xenial"
|
20 |
+
php: 7.2
|
21 |
+
dist: xenial
|
22 |
+
env:
|
23 |
+
- PHPSTAN=1
|
24 |
+
- UPLOADCOVERAGE=0
|
25 |
+
- name: "PHP 7.1, Xenial"
|
26 |
+
php: 7.1
|
27 |
+
dist: xenial
|
28 |
+
env:
|
29 |
+
- PHPSTAN=1
|
30 |
+
- UPLOADCOVERAGE=0
|
31 |
+
- name: "PHP 7.0, Xenial"
|
32 |
+
php: 7.0
|
33 |
+
dist: xenial
|
34 |
+
env:
|
35 |
+
- PHPSTAN=0
|
36 |
+
- UPLOADCOVERAGE=0
|
37 |
+
- name: "PHP 5.6, Trusty"
|
38 |
+
php: 5.6
|
39 |
+
dist: trusty
|
40 |
+
env:
|
41 |
+
- PHPSTAN=0
|
42 |
+
- UPLOADCOVERAGE=0
|
43 |
+
|
44 |
+
before_script:
|
45 |
+
- (composer self-update; true)
|
46 |
+
- composer install
|
47 |
+
- if [[ $PHPSTAN == 1 ]]; then composer require --dev phpstan/phpstan:"^0.12.37"; fi
|
48 |
+
|
49 |
+
script:
|
50 |
+
- composer test
|
51 |
+
- if [[ $PHPSTAN == 1 ]]; then vendor/bin/phpstan analyse src --level=4; fi
|
52 |
+
|
53 |
+
after_script:
|
54 |
+
- |
|
55 |
+
if [[ $UPLOADCOVERAGE == 1 ]]; then
|
56 |
+
wget https://scrutinizer-ci.com/ocular.phar
|
57 |
+
php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
58 |
+
fi
|
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 3, 29 June 2007
|
3 |
+
|
4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
6 |
+
of this license document, but changing it is not allowed.
|
7 |
+
|
8 |
+
Preamble
|
9 |
+
|
10 |
+
The GNU General Public License is a free, copyleft license for
|
11 |
+
software and other kinds of works.
|
12 |
+
|
13 |
+
The licenses for most software and other practical works are designed
|
14 |
+
to take away your freedom to share and change the works. By contrast,
|
15 |
+
the GNU General Public License is intended to guarantee your freedom to
|
16 |
+
share and change all versions of a program--to make sure it remains free
|
17 |
+
software for all its users. We, the Free Software Foundation, use the
|
18 |
+
GNU General Public License for most of our software; it applies also to
|
19 |
+
any other work released this way by its authors. You can apply it to
|
20 |
+
your programs, too.
|
21 |
+
|
22 |
+
When we speak of free software, we are referring to freedom, not
|
23 |
+
price. Our General Public Licenses are designed to make sure that you
|
24 |
+
have the freedom to distribute copies of free software (and charge for
|
25 |
+
them if you wish), that you receive source code or can get it if you
|
26 |
+
want it, that you can change the software or use pieces of it in new
|
27 |
+
free programs, and that you know you can do these things.
|
28 |
+
|
29 |
+
To protect your rights, we need to prevent others from denying you
|
30 |
+
these rights or asking you to surrender the rights. Therefore, you have
|
31 |
+
certain responsibilities if you distribute copies of the software, or if
|
32 |
+
you modify it: responsibilities to respect the freedom of others.
|
33 |
+
|
34 |
+
For example, if you distribute copies of such a program, whether
|
35 |
+
gratis or for a fee, you must pass on to the recipients the same
|
36 |
+
freedoms that you received. You must make sure that they, too, receive
|
37 |
+
or can get the source code. And you must show them these terms so they
|
38 |
+
know their rights.
|
39 |
+
|
40 |
+
Developers that use the GNU GPL protect your rights with two steps:
|
41 |
+
(1) assert copyright on the software, and (2) offer you this License
|
42 |
+
giving you legal permission to copy, distribute and/or modify it.
|
43 |
+
|
44 |
+
For the developers' and authors' protection, the GPL clearly explains
|
45 |
+
that there is no warranty for this free software. For both users' and
|
46 |
+
authors' sake, the GPL requires that modified versions be marked as
|
47 |
+
changed, so that their problems will not be attributed erroneously to
|
48 |
+
authors of previous versions.
|
49 |
+
|
50 |
+
Some devices are designed to deny users access to install or run
|
51 |
+
modified versions of the software inside them, although the manufacturer
|
52 |
+
can do so. This is fundamentally incompatible with the aim of
|
53 |
+
protecting users' freedom to change the software. The systematic
|
54 |
+
pattern of such abuse occurs in the area of products for individuals to
|
55 |
+
use, which is precisely where it is most unacceptable. Therefore, we
|
56 |
+
have designed this version of the GPL to prohibit the practice for those
|
57 |
+
products. If such problems arise substantially in other domains, we
|
58 |
+
stand ready to extend this provision to those domains in future versions
|
59 |
+
of the GPL, as needed to protect the freedom of users.
|
60 |
+
|
61 |
+
Finally, every program is threatened constantly by software patents.
|
62 |
+
States should not allow patents to restrict development and use of
|
63 |
+
software on general-purpose computers, but in those that do, we wish to
|
64 |
+
avoid the special danger that patents applied to a free program could
|
65 |
+
make it effectively proprietary. To prevent this, the GPL assures that
|
66 |
+
patents cannot be used to render the program non-free.
|
67 |
+
|
68 |
+
The precise terms and conditions for copying, distribution and
|
69 |
+
modification follow.
|
70 |
+
|
71 |
+
TERMS AND CONDITIONS
|
72 |
+
|
73 |
+
0. Definitions.
|
74 |
+
|
75 |
+
"This License" refers to version 3 of the GNU General Public License.
|
76 |
+
|
77 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
78 |
+
works, such as semiconductor masks.
|
79 |
+
|
80 |
+
"The Program" refers to any copyrightable work licensed under this
|
81 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
82 |
+
"recipients" may be individuals or organizations.
|
83 |
+
|
84 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
85 |
+
in a fashion requiring copyright permission, other than the making of an
|
86 |
+
exact copy. The resulting work is called a "modified version" of the
|
87 |
+
earlier work or a work "based on" the earlier work.
|
88 |
+
|
89 |
+
A "covered work" means either the unmodified Program or a work based
|
90 |
+
on the Program.
|
91 |
+
|
92 |
+
To "propagate" a work means to do anything with it that, without
|
93 |
+
permission, would make you directly or secondarily liable for
|
94 |
+
infringement under applicable copyright law, except executing it on a
|
95 |
+
computer or modifying a private copy. Propagation includes copying,
|
96 |
+
distribution (with or without modification), making available to the
|
97 |
+
public, and in some countries other activities as well.
|
98 |
+
|
99 |
+
To "convey" a work means any kind of propagation that enables other
|
100 |
+
parties to make or receive copies. Mere interaction with a user through
|
101 |
+
a computer network, with no transfer of a copy, is not conveying.
|
102 |
+
|
103 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
104 |
+
to the extent that it includes a convenient and prominently visible
|
105 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
106 |
+
tells the user that there is no warranty for the work (except to the
|
107 |
+
extent that warranties are provided), that licensees may convey the
|
108 |
+
work under this License, and how to view a copy of this License. If
|
109 |
+
the interface presents a list of user commands or options, such as a
|
110 |
+
menu, a prominent item in the list meets this criterion.
|
111 |
+
|
112 |
+
1. Source Code.
|
113 |
+
|
114 |
+
The "source code" for a work means the preferred form of the work
|
115 |
+
for making modifications to it. "Object code" means any non-source
|
116 |
+
form of a work.
|
117 |
+
|
118 |
+
A "Standard Interface" means an interface that either is an official
|
119 |
+
standard defined by a recognized standards body, or, in the case of
|
120 |
+
interfaces specified for a particular programming language, one that
|
121 |
+
is widely used among developers working in that language.
|
122 |
+
|
123 |
+
The "System Libraries" of an executable work include anything, other
|
124 |
+
than the work as a whole, that (a) is included in the normal form of
|
125 |
+
packaging a Major Component, but which is not part of that Major
|
126 |
+
Component, and (b) serves only to enable use of the work with that
|
127 |
+
Major Component, or to implement a Standard Interface for which an
|
128 |
+
implementation is available to the public in source code form. A
|
129 |
+
"Major Component", in this context, means a major essential component
|
130 |
+
(kernel, window system, and so on) of the specific operating system
|
131 |
+
(if any) on which the executable work runs, or a compiler used to
|
132 |
+
produce the work, or an object code interpreter used to run it.
|
133 |
+
|
134 |
+
The "Corresponding Source" for a work in object code form means all
|
135 |
+
the source code needed to generate, install, and (for an executable
|
136 |
+
work) run the object code and to modify the work, including scripts to
|
137 |
+
control those activities. However, it does not include the work's
|
138 |
+
System Libraries, or general-purpose tools or generally available free
|
139 |
+
programs which are used unmodified in performing those activities but
|
140 |
+
which are not part of the work. For example, Corresponding Source
|
141 |
+
includes interface definition files associated with source files for
|
142 |
+
the work, and the source code for shared libraries and dynamically
|
143 |
+
linked subprograms that the work is specifically designed to require,
|
144 |
+
such as by intimate data communication or control flow between those
|
145 |
+
subprograms and other parts of the work.
|
146 |
+
|
147 |
+
The Corresponding Source need not include anything that users
|
148 |
+
can regenerate automatically from other parts of the Corresponding
|
149 |
+
Source.
|
150 |
+
|
151 |
+
The Corresponding Source for a work in source code form is that
|
152 |
+
same work.
|
153 |
+
|
154 |
+
2. Basic Permissions.
|
155 |
+
|
156 |
+
All rights granted under this License are granted for the term of
|
157 |
+
copyright on the Program, and are irrevocable provided the stated
|
158 |
+
conditions are met. This License explicitly affirms your unlimited
|
159 |
+
permission to run the unmodified Program. The output from running a
|
160 |
+
covered work is covered by this License only if the output, given its
|
161 |
+
content, constitutes a covered work. This License acknowledges your
|
162 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
163 |
+
|
164 |
+
You may make, run and propagate covered works that you do not
|
165 |
+
convey, without conditions so long as your license otherwise remains
|
166 |
+
in force. You may convey covered works to others for the sole purpose
|
167 |
+
of having them make modifications exclusively for you, or provide you
|
168 |
+
with facilities for running those works, provided that you comply with
|
169 |
+
the terms of this License in conveying all material for which you do
|
170 |
+
not control copyright. Those thus making or running the covered works
|
171 |
+
for you must do so exclusively on your behalf, under your direction
|
172 |
+
and control, on terms that prohibit them from making any copies of
|
173 |
+
your copyrighted material outside their relationship with you.
|
174 |
+
|
175 |
+
Conveying under any other circumstances is permitted solely under
|
176 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
177 |
+
makes it unnecessary.
|
178 |
+
|
179 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
180 |
+
|
181 |
+
No covered work shall be deemed part of an effective technological
|
182 |
+
measure under any applicable law fulfilling obligations under article
|
183 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
184 |
+
similar laws prohibiting or restricting circumvention of such
|
185 |
+
measures.
|
186 |
+
|
187 |
+
When you convey a covered work, you waive any legal power to forbid
|
188 |
+
circumvention of technological measures to the extent such circumvention
|
189 |
+
is effected by exercising rights under this License with respect to
|
190 |
+
the covered work, and you disclaim any intention to limit operation or
|
191 |
+
modification of the work as a means of enforcing, against the work's
|
192 |
+
users, your or third parties' legal rights to forbid circumvention of
|
193 |
+
technological measures.
|
194 |
+
|
195 |
+
4. Conveying Verbatim Copies.
|
196 |
+
|
197 |
+
You may convey verbatim copies of the Program's source code as you
|
198 |
+
receive it, in any medium, provided that you conspicuously and
|
199 |
+
appropriately publish on each copy an appropriate copyright notice;
|
200 |
+
keep intact all notices stating that this License and any
|
201 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
202 |
+
keep intact all notices of the absence of any warranty; and give all
|
203 |
+
recipients a copy of this License along with the Program.
|
204 |
+
|
205 |
+
You may charge any price or no price for each copy that you convey,
|
206 |
+
and you may offer support or warranty protection for a fee.
|
207 |
+
|
208 |
+
5. Conveying Modified Source Versions.
|
209 |
+
|
210 |
+
You may convey a work based on the Program, or the modifications to
|
211 |
+
produce it from the Program, in the form of source code under the
|
212 |
+
terms of section 4, provided that you also meet all of these conditions:
|
213 |
+
|
214 |
+
a) The work must carry prominent notices stating that you modified
|
215 |
+
it, and giving a relevant date.
|
216 |
+
|
217 |
+
b) The work must carry prominent notices stating that it is
|
218 |
+
released under this License and any conditions added under section
|
219 |
+
7. This requirement modifies the requirement in section 4 to
|
220 |
+
"keep intact all notices".
|
221 |
+
|
222 |
+
c) You must license the entire work, as a whole, under this
|
223 |
+
License to anyone who comes into possession of a copy. This
|
224 |
+
License will therefore apply, along with any applicable section 7
|
225 |
+
additional terms, to the whole of the work, and all its parts,
|
226 |
+
regardless of how they are packaged. This License gives no
|
227 |
+
permission to license the work in any other way, but it does not
|
228 |
+
invalidate such permission if you have separately received it.
|
229 |
+
|
230 |
+
d) If the work has interactive user interfaces, each must display
|
231 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
232 |
+
interfaces that do not display Appropriate Legal Notices, your
|
233 |
+
work need not make them do so.
|
234 |
+
|
235 |
+
A compilation of a covered work with other separate and independent
|
236 |
+
works, which are not by their nature extensions of the covered work,
|
237 |
+
and which are not combined with it such as to form a larger program,
|
238 |
+
in or on a volume of a storage or distribution medium, is called an
|
239 |
+
"aggregate" if the compilation and its resulting copyright are not
|
240 |
+
used to limit the access or legal rights of the compilation's users
|
241 |
+
beyond what the individual works permit. Inclusion of a covered work
|
242 |
+
in an aggregate does not cause this License to apply to the other
|
243 |
+
parts of the aggregate.
|
244 |
+
|
245 |
+
6. Conveying Non-Source Forms.
|
246 |
+
|
247 |
+
You may convey a covered work in object code form under the terms
|
248 |
+
of sections 4 and 5, provided that you also convey the
|
249 |
+
machine-readable Corresponding Source under the terms of this License,
|
250 |
+
in one of these ways:
|
251 |
+
|
252 |
+
a) Convey the object code in, or embodied in, a physical product
|
253 |
+
(including a physical distribution medium), accompanied by the
|
254 |
+
Corresponding Source fixed on a durable physical medium
|
255 |
+
customarily used for software interchange.
|
256 |
+
|
257 |
+
b) Convey the object code in, or embodied in, a physical product
|
258 |
+
(including a physical distribution medium), accompanied by a
|
259 |
+
written offer, valid for at least three years and valid for as
|
260 |
+
long as you offer spare parts or customer support for that product
|
261 |
+
model, to give anyone who possesses the object code either (1) a
|
262 |
+
copy of the Corresponding Source for all the software in the
|
263 |
+
product that is covered by this License, on a durable physical
|
264 |
+
medium customarily used for software interchange, for a price no
|
265 |
+
more than your reasonable cost of physically performing this
|
266 |
+
conveying of source, or (2) access to copy the
|
267 |
+
Corresponding Source from a network server at no charge.
|
268 |
+
|
269 |
+
c) Convey individual copies of the object code with a copy of the
|
270 |
+
written offer to provide the Corresponding Source. This
|
271 |
+
alternative is allowed only occasionally and noncommercially, and
|
272 |
+
only if you received the object code with such an offer, in accord
|
273 |
+
with subsection 6b.
|
274 |
+
|
275 |
+
d) Convey the object code by offering access from a designated
|
276 |
+
place (gratis or for a charge), and offer equivalent access to the
|
277 |
+
Corresponding Source in the same way through the same place at no
|
278 |
+
further charge. You need not require recipients to copy the
|
279 |
+
Corresponding Source along with the object code. If the place to
|
280 |
+
copy the object code is a network server, the Corresponding Source
|
281 |
+
may be on a different server (operated by you or a third party)
|
282 |
+
that supports equivalent copying facilities, provided you maintain
|
283 |
+
clear directions next to the object code saying where to find the
|
284 |
+
Corresponding Source. Regardless of what server hosts the
|
285 |
+
Corresponding Source, you remain obligated to ensure that it is
|
286 |
+
available for as long as needed to satisfy these requirements.
|
287 |
+
|
288 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
289 |
+
you inform other peers where the object code and Corresponding
|
290 |
+
Source of the work are being offered to the general public at no
|
291 |
+
charge under subsection 6d.
|
292 |
+
|
293 |
+
A separable portion of the object code, whose source code is excluded
|
294 |
+
from the Corresponding Source as a System Library, need not be
|
295 |
+
included in conveying the object code work.
|
296 |
+
|
297 |
+
A "User Product" is either (1) a "consumer product", which means any
|
298 |
+
tangible personal property which is normally used for personal, family,
|
299 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
300 |
+
into a dwelling. In determining whether a product is a consumer product,
|
301 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
302 |
+
product received by a particular user, "normally used" refers to a
|
303 |
+
typical or common use of that class of product, regardless of the status
|
304 |
+
of the particular user or of the way in which the particular user
|
305 |
+
actually uses, or expects or is expected to use, the product. A product
|
306 |
+
is a consumer product regardless of whether the product has substantial
|
307 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
308 |
+
the only significant mode of use of the product.
|
309 |
+
|
310 |
+
"Installation Information" for a User Product means any methods,
|
311 |
+
procedures, authorization keys, or other information required to install
|
312 |
+
and execute modified versions of a covered work in that User Product from
|
313 |
+
a modified version of its Corresponding Source. The information must
|
314 |
+
suffice to ensure that the continued functioning of the modified object
|
315 |
+
code is in no case prevented or interfered with solely because
|
316 |
+
modification has been made.
|
317 |
+
|
318 |
+
If you convey an object code work under this section in, or with, or
|
319 |
+
specifically for use in, a User Product, and the conveying occurs as
|
320 |
+
part of a transaction in which the right of possession and use of the
|
321 |
+
User Product is transferred to the recipient in perpetuity or for a
|
322 |
+
fixed term (regardless of how the transaction is characterized), the
|
323 |
+
Corresponding Source conveyed under this section must be accompanied
|
324 |
+
by the Installation Information. But this requirement does not apply
|
325 |
+
if neither you nor any third party retains the ability to install
|
326 |
+
modified object code on the User Product (for example, the work has
|
327 |
+
been installed in ROM).
|
328 |
+
|
329 |
+
The requirement to provide Installation Information does not include a
|
330 |
+
requirement to continue to provide support service, warranty, or updates
|
331 |
+
for a work that has been modified or installed by the recipient, or for
|
332 |
+
the User Product in which it has been modified or installed. Access to a
|
333 |
+
network may be denied when the modification itself materially and
|
334 |
+
adversely affects the operation of the network or violates the rules and
|
335 |
+
protocols for communication across the network.
|
336 |
+
|
337 |
+
Corresponding Source conveyed, and Installation Information provided,
|
338 |
+
in accord with this section must be in a format that is publicly
|
339 |
+
documented (and with an implementation available to the public in
|
340 |
+
source code form), and must require no special password or key for
|
341 |
+
unpacking, reading or copying.
|
342 |
+
|
343 |
+
7. Additional Terms.
|
344 |
+
|
345 |
+
"Additional permissions" are terms that supplement the terms of this
|
346 |
+
License by making exceptions from one or more of its conditions.
|
347 |
+
Additional permissions that are applicable to the entire Program shall
|
348 |
+
be treated as though they were included in this License, to the extent
|
349 |
+
that they are valid under applicable law. If additional permissions
|
350 |
+
apply only to part of the Program, that part may be used separately
|
351 |
+
under those permissions, but the entire Program remains governed by
|
352 |
+
this License without regard to the additional permissions.
|
353 |
+
|
354 |
+
When you convey a copy of a covered work, you may at your option
|
355 |
+
remove any additional permissions from that copy, or from any part of
|
356 |
+
it. (Additional permissions may be written to require their own
|
357 |
+
removal in certain cases when you modify the work.) You may place
|
358 |
+
additional permissions on material, added by you to a covered work,
|
359 |
+
for which you have or can give appropriate copyright permission.
|
360 |
+
|
361 |
+
Notwithstanding any other provision of this License, for material you
|
362 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
363 |
+
that material) supplement the terms of this License with terms:
|
364 |
+
|
365 |
+
a) Disclaiming warranty or limiting liability differently from the
|
366 |
+
terms of sections 15 and 16 of this License; or
|
367 |
+
|
368 |
+
b) Requiring preservation of specified reasonable legal notices or
|
369 |
+
author attributions in that material or in the Appropriate Legal
|
370 |
+
Notices displayed by works containing it; or
|
371 |
+
|
372 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
373 |
+
requiring that modified versions of such material be marked in
|
374 |
+
reasonable ways as different from the original version; or
|
375 |
+
|
376 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
377 |
+
authors of the material; or
|
378 |
+
|
379 |
+
e) Declining to grant rights under trademark law for use of some
|
380 |
+
trade names, trademarks, or service marks; or
|
381 |
+
|
382 |
+
f) Requiring indemnification of licensors and authors of that
|
383 |
+
material by anyone who conveys the material (or modified versions of
|
384 |
+
it) with contractual assumptions of liability to the recipient, for
|
385 |
+
any liability that these contractual assumptions directly impose on
|
386 |
+
those licensors and authors.
|
387 |
+
|
388 |
+
All other non-permissive additional terms are considered "further
|
389 |
+
restrictions" within the meaning of section 10. If the Program as you
|
390 |
+
received it, or any part of it, contains a notice stating that it is
|
391 |
+
governed by this License along with a term that is a further
|
392 |
+
restriction, you may remove that term. If a license document contains
|
393 |
+
a further restriction but permits relicensing or conveying under this
|
394 |
+
License, you may add to a covered work material governed by the terms
|
395 |
+
of that license document, provided that the further restriction does
|
396 |
+
not survive such relicensing or conveying.
|
397 |
+
|
398 |
+
If you add terms to a covered work in accord with this section, you
|
399 |
+
must place, in the relevant source files, a statement of the
|
400 |
+
additional terms that apply to those files, or a notice indicating
|
401 |
+
where to find the applicable terms.
|
402 |
+
|
403 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
404 |
+
form of a separately written license, or stated as exceptions;
|
405 |
+
the above requirements apply either way.
|
406 |
+
|
407 |
+
8. Termination.
|
408 |
+
|
409 |
+
You may not propagate or modify a covered work except as expressly
|
410 |
+
provided under this License. Any attempt otherwise to propagate or
|
411 |
+
modify it is void, and will automatically terminate your rights under
|
412 |
+
this License (including any patent licenses granted under the third
|
413 |
+
paragraph of section 11).
|
414 |
+
|
415 |
+
However, if you cease all violation of this License, then your
|
416 |
+
license from a particular copyright holder is reinstated (a)
|
417 |
+
provisionally, unless and until the copyright holder explicitly and
|
418 |
+
finally terminates your license, and (b) permanently, if the copyright
|
419 |
+
holder fails to notify you of the violation by some reasonable means
|
420 |
+
prior to 60 days after the cessation.
|
421 |
+
|
422 |
+
Moreover, your license from a particular copyright holder is
|
423 |
+
reinstated permanently if the copyright holder notifies you of the
|
424 |
+
violation by some reasonable means, this is the first time you have
|
425 |
+
received notice of violation of this License (for any work) from that
|
426 |
+
copyright holder, and you cure the violation prior to 30 days after
|
427 |
+
your receipt of the notice.
|
428 |
+
|
429 |
+
Termination of your rights under this section does not terminate the
|
430 |
+
licenses of parties who have received copies or rights from you under
|
431 |
+
this License. If your rights have been terminated and not permanently
|
432 |
+
reinstated, you do not qualify to receive new licenses for the same
|
433 |
+
material under section 10.
|
434 |
+
|
435 |
+
9. Acceptance Not Required for Having Copies.
|
436 |
+
|
437 |
+
You are not required to accept this License in order to receive or
|
438 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
439 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
440 |
+
to receive a copy likewise does not require acceptance. However,
|
441 |
+
nothing other than this License grants you permission to propagate or
|
442 |
+
modify any covered work. These actions infringe copyright if you do
|
443 |
+
not accept this License. Therefore, by modifying or propagating a
|
444 |
+
covered work, you indicate your acceptance of this License to do so.
|
445 |
+
|
446 |
+
10. Automatic Licensing of Downstream Recipients.
|
447 |
+
|
448 |
+
Each time you convey a covered work, the recipient automatically
|
449 |
+
receives a license from the original licensors, to run, modify and
|
450 |
+
propagate that work, subject to this License. You are not responsible
|
451 |
+
for enforcing compliance by third parties with this License.
|
452 |
+
|
453 |
+
An "entity transaction" is a transaction transferring control of an
|
454 |
+
organization, or substantially all assets of one, or subdividing an
|
455 |
+
organization, or merging organizations. If propagation of a covered
|
456 |
+
work results from an entity transaction, each party to that
|
457 |
+
transaction who receives a copy of the work also receives whatever
|
458 |
+
licenses to the work the party's predecessor in interest had or could
|
459 |
+
give under the previous paragraph, plus a right to possession of the
|
460 |
+
Corresponding Source of the work from the predecessor in interest, if
|
461 |
+
the predecessor has it or can get it with reasonable efforts.
|
462 |
+
|
463 |
+
You may not impose any further restrictions on the exercise of the
|
464 |
+
rights granted or affirmed under this License. For example, you may
|
465 |
+
not impose a license fee, royalty, or other charge for exercise of
|
466 |
+
rights granted under this License, and you may not initiate litigation
|
467 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
468 |
+
any patent claim is infringed by making, using, selling, offering for
|
469 |
+
sale, or importing the Program or any portion of it.
|
470 |
+
|
471 |
+
11. Patents.
|
472 |
+
|
473 |
+
A "contributor" is a copyright holder who authorizes use under this
|
474 |
+
License of the Program or a work on which the Program is based. The
|
475 |
+
work thus licensed is called the contributor's "contributor version".
|
476 |
+
|
477 |
+
A contributor's "essential patent claims" are all patent claims
|
478 |
+
owned or controlled by the contributor, whether already acquired or
|
479 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
480 |
+
by this License, of making, using, or selling its contributor version,
|
481 |
+
but do not include claims that would be infringed only as a
|
482 |
+
consequence of further modification of the contributor version. For
|
483 |
+
purposes of this definition, "control" includes the right to grant
|
484 |
+
patent sublicenses in a manner consistent with the requirements of
|
485 |
+
this License.
|
486 |
+
|
487 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
488 |
+
patent license under the contributor's essential patent claims, to
|
489 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
490 |
+
propagate the contents of its contributor version.
|
491 |
+
|
492 |
+
In the following three paragraphs, a "patent license" is any express
|
493 |
+
agreement or commitment, however denominated, not to enforce a patent
|
494 |
+
(such as an express permission to practice a patent or covenant not to
|
495 |
+
sue for patent infringement). To "grant" such a patent license to a
|
496 |
+
party means to make such an agreement or commitment not to enforce a
|
497 |
+
patent against the party.
|
498 |
+
|
499 |
+
If you convey a covered work, knowingly relying on a patent license,
|
500 |
+
and the Corresponding Source of the work is not available for anyone
|
501 |
+
to copy, free of charge and under the terms of this License, through a
|
502 |
+
publicly available network server or other readily accessible means,
|
503 |
+
then you must either (1) cause the Corresponding Source to be so
|
504 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
505 |
+
patent license for this particular work, or (3) arrange, in a manner
|
506 |
+
consistent with the requirements of this License, to extend the patent
|
507 |
+
license to downstream recipients. "Knowingly relying" means you have
|
508 |
+
actual knowledge that, but for the patent license, your conveying the
|
509 |
+
covered work in a country, or your recipient's use of the covered work
|
510 |
+
in a country, would infringe one or more identifiable patents in that
|
511 |
+
country that you have reason to believe are valid.
|
512 |
+
|
513 |
+
If, pursuant to or in connection with a single transaction or
|
514 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
515 |
+
covered work, and grant a patent license to some of the parties
|
516 |
+
receiving the covered work authorizing them to use, propagate, modify
|
517 |
+
or convey a specific copy of the covered work, then the patent license
|
518 |
+
you grant is automatically extended to all recipients of the covered
|
519 |
+
work and works based on it.
|
520 |
+
|
521 |
+
A patent license is "discriminatory" if it does not include within
|
522 |
+
the scope of its coverage, prohibits the exercise of, or is
|
523 |
+
conditioned on the non-exercise of one or more of the rights that are
|
524 |
+
specifically granted under this License. You may not convey a covered
|
525 |
+
work if you are a party to an arrangement with a third party that is
|
526 |
+
in the business of distributing software, under which you make payment
|
527 |
+
to the third party based on the extent of your activity of conveying
|
528 |
+
the work, and under which the third party grants, to any of the
|
529 |
+
parties who would receive the covered work from you, a discriminatory
|
530 |
+
patent license (a) in connection with copies of the covered work
|
531 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
532 |
+
for and in connection with specific products or compilations that
|
533 |
+
contain the covered work, unless you entered into that arrangement,
|
534 |
+
or that patent license was granted, prior to 28 March 2007.
|
535 |
+
|
536 |
+
Nothing in this License shall be construed as excluding or limiting
|
537 |
+
any implied license or other defenses to infringement that may
|
538 |
+
otherwise be available to you under applicable patent law.
|
539 |
+
|
540 |
+
12. No Surrender of Others' Freedom.
|
541 |
+
|
542 |
+
If conditions are imposed on you (whether by court order, agreement or
|
543 |
+
otherwise) that contradict the conditions of this License, they do not
|
544 |
+
excuse you from the conditions of this License. If you cannot convey a
|
545 |
+
covered work so as to satisfy simultaneously your obligations under this
|
546 |
+
License and any other pertinent obligations, then as a consequence you may
|
547 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
548 |
+
to collect a royalty for further conveying from those to whom you convey
|
549 |
+
the Program, the only way you could satisfy both those terms and this
|
550 |
+
License would be to refrain entirely from conveying the Program.
|
551 |
+
|
552 |
+
13. Use with the GNU Affero General Public License.
|
553 |
+
|
554 |
+
Notwithstanding any other provision of this License, you have
|
555 |
+
permission to link or combine any covered work with a work licensed
|
556 |
+
under version 3 of the GNU Affero General Public License into a single
|
557 |
+
combined work, and to convey the resulting work. The terms of this
|
558 |
+
License will continue to apply to the part which is the covered work,
|
559 |
+
but the special requirements of the GNU Affero General Public License,
|
560 |
+
section 13, concerning interaction through a network will apply to the
|
561 |
+
combination as such.
|
562 |
+
|
563 |
+
14. Revised Versions of this License.
|
564 |
+
|
565 |
+
The Free Software Foundation may publish revised and/or new versions of
|
566 |
+
the GNU General Public License from time to time. Such new versions will
|
567 |
+
be similar in spirit to the present version, but may differ in detail to
|
568 |
+
address new problems or concerns.
|
569 |
+
|
570 |
+
Each version is given a distinguishing version number. If the
|
571 |
+
Program specifies that a certain numbered version of the GNU General
|
572 |
+
Public License "or any later version" applies to it, you have the
|
573 |
+
option of following the terms and conditions either of that numbered
|
574 |
+
version or of any later version published by the Free Software
|
575 |
+
Foundation. If the Program does not specify a version number of the
|
576 |
+
GNU General Public License, you may choose any version ever published
|
577 |
+
by the Free Software Foundation.
|
578 |
+
|
579 |
+
If the Program specifies that a proxy can decide which future
|
580 |
+
versions of the GNU General Public License can be used, that proxy's
|
581 |
+
public statement of acceptance of a version permanently authorizes you
|
582 |
+
to choose that version for the Program.
|
583 |
+
|
584 |
+
Later license versions may give you additional or different
|
585 |
+
permissions. However, no additional obligations are imposed on any
|
586 |
+
author or copyright holder as a result of your choosing to follow a
|
587 |
+
later version.
|
588 |
+
|
589 |
+
15. Disclaimer of Warranty.
|
590 |
+
|
591 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
592 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
593 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
594 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
595 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
596 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
597 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
598 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
599 |
+
|
600 |
+
16. Limitation of Liability.
|
601 |
+
|
602 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
603 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
604 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
605 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
606 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
607 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
608 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
609 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
610 |
+
SUCH DAMAGES.
|
611 |
+
|
612 |
+
17. Interpretation of Sections 15 and 16.
|
613 |
+
|
614 |
+
If the disclaimer of warranty and limitation of liability provided
|
615 |
+
above cannot be given local legal effect according to their terms,
|
616 |
+
reviewing courts shall apply local law that most closely approximates
|
617 |
+
an absolute waiver of all civil liability in connection with the
|
618 |
+
Program, unless a warranty or assumption of liability accompanies a
|
619 |
+
copy of the Program in return for a fee.
|
620 |
+
|
621 |
+
END OF TERMS AND CONDITIONS
|
622 |
+
|
623 |
+
How to Apply These Terms to Your New Programs
|
624 |
+
|
625 |
+
If you develop a new program, and you want it to be of the greatest
|
626 |
+
possible use to the public, the best way to achieve this is to make it
|
627 |
+
free software which everyone can redistribute and change under these terms.
|
628 |
+
|
629 |
+
To do so, attach the following notices to the program. It is safest
|
630 |
+
to attach them to the start of each source file to most effectively
|
631 |
+
state the exclusion of warranty; and each file should have at least
|
632 |
+
the "copyright" line and a pointer to where the full notice is found.
|
633 |
+
|
634 |
+
<one line to give the program's name and a brief idea of what it does.>
|
635 |
+
Copyright (C) <year> <name of author>
|
636 |
+
|
637 |
+
This program is free software: you can redistribute it and/or modify
|
638 |
+
it under the terms of the GNU General Public License as published by
|
639 |
+
the Free Software Foundation, either version 3 of the License, or
|
640 |
+
(at your option) any later version.
|
641 |
+
|
642 |
+
This program is distributed in the hope that it will be useful,
|
643 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
644 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
645 |
+
GNU General Public License for more details.
|
646 |
+
|
647 |
+
You should have received a copy of the GNU General Public License
|
648 |
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
649 |
+
|
650 |
+
Also add information on how to contact you by electronic and paper mail.
|
651 |
+
|
652 |
+
If the program does terminal interaction, make it output a short
|
653 |
+
notice like this when it starts in an interactive mode:
|
654 |
+
|
655 |
+
<program> Copyright (C) <year> <name of author>
|
656 |
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
657 |
+
This is free software, and you are welcome to redistribute it
|
658 |
+
under certain conditions; type `show c' for details.
|
659 |
+
|
660 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
661 |
+
parts of the General Public License. Of course, your program's commands
|
662 |
+
might be different; for a GUI interface, you would use an "about box".
|
663 |
+
|
664 |
+
You should also get your employer (if you work as a programmer) or school,
|
665 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
666 |
+
For more information on this, and how to apply and follow the GNU GPL, see
|
667 |
+
<https://www.gnu.org/licenses/>.
|
668 |
+
|
669 |
+
The GNU General Public License does not permit incorporating your program
|
670 |
+
into proprietary programs. If your program is a subroutine library, you
|
671 |
+
may consider it more useful to permit linking proprietary applications with
|
672 |
+
the library. If this is what you want to do, use the GNU Lesser General
|
673 |
+
Public License instead of this License. But first, please read
|
674 |
+
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
@@ -0,0 +1,705 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# htaccess-capability-tester
|
2 |
+
|
3 |
+
[![Latest Stable Version](https://img.shields.io/packagist/v/rosell-dk/htaccess-capability-tester.svg?style=flat-square)](https://packagist.org/packages/rosell-dk/htaccess-capability-tester)
|
4 |
+
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%205.6-8892BF.svg?style=flat-square)](https://php.net)
|
5 |
+
[![Build Status](https://travis-ci.org/rosell-dk/htaccess-capability-tester.png?branch=master)](https://travis-ci.org/rosell-dk/htaccess-capability-tester)
|
6 |
+
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/rosell-dk/htaccess-capability-tester.svg?style=flat-square)](https://scrutinizer-ci.com/g/rosell-dk/htaccess-capability-tester/code-structure/master/code-coverage/src/)
|
7 |
+
[![Quality Score](https://img.shields.io/scrutinizer/g/rosell-dk/htaccess-capability-tester.svg?style=flat-square)](https://scrutinizer-ci.com/g/rosell-dk/htaccess-capability-tester/)
|
8 |
+
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/rosell-dk/htaccess-capability-tester/blob/master/LICENSE)
|
9 |
+
|
10 |
+
|
11 |
+
Detect `.htaccess` capabilities through live tests.
|
12 |
+
|
13 |
+
There are cases where the only way to to learn if a given `.htaccess` capability is enabled / supported on a system is by examining it "from the outside" through a HTTP request. This library is build to handle such testing easily.
|
14 |
+
|
15 |
+
This is what happens behind the scenes:
|
16 |
+
1. Some test files for a given test are put on the server (at least an `.htaccess` file)
|
17 |
+
2. The test is triggered by doing a HTTP request
|
18 |
+
3. The response is interpreted
|
19 |
+
|
20 |
+
## Usage
|
21 |
+
|
22 |
+
To use the library, you must provide a path to where the test files are going to be put and an URL that they can be reached. Besides that, you just need to pick one of the tests that you want to run.
|
23 |
+
|
24 |
+
```php
|
25 |
+
require 'vendor/autoload.php';
|
26 |
+
use HtaccessCapabilityTester\HtaccessCapabilityTester;
|
27 |
+
|
28 |
+
$hct = new HtaccessCapabilityTester($baseDir, $baseUrl);
|
29 |
+
|
30 |
+
if ($hct->moduleLoaded('headers')) {
|
31 |
+
// mod_headers has been tested functional in a real .htaccess
|
32 |
+
}
|
33 |
+
if ($hct->rewriteWorks()) {
|
34 |
+
// rewriting works
|
35 |
+
|
36 |
+
}
|
37 |
+
if ($hct->htaccessEnabled() === false) {
|
38 |
+
// Apache has been configured to ignore .htaccess files
|
39 |
+
}
|
40 |
+
|
41 |
+
// A bunch of other tests are available - see API
|
42 |
+
```
|
43 |
+
While having a reliable `moduleLoaded` method is a great improvement over current state of affairs, beware that it is possible that the server has ie `mod_rewrite` enabled, but at the same time has disallowed using ie the "RewriteRule" directive in `.htaccess` files. This is why the library has the `rewriteWorks()` method and similar methods for testing various capabilites fully. Providing tests for all kinds of functionality, would however be too much for any library. Instead this library makes it a breeze to define a custom test and run it through the `customTest($def)` method.
|
44 |
+
|
45 |
+
|
46 |
+
## Running your own custom tests using the *customTest* method
|
47 |
+
|
48 |
+
A typical test s mentioned, a test has three phases:
|
49 |
+
1. Writing the test files to the directory in question
|
50 |
+
2. Doing a request (in advanced cases, more)
|
51 |
+
3. Interpreting the request
|
52 |
+
|
53 |
+
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?
|
54 |
+
|
55 |
+
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.
|
56 |
+
|
57 |
+
<details><summary><u>Click here to see the PHP example</u></summary>
|
58 |
+
<p><br>
|
59 |
+
<b>PHP example</b>
|
60 |
+
|
61 |
+
```php
|
62 |
+
<?php
|
63 |
+
require 'vendor/autoload.php';
|
64 |
+
use HtaccessCapabilityTester\HtaccessCapabilityTester;
|
65 |
+
|
66 |
+
$hct = new HtaccessCapabilityTester($baseDir, $baseUrl);
|
67 |
+
|
68 |
+
$htaccessFile = <<<'EOD'
|
69 |
+
<IfModule mod_headers.c>
|
70 |
+
Header set X-Response-Header-Test: test
|
71 |
+
</IfModule>
|
72 |
+
EOD;
|
73 |
+
|
74 |
+
$test = [
|
75 |
+
'subdir' => 'header-set',
|
76 |
+
'files' => [
|
77 |
+
['.htaccess', $htaccessFile],
|
78 |
+
['request-me.txt', "hi"],
|
79 |
+
],
|
80 |
+
'request' => 'request-me.txt',
|
81 |
+
'interpretation' => [
|
82 |
+
['success', 'headers', 'contains-key-value', 'X-Response-Header-Test', 'test'],
|
83 |
+
|
84 |
+
// the next three mappings are actually not necessary, as customTest() does standard
|
85 |
+
// error handling automatically (can be turned off)
|
86 |
+
['failure', 'status-code', 'equals', '500'],
|
87 |
+
['inconclusive', 'status-code', 'equals', '403'],
|
88 |
+
['inconclusive', 'status-code', 'equals', '404'],
|
89 |
+
]
|
90 |
+
];
|
91 |
+
|
92 |
+
if ($hct->customTest($test)) {
|
93 |
+
// setting a header in the .htaccess works!
|
94 |
+
}
|
95 |
+
```
|
96 |
+
|
97 |
+
</p>
|
98 |
+
</details>
|
99 |
+
|
100 |
+
```yaml
|
101 |
+
subdir: header-set
|
102 |
+
files:
|
103 |
+
- filename: '.htaccess'
|
104 |
+
content: |
|
105 |
+
<IfModule mod_headers.c>
|
106 |
+
Header set X-Response-Header-Test: test
|
107 |
+
</IfModule>
|
108 |
+
- filename: 'request-me.txt'
|
109 |
+
content: 'hi'
|
110 |
+
|
111 |
+
request:
|
112 |
+
url: 'request-me.txt'
|
113 |
+
|
114 |
+
interpretation:
|
115 |
+
- [success, headers, contains-key-value, 'X-Response-Header-Test', 'test']
|
116 |
+
- [failure, status-code, equals, '500'] # actually not needed (part of standard error handling)
|
117 |
+
- [inconclusive, status-code, equals, '403'] # actually not needed (part of standard error handling)
|
118 |
+
- [inconclusive, status-code, equals, '404'] # actually not needed (part of standard error handling)
|
119 |
+
- [failure]
|
120 |
+
```
|
121 |
+
|
122 |
+
In fact, this is more or less how this library implements it.
|
123 |
+
|
124 |
+
The test definition has the following sub-definitions:
|
125 |
+
- *subdir*: Defines which subdir the test files should reside in
|
126 |
+
- *files*: Defines the files for the test (filename and content)
|
127 |
+
- *request*: Defines which file that should be requested
|
128 |
+
- *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'.
|
129 |
+
|
130 |
+
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"
|
131 |
+
|
132 |
+
## API overview
|
133 |
+
|
134 |
+
### Test methods in HtaccessCapabilityTester
|
135 |
+
|
136 |
+
All the test methods returns a test result, which is *true* for success, *false* for failure or *null* for inconclusive.
|
137 |
+
|
138 |
+
The tests have the following in common:
|
139 |
+
- If the server has been set up to ignore `.htaccess` files, the result will be *failure*.
|
140 |
+
- If the server has been set up to disallow the directive being tested (AllowOverride), the result is *failure* (both when configured to ignore and when configured to go fatal)
|
141 |
+
- A *403 Forbidden* results in *inconclusive*. Why? Because it could be that the server has been set up to forbid access to files matching a pattern that our test file unluckily matches. In most cases, this is unlikely, as most tests requests files with harmless-looking file extensions (often a "request-me.txt"). A few of the tests however requests a "test.php", which is more likely to be denied.
|
142 |
+
|
143 |
+
Most tests are implemented as a definition such as the one accepted in *customTest()*. This means that if you want one of the tests provided by this library to work slightly differently, you can easily grab the code in the corresponding class in the *Testers* directory, make your modification and call *customTest()*.
|
144 |
+
|
145 |
+
<details><summary><b>addTypeWorks()</b></summary>
|
146 |
+
<p><br>
|
147 |
+
Tests if the *AddType* directive works.
|
148 |
+
|
149 |
+
Implementation (YAML definition):
|
150 |
+
|
151 |
+
```yaml
|
152 |
+
subdir: add-type
|
153 |
+
files:
|
154 |
+
- filename: '.htaccess'
|
155 |
+
content: |
|
156 |
+
<IfModule mod_mime.c>
|
157 |
+
AddType image/gif .test
|
158 |
+
</IfModule>
|
159 |
+
- filename: 'request-me.test'
|
160 |
+
content: 'hi'
|
161 |
+
request:
|
162 |
+
url: 'request-me.test'
|
163 |
+
|
164 |
+
interpretation:
|
165 |
+
- ['success', 'headers', 'contains-key-value', 'Content-Type', 'image/gif']
|
166 |
+
- ['inconclusive', 'status-code', 'not-equals', '200']
|
167 |
+
- ['failure', 'headers', 'not-contains-key-value', 'Content-Type', 'image/gif']
|
168 |
+
```
|
169 |
+
|
170 |
+
</p>
|
171 |
+
</details>
|
172 |
+
|
173 |
+
<details><summary><b>contentDigestWorks()</b></summary>
|
174 |
+
<p>
|
175 |
+
|
176 |
+
Implementation (YAML definition):
|
177 |
+
|
178 |
+
```yaml
|
179 |
+
subdir: content-digest
|
180 |
+
subtests:
|
181 |
+
- subdir: on
|
182 |
+
files:
|
183 |
+
- filename: '.htaccess'
|
184 |
+
content: |
|
185 |
+
ContentDigest On
|
186 |
+
- filename: 'request-me.txt'
|
187 |
+
content: 'hi'
|
188 |
+
request:
|
189 |
+
url: 'request-me.txt'
|
190 |
+
interpretation:
|
191 |
+
- ['failure', 'headers', 'not-contains-key', 'Content-MD5'],
|
192 |
+
|
193 |
+
- subdir: off
|
194 |
+
files:
|
195 |
+
- filename: '.htaccess'
|
196 |
+
content: |
|
197 |
+
ContentDigest Off
|
198 |
+
- filename: 'request-me.txt'
|
199 |
+
content: 'hi'
|
200 |
+
request:
|
201 |
+
url: 'request-me.txt'
|
202 |
+
|
203 |
+
interpretation:
|
204 |
+
- ['failure', 'headers', 'contains-key', 'Content-MD5']
|
205 |
+
- ['inconclusive', 'status-code', 'not-equals', '200']
|
206 |
+
- ['success', 'status-code', 'equals', '200']
|
207 |
+
```
|
208 |
+
|
209 |
+
</p>
|
210 |
+
</details>
|
211 |
+
|
212 |
+
<details><summary><b>crashTest($rules, $subdir)</b></summary>
|
213 |
+
<p><br>
|
214 |
+
Test if some rules makes the server "crash" (respond with 500 Internal Server Error for requests to files in the folder).
|
215 |
+
You pass the rules that you want to check.
|
216 |
+
You can optionally pass in a subdir for the tests. If you do not do that, a hash of the rules will be used.
|
217 |
+
|
218 |
+
Implementation (PHP):
|
219 |
+
|
220 |
+
```php
|
221 |
+
/**
|
222 |
+
* @param string $htaccessRules The rules to check
|
223 |
+
* @param string $subSubDir subdir for the test files. If not supplied, a fingerprint of the rules will be used
|
224 |
+
*/
|
225 |
+
public function __construct($htaccessRules, $subSubDir = null)
|
226 |
+
{
|
227 |
+
if (is_null($subSubDir)) {
|
228 |
+
$subSubDir = hash('md5', $htaccessRules);
|
229 |
+
}
|
230 |
+
|
231 |
+
$test = [
|
232 |
+
'subdir' => 'crash-tester/' . $subSubDir,
|
233 |
+
'subtests' => [
|
234 |
+
[
|
235 |
+
'subdir' => 'the-suspect',
|
236 |
+
'files' => [
|
237 |
+
['.htaccess', $htaccessRules],
|
238 |
+
['request-me.txt', 'thanks'],
|
239 |
+
],
|
240 |
+
'request' => [
|
241 |
+
'url' => 'request-me.txt',
|
242 |
+
'bypass-standard-error-handling' => ['all']
|
243 |
+
],
|
244 |
+
'interpretation' => [
|
245 |
+
['success', 'status-code', 'not-equals', '500'],
|
246 |
+
]
|
247 |
+
],
|
248 |
+
[
|
249 |
+
'subdir' => 'the-innocent',
|
250 |
+
'files' => [
|
251 |
+
['.htaccess', '# I am no trouble'],
|
252 |
+
['request-me.txt', 'thanks'],
|
253 |
+
],
|
254 |
+
'request' => [
|
255 |
+
'url' => 'request-me.txt',
|
256 |
+
'bypass-standard-error-handling' => ['all']
|
257 |
+
],
|
258 |
+
'interpretation' => [
|
259 |
+
// The suspect crashed. But if the innocent crashes too, we cannot judge
|
260 |
+
['inconclusive', 'status-code', 'equals', '500'],
|
261 |
+
|
262 |
+
// The innocent did not crash. The suspect is guilty!
|
263 |
+
['failure'],
|
264 |
+
]
|
265 |
+
],
|
266 |
+
]
|
267 |
+
];
|
268 |
+
|
269 |
+
parent::__construct($test);
|
270 |
+
}
|
271 |
+
```
|
272 |
+
|
273 |
+
</p>
|
274 |
+
</details>
|
275 |
+
|
276 |
+
<details><summary><b>customTest($definition)</b></summary>
|
277 |
+
<p>
|
278 |
+
|
279 |
+
Allows you to run a custom test. Check out README.md for instructions
|
280 |
+
|
281 |
+
</p>
|
282 |
+
</details>
|
283 |
+
|
284 |
+
<details><summary><b>directoryIndexWorks()</b></summary>
|
285 |
+
<p><br>
|
286 |
+
Tests if DirectoryIndex works.
|
287 |
+
|
288 |
+
Implementation (YAML definition):
|
289 |
+
|
290 |
+
```yaml
|
291 |
+
subdir: directory-index
|
292 |
+
files:
|
293 |
+
- filename: '.htaccess'
|
294 |
+
content: |
|
295 |
+
<IfModule mod_dir.c>
|
296 |
+
DirectoryIndex index2.html
|
297 |
+
</IfModule>
|
298 |
+
- filename: 'index.html'
|
299 |
+
content: '0'
|
300 |
+
- filename: 'index2.html'
|
301 |
+
content: '1'
|
302 |
+
|
303 |
+
request:
|
304 |
+
url: '' # We request the index, that is why its empty
|
305 |
+
bypass-standard-error-handling: ['404']
|
306 |
+
|
307 |
+
interpretation:
|
308 |
+
- ['success', 'body', 'equals', '1']
|
309 |
+
- ['failure', 'body', 'equals', '0']
|
310 |
+
- ['failure', 'status-code', 'equals', '404'] # "index.html" might not be set to index
|
311 |
+
```
|
312 |
+
|
313 |
+
</p>
|
314 |
+
</details>
|
315 |
+
|
316 |
+
<details><summary><b>headerSetWorks()</b></summary>
|
317 |
+
<p><br>
|
318 |
+
Tests if setting a response header works using the *Header* directive.
|
319 |
+
|
320 |
+
Implementation (YAML definition):
|
321 |
+
|
322 |
+
```yaml
|
323 |
+
subdir: header-set
|
324 |
+
files:
|
325 |
+
- filename: '.htaccess'
|
326 |
+
content: |
|
327 |
+
<IfModule mod_headers.c>
|
328 |
+
Header set X-Response-Header-Test: test
|
329 |
+
</IfModule>
|
330 |
+
- filename: 'request-me.txt'
|
331 |
+
content: 'hi'
|
332 |
+
|
333 |
+
request:
|
334 |
+
url: 'request-me.txt'
|
335 |
+
|
336 |
+
interpretation:
|
337 |
+
- [success, headers, contains-key-value, 'X-Response-Header-Test', 'test'],
|
338 |
+
- [failure]
|
339 |
+
```
|
340 |
+
|
341 |
+
</p>
|
342 |
+
</details>
|
343 |
+
|
344 |
+
<details><summary><b>htaccessEnabled()</b></summary>
|
345 |
+
<p><br>
|
346 |
+
Apache can be configured to ignore `.htaccess` files altogether. This method tests if the `.htaccess` file is processed at all
|
347 |
+
|
348 |
+
The method works by trying out a series of subtests until a conclusion is reached. It will never come out inconclusive.
|
349 |
+
|
350 |
+
How does it work?
|
351 |
+
- The first strategy is testing a series of features, such as `rewriteWorks()`. If any of them works, well, then the `.htaccess` must have been processed.
|
352 |
+
- Secondly, the `serverSignatureWorks()` is tested. The "ServerSignature" directive is special because it is in core and cannot be disabled with AllowOverride. If this test comes out as a failure, it is so *highly likely* that the .htaccess has not been processed, that we conclude that it has not.
|
353 |
+
- Lastly, if all other methods failed, we try calling `crashTest()` on an .htaccess file that we on purpose put syntax errors in. If it crashes, the .htaccess file must have been proccessed. If it does not crash, it has not. This last method is bulletproof - so why not do it first? Because it might generate an entry in the error log.
|
354 |
+
|
355 |
+
Main part of implementation:
|
356 |
+
```php
|
357 |
+
// If we can find anything that works, well the .htaccess must have been proccesed!
|
358 |
+
if ($hct->serverSignatureWorks() // Override: None, Status: Core, REQUIRES PHP
|
359 |
+
|| $hct->contentDigestWorks() // Override: Options, Status: Core
|
360 |
+
|| $hct->addTypeWorks() // Override: FileInfo, Status: Base, Module: mime
|
361 |
+
|| $hct->directoryIndexWorks() // Override: Indexes, Status: Base, Module: mod_dir
|
362 |
+
|| $hct->rewriteWorks() // Override: FileInfo, Status: Extension, Module: rewrite
|
363 |
+
|| $hct->headerSetWorks() // Override: FileInfo, Status: Extension, Module: headers
|
364 |
+
) {
|
365 |
+
$status = true;
|
366 |
+
} else {
|
367 |
+
// The serverSignatureWorks() test is special because if it comes out as a failure,
|
368 |
+
// we can be *almost* certain that the .htaccess has been completely disabled
|
369 |
+
|
370 |
+
$serverSignatureWorks = $hct->serverSignatureWorks();
|
371 |
+
if ($serverSignatureWorks === false) {
|
372 |
+
$status = false;
|
373 |
+
$info = 'ServerSignature directive does not work - and it is in core';
|
374 |
+
} else {
|
375 |
+
// Last bullet in the gun:
|
376 |
+
// Try an .htaccess with syntax errors in it.
|
377 |
+
// (we do this lastly because it may generate an entry in the error log)
|
378 |
+
$crashTestResult = $hct->crashTest('aoeu', 'htaccess-enabled-malformed-htaccess');
|
379 |
+
if ($crashTestResult === false) {
|
380 |
+
// It crashed, - which means .htaccess is processed!
|
381 |
+
$status = true;
|
382 |
+
$info = 'syntax error in an .htaccess causes crash';
|
383 |
+
} elseif ($crashTestResult === true) {
|
384 |
+
// It did not crash. So the .htaccess is not processed, as syntax errors
|
385 |
+
// makes servers crash
|
386 |
+
$status = false;
|
387 |
+
$info = 'syntax error in an .htaccess does not cause crash';
|
388 |
+
} elseif (is_null($crashTestResult)) {
|
389 |
+
// It did crash. But so did a request to an innocent text file in a directory
|
390 |
+
// without a .htaccess file in it. Something is making all requests fail and
|
391 |
+
// we cannot judge.
|
392 |
+
$status = null;
|
393 |
+
$info = 'all requests results in 500 Internal Server Error';
|
394 |
+
}
|
395 |
+
}
|
396 |
+
}
|
397 |
+
return new TestResult($status, $info);
|
398 |
+
```
|
399 |
+
|
400 |
+
</p>
|
401 |
+
</details>
|
402 |
+
|
403 |
+
<details><summary><b>innocentRequestWorks()</b></summary>
|
404 |
+
<p><br>
|
405 |
+
Tests if an innocent request to a text file works. Most tests use this test when they get a 500 Internal Error, in order to decide if this is a general problem (general problem => inconclusive, specific problem => failure).
|
406 |
+
|
407 |
+
Implementation (YAML definition):
|
408 |
+
|
409 |
+
```yaml
|
410 |
+
subdir: innocent-request
|
411 |
+
files:
|
412 |
+
- filename: 'request-me.txt'
|
413 |
+
content: 'thank you my dear'
|
414 |
+
|
415 |
+
request:
|
416 |
+
url: 'request-me.txt'
|
417 |
+
bypass-standard-error-handling: 'all'
|
418 |
+
|
419 |
+
interpretation:
|
420 |
+
- ['success', 'status-code', 'equals', '200']
|
421 |
+
- ['inconclusive', 'status-code', 'equals', '403']
|
422 |
+
- ['inconclusive', 'status-code', 'equals', '404']
|
423 |
+
- ['failure']
|
424 |
+
```
|
425 |
+
|
426 |
+
</p>
|
427 |
+
</details>
|
428 |
+
|
429 |
+
<details><summary><b>moduleLoaded($moduleName)</b></summary>
|
430 |
+
<p><br>
|
431 |
+
Tests if a given module is loaded. Note that you in most cases would want to not just know if a module is loaded, but also ensure that the directives you are using are allowed. So for example, instead of calling `moduleLoaded("rewrite")`, you should probably call `rewriteWorks()`;
|
432 |
+
|
433 |
+
Implementation:
|
434 |
+
|
435 |
+
The method has many ways to test if a module is loaded, based on what works. If for example setting headers has been established to be working and we want to know if "setenvif" module is loaded, the following .htaccess rules will be tested, and the response will be examined.
|
436 |
+
```
|
437 |
+
<IfModule mod_setenvif.c>
|
438 |
+
Header set X-Response-Header-Test: 1
|
439 |
+
</IfModule>
|
440 |
+
<IfModule !mod_setenvif.c>
|
441 |
+
Header set X-Response-Header-Test: 0
|
442 |
+
</IfModule>
|
443 |
+
```
|
444 |
+
|
445 |
+
</p>
|
446 |
+
</details>
|
447 |
+
|
448 |
+
<details><summary><b>passingInfoFromRewriteToScriptThroughEnvWorks()</b></summary>
|
449 |
+
<p><br>
|
450 |
+
Say you have a rewrite rule that points to a PHP script and you would like to pass some information along to the PHP. Usually, you will just pass it in the query string. But this won't do if the information is sensitive. In that case, there are some tricks available. The trick being tested here tells the RewriteRule directive to set an environment variable, which in many setups can be picked up in the script.
|
451 |
+
|
452 |
+
Implementation (YAML definition):
|
453 |
+
|
454 |
+
```yaml
|
455 |
+
subdir: pass-info-from-rewrite-to-script-through-env
|
456 |
+
files:
|
457 |
+
- filename: '.htaccess'
|
458 |
+
content: |
|
459 |
+
<IfModule mod_rewrite.c>
|
460 |
+
|
461 |
+
# Testing if we can pass environment variable from .htaccess to script in a RewriteRule
|
462 |
+
# We pass document root, because that can easily be checked by the script
|
463 |
+
|
464 |
+
RewriteEngine On
|
465 |
+
RewriteRule ^test\.php$ - [E=PASSTHROUGHENV:%{DOCUMENT_ROOT},L]
|
466 |
+
|
467 |
+
</IfModule>
|
468 |
+
- filename: 'test.php'
|
469 |
+
content: |
|
470 |
+
<?php
|
471 |
+
|
472 |
+
/**
|
473 |
+
* Get environment variable set with mod_rewrite module
|
474 |
+
* Return false if the environment variable isn't found
|
475 |
+
*/
|
476 |
+
function getEnvPassedInRewriteRule($envName) {
|
477 |
+
// Environment variables passed through the REWRITE module have "REWRITE_" as a prefix
|
478 |
+
// (in Apache, not Litespeed, if I recall correctly).
|
479 |
+
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
480 |
+
// We simply look for an environment variable that ends with what we are looking for.
|
481 |
+
// (so make sure to make it unique)
|
482 |
+
$len = strlen($envName);
|
483 |
+
foreach ($_SERVER as $key => $item) {
|
484 |
+
if (substr($key, -$len) == $envName) {
|
485 |
+
return $item;
|
486 |
+
}
|
487 |
+
}
|
488 |
+
return false;
|
489 |
+
}
|
490 |
+
|
491 |
+
$result = getEnvPassedInRewriteRule('PASSTHROUGHENV');
|
492 |
+
if ($result === false) {
|
493 |
+
echo '0';
|
494 |
+
exit;
|
495 |
+
}
|
496 |
+
echo ($result == $_SERVER['DOCUMENT_ROOT'] ? '1' : '0');
|
497 |
+
|
498 |
+
request:
|
499 |
+
url: 'test.php'
|
500 |
+
|
501 |
+
interpretation:
|
502 |
+
- ['success', 'body', 'equals', '1']
|
503 |
+
- ['failure', 'body', 'equals', '0']
|
504 |
+
- ['inconclusive', 'body', 'begins-with', '<?php']
|
505 |
+
- ['inconclusive']
|
506 |
+
```
|
507 |
+
|
508 |
+
</p>
|
509 |
+
</details>
|
510 |
+
|
511 |
+
<details><summary><b>passingInfoFromRewriteToScriptThroughRequestHeaderWorks()</b></summary>
|
512 |
+
<p><br>
|
513 |
+
Say you have a rewrite rule that points to a PHP script and you would like to pass some information along to the PHP. Usually, you will just pass it in the query string. But this won't do if the information is sensitive. In that case, there are some tricks available. The trick being tested here tells the RewriteRule directive to set an environment variable which a RequestHeader directive picks up on and passes on to the script in a request header.
|
514 |
+
|
515 |
+
Implementation (YAML definition):
|
516 |
+
|
517 |
+
```yaml
|
518 |
+
subdir: pass-info-from-rewrite-to-script-through-request-header
|
519 |
+
files:
|
520 |
+
- filename: '.htaccess'
|
521 |
+
content: |
|
522 |
+
<IfModule mod_rewrite.c>
|
523 |
+
RewriteEngine On
|
524 |
+
|
525 |
+
# Testing if we can pass an environment variable through a request header
|
526 |
+
# We pass document root, because that can easily be checked by the script
|
527 |
+
|
528 |
+
<IfModule mod_headers.c>
|
529 |
+
RequestHeader set PASSTHROUGHHEADER "%{PASSTHROUGHHEADER}e" env=PASSTHROUGHHEADER
|
530 |
+
</IfModule>
|
531 |
+
RewriteRule ^test\.php$ - [E=PASSTHROUGHHEADER:%{DOCUMENT_ROOT},L]
|
532 |
+
|
533 |
+
</IfModule>
|
534 |
+
- filename: 'test.php'
|
535 |
+
content: |
|
536 |
+
<?php
|
537 |
+
if (isset($_SERVER['HTTP_PASSTHROUGHHEADER'])) {
|
538 |
+
echo ($_SERVER['HTTP_PASSTHROUGHHEADER'] == $_SERVER['DOCUMENT_ROOT'] ? 1 : 0);
|
539 |
+
exit;
|
540 |
+
}
|
541 |
+
echo '0';
|
542 |
+
|
543 |
+
request:
|
544 |
+
url: 'test.php'
|
545 |
+
|
546 |
+
interpretation:
|
547 |
+
- ['success', 'body', 'equals', '1']
|
548 |
+
- ['failure', 'body', 'equals', '0']
|
549 |
+
- ['inconclusive', 'body', 'begins-with', '<?php']
|
550 |
+
- ['inconclusive']
|
551 |
+
```
|
552 |
+
|
553 |
+
</p>
|
554 |
+
</details>
|
555 |
+
|
556 |
+
<details><summary><b>rewriteWorks()</b></summary>
|
557 |
+
<p><br>
|
558 |
+
Tests if rewriting works.
|
559 |
+
|
560 |
+
Implementation (YAML definition):
|
561 |
+
```yaml
|
562 |
+
subdir: rewrite
|
563 |
+
files:
|
564 |
+
- filename: '.htaccess'
|
565 |
+
content: |
|
566 |
+
<IfModule mod_rewrite.c>
|
567 |
+
RewriteEngine On
|
568 |
+
RewriteRule ^0\.txt$ 1\.txt [L]
|
569 |
+
</IfModule>
|
570 |
+
- filename: '0.txt'
|
571 |
+
content: '0'
|
572 |
+
- filename: '1.txt'
|
573 |
+
content: '1'
|
574 |
+
|
575 |
+
request:
|
576 |
+
url: '0.txt'
|
577 |
+
|
578 |
+
interpretation:
|
579 |
+
- [success, body, equals, '1']
|
580 |
+
- [failure, body, equals, '0']
|
581 |
+
```
|
582 |
+
|
583 |
+
</p>
|
584 |
+
</details>
|
585 |
+
|
586 |
+
<details><summary><b>requestHeaderWorks()</b></summary>
|
587 |
+
<p><br>
|
588 |
+
Tests if a request header can be set using the *RequestHeader* directive.
|
589 |
+
|
590 |
+
Implementation (YAML definition):
|
591 |
+
|
592 |
+
```yaml
|
593 |
+
subdir: request-header
|
594 |
+
files:
|
595 |
+
- filename: '.htaccess'
|
596 |
+
content: |
|
597 |
+
<IfModule mod_headers.c>
|
598 |
+
# Certain hosts seem to strip non-standard request headers,
|
599 |
+
# so we use a standard one to avoid a false negative
|
600 |
+
RequestHeader set User-Agent "request-header-test"
|
601 |
+
</IfModule>
|
602 |
+
- filename: 'test.php'
|
603 |
+
content: |
|
604 |
+
<?php
|
605 |
+
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
606 |
+
echo $_SERVER['HTTP_USER_AGENT'] == 'request-header-test' ? 1 : 0;
|
607 |
+
} else {
|
608 |
+
echo 0;
|
609 |
+
}
|
610 |
+
|
611 |
+
request:
|
612 |
+
url: 'test.php'
|
613 |
+
|
614 |
+
interpretation:
|
615 |
+
- ['success', 'body', 'equals', '1']
|
616 |
+
- ['failure', 'body', 'equals', '0']
|
617 |
+
- ['inconclusive', 'body', 'begins-with', '<?php']
|
618 |
+
```
|
619 |
+
|
620 |
+
</p>
|
621 |
+
</details>
|
622 |
+
|
623 |
+
<details><summary><b>serverSignatureWorks()</b></summary>
|
624 |
+
<p><br>
|
625 |
+
Tests if the *ServerSignature* directive works.
|
626 |
+
|
627 |
+
Implementation (YAML definition):
|
628 |
+
```yaml
|
629 |
+
subdir: server-signature
|
630 |
+
subtests:
|
631 |
+
- subdir: on
|
632 |
+
files:
|
633 |
+
- filename: '.htaccess'
|
634 |
+
content: |
|
635 |
+
ServerSignature On
|
636 |
+
- filename: 'test.php'
|
637 |
+
content: |
|
638 |
+
<?php
|
639 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
640 |
+
echo 1;
|
641 |
+
} else {
|
642 |
+
echo 0;
|
643 |
+
}
|
644 |
+
request:
|
645 |
+
url: 'test.php'
|
646 |
+
interpretation:
|
647 |
+
- ['inconclusive', 'body', 'isEmpty']
|
648 |
+
- ['inconclusive', 'status-code', 'not-equals', '200']
|
649 |
+
- ['failure', 'body', 'equals', '0']
|
650 |
+
|
651 |
+
- subdir: off
|
652 |
+
files:
|
653 |
+
- filename: '.htaccess'
|
654 |
+
content: |
|
655 |
+
ServerSignature Off
|
656 |
+
- filename: 'test.php'
|
657 |
+
content: |
|
658 |
+
<?php
|
659 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
660 |
+
echo 0;
|
661 |
+
} else {
|
662 |
+
echo 1;
|
663 |
+
}
|
664 |
+
request:
|
665 |
+
url: 'test.php'
|
666 |
+
interpretation:
|
667 |
+
- ['inconclusive', 'body', 'isEmpty']
|
668 |
+
- ['success', 'body', 'equals', '1']
|
669 |
+
- ['failure', 'body', 'equals', '0']
|
670 |
+
- ['inconclusive']
|
671 |
+
```
|
672 |
+
|
673 |
+
</p>
|
674 |
+
</details>
|
675 |
+
|
676 |
+
### Other methods in HtaccessCapabilityTester
|
677 |
+
|
678 |
+
<details><summary><b>setHttpRequester($requester)</b></summary>
|
679 |
+
<p><br>
|
680 |
+
This allows you to use another object for making HTTP requests than the standard one provided by this library. The standard one uses `file_get_contents` to make the request and is implemented in `SimpleHttpRequester.php`. You might for example prefer to use *curl* or, if you are making a Wordpress plugin, you might want to use the one provided by the Wordpress framework.
|
681 |
+
</p>
|
682 |
+
</details>
|
683 |
+
|
684 |
+
<details><summary><b>setTestFilesLineUpper($testFilesLineUpper)</b></summary>
|
685 |
+
<p><br>
|
686 |
+
This allows you to use another object for lining up the test files than the standard one provided by this library. The standard one uses `file_put_contents` to save files and is implemented in `SimpleTestFileLineUpper.php`. You will probably not need to swap the test file line-upper.
|
687 |
+
</p>
|
688 |
+
</details>
|
689 |
+
|
690 |
+
## Stable API?
|
691 |
+
The 0.8 release is just about right. I do not expect any changes in the part of the API that is mentioned above. So, if you stick to that, it should still work, when the 1.0 release comes.
|
692 |
+
|
693 |
+
Changes in the new 0.8 release:
|
694 |
+
- HttpResponse now takes a map of headers rather than a numeric array. If you have implemented your own HttpRequester rather than using the default, you need to update it.
|
695 |
+
|
696 |
+
Expected changes in the 1.0 release:
|
697 |
+
- TestResult class might be disposed off so the "internal" Tester classes also returns bool|null.
|
698 |
+
- Throw custom exception when test file cannot be created
|
699 |
+
|
700 |
+
## Installation
|
701 |
+
Require the library with *Composer*, like this:
|
702 |
+
|
703 |
+
```text
|
704 |
+
composer require rosell-dk/htaccess-capability-tester
|
705 |
+
```
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "rosell-dk/htaccess-capability-tester",
|
3 |
+
"description": "Test the capabilities of .htaccess files on the server using live tests",
|
4 |
+
"type": "library",
|
5 |
+
"license": "MIT",
|
6 |
+
"keywords": [".htaccess", "apache", "litespeed"],
|
7 |
+
"scripts": {
|
8 |
+
"ci": [
|
9 |
+
"@phpcs src",
|
10 |
+
"@composer validate --no-check-all --strict",
|
11 |
+
"@phpstan-global",
|
12 |
+
"@test-no-cov"
|
13 |
+
],
|
14 |
+
"test": "phpunit --coverage-text",
|
15 |
+
"phpunit": "phpunit --coverage-text",
|
16 |
+
"test-no-cov": "phpunit --no-coverage",
|
17 |
+
"cs-fix-all": [
|
18 |
+
"php-cs-fixer fix src"
|
19 |
+
],
|
20 |
+
"cs-fix": "php-cs-fixer fix",
|
21 |
+
"cs-dry": "php-cs-fixer fix --dry-run --diff",
|
22 |
+
"phpcs": "phpcs --standard=PSR2",
|
23 |
+
"phpcbf": "phpcbf --standard=PSR2",
|
24 |
+
"phpstan": "vendor/bin/phpstan analyse src --level=4",
|
25 |
+
"phpstan-global": "~/.composer/vendor/bin/phpstan analyse src --level=4"
|
26 |
+
},
|
27 |
+
"extra": {
|
28 |
+
"scripts-descriptions": {
|
29 |
+
"ci": "Run tests before CI",
|
30 |
+
"phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
|
31 |
+
"phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
|
32 |
+
"cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
|
33 |
+
"cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
|
34 |
+
"test": "Launches the preconfigured PHPUnit"
|
35 |
+
}
|
36 |
+
},
|
37 |
+
"autoload": {
|
38 |
+
"psr-4": { "HtaccessCapabilityTester\\": "src/" }
|
39 |
+
},
|
40 |
+
"autoload-dev": {
|
41 |
+
"psr-4": { "HtaccessCapabilityTester\\Tests\\": "tests/" }
|
42 |
+
},
|
43 |
+
"authors": [
|
44 |
+
{
|
45 |
+
"name": "Bjørn Rosell",
|
46 |
+
"homepage": "https://www.bitwise-it.dk/contact",
|
47 |
+
"role": "Project Author"
|
48 |
+
}
|
49 |
+
],
|
50 |
+
"require": {
|
51 |
+
"php": "^5.6 | ^7.0"
|
52 |
+
},
|
53 |
+
"suggest": {
|
54 |
+
"php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
|
55 |
+
},
|
56 |
+
"repositories": [
|
57 |
+
{
|
58 |
+
"type": "git",
|
59 |
+
"url": "https://github.com/elyobo/php-code-coverage.git"
|
60 |
+
}
|
61 |
+
],
|
62 |
+
"require-dev": {
|
63 |
+
"phpunit/phpunit": "^5.7",
|
64 |
+
"phpunit/php-code-coverage": "dev-4.0-dev as 4.0.4",
|
65 |
+
"squizlabs/php_codesniffer": "3.*"
|
66 |
+
},
|
67 |
+
"minimum-stability": "dev",
|
68 |
+
"prefer-stable": true,
|
69 |
+
"config": {
|
70 |
+
"sort-packages": true
|
71 |
+
}
|
72 |
+
}
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Grant All Crash Testing
|
2 |
+
|
3 |
+
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.
|
4 |
+
|
5 |
+
I removed the class, as I found it a bit too specialized.
|
6 |
+
Here is the `.htaccess` it was testing:
|
7 |
+
|
8 |
+
```
|
9 |
+
# This .htaccess is here in order to test if it results in a 500 Internal Server Error.
|
10 |
+
# .htaccess files can result in 500 Internal Server Error when they contain directives that has
|
11 |
+
# not been allowed for the directory it is in (that stuff is controlled with "AllowOverride" or
|
12 |
+
# "AllowOverrideList" in httpd.conf)
|
13 |
+
#
|
14 |
+
# The use case of a .htaccess file like the one tested here would be an attempt to override
|
15 |
+
# meassurements taken to prevent access. As an example, in Wordpress, there are security plugins
|
16 |
+
# which puts "Require all denied" into .htaccess files in certain directories in order to strengthen
|
17 |
+
# security. Such security meassurements could even be applied to the plugins directory, as plugins
|
18 |
+
# normally should not need PHPs to be requested directly. But of course, there are cases where plugin
|
19 |
+
# authors need to anyway and thus find themselves counterfighting the security plugin with an .htaccess
|
20 |
+
# like this. But in doing so, they run the risk of the 500 Internal Server Error. There are standard
|
21 |
+
# setups out there which not only does not allow "Require" directives, but are configured to go fatal
|
22 |
+
# about it.
|
23 |
+
#
|
24 |
+
# The following directives is used in this .htaccess file:
|
25 |
+
# - Require (Override: AuthConfig)
|
26 |
+
# - Order (Override: Limit)
|
27 |
+
# - FilesMatch (Override: All)
|
28 |
+
# - IfModule (Override: All)
|
29 |
+
|
30 |
+
# FilesMatch should usually be used in this use case, as you would not want to be granting more access
|
31 |
+
# than you need
|
32 |
+
<FilesMatch "ping\.txt$">
|
33 |
+
<IfModule !mod_authz_core.c>
|
34 |
+
Order deny,allow
|
35 |
+
Allow from all
|
36 |
+
</IfModule>
|
37 |
+
<IfModule mod_authz_core.c>
|
38 |
+
Require all granted
|
39 |
+
</IfModule>
|
40 |
+
</FilesMatch>
|
41 |
+
```
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Which name is best?
|
2 |
+
```php
|
3 |
+
|
4 |
+
|
5 |
+
if ($hct->rewriteWorks()) {
|
6 |
+
|
7 |
+
}
|
8 |
+
if ($hct->addTypeWorks()) {
|
9 |
+
|
10 |
+
}
|
11 |
+
if ($hct->serverSignatureWorks()) {
|
12 |
+
|
13 |
+
}
|
14 |
+
if ($hct->contentDigestWorks()) {
|
15 |
+
}
|
16 |
+
|
17 |
+
|
18 |
+
$hct->rewriteWorks();
|
19 |
+
$hct->canRewrite();
|
20 |
+
$hct->rewrite()
|
21 |
+
$hct->isRewriteWorking();
|
22 |
+
$hct->canUseRewrite();
|
23 |
+
$hct->hasRewrite()
|
24 |
+
$hct->mayRewrite()
|
25 |
+
$hct->doesRewriteWork();
|
26 |
+
$hct->rewriting();
|
27 |
+
$hct->rewritingWorks();
|
28 |
+
$hct->RewriteRule();
|
29 |
+
$hct->rewriteSupported();
|
30 |
+
$hct->rewriteWorks();
|
31 |
+
$hct->testRewriting();
|
32 |
+
$hct->test('RewriteRule');
|
33 |
+
$hct->runTest(new RewriteTester());
|
34 |
+
$hct->runTest()->RewriteRule();
|
35 |
+
$hct->canDoRewrite();
|
36 |
+
$hct->haveRewrite();
|
37 |
+
$hct->rewriteAvail();
|
38 |
+
$hct->isRewriteAvailable();
|
39 |
+
$hct->isRewriteAccessible();
|
40 |
+
$hct->isRewriteOperative();
|
41 |
+
$hct->isRewriteOperational();
|
42 |
+
$hct->isRewriteFunctional();
|
43 |
+
$hct->isRewritePossible();
|
44 |
+
$hct->isRewriteOk();
|
45 |
+
$hct->isRewriteFlying();
|
46 |
+
$hct->rewritePasses();
|
47 |
+
|
48 |
+
if ($hct->canRewrite()) {
|
49 |
+
|
50 |
+
}
|
51 |
+
|
52 |
+
if ($hct->rewriteWorks()) {
|
53 |
+
|
54 |
+
}
|
55 |
+
|
56 |
+
if ($hct->rewriteOk()) {
|
57 |
+
|
58 |
+
}
|
59 |
+
if ($hct->rewriteQM()) {
|
60 |
+
|
61 |
+
}
|
62 |
+
|
63 |
+
if ($hct->rewriteNA()) {
|
64 |
+
|
65 |
+
}
|
66 |
+
|
67 |
+
// --------------
|
68 |
+
|
69 |
+
$hct->canAddType();
|
70 |
+
$hct->addTypeWorks();
|
71 |
+
$hct->addTypeQM();
|
72 |
+
|
73 |
+
$hct->canUseAddType();
|
74 |
+
$hct->doesAddTypeWork();
|
75 |
+
$hct->addType();
|
76 |
+
$hct->AddType();
|
77 |
+
$hct->addTypeSupported();
|
78 |
+
$hct->addTypeWorks();
|
79 |
+
$hct->addTypeLive();
|
80 |
+
$hct->addTypeYes();
|
81 |
+
$hct->addTypeFF(); // fully functional
|
82 |
+
$hct->testAddType();
|
83 |
+
$hct->test('AddType');
|
84 |
+
$hct->run(new AddTypeTester());
|
85 |
+
$hct->runTest('AddType');
|
86 |
+
$hct->runTest()->AddType();
|
87 |
+
$hct->runTest(\HtaccessCapabilityTester\AddType);
|
88 |
+
$hct->canIUse('AddType');
|
89 |
+
|
90 |
+
// ------------------
|
91 |
+
|
92 |
+
if ($hct->canContentDigest()) {
|
93 |
+
}
|
94 |
+
|
95 |
+
if ($hct->contentDigestWorks()) {
|
96 |
+
}
|
97 |
+
|
98 |
+
if ($hct->contentDigestOk()) {
|
99 |
+
}
|
100 |
+
|
101 |
+
if ($hct->contentDigestFF()) {
|
102 |
+
}
|
103 |
+
|
104 |
+
|
105 |
+
$hct->canContentDigest();
|
106 |
+
$hct->contentDigestWorks();
|
107 |
+
$hct->canUseContentDigest();
|
108 |
+
$hct->doesContentDigestWork();
|
109 |
+
$hct->contentDigest();
|
110 |
+
$hct->ContentDigest();
|
111 |
+
|
112 |
+
|
113 |
+
// ---------------------
|
114 |
+
|
115 |
+
if ($hct->serverSignatureWorks()) {
|
116 |
+
}
|
117 |
+
|
118 |
+
if ($hct->canSetServerSignature()) {
|
119 |
+
|
120 |
+
}
|
121 |
+
|
122 |
+
if ($hct->testServerSignature()) {
|
123 |
+
|
124 |
+
}
|
125 |
+
|
126 |
+
if ($hct->doesServerSignatureWork()) {
|
127 |
+
|
128 |
+
}
|
129 |
+
if ($hct->isServerSignatureAllowed()) {
|
130 |
+
|
131 |
+
}
|
132 |
+
if ($hct->isServerSignatureWorking()) {
|
133 |
+
|
134 |
+
|
135 |
+
}
|
136 |
+
|
137 |
+
// --------------------
|
138 |
+
|
139 |
+
$hct->modRewriteLoaded();
|
140 |
+
|
141 |
+
$hct->moduleLoaded('rewrite');
|
142 |
+
|
143 |
+
$hct->testModuleLoaded('rewrite');
|
144 |
+
|
145 |
+
$hct->modLoaded('rewrite');
|
146 |
+
|
147 |
+
// --------------------
|
148 |
+
|
149 |
+
$hct->doesThisCrash();
|
150 |
+
$hct->kaput();
|
151 |
+
$hct->ooo();
|
152 |
+
$hct->na();
|
153 |
+
|
154 |
+
|
155 |
+
|
156 |
+
|
157 |
+
|
158 |
+
```
|
159 |
+
|
160 |
+
# IDEA:
|
161 |
+
```yaml
|
162 |
+
subdir: rewrite
|
163 |
+
files:
|
164 |
+
- filename: '.htaccess'
|
165 |
+
content: |
|
166 |
+
<IfModule mod_rewrite.c>
|
167 |
+
RewriteEngine On
|
168 |
+
RewriteRule ^0\.txt$ 1\.txt [L]
|
169 |
+
</IfModule>
|
170 |
+
- filename: '0.txt'
|
171 |
+
content: '0'
|
172 |
+
- filename: '1.txt'
|
173 |
+
content: '1'
|
174 |
+
|
175 |
+
request:
|
176 |
+
url: '0.txt'
|
177 |
+
|
178 |
+
interpretation:
|
179 |
+
- [success, body, equals, '1']
|
180 |
+
- [failure, body, equals, '0']
|
181 |
+
- [interprete500, status-code, equals, '500'] # inconclusive if innocent also crashes, otherwise failure
|
182 |
+
- [inconclusive, status-code, equals, '403']
|
183 |
+
|
184 |
+
- if: [status-code, equals, '500']
|
185 |
+
then:
|
186 |
+
- if: [doesInnocentCrash()]
|
187 |
+
then: inconclusive
|
188 |
+
else: failure
|
189 |
+
- [inconclusive]
|
190 |
+
|
191 |
+
```
|
192 |
+
or:
|
193 |
+
```yaml
|
194 |
+
interpretation:
|
195 |
+
- [success, body, equals, '1']
|
196 |
+
- [failure, body, equals, '0']
|
197 |
+
- [handle-errors] # Standard error handling (403, 404, 500)
|
198 |
+
|
199 |
+
|
200 |
+
```
|
201 |
+
|
202 |
+
|
203 |
+
```php
|
204 |
+
[
|
205 |
+
'interpretation' => [
|
206 |
+
[
|
207 |
+
'if' => ['body', 'equals', '1'],
|
208 |
+
'then' => ['success']
|
209 |
+
],
|
210 |
+
[
|
211 |
+
'if' => ['body', 'equals', '0'],
|
212 |
+
'then' => ['failure', 'no-effect']
|
213 |
+
],
|
214 |
+
[
|
215 |
+
'if' => ['status-code', 'equals', '500'],
|
216 |
+
'then' => 'handle500()'
|
217 |
+
],
|
218 |
+
[
|
219 |
+
'if' => ['status-code', 'equals', '500'],
|
220 |
+
'then' => 'handle500()'
|
221 |
+
]
|
222 |
+
]
|
223 |
+
|
224 |
+
```
|
225 |
+
|
226 |
+
```yaml
|
227 |
+
|
228 |
+
```
|
229 |
+
|
230 |
+
crashTestInnocent
|
231 |
+
|
232 |
+
handle500:
|
233 |
+
returns "failure" if innocent request succeeds
|
234 |
+
returns "inconclusive" if innocent request fails
|
235 |
+
|
236 |
+
handle403:
|
237 |
+
if innocent request also 403, all requests probably does
|
238 |
+
returns "failure" if innocent request succeeds
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
|
3 |
+
## More examples of what you can test:
|
4 |
+
|
5 |
+
```php
|
6 |
+
|
7 |
+
require 'vendor/autoload.php';
|
8 |
+
use HtaccessCapabilityTester\HtaccessCapabilityTester;
|
9 |
+
|
10 |
+
$hct = new HtaccessCapabilityTester($baseDir, $baseUrl);
|
11 |
+
|
12 |
+
$rulesToCrashTest = <<<'EOD'
|
13 |
+
<ifModule mod_rewrite.c>
|
14 |
+
RewriteEngine On
|
15 |
+
</ifModule>
|
16 |
+
EOD;
|
17 |
+
if ($hct->crashTest($rulesToCrashTest)) {
|
18 |
+
// The rules at least did not cause requests to anything in the folder to "crash".
|
19 |
+
// (even simple rules like the above can make the server respond with a
|
20 |
+
// 500 Internal Server Error - see "docs/TheManyWaysOfHtaccessFailure.md")
|
21 |
+
}
|
22 |
+
|
23 |
+
if ($hct->addTypeWorks()) {
|
24 |
+
// AddType directive works
|
25 |
+
}
|
26 |
+
|
27 |
+
if ($hct->headerSetWorks()) {
|
28 |
+
// "Header set" works
|
29 |
+
}
|
30 |
+
if ($hct->requestHeaderWorks()) {
|
31 |
+
// "RequestHeader set" works
|
32 |
+
}
|
33 |
+
|
34 |
+
// Note that the tests returns null if they are inconclusive
|
35 |
+
$testResult = $hct->htaccessEnabled();
|
36 |
+
if (is_null($testResult)) {
|
37 |
+
// Inconclusive!
|
38 |
+
// Perhaps a 403 Forbidden?
|
39 |
+
// You can get a bit textual insight by using:
|
40 |
+
// $hct->infoFromLastTest
|
41 |
+
}
|
42 |
+
|
43 |
+
// Also note that an exception will be thrown if test files cannot be created.
|
44 |
+
// You might want to wrap your call in a try-catch statement.
|
45 |
+
try {
|
46 |
+
if ($hct->requestHeaderWorks()) {
|
47 |
+
// "RequestHeader set" works
|
48 |
+
}
|
49 |
+
|
50 |
+
} catch (\Exception $e) {
|
51 |
+
// Probably permission problems.
|
52 |
+
// We should probably notify someone
|
53 |
+
}
|
54 |
+
```
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# The many ways of .htaccess failure
|
2 |
+
|
3 |
+
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!
|
4 |
+
|
5 |
+
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.
|
6 |
+
|
7 |
+
The settings of AllowOverride and AllowOverrideList can produce three kinds of failures:
|
8 |
+
|
9 |
+
1. The .htaccess is skipped altogether. This happens when nothing is allowed (when both AllowOverride and AllowOverrideList are set to None)
|
10 |
+
|
11 |
+
2. The forbidden tags are ignored. This happens if the "Nonfatal" setting for AllowOverride is set to "All" or "Override"
|
12 |
+
|
13 |
+
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.
|
14 |
+
|
15 |
+
So, no, using IfModule tags does not make the .htaccess failproof.
|
16 |
+
|
17 |
+
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.
|
18 |
+
|
19 |
+
Besides these cases, there is of course also the authorization directives.
|
20 |
+
The sysadmin might have placed something like this in the virtual host configuration:
|
21 |
+
|
22 |
+
```
|
23 |
+
<Directory /var/www/your-site/media/ >
|
24 |
+
<FilesMatch "\.php$">
|
25 |
+
Require all denied
|
26 |
+
</FilesMatch>
|
27 |
+
</Directory>
|
28 |
+
```
|
29 |
+
|
30 |
+
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.
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
### Running your own test
|
2 |
+
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`).
|
3 |
+
|
4 |
+
### Using another library for making the HTTP request
|
5 |
+
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)`
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"test.php" will either result in "0", "1" or an error.
|
2 |
+
|
3 |
+
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.
|
4 |
+
|
5 |
+
|
6 |
+
|
7 |
+
Here is another example, on how
|
8 |
+
|
9 |
+
For example, the following two files can be used for answering the question: Is mod_env loaded?
|
10 |
+
|
11 |
+
**.htaccess**
|
12 |
+
```
|
13 |
+
<IfModule mod_setenvif.c>
|
14 |
+
ServerSignature On
|
15 |
+
</IfModule>
|
16 |
+
<IfModule !mod_setenvif.c>
|
17 |
+
ServerSignature Off
|
18 |
+
</IfModule>
|
19 |
+
```
|
20 |
+
|
21 |
+
**test.php**
|
22 |
+
```
|
23 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
24 |
+
echo 1;
|
25 |
+
} else {
|
26 |
+
echo 0;
|
27 |
+
}
|
28 |
+
```
|
29 |
+
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
|
30 |
+
|
31 |
+
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
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
|
3 |
+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
4 |
+
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
5 |
+
backupGlobals="false"
|
6 |
+
backupStaticAttributes="false"
|
7 |
+
colors="true"
|
8 |
+
convertErrorsToExceptions="true"
|
9 |
+
convertNoticesToExceptions="true"
|
10 |
+
convertWarningsToExceptions="false"
|
11 |
+
processIsolation="false"
|
12 |
+
stopOnFailure="false"
|
13 |
+
bootstrap="vendor/autoload.php"
|
14 |
+
|
15 |
+
>
|
16 |
+
<testsuites>
|
17 |
+
<testsuite name="HtaccessCapabilitTester Test Suite">
|
18 |
+
<directory>./tests/</directory>
|
19 |
+
</testsuite>
|
20 |
+
</testsuites>
|
21 |
+
|
22 |
+
<filter>
|
23 |
+
<whitelist>
|
24 |
+
<directory suffix=".php">src/</directory>
|
25 |
+
<exclude>
|
26 |
+
<directory>./vendor</directory>
|
27 |
+
<directory>./tests</directory>
|
28 |
+
</exclude>
|
29 |
+
</whitelist>
|
30 |
+
</filter>
|
31 |
+
|
32 |
+
<logging>
|
33 |
+
<log type="junit" target="build/report.junit.xml"/>
|
34 |
+
<log type="coverage-clover" target="coverage.clover"/>
|
35 |
+
<log type="coverage-text" target="build/coverage.txt"/>
|
36 |
+
<log type="coverage-html" target="build/coverage"/>
|
37 |
+
</logging>
|
38 |
+
|
39 |
+
</phpunit>
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\Testers\AbstractTester;
|
6 |
+
use \HtaccessCapabilityTester\Testers\AddTypeTester;
|
7 |
+
use \HtaccessCapabilityTester\Testers\ContentDigestTester;
|
8 |
+
use \HtaccessCapabilityTester\Testers\CrashTester;
|
9 |
+
use \HtaccessCapabilityTester\Testers\CustomTester;
|
10 |
+
use \HtaccessCapabilityTester\Testers\DirectoryIndexTester;
|
11 |
+
use \HtaccessCapabilityTester\Testers\HeaderSetTester;
|
12 |
+
use \HtaccessCapabilityTester\Testers\HtaccessEnabledTester;
|
13 |
+
use \HtaccessCapabilityTester\Testers\InnocentRequestTester;
|
14 |
+
use \HtaccessCapabilityTester\Testers\ModuleLoadedTester;
|
15 |
+
use \HtaccessCapabilityTester\Testers\PassInfoFromRewriteToScriptThroughRequestHeaderTester;
|
16 |
+
use \HtaccessCapabilityTester\Testers\PassInfoFromRewriteToScriptThroughEnvTester;
|
17 |
+
use \HtaccessCapabilityTester\Testers\RewriteTester;
|
18 |
+
use \HtaccessCapabilityTester\Testers\RequestHeaderTester;
|
19 |
+
use \HtaccessCapabilityTester\Testers\ServerSignatureTester;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Main entrance.
|
23 |
+
*
|
24 |
+
* @package HtaccessCapabilityTester
|
25 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
26 |
+
* @since Class available since 0.7
|
27 |
+
*/
|
28 |
+
class HtaccessCapabilityTester
|
29 |
+
{
|
30 |
+
|
31 |
+
/** @var string The dir where the test files should be put */
|
32 |
+
protected $baseDir;
|
33 |
+
|
34 |
+
/** @var string The base url that the tests can be run from (corresponds to $baseDir) */
|
35 |
+
protected $baseUrl;
|
36 |
+
|
37 |
+
/** @var string Additional info regarding last test (often empty) */
|
38 |
+
public $infoFromLastTest;
|
39 |
+
|
40 |
+
/** @var string Status code from last test (can be empty) */
|
41 |
+
public $statusCodeOfLastRequest;
|
42 |
+
|
43 |
+
|
44 |
+
/** @var HttpRequesterInterface The object used to make the HTTP request */
|
45 |
+
private $requester;
|
46 |
+
|
47 |
+
/** @var TestFilesLineUpperInterface The object used to line up the test files */
|
48 |
+
private $testFilesLineUpper;
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Constructor.
|
52 |
+
*
|
53 |
+
* @param string $baseDir Directory on the server where the test files can be put
|
54 |
+
* @param string $baseUrl The base URL of the test files
|
55 |
+
*
|
56 |
+
* @return void
|
57 |
+
*/
|
58 |
+
public function __construct($baseDir, $baseUrl)
|
59 |
+
{
|
60 |
+
$this->baseDir = $baseDir;
|
61 |
+
$this->baseUrl = $baseUrl;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Run a test, store the info and return the status.
|
66 |
+
*
|
67 |
+
* @param AbstractTester $tester
|
68 |
+
*
|
69 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
70 |
+
*/
|
71 |
+
private function runTest($tester)
|
72 |
+
{
|
73 |
+
//$tester->setHtaccessCapabilityTester($this);
|
74 |
+
if (isset($this->requester)) {
|
75 |
+
$tester->setHttpRequester($this->requester);
|
76 |
+
}
|
77 |
+
if (isset($this->testFilesLineUpper)) {
|
78 |
+
$tester->setTestFilesLineUpper($this->testFilesLineUpper);
|
79 |
+
}
|
80 |
+
//$tester->setHtaccessCapabilityTester($this);
|
81 |
+
|
82 |
+
$cacheKeys = [$this->baseDir, $tester->getCacheKey()];
|
83 |
+
if (TestResultCache::isCached($cacheKeys)) {
|
84 |
+
$testResult = TestResultCache::getCached($cacheKeys);
|
85 |
+
} else {
|
86 |
+
$testResult = $tester->run($this->baseDir, $this->baseUrl);
|
87 |
+
TestResultCache::cache($cacheKeys, $testResult);
|
88 |
+
}
|
89 |
+
|
90 |
+
$this->infoFromLastTest = $testResult->info;
|
91 |
+
$this->statusCodeOfLastRequest = $testResult->statusCodeOfLastRequest;
|
92 |
+
return $testResult->status;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Run a test, store the info and return the status.
|
97 |
+
*
|
98 |
+
* @param HttpRequesterInterface $requester
|
99 |
+
*
|
100 |
+
* @return void
|
101 |
+
*/
|
102 |
+
public function setHttpRequester($requester)
|
103 |
+
{
|
104 |
+
$this->requester = $requester;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Set object responsible for lining up the test files.
|
109 |
+
*
|
110 |
+
* @param TestFilesLineUpperInterface $testFilesLineUpper
|
111 |
+
* @return void
|
112 |
+
*/
|
113 |
+
public function setTestFilesLineUpper($testFilesLineUpper)
|
114 |
+
{
|
115 |
+
$this->testFilesLineUpper = $testFilesLineUpper;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Test if .htaccess files are enabled
|
120 |
+
*
|
121 |
+
* Apache can be configured to completely ignore .htaccess files. This test examines
|
122 |
+
* if .htaccess files are proccesed.
|
123 |
+
*
|
124 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
125 |
+
*/
|
126 |
+
public function htaccessEnabled()
|
127 |
+
{
|
128 |
+
return $this->runTest(new HtaccessEnabledTester());
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Test if a module is loaded.
|
133 |
+
*
|
134 |
+
* This test detects if directives inside a "IfModule" is run for a given module
|
135 |
+
*
|
136 |
+
* @param string $moduleName A valid Apache module name (ie "rewrite")
|
137 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
138 |
+
*/
|
139 |
+
public function moduleLoaded($moduleName)
|
140 |
+
{
|
141 |
+
return $this->runTest(new ModuleLoadedTester($moduleName));
|
142 |
+
}
|
143 |
+
|
144 |
+
/**
|
145 |
+
* Test if rewriting works.
|
146 |
+
*
|
147 |
+
* The .htaccess in this test uses the following directives:
|
148 |
+
* - IfModule
|
149 |
+
* - RewriteEngine
|
150 |
+
* - Rewrite
|
151 |
+
*
|
152 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
153 |
+
*/
|
154 |
+
public function rewriteWorks()
|
155 |
+
{
|
156 |
+
return $this->runTest(new RewriteTester());
|
157 |
+
}
|
158 |
+
|
159 |
+
/**
|
160 |
+
* Test if AddType works.
|
161 |
+
*
|
162 |
+
* The .htaccess in this test uses the following directives:
|
163 |
+
* - IfModule (core)
|
164 |
+
* - AddType (mod_mime, FileInfo)
|
165 |
+
*
|
166 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
167 |
+
*/
|
168 |
+
public function addTypeWorks()
|
169 |
+
{
|
170 |
+
return $this->runTest(new AddTypeTester());
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Test if setting a Response Header with the Header directive works.
|
175 |
+
*
|
176 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
177 |
+
*/
|
178 |
+
public function headerSetWorks()
|
179 |
+
{
|
180 |
+
return $this->runTest(new HeaderSetTester());
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* Test if setting a Request Header with the RequestHeader directive works.
|
185 |
+
*
|
186 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
187 |
+
*/
|
188 |
+
public function requestHeaderWorks()
|
189 |
+
{
|
190 |
+
return $this->runTest(new RequestHeaderTester());
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Test if ContentDigest directive works.
|
195 |
+
*
|
196 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
197 |
+
*/
|
198 |
+
public function contentDigestWorks()
|
199 |
+
{
|
200 |
+
return $this->runTest(new ContentDigestTester());
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Test if ServerSignature directive works.
|
205 |
+
*
|
206 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
207 |
+
*/
|
208 |
+
public function serverSignatureWorks()
|
209 |
+
{
|
210 |
+
return $this->runTest(new ServerSignatureTester());
|
211 |
+
}
|
212 |
+
|
213 |
+
|
214 |
+
/**
|
215 |
+
* Test if DirectoryIndex works.
|
216 |
+
*
|
217 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
218 |
+
*/
|
219 |
+
public function directoryIndexWorks()
|
220 |
+
{
|
221 |
+
return $this->runTest(new DirectoryIndexTester());
|
222 |
+
}
|
223 |
+
|
224 |
+
/**
|
225 |
+
* Test a complex construct for passing information from a rewrite to a script through a request header.
|
226 |
+
*
|
227 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
228 |
+
*/
|
229 |
+
public function passingInfoFromRewriteToScriptThroughRequestHeaderWorks()
|
230 |
+
{
|
231 |
+
return $this->runTest(new PassInfoFromRewriteToScriptThroughRequestHeaderTester());
|
232 |
+
}
|
233 |
+
|
234 |
+
|
235 |
+
/**
|
236 |
+
* Test if an environment variable can be set in a rewrite rule and received in PHP.
|
237 |
+
*
|
238 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
239 |
+
*/
|
240 |
+
public function passingInfoFromRewriteToScriptThroughEnvWorks()
|
241 |
+
{
|
242 |
+
return $this->runTest(new PassInfoFromRewriteToScriptThroughEnvTester());
|
243 |
+
}
|
244 |
+
|
245 |
+
/**
|
246 |
+
* Call one of the methods of this class (not all allowed).
|
247 |
+
*
|
248 |
+
* @param string $functionCall ie "rewriteWorks()"
|
249 |
+
*
|
250 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
251 |
+
*/
|
252 |
+
/*
|
253 |
+
public function callMethod($functionCall)
|
254 |
+
{
|
255 |
+
switch ($functionCall) {
|
256 |
+
case 'htaccessEnabled()':
|
257 |
+
return $this->htaccessEnabled();
|
258 |
+
case 'rewriteWorks()':
|
259 |
+
return $this->rewriteWorks();
|
260 |
+
case 'addTypeWorks()':
|
261 |
+
return $this->addTypeWorks();
|
262 |
+
case 'headerSetWorks()':
|
263 |
+
return $this->headerSetWorks();
|
264 |
+
case 'requestHeaderWorks()':
|
265 |
+
return $this->requestHeaderWorks();
|
266 |
+
case 'contentDigestWorks()':
|
267 |
+
return $this->contentDigestWorks();
|
268 |
+
case 'directoryIndexWorks()':
|
269 |
+
return $this->directoryIndexWorks();
|
270 |
+
case 'passingInfoFromRewriteToScriptThroughRequestHeaderWorks()':
|
271 |
+
return $this->passingInfoFromRewriteToScriptThroughRequestHeaderWorks();
|
272 |
+
case 'passingInfoFromRewriteToScriptThroughEnvWorks()':
|
273 |
+
return $this->passingInfoFromRewriteToScriptThroughEnvWorks();
|
274 |
+
default:
|
275 |
+
throw new \Exception('The method is not callable');
|
276 |
+
}
|
277 |
+
|
278 |
+
// TODO: moduleLoaded($moduleName)
|
279 |
+
}*/
|
280 |
+
|
281 |
+
/**
|
282 |
+
* Crash-test some .htaccess rules.
|
283 |
+
*
|
284 |
+
* Tests if the server can withstand the given rules without going fatal.
|
285 |
+
*
|
286 |
+
* - success: if the rules does not result in status 500.
|
287 |
+
* - failure: if the rules results in status 500 while a request to a file in a directory
|
288 |
+
* without any .htaccess succeeds (<> 500)
|
289 |
+
* - inconclusive: if the rules results in status 500 while a request to a file in a directory
|
290 |
+
* without any .htaccess also fails (500)
|
291 |
+
*
|
292 |
+
* @param string $rules Rules to crash-test
|
293 |
+
* @param string $subDir (optional) Subdir for the .htaccess to reside.
|
294 |
+
* if left out, a unique string will be generated
|
295 |
+
*
|
296 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
297 |
+
*/
|
298 |
+
public function crashTest($rules, $subDir = null)
|
299 |
+
{
|
300 |
+
return $this->runTest(new CrashTester($rules, $subDir));
|
301 |
+
}
|
302 |
+
|
303 |
+
/**
|
304 |
+
* Test an innocent request to a text file.
|
305 |
+
*
|
306 |
+
* If this fails, everything else will also fail.
|
307 |
+
*
|
308 |
+
* Possible reasons for failure:
|
309 |
+
* - A .htaccess in a parent folder has forbidden tags / syntax errors
|
310 |
+
*
|
311 |
+
* Possible reasons for inconclusive (= test could not be run)
|
312 |
+
* - 403 Forbidden
|
313 |
+
* - 404 Not Found
|
314 |
+
* - Request fails (ie due to timeout)
|
315 |
+
*
|
316 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
317 |
+
*/
|
318 |
+
public function innocentRequestWorks()
|
319 |
+
{
|
320 |
+
return $this->runTest(new InnocentRequestTester());
|
321 |
+
}
|
322 |
+
|
323 |
+
/**
|
324 |
+
* Run a custom test.
|
325 |
+
*
|
326 |
+
* @param array $definition
|
327 |
+
*
|
328 |
+
* @return bool|null true=success, false=failure, null=inconclusive
|
329 |
+
*/
|
330 |
+
public function customTest($definition)
|
331 |
+
{
|
332 |
+
return $this->runTest(new CustomTester($definition));
|
333 |
+
}
|
334 |
+
}
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
interface HttpRequesterInterface
|
6 |
+
{
|
7 |
+
/**
|
8 |
+
* Make a HTTP request to a URL.
|
9 |
+
*
|
10 |
+
* @return HttpResponse A HttpResponse object, which simply contains body and status code.
|
11 |
+
*/
|
12 |
+
public function makeHttpRequest($url);
|
13 |
+
}
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for holding properties of a HttpResponse
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since 0.7
|
11 |
+
*/
|
12 |
+
class HttpResponse
|
13 |
+
{
|
14 |
+
|
15 |
+
/* @var string the body of the response */
|
16 |
+
public $body;
|
17 |
+
|
18 |
+
/* @var string the status code of the response */
|
19 |
+
public $statusCode;
|
20 |
+
|
21 |
+
/* @var array the response headers keyed by lowercased field name */
|
22 |
+
public $headersMapLowerCase;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Constructor.
|
26 |
+
*
|
27 |
+
* @param string $body
|
28 |
+
* @param string $statusCode
|
29 |
+
* @param array $headersMap Map of headers, keyed by field name.
|
30 |
+
* There is only one value (string) for each key.
|
31 |
+
* If there are multiple values, they must be separated by comma
|
32 |
+
*
|
33 |
+
* @return void
|
34 |
+
*/
|
35 |
+
public function __construct($body, $statusCode, $headersMap)
|
36 |
+
{
|
37 |
+
$this->body = $body;
|
38 |
+
$this->statusCode = $statusCode;
|
39 |
+
$this->headersMapLowerCase = array_change_key_case($headersMap, CASE_LOWER);
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Check if the response has a header
|
44 |
+
*
|
45 |
+
* @param string $fieldName
|
46 |
+
* @return bool
|
47 |
+
*/
|
48 |
+
public function hasHeader($fieldName)
|
49 |
+
{
|
50 |
+
$fieldName = strtolower($fieldName);
|
51 |
+
return (isset($this->headersMapLowerCase[$fieldName]));
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Check if the response has a header with a given value
|
56 |
+
*
|
57 |
+
* @param string $fieldName
|
58 |
+
* @param string $fieldValue
|
59 |
+
* @return bool
|
60 |
+
*/
|
61 |
+
public function hasHeaderValue($fieldName, $fieldValue)
|
62 |
+
{
|
63 |
+
$fieldName = strtolower($fieldName);
|
64 |
+
if (!isset($this->headersMapLowerCase[$fieldName])) {
|
65 |
+
return false;
|
66 |
+
}
|
67 |
+
$values = explode(',', $this->headersMapLowerCase[$fieldName]);
|
68 |
+
foreach ($values as $value) {
|
69 |
+
if (trim($value) == $fieldValue) {
|
70 |
+
return true;
|
71 |
+
}
|
72 |
+
}
|
73 |
+
return false;
|
74 |
+
}
|
75 |
+
|
76 |
+
}
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
class SimpleHttpRequester implements HttpRequesterInterface
|
6 |
+
{
|
7 |
+
/**
|
8 |
+
* Make a HTTP request to a URL.
|
9 |
+
*
|
10 |
+
* @param string $url The URL to make the HTTP request to
|
11 |
+
*
|
12 |
+
* @return HttpResponse A HttpResponse object, which simply contains body, status code
|
13 |
+
* and response headers
|
14 |
+
*/
|
15 |
+
public function makeHttpRequest($url)
|
16 |
+
{
|
17 |
+
// PS: We suppress the E_WARNING level error generated on failure
|
18 |
+
$body = @file_get_contents($url);
|
19 |
+
if ($body === false) {
|
20 |
+
$body = '';
|
21 |
+
}
|
22 |
+
|
23 |
+
// $http_response_header materializes out of thin air when file_get_contents() is called
|
24 |
+
|
25 |
+
// Get status code
|
26 |
+
$statusLine = $http_response_header[0];
|
27 |
+
preg_match('{HTTP\/\S*\s(\d{3})}', $statusLine, $match);
|
28 |
+
$statusCode = $match[1];
|
29 |
+
|
30 |
+
// Create headers map
|
31 |
+
$headersMap = [];
|
32 |
+
foreach ($http_response_header as $header) {
|
33 |
+
$pos = strpos($header, ':');
|
34 |
+
if ($pos > 0) {
|
35 |
+
$fieldName = strtolower(trim(substr($header, 0, $pos)));
|
36 |
+
$value = trim(substr($header, $pos + 1));
|
37 |
+
if (!isset($headersMap[$fieldName])) {
|
38 |
+
$headersMap[$fieldName] = $value;
|
39 |
+
} else {
|
40 |
+
$headersMap[$fieldName] .= ', ' . $value;
|
41 |
+
}
|
42 |
+
}
|
43 |
+
}
|
44 |
+
return new HttpResponse($body, $statusCode, $headersMap);
|
45 |
+
}
|
46 |
+
}
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
class SimpleTestFileLineUpper implements TestFilesLineUpperInterface
|
6 |
+
{
|
7 |
+
|
8 |
+
private function writeFileIfMissingOrChanged($file)
|
9 |
+
{
|
10 |
+
$success = true;
|
11 |
+
list($filename, $content) = $file;
|
12 |
+
$dir = dirname($filename);
|
13 |
+
if (!is_dir($dir)) {
|
14 |
+
if (!mkdir($dir, 0777, true)) {
|
15 |
+
// TODO: Use custom exception
|
16 |
+
throw new \Exception('Failed creating dir: ' . $dir);
|
17 |
+
}
|
18 |
+
}
|
19 |
+
if (file_exists($filename)) {
|
20 |
+
// file already exists, now check if content is the same
|
21 |
+
$existingContent = file_get_contents($filename);
|
22 |
+
if (($existingContent === false) || ($content != $existingContent)) {
|
23 |
+
$success = file_put_contents($filename, $content);
|
24 |
+
}
|
25 |
+
} else {
|
26 |
+
$success = file_put_contents($filename, $content);
|
27 |
+
}
|
28 |
+
if (!$success) {
|
29 |
+
// TODO: Use custom exception
|
30 |
+
throw new \Exception('Failed creating file: ' . $filename);
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Write missing and changed files.
|
36 |
+
*
|
37 |
+
* @param array $files The files that needs to be there
|
38 |
+
*
|
39 |
+
* @return void
|
40 |
+
*/
|
41 |
+
private function writeMissingAndChangedFiles($files)
|
42 |
+
{
|
43 |
+
foreach ($files as $file) {
|
44 |
+
$this->writeFileIfMissingOrChanged($file);
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Remove unused files.
|
50 |
+
*
|
51 |
+
* @param array $files The files that needs to be there (others will be removed)
|
52 |
+
*
|
53 |
+
* @return void
|
54 |
+
*/
|
55 |
+
private function removeUnusedFiles($files)
|
56 |
+
{
|
57 |
+
$dirs = [];
|
58 |
+
foreach ($files as $file) {
|
59 |
+
list($filename, $content) = $file;
|
60 |
+
$dir = dirname($filename);
|
61 |
+
if (!isset($dirs[$dir])) {
|
62 |
+
$dirs[$dir] = [];
|
63 |
+
}
|
64 |
+
$dirs[$dir][] = basename($filename);
|
65 |
+
}
|
66 |
+
|
67 |
+
foreach ($dirs as $dir => $filesSupposedToBeInDir) {
|
68 |
+
$fileIterator = new \FilesystemIterator($dir);
|
69 |
+
while ($fileIterator->valid()) {
|
70 |
+
$filename = $fileIterator->getFilename();
|
71 |
+
if (!in_array($filename, $filesSupposedToBeInDir)) {
|
72 |
+
unlink($dir . '/' . $filename);
|
73 |
+
}
|
74 |
+
$fileIterator->next();
|
75 |
+
}
|
76 |
+
}
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Line-up test files.
|
81 |
+
*
|
82 |
+
* This method should make sure that the files passed in are there and are up-to-date.
|
83 |
+
* - If a file is missing, it should be created.
|
84 |
+
* - If a file has changed content, it should be updated
|
85 |
+
* - If the directory contains a file/dir that should not be there, it should be removed
|
86 |
+
*
|
87 |
+
* @param array $files The files that needs to be there
|
88 |
+
*
|
89 |
+
* @return void
|
90 |
+
*/
|
91 |
+
public function lineUp($files)
|
92 |
+
{
|
93 |
+
// 1. Put missing files / changed files
|
94 |
+
$this->writeMissingAndChangedFiles($files);
|
95 |
+
|
96 |
+
// 2. Remove unused files
|
97 |
+
$this->removeUnusedFiles($files);
|
98 |
+
}
|
99 |
+
}
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
interface TestFilesLineUpperInterface
|
6 |
+
{
|
7 |
+
/**
|
8 |
+
* Line-up test files.
|
9 |
+
*
|
10 |
+
* This method should make sure that the files passed in are there and are up-to-date.
|
11 |
+
* - If a file is missing, it should be created.
|
12 |
+
* - If a file has changed content, it should be updated
|
13 |
+
* - If the directory contains a file/dir that should not be there, it should be removed
|
14 |
+
*
|
15 |
+
* @param array $files The files that needs to be there
|
16 |
+
*
|
17 |
+
* @return void
|
18 |
+
*/
|
19 |
+
public function lineUp($files);
|
20 |
+
}
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for holding properties of a TestResult
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since the beginning
|
11 |
+
*/
|
12 |
+
class TestResult
|
13 |
+
{
|
14 |
+
|
15 |
+
/* @var bool|null The result, null if inconclusive */
|
16 |
+
public $status;
|
17 |
+
|
18 |
+
/* @var string Information about how the test failed / became inconclusive */
|
19 |
+
public $info;
|
20 |
+
|
21 |
+
/* @var string Status code of last request in the test */
|
22 |
+
public $statusCodeOfLastRequest;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Constructor.
|
26 |
+
*
|
27 |
+
* @param bool|null $status
|
28 |
+
* @param string $info
|
29 |
+
* @param string $statusCodeOfLastRequest (optional)
|
30 |
+
*
|
31 |
+
* @return void
|
32 |
+
*/
|
33 |
+
public function __construct($status, $info, $statusCodeOfLastRequest = null)
|
34 |
+
{
|
35 |
+
$this->status = $status;
|
36 |
+
$this->info = $info;
|
37 |
+
$this->statusCodeOfLastRequest = $statusCodeOfLastRequest;
|
38 |
+
}
|
39 |
+
}
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\Testers\AbstractTester;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class caching test results
|
9 |
+
*
|
10 |
+
* @package HtaccessCapabilityTester
|
11 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
12 |
+
* @since Class available since the beginning
|
13 |
+
*/
|
14 |
+
class TestResultCache
|
15 |
+
{
|
16 |
+
|
17 |
+
/* @var array Array for caching */
|
18 |
+
protected static $cache;
|
19 |
+
|
20 |
+
/**
|
21 |
+
*
|
22 |
+
* @param array $cacheKeys Two keys for caching (usually: basedir and the getCacheKey() for the Tester)
|
23 |
+
* @param TestResult $testResult The test result to cache
|
24 |
+
*
|
25 |
+
* @return void
|
26 |
+
*/
|
27 |
+
public static function cache($cacheKeys, $testResult)
|
28 |
+
{
|
29 |
+
if (!isset(self::$cache)) {
|
30 |
+
self::$cache = [];
|
31 |
+
}
|
32 |
+
list($key1, $key2) = $cacheKeys;
|
33 |
+
if (!isset(self::$cache[$key1])) {
|
34 |
+
self::$cache[$key1] = [];
|
35 |
+
}
|
36 |
+
self::$cache[$key1][$key2] = $testResult;
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Check if in cache.
|
41 |
+
*
|
42 |
+
* @param array $cacheKeys Keys for caching (usually: basedir and the getCacheKey() for the Tester)
|
43 |
+
*
|
44 |
+
* @return bool
|
45 |
+
*/
|
46 |
+
public static function isCached($cacheKeys)
|
47 |
+
{
|
48 |
+
if (!isset(self::$cache)) {
|
49 |
+
return false;
|
50 |
+
}
|
51 |
+
list($key1, $key2) = $cacheKeys;
|
52 |
+
if (!isset(self::$cache[$key1])) {
|
53 |
+
return false;
|
54 |
+
}
|
55 |
+
if (!isset(self::$cache[$key1][$key2])) {
|
56 |
+
return false;
|
57 |
+
}
|
58 |
+
return true;
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Get from cache.
|
63 |
+
*
|
64 |
+
* @param array $cacheKeys Keys for caching (usually: basedir and the getCacheKey() for the Tester)
|
65 |
+
*
|
66 |
+
* @return TestResult The test result
|
67 |
+
*/
|
68 |
+
public static function getCached($cacheKeys)
|
69 |
+
{
|
70 |
+
if (!self::isCached($cacheKeys)) {
|
71 |
+
throw new \Exception('Not in cache');
|
72 |
+
}
|
73 |
+
list($key1, $key2) = $cacheKeys;
|
74 |
+
return self::$cache[$key1][$key2];
|
75 |
+
}
|
76 |
+
|
77 |
+
public static function clear()
|
78 |
+
{
|
79 |
+
self::$cache = null;
|
80 |
+
}
|
81 |
+
}
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\HtaccessCapabilityTester;
|
6 |
+
use \HtaccessCapabilityTester\HttpRequesterInterface;
|
7 |
+
use \HtaccessCapabilityTester\HttpResponse;
|
8 |
+
use \HtaccessCapabilityTester\SimpleHttpRequester;
|
9 |
+
use \HtaccessCapabilityTester\SimpleTestFileLineUpper;
|
10 |
+
use \HtaccessCapabilityTester\TestFilesLineUpperInterface;
|
11 |
+
use \HtaccessCapabilityTester\TestResult;
|
12 |
+
|
13 |
+
abstract class AbstractTester
|
14 |
+
{
|
15 |
+
/** @var string The dir where the test files should be put */
|
16 |
+
protected $baseDir;
|
17 |
+
|
18 |
+
/** @var string The base url that the tests can be run from (corresponds to $baseDir) */
|
19 |
+
protected $baseUrl;
|
20 |
+
|
21 |
+
/** @var string Subdir to put .htaccess files in */
|
22 |
+
protected $subDir;
|
23 |
+
|
24 |
+
/** @var array Test files for the test */
|
25 |
+
protected $testFiles;
|
26 |
+
|
27 |
+
/** @var HttpRequesterInterface An object for making the HTTP request */
|
28 |
+
protected $httpRequester;
|
29 |
+
|
30 |
+
/** @var HttpResponse The response of the previous HTTP request (if any) */
|
31 |
+
public $lastHttpResponse;
|
32 |
+
|
33 |
+
/** @var TestFilesLineUpperInterface An object for lining up the test-files */
|
34 |
+
protected $testFilesLineUpper;
|
35 |
+
|
36 |
+
/** @var HtaccessCapabilityTester The HtaccessCapabilityTester to use for subtests */
|
37 |
+
private $hct;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Register the test files using the "registerTestFile" method
|
41 |
+
*
|
42 |
+
* @return void
|
43 |
+
*/
|
44 |
+
abstract protected function registerTestFiles();
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Child classes must implement this method, which tells which subdir the
|
48 |
+
* test files are to be put.
|
49 |
+
*
|
50 |
+
* @return string A subdir for the test files
|
51 |
+
*/
|
52 |
+
abstract protected function getSubDir();
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Get key for caching purposes.
|
56 |
+
*
|
57 |
+
* Return a unique key. The default is to use the subdir. However, if a concrete Tester class
|
58 |
+
* can test different things, it must override this method and make sure to return a different
|
59 |
+
* key per thing it can test
|
60 |
+
*
|
61 |
+
* @return string A key it can be cached under
|
62 |
+
*/
|
63 |
+
public function getCacheKey()
|
64 |
+
{
|
65 |
+
return $this->getSubDir();
|
66 |
+
}
|
67 |
+
|
68 |
+
public function getBaseDir()
|
69 |
+
{
|
70 |
+
return $this->baseDir;
|
71 |
+
}
|
72 |
+
|
73 |
+
public function getBaseUrl()
|
74 |
+
{
|
75 |
+
return $this->baseUrl;
|
76 |
+
}
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Child classes must that implement the registerTestFiles method must call
|
80 |
+
* this method to register each test file.
|
81 |
+
*
|
82 |
+
* @return void
|
83 |
+
*/
|
84 |
+
protected function registerTestFile($filename, $content)
|
85 |
+
{
|
86 |
+
$this->testFiles[] = [$this->baseDir . '/' . $filename, $content];
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Last moment preparations before running the test
|
91 |
+
*
|
92 |
+
* @param string $baseDir Directory on the server where the test files can be put
|
93 |
+
* @param string $baseUrl The base URL of the test files
|
94 |
+
*
|
95 |
+
* @throws \Exception In case the test cannot be prepared due to serious issues
|
96 |
+
*/
|
97 |
+
protected function prepareForRun($baseDir, $baseUrl)
|
98 |
+
{
|
99 |
+
$this->baseDir = $baseDir;
|
100 |
+
$this->baseUrl = $baseUrl;
|
101 |
+
$this->testFiles = [];
|
102 |
+
$this->registerTestFiles();
|
103 |
+
$this->lineUpTestFiles();
|
104 |
+
}
|
105 |
+
|
106 |
+
abstract public function run($baseDir, $baseUrl);
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Constructor.
|
110 |
+
*
|
111 |
+
* @return void
|
112 |
+
*/
|
113 |
+
public function __construct()
|
114 |
+
{
|
115 |
+
$this->subDir = $this->getSubDir();
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Make a HTTP request to a URL.
|
120 |
+
*
|
121 |
+
* @param string $url The URL to make the HTTP request to
|
122 |
+
*
|
123 |
+
* @return HttpResponse A HttpResponse object, which simply contains body and status code.
|
124 |
+
*/
|
125 |
+
protected function makeHttpRequest($url)
|
126 |
+
{
|
127 |
+
if (!isset($this->httpRequester)) {
|
128 |
+
$this->httpRequester = new SimpleHttpRequester();
|
129 |
+
}
|
130 |
+
$this->lastHttpResponse = $this->httpRequester->makeHttpRequest($url);
|
131 |
+
return $this->lastHttpResponse;
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* Set HTTP requester object, which handles making HTTP requests.
|
136 |
+
*
|
137 |
+
* @param HttpRequesterInterface $httpRequester The HTTPRequester to use
|
138 |
+
* @return void
|
139 |
+
*/
|
140 |
+
public function setHttpRequester($httpRequester)
|
141 |
+
{
|
142 |
+
$this->httpRequester = $httpRequester;
|
143 |
+
if (isset($this->hct)) {
|
144 |
+
$this->hct->setHttpRequester($this->httpRequester);
|
145 |
+
}
|
146 |
+
}
|
147 |
+
|
148 |
+
public function lineUpTestFiles()
|
149 |
+
{
|
150 |
+
if (!isset($this->testFilesLineUpper)) {
|
151 |
+
$this->testFilesLineUpper = new SimpleTestFileLineUpper();
|
152 |
+
}
|
153 |
+
$this->testFilesLineUpper->lineUp($this->testFiles);
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Set object responsible for lining up the test files.
|
158 |
+
*
|
159 |
+
* @param TestFilesLineUpperInterface $testFilesLineUpper
|
160 |
+
* @return void
|
161 |
+
*/
|
162 |
+
public function setTestFilesLineUpper($testFilesLineUpper)
|
163 |
+
{
|
164 |
+
$this->testFilesLineUpper = $testFilesLineUpper;
|
165 |
+
if (isset($this->hct)) {
|
166 |
+
$this->hct->setTestFilesLineUpper($this->testFilesLineUpper);
|
167 |
+
}
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Get HtaccessCapabilityTester.
|
172 |
+
*
|
173 |
+
* Some tests use HtaccessCapabilityTester to run other tests.
|
174 |
+
* This gets such object with baseDir and baseUrl set up
|
175 |
+
*
|
176 |
+
* @return HtaccessCapabilityTester
|
177 |
+
*/
|
178 |
+
public function getHtaccessCapabilityTester()
|
179 |
+
{
|
180 |
+
if (!isset($this->hct)) {
|
181 |
+
$this->hct = new HtaccessCapabilityTester($this->baseDir, $this->baseUrl);
|
182 |
+
if (isset($this->testFilesLineUpper)) {
|
183 |
+
$this->hct->setTestFilesLineUpper($this->testFilesLineUpper);
|
184 |
+
}
|
185 |
+
if (isset($this->httpRequester)) {
|
186 |
+
$this->hct->setHttpRequester($this->httpRequester);
|
187 |
+
}
|
188 |
+
}
|
189 |
+
return $this->hct;
|
190 |
+
}
|
191 |
+
}
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if AddType works
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since 0.7
|
11 |
+
*/
|
12 |
+
class AddTypeTester extends CustomTester
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Constructor.
|
17 |
+
*
|
18 |
+
* @return void
|
19 |
+
*/
|
20 |
+
public function __construct()
|
21 |
+
{
|
22 |
+
$htaccessFile = <<<'EOD'
|
23 |
+
<IfModule mod_mime.c>
|
24 |
+
AddType image/gif .test
|
25 |
+
</IfModule>
|
26 |
+
EOD;
|
27 |
+
|
28 |
+
$test = [
|
29 |
+
'subdir' => 'add-type',
|
30 |
+
'files' => [
|
31 |
+
['.htaccess', $htaccessFile],
|
32 |
+
['request-me.test', 'hi'],
|
33 |
+
],
|
34 |
+
'request' => 'request-me.test',
|
35 |
+
'interpretation' => [
|
36 |
+
['success', 'headers', 'contains-key-value', 'Content-Type', 'image/gif'],
|
37 |
+
['inconclusive', 'status-code', 'not-equals', '200'],
|
38 |
+
['failure', 'headers', 'not-contains-key-value', 'Content-Type', 'image/gif'],
|
39 |
+
]
|
40 |
+
];
|
41 |
+
|
42 |
+
parent::__construct($test);
|
43 |
+
}
|
44 |
+
}
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if setting ContentDigest works
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since 0.7
|
11 |
+
*/
|
12 |
+
class ContentDigestTester extends CustomTester
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Constructor.
|
17 |
+
*
|
18 |
+
* @return void
|
19 |
+
*/
|
20 |
+
public function __construct()
|
21 |
+
{
|
22 |
+
$test = [
|
23 |
+
'subdir' => 'content-digest',
|
24 |
+
'subtests' => [
|
25 |
+
[
|
26 |
+
'subdir' => 'on',
|
27 |
+
'files' => [
|
28 |
+
['.htaccess', 'ContentDigest On'],
|
29 |
+
['request-me.txt', 'hi'],
|
30 |
+
],
|
31 |
+
'request' => 'request-me.txt',
|
32 |
+
'interpretation' => [
|
33 |
+
['failure', 'headers', 'not-contains-key', 'Content-MD5'],
|
34 |
+
]
|
35 |
+
],
|
36 |
+
[
|
37 |
+
'subdir' => 'off',
|
38 |
+
'files' => [
|
39 |
+
['.htaccess', 'ContentDigest Off'],
|
40 |
+
['request-me.txt', "hi"],
|
41 |
+
],
|
42 |
+
'request' => 'request-me.txt',
|
43 |
+
'interpretation' => [
|
44 |
+
['failure', 'headers', 'contains-key', 'Content-MD5'],
|
45 |
+
['inconclusive', 'status-code', 'not-equals', '200'],
|
46 |
+
['success', 'status-code', 'equals', '200'],
|
47 |
+
]
|
48 |
+
]
|
49 |
+
]
|
50 |
+
];
|
51 |
+
|
52 |
+
parent::__construct($test);
|
53 |
+
}
|
54 |
+
}
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\TestResult;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class for testing if a .htaccess results in a 500 Internal Server Error
|
9 |
+
* (ie due to being malformed or containing directives that are unknown or not allowed)
|
10 |
+
*
|
11 |
+
* Notes:
|
12 |
+
* - The tester only reports failure on a 500 Internal Server Error. All other status codes (even server errors)
|
13 |
+
* are treated as a success. The assumption here is that malformed .htaccess files / .htaccess
|
14 |
+
* files containing unknown or disallowed directives always results in a 500
|
15 |
+
* - If your purpose is to test if a request succeeds (response 200 Ok), you should create your own class.
|
16 |
+
* (note that if you want to ensure that a php will succeed, make sure that a php is requested)
|
17 |
+
*
|
18 |
+
* @package HtaccessCapabilityTester
|
19 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
20 |
+
* @since Class available since 0.7
|
21 |
+
*/
|
22 |
+
class CrashTester extends CustomTester
|
23 |
+
{
|
24 |
+
|
25 |
+
/**
|
26 |
+
* @param string $htaccessRules The rules to check
|
27 |
+
* @param string $subSubDir subdir for the test files. If not supplied, a fingerprint of the rules will be used
|
28 |
+
*/
|
29 |
+
public function __construct($htaccessRules, $subSubDir = null)
|
30 |
+
{
|
31 |
+
if (is_null($subSubDir)) {
|
32 |
+
$subSubDir = hash('md5', $htaccessRules);
|
33 |
+
}
|
34 |
+
|
35 |
+
$test = [
|
36 |
+
'subdir' => 'crash-tester/' . $subSubDir,
|
37 |
+
'subtests' => [
|
38 |
+
[
|
39 |
+
'subdir' => 'the-suspect',
|
40 |
+
'files' => [
|
41 |
+
['.htaccess', $htaccessRules],
|
42 |
+
['request-me.txt', 'thanks'],
|
43 |
+
],
|
44 |
+
'request' => [
|
45 |
+
'url' => 'request-me.txt',
|
46 |
+
'bypass-standard-error-handling' => ['all']
|
47 |
+
],
|
48 |
+
'interpretation' => [
|
49 |
+
['success', 'status-code', 'not-equals', '500'],
|
50 |
+
// Otherwise fall through to next subtest
|
51 |
+
]
|
52 |
+
],
|
53 |
+
[
|
54 |
+
'subdir' => 'the-innocent',
|
55 |
+
'files' => [
|
56 |
+
['.htaccess', '# I am no trouble'],
|
57 |
+
['request-me.txt', 'thanks'],
|
58 |
+
],
|
59 |
+
'request' => [
|
60 |
+
'url' => 'request-me.txt',
|
61 |
+
'bypass-standard-error-handling' => ['all']
|
62 |
+
],
|
63 |
+
'interpretation' => [
|
64 |
+
// The suspect crashed. But if the innocent crashes too, we cannot judge
|
65 |
+
['inconclusive', 'status-code', 'equals', '500'],
|
66 |
+
|
67 |
+
// The innocent did not crash. The suspect is guilty!
|
68 |
+
['failure'],
|
69 |
+
]
|
70 |
+
],
|
71 |
+
]
|
72 |
+
];
|
73 |
+
|
74 |
+
parent::__construct($test);
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Child classes must implement this method, which tells which subdir the
|
79 |
+
* test files are to be put.
|
80 |
+
*
|
81 |
+
* @return string A subdir for the test files
|
82 |
+
*/
|
83 |
+
public function getSubDir()
|
84 |
+
{
|
85 |
+
return 'crash-tester';
|
86 |
+
}
|
87 |
+
}
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\HtaccessCapabilityTester;
|
6 |
+
use \HtaccessCapabilityTester\HttpRequesterInterface;
|
7 |
+
use \HtaccessCapabilityTester\HttpResponse;
|
8 |
+
use \HtaccessCapabilityTester\SimpleHttpRequester;
|
9 |
+
use \HtaccessCapabilityTester\TestResult;
|
10 |
+
use \HtaccessCapabilityTester\Testers\Helpers\ResponseInterpreter;
|
11 |
+
|
12 |
+
class CustomTester extends AbstractTester
|
13 |
+
{
|
14 |
+
/** @var array A definition defining the test */
|
15 |
+
protected $test;
|
16 |
+
|
17 |
+
/** @var array For convenience, all tests */
|
18 |
+
private $tests;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Constructor.
|
22 |
+
*
|
23 |
+
* @param array $test The test (may contain subtests)
|
24 |
+
*
|
25 |
+
* @return void
|
26 |
+
*/
|
27 |
+
public function __construct($test)
|
28 |
+
{
|
29 |
+
$this->test = $test;
|
30 |
+
|
31 |
+
if (isset($test['subtests'])) {
|
32 |
+
$this->tests = $test['subtests'];
|
33 |
+
|
34 |
+
// Add main subdir to subdir for all subtests
|
35 |
+
foreach ($this->tests as &$subtest) {
|
36 |
+
if (isset($subtest['subdir'])) {
|
37 |
+
$subtest['subdir'] = $test['subdir'] . '/' . $subtest['subdir'];
|
38 |
+
}
|
39 |
+
}
|
40 |
+
} else {
|
41 |
+
$this->tests = [$test];
|
42 |
+
}
|
43 |
+
|
44 |
+
//echo '<pre>' . print_r($this->tests, true) . '</pre>';
|
45 |
+
//echo json_encode($this->tests) . '<br>';
|
46 |
+
parent::__construct();
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Register the test files using the "registerTestFile" method
|
51 |
+
*
|
52 |
+
* @return void
|
53 |
+
*/
|
54 |
+
protected function registerTestFiles()
|
55 |
+
{
|
56 |
+
|
57 |
+
foreach ($this->tests as $test) {
|
58 |
+
if (isset($test['files'])) {
|
59 |
+
foreach ($test['files'] as $file) {
|
60 |
+
// Two syntaxes are allowed:
|
61 |
+
// - Simple array (ie: ['0.txt', '0']
|
62 |
+
// - Named, ie: ['filename' => '0.txt', 'content' => '0']
|
63 |
+
// The second makes more readable YAML definitions
|
64 |
+
if (isset($file['filename'])) {
|
65 |
+
$filename = $file['filename'];
|
66 |
+
$content = $file['content'];
|
67 |
+
} else {
|
68 |
+
list ($filename, $content) = $file;
|
69 |
+
}
|
70 |
+
$this->registerTestFile($test['subdir'] . '/' . $filename, $content);
|
71 |
+
}
|
72 |
+
}
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
public function getSubDir()
|
77 |
+
{
|
78 |
+
return $this->test['subdir'];
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Standard Error handling
|
83 |
+
*
|
84 |
+
* @param HttpResponse $response
|
85 |
+
*
|
86 |
+
* @return TestResult|null If no errors, null is returned, otherwise a TestResult
|
87 |
+
*/
|
88 |
+
private function standardErrorHandling($response)
|
89 |
+
{
|
90 |
+
switch ($response->statusCode) {
|
91 |
+
case '403':
|
92 |
+
return new TestResult(null, '403 Forbidden');
|
93 |
+
case '404':
|
94 |
+
return new TestResult(null, '404 Not Found');
|
95 |
+
case '500':
|
96 |
+
$hct = $this->getHtaccessCapabilityTester();
|
97 |
+
|
98 |
+
// Run innocent request / get it from cache. This sets
|
99 |
+
// $statusCodeOfLastRequest, which we need now
|
100 |
+
$hct->innocentRequestWorks();
|
101 |
+
if ($hct->statusCodeOfLastRequest == '500') {
|
102 |
+
return new TestResult(null, 'Errored with 500. Everything errors with 500.');
|
103 |
+
} else {
|
104 |
+
return new TestResult(
|
105 |
+
false,
|
106 |
+
'Errored with 500. ' .
|
107 |
+
'Not all goes 500, so it must be a forbidden directive in the .htaccess'
|
108 |
+
);
|
109 |
+
}
|
110 |
+
}
|
111 |
+
return null;
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Checks if standard error handling should be bypassed on the test.
|
116 |
+
*
|
117 |
+
* This stuff is controlled in the test definition. More precisely, by the "bypass-standard-error-handling"
|
118 |
+
* property bellow the "request" property. If this property is set to ie ['404', '500'], the standard error
|
119 |
+
* handler will be bypassed for those codes (but still be in effect for ie '403'). If set to ['all'], all
|
120 |
+
* standard error handling will be bypassed.
|
121 |
+
*
|
122 |
+
* @param array $test the subtest
|
123 |
+
* @param HttpResponse $response the response
|
124 |
+
*
|
125 |
+
* @return bool true if error handling should be bypassed
|
126 |
+
*/
|
127 |
+
private function bypassStandardErrorHandling($test, $response)
|
128 |
+
{
|
129 |
+
if (!(isset($test['request']['bypass-standard-error-handling']))) {
|
130 |
+
return false;
|
131 |
+
}
|
132 |
+
$bypassErrors = $test['request']['bypass-standard-error-handling'];
|
133 |
+
if (in_array($response->statusCode, $bypassErrors) || in_array('all', $bypassErrors)) {
|
134 |
+
return true;
|
135 |
+
}
|
136 |
+
return false;
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* Run single test
|
141 |
+
*
|
142 |
+
* @param array $test the subtest to run
|
143 |
+
*
|
144 |
+
* @return TestResult Returns a test result
|
145 |
+
*/
|
146 |
+
private function realRunSubTest($test)
|
147 |
+
{
|
148 |
+
$requestUrl = $this->baseUrl . '/' . $test['subdir'] . '/';
|
149 |
+
if (isset($test['request']['url'])) {
|
150 |
+
$requestUrl .= $test['request']['url'];
|
151 |
+
} else {
|
152 |
+
$requestUrl .= $test['request'];
|
153 |
+
}
|
154 |
+
//echo $requestUrl . '<br>';
|
155 |
+
$response = $this->makeHttpRequest($requestUrl);
|
156 |
+
|
157 |
+
// Standard error handling
|
158 |
+
if (!($this->bypassStandardErrorHandling($test, $response))) {
|
159 |
+
$errorResult = $this->standardErrorHandling($response);
|
160 |
+
if (!is_null($errorResult)) {
|
161 |
+
return $errorResult;
|
162 |
+
}
|
163 |
+
}
|
164 |
+
return ResponseInterpreter::interpret($response, $test['interpretation']);
|
165 |
+
}
|
166 |
+
|
167 |
+
/**
|
168 |
+
* Run
|
169 |
+
*
|
170 |
+
* @param string $baseDir Directory on the server where the test files can be put
|
171 |
+
* @param string $baseUrl The base URL of the test files
|
172 |
+
*
|
173 |
+
* @return TestResult Returns a test result
|
174 |
+
* @throws \Exception In case the test cannot be run due to serious issues
|
175 |
+
*/
|
176 |
+
private function realRun($baseDir, $baseUrl)
|
177 |
+
{
|
178 |
+
$this->prepareForRun($baseDir, $baseUrl);
|
179 |
+
|
180 |
+
$result = null;
|
181 |
+
foreach ($this->tests as $i => $test) {
|
182 |
+
/*
|
183 |
+
Disabled, as I'm no longer sure if it is that useful
|
184 |
+
|
185 |
+
if (isset($test['requirements'])) {
|
186 |
+
$hct = $this->getHtaccessCapabilityTester();
|
187 |
+
|
188 |
+
foreach ($test['requirements'] as $requirement) {
|
189 |
+
$requirementResult = $hct->callMethod($requirement);
|
190 |
+
if (!$requirementResult) {
|
191 |
+
// Skip test
|
192 |
+
continue 2;
|
193 |
+
}
|
194 |
+
}
|
195 |
+
}*/
|
196 |
+
if (isset($test['request'])) {
|
197 |
+
$result = $this->realRunSubTest($test);
|
198 |
+
if ($result->info != 'no-match') {
|
199 |
+
return $result;
|
200 |
+
}
|
201 |
+
}
|
202 |
+
}
|
203 |
+
if (is_null($result)) {
|
204 |
+
$result = new TestResult(null, 'Nothing to test!');
|
205 |
+
}
|
206 |
+
return $result;
|
207 |
+
}
|
208 |
+
|
209 |
+
/**
|
210 |
+
* Run
|
211 |
+
*
|
212 |
+
* @param string $baseDir Directory on the server where the test files can be put
|
213 |
+
* @param string $baseUrl The base URL of the test files
|
214 |
+
*
|
215 |
+
* @return TestResult Returns a test result
|
216 |
+
* @throws \Exception In case the test cannot be run due to serious issues
|
217 |
+
*/
|
218 |
+
public function run($baseDir, $baseUrl)
|
219 |
+
{
|
220 |
+
$testResult = $this->realRun($baseDir, $baseUrl);
|
221 |
+
|
222 |
+
// A test might not create a request if it has an unfulfilled requirement
|
223 |
+
if (isset($this->lastHttpResponse)) {
|
224 |
+
$testResult->statusCodeOfLastRequest = $this->lastHttpResponse->statusCode;
|
225 |
+
}
|
226 |
+
return $testResult;
|
227 |
+
}
|
228 |
+
}
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if DirectoryIndex works
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since 0.7
|
11 |
+
*/
|
12 |
+
class DirectoryIndexTester extends CustomTester
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Constructor.
|
17 |
+
*
|
18 |
+
* @return void
|
19 |
+
*/
|
20 |
+
public function __construct()
|
21 |
+
{
|
22 |
+
$htaccessFile = <<<'EOD'
|
23 |
+
<IfModule mod_dir.c>
|
24 |
+
DirectoryIndex index2.html
|
25 |
+
</IfModule>
|
26 |
+
EOD;
|
27 |
+
|
28 |
+
$test = [
|
29 |
+
'subdir' => 'directory-index',
|
30 |
+
'files' => [
|
31 |
+
['.htaccess', $htaccessFile],
|
32 |
+
['index.html', "0"],
|
33 |
+
['index2.html', "1"]
|
34 |
+
],
|
35 |
+
'request' => [
|
36 |
+
'url' => '', // We request the index, that is why its empty
|
37 |
+
'bypass-standard-error-handling' => ['404']
|
38 |
+
],
|
39 |
+
'interpretation' => [
|
40 |
+
['success', 'body', 'equals', '1'],
|
41 |
+
['failure', 'body', 'equals', '0'],
|
42 |
+
['failure', 'status-code', 'equals', '404'], // "index.html" might not be set to index
|
43 |
+
]
|
44 |
+
];
|
45 |
+
|
46 |
+
parent::__construct($test);
|
47 |
+
}
|
48 |
+
}
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if Header works
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since 0.7
|
11 |
+
*/
|
12 |
+
class HeaderSetTester extends CustomTester
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Constructor.
|
17 |
+
*
|
18 |
+
* @return void
|
19 |
+
*/
|
20 |
+
public function __construct()
|
21 |
+
{
|
22 |
+
$htaccessFile = <<<'EOD'
|
23 |
+
<IfModule mod_headers.c>
|
24 |
+
Header set X-Response-Header-Test: test
|
25 |
+
</IfModule>
|
26 |
+
EOD;
|
27 |
+
|
28 |
+
$test = [
|
29 |
+
'subdir' => 'header-set',
|
30 |
+
'files' => [
|
31 |
+
['.htaccess', $htaccessFile],
|
32 |
+
['request-me.txt', "hi"],
|
33 |
+
],
|
34 |
+
'request' => 'request-me.txt',
|
35 |
+
'interpretation' => [
|
36 |
+
['success', 'headers', 'contains-key-value', 'X-Response-Header-Test', 'test'],
|
37 |
+
['failure'],
|
38 |
+
]
|
39 |
+
];
|
40 |
+
|
41 |
+
parent::__construct($test);
|
42 |
+
}
|
43 |
+
}
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers\Helpers;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\HttpResponse;
|
6 |
+
use \HtaccessCapabilityTester\TestResult;
|
7 |
+
use \HtaccessCapabilityTester\Testers\AbstractTester;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class for interpreting responses using a defined interpretation table.
|
11 |
+
*
|
12 |
+
* @package HtaccessCapabilityTester
|
13 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
14 |
+
* @since Class available since 0.7
|
15 |
+
*/
|
16 |
+
class ResponseInterpreter
|
17 |
+
{
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Parse status string (failure | success | inconclusive) to bool|null.
|
21 |
+
*
|
22 |
+
* @param string $statusString (failure | success | inconclusive)
|
23 |
+
* @return bool|null
|
24 |
+
*/
|
25 |
+
private static function parseStatusString($statusString)
|
26 |
+
{
|
27 |
+
$status = null;
|
28 |
+
switch ($statusString) {
|
29 |
+
case 'failure':
|
30 |
+
$status = false;
|
31 |
+
break;
|
32 |
+
case 'inconclusive':
|
33 |
+
$status = null;
|
34 |
+
break;
|
35 |
+
case 'success':
|
36 |
+
$status = true;
|
37 |
+
break;
|
38 |
+
}
|
39 |
+
return $status;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Interpret headers line
|
44 |
+
*
|
45 |
+
* @param HttpResponse $response
|
46 |
+
* @param string $operator (has-key | )
|
47 |
+
* @param string $fieldName field name of the header
|
48 |
+
* @param string $fieldValue (optional) field value to look for. Only required when
|
49 |
+
* operator is "contains-key-value" or "not-contains-key-value"
|
50 |
+
* @return bool true if the condition matches, false otherwise
|
51 |
+
*/
|
52 |
+
private static function evaluateHeadersLine($response, $operator, $fieldName, $fieldValue)
|
53 |
+
{
|
54 |
+
switch ($operator) {
|
55 |
+
case 'contains-key':
|
56 |
+
return $response->hasHeader($fieldName);
|
57 |
+
case 'not-contains-key':
|
58 |
+
return (!($response->hasHeader($fieldName)));
|
59 |
+
case 'contains-key-value':
|
60 |
+
return $response->hasHeaderValue($fieldName, $fieldValue);
|
61 |
+
case 'not-contains-key-value':
|
62 |
+
return (!($response->hasHeaderValue($fieldName, $fieldValue)));
|
63 |
+
}
|
64 |
+
return false;
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Interpret string line (body or status-code)
|
69 |
+
*
|
70 |
+
* @param HttpResponse $response
|
71 |
+
* @param string $property ("body" or "status-code")
|
72 |
+
* @param string $operator (is-empty | equals | not-equals | begins-with)
|
73 |
+
* @param string $arg1 (only required for some operators)
|
74 |
+
*
|
75 |
+
* @return bool true if the condition matches, false otherwise
|
76 |
+
*/
|
77 |
+
private static function evaluateStringLine($response, $property, $operator, $arg1)
|
78 |
+
{
|
79 |
+
$val = '';
|
80 |
+
switch ($property) {
|
81 |
+
case 'status-code':
|
82 |
+
$val = $response->statusCode;
|
83 |
+
break;
|
84 |
+
case 'body':
|
85 |
+
$val = $response->body;
|
86 |
+
break;
|
87 |
+
}
|
88 |
+
|
89 |
+
switch ($operator) {
|
90 |
+
case 'is-empty':
|
91 |
+
return ($val == '');
|
92 |
+
case 'equals':
|
93 |
+
return ($val == $arg1);
|
94 |
+
case 'not-equals':
|
95 |
+
return ($val != $arg1);
|
96 |
+
case 'begins-with':
|
97 |
+
return (strpos($val, $arg1) === 0);
|
98 |
+
}
|
99 |
+
return false;
|
100 |
+
|
101 |
+
}
|
102 |
+
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Interpret line.
|
106 |
+
*
|
107 |
+
* @param HttpResponse $response
|
108 |
+
* @param array $line
|
109 |
+
*
|
110 |
+
* @return TestResult|null If the condition matches, a TestResult is returned, otherwise null
|
111 |
+
*/
|
112 |
+
private static function interpretLine($response, $line)
|
113 |
+
{
|
114 |
+
// ie:
|
115 |
+
// ['inconclusive', 'body', 'is-empty'],
|
116 |
+
// ['failure', 'statusCode', 'equals', '500']
|
117 |
+
// ['success', 'headers', 'contains-key-value', 'X-Response-Header-Test', 'test'],
|
118 |
+
|
119 |
+
$status = self::parseStatusString($line[0]);
|
120 |
+
|
121 |
+
if (!isset($line[1])) {
|
122 |
+
return new TestResult($status, '');
|
123 |
+
}
|
124 |
+
|
125 |
+
$propertyToExamine = $line[1];
|
126 |
+
$operator = $line[2];
|
127 |
+
$arg1 = (isset($line[3]) ? $line[3] : '');
|
128 |
+
$arg2 = (isset($line[4]) ? $line[4] : '');
|
129 |
+
|
130 |
+
if ($propertyToExamine == 'headers') {
|
131 |
+
$match = self::evaluateHeadersLine($response, $operator, $arg1, $arg2);
|
132 |
+
} else {
|
133 |
+
$match = self::evaluateStringLine($response, $propertyToExamine, $operator, $arg1);
|
134 |
+
}
|
135 |
+
if ($match) {
|
136 |
+
$reason = $propertyToExamine . ' ' . $operator;
|
137 |
+
if (isset($line[3])) {
|
138 |
+
$reason .= ' "' . implode('" "', array_slice($line, 3)) . '"';
|
139 |
+
}
|
140 |
+
/*
|
141 |
+
if (($propertyToExamine == 'status-code') && ($operator == 'not-equals') && (gettype($val) == 'string')) {
|
142 |
+
$reason .= ' - it was: ' . $val;
|
143 |
+
}*/
|
144 |
+
return new TestResult($status, $reason);
|
145 |
+
}
|
146 |
+
|
147 |
+
return null;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Interpret a response using an interpretation table.
|
152 |
+
*
|
153 |
+
* @param HttpResponse $response
|
154 |
+
* @param array $interpretationTable
|
155 |
+
*
|
156 |
+
* @return TestResult If there is no match, the test result will have status = false and
|
157 |
+
* info = "no-match".
|
158 |
+
*/
|
159 |
+
public static function interpret($response, $interpretationTable)
|
160 |
+
{
|
161 |
+
foreach ($interpretationTable as $i => $line) {
|
162 |
+
$testResult = self::interpretLine($response, $line);
|
163 |
+
if (!is_null($testResult)) {
|
164 |
+
return $testResult;
|
165 |
+
}
|
166 |
+
}
|
167 |
+
return new TestResult(null, 'no-match');
|
168 |
+
}
|
169 |
+
}
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\HtaccessCapabilityTester;
|
6 |
+
use \HtaccessCapabilityTester\TestResult;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class for testing if .htaccess files are processed
|
10 |
+
*
|
11 |
+
* @package HtaccessCapabilityTester
|
12 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
13 |
+
* @since Class available since 0.7
|
14 |
+
*/
|
15 |
+
class HtaccessEnabledTester extends AbstractTester
|
16 |
+
{
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Child classes must implement this method, which tells which subdir the
|
20 |
+
* test files are to be put.
|
21 |
+
*
|
22 |
+
* @return string A subdir for the test files
|
23 |
+
*/
|
24 |
+
public function getSubDir()
|
25 |
+
{
|
26 |
+
return 'htaccess-enabled';
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Register the test files using the "registerTestFile" method
|
31 |
+
*
|
32 |
+
* @return void
|
33 |
+
*/
|
34 |
+
public function registerTestFiles()
|
35 |
+
{
|
36 |
+
// No test files for this test
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Run the test.
|
41 |
+
*
|
42 |
+
* @param string $baseDir Directory on the server where the test files can be put
|
43 |
+
* @param string $baseUrl The base URL of the test files
|
44 |
+
*
|
45 |
+
* @return TestResult Returns a test result
|
46 |
+
*/
|
47 |
+
public function run($baseDir, $baseUrl)
|
48 |
+
{
|
49 |
+
$this->prepareForRun($baseDir, $baseUrl);
|
50 |
+
|
51 |
+
/*
|
52 |
+
PS: We could implement this as a definition:
|
53 |
+
|
54 |
+
|
55 |
+
- [success, serverSignatureWorks, is-success]
|
56 |
+
- [success, contentDigestWorks, is-success]
|
57 |
+
- [failure, serverSignatureWorks, is-failure]
|
58 |
+
- [success, canCrash, is-success]
|
59 |
+
*/
|
60 |
+
|
61 |
+
|
62 |
+
$status = null;
|
63 |
+
$info = '';
|
64 |
+
$hct = $this->getHtaccessCapabilityTester();
|
65 |
+
|
66 |
+
// If we can find anything that works, well the .htaccess must have been proccesed!
|
67 |
+
if ($hct->serverSignatureWorks() // Override: None, Status: Core, REQUIRES PHP
|
68 |
+
|| $hct->contentDigestWorks() // Override: Options, Status: Core
|
69 |
+
|| $hct->addTypeWorks() // Override: FileInfo, Status: Base, Module: mime
|
70 |
+
|| $hct->directoryIndexWorks() // Override: Indexes, Status: Base, Module: mod_dir
|
71 |
+
|| $hct->rewriteWorks() // Override: FileInfo, Status: Extension, Module: rewrite
|
72 |
+
|| $hct->headerSetWorks() // Override: FileInfo, Status: Extension, Module: headers
|
73 |
+
) {
|
74 |
+
$status = true;
|
75 |
+
} else {
|
76 |
+
// The serverSignatureWorks() test is special because if it comes out as a failure,
|
77 |
+
// we can be *almost* certain that the .htaccess has been completely disabled
|
78 |
+
|
79 |
+
$serverSignatureWorks = $hct->serverSignatureWorks();
|
80 |
+
if ($serverSignatureWorks === false) {
|
81 |
+
$status = false;
|
82 |
+
$info = 'ServerSignature directive does not work - and it is in core';
|
83 |
+
} else {
|
84 |
+
// Last bullet in the gun:
|
85 |
+
// Try an .htaccess with syntax errors in it.
|
86 |
+
// (we do this lastly because it may generate an entry in the error log)
|
87 |
+
$crashTestResult = $hct->crashTest('aoeu', 'htaccess-enabled-malformed-htaccess');
|
88 |
+
if (is_null($crashTestResult)) {
|
89 |
+
// It did crash. But so did a request to an innocent text file in a directory
|
90 |
+
// without a .htaccess file in it. Something is making all requests fail and
|
91 |
+
// we cannot judge.
|
92 |
+
$status = null;
|
93 |
+
$info = 'all requests results in 500 Internal Server Error';
|
94 |
+
} elseif ($crashTestResult === false) {
|
95 |
+
// It crashed, - which means .htaccess is processed!
|
96 |
+
$status = true;
|
97 |
+
$info = 'syntax error in an .htaccess causes crash';
|
98 |
+
} else {
|
99 |
+
// It did not crash. So the .htaccess is not processed, as syntax errors
|
100 |
+
// makes servers crash
|
101 |
+
$status = false;
|
102 |
+
$info = 'syntax error in an .htaccess does not cause crash';
|
103 |
+
}
|
104 |
+
}
|
105 |
+
}
|
106 |
+
return new TestResult($status, $info);
|
107 |
+
}
|
108 |
+
}
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\TestResult;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class for testing if an innocent request for a txt file succeeds
|
9 |
+
*
|
10 |
+
* @package HtaccessCapabilityTester
|
11 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
12 |
+
* @since Class available since 0.7
|
13 |
+
*/
|
14 |
+
class InnocentRequestTester extends CustomTester
|
15 |
+
{
|
16 |
+
|
17 |
+
public function __construct()
|
18 |
+
{
|
19 |
+
$test = [
|
20 |
+
'subdir' => 'innocent-request',
|
21 |
+
'files' => [
|
22 |
+
['request-me.txt', 'thank you my dear'],
|
23 |
+
],
|
24 |
+
'request' => [
|
25 |
+
'url' => 'request-me.txt',
|
26 |
+
'bypass-standard-error-handling' => ['all']
|
27 |
+
],
|
28 |
+
'interpretation' => [
|
29 |
+
['success', 'status-code', 'equals', '200'],
|
30 |
+
['inconclusive', 'status-code', 'equals', '403'],
|
31 |
+
['inconclusive', 'status-code', 'equals', '404'],
|
32 |
+
['failure'],
|
33 |
+
]
|
34 |
+
];
|
35 |
+
|
36 |
+
parent::__construct($test);
|
37 |
+
}
|
38 |
+
}
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
use \HtaccessCapabilityTester\TestResult;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class for testing if a module is loaded.
|
9 |
+
*
|
10 |
+
* @package HtaccessCapabilityTester
|
11 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
12 |
+
* @since Class available since 0.7
|
13 |
+
*/
|
14 |
+
class ModuleLoadedTester extends AbstractTester
|
15 |
+
{
|
16 |
+
|
17 |
+
/* @var string A valid Apache module name (ie "rewrite") */
|
18 |
+
protected $moduleName;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Constructor.
|
22 |
+
*
|
23 |
+
* @return void
|
24 |
+
*/
|
25 |
+
public function __construct($moduleName)
|
26 |
+
{
|
27 |
+
$this->moduleName = $moduleName;
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Child classes must implement this method, which tells which subdir the
|
32 |
+
* test files are to be put.
|
33 |
+
*
|
34 |
+
* @return string A subdir for the test files
|
35 |
+
*/
|
36 |
+
public function getSubDir()
|
37 |
+
{
|
38 |
+
return 'module-loaded/' . $this->moduleName;
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Register the test files using the "registerTestFile" method
|
43 |
+
*
|
44 |
+
* @return void
|
45 |
+
*/
|
46 |
+
public function registerTestFiles()
|
47 |
+
{
|
48 |
+
// No test files for this test
|
49 |
+
}
|
50 |
+
|
51 |
+
private function getServerSignatureBasedTest()
|
52 |
+
{
|
53 |
+
// Test files, method : Using ServerSignature
|
54 |
+
// --------------------------------------------------
|
55 |
+
// Requires (in order not to be inconclusive):
|
56 |
+
// - Override: All
|
57 |
+
// - Status: Core
|
58 |
+
// - Directives: ServerSignature, IfModule
|
59 |
+
// - PHP?: Yes
|
60 |
+
|
61 |
+
$php = <<<'EOD'
|
62 |
+
<?php
|
63 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
64 |
+
echo 1;
|
65 |
+
} else {
|
66 |
+
echo 0;
|
67 |
+
}
|
68 |
+
EOD;
|
69 |
+
|
70 |
+
$htaccess = <<<'EOD'
|
71 |
+
# The beauty of this trick is that ServerSignature is available in core.
|
72 |
+
# (it requires no modules and cannot easily be made forbidden)
|
73 |
+
# However, it requires PHP to check for the effect
|
74 |
+
|
75 |
+
ServerSignature Off
|
76 |
+
<IfModule mod_xxx.c>
|
77 |
+
ServerSignature On
|
78 |
+
</IfModule>
|
79 |
+
EOD;
|
80 |
+
|
81 |
+
$htaccess = str_replace('mod_xxx', 'mod_' . $this->moduleName, $htaccess);
|
82 |
+
|
83 |
+
return [
|
84 |
+
'subdir' => $this->getSubDir() . '/server-signature',
|
85 |
+
'files' => [
|
86 |
+
['.htaccess', $htaccess],
|
87 |
+
['test.php', $php],
|
88 |
+
],
|
89 |
+
'request' => 'test.php',
|
90 |
+
'interpretation' => [
|
91 |
+
['success', 'body', 'equals', '1'],
|
92 |
+
['failure', 'body', 'equals', '0'],
|
93 |
+
// This time we do not fail for 500 because it is very unlikely that any of the
|
94 |
+
// directives used are forbidden
|
95 |
+
]
|
96 |
+
];
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* @return array
|
101 |
+
*/
|
102 |
+
private function getRewriteBasedTest()
|
103 |
+
{
|
104 |
+
// Test files, method: Using Rewrite
|
105 |
+
// --------------------------------------------------
|
106 |
+
// Requires (in order not to be inconclusive)
|
107 |
+
// - Module: mod_rewrite
|
108 |
+
// - Override: FileInfo
|
109 |
+
// - Directives: RewriteEngine, RewriteRule and IfModule
|
110 |
+
// - PHP?: No
|
111 |
+
|
112 |
+
$htaccess = <<<'EOD'
|
113 |
+
RewriteEngine On
|
114 |
+
<IfModule mod_xxx.c>
|
115 |
+
RewriteRule ^request-me\.txt$ 1.txt [L]
|
116 |
+
</IfModule>
|
117 |
+
<IfModule !mod_xxx.c>
|
118 |
+
RewriteRule ^request-me\.txt$ 0.txt [L]
|
119 |
+
</IfModule>
|
120 |
+
EOD;
|
121 |
+
|
122 |
+
$htaccess = str_replace('mod_xxx', 'mod_' . $this->moduleName, $htaccess);
|
123 |
+
|
124 |
+
return [
|
125 |
+
'subdir' => $this->getSubDir() . '/rewrite',
|
126 |
+
'files' => [
|
127 |
+
['.htaccess', $htaccess],
|
128 |
+
['0.txt', '0'],
|
129 |
+
['1.txt', '1'],
|
130 |
+
['request-me.txt', 'Redirect failed even though rewriting has been proven to work. Strange!'],
|
131 |
+
],
|
132 |
+
'request' => 'request-me.txt',
|
133 |
+
'interpretation' => [
|
134 |
+
['success', 'body', 'equals', '1'],
|
135 |
+
['failure', 'body', 'equals', '0'],
|
136 |
+
//['inconclusive', 'status-code', 'not-equals', '200'],
|
137 |
+
]
|
138 |
+
];
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* @return array
|
143 |
+
*/
|
144 |
+
private function getHeaderSetBasedTest()
|
145 |
+
{
|
146 |
+
|
147 |
+
// Test files, method: Using Response Header
|
148 |
+
// --------------------------------------------------
|
149 |
+
// Requires (in order not to be inconclusive)
|
150 |
+
// - Module: mod_headers
|
151 |
+
// - Override: FileInfo
|
152 |
+
// - Directives: Header and IfModule
|
153 |
+
// - PHP?: No
|
154 |
+
|
155 |
+
$htaccess = <<<'EOD'
|
156 |
+
<IfModule mod_xxx.c>
|
157 |
+
Header set X-Response-Header-Test: 1
|
158 |
+
</IfModule>
|
159 |
+
<IfModule !mod_xxx.c>
|
160 |
+
Header set X-Response-Header-Test: 0
|
161 |
+
</IfModule>
|
162 |
+
EOD;
|
163 |
+
|
164 |
+
$htaccess = str_replace('mod_xxx', 'mod_' . $this->moduleName, $htaccess);
|
165 |
+
|
166 |
+
return [
|
167 |
+
'subdir' => $this->getSubDir() . '/header-set',
|
168 |
+
'files' => [
|
169 |
+
['.htaccess', $htaccess],
|
170 |
+
['request-me.txt', 'thanks'],
|
171 |
+
],
|
172 |
+
'request' => 'request-me.txt',
|
173 |
+
'interpretation' => [
|
174 |
+
['success', 'headers', 'contains-key-value', 'X-Response-Header-Test', '1'],
|
175 |
+
['failure', 'headers', 'contains-key-value', 'X-Response-Header-Test', '0'],
|
176 |
+
]
|
177 |
+
];
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* @return array
|
182 |
+
*/
|
183 |
+
private function getContentDigestBasedTest()
|
184 |
+
{
|
185 |
+
// Test files, method: Using ContentDigest
|
186 |
+
// --------------------------------------------------
|
187 |
+
//
|
188 |
+
// Requires (in order not to be inconclusive)
|
189 |
+
// - Module: None - its in core
|
190 |
+
// - Override: Options
|
191 |
+
// - Directives: ContentDigest
|
192 |
+
// - PHP?: No
|
193 |
+
|
194 |
+
$htaccess = <<<'EOD'
|
195 |
+
<IfModule mod_xxx.c>
|
196 |
+
ContentDigest On
|
197 |
+
</IfModule>
|
198 |
+
<IfModule !mod_xxx.c>
|
199 |
+
ContentDigest Off
|
200 |
+
</IfModule>
|
201 |
+
EOD;
|
202 |
+
|
203 |
+
$htaccess = str_replace('mod_xxx', 'mod_' . $this->moduleName, $htaccess);
|
204 |
+
|
205 |
+
return [
|
206 |
+
'subdir' => $this->getSubDir() . '/content-digest',
|
207 |
+
'files' => [
|
208 |
+
['.htaccess', $htaccess],
|
209 |
+
['request-me.txt', 'thanks'],
|
210 |
+
],
|
211 |
+
'request' => 'request-me.txt',
|
212 |
+
'interpretation' => [
|
213 |
+
['success', 'headers', 'contains-key', 'Content-MD5'],
|
214 |
+
['failure', 'headers', 'not-contains-key', 'Content-MD5'],
|
215 |
+
]
|
216 |
+
];
|
217 |
+
}
|
218 |
+
|
219 |
+
/**
|
220 |
+
* @return array
|
221 |
+
*/
|
222 |
+
private function getDirectoryIndexBasedTest()
|
223 |
+
{
|
224 |
+
// Test files, method: Using DirectoryIndex
|
225 |
+
// --------------------------------------------------
|
226 |
+
//
|
227 |
+
// Requires (in order not to be inconclusive)
|
228 |
+
// - Module: mod_dir (Status: Base)
|
229 |
+
// - Override: Indexes
|
230 |
+
// - Directives: DirectoryIndex
|
231 |
+
// - PHP?: No
|
232 |
+
|
233 |
+
$htaccess = <<<'EOD'
|
234 |
+
<IfModule mod_xxx.c>
|
235 |
+
DirectoryIndex 1.html
|
236 |
+
</IfModule>
|
237 |
+
<IfModule !mod_xxx.c>
|
238 |
+
DirectoryIndex 0.html
|
239 |
+
</IfModule>
|
240 |
+
EOD;
|
241 |
+
|
242 |
+
$htaccess = str_replace('mod_xxx', 'mod_' . $this->moduleName, $htaccess);
|
243 |
+
|
244 |
+
return [
|
245 |
+
'subdir' => $this->getSubDir() . '/directory-index',
|
246 |
+
'files' => [
|
247 |
+
['.htaccess', $htaccess],
|
248 |
+
['0.html', '0'],
|
249 |
+
['1.html', '1'],
|
250 |
+
],
|
251 |
+
'request' => '', // empty - in order to request the index
|
252 |
+
'interpretation' => [
|
253 |
+
['success', 'body', 'equals', '1'],
|
254 |
+
['failure', 'body', 'equals', '0'],
|
255 |
+
]
|
256 |
+
];
|
257 |
+
}
|
258 |
+
|
259 |
+
|
260 |
+
/**
|
261 |
+
* @return array
|
262 |
+
*/
|
263 |
+
private function getAddTypeBasedTest()
|
264 |
+
{
|
265 |
+
// Test files, method: Using AddType
|
266 |
+
// --------------------------------------------------
|
267 |
+
//
|
268 |
+
// Requires (in order not to be inconclusive)
|
269 |
+
// - Module: mod_mime
|
270 |
+
// - Override: FileInfo
|
271 |
+
// - Directives: AddType and IfModule
|
272 |
+
// - PHP?: No
|
273 |
+
|
274 |
+
$htaccess = <<<'EOD'
|
275 |
+
<IfModule mod_xxx.c>
|
276 |
+
AddType image/gif .test
|
277 |
+
</IfModule>
|
278 |
+
<IfModule !mod_xxx.c>
|
279 |
+
AddType image/jpeg .test
|
280 |
+
</IfModule>
|
281 |
+
EOD;
|
282 |
+
|
283 |
+
$htaccess = str_replace('mod_xxx', 'mod_' . $this->moduleName, $htaccess);
|
284 |
+
|
285 |
+
return [
|
286 |
+
'subdir' => $this->getSubDir() . '/add-type',
|
287 |
+
'files' => [
|
288 |
+
['.htaccess', $htaccess],
|
289 |
+
['request-me.test', 'hi'],
|
290 |
+
],
|
291 |
+
'request' => 'request-me.test',
|
292 |
+
'interpretation' => [
|
293 |
+
['success', 'headers', 'contains-key-value', 'Content-Type', 'image/gif'],
|
294 |
+
['failure', 'headers', 'contains-key-value', 'Content-Type', 'image/jpeg'],
|
295 |
+
]
|
296 |
+
];
|
297 |
+
}
|
298 |
+
|
299 |
+
/**
|
300 |
+
* @return bool|null
|
301 |
+
*/
|
302 |
+
private function run2()
|
303 |
+
{
|
304 |
+
$hct = $this->getHtaccessCapabilityTester();
|
305 |
+
|
306 |
+
$testResult = $hct->customTest($this->getServerSignatureBasedTest());
|
307 |
+
if (!is_null($testResult)) {
|
308 |
+
// PHP
|
309 |
+
return $testResult;
|
310 |
+
}
|
311 |
+
|
312 |
+
if ($hct->contentDigestWorks()) {
|
313 |
+
// Override: Options
|
314 |
+
return $hct->customTest($this->getContentDigestBasedTest());
|
315 |
+
}
|
316 |
+
|
317 |
+
if ($hct->addTypeWorks()) {
|
318 |
+
// Override: FileInfo, Status: Base (mod_mime)
|
319 |
+
return $hct->customTest($this->getAddTypeBasedTest());
|
320 |
+
}
|
321 |
+
|
322 |
+
if ($hct->directoryIndexWorks()) {
|
323 |
+
// Override: Indexes, Status: Base (mod_dir)
|
324 |
+
return $hct->customTest($this->getDirectoryIndexBasedTest());
|
325 |
+
}
|
326 |
+
|
327 |
+
if ($hct->rewriteWorks()) {
|
328 |
+
// Override: FileInfo, Module: mod_rewrite
|
329 |
+
return $hct->customTest($this->getRewriteBasedTest());
|
330 |
+
}
|
331 |
+
|
332 |
+
if ($hct->headerSetWorks()) {
|
333 |
+
//Override: FileInfo, Module: mod_headers
|
334 |
+
return $hct->customTest($this->getHeaderSetBasedTest());
|
335 |
+
}
|
336 |
+
return null;
|
337 |
+
}
|
338 |
+
|
339 |
+
/**
|
340 |
+
* Run the test.
|
341 |
+
*
|
342 |
+
* @param string $baseDir Directory on the server where the test files can be put
|
343 |
+
* @param string $baseUrl The base URL of the test files
|
344 |
+
*
|
345 |
+
* @return TestResult Returns a test result
|
346 |
+
*/
|
347 |
+
public function run($baseDir, $baseUrl)
|
348 |
+
{
|
349 |
+
$this->prepareForRun($baseDir, $baseUrl);
|
350 |
+
|
351 |
+
$hct = $this->getHtaccessCapabilityTester();
|
352 |
+
|
353 |
+
$htaccessEnabledTest = $hct->htaccessEnabled();
|
354 |
+
if ($htaccessEnabledTest === false) {
|
355 |
+
return new TestResult(false, '.htaccess files are ignored');
|
356 |
+
} elseif (is_null($htaccessEnabledTest)) {
|
357 |
+
// We happen to know that if that test cannot establish anything,
|
358 |
+
// then none of the usual weapons works - we can surrender
|
359 |
+
return new TestResult(null, 'no methods available - we surrender early');
|
360 |
+
}
|
361 |
+
|
362 |
+
$status = $this->run2();
|
363 |
+
if (is_null($status)) {
|
364 |
+
return new TestResult(null, 'no methods worked');
|
365 |
+
} else {
|
366 |
+
return new TestResult($status, '');
|
367 |
+
}
|
368 |
+
}
|
369 |
+
}
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if an environment variable can be set in a rewrite rule and received in PHP.
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since 0.7
|
11 |
+
*/
|
12 |
+
class PassInfoFromRewriteToScriptThroughEnvTester extends CustomTester
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Constructor.
|
17 |
+
*
|
18 |
+
* @return void
|
19 |
+
*/
|
20 |
+
public function __construct()
|
21 |
+
{
|
22 |
+
$htaccessFile = <<<'EOD'
|
23 |
+
<IfModule mod_rewrite.c>
|
24 |
+
|
25 |
+
# Testing if we can pass environment variable from .htaccess to script in a RewriteRule
|
26 |
+
# We pass document root, because that can easily be checked by the script
|
27 |
+
|
28 |
+
RewriteEngine On
|
29 |
+
RewriteRule ^test\.php$ - [E=PASSTHROUGHENV:%{DOCUMENT_ROOT},L]
|
30 |
+
|
31 |
+
</IfModule>
|
32 |
+
EOD;
|
33 |
+
|
34 |
+
$phpFile = <<<'EOD'
|
35 |
+
<?php
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Get environment variable set with mod_rewrite module
|
39 |
+
* Return false if the environment variable isn't found
|
40 |
+
*/
|
41 |
+
function getEnvPassedInRewriteRule($envName) {
|
42 |
+
// Environment variables passed through the REWRITE module have "REWRITE_" as a prefix
|
43 |
+
// (in Apache, not Litespeed, if I recall correctly).
|
44 |
+
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
45 |
+
// We simply look for an environment variable that ends with what we are looking for.
|
46 |
+
// (so make sure to make it unique)
|
47 |
+
$len = strlen($envName);
|
48 |
+
foreach ($_SERVER as $key => $item) {
|
49 |
+
if (substr($key, -$len) == $envName) {
|
50 |
+
return $item;
|
51 |
+
}
|
52 |
+
}
|
53 |
+
return false;
|
54 |
+
}
|
55 |
+
|
56 |
+
$result = getEnvPassedInRewriteRule('PASSTHROUGHENV');
|
57 |
+
if ($result === false) {
|
58 |
+
echo '0';
|
59 |
+
exit;
|
60 |
+
}
|
61 |
+
echo ($result == $_SERVER['DOCUMENT_ROOT'] ? '1' : '0');
|
62 |
+
EOD;
|
63 |
+
|
64 |
+
$test = [
|
65 |
+
'subdir' => 'pass-info-from-rewrite-to-script-through-env',
|
66 |
+
'files' => [
|
67 |
+
['.htaccess', $htaccessFile],
|
68 |
+
['test.php', $phpFile],
|
69 |
+
],
|
70 |
+
'request' => 'test.php',
|
71 |
+
'interpretation' => [
|
72 |
+
['success', 'body', 'equals', '1'],
|
73 |
+
['failure', 'body', 'equals', '0'],
|
74 |
+
['inconclusive', 'body', 'begins-with', '<' . '?php'],
|
75 |
+
['inconclusive']
|
76 |
+
]
|
77 |
+
];
|
78 |
+
|
79 |
+
parent::__construct($test);
|
80 |
+
}
|
81 |
+
}
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Say you have a rewrite rule that points to a PHP script and you would like to pass some information
|
7 |
+
* along to the PHP. Usually, you will just pass it in the query string. But this won't do if the information
|
8 |
+
* is sensitive. In that case, there are some tricks available. The trick being tested here sets tells the
|
9 |
+
* RewriteRule directive to set an environment variable which a RequestHeader directive picks up on and passes
|
10 |
+
* on to the script in a request header.
|
11 |
+
*
|
12 |
+
* @package HtaccessCapabilityTester
|
13 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
14 |
+
* @since Class available since 0.7
|
15 |
+
*/
|
16 |
+
class PassInfoFromRewriteToScriptThroughRequestHeaderTester extends CustomTester
|
17 |
+
{
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Constructor.
|
21 |
+
*
|
22 |
+
* @return void
|
23 |
+
*/
|
24 |
+
public function __construct()
|
25 |
+
{
|
26 |
+
$htaccessFile = <<<'EOD'
|
27 |
+
<IfModule mod_rewrite.c>
|
28 |
+
RewriteEngine On
|
29 |
+
# We pass document root, because that can easily be checked by the script
|
30 |
+
RewriteRule ^test\.php$ - [E=PASSTHROUGHHEADER:%{DOCUMENT_ROOT},L]
|
31 |
+
|
32 |
+
<IfModule mod_headers.c>
|
33 |
+
RequestHeader set PASSTHROUGHHEADER "%{PASSTHROUGHHEADER}e" env=PASSTHROUGHHEADER
|
34 |
+
</IfModule>
|
35 |
+
|
36 |
+
</IfModule>
|
37 |
+
EOD;
|
38 |
+
|
39 |
+
$phpFile = <<<'EOD'
|
40 |
+
<?php
|
41 |
+
if (isset($_SERVER['HTTP_PASSTHROUGHHEADER'])) {
|
42 |
+
echo ($_SERVER['HTTP_PASSTHROUGHHEADER'] == $_SERVER['DOCUMENT_ROOT'] ? 1 : 0);
|
43 |
+
exit;
|
44 |
+
}
|
45 |
+
echo '0';
|
46 |
+
EOD;
|
47 |
+
|
48 |
+
$test = [
|
49 |
+
'subdir' => 'pass-info-from-rewrite-to-script-through-request-header',
|
50 |
+
'files' => [
|
51 |
+
['.htaccess', $htaccessFile],
|
52 |
+
['test.php', $phpFile],
|
53 |
+
],
|
54 |
+
'request' => 'test.php',
|
55 |
+
'interpretation' => [
|
56 |
+
['success', 'body', 'equals', '1'],
|
57 |
+
['failure', 'body', 'equals', '0'],
|
58 |
+
['inconclusive', 'body', 'begins-with', '<' . '?php'],
|
59 |
+
['inconclusive']
|
60 |
+
]
|
61 |
+
];
|
62 |
+
|
63 |
+
parent::__construct($test);
|
64 |
+
}
|
65 |
+
}
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if RequestHeader works
|
7 |
+
*
|
8 |
+
* @package HtaccessCapabilityTester
|
9 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
10 |
+
* @since Class available since 0.7
|
11 |
+
*/
|
12 |
+
class RequestHeaderTester extends CustomTester
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Constructor.
|
17 |
+
*
|
18 |
+
* @return void
|
19 |
+
*/
|
20 |
+
public function __construct()
|
21 |
+
{
|
22 |
+
$htaccessFile = <<<'EOD'
|
23 |
+
<IfModule mod_headers.c>
|
24 |
+
# Certain hosts seem to strip non-standard request headers,
|
25 |
+
# so we use a standard one to avoid a false negative
|
26 |
+
RequestHeader set User-Agent "request-header-test"
|
27 |
+
</IfModule>
|
28 |
+
EOD;
|
29 |
+
|
30 |
+
$phpFile = <<<'EOD'
|
31 |
+
<?php
|
32 |
+
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
33 |
+
echo (($_SERVER['HTTP_USER_AGENT'] == 'request-header-test') ? "1" : "0");
|
34 |
+
} else {
|
35 |
+
echo "0";
|
36 |
+
}
|
37 |
+
EOD;
|
38 |
+
|
39 |
+
// PS:
|
40 |
+
// There is a little edge case: When .htaccess is disabled AND phps are either not processed
|
41 |
+
// or access is denied. This ought to return *failure*, but it currently returns *inconclusive*.
|
42 |
+
|
43 |
+
$test = [
|
44 |
+
'subdir' => 'request-header',
|
45 |
+
'files' => [
|
46 |
+
['.htaccess', $htaccessFile],
|
47 |
+
['test.php', $phpFile],
|
48 |
+
],
|
49 |
+
'request' => 'test.php',
|
50 |
+
'interpretation' => [
|
51 |
+
['success', 'body', 'equals', '1'],
|
52 |
+
['failure', 'body', 'equals', '0'],
|
53 |
+
['inconclusive', 'body', 'begins-with', '<' . '?php'],
|
54 |
+
]
|
55 |
+
];
|
56 |
+
|
57 |
+
parent::__construct($test);
|
58 |
+
}
|
59 |
+
}
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if rewriting works at the tested location.
|
7 |
+
*
|
8 |
+
* The tester reports success when:
|
9 |
+
* - a rewrite is proven to be working
|
10 |
+
*
|
11 |
+
* The tester reports failure when:
|
12 |
+
* - Server does not have mod_rewrite installed
|
13 |
+
* - Server is set up to ignore .htaccess files in the directory
|
14 |
+
* - Server disallows any the following directives in the directory: RewriteEngine, Rewrite, IfModule
|
15 |
+
* (if disallowed, the result is either a 500 Internal Server Error or that the directive is
|
16 |
+
* ignored, depending on whether Nonfatal is set)
|
17 |
+
* - The request results in a 500 Internal Server Error due to another problem than a disallowed
|
18 |
+
* directive (this is, there is a risk for a false negative)
|
19 |
+
*
|
20 |
+
* The test works by creating an .htaccess which redirects requests to "0.txt"
|
21 |
+
* to "1.txt" and then requesting "0.txt".
|
22 |
+
*
|
23 |
+
* Notes:
|
24 |
+
* - The test might result in the following being written to the error log:
|
25 |
+
* "RewriteEngine not allowed here"
|
26 |
+
* - We are not redirecting to a php, because that would additionally require phps
|
27 |
+
* to be run in that directory
|
28 |
+
* - We are wrapping the .htaccess directives in a "<IfModule mod_rewrite.c>" and therefore this test
|
29 |
+
* also relies on the IfModule directive being allowed. It probably usually is, as it is harmless.
|
30 |
+
* Also, it is good practice to use it, so in most cases it is good that this is checked
|
31 |
+
* too. Actually, the <IfModule> wrap isn't neccessary for our test to work, as the test
|
32 |
+
* identifies a 500 Internal Error as test failure. However, not having the wrap would
|
33 |
+
* cause the test to generate an entry in the error log when mod_rewrite isn't installed
|
34 |
+
* (regardless if overrides are configured to Nonfatal or not):
|
35 |
+
* "Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included
|
36 |
+
* in the server configuration"
|
37 |
+
*
|
38 |
+
* @package HtaccessCapabilityTester
|
39 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
40 |
+
* @since Class available since 0.7
|
41 |
+
*/
|
42 |
+
class RewriteTester extends CustomTester
|
43 |
+
{
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Constructor.
|
47 |
+
*
|
48 |
+
* @return void
|
49 |
+
*/
|
50 |
+
public function __construct()
|
51 |
+
{
|
52 |
+
$htaccessFile = <<<'EOD'
|
53 |
+
# Testing for mod_rewrite
|
54 |
+
# -----------------------
|
55 |
+
# If mod_rewrite is enabled, redirect to 1.txt, which returns "1".
|
56 |
+
# If mod_rewrite is disabled, the rewriting fails, and we end at 0.txt, which returns "0".
|
57 |
+
#
|
58 |
+
# Notes:
|
59 |
+
# - We are not redirecting to a php, because that would additionally require phps
|
60 |
+
# to be run in that directory
|
61 |
+
# - We are wrapping it in a "<IfModule mod_rewrite.c>" and therefore this test also relies
|
62 |
+
# on the IfModule directive being allowed. It probably usually is, as it is harmless.
|
63 |
+
# Also, it is good practice to use it, so in most cases it is good that this is checked
|
64 |
+
# too. Actually, the <IfModule> wrap isn't neccessary for our test to work, as the test
|
65 |
+
# identifies a 500 Internal Error as test failure. However, not having the wrap would
|
66 |
+
# cause the test to generate an entry in the error log when mod_rewrite isn't installed
|
67 |
+
# (regardless if configured to Nonfatal or not): "Invalid command 'RewriteEngine', perhaps
|
68 |
+
# misspelled or defined by a module not included
|
69 |
+
# in the server configuration"
|
70 |
+
|
71 |
+
<IfModule mod_rewrite.c>
|
72 |
+
RewriteEngine On
|
73 |
+
RewriteRule ^0\.txt$ 1\.txt [L]
|
74 |
+
</IfModule>
|
75 |
+
EOD;
|
76 |
+
|
77 |
+
$test = [
|
78 |
+
'subdir' => 'rewrite',
|
79 |
+
'files' => [
|
80 |
+
['.htaccess', $htaccessFile],
|
81 |
+
['0.txt', "0"],
|
82 |
+
['1.txt', "1"]
|
83 |
+
],
|
84 |
+
'request' => '0.txt',
|
85 |
+
'interpretation' => [
|
86 |
+
['success', 'body', 'equals', '1'],
|
87 |
+
['failure', 'body', 'equals', '0'],
|
88 |
+
]
|
89 |
+
];
|
90 |
+
|
91 |
+
parent::__construct($test);
|
92 |
+
}
|
93 |
+
}
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Testers;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class for testing if ServerSignature works
|
7 |
+
*
|
8 |
+
* Testing the ServerSignature directive is of interest because the directive is a core feature.
|
9 |
+
* If a core feature doesn't work, well, it it would seem that .htaccess files are disabled completely.
|
10 |
+
* The test is thus special. If it returns *failure* it is highly probable that the .htaccess file has
|
11 |
+
* not been read.
|
12 |
+
*
|
13 |
+
* Unfortunately, the test requires PHP to examine if a server variable has been set. So the test is not
|
14 |
+
* unlikely to come out inconclusive due to a 403 Forbidden.
|
15 |
+
*
|
16 |
+
* Note that the test assumes that the ServerSignature directive has not been disallowed even though
|
17 |
+
* it is technically possible to do so by setting *AllowOverride* to *None* and by setting *AllowOverrideList*
|
18 |
+
* to a list that does not include *ServerSignature*.
|
19 |
+
*
|
20 |
+
* @package HtaccessCapabilityTester
|
21 |
+
* @author Bjørn Rosell <it@rosell.dk>
|
22 |
+
* @since Class available since 0.7
|
23 |
+
*/
|
24 |
+
class ServerSignatureTester extends CustomTester
|
25 |
+
{
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Constructor.
|
29 |
+
*
|
30 |
+
* @return void
|
31 |
+
*/
|
32 |
+
public function __construct()
|
33 |
+
{
|
34 |
+
$phpOn = <<<'EOD'
|
35 |
+
<?php
|
36 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
37 |
+
echo 1;
|
38 |
+
} else {
|
39 |
+
echo 0;
|
40 |
+
}
|
41 |
+
EOD;
|
42 |
+
|
43 |
+
$phpOff = <<<'EOD'
|
44 |
+
<?php
|
45 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
46 |
+
echo 0;
|
47 |
+
} else {
|
48 |
+
echo 1;
|
49 |
+
}
|
50 |
+
EOD;
|
51 |
+
|
52 |
+
// PS:
|
53 |
+
// There is a little edge case: When .htaccess is disabled AND phps are either not processed
|
54 |
+
// or access is denied. This ought to return *failure*, but it currently returns *inconclusive*.
|
55 |
+
|
56 |
+
$test = [
|
57 |
+
'subdir' => 'server-signature',
|
58 |
+
'subtests' => [
|
59 |
+
[
|
60 |
+
'subdir' => 'on',
|
61 |
+
'files' => [
|
62 |
+
['.htaccess', 'ServerSignature On'],
|
63 |
+
['test.php', $phpOn],
|
64 |
+
],
|
65 |
+
'request' => [
|
66 |
+
'url' => 'test.php',
|
67 |
+
],
|
68 |
+
'interpretation' => [
|
69 |
+
['inconclusive', 'body', 'isEmpty'],
|
70 |
+
['inconclusive', 'status-code', 'not-equals', '200'],
|
71 |
+
['failure', 'body', 'equals', '0'],
|
72 |
+
],
|
73 |
+
],
|
74 |
+
[
|
75 |
+
'subdir' => 'off',
|
76 |
+
'files' => [
|
77 |
+
['.htaccess', 'ServerSignature Off'],
|
78 |
+
['test.php', $phpOff],
|
79 |
+
],
|
80 |
+
'request' => 'test.php',
|
81 |
+
'interpretation' => [
|
82 |
+
['inconclusive', 'body', 'isEmpty'],
|
83 |
+
['success', 'body', 'equals', '1'],
|
84 |
+
['failure', 'body', 'equals', '0'],
|
85 |
+
['inconclusive']
|
86 |
+
]
|
87 |
+
]
|
88 |
+
]
|
89 |
+
];
|
90 |
+
|
91 |
+
parent::__construct($test);
|
92 |
+
}
|
93 |
+
}
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Tests;
|
4 |
+
|
5 |
+
use HtaccessCapabilityTester\HttpResponse;
|
6 |
+
use HtaccessCapabilityTester\HttpRequesterInterface;
|
7 |
+
use HtaccessCapabilityTester\TestFilesLineUpperInterface;
|
8 |
+
use HtaccessCapabilityTester\TestResult;
|
9 |
+
use HtaccessCapabilityTester\TestResultCache;
|
10 |
+
use HtaccessCapabilityTester\Testers\AbstractTester;
|
11 |
+
|
12 |
+
class FakeServer implements TestFilesLineUpperInterface, HttpRequesterInterface
|
13 |
+
{
|
14 |
+
|
15 |
+
/** @var array Files on the server */
|
16 |
+
private $files;
|
17 |
+
|
18 |
+
/** @var array Files as a map, by filename */
|
19 |
+
private $filesMap;
|
20 |
+
|
21 |
+
/** @var bool If .htaccess processing is disabled */
|
22 |
+
private $htaccessDisabled = false;
|
23 |
+
|
24 |
+
/** @var bool If all directives should be disallowed (but .htaccess still read) */
|
25 |
+
private $disallowAllDirectives = false;
|
26 |
+
|
27 |
+
/** @var bool If server should go fatal about forbidden directives */
|
28 |
+
private $fatal = false;
|
29 |
+
|
30 |
+
/** @var bool If all requests should crash! (500) */
|
31 |
+
private $crashAll = false;
|
32 |
+
|
33 |
+
/** @var bool If access is denied for all requests */
|
34 |
+
private $accessAllDenied = false;
|
35 |
+
|
36 |
+
/** @var bool Returns the php text file rather than "Sorry, this server cannot process PHP!" */
|
37 |
+
private $handlePHPasText = false;
|
38 |
+
|
39 |
+
/** @var array Predefined responses for certain urls */
|
40 |
+
private $responses;
|
41 |
+
|
42 |
+
|
43 |
+
public function lineUp($files)
|
44 |
+
{
|
45 |
+
$this->files = $files;
|
46 |
+
$this->filesMap = [];
|
47 |
+
foreach ($files as $file) {
|
48 |
+
list($filename, $content) = $file;
|
49 |
+
$this->filesMap[$filename] = $content;
|
50 |
+
}
|
51 |
+
//$m = new SetRequestHeaderTester();
|
52 |
+
//$m->putFiles('');
|
53 |
+
//print_r($files);
|
54 |
+
}
|
55 |
+
|
56 |
+
public function makeHttpRequest($url)
|
57 |
+
{
|
58 |
+
$body = '';
|
59 |
+
$statusCode = '200';
|
60 |
+
$headers = [];
|
61 |
+
|
62 |
+
//echo 'Fakeserver request:' . $url . "\n";
|
63 |
+
if (isset($this->responses[$url])) {
|
64 |
+
//echo 'predefined: ' . $url . "\n";
|
65 |
+
return $this->responses[$url];
|
66 |
+
}
|
67 |
+
|
68 |
+
if ($this->crashAll) {
|
69 |
+
return new HttpResponse('', '500', []);
|
70 |
+
}
|
71 |
+
|
72 |
+
if (($this->disallowAllDirectives) && ($this->fatal)) {
|
73 |
+
|
74 |
+
$urlToHtaccessInSameFolder = dirname($url) . '/.htaccess';
|
75 |
+
$doesFolderContainHtaccess = isset($this->filesMap[$urlToHtaccessInSameFolder]);
|
76 |
+
|
77 |
+
if ($doesFolderContainHtaccess) {
|
78 |
+
return new HttpResponse('', '500', []);
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
if ($this->accessAllDenied) {
|
83 |
+
// TODO: what body?
|
84 |
+
return new HttpResponse('', '403', []);
|
85 |
+
}
|
86 |
+
|
87 |
+
|
88 |
+
//$simplyServeRequested = ($this->htaccessDisabled || ($this->disallowAllDirectives && (!$this->fatal)));
|
89 |
+
|
90 |
+
// Simply return the file that was requested
|
91 |
+
if (isset($this->filesMap[$url])) {
|
92 |
+
|
93 |
+
$isPhpFile = (strrpos($url, '.php') == strlen($url) - 4);
|
94 |
+
if ($isPhpFile && ($this->handlePHPasText)) {
|
95 |
+
return new HttpResponse('Sorry, this server cannot process PHP!', '200', []); ;
|
96 |
+
} else {
|
97 |
+
return new HttpResponse($this->filesMap[$url], '200', []); ;
|
98 |
+
}
|
99 |
+
} else {
|
100 |
+
return new HttpResponse('Not found', '404', []);
|
101 |
+
}
|
102 |
+
|
103 |
+
//return new HttpResponse('Not found', '404', []);
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Disallows all directives, but do still process .htaccess.
|
108 |
+
*
|
109 |
+
* In essence: Fail, if the folder contains an .htaccess file
|
110 |
+
*
|
111 |
+
* @param string $fatal fatal|nonfatal
|
112 |
+
*/
|
113 |
+
public function disallowAllDirectives($fatal)
|
114 |
+
{
|
115 |
+
$this->disallowAllDirectives = true;
|
116 |
+
$this->fatal = ($fatal = 'fatal');
|
117 |
+
}
|
118 |
+
|
119 |
+
public function disableHtaccess()
|
120 |
+
{
|
121 |
+
$this->htaccessDisabled = true;
|
122 |
+
}
|
123 |
+
|
124 |
+
public function denyAllAccess()
|
125 |
+
{
|
126 |
+
$this->accessAllDenied = true;
|
127 |
+
}
|
128 |
+
|
129 |
+
public function makeAllCrash()
|
130 |
+
{
|
131 |
+
$this->crashAll = true;
|
132 |
+
}
|
133 |
+
|
134 |
+
|
135 |
+
public function handlePHPasText()
|
136 |
+
{
|
137 |
+
$this->handlePHPasText = true;
|
138 |
+
}
|
139 |
+
|
140 |
+
// TODO: denyAccessToPHP
|
141 |
+
|
142 |
+
/**
|
143 |
+
* @param array $responses
|
144 |
+
*/
|
145 |
+
public function setResponses($responses)
|
146 |
+
{
|
147 |
+
$this->responses = $responses;
|
148 |
+
}
|
149 |
+
|
150 |
+
public function connectHCT($hct)
|
151 |
+
{
|
152 |
+
TestResultCache::clear();
|
153 |
+
$hct->setTestFilesLineUpper($this);
|
154 |
+
$hct->setHttpRequester($this);
|
155 |
+
}
|
156 |
+
|
157 |
+
|
158 |
+
/**
|
159 |
+
* @param AbstractTester $tester
|
160 |
+
* @return TestResult
|
161 |
+
*/
|
162 |
+
public function runTester($tester)
|
163 |
+
{
|
164 |
+
TestResultCache::clear();
|
165 |
+
$tester->setTestFilesLineUpper($this);
|
166 |
+
$tester->setHttpRequester($this);
|
167 |
+
|
168 |
+
return $tester->run('', '');
|
169 |
+
}
|
170 |
+
}
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Tests;
|
4 |
+
use HtaccessCapabilityTester\HtaccessCapabilityTester;
|
5 |
+
|
6 |
+
class Helper
|
7 |
+
{
|
8 |
+
|
9 |
+
public static function getTesterUsingFakeServer($fakeServer)
|
10 |
+
{
|
11 |
+
$hct = new HtaccessCapabilityTester('', '');
|
12 |
+
$hct->setTestFilesLineUpper($fakeServer);
|
13 |
+
$hct->setHttpRequester($fakeServer);
|
14 |
+
return $hct;
|
15 |
+
}
|
16 |
+
|
17 |
+
|
18 |
+
}
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: rewrite
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_rewrite.c>
|
8 |
+
RewriteEngine On
|
9 |
+
RewriteRule ^0\.txt$ 1\.txt [L]
|
10 |
+
</IfModule>
|
11 |
+
- filename: '0.txt'
|
12 |
+
content: '0'
|
13 |
+
- filename: '1.txt'
|
14 |
+
content: '1'
|
15 |
+
|
16 |
+
request:
|
17 |
+
url: '0.txt'
|
18 |
+
|
19 |
+
interpretation:
|
20 |
+
- [success, body, equals, '1']
|
21 |
+
- [failure, body, equals, '0']
|
22 |
+
|
23 |
+
|
24 |
+
----
|
25 |
+
|
26 |
+
Tested:
|
27 |
+
|
28 |
+
Server setup | Test result
|
29 |
+
--------------------------------------------------
|
30 |
+
.htaccess disabled | failure
|
31 |
+
forbidden directives (fatal) | failure
|
32 |
+
access denied | inconclusive (it might be allowed to other files)
|
33 |
+
directive has no effect | failure
|
34 |
+
| success
|
35 |
+
*/
|
36 |
+
|
37 |
+
|
38 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
39 |
+
|
40 |
+
use HtaccessCapabilityTester\HtaccessCapabilityTester;
|
41 |
+
use HtaccessCapabilityTester\HttpResponse;
|
42 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
43 |
+
use PHPUnit\Framework\TestCase;
|
44 |
+
|
45 |
+
class HtaccessCapabilityTesterTest extends BasisTestCase
|
46 |
+
{
|
47 |
+
|
48 |
+
public function testHeaderSetWorksSuccess()
|
49 |
+
{
|
50 |
+
$hct = new HtaccessCapabilityTester('', '');
|
51 |
+
|
52 |
+
$fakeServer = new FakeServer();
|
53 |
+
$fakeServer->setResponses([
|
54 |
+
'/header-set/request-me.txt' => new HttpResponse(
|
55 |
+
'hi',
|
56 |
+
'200',
|
57 |
+
['X-Response-Header-Test' => 'test']
|
58 |
+
)
|
59 |
+
]);
|
60 |
+
$fakeServer->connectHCT($hct);
|
61 |
+
$this->assertTrue($hct->headerSetWorks());
|
62 |
+
}
|
63 |
+
|
64 |
+
public function testRequestHeaderWorksSuccess()
|
65 |
+
{
|
66 |
+
$hct = new HtaccessCapabilityTester('', '');
|
67 |
+
|
68 |
+
$fakeServer = new FakeServer();
|
69 |
+
$fakeServer->setResponses([
|
70 |
+
'/request-header/test.php' => new HttpResponse('1', '200', [])
|
71 |
+
]);
|
72 |
+
$fakeServer->connectHCT($hct);
|
73 |
+
$this->assertTrue($hct->requestHeaderWorks());
|
74 |
+
}
|
75 |
+
|
76 |
+
public function testRequestHeaderWorksFailure1()
|
77 |
+
{
|
78 |
+
$hct = new HtaccessCapabilityTester('', '');
|
79 |
+
|
80 |
+
$fakeServer = new FakeServer();
|
81 |
+
$fakeServer->setResponses([
|
82 |
+
'/request-header/test.php' => new HttpResponse('0', '200', [])
|
83 |
+
]);
|
84 |
+
$fakeServer->connectHCT($hct);
|
85 |
+
$this->assertFalse($hct->requestHeaderWorks());
|
86 |
+
}
|
87 |
+
|
88 |
+
public function testPassingThroughRequestHeaderSuccess()
|
89 |
+
{
|
90 |
+
$hct = new HtaccessCapabilityTester('', '');
|
91 |
+
|
92 |
+
$fakeServer = new FakeServer();
|
93 |
+
$fakeServer->setResponses([
|
94 |
+
'/pass-info-from-rewrite-to-script-through-request-header/test.php' =>
|
95 |
+
new HttpResponse('1', '200', [])
|
96 |
+
]);
|
97 |
+
$fakeServer->connectHCT($hct);
|
98 |
+
$this->assertTrue($hct->passingInfoFromRewriteToScriptThroughRequestHeaderWorks());
|
99 |
+
}
|
100 |
+
|
101 |
+
public function testPassingThroughEnvSuccess()
|
102 |
+
{
|
103 |
+
$hct = new HtaccessCapabilityTester('', '');
|
104 |
+
|
105 |
+
$fakeServer = new FakeServer();
|
106 |
+
$fakeServer->setResponses([
|
107 |
+
'/pass-info-from-rewrite-to-script-through-env/test.php' =>
|
108 |
+
new HttpResponse('1', '200', [])
|
109 |
+
]);
|
110 |
+
$fakeServer->connectHCT($hct);
|
111 |
+
$this->assertTrue($hct->passingInfoFromRewriteToScriptThroughEnvWorks());
|
112 |
+
}
|
113 |
+
|
114 |
+
public function testModuleLoadedWhenNotLoaded()
|
115 |
+
{
|
116 |
+
$hct = new HtaccessCapabilityTester('', '');
|
117 |
+
|
118 |
+
$fakeServer = new FakeServer();
|
119 |
+
$fakeServer->setResponses([
|
120 |
+
'/rewrite/0.txt' => new HttpResponse('1', '200', []),
|
121 |
+
'/module-loaded/setenvif/rewrite/request-me.txt' => new HttpResponse('0', '200', []),
|
122 |
+
]);
|
123 |
+
$fakeServer->connectHCT($hct);
|
124 |
+
$this->assertFalse($hct->moduleLoaded('setenvif'));
|
125 |
+
}
|
126 |
+
|
127 |
+
public function testModuleLoadedWhenLoaded()
|
128 |
+
{
|
129 |
+
$hct = new HtaccessCapabilityTester('', '');
|
130 |
+
|
131 |
+
$fakeServer = new FakeServer();
|
132 |
+
$fakeServer->setResponses([
|
133 |
+
'/rewrite/0.txt' => new HttpResponse('1', '200', []),
|
134 |
+
'/module-loaded/setenvif/rewrite/request-me.txt' => new HttpResponse('1', '200', []),
|
135 |
+
]);
|
136 |
+
$fakeServer->connectHCT($hct);
|
137 |
+
$this->assertTrue($hct->moduleLoaded('setenvif'));
|
138 |
+
}
|
139 |
+
|
140 |
+
|
141 |
+
//
|
142 |
+
}
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
4 |
+
|
5 |
+
use HtaccessCapabilityTester\HttpResponse;
|
6 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
7 |
+
use PHPUnit\Framework\TestCase;
|
8 |
+
|
9 |
+
class HttpResponseTest extends TestCase
|
10 |
+
{
|
11 |
+
|
12 |
+
public function test1()
|
13 |
+
{
|
14 |
+
$r = new HttpResponse('hi', '200', [
|
15 |
+
'x-test' => 'test'
|
16 |
+
]);
|
17 |
+
$this->assertTrue($r->hasHeader('x-test'));
|
18 |
+
$this->assertTrue($r->hasHeader('X-Test'));
|
19 |
+
$this->assertTrue($r->hasHeaderValue('X-Test', 'test'));
|
20 |
+
}
|
21 |
+
|
22 |
+
public function test2()
|
23 |
+
{
|
24 |
+
$r = new HttpResponse('hi', '200', [
|
25 |
+
'x-test1' => 'value1, value2',
|
26 |
+
'x-test2' => 'value1,value2'
|
27 |
+
]);
|
28 |
+
$this->assertTrue($r->hasHeaderValue('X-Test1', 'value2'));
|
29 |
+
$this->assertTrue($r->hasHeaderValue('X-Test2', 'value2'));
|
30 |
+
}
|
31 |
+
|
32 |
+
public function test3()
|
33 |
+
{
|
34 |
+
$r = new HttpResponse('hi', '200', [
|
35 |
+
'content-md5' => 'aaoeu'
|
36 |
+
]);
|
37 |
+
$this->assertTrue($r->hasHeader('Content-MD5'));
|
38 |
+
$this->assertTrue($r->hasHeader('content-md5'));
|
39 |
+
}
|
40 |
+
|
41 |
+
public function test4()
|
42 |
+
{
|
43 |
+
$r = new HttpResponse('hi', '200', [
|
44 |
+
'Content-MD5' => 'aaoeu'
|
45 |
+
]);
|
46 |
+
$this->assertTrue($r->hasHeader('Content-MD5'));
|
47 |
+
$this->assertTrue($r->hasHeader('content-md5'));
|
48 |
+
}
|
49 |
+
|
50 |
+
//
|
51 |
+
}
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: add-type
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_mime.c>
|
8 |
+
AddType image/gif .test
|
9 |
+
</IfModule>
|
10 |
+
- filename: 'request-me.test'
|
11 |
+
content: 'hi'
|
12 |
+
request:
|
13 |
+
url: 'request-me.test'
|
14 |
+
|
15 |
+
interpretation:
|
16 |
+
- ['success', 'headers', 'contains-key-value', 'Content-Type', 'image/gif']
|
17 |
+
- ['inconclusive', 'status-code', 'not-equals', '200']
|
18 |
+
- ['failure', 'headers', 'not-contains-key-value', 'Content-Type', 'image/gif']
|
19 |
+
|
20 |
+
----
|
21 |
+
|
22 |
+
Tested:
|
23 |
+
|
24 |
+
| Case | Test result
|
25 |
+
| ------------------------------ | ------------------
|
26 |
+
| .htaccess disabled | failure
|
27 |
+
| forbidden directives (fatal) | failure
|
28 |
+
| access denied | inconclusive
|
29 |
+
| directive has no effect | failure
|
30 |
+
| it works | success
|
31 |
+
*/
|
32 |
+
|
33 |
+
|
34 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
35 |
+
|
36 |
+
use HtaccessCapabilityTester\HttpResponse;
|
37 |
+
use HtaccessCapabilityTester\Testers\AddTypeTester;
|
38 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
39 |
+
use PHPUnit\Framework\TestCase;
|
40 |
+
|
41 |
+
class AddTypeTesterTest extends BasisTestCase
|
42 |
+
{
|
43 |
+
|
44 |
+
public function testHtaccessDisabled()
|
45 |
+
{
|
46 |
+
$fakeServer = new FakeServer();
|
47 |
+
$fakeServer->disableHtaccess();
|
48 |
+
$testResult = $fakeServer->runTester(new AddTypeTester());
|
49 |
+
$this->assertFailure($testResult);
|
50 |
+
}
|
51 |
+
|
52 |
+
public function testDisallowedDirectivesFatal()
|
53 |
+
{
|
54 |
+
$fakeServer = new FakeServer();
|
55 |
+
$fakeServer->disallowAllDirectives('fatal');
|
56 |
+
$testResult = $fakeServer->runTester(new AddTypeTester());
|
57 |
+
$this->assertFailure($testResult);
|
58 |
+
}
|
59 |
+
|
60 |
+
public function testAccessAllDenied()
|
61 |
+
{
|
62 |
+
$fakeServer = new FakeServer();
|
63 |
+
$fakeServer->denyAllAccess();
|
64 |
+
$testResult = $fakeServer->runTester(new AddTypeTester());
|
65 |
+
$this->assertInconclusive($testResult);
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Test when the directive has no effect.
|
70 |
+
* This could happen when:
|
71 |
+
* - The directive is forbidden (non-fatal)
|
72 |
+
* - The module is not loaded
|
73 |
+
*/
|
74 |
+
public function testDirectiveHasNoEffect()
|
75 |
+
{
|
76 |
+
$fakeServer = new FakeServer();
|
77 |
+
$fakeServer->setResponses([
|
78 |
+
'/add-type/request-me.test' => new HttpResponse('hi', '200', [])
|
79 |
+
]);
|
80 |
+
$testResult = $fakeServer->runTester(new AddTypeTester());
|
81 |
+
$this->assertFailure($testResult);
|
82 |
+
}
|
83 |
+
|
84 |
+
public function testSuccess()
|
85 |
+
{
|
86 |
+
$fakeServer = new FakeServer();
|
87 |
+
$fakeServer->setResponses([
|
88 |
+
'/add-type/request-me.test' => new HttpResponse('hi', '200', ['Content-Type' => 'image/gif'])
|
89 |
+
]);
|
90 |
+
$testResult = $fakeServer->runTester(new AddTypeTester());
|
91 |
+
$this->assertSuccess($testResult);
|
92 |
+
}
|
93 |
+
|
94 |
+
}
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
4 |
+
|
5 |
+
use HtaccessCapabilityTester\HtaccessCapabilityTester;
|
6 |
+
use HtaccessCapabilityTester\TestResult;
|
7 |
+
|
8 |
+
use HtaccessCapabilityTester\Testers\RewriteTester;
|
9 |
+
use HtaccessCapabilityTester\Testers\AbstractTester;
|
10 |
+
|
11 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
12 |
+
|
13 |
+
use PHPUnit\Framework\TestCase;
|
14 |
+
|
15 |
+
class BasisTestCase extends TestCase
|
16 |
+
{
|
17 |
+
|
18 |
+
protected function assertSuccess($testResult)
|
19 |
+
{
|
20 |
+
$this->assertTrue($testResult->status, $testResult->info);
|
21 |
+
}
|
22 |
+
|
23 |
+
protected function assertFailure($testResult)
|
24 |
+
{
|
25 |
+
$this->assertFalse($testResult->status, $testResult->info);
|
26 |
+
}
|
27 |
+
|
28 |
+
protected function assertInconclusive($testResult)
|
29 |
+
{
|
30 |
+
$this->assertNull($testResult->status, $testResult->info);
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
*
|
35 |
+
* @param TestResult $testResult
|
36 |
+
* @param string $expectedResult failure|success|inconclusive
|
37 |
+
*
|
38 |
+
*/
|
39 |
+
/*
|
40 |
+
protected function assertTestResult($testResult, $expectedResult)
|
41 |
+
{
|
42 |
+
if ($expectedResult == 'failure') {
|
43 |
+
$this->assertFalse($testResult->status);
|
44 |
+
} elseif ($expectedResult == 'success') {
|
45 |
+
$this->assertTrue($testResult->status);
|
46 |
+
} elseif ($expectedResult == 'inconclusive') {
|
47 |
+
$this->assertNull($testResult->status);
|
48 |
+
}
|
49 |
+
}*/
|
50 |
+
|
51 |
+
/**
|
52 |
+
* @param AbstractTester $tester
|
53 |
+
* @param array $expectedBehaviour
|
54 |
+
* @param FakeServer $fakeServer
|
55 |
+
*/
|
56 |
+
/*
|
57 |
+
protected function behaviourOnFakeServer($tester, $expectedBehaviour, $fakeServer)
|
58 |
+
{
|
59 |
+
$tester->setTestFilesLineUpper($fakeServer);
|
60 |
+
$tester->setHttpRequester($fakeServer);
|
61 |
+
|
62 |
+
// $hct = Helper::getTesterUsingFakeServer($fakeServer);
|
63 |
+
|
64 |
+
if (isset($expectedBehaviour['htaccessDisabled'])) {
|
65 |
+
$fakeServer->disallowAllDirectives = true;
|
66 |
+
$testResult = $tester->run('', '');
|
67 |
+
$this->assertTestResult($testResult, );
|
68 |
+
|
69 |
+
$this->assertFailure($testResult->status);
|
70 |
+
}
|
71 |
+
}*/
|
72 |
+
}
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: content-digest
|
4 |
+
subtests:
|
5 |
+
- subdir: on
|
6 |
+
files:
|
7 |
+
- filename: '.htaccess'
|
8 |
+
content: |
|
9 |
+
ContentDigest On
|
10 |
+
- filename: 'request-me.txt'
|
11 |
+
content: 'hi'
|
12 |
+
request:
|
13 |
+
url: 'request-me.txt'
|
14 |
+
interpretation:
|
15 |
+
- ['failure', 'headers', 'not-contains-key', 'Content-MD5'],
|
16 |
+
|
17 |
+
- subdir: off
|
18 |
+
files:
|
19 |
+
- filename: '.htaccess'
|
20 |
+
content: |
|
21 |
+
ContentDigest Off
|
22 |
+
- filename: 'request-me.txt'
|
23 |
+
content: 'hi'
|
24 |
+
request:
|
25 |
+
url: 'request-me.txt'
|
26 |
+
|
27 |
+
interpretation:
|
28 |
+
- ['failure', 'headers', 'contains-key', 'Content-MD5']
|
29 |
+
- ['inconclusive', 'status-code', 'not-equals', '200']
|
30 |
+
- ['success', 'status-code', 'equals', '200']
|
31 |
+
----
|
32 |
+
|
33 |
+
Tested:
|
34 |
+
|
35 |
+
Server setup | Test result
|
36 |
+
--------------------------------------------------
|
37 |
+
.htaccess disabled | failure
|
38 |
+
forbidden directives (fatal) | failure (Required override: Options)
|
39 |
+
access denied | inconclusive (it might be allowed to other files)
|
40 |
+
directive has no effect | failure
|
41 |
+
| success
|
42 |
+
|
43 |
+
*/
|
44 |
+
|
45 |
+
|
46 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
47 |
+
|
48 |
+
use HtaccessCapabilityTester\HttpResponse;
|
49 |
+
use HtaccessCapabilityTester\Testers\ContentDigestTester;
|
50 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
51 |
+
use PHPUnit\Framework\TestCase;
|
52 |
+
|
53 |
+
class ContentDigestTesterTest extends BasisTestCase
|
54 |
+
{
|
55 |
+
|
56 |
+
public function testHtaccessDisabled()
|
57 |
+
{
|
58 |
+
$fakeServer = new FakeServer();
|
59 |
+
$fakeServer->disableHtaccess();
|
60 |
+
$testResult = $fakeServer->runTester(new ContentDigestTester());
|
61 |
+
$this->assertFailure($testResult);
|
62 |
+
}
|
63 |
+
|
64 |
+
public function testDisallowedDirectivesFatal()
|
65 |
+
{
|
66 |
+
$fakeServer = new FakeServer();
|
67 |
+
$fakeServer->disallowAllDirectives('fatal');
|
68 |
+
$testResult = $fakeServer->runTester(new ContentDigestTester());
|
69 |
+
$this->assertFailure($testResult);
|
70 |
+
}
|
71 |
+
|
72 |
+
public function testAccessAllDenied()
|
73 |
+
{
|
74 |
+
$fakeServer = new FakeServer();
|
75 |
+
$fakeServer->denyAllAccess();
|
76 |
+
$testResult = $fakeServer->runTester(new ContentDigestTester());
|
77 |
+
$this->assertInconclusive($testResult);
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Test when the directive has no effect.
|
82 |
+
* This could happen when:
|
83 |
+
* - The directive is forbidden (non-fatal)
|
84 |
+
* - The module is not loaded
|
85 |
+
*
|
86 |
+
* Test no effect when server is setup to content-digest
|
87 |
+
*/
|
88 |
+
public function testDirectiveHasNoEffect1()
|
89 |
+
{
|
90 |
+
$fakeServer = new FakeServer();
|
91 |
+
$fakeServer->setResponses([
|
92 |
+
'/content-digest/on/request-me.txt' => new HttpResponse('hi', '200', ['Content-MD5' => 'aaoeu']),
|
93 |
+
'/content-digest/off/request-me.txt' => new HttpResponse('hi', '200', ['Content-MD5' => 'aaoeu']),
|
94 |
+
]);
|
95 |
+
$testResult = $fakeServer->runTester(new ContentDigestTester());
|
96 |
+
$this->assertFailure($testResult);
|
97 |
+
}
|
98 |
+
|
99 |
+
/** Test no effect when server is setup NOT to content-digest
|
100 |
+
*/
|
101 |
+
public function testDirectiveHasNoEffect2()
|
102 |
+
{
|
103 |
+
$fakeServer = new FakeServer();
|
104 |
+
$fakeServer->setResponses([
|
105 |
+
'/content-digest/on/request-me.txt' => new HttpResponse('hi', '200', []),
|
106 |
+
'/content-digest/off/request-me.txt' => new HttpResponse('hi', '200', []),
|
107 |
+
]);
|
108 |
+
$testResult = $fakeServer->runTester(new ContentDigestTester());
|
109 |
+
$this->assertFailure($testResult);
|
110 |
+
}
|
111 |
+
|
112 |
+
public function testSuccess()
|
113 |
+
{
|
114 |
+
$fakeServer = new FakeServer();
|
115 |
+
$fakeServer->setResponses([
|
116 |
+
'/content-digest/on/request-me.txt' => new HttpResponse(
|
117 |
+
'hi',
|
118 |
+
'200',
|
119 |
+
['Content-MD5' => 'aaoeu']
|
120 |
+
),
|
121 |
+
'/content-digest/off/request-me.txt' => new HttpResponse('hi', '200', [])
|
122 |
+
]);
|
123 |
+
$testResult = $fakeServer->runTester(new ContentDigestTester());
|
124 |
+
$this->assertSuccess($testResult);
|
125 |
+
}
|
126 |
+
|
127 |
+
}
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: 'crash-tester/xxx' # xxx is a subdir for the specific crash-test
|
4 |
+
subtests:
|
5 |
+
- subdir: the-suspect
|
6 |
+
files:
|
7 |
+
- filename: '.htaccess'
|
8 |
+
content: # the rules goes here
|
9 |
+
- filename: 'request-me.txt'
|
10 |
+
content: 'thanks'
|
11 |
+
request:
|
12 |
+
url: 'request-me.txt'
|
13 |
+
bypass-standard-error-handling': ['all']
|
14 |
+
interpretation:
|
15 |
+
- [success, body, equals, '1']
|
16 |
+
- [failure, body, equals, '0']
|
17 |
+
- [success, status-code, not-equals, '500']
|
18 |
+
|
19 |
+
- subdir: the-innocent
|
20 |
+
files:
|
21 |
+
- filename: '.htaccess'
|
22 |
+
content: '# I am no trouble'
|
23 |
+
- filename: 'request-me.txt'
|
24 |
+
content: 'thanks'
|
25 |
+
request:
|
26 |
+
url: 'request-me.txt'
|
27 |
+
bypass-standard-error-handling: ['all']
|
28 |
+
interpretation:
|
29 |
+
# The suspect crashed. But if the innocent crashes too, we cannot judge
|
30 |
+
[inconclusive, status-code, equals, '500']
|
31 |
+
|
32 |
+
# The innocent did not crash. The suspect is guilty!
|
33 |
+
[failure]
|
34 |
+
|
35 |
+
----
|
36 |
+
|
37 |
+
Tested:
|
38 |
+
|
39 |
+
Server setup | Test result
|
40 |
+
--------------------------------------------------
|
41 |
+
.htaccess disabled | success! (nothing crashes)
|
42 |
+
access denied | success! (nothing crashes. In case there is both errors and
|
43 |
+
access denied, the response is 500. This is however
|
44 |
+
only tested on Apache 2.4.29)
|
45 |
+
all requests crash | inconclusive (even innocent request crashes means that we cannot
|
46 |
+
conclude that the rules are "crashy", or that they are not
|
47 |
+
|
48 |
+
*/
|
49 |
+
|
50 |
+
|
51 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
52 |
+
|
53 |
+
use HtaccessCapabilityTester\HttpResponse;
|
54 |
+
use HtaccessCapabilityTester\Testers\CrashTester;
|
55 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
56 |
+
use PHPUnit\Framework\TestCase;
|
57 |
+
|
58 |
+
class CrashTesterTest extends BasisTestCase
|
59 |
+
{
|
60 |
+
public function testHtaccessDisabled()
|
61 |
+
{
|
62 |
+
$fakeServer = new FakeServer();
|
63 |
+
$fakeServer->disableHtaccess();
|
64 |
+
$testResult = $fakeServer->runTester(new CrashTester(''));
|
65 |
+
$this->assertSuccess($testResult);
|
66 |
+
}
|
67 |
+
|
68 |
+
public function testAccessAllDenied()
|
69 |
+
{
|
70 |
+
$fakeServer = new FakeServer();
|
71 |
+
$fakeServer->denyAllAccess();
|
72 |
+
$testResult = $fakeServer->runTester(new CrashTester(''));
|
73 |
+
$this->assertSuccess($testResult);
|
74 |
+
}
|
75 |
+
|
76 |
+
public function testWhenAllRequestsCrashes()
|
77 |
+
{
|
78 |
+
$fakeServer = new FakeServer();
|
79 |
+
$fakeServer->makeAllCrash();
|
80 |
+
$testResult = $fakeServer->runTester(new CrashTester(''));
|
81 |
+
$this->assertInconclusive($testResult);
|
82 |
+
}
|
83 |
+
|
84 |
+
public function testWhenAllRequestsCrashes2()
|
85 |
+
{
|
86 |
+
$fakeServer = new FakeServer();
|
87 |
+
$fakeServer->setResponses([
|
88 |
+
'/crash-tester/test/the-suspect/request-me.txt' => new HttpResponse('', '500', []),
|
89 |
+
'/crash-tester/test/the-innocent/request-me.txt' => new HttpResponse('', '500', [])
|
90 |
+
]);
|
91 |
+
$testResult = $fakeServer->runTester(new CrashTester('aoeu', 'test'));
|
92 |
+
$this->assertInconclusive($testResult);
|
93 |
+
}
|
94 |
+
|
95 |
+
public function testWhenRequestCrashesButInnocentDoesNot()
|
96 |
+
{
|
97 |
+
$fakeServer = new FakeServer();
|
98 |
+
$fakeServer->setResponses([
|
99 |
+
'/crash-tester/test/the-suspect/request-me.txt' => new HttpResponse('', '500', []),
|
100 |
+
'/crash-tester/test/the-innocent/request-me.txt' => new HttpResponse('thanks', '200', [])
|
101 |
+
]);
|
102 |
+
$testResult = $fakeServer->runTester(new CrashTester('aoeu', 'test'));
|
103 |
+
$this->assertFailure($testResult);
|
104 |
+
}
|
105 |
+
|
106 |
+
}
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: directory-index
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_dir.c>
|
8 |
+
DirectoryIndex index2.html
|
9 |
+
</IfModule>
|
10 |
+
- filename: 'index.html'
|
11 |
+
content: '0'
|
12 |
+
- filename: 'index2.html'
|
13 |
+
content: '1'
|
14 |
+
|
15 |
+
request:
|
16 |
+
url: '' # We request the index, that is why its empty
|
17 |
+
bypass-standard-error-handling: ['404']
|
18 |
+
|
19 |
+
interpretation:
|
20 |
+
- ['success', 'body', 'equals', '1']
|
21 |
+
- ['failure', 'body', 'equals', '0']
|
22 |
+
- ['failure', 'status-code', 'equals', '404'] # "index.html" might not be set to index
|
23 |
+
|
24 |
+
----
|
25 |
+
|
26 |
+
Tested:
|
27 |
+
|
28 |
+
Server setup | Test result
|
29 |
+
--------------------------------------------------
|
30 |
+
.htaccess disabled | failure
|
31 |
+
forbidden directives (fatal) | failure (highly unlikely, as it is part of core - but still possible)
|
32 |
+
access denied | inconclusive (it might be allowed to other files)
|
33 |
+
directive has no effect | failure
|
34 |
+
| success
|
35 |
+
|
36 |
+
*/
|
37 |
+
|
38 |
+
|
39 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
40 |
+
|
41 |
+
use HtaccessCapabilityTester\HttpResponse;
|
42 |
+
use HtaccessCapabilityTester\Testers\DirectoryIndexTester;
|
43 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
44 |
+
use PHPUnit\Framework\TestCase;
|
45 |
+
|
46 |
+
class DirectoryIndexTesterTest extends BasisTestCase
|
47 |
+
{
|
48 |
+
|
49 |
+
public function testHtaccessDisabled()
|
50 |
+
{
|
51 |
+
$fakeServer = new FakeServer();
|
52 |
+
$fakeServer->disableHtaccess();
|
53 |
+
$testResult = $fakeServer->runTester(new DirectoryIndexTester());
|
54 |
+
$this->assertFailure($testResult);
|
55 |
+
}
|
56 |
+
|
57 |
+
public function testDisallowedDirectivesFatal()
|
58 |
+
{
|
59 |
+
$fakeServer = new FakeServer();
|
60 |
+
$fakeServer->disallowAllDirectives('fatal');
|
61 |
+
$testResult = $fakeServer->runTester(new DirectoryIndexTester());
|
62 |
+
$this->assertFailure($testResult);
|
63 |
+
}
|
64 |
+
|
65 |
+
public function testAccessAllDenied()
|
66 |
+
{
|
67 |
+
$fakeServer = new FakeServer();
|
68 |
+
$fakeServer->denyAllAccess();
|
69 |
+
$testResult = $fakeServer->runTester(new DirectoryIndexTester());
|
70 |
+
$this->assertInconclusive($testResult);
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Test when the directive has no effect.
|
75 |
+
* This could happen when:
|
76 |
+
* - The directive is forbidden (non-fatal)
|
77 |
+
* - The module is not loaded
|
78 |
+
*
|
79 |
+
*/
|
80 |
+
public function testDirectiveHasNoEffect()
|
81 |
+
{
|
82 |
+
$fakeServer = new FakeServer();
|
83 |
+
$fakeServer->setResponses([
|
84 |
+
'/directory-index/' => new HttpResponse('0', '200', []),
|
85 |
+
]);
|
86 |
+
$testResult = $fakeServer->runTester(new DirectoryIndexTester());
|
87 |
+
$this->assertFailure($testResult);
|
88 |
+
}
|
89 |
+
|
90 |
+
public function testSuccess()
|
91 |
+
{
|
92 |
+
$fakeServer = new FakeServer();
|
93 |
+
$fakeServer->setResponses([
|
94 |
+
'/directory-index/' => new HttpResponse('1', '200', [])
|
95 |
+
]);
|
96 |
+
$testResult = $fakeServer->runTester(new DirectoryIndexTester());
|
97 |
+
$this->assertSuccess($testResult);
|
98 |
+
}
|
99 |
+
|
100 |
+
}
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: header-set
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_headers.c>
|
8 |
+
Header set X-Response-Header-Test: test
|
9 |
+
</IfModule>
|
10 |
+
- filename: 'request-me.txt'
|
11 |
+
content: 'hi'
|
12 |
+
|
13 |
+
request:
|
14 |
+
url: 'request-me.txt'
|
15 |
+
|
16 |
+
interpretation:
|
17 |
+
- [success, headers, contains-key-value, 'X-Response-Header-Test', 'test'],
|
18 |
+
- [failure]
|
19 |
+
|
20 |
+
|
21 |
+
----
|
22 |
+
|
23 |
+
Tested:
|
24 |
+
|
25 |
+
Server setup | Test result
|
26 |
+
--------------------------------------------------
|
27 |
+
.htaccess disabled | failure
|
28 |
+
forbidden directives (fatal) | failure
|
29 |
+
access denied | inconclusive (it might be allowed to other files)
|
30 |
+
directive has no effect | failure
|
31 |
+
| success
|
32 |
+
*/
|
33 |
+
|
34 |
+
|
35 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
36 |
+
|
37 |
+
use HtaccessCapabilityTester\HttpResponse;
|
38 |
+
use HtaccessCapabilityTester\Testers\HeaderSetTester;
|
39 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
40 |
+
use PHPUnit\Framework\TestCase;
|
41 |
+
|
42 |
+
class HeaderSetTesterTest extends BasisTestCase
|
43 |
+
{
|
44 |
+
|
45 |
+
public function testHtaccessDisabled()
|
46 |
+
{
|
47 |
+
$fakeServer = new FakeServer();
|
48 |
+
$fakeServer->disableHtaccess();
|
49 |
+
$testResult = $fakeServer->runTester(new HeaderSetTester());
|
50 |
+
$this->assertFailure($testResult);
|
51 |
+
}
|
52 |
+
|
53 |
+
public function testDisallowedDirectivesFatal()
|
54 |
+
{
|
55 |
+
$fakeServer = new FakeServer();
|
56 |
+
$fakeServer->disallowAllDirectives('fatal');
|
57 |
+
$testResult = $fakeServer->runTester(new HeaderSetTester());
|
58 |
+
$this->assertFailure($testResult);
|
59 |
+
}
|
60 |
+
|
61 |
+
public function testAccessAllDenied()
|
62 |
+
{
|
63 |
+
$fakeServer = new FakeServer();
|
64 |
+
$fakeServer->denyAllAccess();
|
65 |
+
$testResult = $fakeServer->runTester(new HeaderSetTester());
|
66 |
+
$this->assertInconclusive($testResult);
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Test when the directive has no effect.
|
71 |
+
* This could happen when:
|
72 |
+
* - The directive is forbidden (non-fatal)
|
73 |
+
* - The module is not loaded
|
74 |
+
*/
|
75 |
+
public function testDirectiveHasNoEffect()
|
76 |
+
{
|
77 |
+
$fakeServer = new FakeServer();
|
78 |
+
$fakeServer->setResponses([
|
79 |
+
'/header-set/request-me.txt' => new HttpResponse('hi', '200', [])
|
80 |
+
]);
|
81 |
+
$testResult = $fakeServer->runTester(new HeaderSetTester());
|
82 |
+
$this->assertFailure($testResult);
|
83 |
+
}
|
84 |
+
|
85 |
+
public function testSuccess()
|
86 |
+
{
|
87 |
+
$fakeServer = new FakeServer();
|
88 |
+
$fakeServer->setResponses([
|
89 |
+
'/header-set/request-me.txt' => new HttpResponse(
|
90 |
+
'hi',
|
91 |
+
'200',
|
92 |
+
['X-Response-Header-Test' => 'test']
|
93 |
+
)
|
94 |
+
]);
|
95 |
+
$testResult = $fakeServer->runTester(new HeaderSetTester());
|
96 |
+
$this->assertSuccess($testResult);
|
97 |
+
}
|
98 |
+
|
99 |
+
}
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
|
4 |
+
----
|
5 |
+
|
6 |
+
Tested:
|
7 |
+
|
8 |
+
Server setup | Test result
|
9 |
+
--------------------------------------------------
|
10 |
+
.htaccess disabled | failure
|
11 |
+
access denied | inconclusive (it might be allowed to other files)
|
12 |
+
it works | success
|
13 |
+
*/
|
14 |
+
|
15 |
+
|
16 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
17 |
+
|
18 |
+
use HtaccessCapabilityTester\HttpResponse;
|
19 |
+
use HtaccessCapabilityTester\Testers\HtaccessEnabledTester;
|
20 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
21 |
+
use PHPUnit\Framework\TestCase;
|
22 |
+
|
23 |
+
class HtaccessEnabledTesterTest extends BasisTestCase
|
24 |
+
{
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Test failure when server signature fails
|
28 |
+
*
|
29 |
+
*/
|
30 |
+
public function testSuccessServerSignatureFails()
|
31 |
+
{
|
32 |
+
$fakeServer = new FakeServer();
|
33 |
+
$fakeServer->setResponses([
|
34 |
+
'/server-signature/on/test.php' => new HttpResponse('0', '200', []),
|
35 |
+
'/server-signature/off/test.php' => new HttpResponse('1', '200', [])
|
36 |
+
]);
|
37 |
+
$testResult = $fakeServer->runTester(new HtaccessEnabledTester());
|
38 |
+
$this->assertFailure($testResult);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Test success when server signature works.
|
43 |
+
*
|
44 |
+
*/
|
45 |
+
public function testSuccessServerSignatureSucceeds()
|
46 |
+
{
|
47 |
+
$fakeServer = new FakeServer();
|
48 |
+
$fakeServer->setResponses([
|
49 |
+
'/server-signature/on/test.php' => new HttpResponse('1', '200', []),
|
50 |
+
'/server-signature/off/test.php' => new HttpResponse('1', '200', [])
|
51 |
+
]);
|
52 |
+
$testResult = $fakeServer->runTester(new HtaccessEnabledTester());
|
53 |
+
$this->assertSuccess($testResult);
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Test success when setting a header works.
|
58 |
+
*/
|
59 |
+
public function testSuccessHeaderSetSucceeds()
|
60 |
+
{
|
61 |
+
$fakeServer = new FakeServer();
|
62 |
+
$fakeServer->setResponses([
|
63 |
+
'/header-set/request-me.txt' => new HttpResponse(
|
64 |
+
'hi',
|
65 |
+
'200',
|
66 |
+
['X-Response-Header-Test' => 'test']
|
67 |
+
)
|
68 |
+
]);
|
69 |
+
$testResult = $fakeServer->runTester(new HtaccessEnabledTester());
|
70 |
+
$this->assertSuccess($testResult);
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Test success when malformed .htaccess causes 500
|
75 |
+
*/
|
76 |
+
public function testSuccessMalformedHtaccess()
|
77 |
+
{
|
78 |
+
$fakeServer = new FakeServer();
|
79 |
+
$fakeServer->setResponses([
|
80 |
+
'/crash-tester/htaccess-enabled-malformed-htaccess/the-suspect/request-me.txt' =>
|
81 |
+
new HttpResponse('', '500', []),
|
82 |
+
'/crash-test/htaccess-enabled-malformed-htaccess/the-innocent/request-me.txt' =>
|
83 |
+
new HttpResponse('thanks', '200', [])
|
84 |
+
]);
|
85 |
+
$testResult = $fakeServer->runTester(new HtaccessEnabledTester());
|
86 |
+
$this->assertSuccess($testResult);
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Test failure when malformed .htaccess causes 500
|
91 |
+
*/
|
92 |
+
public function testFailureMalformedHtaccessDoesNotCauseCrash()
|
93 |
+
{
|
94 |
+
$fakeServer = new FakeServer();
|
95 |
+
$fakeServer->setResponses([
|
96 |
+
'/crash-tester/htaccess-enabled-malformed-htaccess/the-suspect/request-me.txt' =>
|
97 |
+
new HttpResponse('thanks', '200', []),
|
98 |
+
'/crash-test/htaccess-enabled-malformed-htaccess/the-innocent/request-me.txt' =>
|
99 |
+
new HttpResponse('thanks', '200', [])
|
100 |
+
]);
|
101 |
+
$testResult = $fakeServer->runTester(new HtaccessEnabledTester());
|
102 |
+
$this->assertFailure($testResult);
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Test inconclusive when all crashes
|
107 |
+
*/
|
108 |
+
public function testInconclusiveWhenAllCrashes()
|
109 |
+
{
|
110 |
+
$fakeServer = new FakeServer();
|
111 |
+
$fakeServer->makeAllCrash();
|
112 |
+
$testResult = $fakeServer->runTester(new HtaccessEnabledTester());
|
113 |
+
$this->assertInconclusive($testResult);
|
114 |
+
}
|
115 |
+
|
116 |
+
}
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: innocent-request
|
4 |
+
files:
|
5 |
+
- filename: 'request-me.txt'
|
6 |
+
content: 'thank you my dear'
|
7 |
+
|
8 |
+
request:
|
9 |
+
url: 'request-me.txt'
|
10 |
+
bypass-standard-error-handling: 'all'
|
11 |
+
|
12 |
+
interpretation:
|
13 |
+
- ['success', 'status-code', 'equals', '200']
|
14 |
+
- ['inconclusive', 'status-code', 'equals', '403']
|
15 |
+
- ['inconclusive', 'status-code', 'equals', '404']
|
16 |
+
- ['failure']
|
17 |
+
----
|
18 |
+
|
19 |
+
Tested:
|
20 |
+
|
21 |
+
Server setup | Test result
|
22 |
+
--------------------------------------------------
|
23 |
+
access denied | inconclusive (it might be allowed to other files)
|
24 |
+
always fatal | failure
|
25 |
+
*/
|
26 |
+
|
27 |
+
|
28 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
29 |
+
|
30 |
+
use HtaccessCapabilityTester\HttpResponse;
|
31 |
+
use HtaccessCapabilityTester\Testers\InnocentRequestTester;
|
32 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
33 |
+
use PHPUnit\Framework\TestCase;
|
34 |
+
|
35 |
+
class InnocentRequestTesterTest extends BasisTestCase
|
36 |
+
{
|
37 |
+
|
38 |
+
|
39 |
+
public function testAccessAllDenied()
|
40 |
+
{
|
41 |
+
$fakeServer = new FakeServer();
|
42 |
+
$fakeServer->denyAllAccess();
|
43 |
+
$testResult = $fakeServer->runTester(new InnocentRequestTester());
|
44 |
+
$this->assertInconclusive($testResult);
|
45 |
+
}
|
46 |
+
|
47 |
+
public function testSuccess()
|
48 |
+
{
|
49 |
+
$fakeServer = new FakeServer();
|
50 |
+
$testResult = $fakeServer->runTester(new InnocentRequestTester());
|
51 |
+
$this->assertSuccess($testResult);
|
52 |
+
}
|
53 |
+
|
54 |
+
}
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
|
4 |
+
subdir: module-loaded
|
5 |
+
subtests:
|
6 |
+
- subdir: server-signature
|
7 |
+
requirements: htaccessEnabled()
|
8 |
+
files:
|
9 |
+
- filename: '.htaccess'
|
10 |
+
content: |
|
11 |
+
ServerSignature Off
|
12 |
+
<IfModule mod_xxx.c>
|
13 |
+
ServerSignature On
|
14 |
+
</IfModule>
|
15 |
+
|
16 |
+
- filename: 'test.php'
|
17 |
+
content: |
|
18 |
+
<?php
|
19 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
20 |
+
echo 1;
|
21 |
+
} else {
|
22 |
+
echo 0;
|
23 |
+
}
|
24 |
+
interpretation:
|
25 |
+
- ['success', 'body', 'equals', '1']
|
26 |
+
- ['failure', 'body', 'equals', '0']
|
27 |
+
- subdir: rewrite
|
28 |
+
...
|
29 |
+
----
|
30 |
+
|
31 |
+
Tested:
|
32 |
+
|
33 |
+
Server setup | Test result
|
34 |
+
--------------------------------------------------
|
35 |
+
.htaccess disabled | failure
|
36 |
+
access denied | inconclusive (it might be allowed to other files)
|
37 |
+
it works | success
|
38 |
+
*/
|
39 |
+
|
40 |
+
|
41 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
42 |
+
|
43 |
+
use HtaccessCapabilityTester\HttpResponse;
|
44 |
+
use HtaccessCapabilityTester\Testers\ModuleLoadedTester;
|
45 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
46 |
+
use PHPUnit\Framework\TestCase;
|
47 |
+
|
48 |
+
class ModuleLoadedTesterTest extends BasisTestCase
|
49 |
+
{
|
50 |
+
|
51 |
+
public function testHtaccessDisabled()
|
52 |
+
{
|
53 |
+
$fakeServer = new FakeServer();
|
54 |
+
$fakeServer->disableHtaccess();
|
55 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
56 |
+
$this->assertFailure($testResult);
|
57 |
+
}
|
58 |
+
|
59 |
+
public function testInconclusiveWhenAllCrashes()
|
60 |
+
{
|
61 |
+
$fakeServer = new FakeServer();
|
62 |
+
|
63 |
+
$fakeServer->makeAllCrash();
|
64 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
65 |
+
|
66 |
+
$this->assertInconclusive($testResult);
|
67 |
+
}
|
68 |
+
|
69 |
+
public function testServerSignatureSucceedsModuleLoaded()
|
70 |
+
{
|
71 |
+
$fakeServer = new FakeServer();
|
72 |
+
$fakeServer->setResponses([
|
73 |
+
'/server-signature/on/test.php' => new HttpResponse('1', '200', []),
|
74 |
+
'/server-signature/off/test.php' => new HttpResponse('1', '200', []),
|
75 |
+
'/module-loaded/setenvif/server-signature/test.php' => new HttpResponse('1', '200', [])
|
76 |
+
]);
|
77 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
78 |
+
$this->assertSuccess($testResult);
|
79 |
+
}
|
80 |
+
|
81 |
+
public function testServerSignatureSucceedsModuleNotLoaded()
|
82 |
+
{
|
83 |
+
$fakeServer = new FakeServer();
|
84 |
+
$fakeServer->setResponses([
|
85 |
+
'/server-signature/on/test.php' => new HttpResponse('1', '200', []),
|
86 |
+
'/server-signature/off/test.php' => new HttpResponse('1', '200', []),
|
87 |
+
'/module-loaded/setenvif/server-signature/test.php' => new HttpResponse('0', '200', [])
|
88 |
+
]);
|
89 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
90 |
+
$this->assertFailure($testResult);
|
91 |
+
}
|
92 |
+
|
93 |
+
public function testContentDigestWorksModuleLoaded()
|
94 |
+
{
|
95 |
+
$fakeServer = new FakeServer();
|
96 |
+
$fakeServer->setResponses([
|
97 |
+
'/content-digest/on/request-me.txt' => new HttpResponse(
|
98 |
+
'hi',
|
99 |
+
'200',
|
100 |
+
['Content-MD5' => 'aaoeu']
|
101 |
+
),
|
102 |
+
'/content-digest/off/request-me.txt' => new HttpResponse('hi', '200', []),
|
103 |
+
'/module-loaded/setenvif/content-digest/request-me.txt' => new HttpResponse(
|
104 |
+
'',
|
105 |
+
'200',
|
106 |
+
['Content-MD5' => 'aoeu']
|
107 |
+
)
|
108 |
+
]);
|
109 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
110 |
+
$this->assertSuccess($testResult);
|
111 |
+
}
|
112 |
+
|
113 |
+
public function testContentDigestWorksModuleNotLoaded()
|
114 |
+
{
|
115 |
+
$fakeServer = new FakeServer();
|
116 |
+
$fakeServer->setResponses([
|
117 |
+
'/content-digest/on/request-me.txt' => new HttpResponse(
|
118 |
+
'hi',
|
119 |
+
'200',
|
120 |
+
['Content-MD5' => 'aaoeu']
|
121 |
+
),
|
122 |
+
'/content-digest/off/request-me.txt' => new HttpResponse('hi', '200', []),
|
123 |
+
'/module-loaded/setenvif/content-digest/request-me.txt' => new HttpResponse('', '200', [])
|
124 |
+
]);
|
125 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
126 |
+
$this->assertFailure($testResult);
|
127 |
+
}
|
128 |
+
|
129 |
+
public function testAddTypeWorksModuleLoaded()
|
130 |
+
{
|
131 |
+
$fakeServer = new FakeServer();
|
132 |
+
$fakeServer->setResponses([
|
133 |
+
'/add-type/request-me.test' => new HttpResponse(
|
134 |
+
'hi',
|
135 |
+
'200',
|
136 |
+
['Content-Type' => 'image/gif']
|
137 |
+
),
|
138 |
+
'/module-loaded/setenvif/add-type/request-me.test' => new HttpResponse(
|
139 |
+
'hi',
|
140 |
+
'200',
|
141 |
+
['Content-Type' => 'image/gif']
|
142 |
+
)
|
143 |
+
]);
|
144 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
145 |
+
$this->assertSuccess($testResult);
|
146 |
+
}
|
147 |
+
|
148 |
+
public function testAddTypeWorksModuleNotLoaded()
|
149 |
+
{
|
150 |
+
$fakeServer = new FakeServer();
|
151 |
+
$fakeServer->setResponses([
|
152 |
+
'/add-type/request-me.test' => new HttpResponse(
|
153 |
+
'hi',
|
154 |
+
'200',
|
155 |
+
['Content-Type' => 'image/gif']
|
156 |
+
),
|
157 |
+
'/module-loaded/setenvif/add-type/request-me.test' => new HttpResponse(
|
158 |
+
'hi',
|
159 |
+
'200',
|
160 |
+
['Content-Type' => 'image/jpeg']
|
161 |
+
)
|
162 |
+
]);
|
163 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
164 |
+
$this->assertFailure($testResult);
|
165 |
+
}
|
166 |
+
|
167 |
+
public function testDirectoryIndexWorksModuleLoaded()
|
168 |
+
{
|
169 |
+
$fakeServer = new FakeServer();
|
170 |
+
$fakeServer->setResponses([
|
171 |
+
'/directory-index/' => new HttpResponse('1', '200', []),
|
172 |
+
'/module-loaded/setenvif/directory-index/' => new HttpResponse('1', '200', [])
|
173 |
+
]);
|
174 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
175 |
+
$this->assertSuccess($testResult);
|
176 |
+
}
|
177 |
+
|
178 |
+
public function testDirectoryIndexWorksModuleNotLoaded()
|
179 |
+
{
|
180 |
+
$fakeServer = new FakeServer();
|
181 |
+
$fakeServer->setResponses([
|
182 |
+
'/directory-index/' => new HttpResponse('1', '200', []),
|
183 |
+
'/module-loaded/setenvif/directory-index/' => new HttpResponse('0', '200', [])
|
184 |
+
]);
|
185 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
186 |
+
$this->assertFailure($testResult);
|
187 |
+
}
|
188 |
+
|
189 |
+
public function testRewriteWorksModuleLoaded()
|
190 |
+
{
|
191 |
+
$fakeServer = new FakeServer();
|
192 |
+
$fakeServer->setResponses([
|
193 |
+
'/rewrite/0.txt' => new HttpResponse('1', '200', []),
|
194 |
+
'/module-loaded/setenvif/rewrite/request-me.txt' => new HttpResponse('1', '200', []),
|
195 |
+
]);
|
196 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
197 |
+
$this->assertSuccess($testResult);
|
198 |
+
}
|
199 |
+
|
200 |
+
public function testRewriteWorksModuleNotLoaded()
|
201 |
+
{
|
202 |
+
$fakeServer = new FakeServer();
|
203 |
+
$fakeServer->setResponses([
|
204 |
+
'/rewrite/0.txt' => new HttpResponse('1', '200', []),
|
205 |
+
'/module-loaded/setenvif/rewrite/request-me.txt' => new HttpResponse('0', '200', []),
|
206 |
+
]);
|
207 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
208 |
+
$this->assertFailure($testResult);
|
209 |
+
}
|
210 |
+
|
211 |
+
public function testHeaderSetWorksModuleLoaded()
|
212 |
+
{
|
213 |
+
$fakeServer = new FakeServer();
|
214 |
+
$fakeServer->setResponses([
|
215 |
+
'/header-set/request-me.txt' => new HttpResponse(
|
216 |
+
'hi',
|
217 |
+
'200',
|
218 |
+
['X-Response-Header-Test' => 'test']
|
219 |
+
),
|
220 |
+
'/module-loaded/setenvif/header-set/request-me.txt' => new HttpResponse(
|
221 |
+
'thanks',
|
222 |
+
'200',
|
223 |
+
['X-Response-Header-Test' => '1']
|
224 |
+
),
|
225 |
+
]);
|
226 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
227 |
+
$this->assertSuccess($testResult);
|
228 |
+
}
|
229 |
+
|
230 |
+
public function testHeaderSetWorksModuleNotLoaded()
|
231 |
+
{
|
232 |
+
$fakeServer = new FakeServer();
|
233 |
+
$fakeServer->setResponses([
|
234 |
+
'/header-set/request-me.txt' => new HttpResponse(
|
235 |
+
'hi',
|
236 |
+
'200',
|
237 |
+
['X-Response-Header-Test' => 'test']
|
238 |
+
),
|
239 |
+
'/module-loaded/setenvif/header-set/request-me.txt' => new HttpResponse(
|
240 |
+
'thanks',
|
241 |
+
'200',
|
242 |
+
['X-Response-Header-Test' => '0']
|
243 |
+
),
|
244 |
+
]);
|
245 |
+
$testResult = $fakeServer->runTester(new ModuleLoadedTester('setenvif'));
|
246 |
+
$this->assertFailure($testResult);
|
247 |
+
}
|
248 |
+
|
249 |
+
}
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: pass-info-from-rewrite-to-script-through-env
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_rewrite.c>
|
8 |
+
|
9 |
+
# Testing if we can pass environment variable from .htaccess to script in a RewriteRule
|
10 |
+
# We pass document root, because that can easily be checked by the script
|
11 |
+
|
12 |
+
RewriteEngine On
|
13 |
+
RewriteRule ^test\.php$ - [E=PASSTHROUGHENV:%{DOCUMENT_ROOT},L]
|
14 |
+
|
15 |
+
</IfModule>
|
16 |
+
- filename: 'test.php'
|
17 |
+
content: |
|
18 |
+
<?php
|
19 |
+
function getEnvPassedInRewriteRule($envName) {
|
20 |
+
// Environment variables passed through the REWRITE module have "REWRITE_" as a prefix
|
21 |
+
// (in Apache, not Litespeed, if I recall correctly).
|
22 |
+
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
23 |
+
// We simply look for an environment variable that ends with what we are looking for.
|
24 |
+
// (so make sure to make it unique)
|
25 |
+
$len = strlen($envName);
|
26 |
+
foreach ($_SERVER as $key => $item) {
|
27 |
+
if (substr($key, -$len) == $envName) {
|
28 |
+
return $item;
|
29 |
+
}
|
30 |
+
}
|
31 |
+
return false;
|
32 |
+
}
|
33 |
+
|
34 |
+
$result = getEnvPassedInRewriteRule('PASSTHROUGHENV');
|
35 |
+
if ($result === false) {
|
36 |
+
echo '0';
|
37 |
+
exit;
|
38 |
+
}
|
39 |
+
echo ($result == $_SERVER['DOCUMENT_ROOT'] ? '1' : '0');
|
40 |
+
|
41 |
+
request:
|
42 |
+
url: 'test.php'
|
43 |
+
|
44 |
+
interpretation:
|
45 |
+
- ['success', 'body', 'equals', '1']
|
46 |
+
- ['failure', 'body', 'equals', '0']
|
47 |
+
- ['inconclusive', 'body', 'begins-with', '<?php']
|
48 |
+
- ['inconclusive']
|
49 |
+
|
50 |
+
----
|
51 |
+
|
52 |
+
Tested:
|
53 |
+
|
54 |
+
Server setup | Test result
|
55 |
+
--------------------------------------------------
|
56 |
+
.htaccess disabled | failure
|
57 |
+
forbidden directives (fatal) | failure
|
58 |
+
access denied | inconclusive (it might be allowed to other files)
|
59 |
+
directive has no effect | failure
|
60 |
+
php is unprocessed | inconclusive
|
61 |
+
directive works | success
|
62 |
+
|
63 |
+
*/
|
64 |
+
|
65 |
+
|
66 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
67 |
+
|
68 |
+
use HtaccessCapabilityTester\HttpResponse;
|
69 |
+
use HtaccessCapabilityTester\Testers\PassInfoFromRewriteToScriptThroughEnvTester;
|
70 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
71 |
+
use PHPUnit\Framework\TestCase;
|
72 |
+
|
73 |
+
class PassInfoFromRewriteToScriptThroughEnvTesterTest extends BasisTestCase
|
74 |
+
{
|
75 |
+
|
76 |
+
/* can't do this test, it would require processing PHP
|
77 |
+
public function testHtaccessDisabled()
|
78 |
+
{
|
79 |
+
$fakeServer = new FakeServer();
|
80 |
+
$fakeServer->disableHtaccess();
|
81 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughEnvTester());
|
82 |
+
$this->assertFailure($testResult);
|
83 |
+
}*/
|
84 |
+
|
85 |
+
public function testDisallowedDirectivesFatal()
|
86 |
+
{
|
87 |
+
$fakeServer = new FakeServer();
|
88 |
+
$fakeServer->disallowAllDirectives('fatal');
|
89 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughEnvTester());
|
90 |
+
$this->assertFailure($testResult);
|
91 |
+
}
|
92 |
+
|
93 |
+
public function testAccessAllDenied()
|
94 |
+
{
|
95 |
+
$fakeServer = new FakeServer();
|
96 |
+
$fakeServer->denyAllAccess();
|
97 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughEnvTester());
|
98 |
+
$this->assertInconclusive($testResult);
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Test when the magic is not working
|
103 |
+
* This could happen when:
|
104 |
+
* - Any of the directives are forbidden (non-fatal)
|
105 |
+
* - Any of the modules are not loaded
|
106 |
+
* - Perhaps these advanced features are not working on all platforms
|
107 |
+
* (does LiteSpeed ie support these this?)
|
108 |
+
*/
|
109 |
+
public function testMagicNotWorking()
|
110 |
+
{
|
111 |
+
$fakeServer = new FakeServer();
|
112 |
+
$fakeServer->setResponses([
|
113 |
+
'/pass-info-from-rewrite-to-script-through-env/test.php' =>
|
114 |
+
new HttpResponse('0', '200', [])
|
115 |
+
]);
|
116 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughEnvTester());
|
117 |
+
$this->assertFailure($testResult);
|
118 |
+
}
|
119 |
+
|
120 |
+
public function testPHPNotProcessed()
|
121 |
+
{
|
122 |
+
$fakeServer = new FakeServer();
|
123 |
+
$fakeServer->handlePHPasText();
|
124 |
+
$testResult = $fakeServer->runTester(
|
125 |
+
new PassInfoFromRewriteToScriptThroughEnvTester()
|
126 |
+
);
|
127 |
+
$this->assertInconclusive($testResult);
|
128 |
+
}
|
129 |
+
|
130 |
+
public function testSuccess()
|
131 |
+
{
|
132 |
+
$fakeServer = new FakeServer();
|
133 |
+
$fakeServer->setResponses([
|
134 |
+
'/pass-info-from-rewrite-to-script-through-env/test.php' =>
|
135 |
+
new HttpResponse('1', '200', [])
|
136 |
+
]);
|
137 |
+
$testResult = $fakeServer->runTester(
|
138 |
+
new PassInfoFromRewriteToScriptThroughEnvTester()
|
139 |
+
);
|
140 |
+
$this->assertSuccess($testResult);
|
141 |
+
}
|
142 |
+
|
143 |
+
}
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: pass-info-from-rewrite-to-script-through-request-header
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_rewrite.c>
|
8 |
+
RewriteEngine On
|
9 |
+
|
10 |
+
# Testing if we can pass an environment variable through a request header
|
11 |
+
# We pass document root, because that can easily be checked by the script
|
12 |
+
|
13 |
+
<IfModule mod_headers.c>
|
14 |
+
RequestHeader set PASSTHROUGHHEADER "%{PASSTHROUGHHEADER}e" env=PASSTHROUGHHEADER
|
15 |
+
</IfModule>
|
16 |
+
RewriteRule ^test\.php$ - [E=PASSTHROUGHHEADER:%{DOCUMENT_ROOT},L]
|
17 |
+
|
18 |
+
</IfModule>
|
19 |
+
- filename: 'test.php'
|
20 |
+
content: |
|
21 |
+
<?php
|
22 |
+
if (isset($_SERVER['HTTP_PASSTHROUGHHEADER'])) {
|
23 |
+
echo ($_SERVER['HTTP_PASSTHROUGHHEADER'] == $_SERVER['DOCUMENT_ROOT'] ? 1 : 0);
|
24 |
+
exit;
|
25 |
+
}
|
26 |
+
echo '0';
|
27 |
+
|
28 |
+
request:
|
29 |
+
url: 'test.php'
|
30 |
+
|
31 |
+
interpretation:
|
32 |
+
- ['success', 'body', 'equals', '1']
|
33 |
+
- ['failure', 'body', 'equals', '0']
|
34 |
+
- ['inconclusive', 'body', 'begins-with', '<?php']
|
35 |
+
- ['inconclusive']
|
36 |
+
----
|
37 |
+
|
38 |
+
Tested:
|
39 |
+
|
40 |
+
Server setup | Test result
|
41 |
+
--------------------------------------------------
|
42 |
+
.htaccess disabled | failure
|
43 |
+
forbidden directives (fatal) | failure
|
44 |
+
access denied | inconclusive (it might be allowed to other files)
|
45 |
+
directive has no effect | failure
|
46 |
+
php is unprocessed | inconclusive
|
47 |
+
directive works | success
|
48 |
+
|
49 |
+
*/
|
50 |
+
|
51 |
+
|
52 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
53 |
+
|
54 |
+
use HtaccessCapabilityTester\HttpResponse;
|
55 |
+
use HtaccessCapabilityTester\Testers\PassInfoFromRewriteToScriptThroughRequestHeaderTester;
|
56 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
57 |
+
use PHPUnit\Framework\TestCase;
|
58 |
+
|
59 |
+
class PassInfoFromRewriteToScriptThroughRequestHeaderTesterTest extends BasisTestCase
|
60 |
+
{
|
61 |
+
|
62 |
+
/* can't do this test, it would require processing PHP
|
63 |
+
public function testHtaccessDisabled()
|
64 |
+
{
|
65 |
+
$fakeServer = new FakeServer();
|
66 |
+
$fakeServer->disableHtaccess();
|
67 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughRequestHeaderTester());
|
68 |
+
$this->assertFailure($testResult);
|
69 |
+
}*/
|
70 |
+
|
71 |
+
public function testDisallowedDirectivesFatal()
|
72 |
+
{
|
73 |
+
$fakeServer = new FakeServer();
|
74 |
+
$fakeServer->disallowAllDirectives('fatal');
|
75 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughRequestHeaderTester());
|
76 |
+
$this->assertFailure($testResult);
|
77 |
+
}
|
78 |
+
|
79 |
+
public function testAccessAllDenied()
|
80 |
+
{
|
81 |
+
$fakeServer = new FakeServer();
|
82 |
+
$fakeServer->denyAllAccess();
|
83 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughRequestHeaderTester());
|
84 |
+
$this->assertInconclusive($testResult);
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Test when the magic is not working
|
89 |
+
* This could happen when:
|
90 |
+
* - Any of the directives are forbidden (non-fatal)
|
91 |
+
* - Any of the modules are not loaded
|
92 |
+
* - Perhaps these advanced features are not working on all platforms
|
93 |
+
* (does LiteSpeed ie support these this?)
|
94 |
+
*/
|
95 |
+
public function testMagicNotWorking()
|
96 |
+
{
|
97 |
+
$fakeServer = new FakeServer();
|
98 |
+
$fakeServer->setResponses([
|
99 |
+
'/pass-info-from-rewrite-to-script-through-request-header/test.php' =>
|
100 |
+
new HttpResponse('0', '200', [])
|
101 |
+
]);
|
102 |
+
$testResult = $fakeServer->runTester(new PassInfoFromRewriteToScriptThroughRequestHeaderTester());
|
103 |
+
$this->assertFailure($testResult);
|
104 |
+
}
|
105 |
+
|
106 |
+
public function testPHPNotProcessed()
|
107 |
+
{
|
108 |
+
$fakeServer = new FakeServer();
|
109 |
+
$fakeServer->handlePHPasText();
|
110 |
+
$testResult = $fakeServer->runTester(
|
111 |
+
new PassInfoFromRewriteToScriptThroughRequestHeaderTester()
|
112 |
+
);
|
113 |
+
$this->assertInconclusive($testResult);
|
114 |
+
}
|
115 |
+
|
116 |
+
public function testSuccess()
|
117 |
+
{
|
118 |
+
$fakeServer = new FakeServer();
|
119 |
+
$fakeServer->setResponses([
|
120 |
+
'/pass-info-from-rewrite-to-script-through-request-header/test.php' =>
|
121 |
+
new HttpResponse('1', '200', [])
|
122 |
+
]);
|
123 |
+
$testResult = $fakeServer->runTester(
|
124 |
+
new PassInfoFromRewriteToScriptThroughRequestHeaderTester()
|
125 |
+
);
|
126 |
+
$this->assertSuccess($testResult);
|
127 |
+
}
|
128 |
+
|
129 |
+
}
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: request-header
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_headers.c>
|
8 |
+
# Certain hosts seem to strip non-standard request headers,
|
9 |
+
# so we use a standard one to avoid a false negative
|
10 |
+
RequestHeader set User-Agent "request-header-test"
|
11 |
+
</IfModule>
|
12 |
+
- filename: 'test.php'
|
13 |
+
content: |
|
14 |
+
<?php
|
15 |
+
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
16 |
+
echo $_SERVER['HTTP_USER_AGENT'] == 'request-header-test' ? 1 : 0;
|
17 |
+
} else {
|
18 |
+
echo 0;
|
19 |
+
}
|
20 |
+
|
21 |
+
request:
|
22 |
+
url: 'test.php'
|
23 |
+
|
24 |
+
interpretation:
|
25 |
+
- ['success', 'body', 'equals', '1']
|
26 |
+
- ['failure', 'body', 'equals', '0']
|
27 |
+
- ['inconclusive', 'body', 'begins-with', '<?php'],
|
28 |
+
|
29 |
+
|
30 |
+
TODO:
|
31 |
+
TEST: php_flag engine off
|
32 |
+
https://stackoverflow.com/questions/1271899/disable-php-in-directory-including-all-sub-directories-with-htaccess
|
33 |
+
TEST: RemoveHandler and RemoveType (https://electrictoolbox.com/disable-php-apache-htaccess/)
|
34 |
+
|
35 |
+
----
|
36 |
+
|
37 |
+
Tested:
|
38 |
+
|
39 |
+
Server setup | Test result
|
40 |
+
--------------------------------------------------
|
41 |
+
.htaccess disabled | failure
|
42 |
+
forbidden directives (fatal) | failure
|
43 |
+
access denied | inconclusive (it might be allowed to other files)
|
44 |
+
directive has no effect | failure
|
45 |
+
php is unprocessed | inconclusive
|
46 |
+
directive works | success
|
47 |
+
|
48 |
+
TODO:
|
49 |
+
*/
|
50 |
+
|
51 |
+
|
52 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
53 |
+
|
54 |
+
use HtaccessCapabilityTester\HttpResponse;
|
55 |
+
use HtaccessCapabilityTester\Testers\RequestHeaderTester;
|
56 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
57 |
+
use PHPUnit\Framework\TestCase;
|
58 |
+
|
59 |
+
class RequestHeaderTesterTest extends BasisTestCase
|
60 |
+
{
|
61 |
+
|
62 |
+
/* can't do this test, it would require processing PHP
|
63 |
+
public function testHtaccessDisabled()
|
64 |
+
{
|
65 |
+
$fakeServer = new FakeServer();
|
66 |
+
$fakeServer->disableHtaccess();
|
67 |
+
$testResult = $fakeServer->runTester(new RequestHeaderTester());
|
68 |
+
$this->assertFailure($testResult);
|
69 |
+
}*/
|
70 |
+
|
71 |
+
public function testDisallowedDirectivesFatal()
|
72 |
+
{
|
73 |
+
$fakeServer = new FakeServer();
|
74 |
+
$fakeServer->disallowAllDirectives('fatal');
|
75 |
+
$testResult = $fakeServer->runTester(new RequestHeaderTester());
|
76 |
+
$this->assertFailure($testResult);
|
77 |
+
}
|
78 |
+
|
79 |
+
public function testAccessAllDenied()
|
80 |
+
{
|
81 |
+
$fakeServer = new FakeServer();
|
82 |
+
$fakeServer->denyAllAccess();
|
83 |
+
$testResult = $fakeServer->runTester(new RequestHeaderTester());
|
84 |
+
$this->assertInconclusive($testResult);
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Test when the directive has no effect.
|
89 |
+
* This could happen when:
|
90 |
+
* - The directive is forbidden (non-fatal)
|
91 |
+
* - The module is not loaded
|
92 |
+
*/
|
93 |
+
public function testDirectiveHasNoEffect()
|
94 |
+
{
|
95 |
+
$fakeServer = new FakeServer();
|
96 |
+
$fakeServer->setResponses([
|
97 |
+
'/request-header/test.php' => new HttpResponse('0', '200', [])
|
98 |
+
]);
|
99 |
+
$testResult = $fakeServer->runTester(new RequestHeaderTester());
|
100 |
+
$this->assertFailure($testResult);
|
101 |
+
}
|
102 |
+
|
103 |
+
|
104 |
+
public function testPHPNotProcessed()
|
105 |
+
{
|
106 |
+
$fakeServer = new FakeServer();
|
107 |
+
$fakeServer->handlePHPasText();
|
108 |
+
$testResult = $fakeServer->runTester(new RequestHeaderTester());
|
109 |
+
$this->assertInconclusive($testResult);
|
110 |
+
}
|
111 |
+
|
112 |
+
|
113 |
+
public function testSuccess()
|
114 |
+
{
|
115 |
+
$fakeServer = new FakeServer();
|
116 |
+
$fakeServer->setResponses([
|
117 |
+
'/request-header/test.php' => new HttpResponse('1', '200', [])
|
118 |
+
]);
|
119 |
+
$testResult = $fakeServer->runTester(new RequestHeaderTester());
|
120 |
+
$this->assertSuccess($testResult);
|
121 |
+
}
|
122 |
+
|
123 |
+
}
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: rewrite
|
4 |
+
files:
|
5 |
+
- filename: '.htaccess'
|
6 |
+
content: |
|
7 |
+
<IfModule mod_rewrite.c>
|
8 |
+
RewriteEngine On
|
9 |
+
RewriteRule ^0\.txt$ 1\.txt [L]
|
10 |
+
</IfModule>
|
11 |
+
- filename: '0.txt'
|
12 |
+
content: '0'
|
13 |
+
- filename: '1.txt'
|
14 |
+
content: '1'
|
15 |
+
|
16 |
+
request:
|
17 |
+
url: '0.txt'
|
18 |
+
|
19 |
+
interpretation:
|
20 |
+
- [success, body, equals, '1']
|
21 |
+
- [failure, body, equals, '0']
|
22 |
+
|
23 |
+
|
24 |
+
----
|
25 |
+
|
26 |
+
Tested:
|
27 |
+
|
28 |
+
Server setup | Test result
|
29 |
+
--------------------------------------------------
|
30 |
+
.htaccess disabled | failure
|
31 |
+
forbidden directives (fatal) | failure
|
32 |
+
access denied | inconclusive (it might be allowed to other files)
|
33 |
+
directive has no effect | failure
|
34 |
+
| success
|
35 |
+
*/
|
36 |
+
|
37 |
+
|
38 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
39 |
+
|
40 |
+
use HtaccessCapabilityTester\HttpResponse;
|
41 |
+
use HtaccessCapabilityTester\Testers\RewriteTester;
|
42 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
43 |
+
use PHPUnit\Framework\TestCase;
|
44 |
+
|
45 |
+
class RewriteTesterTest extends BasisTestCase
|
46 |
+
{
|
47 |
+
|
48 |
+
public function testHtaccessDisabled()
|
49 |
+
{
|
50 |
+
$fakeServer = new FakeServer();
|
51 |
+
$fakeServer->disableHtaccess();
|
52 |
+
$testResult = $fakeServer->runTester(new RewriteTester());
|
53 |
+
$this->assertFailure($testResult);
|
54 |
+
}
|
55 |
+
|
56 |
+
public function testDisallowedDirectivesFatal()
|
57 |
+
{
|
58 |
+
$fakeServer = new FakeServer();
|
59 |
+
$fakeServer->disallowAllDirectives('fatal');
|
60 |
+
$testResult = $fakeServer->runTester(new RewriteTester());
|
61 |
+
$this->assertFailure($testResult);
|
62 |
+
}
|
63 |
+
|
64 |
+
public function testAccessAllDenied()
|
65 |
+
{
|
66 |
+
$fakeServer = new FakeServer();
|
67 |
+
$fakeServer->denyAllAccess();
|
68 |
+
$testResult = $fakeServer->runTester(new RewriteTester());
|
69 |
+
$this->assertInconclusive($testResult);
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Test when the directive has no effect.
|
74 |
+
* This could happen when:
|
75 |
+
* - The directive is forbidden (non-fatal)
|
76 |
+
* - The module is not loaded
|
77 |
+
*/
|
78 |
+
public function testDirectiveHasNoEffect()
|
79 |
+
{
|
80 |
+
$fakeServer = new FakeServer();
|
81 |
+
$fakeServer->setResponses([
|
82 |
+
'/rewrite/0.txt' => new HttpResponse('0', '200', [])
|
83 |
+
]);
|
84 |
+
$testResult = $fakeServer->runTester(new RewriteTester());
|
85 |
+
$this->assertFailure($testResult);
|
86 |
+
}
|
87 |
+
|
88 |
+
public function testSuccess()
|
89 |
+
{
|
90 |
+
$fakeServer = new FakeServer();
|
91 |
+
$fakeServer->setResponses([
|
92 |
+
'/rewrite/0.txt' => new HttpResponse('1', '200', [])
|
93 |
+
]);
|
94 |
+
$testResult = $fakeServer->runTester(new RewriteTester());
|
95 |
+
$this->assertSuccess($testResult);
|
96 |
+
}
|
97 |
+
|
98 |
+
}
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
subdir: server-signature
|
4 |
+
subtests:
|
5 |
+
- subdir: on
|
6 |
+
files:
|
7 |
+
- filename: '.htaccess'
|
8 |
+
content: |
|
9 |
+
ServerSignature On
|
10 |
+
- filename: 'test.php'
|
11 |
+
content: |
|
12 |
+
<?php
|
13 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
14 |
+
echo 1;
|
15 |
+
} else {
|
16 |
+
echo 0;
|
17 |
+
}
|
18 |
+
request:
|
19 |
+
url: 'test.php'
|
20 |
+
interpretation:
|
21 |
+
- ['inconclusive', 'body', 'isEmpty']
|
22 |
+
- ['inconclusive', 'status-code', 'not-equals', '200']
|
23 |
+
- ['failure', 'body', 'equals', '0']
|
24 |
+
|
25 |
+
- subdir: off
|
26 |
+
files:
|
27 |
+
- filename: '.htaccess'
|
28 |
+
content: |
|
29 |
+
ServerSignature Off
|
30 |
+
- filename: 'test.php'
|
31 |
+
content: |
|
32 |
+
<?php
|
33 |
+
if (isset($_SERVER['SERVER_SIGNATURE']) && ($_SERVER['SERVER_SIGNATURE'] != '')) {
|
34 |
+
echo 0;
|
35 |
+
} else {
|
36 |
+
echo 1;
|
37 |
+
}
|
38 |
+
request:
|
39 |
+
url: 'test.php'
|
40 |
+
interpretation:
|
41 |
+
- ['inconclusive', 'body', 'isEmpty']
|
42 |
+
- ['success', 'body', 'equals', '1']
|
43 |
+
- ['failure', 'body', 'equals', '0']
|
44 |
+
- ['inconclusive']
|
45 |
+
|
46 |
+
----
|
47 |
+
|
48 |
+
Tested:
|
49 |
+
|
50 |
+
Server setup | Test result
|
51 |
+
--------------------------------------------------
|
52 |
+
.htaccess disabled | failure
|
53 |
+
forbidden directives (fatal) | inconclusive (special!)
|
54 |
+
access denied | inconclusive (it might be allowed to other files)
|
55 |
+
directive has no effect | failure
|
56 |
+
| success
|
57 |
+
*/
|
58 |
+
|
59 |
+
|
60 |
+
namespace HtaccessCapabilityTester\Tests\Testers;
|
61 |
+
|
62 |
+
use HtaccessCapabilityTester\HttpResponse;
|
63 |
+
use HtaccessCapabilityTester\Testers\ServerSignatureTester;
|
64 |
+
use HtaccessCapabilityTester\Tests\FakeServer;
|
65 |
+
use PHPUnit\Framework\TestCase;
|
66 |
+
|
67 |
+
class ServerSignatureTesterTest extends BasisTestCase
|
68 |
+
{
|
69 |
+
|
70 |
+
/*
|
71 |
+
can't do this test as our fake server does not execute PHP
|
72 |
+
|
73 |
+
public function testHtaccessDisabled()
|
74 |
+
{
|
75 |
+
$fakeServer = new FakeServer();
|
76 |
+
$fakeServer->disableHtaccess();
|
77 |
+
$testResult = $fakeServer->runTester(new ServerSignatureTester());
|
78 |
+
$this->assertFailure($testResult);
|
79 |
+
}*/
|
80 |
+
|
81 |
+
public function testDisallowedDirectivesFatal()
|
82 |
+
{
|
83 |
+
$fakeServer = new FakeServer();
|
84 |
+
$fakeServer->disallowAllDirectives('fatal');
|
85 |
+
$testResult = $fakeServer->runTester(new ServerSignatureTester());
|
86 |
+
$this->assertFailure($testResult);
|
87 |
+
|
88 |
+
// SPECIAL!
|
89 |
+
// As ServerSignature is in core and AllowOverride is None, the tester assumes
|
90 |
+
// that this does not happen. The 500 must then be another problem, which is why
|
91 |
+
// it returns inconclusive
|
92 |
+
//$this->assertInconclusive($testResult);
|
93 |
+
}
|
94 |
+
|
95 |
+
public function testAccessAllDenied()
|
96 |
+
{
|
97 |
+
$fakeServer = new FakeServer();
|
98 |
+
$fakeServer->denyAllAccess();
|
99 |
+
$testResult = $fakeServer->runTester(new ServerSignatureTester());
|
100 |
+
$this->assertInconclusive($testResult);
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Test when the directive has no effect.
|
105 |
+
* This could happen when:
|
106 |
+
* - The directive is forbidden (non-fatal)
|
107 |
+
* - The module is not loaded
|
108 |
+
*
|
109 |
+
* This tests when ServerSignature is set, and the directive has no effect.
|
110 |
+
*/
|
111 |
+
public function testDirectiveHasNoEffect1()
|
112 |
+
{
|
113 |
+
$fakeServer = new FakeServer();
|
114 |
+
$fakeServer->setResponses([
|
115 |
+
'/server-signature/on/test.php' => new HttpResponse('1', '200', []),
|
116 |
+
'/server-signature/off/test.php' => new HttpResponse('0', '200', [])
|
117 |
+
]);
|
118 |
+
$testResult = $fakeServer->runTester(new ServerSignatureTester());
|
119 |
+
$this->assertFailure($testResult);
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* This tests when ServerSignature is unset, and the directive has no effect.
|
124 |
+
*/
|
125 |
+
public function testDirectiveHasNoEffect2()
|
126 |
+
{
|
127 |
+
$fakeServer = new FakeServer();
|
128 |
+
$fakeServer->setResponses([
|
129 |
+
'/server-signature/on/test.php' => new HttpResponse('0', '200', []),
|
130 |
+
'/server-signature/off/test.php' => new HttpResponse('1', '200', [])
|
131 |
+
]);
|
132 |
+
$testResult = $fakeServer->runTester(new ServerSignatureTester());
|
133 |
+
$this->assertFailure($testResult);
|
134 |
+
}
|
135 |
+
|
136 |
+
|
137 |
+
public function testSuccess()
|
138 |
+
{
|
139 |
+
$fakeServer = new FakeServer();
|
140 |
+
$fakeServer->setResponses([
|
141 |
+
'/server-signature/on/test.php' => new HttpResponse('1', '200', []),
|
142 |
+
'/server-signature/off/test.php' => new HttpResponse('1', '200', [])
|
143 |
+
]);
|
144 |
+
$testResult = $fakeServer->runTester(new ServerSignatureTester());
|
145 |
+
$this->assertSuccess($testResult);
|
146 |
+
}
|
147 |
+
|
148 |
+
}
|
@@ -3,7 +3,7 @@
|
|
3 |
* Plugin Name: WebP Express
|
4 |
* Plugin URI: https://github.com/rosell-dk/webp-express
|
5 |
* Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
|
6 |
-
* Version: 0.
|
7 |
* Author: Bjørn Rosell
|
8 |
* Author URI: https://www.bitwise-it.dk
|
9 |
* License: GPL2
|
3 |
* Plugin Name: WebP Express
|
4 |
* Plugin URI: https://github.com/rosell-dk/webp-express
|
5 |
* Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
|
6 |
+
* Version: 0.18.0
|
7 |
* Author: Bjørn Rosell
|
8 |
* Author URI: https://www.bitwise-it.dk
|
9 |
* License: GPL2
|
@@ -1,5 +1,5 @@
|
|
1 |
# Grant access to webp-on-demand.php and webp-realizer.php
|
2 |
-
<FilesMatch "(webp-on-demand|webp-realizer
|
3 |
<IfModule !mod_authz_core.c>
|
4 |
Order deny,allow
|
5 |
Allow from all
|
1 |
# Grant access to webp-on-demand.php and webp-realizer.php
|
2 |
+
<FilesMatch "(webp-on-demand\.php|webp-realizer\.php|ping\.php|ping\.txt)$">
|
3 |
<IfModule !mod_authz_core.c>
|
4 |
Order deny,allow
|
5 |
Allow from all
|
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php echo 'pong';
|
@@ -0,0 +1 @@
|
|
|
1 |
+
pong
|
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php echo 'pong';
|
@@ -0,0 +1 @@
|
|
|
1 |
+
pong
|
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
<?php
|
2 |
+
include '../wod/webp-on-demand.php';
|
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
<?php
|
2 |
+
include '../wod/webp-realizer.php';
|