PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 5.2.7
Wordfence Security – Firewall, Malware Scan, and Login Security v5.2.7
8.2.2 8.2.1 8.2.0 3.7.1 3.7.2 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.8.8 3.8.9 3.9.1 4.0.1 4.0.2 4.0.3 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.0.9 5.1.1 5.1.2 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.1 5.3.10 5.3.11 5.3.12 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.3.8 5.3.9 6.0.1 6.0.10 6.0.11 6.0.12 6.0.14 6.0.15 6.0.16 6.0.17 6.0.18 6.0.19 6.0.2 6.0.20 6.0.21 6.0.22 6.0.23 6.0.24 6.0.25 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.0.8 6.0.9 6.1.1 6.1.10 6.1.11 6.1.12 6.1.14 6.1.15 6.1.16 6.1.17 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.1.9 6.2.0 6.2.1 6.2.10 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10 6.3.11 6.3.12 6.3.14 6.3.15 6.3.16 6.3.17 6.3.18 6.3.19 6.3.2 6.3.20 6.3.21 6.3.22 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.3.8 6.3.9 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1.0 7.1.1 7.1.10 7.1.11 7.1.12 7.1.14 7.1.15 7.1.16 7.1.17 7.1.18 7.1.19 7.1.2 7.1.20 7.1.3 7.1.4 7.1.5 7.1.6 7.1.7 7.1.8 7.1.9 7.10.0 7.10.1 7.10.2 7.10.3 7.10.4 7.10.5 7.10.6 7.10.7 7.11.0 7.11.1 7.11.2 7.11.3 7.11.4 7.11.5 7.11.6 7.11.7 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.4.0 7.4.1 7.4.10 7.4.11 7.4.12 7.4.14 7.4.2 7.4.3 trunk 7.4.4 1.1 7.4.5 1.2 7.4.6 1.3 7.4.7 1.3.1 7.4.8 1.3.2 7.4.9 1.3.3 7.5.0 1.4.2 7.5.1 1.4.3 7.5.10 1.4.4 7.5.11 1.4.5 7.5.2 1.4.6 7.5.3 1.4.7 7.5.4 1.4.8 7.5.5 1.5.1 7.5.6 1.5.2 7.5.7 1.5.3 7.5.8 1.5.4 7.5.9 1.5.5 7.6.0 1.5.6 7.6.1 2.0.1 7.6.2 2.0.2 7.7.0 2.0.3 7.7.1 2.0.5 7.8.0 2.0.6 7.8.1 2.0.7 7.8.2 2.1.0 7.9.0 2.1.1 7.9.1 2.1.2 7.9.2 2.1.3 7.9.3 2.1.4 8.0.0 2.1.5 8.0.1 3.0.2 8.0.2 3.0.3 8.0.3 3.0.4 8.0.4 3.0.5 8.0.5 3.0.6 8.1.0 3.0.7 8.1.1 3.0.8 8.1.2 3.0.9 8.1.3 3.1.0 8.1.4 3.1.1 v1.4.1 3.1.2 3.1.4 3.1.6 3.2.1 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.1 3.4.4 3.4.5 3.5.1 3.5.2 3.6.1 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
wordfence / lib / wfCache.php
wordfence / lib Last commit date
Diff 14 years ago whois 12 years ago .htaccess 14 years ago Diff.php 14 years ago GeoIP.dat 11 years ago IPTraf.php 11 years ago conntest.php 11 years ago dashboard.php 11 years ago diffResult.php 11 years ago email_genericAlert.php 11 years ago email_newIssues.php 11 years ago email_unlockRequest.php 11 years ago menuHeader.php 11 years ago menu_activity.php 11 years ago menu_blockedIPs.php 11 years ago menu_countryBlocking.php 11 years ago menu_options.php 11 years ago menu_rangeBlocking.php 11 years ago menu_scan.php 11 years ago menu_scanSchedule.php 11 years ago menu_sitePerf.php 11 years ago menu_sitePerfStats.php 11 years ago menu_twoFactor.php 11 years ago menu_whois.php 11 years ago pageTitle.php 13 years ago schedWeekEntry.php 12 years ago sysinfo.php 11 years ago unknownFiles.php 11 years ago viewFullActivityLog.php 11 years ago wf503.php 12 years ago wfAPI.php 11 years ago wfAction.php 14 years ago wfArray.php 13 years ago wfBrowscap.php 11 years ago wfBrowscapCache.php 11 years ago wfBulkCountries.php 13 years ago wfCache.php 11 years ago wfConfig.php 11 years ago wfCountryMap.php 13 years ago wfCrawl.php 11 years ago wfDB.php 11 years ago wfDict.php 14 years ago wfGeoIP.php 13 years ago wfIssues.php 11 years ago wfLockedOut.php 13 years ago wfLog.php 11 years ago wfRate.php 14 years ago wfScan.php 11 years ago wfScanEngine.php 11 years ago wfSchema.php 11 years ago wfUnlockMsg.php 11 years ago wfUtils.php 11 years ago wfViewResult.php 11 years ago wordfenceClass.php 11 years ago wordfenceConstants.php 11 years ago wordfenceHash.php 11 years ago wordfenceScanner.php 11 years ago wordfenceURLHoover.php 11 years ago
wfCache.php
677 lines
1 <?php
2 class wfCache {
3 private static $cacheType = false;
4 private static $fileCache = array();
5 private static $cacheStats = array();
6 private static $cacheClearedThisRequest = false;
7 private static $clearScheduledThisRequest = false;
8 private static $lastRecursiveDeleteError = false;
9 public static function setupCaching(){
10 self::$cacheType = wfConfig::get('cacheType');
11 if(self::$cacheType != 'php' && self::$cacheType != 'falcon'){
12 return; //cache is disabled
13 }
14 if(wfUtils::hasLoginCookie()){
15 add_action('publish_post', 'wfCache::action_publishPost');
16 add_action('publish_page', 'wfCache::action_publishPost');
17 foreach(array('clean_object_term_cache', 'clean_post_cache', 'clean_term_cache', 'clean_page_cache', 'after_switch_theme', 'customize_save_after', 'activated_plugin', 'deactivated_plugin', 'update_option_sidebars_widgets') as $action){
18 add_action($action, 'wfCache::action_clearPageCache'); //Schedules a cache clear for immediately so it won't lag current request.
19 }
20 if($_SERVER['REQUEST_METHOD'] == 'POST'){
21 foreach(array(
22 '/\/wp\-admin\/options\.php$/',
23 '/\/wp\-admin\/options\-permalink\.php$/'
24 ) as $pattern){
25 if(preg_match($pattern, $_SERVER['REQUEST_URI'])){
26 self::scheduleCacheClear();
27 break;
28 }
29 }
30 }
31 }
32 add_action('wordfence_cache_clear', 'wfCache::scheduledCacheClear');
33 add_action('wordfence_update_blocked_IPs', 'wfCache::scheduleUpdateBlockedIPs');
34 add_action('comment_post', 'wfCache::action_commentPost'); //Might not be logged in
35 add_filter('wp_redirect', 'wfCache::redirectFilter');
36
37 //Routines to clear cache run even if cache is disabled
38 $file = self::fileFromRequest( ($_SERVER['HTTP_HOST'] ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']), $_SERVER['REQUEST_URI']);
39 $fileDeleted = false;
40 $doDelete = false;
41 if($_SERVER['REQUEST_METHOD'] != 'GET'){ //If our URL is hit with a POST, PUT, DELETE or any other non 'GET' request, then clear cache.
42 $doDelete = true;
43 }
44
45 if($doDelete){
46 @unlink($file);
47 $fileDeleted = true;
48 }
49
50
51 add_action('wp_logout', 'wfCache::logout');
52 if(self::isCachable()){
53 if( (! $fileDeleted) && self::$cacheType == 'php'){ //Then serve the file if it's still valid
54 $stat = @stat($file);
55 if($stat){
56 $age = time() - $stat[9];
57 if($age < 10000){
58 readfile($file); //sends file to stdout
59 die();
60 }
61 }
62 }
63 ob_start('wfCache::obComplete'); //Setup routine to store the file
64 }
65 }
66 public static function redirectFilter($status){
67 if(! defined('WFDONOTCACHE')){
68 define('WFDONOTCACHE', true);
69 }
70 return $status;
71 }
72 public static function isCachable(){
73 if(defined('WFDONOTCACHE') || defined('DONOTCACHEPAGE') || defined('DONOTCACHEDB') || defined('DONOTCACHEOBJECT')){ //If you want to tell Wordfence not to cache something in another plugin, simply define one of these.
74 return false;
75 }
76 if(! wfConfig::get('allowHTTPSCaching')){
77 if(self::isHTTPSPage()){
78 return false;
79 }
80 }
81
82 if(is_admin()){ return false; } //dont cache any admin pages.
83 $uri = $_SERVER['REQUEST_URI'];
84
85 if(strrpos($uri, '/') !== strlen($uri) - 1){ //must end with a '/' char.
86 return false;
87 }
88 if($_SERVER['REQUEST_METHOD'] != 'GET'){ return false; } //Only cache GET's
89 if(isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0 && (! preg_match('/^\d+=\d+$/', $_SERVER['QUERY_STRING'])) ){ //Don't cache query strings unless they are /?123132423=123123234 DDoS style.
90 return false;
91 }
92 //wordpress_logged_in_[hash] cookies indicates logged in
93 if(is_array($_COOKIE)){
94 foreach(array_keys($_COOKIE) as $c){
95 foreach(array('comment_author','wp-postpass','wf_logout','wordpress_logged_in','wptouch_switch_toggle','wpmp_switcher') as $b){
96 if(strpos($c, $b) !== false){ return false; } //contains a cookie which indicates user must not be cached
97 }
98 }
99 }
100 $ex = wfConfig::get('cacheExclusions', false);
101 if($ex){
102 $ex = unserialize($ex);
103 foreach($ex as $v){
104 if($v['pt'] == 'eq'){ if(strtolower($uri) == strtolower($v['p'])){ return false; } }
105 if($v['pt'] == 's'){ if(stripos($uri, $v['p']) === 0){ return false; } }
106 if($v['pt'] == 'e'){ if(stripos($uri, $v['p']) === (strlen($uri) - strlen($v['p'])) ){ return false; } }
107 if($v['pt'] == 'c'){ if(stripos($uri, $v['p']) !== false){ return false; } }
108 if($v['pt'] == 'uac'){ if(stripos($_SERVER['HTTP_USER_AGENT'], $v['p']) !== false){ return false; } } //User-agent contains
109 if($v['pt'] == 'uaeq'){ if(strtolower($_SERVER['HTTP_USER_AGENT']) == strtolower($v['p'])){ return false; } } //user-agent equals
110 if($v['pt'] == 'cc'){
111 foreach($_COOKIE as $cookieName){
112 if(stripos($cookieName, $v['p']) !== false){ //Cookie name contains pattern
113 return false;
114 }
115 }
116 }
117 }
118 }
119 return true;
120 }
121 public static function isHTTPSPage(){
122 if( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] != 'off'){
123 return true;
124 }
125 if( !empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ){ //In case we're behind a proxy and user used HTTPS.
126 return true;
127 }
128 return false;
129 }
130 public static function obComplete($buffer = ''){
131 if(function_exists('is_404') && is_404()){
132 return false;
133 }
134
135 if(defined('WFDONOTCACHE') || defined('DONOTCACHEPAGE') || defined('DONOTCACHEDB') || defined('DONOTCACHEOBJECT')){
136 //These constants may have been set after we did the initial isCachable check by e.g. wp_redirect filter. If they're set then just return the buffer and don't cache.
137 return $buffer;
138 }
139 if(strlen($buffer) < 1000){ //The average web page size is 1246,000 bytes. If web page is less than 1000 bytes, don't cache it.
140 return $buffer;
141 }
142
143 $file = self::fileFromRequest( ($_SERVER['HTTP_HOST'] ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']), $_SERVER['REQUEST_URI']);
144 self::makeDirIfNeeded($file);
145 $append = "";
146 $appendGzip = "";
147 if(wfConfig::get('addCacheComment', false)){
148 $append = "\n<!-- Cached by Wordfence ";
149 if(wfConfig::get('cacheType', false) == 'falcon'){
150 $append .= "Falcon Engine. ";
151 } else {
152 $append .= "PHP Caching Engine. ";
153 }
154 $append .= "Time created on server: " . date('Y-m-d H:i:s T') . ". ";
155 $append .= "Is HTTPS page: " . (self::isHTTPSPage() ? 'HTTPS' : 'no') . ". ";
156 $append .= "Page size: " . strlen($buffer) . " bytes. ";
157 $append .= "Host: " . ($_SERVER['HTTP_HOST'] ? wp_kses($_SERVER['HTTP_HOST'], array()) : wp_kses($_SERVER['SERVER_NAME'], array())) . ". ";
158 $append .= "Request URI: " . wp_kses($_SERVER['REQUEST_URI'], array()) . " ";
159 $appendGzip = $append . " Encoding: GZEncode -->\n";
160 $append .= " Encoding: Uncompressed -->\n";
161 }
162
163 @file_put_contents($file, $buffer . $append, LOCK_EX);
164 chmod($file, 0655);
165 if(self::$cacheType == 'falcon'){ //create gzipped files so we can send precompressed files
166 $file .= '_gzip';
167 @file_put_contents($file, gzencode($buffer . $appendGzip, 9), LOCK_EX);
168 chmod($file, 0655);
169 }
170 return $buffer;
171 }
172 public static function fileFromRequest($host, $URI){
173 return self::fileFromURI($host, $URI, self::isHTTPSPage());
174 }
175 public static function fileFromURI($host, $URI, $isHTTPS){
176 $key = $host . $URI . ($isHTTPS ? '_HTTPS' : '');
177 if(isset(self::$fileCache[$key])){ return self::$fileCache[$key]; }
178 $host = preg_replace('/[^a-zA-Z0-9\-\.]+/', '', $host);
179 $URI = preg_replace('/(?:[^a-zA-Z0-9\-\_\.\~\/]+|\.{2,})/', '', $URI); //Strip out bad chars and multiple dots
180 if(preg_match('/\/*([^\/]*)\/*([^\/]*)\/*([^\/]*)\/*([^\/]*)\/*([^\/]*)(.*)$/', $URI, $matches)){
181 $URI = $matches[1] . '/';
182 for($i = 2; $i <= 6; $i++){
183 $URI .= strlen($matches[$i]) > 0 ? $matches[$i] : '';
184 $URI .= $i < 6 ? '~' : '';
185 }
186 }
187 $ext = '';
188 if($isHTTPS){ $ext = '_https'; }
189 $file = WP_CONTENT_DIR . '/wfcache/' . $host . '_' . $URI . '_wfcache' . $ext . '.html';
190 self::$fileCache[$key] = $file;
191 return $file;
192 }
193 public static function makeDirIfNeeded($file){
194 $file = preg_replace('/\/[^\/]*$/', '', $file);
195 if(! is_dir($file)){
196 @mkdir($file, 0755, true);
197 }
198 }
199 public static function logout(){
200 wfUtils::setcookie('wf_logout', '1', 0, null, null, null, true);
201 }
202 public static function cacheDirectoryTest(){
203 $cacheDir = WP_CONTENT_DIR . '/wfcache/';
204 if(! is_dir($cacheDir)){
205 if(! @mkdir($cacheDir, 0755, true)){
206 $err = error_get_last();
207 $msg = "The directory $cacheDir does not exist and we could not create it.";
208 if($err){
209 $msg .= ' The error we received was: ' . $err['message'];
210 }
211 return $msg;
212 }
213 }
214 if(! @file_put_contents($cacheDir . 'test.php', 'test')){
215 $err = error_get_last();
216 $msg = "We could not write to the file $cacheDir" . "test.php when testing if the cache directory is writable.";
217 if($err){
218 $msg .= " The error was: " . $err['message'];
219 }
220 return $msg;
221 }
222 return false; //Everything is OK
223 }
224 public static function action_publishPost($id){
225 $perm = get_permalink($id);
226 self::deleteFileFromPermalink($perm);
227 self::scheduleCacheClear();
228 }
229 public static function action_commentPost($commentID){
230 $c = get_comment($commentID, ARRAY_A);
231 $perm = get_permalink($c['comment_post_ID']);
232 self::deleteFileFromPermalink($perm);
233 self::scheduleCacheClear();
234 }
235 public static function action_clearPageCache(){ //Can safely call this as many times as we like because it'll only schedule one clear
236 self::scheduleCacheClear();
237 }
238 public static function scheduleCacheClear(){
239 if(self::$clearScheduledThisRequest){ return; }
240 self::$clearScheduledThisRequest = true;
241 wp_schedule_single_event(time() - 15, 'wordfence_cache_clear', array( rand(0,999999999) )); //rand makes sure this is called every time and isn't subject to the 10 minute window where the same event won't be run twice with wp_schedule_single_event
242 $url = admin_url('admin-ajax.php');
243 wp_remote_get($url);
244 }
245 public static function scheduledCacheClear($random){
246 self::clearPageCacheSafe(); //Will only run if clearPageCache() has not run this request
247 }
248 public static function deleteFileFromPermalink($perm){
249 if(preg_match('/\/\/([^\/]+)(\/.*)$/', $perm, $matches)){
250 $host = $matches[1];
251 $uri = $matches[2];
252 $file = self::fileFromRequest($host, $uri);
253 if(is_file($file)){
254 @unlink($file);
255 }
256 }
257 }
258 public static function getCacheStats(){
259 self::$cacheStats = array(
260 'files' => 0,
261 'dirs' => 0,
262 'data' => 0,
263 'compressedFiles' => 0,
264 'compressedKBytes' => 0,
265 'uncompressedFiles' => 0,
266 'uncompressedKBytes' => 0,
267 'oldestFile' => false,
268 'newestFile' => false,
269 'largestFile' => 0,
270 );
271 self::recursiveStats(WP_CONTENT_DIR . '/wfcache/');
272 return self::$cacheStats;
273 }
274 private static function recursiveStats($dir){
275 $files = array_diff(scandir($dir), array('.','..'));
276 foreach($files as $file){
277 $fullPath = $dir . '/' . $file;
278 if(is_dir($fullPath)){
279 self::$cacheStats['dirs']++;
280 self::recursiveStats($fullPath);
281 } else {
282 if($file == 'clear.lock'){ continue; }
283 self::$cacheStats['files']++;
284 $stat = stat($fullPath);
285 if(is_array($stat)){
286 $size = $stat[7];
287 if($size){
288 $size = round($size / 1024);
289 self::$cacheStats['data'] += $size;
290 if(strrpos($file, '_gzip') == strlen($file) - 6){
291 self::$cacheStats['compressedFiles']++;
292 self::$cacheStats['compressedKBytes'] += $size;
293 } else {
294 self::$cacheStats['uncompressedFiles']++;
295 self::$cacheStats['uncompressedKBytes'] += $size;
296 }
297 if(self::$cacheStats['largestFile'] < $size){
298 self::$cacheStats['largestFile'] = $size;
299 }
300 }
301
302 $ctime = $stat[10];
303 if(self::$cacheStats['oldestFile'] > $ctime || self::$cacheStats['oldestFile'] === false){
304 self::$cacheStats['oldestFile'] = $ctime;
305 }
306 if(self::$cacheStats['newestFile'] === false || self::$cacheStats['newestFile'] < $ctime){
307 self::$cacheStats['newestFile'] = $ctime;
308 }
309 }
310 }
311 }
312 }
313 public static function clearPageCacheSafe(){
314 if(self::$cacheClearedThisRequest){ return; }
315 self::$cacheClearedThisRequest = true;
316 self::clearPageCache();
317 }
318 public static function clearPageCache(){ //If a clear is in progress this does nothing.
319 self::$cacheStats = array(
320 'dirsDeleted' => 0,
321 'filesDeleted' => 0,
322 'totalData' => 0,
323 'totalErrors' => 0,
324 'error' => '',
325 );
326 $cacheClearLock = WP_CONTENT_DIR . '/wfcache/clear.lock';
327 if(! is_file($cacheClearLock)){
328 if(! touch($cacheClearLock)){
329 self::$cacheStats['error'] = "Could not create a lock file $cacheClearLock to clear the cache.";
330 self::$cacheStats['totalErrors']++;
331 return self::$cacheStats;
332 }
333 }
334 $fp = fopen($cacheClearLock, 'w');
335 if(! $fp){
336 self::$cacheStats['error'] = "Could not open the lock file $cacheClearLock to clear the cache. Please make sure the directory is writable by your web server.";
337 self::$cacheStats['totalErrors']++;
338 return self::$cacheStats;
339 }
340 if(flock($fp, LOCK_EX | LOCK_NB)){ //non blocking exclusive flock attempt. If we get a lock then it continues and returns true. If we don't lock, then return false, don't block and don't clear the cache.
341 // This logic means that if a cache clear is currently in progress we don't try to clear the cache.
342 // This prevents web server children from being queued up waiting to be able to also clear the cache.
343 self::$lastRecursiveDeleteError = false;
344 self::recursiveDelete(WP_CONTENT_DIR . '/wfcache/');
345 if(self::$lastRecursiveDeleteError){
346 self::$cacheStats['error'] = self::$lastRecursiveDeleteError;
347 self::$cacheStats['totalErrors']++;
348 }
349 flock($fp, LOCK_UN);
350 }
351 fclose($fp);
352
353 return self::$cacheStats;
354 }
355 public static function recursiveDelete($dir){
356 $files = array_diff(scandir($dir), array('.','..'));
357 foreach ($files as $file) {
358 if(is_dir($dir . '/' . $file)){
359 if(! self::recursiveDelete($dir . '/' . $file)){
360 return false;
361 }
362 } else {
363 if($file == 'clear.lock'){ continue; } //Don't delete our lock file
364 $size = filesize($dir . '/' . $file);
365 if($size){
366 self::$cacheStats['totalData'] += round($size / 1024);
367 }
368 if(strpos($dir, 'wfcache/') === false){
369 self::$lastRecursiveDeleteError = "Not deleting file in directory $dir because it appears to be in the wrong path.";
370 self::$cacheStats['totalErrors']++;
371 return false; //Safety check that we're in a subdir of the cache
372 }
373 if(@unlink($dir . '/' . $file)){
374 self::$cacheStats['filesDeleted']++;
375 } else {
376 self::$lastRecursiveDeleteError = "Could not delete file " . $dir . "/" . $file . " : " . wfUtils::getLastError();
377 self::$cacheStats['totalErrors']++;
378 return false;
379 }
380 }
381 }
382 if($dir != WP_CONTENT_DIR . '/wfcache/'){
383 if(strpos($dir, 'wfcache/') === false){
384 self::$lastRecursiveDeleteError = "Not deleting directory $dir because it appears to be in the wrong path.";
385 self::$cacheStats['totalErrors']++;
386 return; //Safety check that we're in a subdir of the cache
387 }
388 if(@rmdir($dir)){
389 self::$cacheStats['dirsDeleted']++;
390 } else {
391 self::$lastRecursiveDeleteError = "Could not delete directory $dir : " . wfUtils::getLastError();
392 self::$cacheStats['totalErrors']++;
393 return false;
394 }
395 return true;
396 } else {
397 return true;
398 }
399 return true;
400 }
401 public static function addHtaccessCode($action){
402 if($action != 'add' && $action != 'remove'){
403 die("Error: addHtaccessCode must be called with 'add' or 'remove' as param");
404 }
405 $htaccessPath = self::getHtaccessPath();
406 if(! $htaccessPath){
407 return "Wordfence could not find your .htaccess file.";
408 }
409 $fh = @fopen($htaccessPath, 'r+');
410 if(! $fh){
411 $err = error_get_last();
412 return $err['message'];
413 }
414 flock($fh, LOCK_EX);
415 fseek($fh, 0, SEEK_SET); //start of file
416 clearstatcache();
417 $contents = fread($fh, filesize($htaccessPath));
418 if(! $contents){
419 fclose($fh);
420 return "Could not read from $htaccessPath";
421 }
422 $contents = preg_replace('/#WFCACHECODE.*WFCACHECODE[r\s\n\t]*/s', '', $contents);
423 if($action == 'add'){
424 $code = self::getHtaccessCode();
425 $contents = $code . "\n" . $contents;
426 }
427 ftruncate($fh, 0);
428 fseek($fh, 0, SEEK_SET);
429 fwrite($fh, $contents);
430 flock($fh, LOCK_UN);
431 fclose($fh);
432 return false;
433 }
434 public static function getHtaccessCode(){
435 $siteURL = site_url();
436 $homeURL = home_url();
437 $pathPrefix = "";
438 if(preg_match('/^https?:\/\/[^\/]+\/(.+)$/i', $siteURL, $matches)){
439 $path = $matches[1];
440 $path = preg_replace('/^\//', '', $path);
441 $path = preg_replace('/\/$/', '', $path);
442 $pathPrefix = '/' . $path; // Which is: /my/path
443 }
444 $matchCaps = '$1/$2~$3~$4~$5~$6';
445 if(preg_match('/^https?:\/\/[^\/]+\/(.+)$/i', $homeURL, $matches)){
446 $path = $matches[1];
447 $path = preg_replace('/^\//', '', $path);
448 $path = preg_replace('/\/$/', '', $path);
449 $pieces = explode('/', $path);
450 if(count($pieces) == 1){
451 # No path: "/wp-content/wfcache/%{HTTP_HOST}_$1/$2~$3~$4~$5~$6_wfcache%{ENV:WRDFNC_HTTPS}.html%{ENV:WRDFNC_ENC}" [L]
452 # One path: "/mdm/wp-content/wfcache/%{HTTP_HOST}_mdm/$1~$2~$3~$4~$5_wfcache%{ENV:WRDFNC_HTTPS}.html%{ENV:WRDFNC_ENC}" [L]
453 $matchCaps = $pieces[0] . '/$1~$2~$3~$4~$5';
454 } else if(count($pieces) == 2){
455 $matchCaps = $pieces[0] . '/' . $pieces[1] . '/$1~$2~$3~$4';
456 } else {
457 $matchCaps = '$1/$2~$3~$4~$5~$6'; #defaults to the regular setting but this won't work. However user should already have gotten a warning that we don't support sites more than 2 dirs deep with falcon.
458 }
459 }
460 $sslString = "RewriteCond %{HTTPS} off";
461 if(wfConfig::get('allowHTTPSCaching')){
462 $sslString = "";
463 }
464 $otherRewriteConds = "";
465 $ex = wfConfig::get('cacheExclusions', false);
466 if($ex){
467 $ex = unserialize($ex);
468 foreach($ex as $v){
469 if($v['pt'] == 'uac'){
470 $otherRewriteConds .= "\n\tRewriteCond %{HTTP_USER_AGENT} !" . self::regexSpaceFix(preg_quote($v['p'])) . " [NC]";
471 }
472 if($v['pt'] == 'uaeq'){
473 $otherRewriteConds .= "\n\tRewriteCond %{HTTP_USER_AGENT} !^" . self::regexSpaceFix(preg_quote($v['p'])) . "$ [NC]";
474 }
475 if($v['pt'] == 'cc'){
476 $otherRewriteConds .= "\n\tRewriteCond %{HTTP_COOKIE} !" . self::regexSpaceFix(preg_quote($v['p'])) . " [NC]";
477 }
478 }
479 }
480
481 //We exclude URLs that are banned so that Wordfence PHP code can catch the IP address, then ban that IP and the ban is added to .htaccess.
482 $excludedURLs = "";
483 if(wfConfig::get('bannedURLs', false)){
484 foreach(explode(',', wfConfig::get('bannedURLs', false)) as $URL){
485 $excludedURLs .= "RewriteCond %{REQUEST_URI} !^" . self::regexSpaceFix(preg_quote(trim($URL))) . "$\n\t";
486 }
487 }
488
489 $code = <<<EOT
490 #WFCACHECODE - Do not remove this line. Disable Web Caching in Wordfence to remove this data.
491 <IfModule mod_deflate.c>
492 AddOutputFilterByType DEFLATE text/css text/x-component application/x-javascript application/javascript text/javascript text/x-js text/html text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon application/json
493 <IfModule mod_headers.c>
494 Header append Vary User-Agent env=!dont-vary
495 </IfModule>
496 <IfModule mod_mime.c>
497 AddOutputFilter DEFLATE js css htm html xml
498 </IfModule>
499 </IfModule>
500 <IfModule mod_mime.c>
501 AddType text/html .html_gzip
502 AddEncoding gzip .html_gzip
503 AddType text/xml .xml_gzip
504 AddEncoding gzip .xml_gzip
505 </IfModule>
506 <IfModule mod_setenvif.c>
507 SetEnvIfNoCase Request_URI \.html_gzip$ no-gzip
508 SetEnvIfNoCase Request_URI \.xml_gzip$ no-gzip
509 </IfModule>
510 <IfModule mod_headers.c>
511 Header set Vary "Accept-Encoding, Cookie"
512 </IfModule>
513 <IfModule mod_rewrite.c>
514 #Prevents garbled chars in cached files if there is no default charset.
515 AddDefaultCharset utf-8
516
517 #Cache rules:
518 RewriteEngine On
519 RewriteBase /
520 RewriteCond %{HTTPS} on
521 RewriteRule .* - [E=WRDFNC_HTTPS:_https]
522 RewriteCond %{HTTP:Accept-Encoding} gzip
523 RewriteRule .* - [E=WRDFNC_ENC:_gzip]
524 RewriteCond %{REQUEST_METHOD} !=POST
525 {$sslString}
526 RewriteCond %{QUERY_STRING} ^(?:\d+=\d+)?$
527 RewriteCond %{REQUEST_URI} (?:\/|\.html)$ [NC]
528 {$excludedURLs}
529 RewriteCond %{HTTP_COOKIE} !(comment_author|wp\-postpass|wf_logout|wordpress_logged_in|wptouch_switch_toggle|wpmp_switcher) [NC]
530 {$otherRewriteConds}
531 RewriteCond %{REQUEST_URI} \/*([^\/]*)\/*([^\/]*)\/*([^\/]*)\/*([^\/]*)\/*([^\/]*)(.*)$
532 RewriteCond "%{DOCUMENT_ROOT}{$pathPrefix}/wp-content/wfcache/%{HTTP_HOST}_%1/%2~%3~%4~%5~%6_wfcache%{ENV:WRDFNC_HTTPS}.html%{ENV:WRDFNC_ENC}" -f
533 RewriteRule \/*([^\/]*)\/*([^\/]*)\/*([^\/]*)\/*([^\/]*)\/*([^\/]*)(.*)$ "{$pathPrefix}/wp-content/wfcache/%{HTTP_HOST}_{$matchCaps}_wfcache%{ENV:WRDFNC_HTTPS}.html%{ENV:WRDFNC_ENC}" [L]
534 </IfModule>
535 #Do not remove this line. Disable Web caching in Wordfence to remove this data - WFCACHECODE
536 EOT;
537 return $code;
538 }
539 private static function regexSpaceFix($str){
540 return str_replace(' ', '\\s', $str);
541 }
542 public static function scheduleUpdateBlockedIPs(){
543 wp_clear_scheduled_hook('wordfence_update_blocked_IPs');
544 if(wfConfig::get('cacheType') != 'falcon'){
545 self::updateBlockedIPs('remove'); //Fail silently if .htaccess is not readable. Will fall back to old blocking via WP
546 return;
547 }
548 self::updateBlockedIPs('add'); //Fail silently if .htaccess is not readable. Will fall back to old blocking via WP
549 wp_schedule_single_event(time() + 300, 'wordfence_update_blocked_IPs');
550 }
551 public static function updateBlockedIPs($action){ //'add' or 'remove'
552 if(wfConfig::get('cacheType') != 'falcon'){ return; }
553
554 $htaccessPath = self::getHtaccessPath();
555 if(! $htaccessPath){
556 return "Wordfence could not find your .htaccess file.";
557 }
558 if($action == 'remove'){
559 $fh = @fopen($htaccessPath, 'r+');
560 if(! $fh){
561 $err = error_get_last();
562 return $err['message'];
563 }
564 flock($fh, LOCK_EX);
565 fseek($fh, 0, SEEK_SET); //start of file
566 clearstatcache();
567 $contents = @fread($fh, filesize($htaccessPath));
568 if(! $contents){
569 fclose($fh);
570 return "Could not read from $htaccessPath";
571 }
572
573 $contents = preg_replace('/#WFIPBLOCKS.*WFIPBLOCKS[r\s\n\t]*/s', '', $contents);
574
575 ftruncate($fh, 0);
576 fseek($fh, 0, SEEK_SET);
577 @fwrite($fh, $contents);
578 flock($fh, LOCK_UN);
579 fclose($fh);
580 return false;
581 } else if($action == 'add'){
582 $fh = @fopen($htaccessPath, 'r+');
583 if(! $fh){
584 $err = error_get_last();
585 return $err['message'];
586 }
587
588 $lines = array();
589 $wfLog = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
590 $IPs = $wfLog->getBlockedIPsAddrOnly();
591 if(sizeof($IPs) > 0){
592 foreach($IPs as $IP){
593 $lines[] = "Deny from $IP\n";
594 }
595 }
596 $ranges = $wfLog->getRangesBasic();
597 $browserAdded = false;
598 $browserLines = array();
599 if($ranges){
600 foreach($ranges as $r){
601 $arr = explode('|', $r);
602 $range = isset($arr[0]) ? $arr[0] : false;
603 $browser = isset($arr[1]) ? $arr[1] : false;
604
605 if($range && $browser){
606 continue; //Don't process browser and range combos
607 } else if($range){
608 $ips = explode('-', $range);
609 $cidrs = wfUtils::rangeToCIDRs($ips[0], $ips[1]);
610 $hIPs = wfUtils::inet_ntoa($ips[0]) . ' - ' . wfUtils::inet_ntoa($ips[1]);
611 if(sizeof($cidrs) > 0){
612 $lines[] = '#Start of blocking code for IP range: ' . $hIPs . "\n";
613 foreach($cidrs as $c){
614 $lines[] = "Deny from $c\n";
615 }
616 $lines[] = '#End of blocking code for IP range: ' . $hIPs . "\n";
617 }
618 } else if($browser){
619 $browserLines[] = "\t#Blocking code for browser pattern: $browser\n";
620 $browser = preg_replace('/([\-\_\.\+\!\@\#\$\%\^\&\(\)\[\]\{\}\/])/', "\\\\$1", $browser);
621 $browser = preg_replace('/\*/', '.*', $browser);
622 $browserLines[] = "\tSetEnvIf User-Agent " . $browser . " WordfenceBadBrowser=1\n";
623 $browserAdded = true;
624 }
625 }
626 }
627 if($browserAdded){
628 $lines[] = "<IfModule mod_setenvif.c>\n";
629 foreach($browserLines as $l){
630 $lines[] = $l;
631 }
632 $lines[] = "\tDeny from env=WordfenceBadBrowser\n";
633 $lines[] = "</IfModule>\n";
634 }
635 }
636 $blockCode = "#WFIPBLOCKS - Do not remove this line. Disable Web Caching in Wordfence to remove this data.\nOrder Deny,Allow\n";
637 $blockCode .= implode('', $lines);
638 $blockCode .= "#Do not remove this line. Disable Web Caching in Wordfence to remove this data - WFIPBLOCKS\n";
639
640
641 //Minimize time between lock/unlock
642 flock($fh, LOCK_EX);
643 fseek($fh, 0, SEEK_SET); //start of file
644 clearstatcache(); //Or we get the wrong size from a cached entry and corrupt the file
645 $contents = @fread($fh, filesize($htaccessPath));
646 if(! $contents){
647 fclose($fh);
648 return "Could not read from $htaccessPath";
649 }
650 $contents = preg_replace('/#WFIPBLOCKS.*WFIPBLOCKS[r\s\n\t]*/s', '', $contents);
651 $contents = $blockCode . $contents;
652 ftruncate($fh, 0);
653 fseek($fh, 0, SEEK_SET);
654 @fwrite($fh, $contents);
655 flock($fh, LOCK_UN);
656 fclose($fh);
657 return false;
658 }
659 public static function getHtaccessPath(){
660 if(file_exists(ABSPATH . '/.htaccess')){
661 return ABSPATH . '/.htaccess';
662 }
663 if(preg_match('/^https?:\/\/[^\/]+\/?$/i', home_url()) && preg_match('/^https?:\/\/[^\/]+\/.+/i', site_url())){
664 $path = realpath(ABSPATH . '/../.htaccess');
665 if(file_exists($path)){
666 return $path;
667 }
668 }
669 return false;
670 }
671 public static function doNotCache(){
672 if(! defined('WFDONOTCACHE')){
673 define('WFDONOTCACHE', true);
674 }
675 }
676 }
677