PluginProbe
OPcache Reset / 2.4.0
OPcache Reset v2.4.0
trunk 1.0.0 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.3.0 2.4.0 2.4.1 2.4.2 2.4.3
opcache-reset / opcache-fastcgi.php

opcache-fastcgi.php in OPcache Reset 2.4.0, at opcache-fastcgi.php

246 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Direct FastCGI client for OPcache reset.
4 *
5 * Implements FastCGI protocol to communicate with PHP-FPM directly,
6 * eliminating the need for cachetool binary.
7 *
8 * Note: This file uses low-level PHP socket functions intentionally.
9 * WP_Filesystem is not suitable for socket communication.
10 *
11 * @package opcache-reset
12 */
13
14 // Prevent direct access.
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 // FastCGI protocol constants.
20 define( 'FCGI_VERSION_1', 1 );
21 define( 'FCGI_BEGIN_REQUEST', 1 );
22 define( 'FCGI_END_REQUEST', 3 );
23 define( 'FCGI_PARAMS', 4 );
24 define( 'FCGI_STDIN', 5 );
25 define( 'FCGI_STDOUT', 6 );
26 define( 'FCGI_RESPONDER', 1 );
27
28 /**
29 * Build a FastCGI record.
30 *
31 * @param int $type Record type.
32 * @param string $content Record content.
33 * @param int $request_id Request ID.
34 * @return string Binary record data.
35 */
36 function gps_fcgi_build_record( $type, $content, $request_id = 1 ) {
37 $content_length = strlen( $content );
38 $padding_length = ( 8 - ( $content_length % 8 ) ) % 8;
39
40 return pack(
41 'CCnnCx',
42 FCGI_VERSION_1,
43 $type,
44 $request_id,
45 $content_length,
46 $padding_length
47 ) . $content . str_repeat( "\0", $padding_length );
48 }
49
50 /**
51 * Build FastCGI name-value pair.
52 *
53 * @param string $name Parameter name.
54 * @param string $value Parameter value.
55 * @return string Binary name-value pair.
56 */
57 function gps_fcgi_build_nvpair( $name, $value ) {
58 $name_len = strlen( $name );
59 $value_len = strlen( $value );
60
61 $result = '';
62 if ( $name_len < 128 ) {
63 $result .= chr( $name_len );
64 } else {
65 $result .= pack( 'N', $name_len | 0x80000000 );
66 }
67 if ( $value_len < 128 ) {
68 $result .= chr( $value_len );
69 } else {
70 $result .= pack( 'N', $value_len | 0x80000000 );
71 }
72
73 return $result . $name . $value;
74 }
75
76 /**
77 * Find cachetool configuration file.
78 *
79 * Searches in WordPress root, then home directory.
80 *
81 * @return string|null Path to config file or null if not found.
82 */
83 function gps_find_cachetool_config() {
84 $locations = array(
85 ABSPATH . '.cachetool.yml',
86 ABSPATH . 'cachetool.yml',
87 );
88
89 $home = getenv( 'HOME' );
90 if ( $home ) {
91 $locations[] = $home . '/.cachetool.yml';
92 }
93
94 foreach ( $locations as $path ) {
95 if ( file_exists( $path ) && is_readable( $path ) ) {
96 return $path;
97 }
98 }
99
100 return null;
101 }
102
103 /**
104 * Parse cachetool.yml configuration file.
105 *
106 * Simple YAML parser for the adapter and fastcgi keys.
107 *
108 * @param string $path Path to the config file.
109 * @return string|null Socket path/address or null if invalid.
110 */
111 function gps_parse_cachetool_yml( $path ) {
112 $contents = file_get_contents( $path );
113 if ( false === $contents ) {
114 return null;
115 }
116
117 $adapter = null;
118 $fastcgi = null;
119
120 $lines = explode( "\n", $contents );
121 foreach ( $lines as $line ) {
122 $line = trim( $line );
123
124 // Skip comments and empty lines.
125 if ( empty( $line ) || '#' === $line[0] ) {
126 continue;
127 }
128
129 // Parse key: value format.
130 if ( preg_match( '/^(\w+)\s*:\s*(.+)$/', $line, $matches ) ) {
131 $key = strtolower( $matches[1] );
132 $value = trim( $matches[2], " \t\n\r\0\x0B\"'" );
133
134 if ( 'adapter' === $key ) {
135 $adapter = $value;
136 } elseif ( 'fastcgi' === $key ) {
137 $fastcgi = $value;
138 }
139 }
140 }
141
142 // Only return socket if adapter is fastcgi.
143 if ( 'fastcgi' === $adapter && $fastcgi ) {
144 return $fastcgi;
145 }
146
147 return null;
148 }
149
150 /**
151 * Reset OPcache via direct FastCGI communication.
152 *
153 * Sends a FastCGI request directly to PHP-FPM with a custom parameter
154 * that triggers the reset handler in opcache-reset.php.
155 * No temporary files needed - completely inline.
156 *
157 * Security: The GPS_OPCACHE_RESET_INTERNAL param can only be set via
158 * direct FastCGI connection. nginx/Apache never forward arbitrary
159 * custom CGI params - only HTTP_* headers from HTTP requests.
160 *
161 * @param string $socket Socket path (Unix) or address (TCP host:port).
162 * @return bool True on success, false on failure.
163 */
164 function gps_fastcgi_opcache_reset( $socket ) {
165 // Determine connection string.
166 if ( strpos( $socket, '/' ) === 0 ) {
167 // Unix socket.
168 $connect_string = 'unix://' . $socket;
169 } elseif ( strpos( $socket, ':' ) !== false ) {
170 // TCP socket (host:port).
171 $connect_string = 'tcp://' . $socket;
172 } else {
173 // Assume TCP with default port.
174 $connect_string = 'tcp://' . $socket . ':9000';
175 }
176
177 // Connect to PHP-FPM. Silence errors - we check $fp instead.
178 $fp = @stream_socket_client( $connect_string, $errno, $errstr, 5 );
179 if ( ! $fp ) {
180 return false;
181 }
182
183 // Use the plugin's own file as the script.
184 $script_filename = __DIR__ . '/opcache-reset.php';
185
186 // Build FCGI_BEGIN_REQUEST.
187 $begin_request = pack( 'nCCCCCC', FCGI_RESPONDER, 0, 0, 0, 0, 0, 0, 0 );
188 $request = gps_fcgi_build_record( FCGI_BEGIN_REQUEST, $begin_request );
189
190 // Build params.
191 // GPS_OPCACHE_RESET_INTERNAL is a custom param that triggers the reset handler.
192 // This param can ONLY be set via direct FastCGI - nginx/Apache never forward it.
193 $params = '';
194 $params .= gps_fcgi_build_nvpair( 'SCRIPT_FILENAME', $script_filename );
195 $params .= gps_fcgi_build_nvpair( 'SCRIPT_NAME', '/opcache-reset.php' );
196 $params .= gps_fcgi_build_nvpair( 'REQUEST_METHOD', 'GET' );
197 $params .= gps_fcgi_build_nvpair( 'QUERY_STRING', '' );
198 $params .= gps_fcgi_build_nvpair( 'REQUEST_URI', '/opcache-reset.php' );
199 $params .= gps_fcgi_build_nvpair( 'DOCUMENT_ROOT', __DIR__ );
200 $params .= gps_fcgi_build_nvpair( 'SERVER_PROTOCOL', 'HTTP/1.1' );
201 $params .= gps_fcgi_build_nvpair( 'GATEWAY_INTERFACE', 'CGI/1.1' );
202 $params .= gps_fcgi_build_nvpair( 'SERVER_SOFTWARE', 'opcache-reset' );
203 $params .= gps_fcgi_build_nvpair( 'REMOTE_ADDR', '127.0.0.1' );
204 $params .= gps_fcgi_build_nvpair( 'REMOTE_PORT', '0' );
205 $params .= gps_fcgi_build_nvpair( 'SERVER_ADDR', '127.0.0.1' );
206 $params .= gps_fcgi_build_nvpair( 'SERVER_PORT', '80' );
207 $params .= gps_fcgi_build_nvpair( 'SERVER_NAME', 'localhost' );
208 // Custom param to trigger reset - not forwardable via HTTP.
209 $params .= gps_fcgi_build_nvpair( 'GPS_OPCACHE_RESET_INTERNAL', '1' );
210
211 $request .= gps_fcgi_build_record( FCGI_PARAMS, $params );
212 $request .= gps_fcgi_build_record( FCGI_PARAMS, '' ); // Empty params to end.
213 $request .= gps_fcgi_build_record( FCGI_STDIN, '' ); // Empty stdin.
214
215 // Send request.
216 fwrite( $fp, $request );
217
218 // Read response.
219 $response = '';
220 $response_length = 0;
221 while ( ! feof( $fp ) ) {
222 $data = fread( $fp, 8192 );
223 if ( false === $data ) {
224 break;
225 }
226 $response .= $data;
227 $response_length = strlen( $response );
228
229 // Check if we received FCGI_END_REQUEST.
230 if ( $response_length >= 8 ) {
231 $pos = 0;
232 while ( $pos + 8 <= $response_length ) {
233 $header = unpack( 'Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength', substr( $response, $pos, 8 ) );
234 if ( FCGI_END_REQUEST === $header['type'] ) {
235 fclose( $fp );
236 return true;
237 }
238 $pos += 8 + $header['contentLength'] + $header['paddingLength'];
239 }
240 }
241 }
242
243 fclose( $fp );
244 return true;
245 }
246