WebP Express - Version 0.18.0

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

Download this release

Release Info

Developer rosell.dk
Plugin Icon 128x128 WebP Express
Version 0.18.0
Comparing to
See all releases

Code changes from version 0.17.5 to 0.18.0

Files changed (116) hide show
  1. .github/FUNDING.yml +2 -0
  2. BACKERS.md +29 -13
  3. README.md +78 -14
  4. README.txt +26 -6
  5. changelog.txt +13 -0
  6. composer.json +2 -1
  7. composer.lock +61 -1
  8. htaccess-capability-tests/.htaccess +0 -6
  9. htaccess-capability-tests/has-mod-header/.htaccess +0 -5
  10. htaccess-capability-tests/has-mod-header/test.php +0 -9
  11. htaccess-capability-tests/has-mod-rewrite/.htaccess +0 -11
  12. htaccess-capability-tests/has-mod-rewrite/1.php +0 -2
  13. htaccess-capability-tests/has-mod-rewrite/test.php +0 -2
  14. htaccess-capability-tests/pass-server-var-through-header/.htaccess +0 -15
  15. htaccess-capability-tests/pass-server-var-through-header/test.php +0 -9
  16. htaccess-capability-tests/pass-through-environment-var/.htaccess +0 -13
  17. htaccess-capability-tests/pass-through-environment-var/test.php +0 -26
  18. htaccess-capability-tests/urls.txt +0 -12
  19. lib/classes/BulkConvert.php +5 -54
  20. lib/classes/CachePurge.php +1 -1
  21. lib/classes/CapabilityTest.php +0 -1
  22. lib/classes/Config.php +8 -14
  23. lib/classes/Convert.php +2 -2
  24. lib/classes/ConvertHelperIndependent.php +1 -1
  25. lib/classes/ConvertLog.php +1 -1
  26. lib/classes/HTAccess.php +0 -86
  27. lib/classes/HTAccessCapabilityTestRunner.php +184 -0
  28. lib/classes/HTAccessRules.php +275 -32
  29. lib/classes/Paths.php +34 -3
  30. lib/classes/PlatformInfo.php +2 -1
  31. lib/classes/PluginActivate.php +0 -2
  32. lib/classes/SelfTest.php +3 -3
  33. lib/classes/SelfTestHelper.php +200 -6
  34. lib/classes/SelfTestRedirectToConverter.php +23 -9
  35. lib/classes/SelfTestRedirectToExisting.php +4 -8
  36. lib/classes/SelfTestRedirectToWebPRealizer.php +11 -0
  37. lib/classes/WPHttpRequester.php +28 -0
  38. lib/migrate/migrate7.php +0 -4
  39. lib/options/css/webp-express-options-page.css +1 -0
  40. lib/options/enqueue_scripts.php +1 -1
  41. lib/options/js/0.16.0/bulk-convert.js +74 -42
  42. lib/options/js/0.16.0/self-test.js +4 -0
  43. lib/options/options/alter-html/alter-html-options.inc +1 -1
  44. lib/options/options/general/cache-control.inc +15 -8
  45. lib/options/options/general/general.inc +1 -1
  46. lib/options/options/operation-mode.inc +1 -3
  47. lib/options/options/redirection-rules/enable-redirection-to-converter.inc +2 -2
  48. lib/options/options/redirection-rules/redirection-rules.inc +14 -4
  49. lib/options/page-messages.php +124 -39
  50. lib/options/submit.php +17 -19
  51. vendor/composer/autoload_classmap.php +24 -0
  52. vendor/composer/autoload_psr4.php +1 -0
  53. vendor/composer/autoload_static.php +32 -0
  54. vendor/composer/installed.json +62 -0
  55. vendor/rosell-dk/htaccess-capability-tester/.github/FUNDING.yml +2 -0
  56. vendor/rosell-dk/htaccess-capability-tester/.travis.yml +58 -0
  57. vendor/rosell-dk/htaccess-capability-tester/LICENSE +674 -0
  58. vendor/rosell-dk/htaccess-capability-tester/README.md +705 -0
  59. vendor/rosell-dk/htaccess-capability-tester/composer.json +72 -0
  60. vendor/rosell-dk/htaccess-capability-tester/docs/GrantAllCrashTesting.md +41 -0
  61. vendor/rosell-dk/htaccess-capability-tester/docs/Ideas.md +238 -0
  62. vendor/rosell-dk/htaccess-capability-tester/docs/MoreExamples.md +54 -0
  63. vendor/rosell-dk/htaccess-capability-tester/docs/TheManyWaysOfHtaccessFailure.md +30 -0
  64. vendor/rosell-dk/htaccess-capability-tester/docs/Usage.md +5 -0
  65. vendor/rosell-dk/htaccess-capability-tester/docs/interpreting.md +31 -0
  66. vendor/rosell-dk/htaccess-capability-tester/phpunit.xml.dist +39 -0
  67. vendor/rosell-dk/htaccess-capability-tester/src/HtaccessCapabilityTester.php +334 -0
  68. vendor/rosell-dk/htaccess-capability-tester/src/HttpRequesterInterface.php +13 -0
  69. vendor/rosell-dk/htaccess-capability-tester/src/HttpResponse.php +76 -0
  70. vendor/rosell-dk/htaccess-capability-tester/src/SimpleHttpRequester.php +46 -0
  71. vendor/rosell-dk/htaccess-capability-tester/src/SimpleTestFileLineUpper.php +99 -0
  72. vendor/rosell-dk/htaccess-capability-tester/src/TestFilesLineUpperInterface.php +20 -0
  73. vendor/rosell-dk/htaccess-capability-tester/src/TestResult.php +39 -0
  74. vendor/rosell-dk/htaccess-capability-tester/src/TestResultCache.php +81 -0
  75. vendor/rosell-dk/htaccess-capability-tester/src/Testers/AbstractTester.php +191 -0
  76. vendor/rosell-dk/htaccess-capability-tester/src/Testers/AddTypeTester.php +44 -0
  77. vendor/rosell-dk/htaccess-capability-tester/src/Testers/ContentDigestTester.php +54 -0
  78. vendor/rosell-dk/htaccess-capability-tester/src/Testers/CrashTester.php +87 -0
  79. vendor/rosell-dk/htaccess-capability-tester/src/Testers/CustomTester.php +228 -0
  80. vendor/rosell-dk/htaccess-capability-tester/src/Testers/DirectoryIndexTester.php +48 -0
  81. vendor/rosell-dk/htaccess-capability-tester/src/Testers/HeaderSetTester.php +43 -0
  82. vendor/rosell-dk/htaccess-capability-tester/src/Testers/Helpers/ResponseInterpreter.php +169 -0
  83. vendor/rosell-dk/htaccess-capability-tester/src/Testers/HtaccessEnabledTester.php +108 -0
  84. vendor/rosell-dk/htaccess-capability-tester/src/Testers/InnocentRequestTester.php +38 -0
  85. vendor/rosell-dk/htaccess-capability-tester/src/Testers/ModuleLoadedTester.php +369 -0
  86. vendor/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughEnvTester.php +81 -0
  87. vendor/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTester.php +65 -0
  88. vendor/rosell-dk/htaccess-capability-tester/src/Testers/RequestHeaderTester.php +59 -0
  89. vendor/rosell-dk/htaccess-capability-tester/src/Testers/RewriteTester.php +93 -0
  90. vendor/rosell-dk/htaccess-capability-tester/src/Testers/ServerSignatureTester.php +93 -0
  91. vendor/rosell-dk/htaccess-capability-tester/tests/FakeServer.php +170 -0
  92. vendor/rosell-dk/htaccess-capability-tester/tests/Helper.php +18 -0
  93. vendor/rosell-dk/htaccess-capability-tester/tests/HtaccessCapabilityTesterTest.php +142 -0
  94. vendor/rosell-dk/htaccess-capability-tester/tests/HttpResponseTest.php +51 -0
  95. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/AddTypeTesterTest.php +94 -0
  96. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/BasisTestCase.php +72 -0
  97. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ContentDigestTesterTest.php +127 -0
  98. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/CrashTesterTest.php +106 -0
  99. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/DirectoryIndexTesterTest.php +100 -0
  100. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/HeaderSetTesterTest.php +99 -0
  101. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/HtaccessEnabledTesterTest.php +116 -0
  102. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/InnocentRequestTesterTest.php +54 -0
  103. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ModuleLoadedTesterTest.php +249 -0
  104. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/PassInfoFromRewriteToScriptThroughEnvTesterTest.php +143 -0
  105. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTesterTest.php +129 -0
  106. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/RequestHeaderTesterTest.php +123 -0
  107. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/RewriteTesterTest.php +98 -0
  108. vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ServerSignatureTesterTest.php +148 -0
  109. webp-express.php +1 -1
  110. wod/.htaccess +1 -1
  111. wod/ping.php +1 -0
  112. wod/ping.txt +1 -0
  113. wod2/ping.php +1 -0
  114. wod2/ping.txt +1 -0
  115. wod2/webp-on-demand.php +2 -0
  116. wod2/webp-realizer.php +2 -0
.github/FUNDING.yml ADDED
@@ -0,0 +1,2 @@
 
 
1
+ ko_fi: rosell
2
+ patreon: rosell
BACKERS.md CHANGED
@@ -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. And I, the developer, promise not to try to harvest it later by creating a PRO version.
5
 
6
- How is it financed then? Well, it isn't exactly. However, some people choose to support the development by buying the developer 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 page at patreon.com](https://www.patreon.com/rosell)
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, along with a message - max 100 chars, and it may contain one link (the link url does not count any chars).
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
README.md CHANGED
@@ -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
- Almost 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,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 ~83% of all traffic, and ~80% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
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
- Once, you have a converter, that works, when you click the "test"-button, you are ready to test the whole stack, and the rewrite rules. To do this, first make sure to select something other than "Do not convert any images!" in *Image types to convert*. Next, click "Save settings". This will save settings, as well as update the *.htaccess*.
 
 
 
93
 
94
- If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" after a successful save. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the *.htaccess* redirection isn't working. It may be that your server just needs some time. Some servers has set up caching. It could also be that your images are handled by nginx.
95
 
96
- Note that the plugin does not change any HTML (unless you enabled the *Alter HTML* option). In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
97
 
98
- - Open the page in Google Chrome
 
 
 
 
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
- In order to test that the image is not being reconverted every time, look at the Response headers of the image. There should be a "X-WebP-Convert-Status" header. It should say "Serving existing converted image" the first time, but "Serving existing converted image" on subsequent requests (WebP-Express is based upon [WebP Convert](https://github.com/rosell-dk/webp-convert)).
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.17 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.18 might allow excluding certain files and folders. 0.19 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.19 might be displaying rules for NGINX. 0.20 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript librare 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
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 @robertgres for providing the fix.
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 Coffee](https://ko-fi.com/rosell)
837
- - [Become a backer](https://github.com/rosell-dk/webp-express/blob/master/BACKERS.md)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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. |
README.txt CHANGED
@@ -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.17.5
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
- * [Become a backer or sponsor on Patreon](https://www.patreon.com/rosell)
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
- = How do I buy you a cup of coffee? =
726
- Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacoff.ee/rosell).
 
 
 
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
 
changelog.txt CHANGED
@@ -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.
composer.json CHANGED
@@ -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
  },
composer.lock CHANGED
@@ -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": "1fcaac76aff0311beaaaf1400bed23d4",
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",
htaccess-capability-tests/.htaccess DELETED
@@ -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>
 
 
 
 
 
 
htaccess-capability-tests/has-mod-header/.htaccess DELETED
@@ -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>
 
 
 
 
 
htaccess-capability-tests/has-mod-header/test.php DELETED
@@ -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
- }
 
 
 
 
 
 
 
 
 
htaccess-capability-tests/has-mod-rewrite/.htaccess DELETED
@@ -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>
 
 
 
 
 
 
 
 
 
 
 
htaccess-capability-tests/has-mod-rewrite/1.php DELETED
@@ -1,2 +0,0 @@
1
- <?php
2
- echo '1';
 
 
htaccess-capability-tests/has-mod-rewrite/test.php DELETED
@@ -1,2 +0,0 @@
1
- <?php
2
- echo '0';
 
 
htaccess-capability-tests/pass-server-var-through-header/.htaccess DELETED
@@ -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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
htaccess-capability-tests/pass-server-var-through-header/test.php DELETED
@@ -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';
 
 
 
 
 
 
 
 
 
htaccess-capability-tests/pass-through-environment-var/.htaccess DELETED
@@ -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>
 
 
 
 
 
 
 
 
 
 
 
 
 
htaccess-capability-tests/pass-through-environment-var/test.php DELETED
@@ -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');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
htaccess-capability-tests/urls.txt DELETED
@@ -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
 
 
 
 
 
 
 
 
 
 
 
 
lib/classes/BulkConvert.php CHANGED
@@ -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
- $results[] = substr($relDir . "/", 2) . $filename; // (we cut the leading "./" off with substr)
 
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('Invalid security nonce (it has probably expired - try refreshing)');
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
  }
lib/classes/CachePurge.php CHANGED
@@ -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('Invalid security nonce (it has probably expired - try refreshing)');
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
 
lib/classes/CapabilityTest.php CHANGED
@@ -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
  }
lib/classes/Config.php CHANGED
@@ -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' => CapabilityTest::passThroughHeaderWorking(),
271
- 'passThroughEnvWorking' => CapabilityTest::passThroughEnvWorking(),
272
- 'modHeaderWorking' => CapabilityTest::modHeaderWorking(),
 
 
 
273
  ];
274
  }
275
 
@@ -646,10 +640,10 @@ class Config
646
  if ($forceRuleUpdating) {
647
  $rewriteRulesNeedsUpdate = true;
648
  } else {
649
- $rewriteRulesNeedsUpdate = HTAccess::doesRewriteRulesNeedUpdate($config);
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
 
lib/classes/Convert.php CHANGED
@@ -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('Invalid security nonce (it has probably expired - try refreshing)');
183
  //wp_die();
184
 
185
  $result = [
186
  'success' => false,
187
- 'msg' => 'Invalid security nonce (it has probably expired - try refreshing)',
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
 
lib/classes/ConvertHelperIndependent.php CHANGED
@@ -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.17.5. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
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
 
lib/classes/ConvertLog.php CHANGED
@@ -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('Invalid security nonce (it has probably expired - try refreshing)');
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
 
lib/classes/HTAccess.php CHANGED
@@ -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
lib/classes/HTAccessCapabilityTestRunner.php ADDED
@@ -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
+ }
lib/classes/HTAccessRules.php CHANGED
@@ -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 $addVary;
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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
- "/" . Paths::getWebPRealizerUrlPath() .
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
- "/" . Paths::getWebPRealizerUrlPath() .
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
- "/" . Paths::getWebPRealizerUrlPath() .
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
- "/" . Paths::getWebPRealizerUrlPath() .
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
- "/" . Paths::getWodUrlPath() .
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
- "/" . Paths::getWodUrlPath() .
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
- "/" . Paths::getWodUrlPath() .
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
- "/" . Paths::getWodUrlPath() .
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
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::$passThroughEnvVarDefinitelyUnavailable = ($capTests['passThroughEnvWorking'] === false);
685
- self::$passThroughEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
 
 
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::$addVary = self::$config['redirect-to-existing-in-htaccess'];
767
  if (self::$modHeaderDefinitelyUnavailable) {
768
- self::$addVary = false;
769
  }
770
 
771
  /* Build rules */
772
  $rules = '';
773
  $rules .= self::infoRules();
774
 
775
- if ($dirContainsSourceImages) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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::$addVary) {
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::$addVary = (self::$config['enable-redirection-to-converter'] && (self::$config['success-response'] == 'converted')) || (self::$config['redirect-to-existing-in-htaccess']);
811
 
812
  /*
813
- if (self::$addVary) {
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
- $rules .= self::addVaryHeaderEnvRules();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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";
lib/classes/Paths.php CHANGED
@@ -690,15 +690,46 @@ APACHE
690
  return self::getUrlPathFromUrl(self::getWebPExpressPluginUrl());
691
  }
692
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  public static function getWodUrlPath()
694
  {
695
- return self::getWebPExpressPluginUrlPath() . '/wod/webp-on-demand.php';
696
- //return self::getHomeUrlPath() . '/webp-on-demand';
 
 
 
 
 
 
 
 
697
  }
698
 
699
  public static function getWebPRealizerUrlPath()
700
  {
701
- return self::getWebPExpressPluginUrlPath() . '/wod/webp-realizer.php';
 
 
 
 
 
 
 
 
 
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()
lib/classes/PlatformInfo.php CHANGED
@@ -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
  }
lib/classes/PluginActivate.php CHANGED
@@ -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
  }
lib/classes/SelfTest.php CHANGED
@@ -43,8 +43,8 @@ class SelfTest
43
  /*
44
  $result = [];
45
  $result[] = '# Redirection tests';
46
- $modRewriteWorking = CapabilityTest::modRewriteWorking();
47
- $modHeaderWorking = CapabilityTest::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('Invalid security nonce (it has probably expired - try refreshing)');
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
 
lib/classes/SelfTestHelper.php CHANGED
@@ -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[] = '- mod_rewrite working?: ' . self::trueFalseNullString(CapabilityTest::modRewriteWorking());
502
- $log[] = '- mod_header working?: ' . self::trueFalseNullString($capTests['modHeaderWorking']);
 
 
 
 
 
 
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 = CapabilityTest::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
  }
lib/classes/SelfTestRedirectToConverter.php CHANGED
@@ -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
- $log[] = 'The test cannot be completed';
 
 
 
 
 
 
 
 
 
 
 
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[count($log) - 1] .= '. **BUT!**';
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[count($log) - 1] .= '. **BUT!**';
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
 
lib/classes/SelfTestRedirectToExisting.php CHANGED
@@ -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[count($log) - 1] .= '. **BUT!**';
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[count($log) - 1] .= '. **BUT!**';
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
 
lib/classes/SelfTestRedirectToWebPRealizer.php CHANGED
@@ -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 ' .
lib/classes/WPHttpRequester.php ADDED
@@ -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
+ }
lib/migrate/migrate7.php CHANGED
@@ -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);
lib/options/css/webp-express-options-page.css CHANGED
@@ -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,
lib/options/enqueue_scripts.php CHANGED
@@ -5,7 +5,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
5
  use \WebPExpress\Paths;
6
  use \WebPExpress\Config;
7
 
8
- $ver = '2'; // 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')) {
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')) {
lib/options/js/0.16.0/bulk-convert.js CHANGED
@@ -8,48 +8,80 @@ function openBulkConvertPopup() {
8
  'action': 'list_unconverted_files',
9
  'nonce' : window.webpExpress['ajax-nonces']['list-unconverted-files'],
10
  };
11
- jQuery.post(ajaxurl, data, function(response) {
12
- if ((typeof response == 'object') && (response['success'] == false)) {
13
- html = '<h1>Error</h1>';
14
- if (response['data'] && ((typeof response['data']) == 'string')) {
15
- html += webpexpress_escapeHTML(response['data']);
16
- }
17
- document.getElementById('bulkconvertcontent').innerHTML = html;
18
- return;
19
- }
20
- var bulkInfo = {
21
- 'groups': JSON.parse(response),
22
- 'groupPointer': 0,
23
- 'filePointer': 0,
24
- 'paused': false,
25
- 'webpTotalFilesize': 0,
26
- 'orgTotalFilesize': 0,
27
- };
28
- window.webpexpress_bulkconvert = bulkInfo;
29
-
30
- // count files
31
- var numFiles = 0;
32
- for (var i=0; i<bulkInfo.groups.length; i++) {
33
- numFiles += bulkInfo.groups[i].files.length;
34
- }
35
-
36
- //console.log(JSON.parse(response));
37
- var html = '';
38
- if (numFiles == 0) {
39
- html += '<p>There are no unconverted files</p>';
40
- } else {
41
- html += '<div>'
42
- html += '<p>There are ' + numFiles + ' unconverted files.</p>';
43
- html += '<p><i>Note that in a typical setup, you will have redirect rules which trigger conversion when needed, ' +
44
- 'and thus you have no need for bulk conversion. In fact, in that case, you should probably not bulk convert ' +
45
- 'because bulk conversion will also convert images and thumbnails which are not in use, and thus take up ' +
46
- 'more disk space than necessary. The bulk conversion feature was only added in order to make the plugin usable even when ' +
47
- 'there are problems with redirects (ie on Nginx in case you do not have access to the config or on Microsoft IIS). ' +
48
- '</i></p><br>';
49
- html += '<button onclick="startBulkConversion()" class="button button-primary" type="button">Start conversion</button>';
50
- html += '</div>';
51
- }
52
- document.getElementById('bulkconvertcontent').innerHTML = html;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
 
lib/options/js/0.16.0/self-test.js CHANGED
@@ -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) == '&#39;&#39;&#39;') {
60
+ line = '<pre>' + line.substr(15) + '</pre>';
61
+ }
62
+
63
  // Empty line
64
  if (line == '') {
65
  line = '<br>';
lib/options/options/alter-html/alter-html-options.inc CHANGED
@@ -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. Since 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: ' .
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: ' .
lib/options/options/general/cache-control.inc CHANGED
@@ -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
- if ($config['operation-mode'] == 'no-conversion') {
12
- echo helpIcon('<p>Optionally set cache-control header for the internally redirected images (recommended!)</p>');
13
- } else {
14
- echo helpIcon('<p>Controls the cache-control header on successful conversion and direct redirection to converted ' .
15
- 'image in .htaccess. In case of convert failure, headers will be sent to prevent caching.</p>' .
16
- '<p>PS: In order to set <i>stale-while-revalidate</i> and <i>stale-if-error directives<i>, you must ' .
17
- '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>.' .
18
- '</p>');
 
 
 
 
 
 
 
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>
lib/options/options/general/general.inc CHANGED
@@ -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
 
lib/options/options/operation-mode.inc CHANGED
@@ -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 &ndash; 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 &ndash; 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; ?>
lib/options/options/redirection-rules/enable-redirection-to-converter.inc CHANGED
@@ -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>
lib/options/options/redirection-rules/redirection-rules.inc CHANGED
@@ -1,16 +1,26 @@
1
  <fieldset class="block">
2
- <?php if ($config['operation-mode'] == 'no-conversion') : ?>
 
 
 
 
 
 
 
 
 
 
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 affects the rules created in the .htaccess. <?php
 
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; ?>
lib/options/page-messages.php CHANGED
@@ -2,17 +2,20 @@
2
 
3
  if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
- use \WebPExpress\CapabilityTest;
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 (CapabilityTest::modRewriteWorking()) {
28
  echo 'mod rewrite works. that is nice';
29
  }*/
30
 
31
- /*if (CapabilityTest::modHeaderWorking() === true) {
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
- if (HTAccess::arePathsUsedInHTAccessOutdated()) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  Messenger::printMessage(
227
  'warning',
228
- 'Warning: Wordpress paths have changed since the last time the Rewrite Rules was generated. The rules ' .
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
  }
lib/options/submit.php CHANGED
@@ -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 (we should only store results when .htaccess is updated as well)
688
- if ($sanitized['force'] || HTAccess::doesRewriteRulesNeedUpdate($config)) {
 
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
 
vendor/composer/autoload_classmap.php CHANGED
@@ -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',
vendor/composer/autoload_psr4.php CHANGED
@@ -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
  );
vendor/composer/autoload_static.php CHANGED
@@ -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',
vendor/composer/installed.json CHANGED
@@ -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",
vendor/rosell-dk/htaccess-capability-tester/.github/FUNDING.yml ADDED
@@ -0,0 +1,2 @@
 
 
1
+ ko_fi: rosell
2
+ patreon: rosell
vendor/rosell-dk/htaccess-capability-tester/.travis.yml ADDED
@@ -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
vendor/rosell-dk/htaccess-capability-tester/LICENSE ADDED
@@ -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>.
vendor/rosell-dk/htaccess-capability-tester/README.md ADDED
@@ -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
+ ```
vendor/rosell-dk/htaccess-capability-tester/composer.json ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/docs/GrantAllCrashTesting.md ADDED
@@ -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
+ ```
vendor/rosell-dk/htaccess-capability-tester/docs/Ideas.md ADDED
@@ -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
vendor/rosell-dk/htaccess-capability-tester/docs/MoreExamples.md ADDED
@@ -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
+ ```
vendor/rosell-dk/htaccess-capability-tester/docs/TheManyWaysOfHtaccessFailure.md ADDED
@@ -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.
vendor/rosell-dk/htaccess-capability-tester/docs/Usage.md ADDED
@@ -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)`
vendor/rosell-dk/htaccess-capability-tester/docs/interpreting.md ADDED
@@ -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
vendor/rosell-dk/htaccess-capability-tester/phpunit.xml.dist ADDED
@@ -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>
vendor/rosell-dk/htaccess-capability-tester/src/HtaccessCapabilityTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/HttpRequesterInterface.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/HttpResponse.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/SimpleHttpRequester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/SimpleTestFileLineUpper.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/TestFilesLineUpperInterface.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/TestResult.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/TestResultCache.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/AbstractTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/AddTypeTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/ContentDigestTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/CrashTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/CustomTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/DirectoryIndexTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/HeaderSetTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/Helpers/ResponseInterpreter.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/HtaccessEnabledTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/InnocentRequestTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/ModuleLoadedTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughEnvTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/RequestHeaderTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/RewriteTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/src/Testers/ServerSignatureTester.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/FakeServer.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Helper.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/HtaccessCapabilityTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/HttpResponseTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/AddTypeTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/BasisTestCase.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ContentDigestTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/CrashTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/DirectoryIndexTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/HeaderSetTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/HtaccessEnabledTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/InnocentRequestTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ModuleLoadedTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/PassInfoFromRewriteToScriptThroughEnvTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/PassInfoFromRewriteToScriptThroughRequestHeaderTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/RequestHeaderTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/RewriteTesterTest.php ADDED
@@ -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
+ }
vendor/rosell-dk/htaccess-capability-tester/tests/Testers/ServerSignatureTesterTest.php ADDED
@@ -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
+ }
webp-express.php CHANGED
@@ -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.17.5
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
wod/.htaccess CHANGED
@@ -1,5 +1,5 @@
1
  # Grant access to webp-on-demand.php and webp-realizer.php
2
- <FilesMatch "(webp-on-demand|webp-realizer)\.php$">
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
wod/ping.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php echo 'pong';
wod/ping.txt ADDED
@@ -0,0 +1 @@
 
1
+ pong
wod2/ping.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php echo 'pong';
wod2/ping.txt ADDED
@@ -0,0 +1 @@
 
1
+ pong
wod2/webp-on-demand.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+ include '../wod/webp-on-demand.php';
wod2/webp-realizer.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+ include '../wod/webp-realizer.php';