WebP Express - Version 0.14.10

Version Description

(released: 24 jun 2019)

  • Tidied up code
Download this release

Release Info

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

Code changes from version 0.14.9 to 0.14.10

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.2
7
- Stable tag: 0.14.9
8
Requires PHP: 5.6
9
License: GPLv3
10
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -605,6 +605,11 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacof
605
606
== Changelog ==
607
608
= 0.14.9 =
609
*(released: 22 jun 2019)*
610
@@ -844,6 +849,9 @@ For older releases, check out changelog.txt
844
845
== Upgrade Notice ==
846
847
= 0.14.9 =
848
Tidied up code
849
4
Tags: webp, images, performance
5
Requires at least: 4.0
6
Tested up to: 5.2
7
+ Stable tag: 0.14.10
8
Requires PHP: 5.6
9
License: GPLv3
10
License URI: https://www.gnu.org/licenses/gpl-3.0.html
605
606
== Changelog ==
607
608
+ = 0.14.10 =
609
+ *(released: 24 jun 2019)*
610
+
611
+ * Tidied up code
612
+
613
= 0.14.9 =
614
*(released: 22 jun 2019)*
615
849
850
== Upgrade Notice ==
851
852
+ = 0.14.10 =
853
+ Tidied up code
854
+
855
= 0.14.9 =
856
Tidied up code
857
lib/classes/Convert.php CHANGED
@@ -5,7 +5,8 @@ namespace WebPExpress;
5
use \WebPExpress\ConvertHelperIndependent;
6
use \WebPExpress\Config;
7
use \WebPExpress\ConvertersHelper;
8
- use \WebPExpress\Sanitize;
9
use \WebPExpress\Validate;
10
use \WebPExpress\ValidateException;
11
@@ -79,41 +80,44 @@ class Convert
79
wp_die();
80
}
81
82
- // Validate input
83
- // ---------------------------
84
try {
85
- // validate "filename"
86
- $validating = '"filename" argument';
87
Validate::postHasKey('filename');
88
$filename = sanitize_text_field($_POST['filename']);
89
- Validate::absPathLooksSaneExistsAndIsNotDir($filename);
90
91
92
- // validate converter id
93
// ---------------------
94
- $validating = '"converter" argument';
95
if (isset($_POST['converter'])) {
96
$converterId = sanitize_text_field($_POST['converter']);
97
Validate::isConverterId($converterId);
98
}
99
100
101
- // validate "config-overrides"
102
// ---------------------------
103
- $validating = '"config-overrides" argument';
104
if (isset($_POST['config-overrides'])) {
105
- $configOverridesJSON = Sanitize::removeNUL($_POST['config-overrides']);
106
$configOverridesJSON = preg_replace('/\\\\"/', '"', $configOverridesJSON); // We got crazy encoding, perhaps by jQuery. This cleans it up
107
108
- Validate::isJSONObject($configOverridesJSON, $configOverridesJSON);
109
$configOverrides = json_decode($configOverridesJSON, true);
110
111
// PS: We do not need to validate the overrides.
112
// webp-convert checks all options. Nothing can be passed to webp-convert which causes harm.
113
}
114
115
} catch (ValidateException $e) {
116
- wp_send_json_error('failed validating ' . $validating . ': '. $e->getMessage());
117
wp_die();
118
}
119
5
use \WebPExpress\ConvertHelperIndependent;
6
use \WebPExpress\Config;
7
use \WebPExpress\ConvertersHelper;
8
+ use \WebPExpress\SanityCheck;
9
+ use \WebPExpress\SanityException;
10
use \WebPExpress\Validate;
11
use \WebPExpress\ValidateException;
12
80
wp_die();
81
}
82
83
+ // Check input
84
+ // --------------
85
try {
86
+ // Check "filename"
87
+ $checking = '"filename" argument';
88
Validate::postHasKey('filename');
89
$filename = sanitize_text_field($_POST['filename']);
90
+ $filename = SanityCheck::absPathExistsAndIsNotDir($filename);
91
92
93
+ // Check converter id
94
// ---------------------
95
+ $checking = '"converter" argument';
96
if (isset($_POST['converter'])) {
97
$converterId = sanitize_text_field($_POST['converter']);
98
Validate::isConverterId($converterId);
99
}
100
101
102
+ // Check "config-overrides"
103
// ---------------------------
104
+ $checking = '"config-overrides" argument';
105
if (isset($_POST['config-overrides'])) {
106
+ $configOverridesJSON = SanityCheck::noControlChars($_POST['config-overrides']);
107
$configOverridesJSON = preg_replace('/\\\\"/', '"', $configOverridesJSON); // We got crazy encoding, perhaps by jQuery. This cleans it up
108
109
+ $configOverridesJSON = SanityCheck::isJSONObject($configOverridesJSON);
110
$configOverrides = json_decode($configOverridesJSON, true);
111
112
// PS: We do not need to validate the overrides.
113
// webp-convert checks all options. Nothing can be passed to webp-convert which causes harm.
114
}
115
116
+ } catch (SanityException $e) {
117
+ wp_send_json_error('Sanitation check failed for ' . $checking . ': '. $e->getMessage());
118
+ wp_die();
119
} catch (ValidateException $e) {
120
+ wp_send_json_error('Validation failed for ' . $checking . ': '. $e->getMessage());
121
wp_die();
122
}
123
lib/classes/ConvertHelperIndependent.php CHANGED
@@ -268,7 +268,7 @@ APACHE
268
269
$text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
270
271
- $text = 'WebP Express 0.14.9. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
272
273
$logFile = self::getLogFilename($source, $logDir);
274
268
269
$text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
270
271
+ $text = 'WebP Express 0.14.10. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
272
273
$logFile = self::getLogFilename($source, $logDir);
274
lib/classes/SanityCheck.php ADDED
@@ -0,0 +1,195 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ use \WebPExpress\Sanitize;
6
+ use \WebPExpress\SanityException;
7
+
8
+ class SanityCheck
9
+ {
10
+
11
+ /**
12
+ *
13
+ * @param string $input string to test for NUL char
14
+ */
15
+ public static function mustBeString($input, $errorMsg = 'String expected')
16
+ {
17
+ if (gettype($input) !== 'string') {
18
+ throw new SanityException($errorMsg);
19
+ }
20
+ return $input;
21
+ }
22
+
23
+ /**
24
+ * The NUL character is a demon, because it can be used to bypass other tests
25
+ * See https://st-g.de/2011/04/doing-filename-checks-securely-in-PHP.
26
+ *
27
+ * @param string $input string to test for NUL char
28
+ */
29
+ public static function noNUL($input, $errorMsg = 'NUL character is not allowed')
30
+ {
31
+ self::mustBeString($input);
32
+ if (strpos($input, chr(0)) !== false) {
33
+ throw new SanityException($errorMsg);
34
+ }
35
+ return $input;
36
+ }
37
+
38
+ /**
39
+ * Prevent control chararters (#00 - #20).
40
+ *
41
+ * This prevents line feed, new line, tab, charater return, tab, ets.
42
+ * https://www.rapidtables.com/code/text/ascii-table.html
43
+ *
44
+ * @param string $input string to test for control characters
45
+ */
46
+ public static function noControlChars($input)
47
+ {
48
+ self::mustBeString($input);
49
+ self::noNUL($input);
50
+ if (preg_match('#[\x{0}-\x{1f}]#', $input)) {
51
+ throw new SanityException('Control characters are not allowed');
52
+ }
53
+ return $input;
54
+ }
55
+
56
+
57
+ /**
58
+ *
59
+ * @param mixed $input something that may not be empty
60
+ */
61
+ public static function notEmpty($input, $errorMsg = 'Must be non-empty')
62
+ {
63
+ if (empty($input)) {
64
+ throw new SanityException($input);
65
+ }
66
+ return $input;
67
+ }
68
+
69
+
70
+
71
+ public static function noDirectoryTraversal($input, $errorMsg = 'Directory traversal is not allowed')
72
+ {
73
+ self::mustBeString($input);
74
+ self::noControlChars($input);
75
+ if (preg_match('#\.\.\/#', $input)) {
76
+ throw new SanityException($errorMsg);
77
+ }
78
+ return $input;
79
+ }
80
+
81
+ public static function noStreamWrappers($input, $errorMsg = 'Stream wrappers are not allowed')
82
+ {
83
+ self::mustBeString($input);
84
+ self::noControlChars($input);
85
+
86
+ // Prevent stream wrappers ("phar://", "php://" and the like)
87
+ // https://www.php.net/manual/en/wrappers.phar.php
88
+ if (preg_match('#^\\w+://#', Sanitize::removeNUL($input))) {
89
+ throw new SanityException($errorMsg);
90
+ }
91
+ return $input;
92
+ }
93
+
94
+ public static function path($input)
95
+ {
96
+ self::notEmpty($input);
97
+ self::mustBeString($input);
98
+ self::noControlChars($input);
99
+ self::noDirectoryTraversal($input);
100
+ self::noStreamWrappers($input);
101
+ return $input;
102
+ }
103
+
104
+ public static function pathWithoutDirectoryTraversal($input)
105
+ {
106
+ return self::path($input);
107
+ }
108
+
109
+ public static function pathBeginsWith($input, $beginsWith, $errorMsg = 'Path is outside allowed path')
110
+ {
111
+ self::path($input);
112
+ if (!(strpos($input, $beginsWith) === 0)) {
113
+ throw new SanityException($errorMsg);
114
+ }
115
+ return $input;
116
+ }
117
+
118
+ public static function absPath($input)
119
+ {
120
+ return self::path($input);
121
+ }
122
+
123
+ public static function absPathExists($input, $errorMsg = 'Path does not exist')
124
+ {
125
+ self::absPath($input);
126
+ if (@!file_exists($input)) {
127
+ throw new SanityException($errorMsg);
128
+ }
129
+ return $input;
130
+ }
131
+
132
+ public static function absPathExistsAndIsDir(
133
+ $input,
134
+ $errorMsg = 'Path points to a file (it should point to a directory)'
135
+ ) {
136
+ self::absPathExists($input);
137
+ if (!is_dir($input)) {
138
+ throw new SanityException($errorMsg);
139
+ }
140
+ return $input;
141
+ }
142
+
143
+ public static function absPathExistsAndIsFile(
144
+ $input,
145
+ $errorMsg = 'Path points to a directory (it should not do that)'
146
+ ) {
147
+ self::absPathExists($input, 'File does not exist');
148
+ if (@is_dir($input)) {
149
+ throw new SanityException($errorMsg);
150
+ }
151
+ return $input;
152
+ }
153
+
154
+ public static function absPathExistsAndIsNotDir(
155
+ $input,
156
+ $errorMsg = 'Path points to a directory (it should point to a file)'
157
+ ) {
158
+ self::absPathExistsAndIsFile($input, $errorMsg);
159
+ return $input;
160
+ }
161
+
162
+
163
+ public static function pregMatch($pattern, $input, $errorMsg = 'Does not match expected pattern')
164
+ {
165
+ self::noNUL($input);
166
+ self::mustBeString($input);
167
+ if (!preg_match($pattern, $input)) {
168
+ throw new SanityException($errorMsg);
169
+ }
170
+ return $input;
171
+ }
172
+
173
+ public static function isJSONArray($input, $errorMsg = 'Not a JSON array')
174
+ {
175
+ self::noNUL($input);
176
+ self::mustBeString($input);
177
+ self::notEmpty($input);
178
+ if ((strpos($input, '[') !== 0) || (!is_array(json_decode($input)))) {
179
+ throw new SanityException($errorMsg);
180
+ }
181
+ return $input;
182
+ }
183
+
184
+ public static function isJSONObject($input, $errorMsg = 'Not a JSON object')
185
+ {
186
+ self::noNUL($input);
187
+ self::mustBeString($input);
188
+ self::notEmpty($input);
189
+ if ((strpos($input, '{') !== 0) || (!is_object(json_decode($input)))) {
190
+ throw new SanityException($errorMsg);
191
+ }
192
+ return $input;
193
+ }
194
+
195
+ }
lib/classes/SanityException.php ADDED
@@ -0,0 +1,7 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class SanityException extends \Exception
6
+ {
7
+ }
lib/classes/Validate.php CHANGED
@@ -2,8 +2,9 @@
2
3
namespace WebPExpress;
4
5
- use \WebPExpress\Sanitize;
6
use \WebPExpress\ValidateException;
7
8
class Validate
9
{
@@ -15,151 +16,10 @@ class Validate
15
}
16
}
17
18
- /**
19
- * The NUL character is a demon, because it can be used to bypass other tests
20
- * See https://st-g.de/2011/04/doing-filename-checks-securely-in-PHP.
21
- *
22
- * @param string $string string to test for NUL char
23
- */
24
- public static function noNUL($string)
25
- {
26
- if (strpos($string, chr(0)) !== false) {
27
- throw new ValidateException('NUL character is not allowed');
28
- }
29
- }
30
-
31
- /**
32
- * Prevent control chararters (#00 - #20).
33
- *
34
- * This prevents line feed, new line, tab, charater return, tab, ets.
35
- * https://www.rapidtables.com/code/text/ascii-table.html
36
- *
37
- * @param string $string string to test for control characters
38
- */
39
- public static function noControlChars($string)
40
- {
41
- if (preg_match('#[\x{0}-\x{1f}]#', $string)) {
42
- throw new ValidateException('Control characters are not allowed');
43
- }
44
- }
45
-
46
- /**
47
- *
48
- * @param mixed $mixed something that may not be empty
49
- */
50
- public static function notEmpty($mixed)
51
- {
52
- if (empty($mixed)) {
53
- throw new ValidateException('Must be non-empty');
54
- }
55
- }
56
-
57
- public static function noDirectoryTraversal($path)
58
- {
59
- if (preg_match('#\.\.\/#', Sanitize::removeNUL($path))) {
60
- throw new ValidateException('Directory traversal is not allowed');
61
- }
62
- }
63
-
64
- public static function noStreamWrappers($path)
65
- {
66
- // Prevent stream wrappers ("phar://", "php://" and the like)
67
- // https://www.php.net/manual/en/wrappers.phar.php
68
- if (preg_match('#^\\w+://#', Sanitize::removeNUL($path))) {
69
- throw new ValidateException('Stream wrappers are not allowed');
70
- }
71
- }
72
-
73
- public static function pathLooksSane($path)
74
- {
75
- self::notEmpty($path);
76
- self::noControlChars($path);
77
- self::noDirectoryTraversal($path);
78
- self::noStreamWrappers($path);
79
- }
80
-
81
- public static function absPathLooksSane($path)
82
- {
83
- self::pathLooksSane($path);
84
- }
85
-
86
-
87
- public static function absPathLooksSaneAndExists($path, $errorMsg = 'Path does not exist')
88
- {
89
- self::absPathLooksSane($path);
90
- if (@!file_exists($path)) {
91
- throw new ValidateException($errorMsg);
92
- }
93
- }
94
-
95
- public static function absPathLooksSaneExistsAndIsDir(
96
- $path,
97
- $errorMsg = 'Path points to a file (it should point to a directory)'
98
- ) {
99
- self::absPathLooksSaneAndExists($path);
100
- if (!is_dir($path)) {
101
- throw new ValidateException($errorMsg);
102
- }
103
- }
104
-
105
- public static function absPathLooksSaneExistsAndIsFile(
106
- $path,
107
- $errorMsg = 'Path points to a directory (it should not do that)'
108
- ) {
109
- self::absPathLooksSaneAndExists($path, 'File does not exist');
110
- if (@is_dir($path)) {
111
- throw new ValidateException($errorMsg);
112
- }
113
- }
114
-
115
- public static function absPathLooksSaneExistsAndIsNotDir(
116
- $path,
117
- $errorMsg = 'Path points to a directory (it should point to a file)'
118
- ) {
119
- self::absPathLooksSaneExistsAndIsFile($path, $errorMsg);
120
- }
121
-
122
-
123
- public static function isString($string, $errorMsg = 'Not a string')
124
- {
125
- if (!is_string($string)) {
126
- throw new ValidateException($errorMsg);
127
- }
128
- }
129
-
130
- public static function pregMatch($pattern, $subject, $errorMsg = 'Does not match expected pattern')
131
- {
132
- self::noNUL($subject);
133
- self::isString($subject);
134
- if (!preg_match($pattern, $subject)) {
135
- throw new ValidateException($errorMsg);
136
- }
137
- }
138
-
139
- public static function isJSONArray($json, $errorMsg = 'Not a JSON array')
140
- {
141
- self::noNUL($json);
142
- self::isString($json);
143
- self::notEmpty($json);
144
- if ((strpos($json, '[') !== 0) || (!is_array(json_decode($json)))) {
145
- throw new ValidateException($errorMsg);
146
- }
147
- }
148
-
149
- public static function isJSONObject($json, $errorMsg = 'Not a JSON object')
150
- {
151
- self::noNUL($json);
152
- self::isString($json);
153
- self::notEmpty($json);
154
- if ((strpos($json, '{') !== 0) || (!is_object(json_decode($json)))) {
155
- throw new ValidateException($errorMsg);
156
- }
157
- }
158
-
159
public static function isConverterId($converterId, $errorMsg = 'Not a valid converter id')
160
{
161
- self::pregMatch('#^[a-z]+$#', $converterId, $errorMsg);
162
- if (!in_array($converterId, \WebPExpress\ConvertersHelper::getDefaultConverterNames())) {
163
throw new ValidateException($errorMsg);
164
}
165
}
2
3
namespace WebPExpress;
4
5
+ use \WebPExpress\ConvertersHelper;
6
use \WebPExpress\ValidateException;
7
+ use \WebPExpress\SanityCheck;
8
9
class Validate
10
{
16
}
17
}
18
19
public static function isConverterId($converterId, $errorMsg = 'Not a valid converter id')
20
{
21
+ SanityCheck::pregMatch('#^[a-z]+$#', $converterId, $errorMsg);
22
+ if (!in_array($converterId, ConvertersHelper::getDefaultConverterNames())) {
23
throw new ValidateException($errorMsg);
24
}
25
}
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.14.9
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.14.10
7
* Author: Bjørn Rosell
8
* Author URI: https://www.bitwise-it.dk
9
* License: GPL2
wod/webp-on-demand.php CHANGED
@@ -5,28 +5,25 @@ use \WebPConvert\WebPConvert;
5
use \WebPConvert\Serve\ServeConvertedWebP;
6
use \WebPExpress\ConvertHelperIndependent;
7
use \WebPExpress\Sanitize;
8
- use \WebPExpress\ValidateException;
9
10
class WebPOnDempand
11
{
12
13
private static $docRoot;
14
15
- public static function exitWithError($msg) {
16
header('X-WebP-Express-Error: ' . $msg, true);
17
echo $msg;
18
exit;
19
}
20
21
- //echo $_SERVER["SERVER_SOFTWARE"]; exit;
22
- //stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false
23
-
24
-
25
/**
26
* Get environment variable set with mod_rewrite module
27
* Return false if the environment variable isn't found
28
*/
29
- static function getEnvPassedInRewriteRule($envName) {
30
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
31
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
32
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
@@ -41,168 +38,67 @@ class WebPOnDempand
41
return false;
42
}
43
44
- /**
45
- * Get absolute path to source file.
46
- *
47
- * The path can be passed to this file from the .htaccess file / nginx config in various ways.
48
- *
49
- * @return string Absolute path to source (unsanitized! - call sanitizeAbsFilePath immidiately after calling this method)
50
- */
51
- static function getSourceUnsanitized($docRoot, $wodOptions) {
52
-
53
- // First check if it is in an environment variable - thats the safest way
54
- $source = self::getEnvPassedInRewriteRule('REQFN');
55
- if ($source !== false) {
56
- return $source;
57
- }
58
-
59
- // Then header
60
- if (isset($wodOptions['base-htaccess-on-these-capability-tests'])) {
61
- $capTests = $wodOptions['base-htaccess-on-these-capability-tests'];
62
- $passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
63
- $passThrougEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
64
- } else {
65
- $passThroughHeaderDefinitelyUnavailable = false;
66
- $passThrougEnvVarDefinitelyAvailable = false;
67
- }
68
- if ((!$passThrougEnvVarDefinitelyAvailable) && (!$passThroughHeaderDefinitelyUnavailable)) {
69
- if (isset($_SERVER['HTTP_REQFN'])) {
70
- return $_SERVER['HTTP_REQFN'];
71
- }
72
- }
73
-
74
- // Then querystring (relative path)
75
- $srcRel = '';
76
- if (isset($_GET['xsource-rel'])) {
77
- $srcRel = substr($_GET['xsource-rel'], 1);
78
- } elseif (isset($_GET['source-rel'])) {
79
- $srcRel = $_GET['source-rel'];
80
- }
81
- if ($srcRel != '') {
82
- /*
83
- if (isset($_GET['source-rel-filter'])) {
84
- if ($_GET['source-rel-filter'] == 'discard-parts-before-wp-content') {
85
- $parts = explode('/', $srcRel);
86
- $wp_content = isset($_GET['wp-content']) ? $_GET['wp-content'] : 'wp-content';
87
-
88
- if (in_array($wp_content, $parts)) {
89
- foreach($parts as $index => $part) {
90
- if($part !== $wp_content) {
91
- unset($parts[$index]);
92
- } else {
93
- break;
94
- }
95
- }
96
- $srcRel = implode('/', $parts);
97
- }
98
- }
99
- }*/
100
- return $docRoot . '/' . $srcRel;
101
- }
102
-
103
- // Then querystring (full path) - But only on Nginx (our Apache .htaccess rules never passes absolute url)
104
- if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) {
105
-
106
- // On Nginx, we allow passing absolute path
107
- if (isset($_GET['xsource'])) {
108
- return substr($_GET['xsource'], 1); // No url decoding needed as $_GET is already decoded
109
- } elseif (isset($_GET['source'])) {
110
- return $_GET['source'];
111
- }
112
113
- }
114
-
115
- // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
116
- // correct result in all setups (ie "folder method 1")
117
- $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
118
- //$docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
119
- $source = Sanitize::removeNUL($docRoot . urldecode($requestUriNoQS));
120
- if (@file_exists($source)) {
121
- return $source;
122
- }
123
-
124
- // No luck whatsoever!
125
- self::exitWithError('webp-on-demand.php was not passed any filename to convert');
126
- }
127
-
128
- private static function getWpContentRelUnsanitized() {
129
- // Passed in env variable?
130
- $wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
131
- if ($wpContentDirRel !== false) {
132
- return $wpContentDirRel;
133
- }
134
135
- // Passed in QS?
136
- if (isset($_GET['wp-content'])) {
137
- return $_GET['wp-content'];
138
- }
139
140
- // In case above fails, fall back to standard location
141
- return 'wp-content';
142
- }
143
144
- /*
145
- static function registerAutoload()
146
- {
147
- define('WEBPEXPRESS_PLUGIN_DIR', __DIR__);
148
149
- // Autoload WebPExpress classes
150
- spl_autoload_register('webpexpress_autoload');
151
- function webpexpress_autoload($class) {
152
- if (strpos($class, 'WebPExpress\\') === 0) {
153
- require_once WEBPEXPRESS_PLUGIN_DIR . '/lib/classes/' . substr($class, 12) . '.php';
154
}
155
- }
156
- }*/
157
158
- static function process() {
159
-
160
- include_once "../lib/classes/ConvertHelperIndependent.php";
161
- include_once __DIR__ . '/../lib/classes/Sanitize.php';
162
- include_once __DIR__ . '/../lib/classes/Validate.php';
163
- include_once __DIR__ . '/../lib/classes/ValidateException.php';
164
-
165
- // Validate!
166
- // ----------
167
168
- try {
169
170
- // Validate DOCUMENT_ROOT
171
- // ----------------------
172
- $validating = 'DOCUMENT_ROOT';
173
- $realPathResult = realpath(Sanitize::removeNUL($_SERVER["DOCUMENT_ROOT"]));
174
- if ($realPathResult === false) {
175
- throw new ValidateException('Cannot find document root');
176
}
177
- $docRoot = rtrim($realPathResult, '/');
178
- Validate::absPathLooksSaneExistsAndIsDir($docRoot);
179
- $docRoot = $docRoot;
180
-
181
182
- // Validate WebP Express content dir
183
// ---------------------------------
184
- $validating = 'WebP Express content dir';
185
- $webExpressContentDirAbs = ConvertHelperIndependent::sanitizeAbsFilePath(
186
- $docRoot . '/' . self::getWpContentRelUnsanitized() . '/webp-express'
187
- );
188
- Validate::absPathLooksSaneExistsAndIsDir($webExpressContentDirAbs);
189
190
191
- // Validate config file name
192
// ---------------------------------
193
- $validating = 'config file';
194
- $configFilename = $webExpressContentDirAbs . '/config/wod-options.json';
195
- Validate::absPathLooksSaneExistsAndIsFile($configFilename);
196
197
198
- // Validate config file
199
// --------------------
200
$configLoadResult = file_get_contents($configFilename);
201
if ($configLoadResult === false) {
202
throw new ValidateException('Cannot open config file');
203
}
204
- Validate::isJSONObject($configLoadResult);
205
- $json = $configLoadResult;
206
$options = json_decode($json, true);
207
$wodOptions = $options['wod'];
208
$serveOptions = $options['webp-convert'];
@@ -212,23 +108,68 @@ class WebPOnDempand
212
213
// Validate that WebPExpress was configured to redirect to this conversion script
214
// ------------------------------------------------------------------------------
215
- $validating = 'settings';
216
if (!isset($wodOptions['enable-redirection-to-converter']) || ($wodOptions['enable-redirection-to-converter'] === false)) {
217
throw new ValidateException('Redirection to conversion script is not enabled');
218
}
219
220
221
- // Validate source (the image to be converted)
222
// --------------------------------------------
223
- $validating = 'source';
224
- $source = Sanitize::removeNUL(self::getSourceUnsanitized($docRoot, $options['wod']));
225
- Validate::absPathLooksSaneExistsAndIsFile($source);
226
- //echo $source; exit;
227
-
228
229
- // Validate destination path
230
// --------------------------------------------
231
- $validating = 'destination path';
232
$destination = ConvertHelperIndependent::getDestination(
233
$source,
234
$wodOptions['destination-folder'],
@@ -236,23 +177,18 @@ class WebPOnDempand
236
$webExpressContentDirAbs,
237
$docRoot . '/' . $wodOptions['paths']['uploadDirRel']
238
);
239
- Validate::absPathLooksSane($destination);
240
- //echo $destination; exit;
241
242
} catch (ValidateException $e) {
243
- self::exitWithError('failed validating ' . $validating . ': '. $e->getMessage());
244
}
245
246
- /*
247
- if ($wodOptions['forward-query-string']) {
248
- if (isset($_GET['debug'])) {
249
- $serveOptions['show-report'] = true;
250
- }
251
- if (isset($_GET['reconvert'])) {
252
- $serveOptions['reconvert'] = true;
253
- }
254
- }*/
255
-
256
if (isset($wodOptions['success-response']) && ($wodOptions['success-response'] == 'original')) {
257
$serveOptions['serve-original'] = true;
258
$serveOptions['serve-image']['headers']['vary-accept'] = false;
@@ -260,9 +196,6 @@ class WebPOnDempand
260
$serveOptions['serve-image']['headers']['vary-accept'] = true;
261
}
262
263
- //echo '<pre>' . print_r($serveOptions, true) . '</pre>'; exit;
264
- //$serveOptions['show-report'] = true;
265
-
266
ConvertHelperIndependent::serveConverted(
267
$source,
268
$destination,
5
use \WebPConvert\Serve\ServeConvertedWebP;
6
use \WebPExpress\ConvertHelperIndependent;
7
use \WebPExpress\Sanitize;
8
+ use \WebPExpress\SanityCheck;
9
+ use \WebPExpress\SanityException;
10
11
class WebPOnDempand
12
{
13
14
private static $docRoot;
15
16
+ private static function exitWithError($msg) {
17
header('X-WebP-Express-Error: ' . $msg, true);
18
echo $msg;
19
exit;
20
}
21
22
/**
23
* Get environment variable set with mod_rewrite module
24
* Return false if the environment variable isn't found
25
*/
26
+ private static function getEnvPassedInRewriteRule($envName) {
27
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
28
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
29
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
38
return false;
39
}
40
41
+ public static function process() {
42
43
+ include_once __DIR__ . "/../lib/classes/ConvertHelperIndependent.php";
44
+ include_once __DIR__ . '/../lib/classes/Sanitize.php';
45
+ include_once __DIR__ . '/../lib/classes/SanityCheck.php';
46
+ include_once __DIR__ . '/../lib/classes/SanityException.php';
47
48
+ // Check input
49
+ // --------------
50
51
+ try {
52
53
+ // Check DOCUMENT_ROOT
54
+ // ----------------------
55
+ $checking = 'DOCUMENT_ROOT';
56
+ $docRoot = SanityCheck::absPath($_SERVER["DOCUMENT_ROOT"]);
57
58
+ // Use realpath to expand symbolic links and check if it exists
59
+ $docRoot = realpath($docRoot);
60
+ if ($docRoot === false) {
61
+ throw new SanityException('Cannot find document root');
62
}
63
+ $docRoot = rtrim($docRoot, '/');
64
+ $docRoot = SanityCheck::absPathExistsAndIsDir($docRoot);
65
66
+ // Check wp-content
67
+ // ----------------------
68
69
+ // Passed in env variable?
70
+ $wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
71
+ if ($wpContentDirRel === false) {
72
73
+ // Passed in QS?
74
+ if (isset($_GET['wp-content'])) {
75
+ $wpContentDirRel = SanityCheck::pathWithoutDirectoryTraversal($_GET['wp-content']);
76
+ } else {
77
+ // In case above fails, fall back to standard location
78
+ $wpContentDirRel = 'wp-content';
79
+ }
80
}
81
82
+ // Check WebP Express content dir
83
// ---------------------------------
84
+ $checking = 'WebP Express content dir';
85
+ $webExpressContentDirAbs = SanityCheck::absPathExistsAndIsDir($docRoot . '/' . $wpContentDirRel . '/webp-express');
86
87
88
+ // Check config file name
89
// ---------------------------------
90
+ $checking = 'config file';
91
+ $configFilename = SanityCheck::absPathExistsAndIsFile($webExpressContentDirAbs . '/config/wod-options.json');
92
93
94
+ // Check config file
95
// --------------------
96
$configLoadResult = file_get_contents($configFilename);
97
if ($configLoadResult === false) {
98
throw new ValidateException('Cannot open config file');
99
}
100
+ $json = SanityCheck::isJSONObject($configLoadResult);
101
+
102
$options = json_decode($json, true);
103
$wodOptions = $options['wod'];
104
$serveOptions = $options['webp-convert'];
108
109
// Validate that WebPExpress was configured to redirect to this conversion script
110
// ------------------------------------------------------------------------------
111
+ $checking = 'settings';
112
if (!isset($wodOptions['enable-redirection-to-converter']) || ($wodOptions['enable-redirection-to-converter'] === false)) {
113
throw new ValidateException('Redirection to conversion script is not enabled');
114
}
115
116
117
+ // Check source (the image to be converted)
118
// --------------------------------------------
119
+ $checking = 'source';
120
+
121
+ // Check if it is in an environment variable
122
+ $source = self::getEnvPassedInRewriteRule('REQFN');
123
+ if ($source !== false) {
124
+ $source = SanityCheck::absPathExistsAndIsFile($source);
125
+ } else {
126
+ // Check if it is in header (but only if .htaccess was configured to send in header)
127
+ if (isset($wodOptions['base-htaccess-on-these-capability-tests'])) {
128
+ $capTests = $wodOptions['base-htaccess-on-these-capability-tests'];
129
+ $passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
130
+ $passThrougEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
131
+ } else {
132
+ $passThroughHeaderDefinitelyUnavailable = false;
133
+ $passThrougEnvVarDefinitelyAvailable = false;
134
+ }
135
+ if ((!$passThrougEnvVarDefinitelyAvailable) && (!$passThroughHeaderDefinitelyUnavailable)) {
136
+ if (isset($_SERVER['HTTP_REQFN'])) {
137
+ $source = SanityCheck::absPathExistsAndIsFile($_SERVER['HTTP_REQFN']);
138
+ }
139
+ } else {
140
+ // Check querystring (relative path)
141
+ $srcRel = '';
142
+ if (isset($_GET['xsource-rel'])) {
143
+ $xsrcRel = SanityCheck::noControlChars($_GET['xsource-rel']);
144
+ $srcRel = SanityCheck::pathWithoutDirectoryTraversal(substr($xsrcRel, 1));
145
+ $source = SanityCheck::absPathExistsAndIsFile($docRoot . '/' . $srcRel);
146
+ } else {
147
+ // Then querystring (full path)
148
+ // - But only on Nginx (our Apache .htaccess rules never passes absolute url)
149
+ if (
150
+ (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) &&
151
+ (isset($_GET['source']) || isset($_GET['xsource']))
152
+ ) {
153
+ if (isset($_GET['source'])) {
154
+ $source = SanityCheck::absPathExistsAndIsFile($_GET['source']);
155
+ } else {
156
+ $xsrc = SanityCheck::noControlChars($_GET['xsource']);
157
+ $source = SanityCheck::absPathExistsAndIsFile(substr($xsrc, 1));
158
+ }
159
+ } else {
160
+ // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
161
+ // correct result in all setups (ie "folder method 1")
162
+ $srcRel = SanityCheck::pathWithoutDirectoryTraversal(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
163
+ $source = SanityCheck::absPathExistsAndIsFile($docRoot . $srcRel);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ $source = SanityCheck::pathBeginsWith($source, $docRoot . '/');
169
170
+ // Check destination path
171
// --------------------------------------------
172
+ $checking = 'destination path';
173
$destination = ConvertHelperIndependent::getDestination(
174
$source,
175
$wodOptions['destination-folder'],
177
$webExpressContentDirAbs,
178
$docRoot . '/' . $wodOptions['paths']['uploadDirRel']
179
);
180
+ $destination = SanityCheck::absPath($destination);
181
+ $destination = SanityCheck::pregMatch('#\.webp$#', $destination, 'Does not end with .webp');
182
+ $destination = SanityCheck::pathBeginsWith($destination, $docRoot . '/');
183
184
+ } catch (SanityException $e) {
185
+ self::exitWithError('Sanity check failed for ' . $checking . ': '. $e->getMessage());
186
} catch (ValidateException $e) {
187
+ self::exitWithError('Validation failed for ' . $checking . ': '. $e->getMessage());
188
}
189
190
+ // Done with sanitizing, lets get to work!
191
+ // ---------------------------------------
192
if (isset($wodOptions['success-response']) && ($wodOptions['success-response'] == 'original')) {
193
$serveOptions['serve-original'] = true;
194
$serveOptions['serve-image']['headers']['vary-accept'] = false;
196
$serveOptions['serve-image']['headers']['vary-accept'] = true;
197
}
198
199
ConvertHelperIndependent::serveConverted(
200
$source,
201
$destination,
wod/webp-realizer-old.php ADDED
@@ -0,0 +1,278 @@
1
+ <?php
2
+ namespace WebPExpress;
3
+
4
+ use \WebPConvert\WebPConvert;
5
+ use \WebPConvert\Serve\ServeConvertedWebP;
6
+ use \WebPExpress\ConvertHelperIndependent;
7
+ use \WebPExpress\Sanitize;
8
+ use \WebPExpress\ValidateException;
9
+
10
+ class WebPRealizer
11
+ {
12
+
13
+ private static $docRoot;
14
+
15
+ public static function exitWithError($msg) {
16
+ header('X-WebP-Express-Error: ' . $msg, true);
17
+ echo $msg;
18
+ exit;
19
+ }
20
+
21
+ //echo $_SERVER["SERVER_SOFTWARE"]; exit;
22
+ //stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false
23
+
24
+
25
+ /**
26
+ * Get environment variable set with mod_rewrite module
27
+ * Return false if the environment variable isn't found
28
+ */
29
+ static function getEnvPassedInRewriteRule($envName) {
30
+ // Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
31
+ // Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
32
+ // Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
33
+ // We simply look for an environment variable that ends with what we are looking for.
34
+ // (so make sure to make it unique)
35
+ $len = strlen($envName);
36
+ foreach ($_SERVER as $key => $item) {
37
+ if (substr($key, -$len) == $envName) {
38
+ return $item;
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+
44
+ /**
45
+ * Get absolute path to destination file.
46
+ *
47
+ * The path can be passed to this file from the .htaccess file / nginx config in various ways.
48
+ *
49
+ * @return string Absolute path to destination (unsanitized! - call sanitizeAbsFilePath immidiately after calling this method)
50
+ */
51
+ static function getDestinationUnsanitized($docRoot) {
52
+
53
+ // First check if it is in an environment variable - thats the safest way
54
+ $destinationRel = self::getEnvPassedInRewriteRule('DESTINATIONREL');
55
+ if ($destinationRel !== false) {
56
+ return $docRoot . '/' . $destinationRel;
57
+ }
58
+
59
+ // Next, check querystring (relative path)
60
+ $destinationRel = '';
61
+ if (isset($_GET['xdestination-rel'])) {
62
+ $destinationRel = substr(Sanitize::removeNUL($_GET['xdestination-rel']), 1);
63
+ } elseif (isset($_GET['destination-rel'])) {
64
+ $destinationRel = Sanitize::removeNUL($_GET['destination-rel']);
65
+ }
66
+ if ($destinationRel != '') {
67
+ /*
68
+ if (isset($_GET['source-rel-filter'])) {
69
+ if (Sanitize::removeNUL($_GET['source-rel-filter']) == 'discard-parts-before-wp-content') {
70
+ $parts = explode('/', $destinationRel);
71
+ $wp_content = isset($_GET['wp-content']) ? Sanitize::removeNUL($_GET['wp-content']) : 'wp-content';
72
+
73
+ if (in_array($wp_content, $parts)) {
74
+ foreach($parts as $index => $part) {
75
+ if($part !== $wp_content) {
76
+ unset($parts[$index]);
77
+ } else {
78
+ break;
79
+ }
80
+ }
81
+ $destinationRel = implode('/', $parts);
82
+ }
83
+ }
84
+ }*/
85
+ return $docRoot . '/' . $destinationRel;
86
+ }
87
+
88
+ // Then querystring (full path) - But only on Nginx (our Apache .htaccess rules never passes absolute url)
89
+ if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) {
90
+ if (isset($_GET['xdestination'])) {
91
+ return substr($_GET['xdestination'], 1); // No url decoding needed as $_GET is already decoded
92
+ } elseif (isset($_GET['destination'])) {
93
+ return $_GET['destination'];
94
+ }
95
+ }
96
+
97
+ // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
98
+ // correct result in all setups (ie "folder method 1")
99
+ $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
100
+ $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
101
+ $dest = $docRoot . urldecode($requestUriNoQS);
102
+ return $dest;
103
+ }
104
+
105
+
106
+ static function getWpContentRelUnsanitized() {
107
+ // Passed in env variable?
108
+ $wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
109
+ if ($wpContentDirRel !== false) {
110
+ return $wpContentDirRel;
111
+ }
112
+
113
+ // Passed in QS?
114
+ if (isset($_GET['wp-content'])) {
115
+ return $_GET['wp-content'];
116
+ }
117
+
118
+ // In case above fails, fall back to standard location
119
+ return 'wp-content';
120
+ }
121
+
122
+ /*
123
+ static function registerAutoload()
124
+ {
125
+ define('WEBPEXPRESS_PLUGIN_DIR', __DIR__);
126
+
127
+ // Autoload WebPExpress classes
128
+ spl_autoload_register('webpexpress_autoload');
129
+ function webpexpress_autoload($class) {
130
+ if (strpos($class, 'WebPExpress\\') === 0) {
131
+ require_once WEBPEXPRESS_PLUGIN_DIR . '/lib/classes/' . substr($class, 12) . '.php';
132
+ }
133
+ }
134
+ }*/
135
+
136
+ static function process() {
137
+
138
+ include_once "../lib/classes/ConvertHelperIndependent.php";
139
+ include_once __DIR__ . '/../lib/classes/Sanitize.php';
140
+ include_once __DIR__ . '/../lib/classes/Validate.php';
141
+ include_once __DIR__ . '/../lib/classes/ValidateException.php';
142
+
143
+ // Validate!
144
+ // ----------
145
+
146
+ try {
147
+
148
+ // Validate DOCUMENT_ROOT
149
+ // ----------------------
150
+ $validating = 'DOCUMENT_ROOT';
151
+ $realPathResult = realpath(Sanitize::removeNUL($_SERVER["DOCUMENT_ROOT"]));
152
+ if ($realPathResult === false) {
153
+ throw new ValidateException('Cannot find document root');
154
+ }
155
+ $docRoot = rtrim($realPathResult, '/');
156
+ SanityCheck::absPathExistsAndIsDir($docRoot);
157
+ $docRoot = $docRoot;
158
+
159
+
160
+ // Validate WebP Express content dir
161
+ // ---------------------------------
162
+ $validating = 'WebP Express content dir';
163
+ $webExpressContentDirAbs = ConvertHelperIndependent::sanitizeAbsFilePath(
164
+ $docRoot . '/' . self::getWpContentRelUnsanitized() . '/webp-express'
165
+ );
166
+ SanityCheck::absPathExistsAndIsDir($webExpressContentDirAbs);
167
+
168
+
169
+ // Validate config file name
170
+ // ---------------------------------
171
+ $validating = 'config file';
172
+ $configFilename = $webExpressContentDirAbs . '/config/wod-options.json';
173
+ SanityCheck::absPathExistsAndIsFile($configFilename);
174
+
175
+
176
+ // Validate config file
177
+ // --------------------
178
+ $configLoadResult = file_get_contents($configFilename);
179
+ if ($configLoadResult === false) {
180
+ throw new ValidateException('Cannot open config file');
181
+ }
182
+ SanityCheck::isJSONObject($configLoadResult);
183
+ $json = $configLoadResult;
184
+ $options = json_decode($json, true);
185
+ $wodOptions = $options['wod'];
186
+ $serveOptions = $options['webp-convert'];
187
+ $convertOptions = &$serveOptions['convert'];
188
+ //echo '<pre>' . print_r($wodOptions, true) . '</pre>'; exit;
189
+
190
+
191
+ // Validate that WebPExpress was configured to redirect to this conversion script
192
+ // ------------------------------------------------------------------------------
193
+ $validating = 'settings';
194
+ if (!isset($wodOptions['enable-redirection-to-webp-realizer']) || ($wodOptions['enable-redirection-to-webp-realizer'] === false)) {
195
+ throw new ValidateException('Redirection to conversion script is not enabled');
196
+ }
197
+
198
+
199
+ // Validate destination (the image that was requested, but has not been converted yet)
200
+ // ------------------------------------------------------------------------------------
201
+ $validating = 'destination path';
202
+ $destination = Sanitize::removeNUL(self::getDestinationUnsanitized($docRoot));
203
+ SanityCheck::absPath($destination);
204
+ //echo $destination; exit;
205
+
206
+
207
+ // Validate source path
208
+ // --------------------------------------------
209
+ $validating = 'source path';
210
+ $source = ConvertHelperIndependent::findSource(
211
+ $destination,
212
+ $wodOptions['destination-folder'],
213
+ $wodOptions['destination-extension'],
214
+ $webExpressContentDirAbs
215
+ );
216
+
217
+ if ($source === false) {
218
+ header('X-WebP-Express-Error: webp-realizer.php could not find an existing jpg or png that corresponds to the webp requested', true);
219
+
220
+ $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
221
+ header($protocol . " 404 Not Found");
222
+ die();
223
+ //echo 'destination requested:<br><i>' . $destination . '</i>';
224
+ }
225
+ SanityCheck::absPathExistsAndIsFile($source);
226
+ //echo $source; exit;
227
+
228
+ } catch (ValidateException $e) {
229
+ self::exitWithError('failed validating ' . $validating . ': '. $e->getMessage());
230
+ }
231
+
232
+ /*
233
+ if ($wodOptions['forward-query-string']) {
234
+ if (isset($_GET['debug'])) {
235
+ $serveOptions['show-report'] = true;
236
+ }
237
+ if (isset($_GET['reconvert'])) {
238
+ $serveOptions['reconvert'] = true;
239
+ }
240
+ }*/
241
+
242
+ $serveOptions['add-vary-header'] = false;
243
+ $serveOptions['fail'] = '404';
244
+ $serveOptions['fail-when-fail-fails'] = '404';
245
+ $serveOptions['serve-image']['headers']['vary-accept'] = false;
246
+
247
+ /*
248
+ function aboutToServeImageCallBack($servingWhat, $whyServingThis, $obj) {
249
+ // Redirect to same location.
250
+ header('Location: ?fresh' , 302);
251
+ return false; // tell webp-convert not to serve!
252
+ }
253
+ */
254
+
255
+ //echo '<pre>' . print_r($serveOptions, true) . '</pre>'; exit;
256
+ //$serveOptions['show-report'] = true;
257
+
258
+ ConvertHelperIndependent::serveConverted(
259
+ $source,
260
+ $destination,
261
+ $serveOptions,
262
+ $webExpressContentDirAbs . '/log',
263
+ 'Conversion triggered with the conversion script (wod/webp-realizer.php)'
264
+ );
265
+
266
+ }
267
+ }
268
+
269
+ // Protect against directly accessing webp-on-demand.php
270
+ // Only protect on Apache. We know for sure that the method is not reliable on nginx. We have not tested on litespeed yet, so we dare not.
271
+ if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
272
+ if (strpos($_SERVER['REQUEST_URI'], 'webp-realizer.php') !== false) {
273
+ WebPRealizer::exitWithError('It seems you are visiting this file (plugins/webp-express/wod/webp-realizer.php) directly. We do not allow this.');
274
+ exit;
275
+ }
276
+ }
277
+
278
+ WebPRealizer::process();
wod/webp-realizer.php CHANGED
@@ -5,28 +5,25 @@ use \WebPConvert\WebPConvert;
5
use \WebPConvert\Serve\ServeConvertedWebP;
6
use \WebPExpress\ConvertHelperIndependent;
7
use \WebPExpress\Sanitize;
8
- use \WebPExpress\ValidateException;
9
10
class WebPRealizer
11
{
12
13
private static $docRoot;
14
15
- public static function exitWithError($msg) {
16
header('X-WebP-Express-Error: ' . $msg, true);
17
echo $msg;
18
exit;
19
}
20
21
- //echo $_SERVER["SERVER_SOFTWARE"]; exit;
22
- //stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false
23
-
24
-
25
/**
26
* Get environment variable set with mod_rewrite module
27
* Return false if the environment variable isn't found
28
*/
29
- static function getEnvPassedInRewriteRule($envName) {
30
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
31
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
32
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
@@ -41,146 +38,67 @@ class WebPRealizer
41
return false;
42
}
43
44
- /**
45
- * Get absolute path to destination file.
46
- *
47
- * The path can be passed to this file from the .htaccess file / nginx config in various ways.
48
- *
49
- * @return string Absolute path to destination (unsanitized! - call sanitizeAbsFilePath immidiately after calling this method)
50
- */
51
- static function getDestinationUnsanitized($docRoot) {
52
-
53
- // First check if it is in an environment variable - thats the safest way
54
- $destinationRel = self::getEnvPassedInRewriteRule('DESTINATIONREL');
55
- if ($destinationRel !== false) {
56
- return $docRoot . '/' . $destinationRel;
57
- }
58
-
59
- // Next, check querystring (relative path)
60
- $destinationRel = '';
61
- if (isset($_GET['xdestination-rel'])) {
62
- $destinationRel = substr(Sanitize::removeNUL($_GET['xdestination-rel']), 1);
63
- } elseif (isset($_GET['destination-rel'])) {
64
- $destinationRel = Sanitize::removeNUL($_GET['destination-rel']);
65
- }
66
- if ($destinationRel != '') {
67
- /*
68
- if (isset($_GET['source-rel-filter'])) {
69
- if (Sanitize::removeNUL($_GET['source-rel-filter']) == 'discard-parts-before-wp-content') {
70
- $parts = explode('/', $destinationRel);
71
- $wp_content = isset($_GET['wp-content']) ? Sanitize::removeNUL($_GET['wp-content']) : 'wp-content';
72
-
73
- if (in_array($wp_content, $parts)) {
74
- foreach($parts as $index => $part) {
75
- if($part !== $wp_content) {
76
- unset($parts[$index]);
77
- } else {
78
- break;
79
- }
80
- }
81
- $destinationRel = implode('/', $parts);
82
- }
83
- }
84
- }*/
85
- return $docRoot . '/' . $destinationRel;
86
- }
87
88
- // Then querystring (full path) - But only on Nginx (our Apache .htaccess rules never passes absolute url)
89
- if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) {
90
- if (isset($_GET['xdestination'])) {
91
- return substr($_GET['xdestination'], 1); // No url decoding needed as $_GET is already decoded
92
- } elseif (isset($_GET['destination'])) {
93
- return $_GET['destination'];
94
- }
95
- }
96
-
97
- // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
98
- // correct result in all setups (ie "folder method 1")
99
- $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
100
- $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
101
- $dest = $docRoot . urldecode($requestUriNoQS);
102
- return $dest;
103
- }
104
-
105
-
106
- static function getWpContentRelUnsanitized() {
107
- // Passed in env variable?
108
- $wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
109
- if ($wpContentDirRel !== false) {
110
- return $wpContentDirRel;
111
- }
112
113
- // Passed in QS?
114
- if (isset($_GET['wp-content'])) {
115
- return $_GET['wp-content'];
116
- }
117
118
- // In case above fails, fall back to standard location
119
- return 'wp-content';
120
- }
121
122
- /*
123
- static function registerAutoload()
124
- {
125
- define('WEBPEXPRESS_PLUGIN_DIR', __DIR__);
126
127
- // Autoload WebPExpress classes
128
- spl_autoload_register('webpexpress_autoload');
129
- function webpexpress_autoload($class) {
130
- if (strpos($class, 'WebPExpress\\') === 0) {
131
- require_once WEBPEXPRESS_PLUGIN_DIR . '/lib/classes/' . substr($class, 12) . '.php';
132
}
133
- }
134
- }*/
135
-
136
- static function process() {
137
138
- include_once "../lib/classes/ConvertHelperIndependent.php";
139
- include_once __DIR__ . '/../lib/classes/Sanitize.php';
140
- include_once __DIR__ . '/../lib/classes/Validate.php';
141
- include_once __DIR__ . '/../lib/classes/ValidateException.php';
142
-
143
- // Validate!
144
- // ----------
145
146
- try {
147
148
- // Validate DOCUMENT_ROOT
149
- // ----------------------
150
- $validating = 'DOCUMENT_ROOT';
151
- $realPathResult = realpath(Sanitize::removeNUL($_SERVER["DOCUMENT_ROOT"]));
152
- if ($realPathResult === false) {
153
- throw new ValidateException('Cannot find document root');
154
}
155
- $docRoot = rtrim($realPathResult, '/');
156
- Validate::absPathLooksSaneExistsAndIsDir($docRoot);
157
- $docRoot = $docRoot;
158
159
-
160
- // Validate WebP Express content dir
161
// ---------------------------------
162
- $validating = 'WebP Express content dir';
163
- $webExpressContentDirAbs = ConvertHelperIndependent::sanitizeAbsFilePath(
164
- $docRoot . '/' . self::getWpContentRelUnsanitized() . '/webp-express'
165
- );
166
- Validate::absPathLooksSaneExistsAndIsDir($webExpressContentDirAbs);
167
168
169
- // Validate config file name
170
// ---------------------------------
171
- $validating = 'config file';
172
- $configFilename = $webExpressContentDirAbs . '/config/wod-options.json';
173
- Validate::absPathLooksSaneExistsAndIsFile($configFilename);
174
175
176
- // Validate config file
177
// --------------------
178
$configLoadResult = file_get_contents($configFilename);
179
if ($configLoadResult === false) {
180
throw new ValidateException('Cannot open config file');
181
}
182
- Validate::isJSONObject($configLoadResult);
183
- $json = $configLoadResult;
184
$options = json_decode($json, true);
185
$wodOptions = $options['wod'];
186
$serveOptions = $options['webp-convert'];
@@ -190,23 +108,56 @@ class WebPRealizer
190
191
// Validate that WebPExpress was configured to redirect to this conversion script
192
// ------------------------------------------------------------------------------
193
- $validating = 'settings';
194
- if (!isset($wodOptions['enable-redirection-to-webp-realizer']) || ($wodOptions['enable-redirection-to-webp-realizer'] === false)) {
195
throw new ValidateException('Redirection to conversion script is not enabled');
196
}
197
198
199
- // Validate destination (the image that was requested, but has not been converted yet)
200
// ------------------------------------------------------------------------------------
201
- $validating = 'destination path';
202
- $destination = Sanitize::removeNUL(self::getDestinationUnsanitized($docRoot));
203
- Validate::absPathLooksSane($destination);
204
- //echo $destination; exit;
205
206
207
// Validate source path
208
// --------------------------------------------
209
- $validating = 'source path';
210
$source = ConvertHelperIndependent::findSource(
211
$destination,
212
$wodOptions['destination-folder'],
@@ -215,46 +166,30 @@ class WebPRealizer
215
);
216
217
if ($source === false) {
218
- header('X-WebP-Express-Error: webp-realizer.php could not find an existing jpg or png that corresponds to the webp requested', true);
219
220
$protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
221
header($protocol . " 404 Not Found");
222
die();
223
//echo 'destination requested:<br><i>' . $destination . '</i>';
224
}
225
- Validate::absPathLooksSaneExistsAndIsFile($source);
226
- //echo $source; exit;
227
228
} catch (ValidateException $e) {
229
- self::exitWithError('failed validating ' . $validating . ': '. $e->getMessage());
230
}
231
232
- /*
233
- if ($wodOptions['forward-query-string']) {
234
- if (isset($_GET['debug'])) {
235
- $serveOptions['show-report'] = true;
236
- }
237
- if (isset($_GET['reconvert'])) {
238
- $serveOptions['reconvert'] = true;
239
- }
240
- }*/
241
242
$serveOptions['add-vary-header'] = false;
243
$serveOptions['fail'] = '404';
244
$serveOptions['fail-when-fail-fails'] = '404';
245
$serveOptions['serve-image']['headers']['vary-accept'] = false;
246
247
- /*
248
- function aboutToServeImageCallBack($servingWhat, $whyServingThis, $obj) {
249
- // Redirect to same location.
250
- header('Location: ?fresh' , 302);
251
- return false; // tell webp-convert not to serve!
252
- }
253
- */
254
-
255
- //echo '<pre>' . print_r($serveOptions, true) . '</pre>'; exit;
256
- //$serveOptions['show-report'] = true;
257
-
258
ConvertHelperIndependent::serveConverted(
259
$source,
260
$destination,
@@ -262,7 +197,6 @@ class WebPRealizer
262
$webExpressContentDirAbs . '/log',
263
'Conversion triggered with the conversion script (wod/webp-realizer.php)'
264
);
265
-
266
}
267
}
268
5
use \WebPConvert\Serve\ServeConvertedWebP;
6
use \WebPExpress\ConvertHelperIndependent;
7
use \WebPExpress\Sanitize;
8
+ use \WebPExpress\SanityCheck;
9
+ use \WebPExpress\SanityException;
10
11
class WebPRealizer
12
{
13
14
private static $docRoot;
15
16
+ private static function exitWithError($msg) {
17
header('X-WebP-Express-Error: ' . $msg, true);
18
echo $msg;
19
exit;
20
}
21
22
/**
23
* Get environment variable set with mod_rewrite module
24
* Return false if the environment variable isn't found
25
*/
26
+ private static function getEnvPassedInRewriteRule($envName) {
27
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
28
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
29
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
38
return false;
39
}
40
41
+ public static function process() {
42
43
+ include_once __DIR__ . "/../lib/classes/ConvertHelperIndependent.php";
44
+ include_once __DIR__ . '/../lib/classes/Sanitize.php';
45
+ include_once __DIR__ . '/../lib/classes/SanityCheck.php';
46
+ include_once __DIR__ . '/../lib/classes/SanityException.php';
47
48
+ // Check input
49
+ // --------------
50
51
+ try {
52
53
+ // Check DOCUMENT_ROOT
54
+ // ----------------------
55
+ $checking = 'DOCUMENT_ROOT';
56
+ $docRoot = SanityCheck::absPath($_SERVER["DOCUMENT_ROOT"]);
57
58
+ // Use realpath to expand symbolic links and check if it exists
59
+ $docRoot = realpath($docRoot);
60
+ if ($docRoot === false) {
61
+ throw new SanityException('Cannot find document root');
62
}
63
+ $docRoot = rtrim($docRoot, '/');
64
+ $docRoot = SanityCheck::absPathExistsAndIsDir($docRoot);
65
66
+ // Check wp-content
67
+ // ----------------------
68
69
+ // Passed in env variable?
70
+ $wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
71
+ if ($wpContentDirRel === false) {
72
73
+ // Passed in QS?
74
+ if (isset($_GET['wp-content'])) {
75
+ $wpContentDirRel = SanityCheck::pathWithoutDirectoryTraversal($_GET['wp-content']);
76
+ } else {
77
+ // In case above fails, fall back to standard location
78
+ $wpContentDirRel = 'wp-content';
79
+ }
80
}
81
82
+ // Check WebP Express content dir
83
// ---------------------------------
84
+ $checking = 'WebP Express content dir';
85
+ $webExpressContentDirAbs = SanityCheck::absPathExistsAndIsDir($docRoot . '/' . $wpContentDirRel . '/webp-express');
86
87
88
+ // Check config file name
89
// ---------------------------------
90
+ $checking = 'config file';
91
+ $configFilename = SanityCheck::absPathExistsAndIsFile($webExpressContentDirAbs . '/config/wod-options.json');
92
93
94
+ // Check config file
95
// --------------------
96
$configLoadResult = file_get_contents($configFilename);
97
if ($configLoadResult === false) {
98
throw new ValidateException('Cannot open config file');
99
}
100
+ $json = SanityCheck::isJSONObject($configLoadResult);
101
+
102
$options = json_decode($json, true);
103
$wodOptions = $options['wod'];
104
$serveOptions = $options['webp-convert'];
108
109
// Validate that WebPExpress was configured to redirect to this conversion script
110
// ------------------------------------------------------------------------------
111
+ $checking = 'settings';
112
+ if (!isset($wodOptions['enable-redirection-to-converter']) || ($wodOptions['enable-redirection-to-converter'] === false)) {
113
throw new ValidateException('Redirection to conversion script is not enabled');
114
}
115
116
117
+ // Check destination (the image that was requested, but has not been converted yet)
118
// ------------------------------------------------------------------------------------
119
+ $checking = 'destination path';
120
+
121
+ // Check if it is in an environment variable
122
+ $destRel = self::getEnvPassedInRewriteRule('DESTINATIONREL');
123
+ if ($destRel !== false) {
124
+ $destination = SanityCheck::absPath($docRoot . '/' . $destRel);
125
+ } else {
126
+ // Check querystring (relative path)
127
+ if (isset($_GET['xdestination-rel'])) {
128
+ $xdestRel = SanityCheck::noControlChars($_GET['xdestination-rel']);
129
+ $destRel = SanityCheck::pathWithoutDirectoryTraversal(substr($xdestRel, 1));
130
+ $destination = SanityCheck::absPath($docRoot . '/' . $destRel);
131
+ } else {
132
+
133
+ // Then querystring (full path)
134
+ // - But only on Nginx (our Apache .htaccess rules never passes absolute url)
135
+ if (
136
+ (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) &&
137
+ (isset($_GET['destination']) || isset($_GET['xdestination']))
138
+ ) {
139
+ if (isset($_GET['destination'])) {
140
+ $destination = SanityCheck::absPath($_GET['destination']);
141
+ } else {
142
+ $xdest = SanityCheck::noControlChars($_GET['xdestination']);
143
+ $destination = SanityCheck::absPath(substr($xdest, 1));
144
+ }
145
+ } else {
146
+ // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
147
+ // correct result in all setups (ie "folder method 1")
148
+ $destRel = SanityCheck::pathWithoutDirectoryTraversal(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
149
+ $destination = SanityCheck::absPath($docRoot . $destRel);
150
+ }
151
+ }
152
+ }
153
+
154
+ $destination = SanityCheck::pregMatch('#\.webp$#', $destination, 'Does not end with .webp');
155
+ $destination = SanityCheck::pathBeginsWith($destination, $docRoot . '/');
156
157
158
// Validate source path
159
// --------------------------------------------
160
+ $checking = 'source path';
161
$source = ConvertHelperIndependent::findSource(
162
$destination,
163
$wodOptions['destination-folder'],
166
);
167
168
if ($source === false) {
169
+ header('X-WebP-Express-Error: webp-realizer.php could not find an existing jpg/png that corresponds to the webp requested', true);
170
171
$protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
172
header($protocol . " 404 Not Found");
173
die();
174
//echo 'destination requested:<br><i>' . $destination . '</i>';
175
}
176
+ $source = SanityCheck::absPathExistsAndIsFile($source);
177
+ $source = SanityCheck::pathBeginsWith($source, $docRoot . '/');
178
179
+ } catch (SanityException $e) {
180
+ self::exitWithError('Sanity check failed for ' . $checking . ': '. $e->getMessage());
181
} catch (ValidateException $e) {
182
+ self::exitWithError('Validation failed for ' . $checking . ': '. $e->getMessage());
183
}
184
185
186
+ // Done with sanitizing, lets get to work!
187
+ // ---------------------------------------
188
$serveOptions['add-vary-header'] = false;
189
$serveOptions['fail'] = '404';
190
$serveOptions['fail-when-fail-fails'] = '404';
191
$serveOptions['serve-image']['headers']['vary-accept'] = false;
192
193
ConvertHelperIndependent::serveConverted(
194
$source,
195
$destination,
197
$webExpressContentDirAbs . '/log',
198
'Conversion triggered with the conversion script (wod/webp-realizer.php)'
199
);
200
}
201
}
202