Wordfence Security – Firewall & Malware Scan - Version 6.1.4

Version Description

Fix: Fixed potential bug with 'stored data not found after a fork. Got type: boolean'. Improvement: Added bulk actions and filters to WAF whitelist table. Improvement: Added a check while in learning mode to verify the response is not 404 before whitelising. Fix: Added index to attackLogTime. wfHits trimmed on runInstall now. Fix: Fixed attack data sync for hosts that cannot use wp-cron. Improvement: Use wftest@wordfence.com as the Diagnostics page default email address. Improvement: When WFWAF_ENABLED is set to false to disable the firewall, show this on the Firewall page. Fix: Prevent warnings when $_SERVER is empty. Fix: Bug fix for illegal string offset. Fix: Hooked up multibyte string functions to binary safe equivalents. Fix: Hooked up reverse IP lookup in Live Traffic. Fix: Add the user the web server (or PHP) is currently running as to Diagnostics page. Improvement: Pause Live Traffic after scrolling past the first entry. Improvement: Move "Permanently block all temporarily blocked IP addresses" button to top of blocked IP list. Fix: Added JSON fallback for PHP installations that don't have JSON enabled.

Download this release

Release Info

Developer wfmatt
Plugin Icon 128x128 Wordfence Security – Firewall & Malware Scan
Version 6.1.4
Comparing to
See all releases

Code changes from version 6.1.3 to 6.1.4

css/main.css CHANGED
@@ -535,6 +535,8 @@ table.wf-table td {
535
}
536
table.wf-table thead th,
537
table.wf-table thead td,
538
table.wf-table tbody.thead th,
539
table.wf-table tbody.thead td {
540
background-color: #222;
@@ -543,13 +545,14 @@ table.wf-table tbody.thead td {
543
border-color: #474747;
544
text-align: left;
545
}
546
- table.wf-table tbody tr td {
547
- background-color: #fff;
548
- }
549
table.wf-table tbody tr.even td,
550
table.wf-table tbody tr:nth-child(2n) td {
551
background-color: #eee;
552
}
553
table.wf-table tbody tr:hover > td {
554
background-color: #fffbd8;
555
}
@@ -685,6 +688,9 @@ table.whitelist-table .edit-mode input.whitelist-edit {
685
cursor: pointer;
686
text-decoration: underline;
687
}
688
.wfActionBlocked {
689
background-color: #fff6f6;
690
}
@@ -977,7 +983,7 @@ table.whitelist-table .edit-mode input.whitelist-edit {
977
border-color: #e5ae35;
978
}
979
/*.wafStatus-disabled,*/
980
- .wafStatus-disabled.select2-container--default .select2-selection--single {
981
background-color: #ff6d69;
982
border-color: #dd422c;
983
}
@@ -1012,3 +1018,17 @@ pre.wf-pre {
1012
border: 1px solid #999999;
1013
overflow: auto;
1014
}
535
}
536
table.wf-table thead th,
537
table.wf-table thead td,
538
+ table.wf-table tfoot th,
539
+ table.wf-table tfoot td,
540
table.wf-table tbody.thead th,
541
table.wf-table tbody.thead td {
542
background-color: #222;
545
border-color: #474747;
546
text-align: left;
547
}
548
table.wf-table tbody tr.even td,
549
table.wf-table tbody tr:nth-child(2n) td {
550
background-color: #eee;
551
}
552
+ table.wf-table tbody tr td,
553
+ table.wf-table tbody tr.odd td {
554
+ background-color: #fff;
555
+ }
556
table.wf-table tbody tr:hover > td {
557
background-color: #fffbd8;
558
}
688
cursor: pointer;
689
text-decoration: underline;
690
}
691
+ #wf-lt-listings a.button {
692
+ text-decoration: none;
693
+ }
694
.wfActionBlocked {
695
background-color: #fff6f6;
696
}
983
border-color: #e5ae35;
984
}
985
/*.wafStatus-disabled,*/
986
+ .wafStatus-disabled.select2-container--default .select2-selection--single, .wafStatus-disabled.select2-container--default.select2-container--disabled .select2-selection--single, .wafStatus-learning-mode.select2-container--default.select2-container--disabled .select2-selection--single, .wafStatus-enabled.select2-container--default.select2-container--disabled .select2-selection--single {
987
background-color: #ff6d69;
988
border-color: #dd422c;
989
}
1018
border: 1px solid #999999;
1019
overflow: auto;
1020
}
1021
+
1022
+
1023
+ /*
1024
+ Whitelist table
1025
+ */
1026
+ .wf-bulk-action {
1027
+ margin: 12px 0;
1028
+ }
1029
+ tr.wf-table-filters {
1030
+
1031
+ }
1032
+ tr.wf-table-filters input {
1033
+ max-width: 140px;
1034
+ }
js/admin.js CHANGED
@@ -65,7 +65,12 @@
65
});
66
67
$('#doSendEmail').click(function() {
68
- WFAD.ajax('wordfence_sendDiagnostic', {email: $('#_email').val()}, function(res) {
69
if (res.result) {
70
self.colorbox('400px', "Email Diagnostic Report", "Diagnostic report has been sent successfully.");
71
} else {
@@ -699,10 +704,10 @@
699
var self = this;
700
var ips = [];
701
jQuery('.wfReverseLookup').each(function(idx, elem) {
702
- var txt = jQuery(elem).text();
703
if (/^\d+\.\d+\.\d+\.\d+#x2F;.test(txt) && (!jQuery(elem).data('wfReverseDone'))) {
704
jQuery(elem).data('wfReverseDone', true);
705
- ips.push(jQuery(elem).text());
706
}
707
});
708
if (ips.length < 1) {
@@ -722,7 +727,7 @@
722
function(res) {
723
if (res.ok) {
724
jQuery('.wfReverseLookup').each(function(idx, elem) {
725
- var txt = jQuery(elem).text();
726
for (var ip in res.ips) {
727
if (txt == ip) {
728
if (res.ips[ip]) {
@@ -2401,7 +2406,8 @@
2401
whitelistedURLParams: []
2402
},
2403
2404
- wafConfigSave: function(action, data, onSuccess) {
2405
var self = this;
2406
if (typeof(data) == 'string') {
2407
if (data.length > 0) {
@@ -2420,8 +2426,10 @@
2420
2421
this.ajax('wordfence_saveWAFConfig', data, function(res) {
2422
if (typeof res === 'object' && res.success) {
2423
- self.colorbox('400px', 'Firewall Configuration', 'The Wordfence Web Application Firewall ' +
2424
- 'configuration was saved successfully.');
2425
self.wafData = res.data;
2426
self.wafConfigPageRender();
2427
if (typeof onSuccess === 'function') {
@@ -2466,6 +2474,7 @@
2466
var date = new Date(this.wafData['rulesLastUpdated'] * 1000);
2467
this.renderWAFRulesLastUpdated(date);
2468
}
2469
},
2470
2471
renderWAFRulesLastUpdated: function(date) {
65
});
66
67
$('#doSendEmail').click(function() {
68
+ var ticket = $('#_ticketnumber').val();
69
+ if (ticket === null || typeof ticket === "undefined" || ticket.length == 0) {
70
+ self.colorbox('400px', "Error", "Please include your support ticket number or forum username.");
71
+ return;
72
+ }
73
+ WFAD.ajax('wordfence_sendDiagnostic', {email: $('#_email').val(), ticket: ticket}, function(res) {
74
if (res.result) {
75
self.colorbox('400px', "Email Diagnostic Report", "Diagnostic report has been sent successfully.");
76
} else {
704
var self = this;
705
var ips = [];
706
jQuery('.wfReverseLookup').each(function(idx, elem) {
707
+ var txt = jQuery(elem).text().trim();
708
if (/^\d+\.\d+\.\d+\.\d+#x2F;.test(txt) && (!jQuery(elem).data('wfReverseDone'))) {
709
jQuery(elem).data('wfReverseDone', true);
710
+ ips.push(txt);
711
}
712
});
713
if (ips.length < 1) {
727
function(res) {
728
if (res.ok) {
729
jQuery('.wfReverseLookup').each(function(idx, elem) {
730
+ var txt = jQuery(elem).text().trim();
731
for (var ip in res.ips) {
732
if (txt == ip) {
733
if (res.ips[ip]) {
2406
whitelistedURLParams: []
2407
},
2408
2409
+ wafConfigSave: function(action, data, onSuccess, showColorBox) {
2410
+ showColorBox = showColorBox === undefined ? true : !!showColorBox;
2411
var self = this;
2412
if (typeof(data) == 'string') {
2413
if (data.length > 0) {
2426
2427
this.ajax('wordfence_saveWAFConfig', data, function(res) {
2428
if (typeof res === 'object' && res.success) {
2429
+ if (showColorBox) {
2430
+ self.colorbox('400px', 'Firewall Configuration', 'The Wordfence Web Application Firewall ' +
2431
+ 'configuration was saved successfully.');
2432
+ }
2433
self.wafData = res.data;
2434
self.wafConfigPageRender();
2435
if (typeof onSuccess === 'function') {
2474
var date = new Date(this.wafData['rulesLastUpdated'] * 1000);
2475
this.renderWAFRulesLastUpdated(date);
2476
}
2477
+ $(window).trigger('wordfenceWAFConfigPageRender');
2478
},
2479
2480
renderWAFRulesLastUpdated: function(date) {
js/admin.liveTraffic.js CHANGED
@@ -648,6 +648,7 @@
648
649
var legend = $('#wf-live-traffic-legend');
650
var adminBar = $('#wpadminbar');
651
652
var hasScrolled = false;
653
var loadingListings = false;
@@ -659,6 +660,17 @@
659
legend.removeClass('sticky');
660
}
661
662
// console.log(win.scrollTop() + window.innerHeight, liveTrafficWrapper.outerHeight() + liveTrafficWrapper.offset().top);
663
var currentScrollBottom = win.scrollTop() + window.innerHeight;
664
var scrollThreshold = liveTrafficWrapper.outerHeight() + liveTrafficWrapper.offset().top;
@@ -669,6 +681,7 @@
669
hasScrolled = false;
670
WFAD.wfLiveTraffic.loadNextListings(function() {
671
loadingListings = false;
672
});
673
} else if (currentScrollBottom < scrollThreshold) {
674
hasScrolled = true;
648
649
var legend = $('#wf-live-traffic-legend');
650
var adminBar = $('#wpadminbar');
651
+ var liveTrafficListings = $('#wf-lt-listings');
652
653
var hasScrolled = false;
654
var loadingListings = false;
660
legend.removeClass('sticky');
661
}
662
663
+ var firstRow = liveTrafficListings.children().first();
664
+ if (firstRow.offset().top + firstRow.height() < win.scrollTop() + adminBar.outerHeight() + 20) {
665
+ if (WFAD.mode != 'liveTraffic_paused') {
666
+ WFAD.mode = 'liveTraffic_paused';
667
+ }
668
+ } else {
669
+ if (WFAD.mode != 'liveTraffic') {
670
+ WFAD.mode = 'liveTraffic';
671
+ }
672
+ }
673
+
674
// console.log(win.scrollTop() + window.innerHeight, liveTrafficWrapper.outerHeight() + liveTrafficWrapper.offset().top);
675
var currentScrollBottom = win.scrollTop() + window.innerHeight;
676
var scrollThreshold = liveTrafficWrapper.outerHeight() + liveTrafficWrapper.offset().top;
681
hasScrolled = false;
682
WFAD.wfLiveTraffic.loadNextListings(function() {
683
loadingListings = false;
684
+ WFAD.reverseLookupIPs();
685
});
686
} else if (currentScrollBottom < scrollThreshold) {
687
hasScrolled = true;
lib/menu_activity.php CHANGED
@@ -343,45 +343,44 @@
343
<tr>
344
<td>
345
<span data-bind="if: blocked()">
346
- <button type="button" class="button button-small"
347
data-bind="click: $root.unblockIP">
348
Unblock this IP
349
- </button>
350
</span>
351
<span data-bind="if: rangeBlocked()">
352
- <button type="button" class="button button-small"
353
data-bind="click: $root.unblockNetwork">Unblock this range
354
- </button>
355
</span>
356
<span data-bind="if: !blocked() && !rangeBlocked()">
357
- <button type="button" class="button button-small"
358
data-bind="click: $root.blockIP">
359
Block this IP
360
- </button>
361
</span>
362
- <button type="button" class="button button-small"
363
- data-bind="click: function() { location = 'admin.php?page=WordfenceWhois&whoisval=' + IP() + '&wfnetworkblock=1' }">
364
Block this network
365
- </button>
366
- <button type="button" class="button button-small" data-bind="text: 'Run WHOIS on ' + IP(),
367
- click: function() { window.open('admin.php?page=WordfenceWhois&whoisval=' + IP()) }"
368
- target="_blank"></button>
369
- <button type="button" class="button button-small"
370
- data-bind="click: function() { window.open(WFAD.makeIPTrafLink(IP())) }">
371
- See
372
- recent traffic
373
- </button>
374
<span data-bind="if: action() == 'blocked:waf'">
375
- <button type="button" class="button button-small"
376
data-bind="click: function () { $root.whitelistWAFParamKey(actionData().path, actionData().paramKey, actionData().failedRules) }"
377
title="If this is a false positive, you can exclude this parameter from being filtered by the firewall">
378
Whitelist param from Firewall
379
- </button>
380
<?php if (WFWAF_DEBUG): ?>
381
- <button type="button" class="button button-small"
382
- data-bind="click: function() { window.open('<?php echo esc_js(home_url()) ?>?_wfsf=debugWAF&nonce=' + WFAD.nonce + '&hitid=' + id(), 'debugWAF');}">
383
Debug this Request
384
- </button>
385
<?php endif ?>
386
</span>
387
</td>
343
<tr>
344
<td>
345
<span data-bind="if: blocked()">
346
+ <a href="#" class="button button-small"
347
data-bind="click: $root.unblockIP">
348
Unblock this IP
349
+ </a>
350
</span>
351
<span data-bind="if: rangeBlocked()">
352
+ <a href="#" class="button button-small"
353
data-bind="click: $root.unblockNetwork">Unblock this range
354
+ </a>
355
</span>
356
<span data-bind="if: !blocked() && !rangeBlocked()">
357
+ <a href="#" class="button button-small"
358
data-bind="click: $root.blockIP">
359
Block this IP
360
+ </a>
361
</span>
362
+ <a class="button button-small"
363
+ data-bind="attr: { href: 'admin.php?page=WordfenceWhois&whoisval=' + IP() + '&wfnetworkblock=1'}">
364
Block this network
365
+ </a>
366
+ <a class="button button-small" data-bind="text: 'Run WHOIS on ' + IP(),
367
+ attr: { href: 'admin.php?page=WordfenceWhois&whoisval=' + IP() }"
368
+ target="_blank"></a>
369
+ <a class="button button-small"
370
+ data-bind="attr: { href: WFAD.makeIPTrafLink(IP()) }" target="_blank">
371
+ See recent traffic
372
+ </a>
373
<span data-bind="if: action() == 'blocked:waf'">
374
+ <a href="#" class="button button-small"
375
data-bind="click: function () { $root.whitelistWAFParamKey(actionData().path, actionData().paramKey, actionData().failedRules) }"
376
title="If this is a false positive, you can exclude this parameter from being filtered by the firewall">
377
Whitelist param from Firewall
378
+ </a>
379
<?php if (WFWAF_DEBUG): ?>
380
+ <a href="#" class="button button-small"
381
+ data-bind="attr: { href: '<?php echo esc_js(home_url()) ?>?_wfsf=debugWAF&nonce=' + WFAD.nonce + '&hitid=' + id() }" target="_blank">
382
Debug this Request
383
+ </a>
384
<?php endif ?>
385
</span>
386
</td>
lib/menu_blockedIPs.php CHANGED
@@ -65,7 +65,8 @@
65
66
<script type="text/x-jquery-template" id="wfLockedOutIPsTmpl">
67
<div>
68
- <div style="border-bottom: 1px solid #CCC; padding-bottom: 10px; margin-bottom: 10px;">
69
<table border="0" style="width: 100%">
70
{{each(idx, elem) results}}
71
<tr><td>
@@ -103,13 +104,13 @@
103
{{/each}}
104
</table>
105
</div>
106
- <p><a class="button" href="#" onclick="WFAD.permanentlyBlockAllIPs('lockedOut'); return false;">Permanently block all locked out IP addresses</a></p>
107
</div>
108
</script>
109
110
<script type="text/x-jquery-template" id="wfBlockedIPsTmpl">
111
<div>
112
- <div style="border-bottom: 1px solid #CCC; padding-bottom: 10px; margin-bottom: 10px;">
113
<table border="0" style="width: 100%">
114
{{each(idx, elem) results}}
115
<tr><td>
@@ -159,7 +160,6 @@
159
{{/each}}
160
</table>
161
</div>
162
- <p><a class="button" href="#" onclick="WFAD.permanentlyBlockAllIPs('blocked'); return false;">Permanently block all temporarily blocked IP addresses</a></p>
163
</div>
164
</script>
165
65
66
<script type="text/x-jquery-template" id="wfLockedOutIPsTmpl">
67
<div>
68
+ <p><a class="button" href="#" onclick="WFAD.permanentlyBlockAllIPs('lockedOut'); return false;">Permanently block all locked out IP addresses</a></p>
69
+ <div style="border-top: 1px solid #CCC; padding-top: 10px; margin-top: 10px;">
70
<table border="0" style="width: 100%">
71
{{each(idx, elem) results}}
72
<tr><td>
104
{{/each}}
105
</table>
106
</div>
107
</div>
108
</script>
109
110
<script type="text/x-jquery-template" id="wfBlockedIPsTmpl">
111
<div>
112
+ <p><a class="button" href="#" onclick="WFAD.permanentlyBlockAllIPs('blocked'); return false;">Permanently block all temporarily blocked IP addresses</a></p>
113
+ <div style="border-top: 1px solid #CCC; padding-top: 10px; margin-top: 10px;">
114
<table border="0" style="width: 100%">
115
{{each(idx, elem) results}}
116
<tr><td>
160
{{/each}}
161
</table>
162
</div>
163
</div>
164
</script>
165
lib/menu_diagnostic.php CHANGED
@@ -293,8 +293,14 @@ $w = new wfConfig();
293
<table class="wfConfigForm">
294
<tr>
295
<th>Email address:</th>
296
- <td><input type="email" id="_email" value="samples@wordfence.com"/></td>
297
- <td colspan="2"><input class="button" type="button" id="doSendEmail" value="Send"/></td>
298
</tr>
299
</table>
300
</div>
293
<table class="wfConfigForm">
294
<tr>
295
<th>Email address:</th>
296
+ <td><input type="email" id="_email" value="wftest@wordfence.com"/></td>
297
+ </tr>
298
+ <tr>
299
+ <th>Ticket Number/Forum Username:</th>
300
+ <td><input type="text" id="_ticketnumber" required/></td>
301
+ </tr>
302
+ <tr>
303
+ <td colspan="2" style="text-align: right;"><input class="button" type="button" id="doSendEmail" value="Send"/></td>
304
</tr>
305
</table>
306
</div>
lib/menu_waf.php CHANGED
@@ -13,6 +13,33 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
13
include('pageTitle.php');
14
?>
15
<div class="wordfenceModeElem" id="wordfenceMode_waf"></div>
16
<?php if (!empty($storageExceptionMessage)): ?>
17
<div style="font-weight: bold; margin: 20px 0px;;">
18
<?php echo wp_kses($storageExceptionMessage, 'post') ?>
@@ -21,7 +48,8 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
21
<?php echo $wafActionContent ?>
22
23
<p class="wf-notice"><em>If you cannot complete the setup process,
24
- <a target="_blank" href="https://docs.wordfence.com/en/Web_Application_Firewall_Setup">click here for help</a>.</em></p>
25
26
<?php else: ?>
27
@@ -37,7 +65,8 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
37
38
<p>As new threats emerge, the Threat Defense Feed is updated to protect you from new attacks. The
39
Premium version of the Threat Defense Feed is updated in real-time protecting you immediately. As a
40
- free user <strong>you are receiving the community version</strong> of the feed which is updated 30 days later.
41
Upgrade now for less than $5 a month!</p>
42
43
<p class="center"><a class="button button-primary"
@@ -55,7 +84,9 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
55
<?php if (WFWAF_SUBDIRECTORY_INSTALL): ?>
56
<div class="wf-notice">
57
You are currently running the Wordfence Web Application Firewall from another WordPress installation.
58
- Please <a href="<?php echo network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend'); ?>">click here</a> to configure the Firewall to run correctly on this site.
59
</div>
60
<?php else: ?>
61
<div class="wordfenceWrap" style="margin: 20px 20px 20px 30px;">
@@ -79,23 +110,26 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
79
</td>
80
</tr>
81
<tr>
82
<td><h2>Firewall Status:<a href="http://docs.wordfence.com/en/WAF#Firewall_Status"
83
target="_blank" class="wfhelp"></a></h2></td>
84
<td colspan="2">
85
- <select style="width: 300px" name="wafStatus" id="input-wafStatus">
86
- <option<?php echo $config->getConfig('wafStatus') == 'enabled' ? ' selected' : '' ?>
87
class="wafStatus-enabled" value="enabled">Enabled and Protecting
88
</option>
89
- <option<?php echo $config->getConfig('wafStatus') == 'learning-mode' ? ' selected' : '' ?>
90
class="wafStatus-learning-mode" value="learning-mode">Learning Mode
91
</option>
92
- <option<?php echo $config->getConfig('wafStatus') == 'disabled' ? ' selected' : '' ?>
93
class="wafStatus-disabled" value="disabled">Disabled
94
</option>
95
</select>
96
<script>
97
(function($) {
98
- $('#input-wafStatus').val(<?php echo json_encode($config->getConfig('wafStatus')) ?>)
99
.on('change', function() {
100
var val = $(this).val();
101
$('.wafStatus-description').hide();
@@ -125,7 +159,7 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
125
</tr>
126
<tr>
127
<td style="text-align: center">
128
- <button type="submit" class="button button-primary">Save</button>
129
</td>
130
<td colspan="2">
131
<div class="wafStatus-description" id="wafStatus-enabled-description">
@@ -133,10 +167,13 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
133
matching known attack patterns, and is actively protecting your site from attackers.
134
</div>
135
<div class="wafStatus-description" id="wafStatus-learning-mode-description">
136
- When you first install the Wordfence Web Application Firewall, it will be in learning
137
mode. This allows
138
- Wordfence to learn about your site so that we can understand how to protect it and how
139
- to allow normal visitors through the firewall. We recommend you let Wordfence learn for
140
a week before you enable the firewall.
141
</div>
142
<div class="wafStatus-description" id="wafStatus-disabled-description">
@@ -169,7 +206,7 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
169
<?php else: ?>
170
You are running Wordfence community firewall rules.
171
<?php endif ?>
172
- <!-- <em id="waf-rules-last-updated"></em>-->
173
</p>
174
175
</form>
@@ -247,9 +284,24 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
247
</table>
248
</script>
249
<script type="text/x-jquery-template" id="waf-whitelisted-urls-tmpl">
250
<table class="wf-table whitelist-table">
251
<thead>
252
<tr>
253
<th style="width: 5%;">Enabled</th>
254
<th>URL</th>
255
<th>Param</th>
@@ -260,9 +312,35 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
260
<th>Action</th>
261
</tr>
262
</thead>
263
<tbody>
264
{{each(idx, whitelistedURLParam) whitelistedURLParams}}
265
<tr data-index="${idx}">
266
<td style="text-align: center;">
267
<input name="replaceWhitelistedEnabled" type="hidden" value="${whitelistedURLParam.data.disabled}">
268
<input name="whitelistedEnabled" type="checkbox" value="1"
@@ -326,11 +404,13 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
326
{{/each}}
327
{{if (whitelistedURLParams.length == 0)}}
328
<tr>
329
- <td colspan="8">No whitelisted URLs currently set.</td>
330
</tr>
331
{{/if}}
332
</tbody>
333
</table>
334
</script>
335
336
<script type="text/javascript">
@@ -351,7 +431,10 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
351
whitelistedEnabled: 1,
352
whitelistedPath: url,
353
whitelistedParam: param + '[' + paramName + ']'
354
- });
355
}
356
});
357
@@ -403,6 +486,144 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
403
}
404
}).triggerHandler('click');
405
406
});
407
408
$(document).on('click', '.whitelist-url-edit', function() {
@@ -418,7 +639,10 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
418
WFAD.wafConfigSave('deleteWhitelist', {
419
deletedWhitelistedPath: pathInput.val(),
420
deletedWhitelistedParam: paramInput.val()
421
- });
422
}
423
});
424
$(document).on('click', '.whitelist-url-save', function() {
@@ -439,7 +663,10 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
439
newWhitelistedPath: newWhitelistedPath.val(),
440
newWhitelistedParam: newWhitelistedParam.val(),
441
newWhitelistedEnabled: newWhitelistedEnabled.val()
442
- });
443
});
444
$(document).on('click', '.whitelist-url-cancel', function() {
445
var tr = $(this).closest('tr');
@@ -456,7 +683,10 @@ $wafConfigURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configu
456
whitelistedPath: oldWhitelistedPath.val(),
457
whitelistedParam: oldWhitelistedParam.val(),
458
whitelistedEnabled: enabled
459
- });
460
});
461
462
$(document).on('click', 'input[name=ruleEnabled]', function() {
13
include('pageTitle.php');
14
?>
15
<div class="wordfenceModeElem" id="wordfenceMode_waf"></div>
16
+
17
+ <?php
18
+ if (defined('WFWAF_ENABLED') && !WFWAF_ENABLED) :
19
+ $message = 'To allow the firewall to re-enable, please remove this line from the appropriate file.';
20
+ $pattern = '/define\s*\(\s*(?:\'WFWAF_ENABLED\'|"WFWAF_ENABLED")\s*,(.+?)\)/x';
21
+ $checkFiles = array(
22
+ ABSPATH . 'wp-config.php',
23
+ wordfence::getWAFBootstrapPath(),
24
+ );
25
+
26
+ foreach($checkFiles as $path) {
27
+ if (!file_exists($path)) {
28
+ continue;
29
+ }
30
+
31
+ if (($contents = file_get_contents($path)) !== false) {
32
+ if (preg_match($pattern, $contents, $matches) && (trim($matches[1]) == 'false' || trim($matches[1]) == '0')) {
33
+ $message = "To allow the firewall to re-enable, please remove this line from the file '" . $path . "'.";
34
+ break;
35
+ }
36
+ }
37
+ }
38
+
39
+ ?>
40
+ <p class="wf-notice">The Wordfence Firewall is currently disabled because WFWAF_ENABLED is overridden and set to false. <?php echo $message; ?></p>
41
+ <?php endif ?>
42
+
43
<?php if (!empty($storageExceptionMessage)): ?>
44
<div style="font-weight: bold; margin: 20px 0px;;">
45
<?php echo wp_kses($storageExceptionMessage, 'post') ?>
48
<?php echo $wafActionContent ?>
49
50
<p class="wf-notice"><em>If you cannot complete the setup process,
51
+ <a target="_blank" href="https://docs.wordfence.com/en/Web_Application_Firewall_Setup">click here for
52
+ help</a>.</em></p>
53
54
<?php else: ?>
55
65
66
<p>As new threats emerge, the Threat Defense Feed is updated to protect you from new attacks. The
67
Premium version of the Threat Defense Feed is updated in real-time protecting you immediately. As a
68
+ free user <strong>you are receiving the community version</strong> of the feed which is updated 30
69
+ days later.
70
Upgrade now for less than $5 a month!</p>
71
72
<p class="center"><a class="button button-primary"
84
<?php if (WFWAF_SUBDIRECTORY_INSTALL): ?>
85
<div class="wf-notice">
86
You are currently running the Wordfence Web Application Firewall from another WordPress installation.
87
+ Please <a
88
+ href="<?php echo network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend'); ?>">click
89
+ here</a> to configure the Firewall to run correctly on this site.
90
</div>
91
<?php else: ?>
92
<div class="wordfenceWrap" style="margin: 20px 20px 20px 30px;">
110
</td>
111
</tr>
112
<tr>
113
+ <?php
114
+ $wafStatus = (!WFWAF_ENABLED ? 'disabled' : $config->getConfig('wafStatus'));
115
+ ?>
116
<td><h2>Firewall Status:<a href="http://docs.wordfence.com/en/WAF#Firewall_Status"
117
target="_blank" class="wfhelp"></a></h2></td>
118
<td colspan="2">
119
+ <select style="width: 300px" name="wafStatus" id="input-wafStatus"<?php echo !WFWAF_ENABLED ? ' disabled' : '' ?>>
120
+ <option<?php echo $wafStatus == 'enabled' ? ' selected' : '' ?>
121
class="wafStatus-enabled" value="enabled">Enabled and Protecting
122
</option>
123
+ <option<?php echo $wafStatus == 'learning-mode' ? ' selected' : '' ?>
124
class="wafStatus-learning-mode" value="learning-mode">Learning Mode
125
</option>
126
+ <option<?php echo $wafStatus == 'disabled' ? ' selected' : '' ?>
127
class="wafStatus-disabled" value="disabled">Disabled
128
</option>
129
</select>
130
<script>
131
(function($) {
132
+ $('#input-wafStatus').val(<?php echo json_encode($wafStatus) ?>)
133
.on('change', function() {
134
var val = $(this).val();
135
$('.wafStatus-description').hide();
159
</tr>
160
<tr>
161
<td style="text-align: center">
162
+ <button type="submit" class="button button-primary"<?php echo !WFWAF_ENABLED ? ' disabled' : '' ?>>Save</button>
163
</td>
164
<td colspan="2">
165
<div class="wafStatus-description" id="wafStatus-enabled-description">
167
matching known attack patterns, and is actively protecting your site from attackers.
168
</div>
169
<div class="wafStatus-description" id="wafStatus-learning-mode-description">
170
+ When you first install the Wordfence Web Application Firewall, it will be in
171
+ learning
172
mode. This allows
173
+ Wordfence to learn about your site so that we can understand how to protect it and
174
+ how
175
+ to allow normal visitors through the firewall. We recommend you let Wordfence learn
176
+ for
177
a week before you enable the firewall.
178
</div>
179
<div class="wafStatus-description" id="wafStatus-disabled-description">
206
<?php else: ?>
207
You are running Wordfence community firewall rules.
208
<?php endif ?>
209
+ <!-- <em id="waf-rules-last-updated"></em>-->
210
</p>
211
212
</form>
284
</table>
285
</script>
286
<script type="text/x-jquery-template" id="waf-whitelisted-urls-tmpl">
287
+ <?php ob_start() ?>
288
+ <form action="javascript:void(0)" class="wf-bulk-action wf-whitelist-actions">
289
+ <select name="wf-bulk-action">
290
+ <option value="">Bulk Actions</option>
291
+ <option value="delete">Delete</option>
292
+ <option value="enable">Enable</option>
293
+ <option value="disable">Disable</option>
294
+ </select>
295
+ <button type="submit" class="button">Apply</button>
296
+ </form>
297
+ <?php
298
+ $bulkActionForm = ob_get_clean();
299
+ echo $bulkActionForm;
300
+ ?>
301
<table class="wf-table whitelist-table">
302
<thead>
303
<tr>
304
+ <th style="width: 2%;text-align: center"><input type="checkbox" class="wf-whitelist-table-bulk-action"></th>
305
<th style="width: 5%;">Enabled</th>
306
<th>URL</th>
307
<th>Param</th>
312
<th>Action</th>
313
</tr>
314
</thead>
315
+ {{if whitelistedURLParams.length > 5}}
316
+ <tfoot>
317
+ <tr>
318
+ <th><input type="checkbox" class="wf-whitelist-table-bulk-action"></th>
319
+ <th style="width: 5%;">Enabled</th>
320
+ <th>URL</th>
321
+ <th>Param</th>
322
+ <th>Created</th>
323
+ <th>Source</th>
324
+ <th>User</th>
325
+ <th>IP</th>
326
+ <th>Action</th>
327
+ </tr>
328
+ {{/if}}
329
+ </tfoot>
330
<tbody>
331
+ <tr class="wf-table-filters">
332
+ <td colspan="2"></td>
333
+ <td><input data-column-index="2" placeholder="Filter URL" type="text"></td>
334
+ <td><input data-column-index="3" placeholder="Filter Param" type="text"></td>
335
+ <td><input data-column-index="4" placeholder="Filter Created" type="text"></td>
336
+ <td><input data-column-index="5" placeholder="Filter Source" type="text"></td>
337
+ <td><input style="max-width:100px;" data-column-index="6" placeholder="Filter User" type="text"></td>
338
+ <td><input style="max-width:100px;" data-column-index="7" placeholder="Filter IP" type="text"></td>
339
+ <td></td>
340
+ </tr>
341
{{each(idx, whitelistedURLParam) whitelistedURLParams}}
342
<tr data-index="${idx}">
343
+ <td style="text-align: center;"><input type="checkbox" class="wf-whitelist-table-bulk-checkbox"></td>
344
<td style="text-align: center;">
345
<input name="replaceWhitelistedEnabled" type="hidden" value="${whitelistedURLParam.data.disabled}">
346
<input name="whitelistedEnabled" type="checkbox" value="1"
404
{{/each}}
405
{{if (whitelistedURLParams.length == 0)}}
406
<tr>
407
+ <td colspan="9">No whitelisted URLs currently set.</td>
408
</tr>
409
{{/if}}
410
</tbody>
411
</table>
412
+ <?php echo $bulkActionForm ?>
413
+
414
</script>
415
416
<script type="text/javascript">
431
whitelistedEnabled: 1,
432
whitelistedPath: url,
433
whitelistedParam: param + '[' + paramName + ']'
434
+ }, function() {
435
+ WFAD.colorbox('400px', 'Firewall Configuration', 'The Wordfence Web Application Firewall ' +
436
+ 'whitelist was saved successfully.');
437
+ }, false);
438
}
439
});
440
486
}
487
}).triggerHandler('click');
488
489
+ var whitelistWrapper = $('#waf-whitelisted-urls-wrapper');
490
+
491
+ var whitelistTable = null;
492
+ var whitelistTableRows = null;
493
+ var bulkActionCheckboxes = null;
494
+ var bulkActionTriggerCheckboxes = null;
495
+ var filterInputs = [];
496
+
497
+ function requeryWhitelistDOMElements() {
498
+ whitelistWrapper = $('#waf-whitelisted-urls-wrapper');
499
+ whitelistTable = whitelistWrapper.find('.whitelist-table');
500
+ whitelistTableRows = whitelistTable.find('> tbody > tr[data-index]');
501
+ bulkActionCheckboxes = whitelistTable.find('> tbody > tr[data-index] > td > input[type=checkbox].wf-whitelist-table-bulk-checkbox');
502
+ filterInputs = [];
503
+
504
+ whitelistWrapper.find('.wf-table-filters input').each(function() {
505
+ var el = $(this);
506
+ var index = el.attr('data-column-index');
507
+ filterInputs[index] = function(td) {
508
+ if (el.val().length == 0) {
509
+ return true;
510
+ }
511
+ return $(td).text().indexOf(el.val()) > -1;
512
+ };
513
+ }).on('keydown', function(evt) {
514
+
515
+ if (evt.keyCode == 13) {
516
+ filterWhitelistTable();
517
+ return false;
518
+ }
519
+ }).on('blur', function() {
520
+ filterWhitelistTable();
521
+ });
522
+
523
+ // Bulk actions
524
+ bulkActionTriggerCheckboxes = whitelistWrapper.find('input[type=checkbox].wf-whitelist-table-bulk-action').on('click', function() {
525
+ if (this.checked) {
526
+ whitelistCheckAllVisible();
527
+ } else {
528
+ whitelistUncheckAll();
529
+ }
530
+ });
531
+
532
+ whitelistWrapper.find('form.wf-whitelist-actions').on('submit', function() {
533
+ var select = $(this).find('select[name=wf-bulk-action]');
534
+ var bulkActionCallback = function(res) {
535
+ if (typeof res === 'object' && res.success) {
536
+ WFAD.colorbox('400px', 'Firewall Configuration', 'The Wordfence Web Application Firewall ' +
537
+ 'whitelist was saved successfully.');
538
+ WFAD.wafData = res.data;
539
+ WFAD.wafConfigPageRender();
540
+ } else {
541
+ WFAD.colorbox('400px', 'Error saving Firewall configuration', 'There was an error saving the ' +
542
+ 'Web Application Firewall whitelist.');
543
+ }
544
+ };
545
+ switch (select.val()) {
546
+ case 'delete':
547
+ WFAD.ajax('wordfence_whitelistBulkDelete', {
548
+ items: JSON.stringify(getBulkWhitelistChecked())
549
+ }, bulkActionCallback);
550
+ break;
551
+
552
+ case 'enable':
553
+ WFAD.ajax('wordfence_whitelistBulkEnable', {
554
+ items: JSON.stringify(getBulkWhitelistChecked())
555
+ }, bulkActionCallback);
556
+ break;
557
+
558
+ case 'disable':
559
+ WFAD.ajax('wordfence_whitelistBulkDisable', {
560
+ items: JSON.stringify(getBulkWhitelistChecked())
561
+ }, bulkActionCallback);
562
+ break;
563
+ }
564
+ return false;
565
+ });
566
+ }
567
+
568
+ // Whitelist table filters
569
+ function filterWhitelistTable() {
570
+ var zebraCount = 0;
571
+ whitelistTableRows.each(function() {
572
+ var tr = $(this);
573
+ var isMatch = true;
574
+ tr.find('> td').each(function(index) {
575
+ if (typeof filterInputs[index] === 'function') {
576
+ isMatch = filterInputs[index](this);
577
+ if (!isMatch) {
578
+ return false;
579
+ }
580
+ }
581
+ });
582
+ tr.removeClass('even odd');
583
+ if (isMatch) {
584
+ tr.show();
585
+ tr.addClass(zebraCount++ % 2 === 0 ? 'even' : 'odd');
586
+ } else {
587
+ tr.hide();
588
+ }
589
+ });
590
+ whitelistUncheckAll();
591
+ }
592
+
593
+ var keyupTimeout = null;
594
+
595
+ function getBulkWhitelistChecked() {
596
+ var data = [];
597
+ bulkActionCheckboxes.each(function() {
598
+ if (this.checked) {
599
+ var tr = $(this).closest('tr');
600
+ if (tr.is(':visible')) {
601
+ var path = tr.find('input[name=whitelistedPath]').val();
602
+ var paramKey = tr.find('input[name=whitelistedParam]').val();
603
+ var enabled = tr.find('input[name=whitelistedEnabled]').attr('checked') ? 1 : 0;
604
+ data.push([encodeURIComponent(path), encodeURIComponent(paramKey), enabled]);
605
+ }
606
+ }
607
+ });
608
+ return data;
609
+ }
610
+
611
+ function whitelistCheckAllVisible() {
612
+ bulkActionTriggerCheckboxes.attr('checked', true);
613
+ bulkActionCheckboxes.each(function() {
614
+ this.checked = $(this).closest('tr').is(':visible');
615
+ });
616
+ }
617
+
618
+ function whitelistUncheckAll() {
619
+ bulkActionTriggerCheckboxes.attr('checked', false);
620
+ bulkActionCheckboxes.attr('checked', false);
621
+ }
622
+
623
+ requeryWhitelistDOMElements();
624
+ $(window).on('wordfenceWAFConfigPageRender', function() {
625
+ requeryWhitelistDOMElements();
626
+ })
627
});
628
629
$(document).on('click', '.whitelist-url-edit', function() {
639
WFAD.wafConfigSave('deleteWhitelist', {
640
deletedWhitelistedPath: pathInput.val(),
641
deletedWhitelistedParam: paramInput.val()
642
+ }, function() {
643
+ WFAD.colorbox('400px', 'Firewall Configuration', 'The Wordfence Web Application Firewall ' +
644
+ 'whitelist was saved successfully.');
645
+ }, false);
646
}
647
});
648
$(document).on('click', '.whitelist-url-save', function() {
663
newWhitelistedPath: newWhitelistedPath.val(),
664
newWhitelistedParam: newWhitelistedParam.val(),
665
newWhitelistedEnabled: newWhitelistedEnabled.val()
666
+ }, function() {
667
+ WFAD.colorbox('400px', 'Firewall Configuration', 'The Wordfence Web Application Firewall ' +
668
+ 'whitelist was saved successfully.');
669
+ }, false);
670
});
671
$(document).on('click', '.whitelist-url-cancel', function() {
672
var tr = $(this).closest('tr');
683
whitelistedPath: oldWhitelistedPath.val(),
684
whitelistedParam: oldWhitelistedParam.val(),
685
whitelistedEnabled: enabled
686
+ }, function() {
687
+ WFAD.colorbox('400px', 'Firewall Configuration', 'The Wordfence Web Application Firewall ' +
688
+ 'whitelist was saved successfully.');
689
+ }, false);
690
});
691
692
$(document).on('click', 'input[name=ruleEnabled]', function() {
lib/wfConfig.php CHANGED
@@ -685,7 +685,7 @@ class wfConfig {
685
// because we would have to concatenate $val twice into the query which could also exceed max packet for the mysql server
686
$serialized = serialize($val);
687
$tempFilename = 'wordfence_tmpfile_' . $key . '.php';
688
- if((strlen($serialized) * 1.1) > self::getDB()->getMaxAllowedPacketBytes()){ //If it's greater than max_allowed_packet + 10% for escaping and SQL
689
if($canUseDisk){
690
$dir = self::getTempDir();
691
$potentialDirs = self::getPotentialTempDirs();
@@ -719,10 +719,12 @@ class wfConfig {
719
self::deleteOldTempFile($tempDir . $tempFilename);
720
}
721
$exists = self::getDB()->querySingle("select name from " . self::table() . " where name='%s'", $key);
722
if($exists){
723
- self::getDB()->queryWrite("update " . self::table() . " set val=%s where name=%s", $serialized, $key);
724
} else {
725
- self::getDB()->queryWrite("insert IGNORE into " . self::table() . " (name, val) values (%s, %s)", $key, $serialized);
726
}
727
}
728
self::getDB()->flush();
685
// because we would have to concatenate $val twice into the query which could also exceed max packet for the mysql server
686
$serialized = serialize($val);
687
$tempFilename = 'wordfence_tmpfile_' . $key . '.php';
688
+ if((strlen($serialized) * 2) + 50 > self::getDB()->getMaxAllowedPacketBytes()){ //If it's greater than max_allowed_packet + 20% for escaping and SQL
689
if($canUseDisk){
690
$dir = self::getTempDir();
691
$potentialDirs = self::getPotentialTempDirs();
719
self::deleteOldTempFile($tempDir . $tempFilename);
720
}
721
$exists = self::getDB()->querySingle("select name from " . self::table() . " where name='%s'", $key);
722
+ $serializedHex = bin2hex($serialized);
723
+
724
if($exists){
725
+ self::getDB()->queryWrite(sprintf("update " . self::table() . " set val=X'%s' where name=%%s", $serializedHex), $key);
726
} else {
727
+ self::getDB()->queryWrite(sprintf("insert ignore into " . self::table() . " (name, val) values (%%s, X'%s')", $serializedHex), $key);
728
}
729
}
730
self::getDB()->flush();
lib/wfDiagnostic.php CHANGED
@@ -64,6 +64,7 @@ class wfDiagnostic
64
),
65
'PHP' => array(
66
'phpVersion' => 'PHP version >= PHP 5.2.4<br><em> (<a href="https://wordpress.org/about/requirements/" target="_blank">Minimum version required by WordPress</a>)</em>',
67
'hasOpenSSL' => 'Checking for OpenSSL support',
68
'hasCurl' => 'Checking for cURL support',
69
),
@@ -156,6 +157,56 @@ class wfDiagnostic
156
);
157
}
158
159
public function hasOpenSSL() {
160
return is_callable('openssl_open');
161
}
64
),
65
'PHP' => array(
66
'phpVersion' => 'PHP version >= PHP 5.2.4<br><em> (<a href="https://wordpress.org/about/requirements/" target="_blank">Minimum version required by WordPress</a>)</em>',
67
+ 'processOwner' => 'Process Owner',
68
'hasOpenSSL' => 'Checking for OpenSSL support',
69
'hasCurl' => 'Checking for cURL support',
70
),
157
);
158
}
159
160
+ public function processOwner() {
161
+ $disabledFunctions = explode(',', ini_get('disable_functions'));
162
+
163
+ if (is_callable('posix_geteuid')) {
164
+ if (!is_callable('posix_getpwuid') || in_array('posix_getpwuid', $disabledFunctions)) {
165
+ return array(
166
+ 'test' => false,
167
+ 'message' => 'Unavailable',
168
+ );
169
+ }
170
+
171
+ $processOwner = posix_getpwuid(posix_geteuid());
172
+ if ($processOwner !== null)
173
+ {
174
+ return array(
175
+ 'test' => true,
176
+ 'message' => $processOwner['name'],
177
+ );
178
+ }
179
+ }
180
+
181
+ $usernameOrUserEnv = getenv('USERNAME') ? getenv('USERNAME') : getenv('USER');
182
+ if (!empty($usernameOrUserEnv)) { //Check some environmental variable possibilities
183
+ return array(
184
+ 'test' => true,
185
+ 'message' => $usernameOrUserEnv,
186
+ );
187
+ }
188
+
189
+ $currentUser = get_current_user();
190
+ if (!empty($currentUser)) { //php.net comments indicate on Windows this returns the process owner rather than the file owner
191
+ return array(
192
+ 'test' => true,
193
+ 'message' => $currentUser,
194
+ );
195
+ }
196
+
197
+ if (!empty($_SERVER['LOGON_USER'])) { //Last resort for IIS since POSIX functions are unavailable, Source: https://msdn.microsoft.com/en-us/library/ms524602(v=vs.90).aspx
198
+ return array(
199
+ 'test' => true,
200
+ 'message' => $_SERVER['LOGON_USER'],
201
+ );
202
+ }
203
+
204
+ return array(
205
+ 'test' => false,
206
+ 'message' => 'Unknown',
207
+ );
208
+ }
209
+
210
public function hasOpenSSL() {
211
return is_callable('openssl_open');
212
}
lib/wordfenceClass.php CHANGED
@@ -498,6 +498,21 @@ SQL
498
}
499
}
500
501
//Must be the final line
502
}
503
private static function doEarlyAccessLogging(){
@@ -664,7 +679,9 @@ SQL
664
if (!empty($_GET['wordfence_syncAttackData']) && get_site_option('wordfence_syncingAttackData') <= time() - 60) {
665
ignore_user_abort(true);
666
update_site_option('wordfence_syncingAttackData', time());
667
- add_action('init', 'wordfence::syncAttackData');
668
}
669
670
if (wfConfig::get('other_hideWPVersion')) {
@@ -998,14 +1015,25 @@ SQL
998
$waf->getStorageEngine()->setConfig($key, $value);
999
}
1000
1001
- $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$wpdb->base_prefix}wfHits");
1002
- if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
1003
- if (get_site_option('wordfence_syncingAttackData') <= time() - 60) {
1004
- wp_remote_post(add_query_arg('wordfence_syncAttackData', microtime(true), home_url('/')), array(
1005
- 'timeout' => 0.01,
1006
- 'blocking' => false,
1007
- 'sslverify' => apply_filters('https_local_ssl_verify', false)
1008
- ));
1009
}
1010
}
1011
@@ -1381,7 +1409,7 @@ SQL
1381
}
1382
public static function ajax_sendDiagnostic_callback(){
1383
$inEmail = true;
1384
- $body = "This email is the diagnostic from " . site_url() . ".\nThe IP address that requested this was: " . wfUtils::getIP();
1385
ob_start();
1386
require 'menu_diagnostic.php';
1387
$body = nl2br($body) . ob_get_clean();
@@ -1393,7 +1421,7 @@ SQL
1393
'<td class="inactive"' => '<td style="font-weight:bold;color:#666666;" class="inactive"',
1394
);
1395
$body = str_replace(array_keys($findReplace), array_values($findReplace), $body);
1396
- $result = wfUtils::htmlEmail($_POST['email'], '[Wordfence] Diagnostic results', $body);
1397
return compact('result');
1398
}
1399
public static function ajax_sendTestEmail_callback(){
@@ -2458,6 +2486,9 @@ SQL
2458
$events = self::getLog()->getPerfStats($newestEventTime);
2459
2460
} else if ($alsoGet == 'liveTraffic') {
2461
$results = self::ajax_loadLiveTraffic_callback();
2462
$events = $results['data'];
2463
if (isset($results['sql'])) {
@@ -3446,6 +3477,7 @@ HTML;
3446
'sendDiagnostic', 'saveWAFConfig', 'updateWAFRules', 'loadLiveTraffic', 'whitelistWAFParamKey',
3447
'disableDirectoryListing', 'fixFPD', 'deleteAdminUser', 'revokeAdminUser',
3448
'hideFileHtaccess', 'saveDebuggingConfig', 'wafConfigureAutoPrepend',
3449
) as $func){
3450
add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
3451
}
@@ -4681,6 +4713,74 @@ to your httpd.conf if using Apache, or find documentation on how to disable dire
4681
return false;
4682
}
4683
4684
private static function _getWAFData() {
4685
$data['learningMode'] = wfWAF::getInstance()->isInLearningMode();
4686
$data['rules'] = wfWAF::getInstance()->getRules();
@@ -4867,7 +4967,7 @@ LIMIT %d", $lastSendTime, $limit));
4867
self::trimWfHits();
4868
}
4869
4870
- public static function syncAttackData() {
4871
global $wpdb;
4872
$waf = wfWAF::getInstance();
4873
$lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$wpdb->base_prefix}wfHits");
@@ -4961,7 +5061,17 @@ LIMIT %d", $lastSendTime, $limit));
4961
$waf->getStorageEngine()->truncateAttackData();
4962
}
4963
update_site_option('wordfence_syncingAttackData', 0);
4964
- exit;
4965
}
4966
4967
/**
498
}
499
}
500
501
+ // Call this before creating the index in cases where the wp-cron isn't running.
502
+ self::trimWfHits();
503
+ $hitsTable = "{$wpdb->base_prefix}wfHits";
504
+ $hasAttackLogTimeIndex = $wpdb->get_var($wpdb->prepare(<<<SQL
505
+ SELECT COLUMN_KEY FROM information_schema.COLUMNS
506
+ WHERE TABLE_SCHEMA = DATABASE()
507
+ AND TABLE_NAME = %s
508
+ AND COLUMN_NAME = 'attackLogTime'
509
+ SQL
510
+ , $hitsTable));
511
+
512
+ if (!$hasAttackLogTimeIndex) {
513
+ $wpdb->query("ALTER TABLE $hitsTable ADD INDEX `attackLogTime` (`attackLogTime`)");
514
+ }
515
+
516
//Must be the final line
517
}
518
private static function doEarlyAccessLogging(){
679
if (!empty($_GET['wordfence_syncAttackData']) && get_site_option('wordfence_syncingAttackData') <= time() - 60) {
680
ignore_user_abort(true);
681
update_site_option('wordfence_syncingAttackData', time());
682
+ header('Content-Type: text/javascript');
683
+ add_action('init', 'wordfence::syncAttackData', 10, 0);
684
+ add_filter('woocommerce_unforce_ssl_checkout', '__return_false');
685
}
686
687
if (wfConfig::get('other_hideWPVersion')) {
1015
$waf->getStorageEngine()->setConfig($key, $value);
1016
}
1017
1018
+ if (empty($_GET['wordfence_syncAttackData'])) {
1019
+ $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$wpdb->base_prefix}wfHits");
1020
+ if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
1021
+ if (get_site_option('wordfence_syncingAttackData') <= time() - 60) {
1022
+ // Could be the request to itself is not completing, add ajax to the head as a workaround
1023
+ $attempts = get_site_option('wordfence_syncAttackDataAttempts', 0);
1024
+ if ($attempts > 10) {
1025
+ add_action('wp_head', 'wordfence::addSyncAttackDataAjax');
1026
+ add_action('login_head', 'wordfence::addSyncAttackDataAjax');
1027
+ add_action('admin_head', 'wordfence::addSyncAttackDataAjax');
1028
+ } else {
1029
+ update_site_option('wordfence_syncAttackDataAttempts', ++$attempts);
1030
+ wp_remote_post(add_query_arg('wordfence_syncAttackData', microtime(true), home_url('/')), array(
1031
+ 'timeout' => 0.01,
1032
+ 'blocking' => false,
1033
+ 'sslverify' => apply_filters('https_local_ssl_verify', false)
1034
+ ));
1035
+ }
1036
+ }
1037
}
1038
}
1039
1409
}
1410
public static function ajax_sendDiagnostic_callback(){
1411
$inEmail = true;
1412
+ $body = "This email is the diagnostic from " . site_url() . ".\nThe IP address that requested this was: " . wfUtils::getIP() . "\nTicket Number/Forum Username: " . $_POST['ticket'];
1413
ob_start();
1414
require 'menu_diagnostic.php';
1415
$body = nl2br($body) . ob_get_clean();
1421
'<td class="inactive"' => '<td style="font-weight:bold;color:#666666;" class="inactive"',
1422
);
1423
$body = str_replace(array_keys($findReplace), array_values($findReplace), $body);
1424
+ $result = wfUtils::htmlEmail($_POST['email'], '[Wordfence] Diagnostic results (' . $_POST['ticket'] . ')', $body);
1425
return compact('result');
1426
}
1427
public static function ajax_sendTestEmail_callback(){
2486
$events = self::getLog()->getPerfStats($newestEventTime);
2487
2488
} else if ($alsoGet == 'liveTraffic') {
2489
+ if (get_site_option('wordfence_syncAttackDataAttempts') > 10) {
2490
+ self::syncAttackData(false);
2491
+ }
2492
$results = self::ajax_loadLiveTraffic_callback();
2493
$events = $results['data'];
2494
if (isset($results['sql'])) {
3477
'sendDiagnostic', 'saveWAFConfig', 'updateWAFRules', 'loadLiveTraffic', 'whitelistWAFParamKey',
3478
'disableDirectoryListing', 'fixFPD', 'deleteAdminUser', 'revokeAdminUser',
3479
'hideFileHtaccess', 'saveDebuggingConfig', 'wafConfigureAutoPrepend',
3480
+ 'whitelistBulkDelete', 'whitelistBulkEnable', 'whitelistBulkDisable',
3481
) as $func){
3482
add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
3483
}
4713
return false;
4714
}
4715
4716
+ public static function ajax_whitelistBulkDelete_callback() {
4717
+ if (class_exists('wfWAF') && $waf = wfWAF::getInstance()) {
4718
+ if (!empty($_POST['items']) && ($items = json_decode(stripslashes($_POST['items']), true)) !== false) {
4719
+ $whitelist = $waf->getStorageEngine()->getConfig('whitelistedURLParams');
4720
+ if (!is_array($whitelist)) {
4721
+ $whitelist = array();
4722
+ }
4723
+ foreach ($items as $key) {
4724
+ list($path, $paramKey, ) = $key;
4725
+ $whitelistKey = base64_encode(rawurldecode($path)) . '|' . base64_encode(rawurldecode($paramKey));
4726
+ if (array_key_exists($whitelistKey, $whitelist)) {
4727
+ unset($whitelist[$whitelistKey]);
4728
+ }
4729
+ }
4730
+ $waf->getStorageEngine()->setConfig('whitelistedURLParams', $whitelist);
4731
+ return array(
4732
+ 'data' => self::_getWAFData(),
4733
+ 'success' => true,
4734
+ );
4735
+ }
4736
+ }
4737
+ return false;
4738
+ }
4739
+
4740
+ public static function ajax_whitelistBulkEnable_callback() {
4741
+ if (class_exists('wfWAF') && $waf = wfWAF::getInstance()) {
4742
+ if (!empty($_POST['items']) && ($items = json_decode(stripslashes($_POST['items']), true)) !== false) {
4743
+ self::_whitelistBulkToggle($items, true);
4744
+ return array(
4745
+ 'data' => self::_getWAFData(),
4746
+ 'success' => true,
4747
+ );
4748
+ }
4749
+ }
4750
+ return false;
4751
+ }
4752
+
4753
+ public static function ajax_whitelistBulkDisable_callback() {
4754
+ if (class_exists('wfWAF') && $waf = wfWAF::getInstance()) {
4755
+ if (!empty($_POST['items']) && ($items = json_decode(stripslashes($_POST['items']), true)) !== false) {
4756
+ self::_whitelistBulkToggle($items, false);
4757
+ return array(
4758
+ 'data' => self::_getWAFData(),
4759
+ 'success' => true,
4760
+ );
4761
+ }
4762
+ }
4763
+ return false;
4764
+ }
4765
+
4766
+ private static function _whitelistBulkToggle($items, $enabled) {
4767
+ $waf = wfWAF::getInstance();
4768
+ $whitelist = $waf->getStorageEngine()->getConfig('whitelistedURLParams');
4769
+ if (!is_array($whitelist)) {
4770
+ $whitelist = array();
4771
+ }
4772
+ foreach ($items as $key) {
4773
+ list($path, $paramKey, ) = $key;
4774
+ $whitelistKey = base64_encode(rawurldecode($path)) . '|' . base64_encode(rawurldecode($paramKey));
4775
+ if (array_key_exists($whitelistKey, $whitelist) && is_array($whitelist[$whitelistKey])) {
4776
+ foreach ($whitelist[$whitelistKey] as $ruleID => $data) {
4777
+ $whitelist[$whitelistKey][$ruleID]['disabled'] = !$enabled;
4778
+ }
4779
+ }
4780
+ }
4781
+ $waf->getStorageEngine()->setConfig('whitelistedURLParams', $whitelist);
4782
+ }
4783
+
4784
private static function _getWAFData() {
4785
$data['learningMode'] = wfWAF::getInstance()->isInLearningMode();
4786
$data['rules'] = wfWAF::getInstance()->getRules();
4967
self::trimWfHits();
4968
}
4969
4970
+ public static function syncAttackData($exit = true) {
4971
global $wpdb;
4972
$waf = wfWAF::getInstance();
4973
$lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$wpdb->base_prefix}wfHits");
5061
$waf->getStorageEngine()->truncateAttackData();
5062
}
5063
update_site_option('wordfence_syncingAttackData', 0);
5064
+ update_site_option('wordfence_syncAttackDataAttempts', 0);
5065
+ if ($exit) {
5066
+ exit;
5067
+ }
5068
+ }
5069
+
5070
+ public static function addSyncAttackDataAjax() {
5071
+ $URL = home_url('/?wordfence_syncAttackData=' . microtime(true));
5072
+ $URL = esc_url(preg_replace('/^https?:/i', '', $URL));
5073
+ // Load as external script async so we don't slow page down.
5074
+ echo "<script type=\"text/javascript\" src=\"$URL\" async></script>";
5075
}
5076
5077
/**
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: mmaunder
3
Tags: wordpress, security, web application firewall, waf, performance, speed, caching, cache, caching plugin, wordpress cache, wordpress caching, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure, two factor, cellphone sign-in, cellphone signin, cellphone, twofactor, security, secure, htaccess, login, log, users, login alerts, lock, chmod, maintenance, plugin, private, privacy, protection, permissions, 503, base64, injection, code, encode, script, attack, hack, hackers, block, blocked, prevent, prevention, RFI, XSS, CRLF, CSRF, SQL Injection, vulnerability, website security, WordPress security, security log, logging, HTTP log, error log, login security, personal security, infrastructure security, firewall security, front-end security, web server security, proxy security, reverse proxy security, secure website, secure login, two factor security, two factor authentication, maximum login security, heartbleed, heart bleed, heartbleed vulnerability, openssl vulnerability, nginx, litespeed, php5-fpm, woocommerce support, woocommerce caching, IPv6, IP version 6
4
Requires at least: 3.9
5
Tested up to: 4.5
6
- Stable tag: 6.1.3
7
8
The Wordfence WordPress security plugin provides free enterprise-class WordPress security, protecting your website from hacks and malware.
9
== Description ==
@@ -195,6 +195,23 @@ Designed for every skill level, [The WordPress Security Learning Center](https:/
195
196
== Changelog ==
197
198
= 6.1.3 =
199
* Improvement: Added dismiss button to the Wordfence WAF setup admin notice.
200
* Fix: Removed .htaccess and .user.ini from publicly accessible config and backup file scan.
3
Tags: wordpress, security, web application firewall, waf, performance, speed, caching, cache, caching plugin, wordpress cache, wordpress caching, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure, two factor, cellphone sign-in, cellphone signin, cellphone, twofactor, security, secure, htaccess, login, log, users, login alerts, lock, chmod, maintenance, plugin, private, privacy, protection, permissions, 503, base64, injection, code, encode, script, attack, hack, hackers, block, blocked, prevent, prevention, RFI, XSS, CRLF, CSRF, SQL Injection, vulnerability, website security, WordPress security, security log, logging, HTTP log, error log, login security, personal security, infrastructure security, firewall security, front-end security, web server security, proxy security, reverse proxy security, secure website, secure login, two factor security, two factor authentication, maximum login security, heartbleed, heart bleed, heartbleed vulnerability, openssl vulnerability, nginx, litespeed, php5-fpm, woocommerce support, woocommerce caching, IPv6, IP version 6
4
Requires at least: 3.9
5
Tested up to: 4.5
6
+ Stable tag: 6.1.4
7
8
The Wordfence WordPress security plugin provides free enterprise-class WordPress security, protecting your website from hacks and malware.
9
== Description ==
195
196
== Changelog ==
197
198
+ = 6.1.4 =
199
+ Fix: Fixed potential bug with 'stored data not found after a fork. Got type: boolean'.
200
+ Improvement: Added bulk actions and filters to WAF whitelist table.
201
+ Improvement: Added a check while in learning mode to verify the response is not 404 before whitelising.
202
+ Fix: Added index to attackLogTime. wfHits trimmed on runInstall now.
203
+ Fix: Fixed attack data sync for hosts that cannot use wp-cron.
204
+ Improvement: Use wftest@wordfence.com as the Diagnostics page default email address.
205
+ Improvement: When WFWAF_ENABLED is set to false to disable the firewall, show this on the Firewall page.
206
+ Fix: Prevent warnings when $_SERVER is empty.
207
+ Fix: Bug fix for illegal string offset.
208
+ Fix: Hooked up multibyte string functions to binary safe equivalents.
209
+ Fix: Hooked up reverse IP lookup in Live Traffic.
210
+ Fix: Add the user the web server (or PHP) is currently running as to Diagnostics page.
211
+ Improvement: Pause Live Traffic after scrolling past the first entry.
212
+ Improvement: Move "Permanently block all temporarily blocked IP addresses" button to top of blocked IP list.
213
+ Fix: Added JSON fallback for PHP installations that don't have JSON enabled.
214
+
215
= 6.1.3 =
216
* Improvement: Added dismiss button to the Wordfence WAF setup admin notice.
217
* Fix: Removed .htaccess and .user.ini from publicly accessible config and backup file scan.
vendor/wordfence/wf-waf/src/lib/http.php CHANGED
@@ -292,11 +292,11 @@ class wfWAFHTTPTransportCurl extends wfWAFHTTPTransport {
292
if (is_array($queryString)) {
293
$queryString = http_build_query($queryString);
294
}
295
- $url .= (strpos($url, '?') !== false ? '&' : '?') . $queryString;
296
}
297
298
$ch = curl_init($url);
299
- switch (strtolower($request->getMethod())) {
300
case 'post':
301
curl_setopt($ch, CURLOPT_POST, 1);
302
break;
@@ -328,8 +328,8 @@ class wfWAFHTTPTransportCurl extends wfWAFHTTPTransport {
328
$curlResponse = curl_exec($ch);
329
if ($curlResponse !== false) {
330
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
331
- $header = substr($curlResponse, 0, $headerSize);
332
- $body = substr($curlResponse, $headerSize);
333
334
$response = new wfWAFHTTPResponse();
335
$response->setBody($body);
@@ -356,7 +356,7 @@ class wfWAFHTTPTransportStreams extends wfWAFHTTPTransport {
356
if (is_array($queryString)) {
357
$queryString = http_build_query($queryString);
358
}
359
- $url .= (strpos($url, '?') !== false ? '&' : '?') . $queryString;
360
}
361
362
$urlParsed = parse_url($request->getUrl());
@@ -375,7 +375,7 @@ class wfWAFHTTPTransportStreams extends wfWAFHTTPTransport {
375
if ($_headers = $request->getHeaders()) {
376
if (is_array($_headers)) {
377
foreach ($_headers as $header => $value) {
378
- if (trim(strtolower($header)) === 'user-agent') {
379
$hasUA = true;
380
}
381
$headers .= $header . ': ' . $value . "\r\n";
@@ -393,14 +393,14 @@ class wfWAFHTTPTransportStreams extends wfWAFHTTPTransport {
393
'follow_location' => 1,
394
'max_redirects' => 5,
395
);
396
- if (strlen($request->getBody()) > 0) {
397
$httpOptions['content'] = $request->getBody();
398
- $headers .= 'Content-Length: ' . strlen($httpOptions['content']) . "\r\n";
399
}
400
$httpOptions['header'] = $headers;
401
402
$options = array(
403
- strtolower($urlParsed['scheme']) => $httpOptions,
404
);
405
406
$context = stream_context_create($options);
292
if (is_array($queryString)) {
293
$queryString = http_build_query($queryString);
294
}
295
+ $url .= (wfWAFUtils::strpos($url, '?') !== false ? '&' : '?') . $queryString;
296
}
297
298
$ch = curl_init($url);
299
+ switch (wfWAFUtils::strtolower($request->getMethod())) {
300
case 'post':
301
curl_setopt($ch, CURLOPT_POST, 1);
302
break;
328
$curlResponse = curl_exec($ch);
329
if ($curlResponse !== false) {
330
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
331
+ $header = wfWAFUtils::substr($curlResponse, 0, $headerSize);
332
+ $body = wfWAFUtils::substr($curlResponse, $headerSize);
333
334
$response = new wfWAFHTTPResponse();
335
$response->setBody($body);
356
if (is_array($queryString)) {
357
$queryString = http_build_query($queryString);
358
}
359
+ $url .= (wfWAFUtils::strpos($url, '?') !== false ? '&' : '?') . $queryString;
360
}
361
362
$urlParsed = parse_url($request->getUrl());
375
if ($_headers = $request->getHeaders()) {
376
if (is_array($_headers)) {
377
foreach ($_headers as $header => $value) {
378
+ if (trim(wfWAFUtils::strtolower($header)) === 'user-agent') {
379
$hasUA = true;
380
}
381
$headers .= $header . ': ' . $value . "\r\n";
393
'follow_location' => 1,
394
'max_redirects' => 5,
395
);
396
+ if (wfWAFUtils::strlen($request->getBody()) > 0) {
397
$httpOptions['content'] = $request->getBody();
398
+ $headers .= 'Content-Length: ' . wfWAFUtils::strlen($httpOptions['content']) . "\r\n";
399
}
400
$httpOptions['header'] = $headers;
401
402
$options = array(
403
+ wfWAFUtils::strtolower($urlParsed['scheme']) => $httpOptions,
404
);
405
406
$context = stream_context_create($options);
vendor/wordfence/wf-waf/src/lib/json.php ADDED
@@ -0,0 +1,958 @@
1
+ <?php
2
+
3
+ /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
+ /**
5
+ * Converts to and from JSON format.
6
+ *
7
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
8
+ * format. It is easy for humans to read and write. It is easy for machines
9
+ * to parse and generate. It is based on a subset of the JavaScript
10
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
11
+ * This feature can also be found in Python. JSON is a text format that is
12
+ * completely language independent but uses conventions that are familiar
13
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
14
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
15
+ * ideal data-interchange language.
16
+ *
17
+ * This package provides a simple encoder and decoder for JSON notation. It
18
+ * is intended for use with client-side Javascript applications that make
19
+ * use of HTTPRequest to perform server communication functions - data can
20
+ * be encoded into JSON notation for use in a client-side javascript, or
21
+ * decoded from incoming Javascript requests. JSON format is native to
22
+ * Javascript, and can be directly eval()'ed with no further parsing
23
+ * overhead
24
+ *
25
+ * All strings should be in ASCII or UTF-8 format!
26
+ *
27
+ * LICENSE: Redistribution and use in source and binary forms, with or
28
+ * without modification, are permitted provided that the following
29
+ * conditions are met: Redistributions of source code must retain the
30
+ * above copyright notice, this list of conditions and the following
31
+ * disclaimer. Redistributions in binary form must reproduce the above
32
+ * copyright notice, this list of conditions and the following disclaimer
33
+ * in the documentation and/or other materials provided with the
34
+ * distribution.
35
+ *
36
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
39
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
40
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
41
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
42
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
44
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
45
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46
+ * DAMAGE.
47
+ *
48
+ * @category
49
+ * @package Services_JSON
50
+ * @author Michal Migurski <mike-json@teczno.com>
51
+ * @author Matt Knapp <mdknapp[at]gmail[dot]com>
52
+ * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
53
+ * @copyright 2005 Michal Migurski
54
+ * @version CVS: $Id: JSON.php 305040 2010-11-02 23:19:03Z alan_k $
55
+ * @license http://www.opensource.org/licenses/bsd-license.php
56
+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
57
+ */
58
+
59
+ /**
60
+ * Marker constant for Services_JSON::decode(), used to flag stack state
61
+ */
62
+ define('WF_SERVICES_JSON_SLICE', 1);
63
+
64
+ /**
65
+ * Marker constant for Services_JSON::decode(), used to flag stack state
66
+ */
67
+ define('WF_SERVICES_JSON_IN_STR', 2);
68
+
69
+ /**
70
+ * Marker constant for Services_JSON::decode(), used to flag stack state
71
+ */
72
+ define('WF_SERVICES_JSON_IN_ARR', 3);
73
+
74
+ /**
75
+ * Marker constant for Services_JSON::decode(), used to flag stack state
76
+ */
77
+ define('WF_SERVICES_JSON_IN_OBJ', 4);
78
+
79
+ /**
80
+ * Marker constant for Services_JSON::decode(), used to flag stack state
81
+ */
82
+ define('WF_SERVICES_JSON_IN_CMT', 5);
83
+
84
+ /**
85
+ * Behavior switch for Services_JSON::decode()
86
+ */
87
+ define('WF_SERVICES_JSON_LOOSE_TYPE', 16);
88
+
89
+ /**
90
+ * Behavior switch for Services_JSON::decode()
91
+ */
92
+ define('WF_SERVICES_JSON_SUPPRESS_ERRORS', 32);
93
+
94
+ /**
95
+ * Behavior switch for Services_JSON::decode()
96
+ */
97
+ define('WF_SERVICES_JSON_USE_TO_JSON', 64);
98
+
99
+ /**
100
+ * Converts to and from JSON format.
101
+ *
102
+ * Brief example of use:
103
+ *
104
+ * <code>
105
+ * // create a new instance of Services_JSON
106
+ * $json = new Services_JSON();
107
+ *
108
+ * // convert a complexe value to JSON notation, and send it to the browser
109
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
110
+ * $output = $json->encode($value);
111
+ *
112
+ * print($output);
113
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
114
+ *
115
+ * // accept incoming POST data, assumed to be in JSON notation
116
+ * $input = file_get_contents('php://input', 1000000);
117
+ * $value = $json->decode($input);
118
+ * </code>
119
+ */
120
+ class wfServices_JSON
121
+ {
122
+ /**
123
+ * constructs a new JSON instance
124
+ *
125
+ * @param int $use object behavior flags; combine with boolean-OR
126
+ *
127
+ * possible values:
128
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
129
+ * "{...}" syntax creates associative arrays
130
+ * instead of objects in decode().
131
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
132
+ * Values which can't be encoded (e.g. resources)
133
+ * appear as NULL instead of throwing errors.
134
+ * By default, a deeply-nested resource will
135
+ * bubble up with an error, so all return values
136
+ * from encode() should be checked with isError()
137
+ * - SERVICES_JSON_USE_TO_JSON: call toJSON when serializing objects
138
+ * It serializes the return value from the toJSON call rather
139
+ * than the object itself, toJSON can return associative arrays,
140
+ * strings or numbers, if you return an object, make sure it does
141
+ * not have a toJSON method, otherwise an error will occur.
142
+ */
143
+ function __construct( $use = 0 )
144
+ {
145
+ $this->use = $use;
146
+ $this->_mb_strlen = function_exists('mb_strlen');
147
+ $this->_mb_convert_encoding = function_exists('mb_convert_encoding');
148
+ $this->_mb_substr = function_exists('mb_substr');
149
+ }
150
+
151
+ /**
152
+ * PHP4 constructor.
153
+ */
154
+ public function wfServices_JSON( $use = 0 ) {
155
+ self::__construct( $use );
156
+ }
157
+ // private - cache the mbstring lookup results..
158
+ var $_mb_strlen = false;
159
+ var $_mb_substr = false;
160
+ var $_mb_convert_encoding = false;
161
+
162
+ /**
163
+ * convert a string from one UTF-16 char to one UTF-8 char
164
+ *
165
+ * Normally should be handled by mb_convert_encoding, but
166
+ * provides a slower PHP-only method for installations
167
+ * that lack the multibye string extension.
168
+ *
169
+ * @param string $utf16 UTF-16 character
170
+ * @return string UTF-8 character
171
+ * @access private
172
+ */
173
+ function utf162utf8($utf16)
174
+ {
175
+ // oh please oh please oh please oh please oh please
176
+ if($this->_mb_convert_encoding) {
177
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
178
+ }
179
+
180
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
181
+
182
+ switch(true) {
183
+ case ((0x7F & $bytes) == $bytes):
184
+ // this case should never be reached, because we are in ASCII range
185
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
186
+ return chr(0x7F & $bytes);
187
+
188
+ case (0x07FF & $bytes) == $bytes:
189
+ // return a 2-byte UTF-8 character
190
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
191
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
192
+ . chr(0x80 | ($bytes & 0x3F));
193
+
194
+ case (0xFFFF & $bytes) == $bytes:
195
+ // return a 3-byte UTF-8 character
196
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
197
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
198
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
199
+ . chr(0x80 | ($bytes & 0x3F));
200
+ }
201
+
202
+ // ignoring UTF-32 for now, sorry
203
+ return '';
204
+ }
205
+
206
+ /**
207
+ * convert a string from one UTF-8 char to one UTF-16 char
208
+ *
209
+ * Normally should be handled by mb_convert_encoding, but
210
+ * provides a slower PHP-only method for installations
211
+ * that lack the multibye string extension.
212
+ *
213
+ * @param string $utf8 UTF-8 character
214
+ * @return string UTF-16 character
215
+ * @access private
216
+ */
217
+ function utf82utf16($utf8)
218
+ {
219
+ // oh please oh please oh please oh please oh please
220
+ if($this->_mb_convert_encoding) {
221
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
222
+ }
223
+
224
+ switch($this->strlen8($utf8)) {
225
+ case 1:
226
+ // this case should never be reached, because we are in ASCII range
227
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
228
+ return $utf8;
229
+
230
+ case 2:
231
+ // return a UTF-16 character from a 2-byte UTF-8 char
232
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
233
+ return chr(0x07 & (ord($utf8{0}) >> 2))
234
+ . chr((0xC0 & (ord($utf8{0}) << 6))
235
+ | (0x3F & ord($utf8{1})));
236
+
237
+ case 3:
238
+ // return a UTF-16 character from a 3-byte UTF-8 char
239
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
240
+ return chr((0xF0 & (ord($utf8{0}) << 4))
241
+ | (0x0F & (ord($utf8{1}) >> 2)))
242
+ . chr((0xC0 & (ord($utf8{1}) << 6))
243
+ | (0x7F & ord($utf8{2})));
244
+ }
245
+
246
+ // ignoring UTF-32 for now, sorry
247
+ return '';
248
+ }
249
+
250
+ /**
251
+ * encodes an arbitrary variable into JSON format (and sends JSON Header)
252
+ *
253
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
254
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
255
+ * if var is a strng, note that encode() always expects it
256
+ * to be in ASCII or UTF-8 format!
257
+ *
258
+ * @return mixed JSON string representation of input var or an error if a problem occurs
259
+ * @access public
260
+ */
261
+ function encode($var)
262
+ {
263
+ header('Content-type: application/json');
264
+ return $this->encodeUnsafe($var);
265
+ }
266
+ /**
267
+ * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!)
268
+ *
269
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
270
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
271
+ * if var is a strng, note that encode() always expects it
272
+ * to be in ASCII or UTF-8 format!
273
+ *
274
+ * @return mixed JSON string representation of input var or an error if a problem occurs
275
+ * @access public
276
+ */
277
+ function encodeUnsafe($var)
278
+ {
279
+ // see bug #16908 - regarding numeric locale printing
280
+ $lc = setlocale(LC_NUMERIC, 0);
281
+ setlocale(LC_NUMERIC, 'C');
282
+ $ret = $this->_encode($var);
283
+ setlocale(LC_NUMERIC, $lc);
284
+ return $ret;
285
+
286
+ }
287
+ /**
288
+ * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
289
+ *
290
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
291
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
292
+ * if var is a strng, note that encode() always expects it
293
+ * to be in ASCII or UTF-8 format!
294
+ *
295
+ * @return mixed JSON string representation of input var or an error if a problem occurs
296
+ * @access public
297
+ */
298
+ function _encode($var)
299
+ {
300
+
301
+ switch (gettype($var)) {
302
+ case 'boolean':
303
+ return $var ? 'true' : 'false';
304
+
305
+ case 'NULL':
306
+ return 'null';
307
+
308
+ case 'integer':
309
+ return (int) $var;
310
+
311
+ case 'double':
312
+ case 'float':
313
+ return (float) $var;
314
+
315
+ case 'string':
316
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
317
+ $ascii = '';
318
+ $strlen_var = $this->strlen8($var);
319
+
320
+ /*
321
+ * Iterate over every character in the string,
322
+ * escaping with a slash or encoding to UTF-8 where necessary
323
+ */
324
+ for ($c = 0; $c < $strlen_var; ++$c) {
325
+
326
+ $ord_var_c = ord($var{$c});
327
+
328
+ switch (true) {
329
+ case $ord_var_c == 0x08:
330
+ $ascii .= '\b';
331
+ break;
332
+ case $ord_var_c == 0x09:
333
+ $ascii .= '\t';
334
+ break;
335
+ case $ord_var_c == 0x0A:
336
+ $ascii .= '\n';
337
+ break;
338
+ case $ord_var_c == 0x0C:
339
+ $ascii .= '\f';
340
+ break;
341
+ case $ord_var_c == 0x0D:
342
+ $ascii .= '\r';
343
+ break;
344
+
345
+ case $ord_var_c == 0x22:
346
+ case $ord_var_c == 0x2F:
347
+ case $ord_var_c == 0x5C:
348
+ // double quote, slash, slosh
349
+ $ascii .= '\\'.$var{$c};
350
+ break;
351
+
352
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
353
+ // characters U-00000000 - U-0000007F (same as ASCII)
354
+ $ascii .= $var{$c};
355
+ break;
356
+
357
+ case (($ord_var_c & 0xE0) == 0xC0):
358
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
359
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
360
+ if ($c+1 >= $strlen_var) {
361
+ $c += 1;
362
+ $ascii .= '?';
363
+ break;
364
+ }
365
+
366
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
367
+ $c += 1;
368
+ $utf16 = $this->utf82utf16($char);
369
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
370
+ break;
371
+
372
+ case (($ord_var_c & 0xF0) == 0xE0):
373
+ if ($c+2 >= $strlen_var) {
374
+ $c += 2;
375
+ $ascii .= '?';
376
+ break;
377
+ }
378
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
379
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
380
+ $char = pack('C*', $ord_var_c,
381
+ @ord($var{$c + 1}),
382
+ @ord($var{$c + 2}));
383
+ $c += 2;
384
+ $utf16 = $this->utf82utf16($char);
385
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
386
+ break;
387
+
388
+ case (($ord_var_c & 0xF8) == 0xF0):
389
+ if ($c+3 >= $strlen_var) {
390
+ $c += 3;
391
+ $ascii .= '?';
392
+ break;
393
+ }
394
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
395
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
396
+ $char = pack('C*', $ord_var_c,
397
+ ord($var{$c + 1}),
398
+ ord($var{$c + 2}),
399
+ ord($var{$c + 3}));
400
+ $c += 3;
401
+ $utf16 = $this->utf82utf16($char);
402
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
403
+ break;
404
+
405
+ case (($ord_var_c & 0xFC) == 0xF8):
406
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
407
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
408
+ if ($c+4 >= $strlen_var) {
409
+ $c += 4;
410
+ $ascii .= '?';
411
+ break;
412
+ }
413
+ $char = pack('C*', $ord_var_c,
414
+ ord($var{$c + 1}),
415
+ ord($var{$c + 2}),
416
+ ord($var{$c + 3}),
417
+ ord($var{$c + 4}));
418
+ $c += 4;
419
+ $utf16 = $this->utf82utf16($char);
420
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
421
+ break;
422
+
423
+ case (($ord_var_c & 0xFE) == 0xFC):
424
+ if ($c+5 >= $strlen_var) {
425
+ $c += 5;
426
+ $ascii .= '?';
427
+ break;
428
+ }
429
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
430
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
431
+ $char = pack('C*', $ord_var_c,
432
+ ord($var{$c + 1}),
433
+ ord($var{$c + 2}),
434
+ ord($var{$c + 3}),
435
+ ord($var{$c + 4}),
436
+ ord($var{$c + 5}));
437
+ $c += 5;
438
+ $utf16 = $this->utf82utf16($char);
439
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
440
+ break;
441
+ }
442
+ }
443
+ return '"'.$ascii.'"';
444
+
445
+ case 'array':
446
+ /*
447
+ * As per JSON spec if any array key is not an integer
448
+ * we must treat the whole array as an object. We
449
+ * also try to catch a sparsely populated associative
450
+ * array with numeric keys here because some JS engines
451
+ * will create an array with empty indexes up to
452
+ * max_index which can cause memory issues and because
453
+ * the keys, which may be relevant, will be remapped
454
+ * otherwise.
455
+ *
456
+ * As per the ECMA and JSON specification an object may
457
+ * have any string as a property. Unfortunately due to
458
+ * a hole in the ECMA specification if the key is a
459
+ * ECMA reserved word or starts with a digit the
460
+ * parameter is only accessible using ECMAScript's
461
+ * bracket notation.
462
+ */
463
+
464
+ // treat as a JSON object
465
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
466
+ $properties = array_map(array($this, 'name_value'),
467
+ array_keys($var),
468
+ array_values($var));
469
+
470
+ foreach($properties as $property) {
471
+ if(wfServices_JSON::isError($property)) {
472
+ return $property;
473
+ }
474
+ }
475
+
476
+ return '{' . join(',', $properties) . '}';
477
+ }
478
+
479
+ // treat it like a regular array
480
+ $elements = array_map(array($this, '_encode'), $var);
481
+
482
+ foreach($elements as $element) {
483
+ if(wfServices_JSON::isError($element)) {
484
+ return $element;
485
+ }
486
+ }
487
+
488
+ return '[' . join(',', $elements) . ']';
489
+
490
+ case 'object':
491
+
492
+ // support toJSON methods.
493
+ if (($this->use & WF_SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) {
494
+ // this may end up allowing unlimited recursion
495
+ // so we check the return value to make sure it's not got the same method.
496
+ $recode = $var->toJSON();
497
+
498
+ if (method_exists($recode, 'toJSON')) {
499
+
500
+ return ($this->use & WF_SERVICES_JSON_SUPPRESS_ERRORS)
501
+ ? 'null'
502
+ : new wfServices_JSON_Error(get_class($var).
503
+ " toJSON returned an object with a toJSON method.");
504
+
505
+ }
506
+
507
+ return $this->_encode( $recode );
508
+ }
509
+
510
+ $vars = get_object_vars($var);
511
+
512
+ $properties = array_map(array($this, 'name_value'),
513
+ array_keys($vars),
514
+ array_values($vars));
515
+
516
+ foreach($properties as $property) {
517
+ if(wfServices_JSON::isError($property)) {
518
+ return $property;
519
+ }
520
+ }
521
+
522
+ return '{' . join(',', $properties) . '}';
523
+
524
+ default:
525
+ return ($this->use & WF_SERVICES_JSON_SUPPRESS_ERRORS)
526
+ ? 'null'
527
+ : new wfServices_JSON_Error(gettype($var)." can not be encoded as JSON string");
528
+ }
529
+ }
530
+
531
+ /**
532
+ * array-walking function for use in generating JSON-formatted name-value pairs
533
+ *
534
+ * @param string $name name of key to use
535
+ * @param mixed $value reference to an array element to be encoded
536
+ *
537
+ * @return string JSON-formatted name-value pair, like '"name":value'
538
+ * @access private
539
+ */
540
+ function name_value($name, $value)
541
+ {
542
+ $encoded_value = $this->_encode($value);
543
+
544
+ if(wfServices_JSON::isError($encoded_value)) {
545
+ return $encoded_value;
546
+ }
547
+
548
+ return $this->_encode(strval($name)) . ':' . $encoded_value;
549
+ }
550
+
551
+ /**
552
+ * reduce a string by removing leading and trailing comments and whitespace
553
+ *
554
+ * @param $str string string value to strip of comments and whitespace
555
+ *
556
+ * @return string string value stripped of comments and whitespace
557
+ * @access private
558
+ */
559
+ function reduce_string($str)
560
+ {
561
+ $str = preg_replace(array(
562
+
563
+ // eliminate single line comments in '// ...' form
564
+ '#^\s*//(.+)$#m',
565
+
566
+ // eliminate multi-line comments in '/* ... */' form, at start of string
567
+ '#^\s*/\*(.+)\*/#Us',
568
+
569
+ // eliminate multi-line comments in '/* ... */' form, at end of string
570
+ '#/\*(.+)\*/\s*$#Us'
571
+
572
+ ), '', $str);
573
+
574
+ // eliminate extraneous space
575
+ return trim($str);
576
+ }
577
+
578
+ /**
579
+ * decodes a JSON string into appropriate variable
580
+ *
581
+ * @param string $str JSON-formatted string
582
+ *
583
+ * @return mixed number, boolean, string, array, or object
584
+ * corresponding to given JSON input string.
585
+ * See argument 1 to Services_JSON() above for object-output behavior.
586
+ * Note that decode() always returns strings
587
+ * in ASCII or UTF-8 format!
588
+ * @access public
589
+ */
590
+ function decode($str)
591
+ {
592
+ $str = $this->reduce_string($str);
593
+
594
+ switch (strtolower($str)) {
595
+ case 'true':
596
+ return true;
597
+
598
+ case 'false':
599
+ return false;
600
+
601
+ case 'null':
602
+ return null;
603
+
604
+ default:
605
+ $m = array();
606
+
607
+ if (is_numeric($str)) {
608
+ // Lookie-loo, it's a number
609
+
610
+ // This would work on its own, but I'm trying to be
611
+ // good about returning integers where appropriate:
612
+ // return (float)$str;
613
+
614
+ // Return float or int, as appropriate
615
+ return ((float)$str == (integer)$str)
616
+ ? (integer)$str
617
+ : (float)$str;
618
+
619
+ } elseif (preg_match('/^("|\').*(\1)#x2F;s', $str, $m) && $m[1] == $m[2]) {
620
+ // STRINGS RETURNED IN UTF-8 FORMAT
621
+ $delim = $this->substr8($str, 0, 1);
622
+ $chrs = $this->substr8($str, 1, -1);
623
+ $utf8 = '';
624
+ $strlen_chrs = $this->strlen8($chrs);
625
+
626
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
627