: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$hiddenReadmeFile = $readmePathInfo['filename'] . '.' . wp_hash('readme') . '.' . $readmePathInfo['extension'];
return @rename($readmePath, $readmePathInfo['dirname'] . '/' . $hiddenReadmeFile);
* @param string $readmePath
public static function showReadme($readmePath = null) {
if ($readmePath === null) {
$readmePath = ABSPATH . 'readme.html';
$readmePathInfo = pathinfo($readmePath);
require_once(ABSPATH . WPINC . '/pluggable.php');
$hiddenReadmeFile = $readmePathInfo['dirname'] . '/' . $readmePathInfo['filename'] . '.' . wp_hash('readme') . '.' . $readmePathInfo['extension'];
if (file_exists($hiddenReadmeFile)) {
return @rename($hiddenReadmeFile, $readmePath);
public static function htaccessAppend($code)
$htaccess = wfCache::getHtaccessPath();
$content = self::htaccess();
if (wfUtils::isNginx() || !is_writable($htaccess)) {
if (strpos($content, $code) === false) {
// make sure we write this once
file_put_contents($htaccess, $content . "\n" . trim($code), LOCK_EX);
public static function htaccessPrepend($code)
$htaccess = wfCache::getHtaccessPath();
$content = self::htaccess();
if (wfUtils::isNginx() || !is_writable($htaccess)) {
if (strpos($content, $code) === false) {
// make sure we write this once
file_put_contents($htaccess, trim($code) . "\n" . $content, LOCK_EX);
public static function htaccess() {
$htaccess = wfCache::getHtaccessPath();
if (is_readable($htaccess) && !wfUtils::isNginx()) {
return file_get_contents($htaccess);
public static function arrayReplaceKey($array, $oldKey, $newKey) {
$keys = array_keys($array);
if (($index = array_search($oldKey, $keys)) === false) {
throw new Exception(sprintf('Key "%s" does not exist', $oldKey));
return array_combine($keys, array_values($array));
* Takes a string that may have characters that will be interpreted as invalid UTF-8 byte sequences and translates them into a string of the equivalent hex sequence.
public static function potentialBinaryStringToHTML($string, $inline = false, $allowmb4 = false) {
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 0);
$span = '<span class="wf-hex-sequence">';
$span = '<span style="color:#587ECB">';
for ($i = 0; $i < wfUtils::strlen($string); $i++) {
$output .= $span . '\x' . str_pad(dechex($b), 2, '0', STR_PAD_LEFT) . '</span>';
$output .= htmlspecialchars($c, ENT_QUOTES, 'ISO-8859-1');
else { //Assume multi-byte UTF-8
while (($test & 0x80) > 0) {
$test = (($test << 1) & 0xff);
$brokenUTF8 = ($i + $bytes > wfUtils::strlen($string) || $bytes == 1);
if (!$brokenUTF8) { //Make sure we have all the bytes
for ($n = 1; $n < $bytes; $n++) {
if (($b2 & 0xc0) != 0x80) {
if (!$brokenUTF8) { //Ensure the byte sequences are within the accepted ranges: https://tools.ietf.org/html/rfc3629
* UTF8-octets = *( UTF8-char )
* UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
* UTF8-2 = %xC2-DF UTF8-tail
* UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
* %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
* UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
* %xF4 %x80-8F 2( UTF8-tail )
$testString = wfUtils::substr($string, $i, $bytes);
'[\xc2-\xdf][\x80-\xbf]' . //UTF8-2
'|' . '\xe0[\xa0-\xbf][\x80-\xbf]' . //UTF8-3
'|' . '[\xe1-\xec][\x80-\xbf]{2}' .
'|' . '\xed[\x80-\x9f][\x80-\xbf]' .
'|' . '[\xee-\xef][\x80-\xbf]{2}';
$regex .= '|' . '\xf0[\x90-\xbf][\x80-\xbf]{2}' . //UTF8-4
'|' . '[\xf1-\xf3][\x80-\xbf]{3}' .
'|' . '\xf4[\x80-\x8f][\x80-\xbf]{2}';
if (!preg_match($regex, $testString)) {
$bytes = min($bytes, strlen($string) - $i);
for ($n = 0; $n < $bytes; $n++) {
$output .= $span . '\x' . str_pad(dechex($b2), 2, '0', STR_PAD_LEFT) . '</span>';
$output .= htmlspecialchars(wfUtils::substr($string, $i, $bytes), ENT_QUOTES | ENT_SUBSTITUTE, 'ISO-8859-1');
public static function requestDetectProxyCallback($timeout = 2, $blocking = true, $forceCheck = false) {
$currentRecommendation = wfConfig::get('detectProxyRecommendation', '');
$detectProxyNextCheck = wfConfig::get('detectProxyNextCheck', false);
if ($detectProxyNextCheck !== false && time() < $detectProxyNextCheck) {
if (empty($currentRecommendation)) {
wfConfig::set('detectProxyRecommendation', 'DEFERRED', wfConfig::DONT_AUTOLOAD);
return; //Let it pull the currently-stored value
$waf = wfWAF::getInstance();
if ($waf->getStorageEngine()->getConfig('attackDataKey', false) === false) {
$waf->getStorageEngine()->setConfig('attackDataKey', mt_rand(0, 0xfff));
$response = wp_remote_get(sprintf(WFWAF_API_URL_SEC . "proxy-check/%d.txt", $waf->getStorageEngine()->getConfig('attackDataKey')), array('headers' => array('Referer' => false)));
if (!is_wp_error($response)) {
$okToSendBody = wp_remote_retrieve_body($response);
if (preg_match('/^(ok|wait),\s*(\d+)$/i', $okToSendBody, $matches)) {
if ($command == 'wait') {
wfConfig::set('detectProxyNextCheck', time() + $ttl, wfConfig::DONT_AUTOLOAD);
if (empty($currentRecommendation) || $currentRecommendation == 'UNKNOWN') {
wfConfig::set('detectProxyRecommendation', 'DEFERRED', wfConfig::DONT_AUTOLOAD);
wfConfig::set('detectProxyNextCheck', time() + $ttl, wfConfig::DONT_AUTOLOAD);
else { //Unknown response
wfConfig::set('detectProxyNextCheck', false, wfConfig::DONT_AUTOLOAD);
if (empty($currentRecommendation) || $currentRecommendation == 'UNKNOWN') {
wfConfig::set('detectProxyRecommendation', 'DEFERRED', wfConfig::DONT_AUTOLOAD);
$nonce = bin2hex(wfWAFUtils::random_bytes(32));
$callback = self::getSiteBaseURL() . '?_wfsf=detectProxy';
wfConfig::set('detectProxyNonce', $nonce, wfConfig::DONT_AUTOLOAD);
wfConfig::set('detectProxyRecommendation', '', wfConfig::DONT_AUTOLOAD);
$homeurl = wfUtils::wpHomeURL();
$siteurl = wfUtils::wpSiteURL();
$response = wp_remote_post(WFWAF_API_URL_SEC . "?" . http_build_query(array(
'action' => 'detect_proxy',
'k' => wfConfig::get('apiKey'),
'lang' => get_site_option('WPLANG'),
'body' => json_encode($payload),
'Content-Type' => 'application/json',
if (!is_wp_error($response)) {
$jsonResponse = wp_remote_retrieve_body($response);
$decoded = @json_decode($jsonResponse, true);
if (is_array($decoded) && isset($decoded['data']) && is_array($decoded['data']) && isset($decoded['data']['ip']) && wfUtils::isValidIP($decoded['data']['ip'])) {
wfConfig::set('serverIP', time() . ';' . $decoded['data']['ip']);
* @return bool Returns false if the payload is invalid, true if it processed the callback (even if the IP wasn't found).
public static function processDetectProxyCallback() {
$nonce = wfConfig::get('detectProxyNonce', '');
$testNonce = (isset($_POST['nonce']) ? $_POST['nonce'] : '');
if (empty($nonce) || empty($testNonce)) {
if (!hash_equals($nonce, $testNonce)) {
$ips = (isset($_POST['ips']) ? $_POST['ips'] : array());
$expandedIPs[] = self::inet_pton($ip);
$checks = array('HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP', 'REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR');
foreach ($checks as $key) {
if (!isset($_SERVER[$key])) {
$testIP = self::getCleanIPAndServerVar(array(array($_SERVER[$key], $key)));
$testIP = self::inet_pton($testIP[0]);
if (in_array($testIP, $expandedIPs)) {
wfConfig::set('detectProxyRecommendation', $key, wfConfig::DONT_AUTOLOAD);
wfConfig::set('detectProxyNonce', '', wfConfig::DONT_AUTOLOAD);
wfConfig::set('detectProxyRecommendation', 'UNKNOWN', wfConfig::DONT_AUTOLOAD);
wfConfig::set('detectProxyNonce', '', wfConfig::DONT_AUTOLOAD);
public static function uuid() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
wfWAFUtils::random_int(0, 0xffff), wfWAFUtils::random_int(0, 0xffff),
// 16 bits for "time_mid"
wfWAFUtils::random_int(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
wfWAFUtils::random_int(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
wfWAFUtils::random_int(0, 0x3fff) | 0x8000,
wfWAFUtils::random_int(0, 0xffff), wfWAFUtils::random_int(0, 0xffff), wfWAFUtils::random_int(0, 0xffff)
public static function base32_encode($rawString, $rightPadFinalBits = false, $padFinalGroup = false, $padCharacter = '=') //Adapted from https://github.com/ademarre/binary-to-text-php
// Unpack string into an array of bytes
$bytes = unpack('C*', $rawString);
$byteCount = count($bytes);
$byte = array_shift($bytes);
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$charsPerByte = 8 / $bitsPerCharacter;
$encodedLength = $byteCount * $charsPerByte;
// Generate encoded output; each loop produces one encoded character
for ($c = 0; $c < $encodedLength; $c++) {
// Get the bits needed for this encoded character
if ($bitsRead + $bitsPerCharacter > 8) {
// Not enough bits remain in this byte for the current character
// Save the remaining bits before getting the next byte
$oldBitCount = 8 - $bitsRead;
$oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount);
$newBitCount = $bitsPerCharacter - $oldBitCount;
// Last bits; match final character and exit loop
if ($rightPadFinalBits) $oldBits <<= $newBitCount;
$encodedString .= $chars[$oldBits];
// Array of the lowest common multiples of $bitsPerCharacter and 8, divided by 8
$lcmMap = array(1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1);
$bytesPerGroup = $lcmMap[$bitsPerCharacter];
$pads = $bytesPerGroup * $charsPerByte - ceil((strlen($rawString) % $bytesPerGroup) * $charsPerByte);
$encodedString .= str_repeat($padCharacter, $pads);
$byte = array_shift($bytes);
$newBitCount = $bitsPerCharacter;
// Read only the needed bits from this byte
$bits = $byte >> 8 - ($bitsRead + ($newBitCount));
$bits ^= $bits >> $newBitCount << $newBitCount;
$bitsRead += $newBitCount;
// Bits come from seperate bytes, add $oldBits to $bits
$bits = ($oldBits << $newBitCount) | $bits;
$encodedString .= $chars[$bits];
private static function _home_url_nofilter($path = '', $scheme = null) { //A version of the native get_home_url and get_option without the filter calls
global $pagenow, $wpdb, $blog_id;
static $cached_url = null;
if ($cached_url !== null) {
if (defined('WP_HOME') && WORDFENCE_PREFER_WP_HOME_FOR_WPML) {
if ( empty( $blog_id ) || !is_multisite() ) {
$url = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = 'home' LIMIT 1");
if (empty($url)) { //get_option uses siteurl instead if home is empty
$url = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = 'siteurl' LIMIT 1");
else if (is_multisite()) {
$current_network = get_network();
if ( 'relative' == $scheme )
$url = rtrim($current_network->path, '/');
$url = 'http://' . rtrim($current_network->domain, '/') . '/' . trim($current_network->path, '/');
if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ) ) ) {
if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $pagenow )
$scheme = parse_url( $url, PHP_URL_SCHEME );
$url = set_url_scheme( $url, $scheme );
if ( $path && is_string( $path ) )
$url .= '/' . ltrim( $path, '/' );
public static function refreshCachedHomeURL() {
$pullDirectly = class_exists('WPML_URL_Filters');
//A version of the native get_home_url without the filter call
$homeurl = self::_home_url_nofilter();
if (function_exists('get_bloginfo') && empty($homeurl)) {
$homeurl = network_home_url();
$homeurl = rtrim($homeurl, '/'); //Because previously we used get_bloginfo and it returns http://example.com without a '/' char.
if (wfConfig::get('wp_home_url') !== $homeurl) {
wfConfig::set('wp_home_url', $homeurl);
public static function wpHomeURL($path = '', $scheme = null) {
$homeurl = wfConfig::get('wp_home_url', '');
if (function_exists('get_bloginfo') && empty($homeurl)) {
$homeurl = network_home_url($path, $scheme);
$homeurl = home_url($path, $scheme);
$homeurl = rtrim($homeurl, '/'); //Because previously we used get_bloginfo and it returns http://example.com without a '/' char.
$homeurl = set_url_scheme($homeurl, $scheme);
if ($path && is_string($path)) {
$homeurl .= '/' . ltrim($path, '/');