PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
jetpack / jetpack_vendor / automattic / jetpack-waf / src / class-waf-standalone-bootstrap.php

class-waf-standalone-bootstrap.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-waf/src/class-waf-standalone-bootstrap.php

237 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles generation and deletion of the bootstrap for the standalone WAF mode.
4 *
5 * @package automattic/jetpack-waf
6 */
7
8 namespace Automattic\Jetpack\Waf;
9
10 use Composer\InstalledVersions;
11
12 /**
13 * Handles the bootstrap.
14 *
15 * @phan-constructor-used-for-side-effects
16 */
17 class Waf_Standalone_Bootstrap {
18
19 /**
20 * Ensures that constants are initialized if this class is used.
21 *
22 * @return void
23 */
24 public function __construct() {
25 $this->guard_against_missing_abspath();
26 $this->initialize_constants();
27 }
28
29 /**
30 * Ensures that this class is not used unless we are in the right context.
31 *
32 * @throws Waf_Exception If we are outside of WordPress.
33 *
34 * @return void
35 */
36 private function guard_against_missing_abspath() {
37
38 if ( ! defined( 'ABSPATH' ) ) {
39 throw new Waf_Exception( 'Cannot generate the WAF bootstrap if we are not running in WordPress context.' );
40 }
41 }
42
43 /**
44 * Initializes the constants required for generating the bootstrap, if they have not been initialized yet.
45 *
46 * @return void
47 */
48 private function initialize_constants() {
49 Waf_Constants::initialize_constants();
50 }
51
52 /**
53 * Initialized the WP filesystem and serves as a mocking hook for tests.
54 *
55 * Should only be implemented after the wp_loaded action hook:
56 *
57 * @link https://developer.wordpress.org/reference/functions/wp_filesystem/#more-information
58 *
59 * @return void
60 */
61 protected function initialize_filesystem() {
62 if ( ! function_exists( '\\WP_Filesystem' ) ) {
63 require_once ABSPATH . 'wp-admin/includes/file.php';
64 }
65
66 WP_Filesystem();
67 }
68
69 /**
70 * Finds the path to Composer's generated classmap, which the generated bootstrap file uses to autoload WAF classes.
71 *
72 * @throws Waf_Exception In case the classmap file cannot be found.
73 *
74 * @return string
75 */
76 private function locate_classmap_file() {
77 global $jetpack_autoloader_loader;
78
79 $vendor_dirs = array();
80
81 // Try the Jetpack autoloader.
82 if ( isset( $jetpack_autoloader_loader ) ) {
83 $class_file = $jetpack_autoloader_loader->find_class_file( Waf_Runner::class );
84 if ( $class_file ) {
85 $vendor_dirs[] = dirname( $class_file, 5 ) . '/vendor';
86 }
87 }
88
89 // Try Composer's autoloader.
90 if ( is_callable( array( InstalledVersions::class, 'getInstallPath' ) )
91 && InstalledVersions::isInstalled( 'automattic/jetpack-waf' )
92 ) {
93 $package_file = InstalledVersions::getInstallPath( 'automattic/jetpack-waf' );
94 if ( substr( $package_file, -23 ) === '/automattic/jetpack-waf' ) {
95 $vendor_dirs[] = dirname( $package_file, 3 ) . '/vendor';
96 }
97 }
98
99 // Guess. First look for being in a `vendor/automattic/jetpack-waf/src/', then see if we're standalone with our own vendor dir.
100 $vendor_dirs[] = dirname( __DIR__, 4 ) . '/vendor';
101 $vendor_dirs[] = dirname( __DIR__ ) . '/vendor';
102
103 // A candidate can exist without containing this package, e.g. a monorepo's root vendor dir.
104 foreach ( $vendor_dirs as $vendor_dir ) {
105 $classmap_file = $vendor_dir . '/composer/autoload_classmap.php';
106 if ( ! file_exists( $classmap_file ) ) {
107 continue;
108 }
109 $classmap = require $classmap_file;
110 if ( isset( $classmap[ Waf_Runner::class ] ) ) {
111 return $classmap_file;
112 }
113 }
114
115 throw new Waf_Exception( 'Cannot find the Composer classmap, and the WAF standalone bootstrap will not work without it.' );
116 }
117
118 /**
119 * Gets the path to the bootstrap.php file.
120 *
121 * @return string The bootstrap.php file path.
122 */
123 public function get_bootstrap_file_path() {
124 return trailingslashit( JETPACK_WAF_DIR ) . 'bootstrap.php';
125 }
126
127 /**
128 * Gets the entrypoint file.
129 *
130 * @return string The entrypoint file.
131 */
132 private function get_entrypoint() {
133 return defined( 'JETPACK_WAF_ENTRYPOINT' ) ? JETPACK_WAF_ENTRYPOINT : 'rules/rules.php';
134 }
135
136 /**
137 * Generates the bootstrap file.
138 *
139 * @throws File_System_Exception If the filesystem is not available.
140 * @throws File_System_Exception If the WAF directory cannot be created.
141 * @throws File_System_Exception If the bootstrap file cannot be created.
142 *
143 * @return string Absolute path to the bootstrap file.
144 */
145 public function generate() {
146
147 $this->initialize_filesystem();
148
149 global $wp_filesystem;
150 if ( ! $wp_filesystem ) {
151 throw new File_System_Exception( 'Cannot work without the file system being initialized.' );
152 }
153
154 $classmap_file = $this->locate_classmap_file();
155
156 $bootstrap_file = $this->get_bootstrap_file_path();
157 $entrypoint = $this->get_entrypoint();
158 $mode_option = get_option( Waf_Runner::MODE_OPTION_NAME, false );
159 $share_data_option = get_option( Waf_Runner::SHARE_DATA_OPTION_NAME, false );
160 $share_debug_data_option = get_option( Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME, false );
161
162 // Autoload from the classmap alone rather than `vendor/autoload.php`: Composer's loader would also run every
163 // package's `files` entries and mark them loaded, so the Jetpack autoloader later skips its own, possibly newer, copies.
164 // The closure keeps every variable, including the classmap's own `$vendorDir`/`$baseDir`, out of the global scope.
165 $template = <<<'PHP'
166 <?php
167 define( 'DISABLE_JETPACK_WAF', {{disable}} );
168 if ( defined( 'DISABLE_JETPACK_WAF' ) && DISABLE_JETPACK_WAF ) return;
169 define( 'JETPACK_WAF_MODE', {{mode}} );
170 define( 'JETPACK_WAF_SHARE_DATA', {{share_data}} );
171 define( 'JETPACK_WAF_SHARE_DEBUG_DATA', {{share_debug_data}} );
172 define( 'JETPACK_WAF_DIR', {{dir}} );
173 define( 'JETPACK_WAF_WPCONFIG', {{wpconfig}} );
174 define( 'JETPACK_WAF_ENTRYPOINT', {{entrypoint}} );
175 ( static function () {
176 $classmap_file = {{classmap_file}};
177 if ( ! is_file( $classmap_file ) ) {
178 return;
179 }
180 $classmap = require $classmap_file;
181 $autoloader = static function ( $class_name ) use ( $classmap ) {
182 if ( isset( $classmap[ $class_name ] ) ) {
183 require $classmap[ $class_name ];
184 }
185 };
186 spl_autoload_register( $autoloader );
187 Automattic\Jetpack\Waf\Waf_Runner::initialize();
188 spl_autoload_unregister( $autoloader );
189
190 // The preloaded WAF classes keep running once WordPress starts, and the active plugin's older copy may lack a class
191 // they reference. Keep resolving WAF classes from here, behind the Jetpack autoloader, which prepends itself.
192 $waf_classmap = array_filter(
193 $classmap,
194 static function ( $class_name ) {
195 return 0 === strpos( $class_name, 'Automattic\Jetpack\Waf\\' );
196 },
197 ARRAY_FILTER_USE_KEY
198 );
199 spl_autoload_register(
200 static function ( $class_name ) use ( $waf_classmap ) {
201 if ( isset( $waf_classmap[ $class_name ] ) ) {
202 require $waf_classmap[ $class_name ];
203 }
204 }
205 );
206 } )();
207
208 PHP;
209
210 $code = strtr(
211 $template,
212 array(
213 '{{disable}}' => var_export( defined( 'DISABLE_JETPACK_WAF' ) && DISABLE_JETPACK_WAF, true ),
214 '{{mode}}' => var_export( $mode_option ? $mode_option : 'silent', true ),
215 '{{share_data}}' => var_export( $share_data_option, true ),
216 '{{share_debug_data}}' => var_export( $share_debug_data_option, true ),
217 '{{dir}}' => var_export( JETPACK_WAF_DIR, true ),
218 '{{wpconfig}}' => var_export( JETPACK_WAF_WPCONFIG, true ),
219 '{{entrypoint}}' => var_export( $entrypoint, true ),
220 '{{classmap_file}}' => var_export( $classmap_file, true ),
221 )
222 );
223
224 if ( ! $wp_filesystem->is_dir( JETPACK_WAF_DIR ) ) {
225 if ( ! $wp_filesystem->mkdir( JETPACK_WAF_DIR ) ) {
226 throw new File_System_Exception( 'Failed creating WAF standalone bootstrap file directory: ' . JETPACK_WAF_DIR );
227 }
228 }
229
230 if ( ! $wp_filesystem->put_contents( $bootstrap_file, $code ) ) {
231 throw new File_System_Exception( 'Failed writing WAF standalone bootstrap file to: ' . $bootstrap_file );
232 }
233
234 return $bootstrap_file;
235 }
236 }
237