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 |