WebP Express - Version 0.14.9

Version Description

(released: 22 jun 2019)

  • Tidied up code
Download this release

Release Info

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

Code changes from version 0.14.8 to 0.14.9

Files changed (39) hide show
  1. README.txt +9 -1
  2. composer.json +1 -1
  3. composer.lock +6 -6
  4. lib/classes/AdminInit.php +2 -2
  5. lib/classes/CachePurge.php +1 -1
  6. lib/classes/Config.php +2 -0
  7. lib/classes/Convert.php +70 -31
  8. lib/classes/ConvertHelperIndependent.php +2 -4
  9. lib/classes/ConvertLog.php +2 -2
  10. lib/classes/ConvertersHelper.php +8 -2
  11. lib/classes/DismissableMessages.php +3 -3
  12. lib/classes/Paths.php +1 -0
  13. lib/classes/Sanitize.php +19 -0
  14. lib/classes/Validate.php +167 -0
  15. lib/classes/ValidateException.php +7 -0
  16. lib/migrate/migrate10.php +68 -0
  17. lib/options/enqueue_scripts.php +2 -2
  18. lib/options/js/{0.14.5 → 0.14.9}/authorized_sites_bak.js +0 -0
  19. lib/options/js/{0.14.5 → 0.14.9}/bulk-convert.js +0 -0
  20. lib/options/js/{0.14.5 → 0.14.9}/converters.js +8 -3
  21. lib/options/js/{0.14.5 → 0.14.9}/das-popup.js +0 -0
  22. lib/options/js/{0.14.5 → 0.14.9}/escapeHTML.js +0 -0
  23. lib/options/js/{0.14.5 → 0.14.9}/image-comparison-slider.js +0 -0
  24. lib/options/js/{0.14.5 → 0.14.9}/page.js +0 -0
  25. lib/options/js/{0.14.5 → 0.14.9}/purge-cache.js +0 -0
  26. lib/options/js/{0.14.5 → 0.14.9}/sortable.min.js +0 -0
  27. lib/options/js/{0.14.5 → 0.14.9}/test-convert.js +0 -0
  28. lib/options/js/{0.14.5 → 0.14.9}/whitelist.js +0 -0
  29. lib/options/page-messages.php +15 -5
  30. lib/options/submit.php +3 -6
  31. vendor/composer/installed.json +6 -6
  32. vendor/rosell-dk/webp-convert/composer.json +1 -0
  33. vendor/rosell-dk/webp-convert/docs/v2.0/converting/options.md +24 -3
  34. vendor/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php +3 -1
  35. vendor/rosell-dk/webp-convert/src/Helpers/PathChecker.php +6 -0
  36. vendor/rosell-dk/webp-convert/src/Serve/Report.php +1 -1
  37. webp-express.php +7 -6
  38. wod/webp-on-demand.php +239 -192
  39. wod/webp-realizer.php +237 -172
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.8
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.8 =
609
  *(released: 21 jun 2019)*
610
 
@@ -839,6 +844,9 @@ For older releases, check out changelog.txt
839
 
840
  == Upgrade Notice ==
841
 
 
 
 
842
  = 0.14.8 =
843
  Tidied up code
844
 
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
 
606
  == Changelog ==
607
 
608
+ = 0.14.9 =
609
+ *(released: 22 jun 2019)*
610
+
611
+ * Tidied up code
612
+
613
  = 0.14.8 =
614
  *(released: 21 jun 2019)*
615
 
844
 
845
  == Upgrade Notice ==
846
 
847
+ = 0.14.9 =
848
+ Tidied up code
849
+
850
  = 0.14.8 =
851
  Tidied up code
852
 
composer.json CHANGED
@@ -4,7 +4,7 @@
4
  "type": "project",
5
  "license": "MIT",
6
  "require": {
7
- "rosell-dk/webp-convert": "^2.1.1",
8
  "rosell-dk/webp-convert-cloud-service": "^2.0.0",
9
  "rosell-dk/dom-util-for-webp": "^0.3.0"
10
  },
4
  "type": "project",
5
  "license": "MIT",
6
  "require": {
7
+ "rosell-dk/webp-convert": "^2.1.3",
8
  "rosell-dk/webp-convert-cloud-service": "^2.0.0",
9
  "rosell-dk/dom-util-for-webp": "^0.3.0"
10
  },
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": "d012f5aa2f2575b563cddf193216562a",
8
  "packages": [
9
  {
10
  "name": "rosell-dk/dom-util-for-webp",
@@ -118,16 +118,16 @@
118
  },
119
  {
120
  "name": "rosell-dk/webp-convert",
121
- "version": "2.1.1",
122
  "source": {
123
  "type": "git",
124
  "url": "https://github.com/rosell-dk/webp-convert.git",
125
- "reference": "cc0ceb81193a0b930b2e7c58886a7f3662fb45c6"
126
  },
127
  "dist": {
128
  "type": "zip",
129
- "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/cc0ceb81193a0b930b2e7c58886a7f3662fb45c6",
130
- "reference": "cc0ceb81193a0b930b2e7c58886a7f3662fb45c6",
131
  "shasum": ""
132
  },
133
  "require": {
@@ -190,7 +190,7 @@
190
  "png",
191
  "png2webp"
192
  ],
193
- "time": "2019-06-21T11:15:37+00:00"
194
  },
195
  {
196
  "name": "rosell-dk/webp-convert-cloud-service",
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": "4b326e154cd42a9b5e0182756cab2642",
8
  "packages": [
9
  {
10
  "name": "rosell-dk/dom-util-for-webp",
118
  },
119
  {
120
  "name": "rosell-dk/webp-convert",
121
+ "version": "2.1.3",
122
  "source": {
123
  "type": "git",
124
  "url": "https://github.com/rosell-dk/webp-convert.git",
125
+ "reference": "959f0515b79a785fa1015c0023c22619f55d6392"
126
  },
127
  "dist": {
128
  "type": "zip",
129
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/959f0515b79a785fa1015c0023c22619f55d6392",
130
+ "reference": "959f0515b79a785fa1015c0023c22619f55d6392",
131
  "shasum": ""
132
  },
133
  "require": {
190
  "png",
191
  "png2webp"
192
  ],
193
+ "time": "2019-06-22T22:24:30+00:00"
194
  },
195
  {
196
  "name": "rosell-dk/webp-convert-cloud-service",
lib/classes/AdminInit.php CHANGED
@@ -30,7 +30,7 @@ class AdminInit
30
  public static function runMigrationIfNeeded()
31
  {
32
  // When an update requires a migration, the number should be increased
33
- define('WEBPEXPRESS_MIGRATION_VERSION', '9');
34
 
35
  if (WEBPEXPRESS_MIGRATION_VERSION != Option::getOption('webp-express-migration-version', 0)) {
36
  // run migration logic
@@ -38,7 +38,7 @@ class AdminInit
38
  }
39
 
40
  // uncomment next line to test-run a migration
41
- //include WEBPEXPRESS_PLUGIN_DIR . '/lib/migrate/migrate9.php';
42
  }
43
 
44
  public static function adminInitHandler()
30
  public static function runMigrationIfNeeded()
31
  {
32
  // When an update requires a migration, the number should be increased
33
+ define('WEBPEXPRESS_MIGRATION_VERSION', '10');
34
 
35
  if (WEBPEXPRESS_MIGRATION_VERSION != Option::getOption('webp-express-migration-version', 0)) {
36
  // run migration logic
38
  }
39
 
40
  // uncomment next line to test-run a migration
41
+ //include WEBPEXPRESS_PLUGIN_DIR . '/lib/migrate/migrate10.php';
42
  }
43
 
44
  public static function adminInitHandler()
lib/classes/CachePurge.php CHANGED
@@ -152,7 +152,7 @@ class CachePurge
152
  wp_die();
153
  }
154
 
155
- $onlyPng = ($_POST['only-png'] == 'true');
156
 
157
  $config = Config::loadConfigAndFix();
158
  $result = self::purge($config, $onlyPng);
152
  wp_die();
153
  }
154
 
155
+ $onlyPng = (sanitize_text_field($_POST['only-png']) == 'true');
156
 
157
  $config = Config::loadConfigAndFix();
158
  $result = self::purge($config, $onlyPng);
lib/classes/Config.php CHANGED
@@ -602,6 +602,8 @@ class Config
602
  // WOD options
603
  // -------------
604
  $wod = [
 
 
605
  'base-htaccess-on-these-capability-tests' => $config['base-htaccess-on-these-capability-tests'],
606
  'destination-extension' => $config['destination-extension'],
607
  'destination-folder' => $config['destination-folder'],
602
  // WOD options
603
  // -------------
604
  $wod = [
605
+ 'enable-redirection-to-converter' => $config['enable-redirection-to-converter'],
606
+ 'enable-redirection-to-webp-realizer' => $config['enable-redirection-to-webp-realizer'],
607
  'base-htaccess-on-these-capability-tests' => $config['base-htaccess-on-these-capability-tests'],
608
  'destination-extension' => $config['destination-extension'],
609
  'destination-folder' => $config['destination-folder'],
lib/classes/Convert.php CHANGED
@@ -1,14 +1,13 @@
1
  <?php
2
 
3
- /*
4
- This class is made to be independent of other classes, and must be kept like that.
5
- It is used by webp-on-demand.php, which does not register an auto loader. It is also used for bulk conversion.
6
- */
7
  namespace WebPExpress;
8
 
9
  use \WebPExpress\ConvertHelperIndependent;
10
  use \WebPExpress\Config;
11
- use \WebpExpress\ConvertersHelper;
 
 
 
12
 
13
  class Convert
14
  {
@@ -74,47 +73,87 @@ class Convert
74
 
75
  public static function processAjaxConvertFile()
76
  {
 
77
  if (!check_ajax_referer('webpexpress-ajax-convert-nonce', 'nonce', false)) {
78
  wp_send_json_error('Invalid security nonce (it has probably expired - try refreshing)');
79
  wp_die();
80
  }
81
 
82
- // No need to sanitize filename. self::convertFile does that for us. And the WebPConvert library also does.
83
- // Also, no need to check mime type as the WebPConvert library also does that (it only allows image/jpeg and image/png)
84
- $filename = $_POST['filename'];
 
 
 
 
 
85
 
86
- if (isset($_POST['config-overrides'])) {
87
- $config = Config::loadConfigAndFix();
88
 
89
- // overrides
90
- $overrides = $_POST['config-overrides'];
91
- $overrides = preg_replace('/\\\\"/', '"', $overrides); // We got crazy encoding, perhaps by jQuery. This cleans it up
92
- $overrides = json_decode($overrides, true);
 
 
 
93
 
94
- $config = array_merge($config, $overrides);
95
 
96
- // single converter
97
- $converter = null;
98
- $convertOptions = null;
99
- if (isset($_POST['converter'])) {
100
- $converter = $_POST['converter'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- // find converter
103
- $c = ConvertersHelper::getConverterById($config, $converter);
104
- if ($c !== false) {
105
 
106
- $convertOptions = Config::generateWodOptionsFromConfigObj($config)['webp-convert']['convert'];
107
- $convertOptions = array_merge($convertOptions, $c['options']);
108
- unset($convertOptions['converters']);
109
 
110
- $config = array_merge($config, $c['options']);
111
- //echo 'options: <pre>' . print_r($convertOptions, true) . '</pre>'; exit;
112
- //echo 'options: <pre>' . print_r($c['options'], true) . '</pre>'; exit;
 
113
  }
114
- }
115
 
116
- $result = self::convertFile($filename, $config, $convertOptions, $converter);
 
 
 
 
 
 
 
 
 
117
 
 
 
 
 
 
 
 
118
  } else {
119
  $result = self::convertFile($filename);
120
  }
1
  <?php
2
 
 
 
 
 
3
  namespace WebPExpress;
4
 
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
 
12
  class Convert
13
  {
73
 
74
  public static function processAjaxConvertFile()
75
  {
76
+
77
  if (!check_ajax_referer('webpexpress-ajax-convert-nonce', 'nonce', false)) {
78
  wp_send_json_error('Invalid security nonce (it has probably expired - try refreshing)');
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
+
120
+
121
+ // Input has been processed, now lets get to work!
122
+ // -----------------------------------------------
123
+ if (isset($configOverrides)) {
124
+ $config = Config::loadConfigAndFix();
125
+
126
 
127
+ // convert using specific converter
128
+ if (!is_null($converterId)) {
 
129
 
130
+ // Merge in the config-overrides (config-overrides only have effect when using a specific converter)
131
+ $config = array_merge($config, $configOverrides);
 
132
 
133
+ $converter = ConvertersHelper::getConverterById($config, $converterId);
134
+ if ($converter === false) {
135
+ wp_send_json_error('Converter could not be loaded');
136
+ wp_die();
137
  }
 
138
 
139
+ // the converter options stored in config.json is not precisely the same as the ones
140
+ // we send to webp-convert.
141
+ // We need to "regenerate" webp-convert options in order to use the ones specified in the config-overrides
142
+ // And we need to merge the general options (such as quality etc) into the option for the specific converter
143
+
144
+ $generalWebpConvertOptions = Config::generateWodOptionsFromConfigObj($config)['webp-convert']['convert'];
145
+ $converterSpecificWebpConvertOptions = $converter['options'];
146
+
147
+ $webpConvertOptions = array_merge($generalWebpConvertOptions, $converterSpecificWebpConvertOptions);
148
+ unset($webpConvertOptions['converters']);
149
 
150
+ // what is this? - I forgot why!
151
+ //$config = array_merge($config, $converter['options']);
152
+ $result = self::convertFile($filename, $config, $webpConvertOptions, $converterId);
153
+
154
+ } else {
155
+ $result = self::convertFile($filename, $config);
156
+ }
157
  } else {
158
  $result = self::convertFile($filename);
159
  }
lib/classes/ConvertHelperIndependent.php CHANGED
@@ -182,12 +182,10 @@ class ConvertHelperIndependent
182
  */
183
  private static function findSourceMingled($destination, $destinationExt)
184
  {
185
- global $options;
186
- global $destination;
187
  if ($destinationExt == 'append') {
188
  $source = preg_replace('/\\.(webp)$/', '', $destination);
189
  } else {
190
- $source = preg_replace('/\\.webp$/', '.jpg', $destination);
191
  if (!@file_exists($source)) {
192
  $source = preg_replace('/\\.webp$/', '.jpeg', $destination);
193
  }
@@ -270,7 +268,7 @@ APACHE
270
 
271
  $text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
272
 
273
- $text = 'WebP Express 0.14.8. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
274
 
275
  $logFile = self::getLogFilename($source, $logDir);
276
 
182
  */
183
  private static function findSourceMingled($destination, $destinationExt)
184
  {
 
 
185
  if ($destinationExt == 'append') {
186
  $source = preg_replace('/\\.(webp)$/', '', $destination);
187
  } else {
188
+ $source = preg_replace('#\\.webp$#', '.jpg', $destination);
189
  if (!@file_exists($source)) {
190
  $source = preg_replace('/\\.webp$/', '.jpeg', $destination);
191
  }
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
 
lib/classes/ConvertLog.php CHANGED
@@ -13,14 +13,14 @@ class ConvertLog
13
  wp_send_json_error('Invalid security nonce (it has probably expired - try refreshing)');
14
  wp_die();
15
  }
16
- $source = $_POST['source'];
17
 
18
- // We need to be absolute certain that this feature can be misused.
19
  // - so disabling until I get the time...
20
 
21
  $msg = 'This feature is on the road map...';
22
  echo json_encode($msg, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
23
  /*
 
24
  $logFile = ConvertHelperIndependent::getLogFilename($source, Paths::getLogDirAbs());
25
  $msg = 'Log file: <i>' . $logFile . '</i><br><br><hr>';
26
 
13
  wp_send_json_error('Invalid security nonce (it has probably expired - try refreshing)');
14
  wp_die();
15
  }
 
16
 
17
+ // We need to be absolute certain that this feature cannot be misused.
18
  // - so disabling until I get the time...
19
 
20
  $msg = 'This feature is on the road map...';
21
  echo json_encode($msg, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
22
  /*
23
+ $source = sanitize_text_field($_POST['source']);
24
  $logFile = ConvertHelperIndependent::getLogFilename($source, Paths::getLogDirAbs());
25
  $msg = 'Log file: <i>' . $logFile . '</i><br><br><hr>';
26
 
lib/classes/ConvertersHelper.php CHANGED
@@ -10,7 +10,6 @@ class ConvertersHelper
10
  'try-common-system-paths' => true,
11
  'try-supplied-binary-for-os' => true,
12
  'method' => 6,
13
- 'size-in-percentage' => null,
14
  'low-memory' => true,
15
  'command-line-options' => '',
16
  ]],
@@ -35,7 +34,14 @@ class ConvertersHelper
35
 
36
  public static function getDefaultConverterNames()
37
  {
38
- return array_column(self::$defaultConverters, 'converter');
 
 
 
 
 
 
 
39
  }
40
 
41
  public static function getConverterNames($converters)
10
  'try-common-system-paths' => true,
11
  'try-supplied-binary-for-os' => true,
12
  'method' => 6,
 
13
  'low-memory' => true,
14
  'command-line-options' => '',
15
  ]],
34
 
35
  public static function getDefaultConverterNames()
36
  {
37
+ $availableConverterIDs = [];
38
+ foreach (self::$defaultConverters as $converter) {
39
+ $availableConverterIDs[] = $converter['converter'];
40
+ }
41
+ return $availableConverterIDs;
42
+
43
+ // PS: In a couple of years:
44
+ //return array_column(self::$defaultConverters, 'converter');
45
  }
46
 
47
  public static function getConverterNames($converters)
lib/classes/DismissableMessages.php CHANGED
@@ -75,10 +75,10 @@ class DismissableMessages
75
 
76
  public static function processAjaxDismissMessage() {
77
  /*
78
- We have no security nonce here. Dismissing a message is not harmful and dismissMessage($id) do anything harmful, no matter what you
79
- send in the "id"
80
  */
81
- $id = $_POST['id'];
82
  self::dismissMessage($id);
83
  }
84
 
75
 
76
  public static function processAjaxDismissMessage() {
77
  /*
78
+ We have no security nonce here
79
+ Dismissing a message is not harmful and dismissMessage($id) do anything harmful, no matter what you send in the "id"
80
  */
81
+ $id = sanitize_text_field($_POST['id']);
82
  self::dismissMessage($id);
83
  }
84
 
lib/classes/Paths.php CHANGED
@@ -400,6 +400,7 @@ APACHE
400
  public static function getWodUrlPath()
401
  {
402
  return self::getPluginUrlPath() . '/wod/webp-on-demand.php';
 
403
  }
404
 
405
  public static function getWebPRealizerUrlPath()
400
  public static function getWodUrlPath()
401
  {
402
  return self::getPluginUrlPath() . '/wod/webp-on-demand.php';
403
+ //return self::getHomeUrlPath() . '/webp-on-demand';
404
  }
405
 
406
  public static function getWebPRealizerUrlPath()
lib/classes/Sanitize.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class Sanitize
6
+ {
7
+
8
+ /**
9
+ * The NUL character is a demon, because it can be used to bypass other tests
10
+ * See https://st-g.de/2011/04/doing-filename-checks-securely-in-PHP.
11
+ *
12
+ * @param string $string string remove NUL characters in
13
+ */
14
+ public static function removeNUL($string)
15
+ {
16
+ return str_replace(chr(0), '', $string);
17
+ }
18
+
19
+ }
lib/classes/Validate.php ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ use \WebPExpress\Sanitize;
6
+ use \WebPExpress\ValidateException;
7
+
8
+ class Validate
9
+ {
10
+
11
+ public static function postHasKey($key)
12
+ {
13
+ if (!isset($_POST[$key])) {
14
+ throw new ValidateException('Expected parameter in POST missing: ' . $key);
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
+ }
166
+
167
+ }
lib/classes/ValidateException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class ValidateException extends \Exception
6
+ {
7
+ }
lib/migrate/migrate10.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ use \WebPExpress\Config;
6
+ use \WebPExpress\Messenger;
7
+
8
+ function webpexpress_migrate10() {
9
+
10
+ $config = Config::loadConfigAndFix(false); // false, because we do not need to test if quality detection is working
11
+
12
+ $converters = &$config['converters'];
13
+ if (is_array($converters)) {
14
+
15
+ // Change specific converter options
16
+ foreach ($converters as &$converter) {
17
+ if (!isset($converter['converter'])) {
18
+ continue;
19
+ }
20
+ $options = &$converter['options'];
21
+
22
+ switch ($converter['converter']) {
23
+ case 'wpc':
24
+ if (isset($options['api-version'])) {
25
+ $options['api-version'] = intval($options['api-version']);
26
+ } else {
27
+ $options['api-version'] = 1;
28
+ }
29
+ break;
30
+ case 'cwebp':
31
+ if (isset($options['method'])) {
32
+ $options['method'] = intval($options['method']);
33
+ } else {
34
+ $options['method'] = 6;
35
+ }
36
+ if (isset($options['size-in-percentage'])) {
37
+ $options['size-in-percentage'] = intval($options['size-in-percentage']);
38
+ }
39
+ break;
40
+ }
41
+ }
42
+ }
43
+
44
+
45
+ // Save both configs.
46
+ // The reason we do it is that we need to update wod-options.json, so the scripts can access the newly available
47
+ // "enable-redirection-to-converter" and "enable-redirection-to-webp-realizer" options
48
+
49
+ $forceHtaccessRegeneration = false;
50
+ $result = Config::saveConfigurationAndHTAccess($config, $forceHtaccessRegeneration);
51
+
52
+ if ($result['saved-both-config']) {
53
+ Messenger::addMessage(
54
+ 'info',
55
+ 'Successfully migrated <i>WebP Express</i> options for 0.14.9.'
56
+ );
57
+ Option::updateOption('webp-express-migration-version', '10');
58
+
59
+ } else {
60
+ Messenger::addMessage(
61
+ 'error',
62
+ 'Failed migrating webp express options to 0.14.9. Probably you need to grant write permissions in your wp-content folder.'
63
+ );
64
+ }
65
+
66
+ }
67
+
68
+ webpexpress_migrate10();
lib/options/enqueue_scripts.php CHANGED
@@ -8,8 +8,8 @@ use \WebPExpress\Paths;
8
  include_once __DIR__ . '/../classes/Config.php';
9
  use \WebPExpress\Config;
10
 
11
- $ver = '2'; // note: Minimum 1
12
- $jsDir = 'js/0.14.5'; // We change dir when it is critical that no-one gets the cached version (there is a plugin that strips version strings out there...)
13
 
14
  if (!function_exists('webp_express_add_inline_script')) {
15
  function webp_express_add_inline_script($id, $script, $position) {
8
  include_once __DIR__ . '/../classes/Config.php';
9
  use \WebPExpress\Config;
10
 
11
+ $ver = '1'; // note: Minimum 1
12
+ $jsDir = 'js/0.14.9'; // We change dir when it is critical that no-one gets the cached version (there is a plugin that strips version strings out there...)
13
 
14
  if (!function_exists('webp_express_add_inline_script')) {
15
  function webp_express_add_inline_script($id, $script, $position) {
lib/options/js/{0.14.5 → 0.14.9}/authorized_sites_bak.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/bulk-convert.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/converters.js RENAMED
@@ -342,7 +342,7 @@ function updateConverterOptions() {
342
  //setConverterOption(converter, 'url-2', document.getElementById('wpc_url_2').value);
343
  //setConverterOption(converter, 'secret-2', document.getElementById('wpc_secret_2').value);*/
344
 
345
- var apiVersion = document.getElementById('wpc_api_version').value;
346
  setConverterOption(converter, 'api-version', apiVersion);
347
 
348
  if (apiVersion == '0') {
@@ -367,11 +367,16 @@ function updateConverterOptions() {
367
  break;
368
  case 'cwebp':
369
  setConverterOption(converter, 'use-nice', document.getElementById('cwebp_use_nice').checked);
370
- setConverterOption(converter, 'method', document.getElementById('cwebp_method').value);
 
 
371
  setConverterOption(converter, 'try-common-system-paths', document.getElementById('cwebp_try_common_system_paths').checked);
372
  setConverterOption(converter, 'try-supplied-binary-for-os', document.getElementById('cwebp_try_supplied_binary').checked);
373
  setConverterOption(converter, 'set-size', document.getElementById('cwebp_set_size').checked);
374
- setConverterOption(converter, 'size-in-percentage', document.getElementById('cwebp_size_in_percentage').value);
 
 
 
375
  setConverterOption(converter, 'command-line-options', document.getElementById('cwebp_command_line_options').value);
376
  break;
377
  case 'imagemagick':
342
  //setConverterOption(converter, 'url-2', document.getElementById('wpc_url_2').value);
343
  //setConverterOption(converter, 'secret-2', document.getElementById('wpc_secret_2').value);*/
344
 
345
+ var apiVersion = parseInt(document.getElementById('wpc_api_version').value, 10);
346
  setConverterOption(converter, 'api-version', apiVersion);
347
 
348
  if (apiVersion == '0') {
367
  break;
368
  case 'cwebp':
369
  setConverterOption(converter, 'use-nice', document.getElementById('cwebp_use_nice').checked);
370
+ var methodString = document.getElementById('cwebp_method').value;
371
+ var methodNum = (methodString == '') ? 6 : parseInt(methodString, 10);
372
+ setConverterOption(converter, 'method', methodNum);
373
  setConverterOption(converter, 'try-common-system-paths', document.getElementById('cwebp_try_common_system_paths').checked);
374
  setConverterOption(converter, 'try-supplied-binary-for-os', document.getElementById('cwebp_try_supplied_binary').checked);
375
  setConverterOption(converter, 'set-size', document.getElementById('cwebp_set_size').checked);
376
+
377
+ var sizeInPercentageString = document.getElementById('cwebp_size_in_percentage').value;
378
+ var sizeInPercentageNumber = (sizeInPercentageString == '') ? '' : parseInt(sizeInPercentageString, 10);
379
+ setConverterOption(converter, 'size-in-percentage', sizeInPercentageNumber);
380
  setConverterOption(converter, 'command-line-options', document.getElementById('cwebp_command_line_options').value);
381
  break;
382
  case 'imagemagick':
lib/options/js/{0.14.5 → 0.14.9}/das-popup.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/escapeHTML.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/image-comparison-slider.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/page.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/purge-cache.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/sortable.min.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/test-convert.js RENAMED
File without changes
lib/options/js/{0.14.5 → 0.14.9}/whitelist.js RENAMED
File without changes
lib/options/page-messages.php CHANGED
@@ -205,11 +205,21 @@ if (!Paths::createContentDirIfMissing()) {
205
 
206
  if (Config::isConfigFileThere()) {
207
  if (!Config::isConfigFileThereAndOk()) {
208
- Messenger::printMessage(
209
- 'warning',
210
- 'Warning: The configuration file is not ok! (cant be read, or not valid json).<br>' .
211
- 'file: "' . esc_html(Paths::getConfigFileName()) . '"'
212
- );
 
 
 
 
 
 
 
 
 
 
213
  } else {
214
  if (HTAccess::arePathsUsedInHTAccessOutdated()) {
215
  Messenger::printMessage(
205
 
206
  if (Config::isConfigFileThere()) {
207
  if (!Config::isConfigFileThereAndOk()) {
208
+ $json = FileHelper::loadFile(Paths::getConfigFileName());
209
+ if ($json === false) {
210
+ Messenger::printMessage(
211
+ 'warning',
212
+ 'Warning: The configuration file is not ok! (cant be read).<br>' .
213
+ 'file: "' . esc_html(Paths::getConfigFileName()) . '"'
214
+ );
215
+ } else {
216
+ Messenger::printMessage(
217
+ 'warning',
218
+ 'Warning: The configuration file is not ok! (not valid json).<br>' .
219
+ 'file: "' . esc_html(Paths::getConfigFileName()) . '"'
220
+ );
221
+ }
222
+
223
  } else {
224
  if (HTAccess::arePathsUsedInHTAccessOutdated()) {
225
  Messenger::printMessage(
lib/options/submit.php CHANGED
@@ -169,10 +169,7 @@ function webpexpress_getSanitizedConverters() {
169
  $convertersSanitized = [];
170
 
171
  // Get list of possible converter ids.
172
- $availableConverterIDs = [];
173
- foreach (ConvertersHelper::$defaultConverters as $converter) {
174
- $availableConverterIDs[] = $converter['converter'];
175
- }
176
 
177
  // Add converters one at the time.
178
  foreach ($convertersPosted as $unsanitizedConverter) {
@@ -211,8 +208,8 @@ function webpexpress_getSanitizedConverters() {
211
  // cwebp
212
  "try-common-system-paths" => 'boolean',
213
  "try-supplied-binary-for-os" => 'boolean',
214
- "method" => 'string', // 0-6, // TODO: make a migration so we use integer instead
215
- "size-in-percentage" => 'string',
216
  "low-memory" => 'boolean',
217
  "command-line-options" => 'string', // webp-convert takes care of sanitizing this very carefully!
218
  "set-size" => 'boolean',
169
  $convertersSanitized = [];
170
 
171
  // Get list of possible converter ids.
172
+ $availableConverterIDs = ConvertersHelper::getDefaultConverterNames();
 
 
 
173
 
174
  // Add converters one at the time.
175
  foreach ($convertersPosted as $unsanitizedConverter) {
208
  // cwebp
209
  "try-common-system-paths" => 'boolean',
210
  "try-supplied-binary-for-os" => 'boolean',
211
+ "method" => 'integer', // 0-6,
212
+ "size-in-percentage" => 'integer', // 0-100
213
  "low-memory" => 'boolean',
214
  "command-line-options" => 'string', // webp-convert takes care of sanitizing this very carefully!
215
  "set-size" => 'boolean',
vendor/composer/installed.json CHANGED
@@ -115,17 +115,17 @@
115
  },
116
  {
117
  "name": "rosell-dk/webp-convert",
118
- "version": "2.1.1",
119
- "version_normalized": "2.1.1.0",
120
  "source": {
121
  "type": "git",
122
  "url": "https://github.com/rosell-dk/webp-convert.git",
123
- "reference": "cc0ceb81193a0b930b2e7c58886a7f3662fb45c6"
124
  },
125
  "dist": {
126
  "type": "zip",
127
- "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/cc0ceb81193a0b930b2e7c58886a7f3662fb45c6",
128
- "reference": "cc0ceb81193a0b930b2e7c58886a7f3662fb45c6",
129
  "shasum": ""
130
  },
131
  "require": {
@@ -143,7 +143,7 @@
143
  "ext-vips": "to use Vips extension for converting.",
144
  "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
145
  },
146
- "time": "2019-06-21T11:15:37+00:00",
147
  "type": "library",
148
  "extra": {
149
  "scripts-descriptions": {
115
  },
116
  {
117
  "name": "rosell-dk/webp-convert",
118
+ "version": "2.1.3",
119
+ "version_normalized": "2.1.3.0",
120
  "source": {
121
  "type": "git",
122
  "url": "https://github.com/rosell-dk/webp-convert.git",
123
+ "reference": "959f0515b79a785fa1015c0023c22619f55d6392"
124
  },
125
  "dist": {
126
  "type": "zip",
127
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/959f0515b79a785fa1015c0023c22619f55d6392",
128
+ "reference": "959f0515b79a785fa1015c0023c22619f55d6392",
129
  "shasum": ""
130
  },
131
  "require": {
143
  "ext-vips": "to use Vips extension for converting.",
144
  "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
145
  },
146
+ "time": "2019-06-22T22:24:30+00:00",
147
  "type": "library",
148
  "extra": {
149
  "scripts-descriptions": {
vendor/rosell-dk/webp-convert/composer.json CHANGED
@@ -31,6 +31,7 @@
31
  ],
32
  "test-src": "phpunit --coverage-text",
33
  "phpunit": "phpunit --coverage-text",
 
34
  "cs-fix-all": [
35
  "php-cs-fixer fix src"
36
  ],
31
  ],
32
  "test-src": "phpunit --coverage-text",
33
  "phpunit": "phpunit --coverage-text",
34
+ "test-no-cov": "phpunit --no-coverage",
35
  "cs-fix-all": [
36
  "php-cs-fixer fix src"
37
  ],
vendor/rosell-dk/webp-convert/docs/v2.0/converting/options.md CHANGED
@@ -28,6 +28,30 @@ Supported by: cwebp
28
  ```
29
  This allows you to set any parameter available for cwebp in the same way as you would do when executing *cwebp*. You could ie set it to "-sharpness 5 -mt -crop 10 10 40 40". Read more about all the available parameters in [the docs](https://developers.google.com/speed/webp/docs/cwebp).<br><br>
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  ### `default-quality`
32
  ```
33
  Type: integer (0-100)
@@ -169,9 +193,6 @@ $options = [
169
  'gmagick-skip' => true,
170
  ];
171
  ```
172
-
173
-
174
-
175
  <br>
176
 
177
  ### `stack-converters`
28
  ```
29
  This allows you to set any parameter available for cwebp in the same way as you would do when executing *cwebp*. You could ie set it to "-sharpness 5 -mt -crop 10 10 40 40". Read more about all the available parameters in [the docs](https://developers.google.com/speed/webp/docs/cwebp).<br><br>
30
 
31
+ ### `cwebp-rel-path-to-precompiled-binaries`
32
+ ```
33
+ Type: string
34
+ Default: './Binaries'
35
+ Supported by: cwebp
36
+ ```
37
+ Allows you to change where to look for the precompiled binaries. While this may look as a risk, it is completely safe, as the binaries are hash-checked before being executed. The option is needed when you are using two-file version of webp-on-demand.
38
+
39
+ ### `cwebp-try-common-system-paths`
40
+ ```
41
+ Type: boolean
42
+ Default: true
43
+ Supported by: cwebp
44
+ ```
45
+ If set, the converter will try to look for cwebp in locations such as `/usr/bin/cwebp`.
46
+
47
+ ### `cwebp-try-supplied-binary-for-os`
48
+ ```
49
+ Type: boolean
50
+ Default: true
51
+ Supported by: cwebp
52
+ ```
53
+ If set, the converter will try the precompiled cwebp binary that are located in `src/Convert/Converters/Binaries`, for the current OS. The binaries are hash-checked before executed.
54
+
55
  ### `default-quality`
56
  ```
57
  Type: integer (0-100)
193
  'gmagick-skip' => true,
194
  ];
195
  ```
 
 
 
196
  <br>
197
 
198
  ### `stack-converters`
vendor/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php CHANGED
@@ -125,7 +125,9 @@ class Cwebp extends AbstractConverter
125
  private static function escapeShellArgOnCommandLineOptions($commandLineOptions)
126
  {
127
  if (!ctype_print($commandLineOptions)) {
128
- throw new ConversionFailedException('Non-printable characters are not allowed in the extra command line options');
 
 
129
  }
130
 
131
  if (preg_match('#[^a-zA-Z0-9_\s\-]#', $commandLineOptions)) {
125
  private static function escapeShellArgOnCommandLineOptions($commandLineOptions)
126
  {
127
  if (!ctype_print($commandLineOptions)) {
128
+ throw new ConversionFailedException(
129
+ 'Non-printable characters are not allowed in the extra command line options'
130
+ );
131
  }
132
 
133
  if (preg_match('#[^a-zA-Z0-9_\s\-]#', $commandLineOptions)) {
vendor/rosell-dk/webp-convert/src/Helpers/PathChecker.php CHANGED
@@ -38,8 +38,14 @@ class PathChecker
38
  }
39
 
40
  // Prevent non printable characters
 
41
  if (!ctype_print($absFilePath)) {
42
  throw new InvalidInputException('Non-printable characters are not allowed in ' . $text);
 
 
 
 
 
43
  }
44
 
45
  // Prevent directory traversal
38
  }
39
 
40
  // Prevent non printable characters
41
+ /*
42
  if (!ctype_print($absFilePath)) {
43
  throw new InvalidInputException('Non-printable characters are not allowed in ' . $text);
44
+ }*/
45
+
46
+ // Prevent control characters (at least the first 32 (#0 - #1f)
47
+ if (preg_match('#[\x{0}-\x{1f}]#', $absFilePath)) {
48
+ throw new InvalidInputException('Non-printable characters are not allowed');
49
  }
50
 
51
  // Prevent directory traversal
vendor/rosell-dk/webp-convert/src/Serve/Report.php CHANGED
@@ -38,7 +38,7 @@ class Report
38
  try {
39
  $echoLogger = new EchoLogger();
40
  $options['log-call-arguments'] = true;
41
- WebPConvert::convert($source, $destination, $options, $echoLogger);
42
  } catch (\Exception $e) {
43
  $msg = $e->getMessage();
44
  echo '<b>' . $msg . '</b>';
38
  try {
39
  $echoLogger = new EchoLogger();
40
  $options['log-call-arguments'] = true;
41
+ WebPConvert::convert($source, $destination, $options['convert'], $echoLogger);
42
  } catch (\Exception $e) {
43
  $msg = $e->getMessage();
44
  echo '<b>' . $msg . '</b>';
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.8
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
@@ -22,7 +22,7 @@ use \WebPExpress\Option;
22
  define('WEBPEXPRESS_PLUGIN', __FILE__);
23
  define('WEBPEXPRESS_PLUGIN_DIR', __DIR__);
24
 
25
- // Autoloading rules!
26
  spl_autoload_register('webpexpress_autoload');
27
  function webpexpress_autoload($class) {
28
  if (strpos($class, 'WebPExpress\\') === 0) {
@@ -37,14 +37,15 @@ if (is_admin()) {
37
  function webp_express_process_post() {
38
  // strip query string
39
  $requestUriNoQS = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
 
40
 
41
- if (!preg_match('/webp-express-web-service$/', $requestUriNoQS)) {
42
- return;
 
43
  }
44
- include __DIR__ . '/web-service/wpc.php';
45
- die();
46
  }
47
  add_action( 'init', 'webp_express_process_post' );
 
48
 
49
  if (Option::getOption('webp-express-alter-html', false)) {
50
  require_once __DIR__ . '/lib/classes/AlterHtmlInit.php';
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
22
  define('WEBPEXPRESS_PLUGIN', __FILE__);
23
  define('WEBPEXPRESS_PLUGIN_DIR', __DIR__);
24
 
25
+ // Autoload WebPExpress classes
26
  spl_autoload_register('webpexpress_autoload');
27
  function webpexpress_autoload($class) {
28
  if (strpos($class, 'WebPExpress\\') === 0) {
37
  function webp_express_process_post() {
38
  // strip query string
39
  $requestUriNoQS = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
40
+ //echo '<pre>' . print_r($_SERVER, true) . '</pre>'; die();
41
 
42
+ if (preg_match('/webp-express-web-service$/', $requestUriNoQS)) {
43
+ include __DIR__ . '/web-service/wpc.php';
44
+ die();
45
  }
 
 
46
  }
47
  add_action( 'init', 'webp_express_process_post' );
48
+ //add_action( 'parse_request', 'webp_express_process_post' );
49
 
50
  if (Option::getOption('webp-express-alter-html', false)) {
51
  require_once __DIR__ . '/lib/classes/AlterHtmlInit.php';
wod/webp-on-demand.php CHANGED
@@ -1,238 +1,285 @@
1
  <?php
 
2
 
3
  use \WebPConvert\WebPConvert;
4
  use \WebPConvert\Serve\ServeConvertedWebP;
5
-
6
- include_once "../lib/classes/ConvertHelperIndependent.php";
7
  use \WebPExpress\ConvertHelperIndependent;
 
 
8
 
9
- function exitWithError($msg) {
10
- header('X-WebP-Express-Error: ' . $msg, true);
11
- echo $msg;
12
- exit;
13
- }
14
 
15
- //echo $_SERVER["SERVER_SOFTWARE"]; exit;
16
- //stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false
17
 
18
- // Protect against directly accessing webp-on-demand.php
19
- // 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.
20
- if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
21
- if (strpos($_SERVER['REQUEST_URI'], 'webp-on-demand.php') !== false) {
22
- exitWithError('It seems you are visiting this file (plugins/webp-express/wod/webp-on-demand.php) directly. We do not allow this.');
23
  exit;
24
  }
25
- }
26
 
27
-
28
- /**
29
- * Get environment variable set with mod_rewrite module
30
- * Return false if the environment variable isn't found
31
- */
32
- function getEnvPassedInRewriteRule($envName) {
33
- // Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
34
- // Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
35
- // Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
36
- // We simply look for an environment variable that ends with what we are looking for.
37
- // (so make sure to make it unique)
38
- $len = strlen($envName);
39
- foreach ($_SERVER as $key => $item) {
40
- if (substr($key, -$len) == $envName) {
41
- return $item;
 
 
 
 
42
  }
 
43
  }
44
- return false;
45
- }
46
 
47
- function loadConfig($configFilename) {
48
- if (!file_exists($configFilename)) {
49
- header('X-WebP-Express-Error: Configuration file not found!', true);
50
- echo 'Configuration file not found!';
51
- //WebPConvert::convertAndServe($source, $destination, []);
52
- exit;
53
- }
 
 
 
 
 
 
 
54
 
55
- // TODO: Handle read error / json error
56
- $handle = @fopen($configFilename, "r");
57
- $json = fread($handle, filesize($configFilename));
58
- fclose($handle);
59
- return json_decode($json, true);
60
- }
 
 
 
 
 
 
 
 
61
 
62
- /**
63
- * Get absolute path to source file.
64
- *
65
- * The path can be passed to this file from the .htaccess file / nginx config in various ways.
66
- *
67
- * @return string Absolute path to source (unsanitized! - call sanitizeAbsFilePath immidiately after calling this method)
68
- */
69
- function getSourceUnsanitized() {
70
- global $wodOptions;
71
- global $docRoot;
72
-
73
- // First check if it is in an environment variable - thats the safest way
74
- $source = getEnvPassedInRewriteRule('REQFN');
75
- if ($source !== false) {
76
- return $source;
77
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- // Then header
80
- if (isset($wodOptions['base-htaccess-on-these-capability-tests'])) {
81
- $capTests = $wodOptions['base-htaccess-on-these-capability-tests'];
82
- $passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
83
- $passThrougEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
84
- } else {
85
- $passThroughHeaderDefinitelyUnavailable = false;
86
- $passThrougEnvVarDefinitelyAvailable = false;
87
- }
88
- if ((!$passThrougEnvVarDefinitelyAvailable) && (!$passThroughHeaderDefinitelyUnavailable)) {
89
- if (isset($_SERVER['HTTP_REQFN'])) {
90
- return $_SERVER['HTTP_REQFN'];
91
  }
92
- }
93
 
94
- // Then querystring (full path).
95
- // PS: The parameters in $_GET are already url decoded. Sanitizing happens on the result
 
 
 
 
 
 
96
 
97
- // TODO: perhaps only allow passing absolute path on nginx?
98
- if (isset($_GET['xsource'])) {
99
- return substr($_GET['xsource'], 1); // No url decoding needed as $_GET is already decoded
100
- } elseif (isset($_GET['source'])) {
101
- return $_GET['source'];
102
  }
103
 
104
- // Then querystring (relative path)
105
- $srcRel = '';
106
- if (isset($_GET['xsource-rel'])) {
107
- $srcRel = substr($_GET['xsource-rel'], 1);
108
- } elseif (isset($_GET['source-rel'])) {
109
- $srcRel = $_GET['source-rel'];
 
 
 
 
 
 
 
 
110
  }
111
- if ($srcRel != '') {
112
- if (isset($_GET['source-rel-filter'])) {
113
- if ($_GET['source-rel-filter'] == 'discard-parts-before-wp-content') {
114
- $parts = explode('/', $srcRel);
115
- $wp_content = isset($_GET['wp-content']) ? $_GET['wp-content'] : 'wp-content';
116
-
117
- if (in_array($wp_content, $parts)) {
118
- foreach($parts as $index => $part) {
119
- if($part !== $wp_content) {
120
- unset($parts[$index]);
121
- } else {
122
- break;
123
- }
124
- }
125
- $srcRel = implode('/', $parts);
126
- }
127
  }
128
  }
129
- return $docRoot . '/' . $srcRel;
130
- }
131
 
132
- // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
133
- // correct result in all setups (ie "folder method 1")
134
- $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
135
- //$docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
136
- $source = $docRoot . urldecode($requestUriNoQS);
137
- if (file_exists($source)) {
138
- return $source;
139
- }
140
 
141
- // No luck whatsoever!
142
- exitWithError('webp-on-demand.php was not passed any filename to convert');
143
- }
 
144
 
145
- function getSource() {
146
- return ConvertHelperIndependent::sanitizeAbsFilePath(
147
- getSourceUnsanitized()
148
- );
149
- }
150
 
151
- function getWpContentRel() {
152
- // Passed in env variable?
153
- $wpContentDirRel = getEnvPassedInRewriteRule('WPCONTENT');
154
- if ($wpContentDirRel !== false) {
155
- return $wpContentDirRel;
156
- }
157
 
158
- // Passed in QS?
159
- if (isset($_GET['wp-content'])) {
160
- return $_GET['wp-content'];
161
- }
 
 
 
 
 
 
162
 
163
- // In case above fails, fall back to standard location
164
- return 'wp-content';
165
- }
166
 
167
- $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
 
 
 
 
 
 
168
 
169
- // PS: the following sanitizing will remove any "../" (which is good)
170
- $webExpressContentDirAbs = ConvertHelperIndependent::sanitizeAbsFilePath($docRoot . '/' . getWpContentRel() . '/webp-express');
171
- $options = loadConfig($webExpressContentDirAbs . '/config/wod-options.json');
172
 
173
- // TODO:
174
- // Exit here, if not configured to redirect to conversion script
 
 
 
175
 
176
- $wodOptions = $options['wod'];
177
- $serveOptions = $options['webp-convert'];
178
- $convertOptions = &$serveOptions['convert'];
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- $source = getSource();
182
- //$source = getSource(false, false);
183
 
184
- //echo $source; exit;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- if (!file_exists($source)) {
187
- header('X-WebP-Express-Error: Source file not found!', true);
188
- echo 'Source file not found!';
189
- exit;
190
- }
 
 
 
 
191
 
192
- $destination = ConvertHelperIndependent::getDestination(
193
- $source,
194
- $wodOptions['destination-folder'],
195
- $wodOptions['destination-extension'],
196
- $webExpressContentDirAbs,
197
- $docRoot . '/' . $wodOptions['paths']['uploadDirRel']
198
- );
199
-
200
- //echo $destination; exit;
201
- //echo '<pre>' . print_r($wodOptions, true) . '</pre>'; exit;
202
-
203
- foreach ($convertOptions['converters'] as &$converter) {
204
- if (isset($converter['converter'])) {
205
- $converterId = $converter['converter'];
206
- } else {
207
- $converterId = $converter;
208
- }
209
- if ($converterId == 'cwebp') {
210
- $converter['options']['rel-path-to-precompiled-binaries'] = '../src/Converters/Binaries';
211
- }
212
- }
213
 
214
- if ($wodOptions['forward-query-string']) {
215
- if (isset($_GET['debug'])) {
216
- $serveOptions['show-report'] = true;
217
- }
218
- if (isset($_GET['reconvert'])) {
219
- $serveOptions['reconvert'] = true;
 
 
 
 
220
  }
221
  }
222
 
223
-
224
- if (isset($wodOptions['success-response']) && ($wodOptions['success-response'] == 'original')) {
225
- $serveOptions['serve-original'] = true;
226
- $serveOptions['serve-image']['headers']['vary-accept'] = false;
227
- } else {
228
- $serveOptions['serve-image']['headers']['vary-accept'] = true;
 
229
  }
230
 
231
-
232
- ConvertHelperIndependent::serveConverted(
233
- $source,
234
- $destination,
235
- $serveOptions,
236
- $webExpressContentDirAbs . '/log',
237
- 'Conversion triggered with the conversion script (wod/webp-on-demand.php)'
238
- );
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 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.
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 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'];
209
+ $convertOptions = &$serveOptions['convert'];
210
+ //echo '<pre>' . print_r($wodOptions, true) . '</pre>'; exit;
211
+
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'],
235
+ $wodOptions['destination-extension'],
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;
259
+ } else {
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,
269
+ $serveOptions,
270
+ $webExpressContentDirAbs . '/log',
271
+ 'Conversion triggered with the conversion script (wod/webp-on-demand.php)'
272
+ );
273
  }
274
  }
275
 
276
+ // Protect against directly accessing webp-on-demand.php
277
+ // 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.
278
+ if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
279
+ if (strpos($_SERVER['REQUEST_URI'], 'webp-on-demand.php') !== false) {
280
+ WebPOnDempand::exitWithError('It seems you are visiting this file (plugins/webp-express/wod/webp-on-demand.php) directly. We do not allow this.');
281
+ exit;
282
+ }
283
  }
284
 
285
+ WebPOnDempand::process();
 
 
 
 
 
 
 
wod/webp-realizer.php CHANGED
@@ -1,213 +1,278 @@
1
  <?php
 
2
 
3
  use \WebPConvert\WebPConvert;
4
- use \WebPConvert\ServeExistingOrHandOver;
5
-
6
- include_once "../lib/classes/ConvertHelperIndependent.php";
7
  use \WebPExpress\ConvertHelperIndependent;
 
 
8
 
9
- function exitWithError($msg) {
10
- header('X-WebP-Express-Error: ' . $msg, true);
11
- echo $msg;
12
- exit;
13
- }
14
 
15
- // Protect against directly accessing webp-on-demand.php
16
- // 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.
17
- if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
18
- if (strpos($_SERVER['REQUEST_URI'], 'webp-realizer.php') !== false) {
19
- exitWithError('It seems you are visiting this file (plugins/webp-express/wod/webp-realizer.php) directly. We do not allow this.');
20
  exit;
21
  }
22
- }
23
 
24
- /**
25
- * Get environment variable set with mod_rewrite module
26
- * Return false if the environment variable isn't found
27
- */
28
- function getEnvPassedInRewriteRule($envName) {
29
- // Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
30
- // Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
31
- // Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
32
- // We simply look for an environment variable that ends with what we are looking for.
33
- // (so make sure to make it unique)
34
- $len = strlen($envName);
35
- foreach ($_SERVER as $key => $item) {
36
- if (substr($key, -$len) == $envName) {
37
- return $item;
 
 
 
 
 
38
  }
 
39
  }
40
- return false;
41
- }
42
 
43
- function loadConfig($configFilename) {
44
- if (!file_exists($configFilename)) {
45
- header('X-WebP-Express-Error: Configuration file not found!', true);
46
- echo 'Configuration file not found!';
47
- //WebPConvert::convertAndServe($source, $destination, []);
48
- exit;
49
- }
 
 
 
 
 
 
 
50
 
51
- // TODO: Handle read error / json error
52
- $handle = @fopen($configFilename, "r");
53
- $json = fread($handle, filesize($configFilename));
54
- fclose($handle);
55
- return json_decode($json, true);
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- /*
59
- function getDestinationRealPath($dest) {
60
- //echo $_SERVER["DOCUMENT_ROOT"] . '<br>' . $dest . '<br>';
61
- if (strpos($dest, $_SERVER["DOCUMENT_ROOT"]) === 0) {
62
- return realpath($_SERVER["DOCUMENT_ROOT"]) . substr($dest, strlen($_SERVER["DOCUMENT_ROOT"]));
63
- } else {
64
  return $dest;
65
  }
66
- }*/
67
 
68
- function getDestinationUnsanitized() {
69
- global $docRoot;
70
 
71
- // First check if it is in an environment variable - thats the safest way
72
- $destinationRel = getEnvPassedInRewriteRule('DESTINATIONREL');
73
- if ($destinationRel !== false) {
74
- return $docRoot . '/' . $destinationRel;
75
- }
 
76
 
77
- // Next, check querystring (full path)
78
- if (isset($_GET['xdestination'])) {
79
- return substr($_GET['xdestination'], 1); // No url decoding needed as $_GET is already decoded
80
- } elseif (isset($_GET['destination'])) {
81
- return $_GET['destination'];
82
- }
83
 
84
- // Next, check querystring (relative path)
85
- $destinationRel = '';
86
- if (isset($_GET['xdestination-rel'])) {
87
- $destinationRel = substr($_GET['xdestination-rel'], 1);
88
- } elseif (isset($_GET['destination-rel'])) {
89
- $destinationRel = $_GET['destination-rel'];
90
  }
91
- if ($destinationRel != '') {
92
- if (isset($_GET['source-rel-filter'])) {
93
- if ($_GET['source-rel-filter'] == 'discard-parts-before-wp-content') {
94
- $parts = explode('/', $destinationRel);
95
- $wp_content = isset($_GET['wp-content']) ? $_GET['wp-content'] : 'wp-content';
96
-
97
- if (in_array($wp_content, $parts)) {
98
- foreach($parts as $index => $part) {
99
- if($part !== $wp_content) {
100
- unset($parts[$index]);
101
- } else {
102
- break;
103
- }
104
- }
105
- $destinationRel = implode('/', $parts);
106
- }
107
  }
108
  }
109
- return $docRoot . '/' . $destinationRel;
110
- }
111
 
112
- // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
113
- // correct result in all setups (ie "folder method 1")
114
- $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
115
- $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
116
- $dest = $docRoot . urldecode($requestUriNoQS);
117
- return $dest;
118
- }
119
 
120
- function getDestination() {
121
- return ConvertHelperIndependent::sanitizeAbsFilePath(
122
- getDestinationUnsanitized()
123
- );
124
- }
125
 
126
- function getWpContentRel() {
127
- // Passed in env variable?
128
- $wpContentDirRel = getEnvPassedInRewriteRule('WPCONTENT');
129
- if ($wpContentDirRel !== false) {
130
- return $wpContentDirRel;
131
- }
132
 
133
- // Passed in QS?
134
- if (isset($_GET['wp-content'])) {
135
- return $_GET['wp-content'];
136
- }
137
 
138
- // In case above fails, fall back to standard location
139
- return 'wp-content';
140
- }
 
 
 
 
 
 
 
141
 
142
- $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
143
- $webExpressContentDirAbs = $docRoot . '/' . getWpContentRel() . '/webp-express';
144
- $options = loadConfig($webExpressContentDirAbs . '/config/wod-options.json');
145
- $wodOptions = $options['wod'];
146
- $serveOptions = $options['webp-convert'];
147
- $convertOptions = &$serveOptions['convert'];
148
 
149
- $destination = getDestination();
 
 
 
 
 
 
150
 
151
- //echo 'destination: ' . $destination; exit;
152
 
153
- $source = ConvertHelperIndependent::findSource(
154
- $destination,
155
- $wodOptions['destination-folder'],
156
- $wodOptions['destination-extension'],
157
- $webExpressContentDirAbs
158
- );
159
 
160
- if ($source === false) {
161
- header('X-WebP-Express-Error: webp-realizer.php could not find an existing jpg or png that corresponds to the webp requested', true);
162
 
163
- $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
164
- header($protocol . " 404 Not Found");
165
- //echo '<p>webp-realizer.php could not find an existing jpg or png that corresponds to the webp requested!</p>';
166
- //echo 'destination requested:<br><i>' . $destination . '</i>';
167
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- foreach ($convertOptions['converters'] as &$converter) {
170
- if (isset($converter['converter'])) {
171
- $converterId = $converter['converter'];
172
- } else {
173
- $converterId = $converter;
174
- }
175
- if ($converterId == 'cwebp') {
176
- $converter['options']['rel-path-to-precompiled-binaries'] = '../src/Converters/Binaries';
177
- }
178
- }
179
 
180
- if ($wodOptions['forward-query-string']) {
181
- if (isset($_GET['debug'])) {
182
- $serveOptions['show-report'] = true;
183
- }
184
- if (isset($_GET['reconvert'])) {
185
- $options['reconvert'] = true;
186
- }
187
- }
188
 
189
- $serveOptions['add-vary-header'] = false;
190
- $serveOptions['fail'] = '404';
191
- $serveOptions['fail-when-fail-fails'] = '404';
192
- //$options['show-report'] = true;
193
- $serveOptions['serve-image']['headers']['vary-accept'] = false;
194
-
195
- /*
196
- function aboutToServeImageCallBack($servingWhat, $whyServingThis, $obj) {
197
- // Redirect to same location.
198
- header('Location: ?fresh' , 302);
199
- return false; // tell webp-convert not to serve!
200
- }
201
- */
202
 
203
- ConvertHelperIndependent::serveConverted(
204
- $source,
205
- $destination,
206
- $serveOptions,
207
- $webExpressContentDirAbs . '/log',
208
- 'Conversion triggered with the conversion script (wod/webp-realizer.php)'
209
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
- //echo "<pre>source: $source \ndestination: $destination \n\noptions:" . print_r($options, true) . '</pre>'; exit;
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
+ 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'];
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
+ 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'],
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
+ 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,
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();