Wordfence Security – Firewall & Malware Scan - Version 6.1.1

Version Description

  • Enhancement: Added Web Application Firewall
  • Enhancement: Added Diagnostics page
  • Enhancement: Added new scans:
    • Admins created outside of WordPress
    • Publicly accessible common (database or wp-config.php) backup files
  • Improvement: Updated Live Traffic with filters and to include blocked requests in the feed.
Download this release

Release Info

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

Code changes from version 6.0.25 to 6.1.1

Files changed (86) hide show
  1. css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  2. css/images/ui-bg_flat_100_1997c7_40x100.png +0 -0
  3. css/images/ui-bg_flat_100_222_40x100.png +0 -0
  4. css/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  5. css/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  6. css/images/ui-bg_highlight-soft_75_a5a5a5_1x100.png +0 -0
  7. css/images/ui-icons_222222_256x240.png +0 -0
  8. css/images/ui-icons_cd0a0a_256x240.png +0 -0
  9. css/images/ui-icons_fbe569_256x240.png +0 -0
  10. css/images/ui-icons_fff_256x240.png +0 -0
  11. css/jquery-ui-timepicker-addon.css +27 -0
  12. css/jquery-ui.min.css +7 -0
  13. css/jquery-ui.structure.min.css +5 -0
  14. css/jquery-ui.theme.min.css +5 -0
  15. css/main.css +489 -5
  16. css/select2.min.css +1 -0
  17. js/admin.js +455 -39
  18. js/admin.liveTraffic.js +680 -0
  19. js/jquery-ui-timepicker-addon.js +2245 -0
  20. js/knockout-3.3.0.js +115 -0
  21. js/select2.min.js +2 -0
  22. lib/compat.php +17 -0
  23. lib/dashboard.php +0 -1
  24. lib/email_newIssues.php +11 -3
  25. lib/menu_activity.php +416 -209
  26. lib/menu_countryBlocking.php +7 -7
  27. lib/menu_diagnostic.php +404 -0
  28. lib/menu_options.php +61 -112
  29. lib/menu_passwd.php +5 -4
  30. lib/menu_scan.php +201 -18
  31. lib/menu_scanSchedule.php +6 -9
  32. lib/menu_twoFactor.php +7 -8
  33. lib/menu_waf.php +472 -0
  34. lib/sysinfo.php +1 -1
  35. lib/wf503.php +2 -2
  36. lib/wfAPI.php +6 -5
  37. lib/wfConfig.php +36 -11
  38. lib/wfCrawl.php +9 -11
  39. lib/wfDB.php +159 -0
  40. lib/wfDiagnostic.php +245 -0
  41. lib/wfIssues.php +5 -2
  42. lib/wfLog.php +979 -31
  43. lib/wfScanEngine.php +524 -87
  44. lib/wfSchema.php +2 -2
  45. lib/wfUtils.php +306 -19
  46. lib/wfView.php +2 -2
  47. lib/wordfenceClass.php +1794 -78
  48. lib/wordfenceConstants.php +3 -1
  49. lib/wordfenceHash.php +9 -14
  50. lib/wordfenceScanner.php +111 -96
  51. readme.txt +17 -4
  52. vendor/autoload.php +7 -0
  53. vendor/composer/ClassLoader.php +413 -0
  54. vendor/composer/LICENSE +21 -0
  55. vendor/composer/autoload_classmap.php +9 -0
  56. vendor/composer/autoload_namespaces.php +9 -0
  57. vendor/composer/autoload_psr4.php +9 -0
  58. vendor/composer/autoload_real.php +45 -0
  59. vendor/composer/installed.json +20 -0
  60. vendor/wordfence/wf-waf/src/baseRules.rules +187 -0
  61. vendor/wordfence/wf-waf/src/bootstrap-sample.php +57 -0
  62. vendor/wordfence/wf-waf/src/init.php +28 -0
  63. vendor/wordfence/wf-waf/src/lib/config.php +2 -0
  64. vendor/wordfence/wf-waf/src/lib/http.php +438 -0
  65. vendor/wordfence/wf-waf/src/lib/parser/lexer.php +667 -0
  66. vendor/wordfence/wf-waf/src/lib/parser/parser.php +754 -0
  67. vendor/wordfence/wf-waf/src/lib/parser/sqli.php +2971 -0
  68. vendor/wordfence/wf-waf/src/lib/request.php +816 -0
  69. vendor/wordfence/wf-waf/src/lib/rules.php +1229 -0
  70. vendor/wordfence/wf-waf/src/lib/storage.php +62 -0
  71. vendor/wordfence/wf-waf/src/lib/storage/file.php +1320 -0
  72. vendor/wordfence/wf-waf/src/lib/utils.php +321 -0
  73. vendor/wordfence/wf-waf/src/lib/view.php +127 -0
  74. vendor/wordfence/wf-waf/src/lib/waf.php +1545 -0
  75. vendor/wordfence/wf-waf/src/logs/.htaccess +7 -0
  76. vendor/wordfence/wf-waf/src/logs/attack-data.php +1 -0
  77. vendor/wordfence/wf-waf/src/logs/config.php +0 -0
  78. vendor/wordfence/wf-waf/src/logs/ips.php +1 -0
  79. vendor/wordfence/wf-waf/src/rules.key +9 -0
  80. vendor/wordfence/wf-waf/src/rules.php +185 -0
  81. vendor/wordfence/wf-waf/src/views/403-roadblock.php +110 -0
  82. vendor/wordfence/wf-waf/src/views/403.php +14 -0
  83. views/waf/debug.php +225 -0
  84. waf/bootstrap.php +249 -0
  85. waf/wfWAFUserIPRange.php +224 -0
  86. wordfence.php +37 -2
css/images/ui-bg_flat_0_aaaaaa_40x100.png ADDED
Binary file
css/images/ui-bg_flat_100_1997c7_40x100.png ADDED
Binary file
css/images/ui-bg_flat_100_222_40x100.png ADDED
Binary file
css/images/ui-bg_flat_75_ffffff_40x100.png ADDED
Binary file
css/images/ui-bg_glass_95_fef1ec_1x400.png ADDED
Binary file
css/images/ui-bg_highlight-soft_75_a5a5a5_1x100.png ADDED
Binary file
css/images/ui-icons_222222_256x240.png ADDED
Binary file
css/images/ui-icons_cd0a0a_256x240.png ADDED
Binary file
css/images/ui-icons_fbe569_256x240.png ADDED
Binary file
css/images/ui-icons_fff_256x240.png ADDED
Binary file
css/jquery-ui-timepicker-addon.css ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
2
+ .ui-timepicker-div dl { text-align: left; }
3
+ .ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; }
4
+ .ui-timepicker-div dl dd { margin: 0 10px 10px 40%; }
5
+ .ui-timepicker-div td { font-size: 90%; }
6
+ .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
7
+ .ui-timepicker-div .ui_tpicker_unit_hide{ display: none; }
8
+
9
+ .ui-timepicker-rtl{ direction: rtl; }
10
+ .ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; }
11
+ .ui-timepicker-rtl dl dt{ float: right; clear: right; }
12
+ .ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; }
13
+
14
+ /* Shortened version style */
15
+ .ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; }
16
+ .ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time,
17
+ .ui-timepicker-div.ui-timepicker-oneLine dt { display: none; }
18
+ .ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; }
19
+ .ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; }
20
+ .ui-timepicker-div.ui-timepicker-oneLine dl dd,
21
+ .ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; }
22
+ .ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before,
23
+ .ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; }
24
+ .ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before,
25
+ .ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; }
26
+ .ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide,
27
+ .ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; }
css/jquery-ui.min.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ /*! jQuery UI - v1.11.4 - 2015-10-13
2
+ * http://jqueryui.com
3
+ * Includes: core.css, draggable.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, menu.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css
4
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=3px&bgColorHeader=%23222&bgTextureHeader=flat&bgImgOpacityHeader=100&borderColorHeader=%23474747&fcHeader=%23fff&iconColorHeader=%23fff&bgColorContent=%23ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=%23aaaaaa&fcContent=%23222222&iconColorContent=%23222222&bgColorDefault=%23a5a5a5&bgTextureDefault=highlight_soft&bgImgOpacityDefault=75&borderColorDefault=%239f9f9f&fcDefault=%23fff&iconColorDefault=%23fff&bgColorHover=%231997c7&bgTextureHover=flat&bgImgOpacityHover=100&borderColorHover=%23198cb7&fcHover=%23fff&iconColorHover=%23fff&bgColorActive=%231997c7&bgTextureActive=flat&bgImgOpacityActive=100&borderColorActive=%23198cb7&fcActive=%23fff&iconColorActive=%23fff&bgColorHighlight=%23fffaba&bgTextureHighlight=flat&bgImgOpacityHighlight=100&borderColorHighlight=%23eac500&fcHighlight=%23222222&iconColorHighlight=%23ec882f&bgColorError=%23fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=%23cd0a0a&fcError=%23cd0a0a&iconColorError=%23cd0a0a&bgColorOverlay=%23aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=%23aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
5
+ * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
6
+
7
+ .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;min-height:0;font-size:100%}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:none}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;cursor:pointer;min-height:0;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none;cursor:pointer}.ui-selectmenu-button span.ui-icon{right:0.5em;left:auto;margin-top:-8px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:0.4em 2.1em 0.4em 1em;display:block;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #474747;background:#222 url("images/ui-bg_flat_100_222_40x100.png") 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #9f9f9f;background:#a5a5a5 url("images/ui-bg_highlight-soft_75_a5a5a5_1x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#fff;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #198cb7;background:#1997c7 url("images/ui-bg_flat_100_1997c7_40x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#fff;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #198cb7;background:#1997c7 url("images/ui-bg_flat_100_1997c7_40x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #eac500;background:#fffaba url("images/ui-bg_flat_100_fffaba_40x100.png") 50% 50% repeat-x;color:#222}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#222}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_ec882f_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cd0a0a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px}
css/jquery-ui.structure.min.css ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ /*! jQuery UI - v1.11.4 - 2015-10-11
2
+ * http://jqueryui.com
3
+ * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
4
+
5
+ .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;min-height:0;font-size:100%}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:none}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;cursor:pointer;min-height:0;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none;cursor:pointer}.ui-selectmenu-button span.ui-icon{right:0.5em;left:auto;margin-top:-8px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:0.4em 2.1em 0.4em 1em;display:block;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}
css/jquery-ui.theme.min.css ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ /*! jQuery UI - v1.11.4 - 2015-10-13
2
+ * http://jqueryui.com
3
+ * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
4
+
5
+ .ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #474747;background:#222 url("images/ui-bg_flat_100_222_40x100.png") 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #9f9f9f;background:#a5a5a5 url("images/ui-bg_highlight-soft_75_a5a5a5_1x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#fff;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #198cb7;background:#1997c7 url("images/ui-bg_flat_100_1997c7_40x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#fff;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #198cb7;background:#1997c7 url("images/ui-bg_flat_100_1997c7_40x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #eac500;background:#fffaba url("images/ui-bg_flat_100_fffaba_40x100.png") 50% 50% repeat-x;color:#222}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#222}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_fff_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_ec882f_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cd0a0a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px}
css/main.css CHANGED
@@ -18,6 +18,9 @@ div.wordfenceLive {
18
  font-size: 14px;
19
  -webkit-font-smoothing: antialiased;
20
  }
 
 
 
21
  div.wordfenceLive h2 {
22
  font-weight: bold;
23
  color: #888;
@@ -46,6 +49,13 @@ div.wordfenceLive p {
46
  #wfHeading {
47
  white-space: nowrap;
48
  }
 
 
 
 
 
 
 
49
  div.wordfence-lock-icon {
50
  background-image: url(../images/wordfence-logo-32x32.png);
51
  }
@@ -170,6 +180,9 @@ div.wfIssue table.wfIssueLinks td { border-width: 0; text-align: left; padding-r
170
  display: block;
171
  width: 60px;
172
  }
 
 
 
173
  .wfProbSev1, .wfProbSev2, .wfAjaxLight128, .wfResolved {
174
  width: 128px;
175
  height: 128px;
@@ -191,7 +204,77 @@ div.wfIssue table.wfIssueLinks td { border-width: 0; text-align: left; padding-r
191
  img.wfFlag { vertical-align: middle; margin: -3px 4px 0 0; }
192
  .wfHitTime { font-style: italic; }
193
  .wfAvatar img { vertical-align: middle; }
194
- .wfActEvent { border-bottom: 1px solid #CCC; margin: 0 0 10px 0; padding: 0 0 10px 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  .wfTimeAgo { font-family: Georgia, times; color: #999; font-weight: bold; font-style: italic; }
196
  table.wfConfigForm th {
197
  font-weight: normal;
@@ -284,7 +367,11 @@ h3.wfConfigHeading {
284
  font-weight: bold;
285
  color: #555;
286
  }
287
- input.wfStartScanButton { width: 160px; text-align: left; padding-left: 20px; }
 
 
 
 
288
  .wferror {
289
  color: #F00;
290
  }
@@ -447,7 +534,9 @@ table.wf-table td {
447
  border: 1px solid #ccc;
448
  }
449
  table.wf-table thead th,
450
- table.wf-table thead td {
 
 
451
  background-color: #222;
452
  color: #fff;
453
  font-weight: bold;
@@ -461,9 +550,14 @@ table.wf-table tbody tr.even td,
461
  table.wf-table tbody tr:nth-child(2n) td {
462
  background-color: #eee;
463
  }
464
- table.wf-table tbody tr:hover td {
465
  background-color: #fffbd8;
466
  }
 
 
 
 
 
467
 
468
  table.block-ranges-table {
469
  border-collapse: collapse;
@@ -482,6 +576,13 @@ table.block-ranges-table tr td {
482
  border: 1px solid #ffd975;
483
  border-width: 1px 1px 1px 10px;
484
  }
 
 
 
 
 
 
 
485
 
486
  .wf-premium-callout {
487
  border: 1px solid #00709E;
@@ -512,4 +613,387 @@ table.block-ranges-table tr td {
512
  text-transform: uppercase;
513
  font-weight: bold;
514
  background-color: #00709E;
515
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  font-size: 14px;
19
  -webkit-font-smoothing: antialiased;
20
  }
21
+ .branch-4-4 div.wordfenceLive td {
22
+ padding: 5px 0px 0px;
23
+ }
24
  div.wordfenceLive h2 {
25
  font-weight: bold;
26
  color: #888;
49
  #wfHeading {
50
  white-space: nowrap;
51
  }
52
+ #wfHeading:after {
53
+ content: '.';
54
+ visibility: hidden;
55
+ display: block;
56
+ clear: both;
57
+ height: 0px;
58
+ }
59
  div.wordfence-lock-icon {
60
  background-image: url(../images/wordfence-logo-32x32.png);
61
  }
180
  display: block;
181
  width: 60px;
182
  }
183
+ .wfIssueOptions p {
184
+ margin:6px 0px 0px;
185
+ }
186
  .wfProbSev1, .wfProbSev2, .wfAjaxLight128, .wfResolved {
187
  width: 128px;
188
  height: 128px;
204
  img.wfFlag { vertical-align: middle; margin: -3px 4px 0 0; }
205
  .wfHitTime { font-style: italic; }
206
  .wfAvatar img { vertical-align: middle; }
207
+ .wfActEvent {
208
+ border-bottom: 1px solid #CCC;
209
+ padding: 10px 20px;
210
+ overflow: auto;
211
+ }
212
+ #wf-lt-listings .wfActEvent {
213
+ padding-left: 15px;
214
+ border-left: 5px solid #cccccc;
215
+ }
216
+ #wf-lt-listings .wfActEvent.wfHuman {
217
+ border-left: 5px solid #74cb76;
218
+ }
219
+ #wf-lt-listings .wfActEvent.wfActionBlocked {
220
+ border-left: 5px solid #d03935;
221
+ }
222
+ #wf-lt-listings .wfActEvent.wfNotice,
223
+ #wf-lt-listings .wfActEvent.wf404 {
224
+ border-left: 5px solid #ffeaa0;
225
+ }
226
+ #wf-lt-listings .wfActEvent.wfWarning {
227
+ border-left: 5px solid #ffa13f;
228
+ }
229
+ #wf-lt-listings .wfActEvent:hover {
230
+ background-color: #fff9e9 !important;
231
+ }
232
+ #wf-live-traffic {
233
+ position: relative;
234
+ overflow: visible;
235
+ }
236
+ #wf-live-traffic-legend {
237
+ position: absolute;
238
+ top: -1px;
239
+ left: auto;
240
+ right: -108px;
241
+ bottom: 100%;
242
+ }
243
+ #wf-live-traffic-legend.sticky {
244
+ position: fixed;
245
+ top: 51px;
246
+ right: auto;
247
+ left: 1150px;
248
+ }
249
+ #wf-live-traffic-legend ul {
250
+ margin: 0;
251
+ padding: 10px;
252
+ background-color: #fff;
253
+ border: 1px solid #CCC;
254
+ }
255
+ #wf-live-traffic-legend ul li {
256
+ margin: 0;
257
+ padding: 0;
258
+ }
259
+ #wf-live-traffic-legend ul li:before {
260
+ content: '';
261
+ display: block;
262
+ float: left;
263
+ margin: 3px 6px 0 0;
264
+ width: 12px;
265
+ height: 12px;
266
+ background-color: #CCC;
267
+ }
268
+ #wf-live-traffic-legend ul li.wfHuman:before {
269
+ background-color: #74cb76;
270
+ }
271
+ #wf-live-traffic-legend ul li.wfNotice:before {
272
+ background-color: #ffeaa0;
273
+ }
274
+ #wf-live-traffic-legend ul li.wfBlocked:before {
275
+ background-color: #d03935;
276
+ }
277
+
278
  .wfTimeAgo { font-family: Georgia, times; color: #999; font-weight: bold; font-style: italic; }
279
  table.wfConfigForm th {
280
  font-weight: normal;
367
  font-weight: bold;
368
  color: #555;
369
  }
370
+ .wfStartScanButton { text-align: center; }
371
+ .wf-spinner {
372
+ display: inline-block;
373
+ width: 4px;
374
+ }
375
  .wferror {
376
  color: #F00;
377
  }
534
  border: 1px solid #ccc;
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;
541
  color: #fff;
542
  font-weight: bold;
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
  }
556
+ table.wf-table tbody.empty-row tr td {
557
+ border-width: 0;
558
+ padding: 8px 0;
559
+ background-color: transparent;
560
+ }
561
 
562
  table.block-ranges-table {
563
  border-collapse: collapse;
576
  border: 1px solid #ffd975;
577
  border-width: 1px 1px 1px 10px;
578
  }
579
+ .wf-success {
580
+ margin: 12px 0;
581
+ padding: 8px;
582
+ background-color: #ffffff;
583
+ border: 1px solid #74cb76;
584
+ border-width: 1px 1px 1px 10px;
585
+ }
586
 
587
  .wf-premium-callout {
588
  border: 1px solid #00709E;
613
  text-transform: uppercase;
614
  font-weight: bold;
615
  background-color: #00709E;
616
+ }
617
+
618
+
619
+ .wf-table td.error {
620
+ color: #d0514c;
621
+ font-weight: bold;
622
+ }
623
+ .wf-table td.success:before,
624
+ .wf-table td.error:before {
625
+ font-size: 16px;
626
+ display: inline-block;
627
+ margin: 0px 8px 0px 0px;
628
+ }
629
+ .wf-table td.error:before {
630
+ content: "\2718";
631
+ }
632
+ .wf-table td.success {
633
+ color: #008c10;
634
+ font-weight: bold;
635
+
636
+ max-width: 20%;
637
+ }
638
+ .wf-table td.success:before {
639
+ content: "\2713";
640
+ }
641
+ .wf-table td.inactive {
642
+ font-weight: bold;
643
+ color: #666666;
644
+ }
645
+
646
+ table.whitelist-table {
647
+
648
+ }
649
+ table.whitelist-table .whitelist-edit {
650
+ display: none;
651
+ }
652
+ table.whitelist-table .edit-mode .whitelist-display {
653
+ display: none;
654
+ }
655
+ table.whitelist-table .edit-mode .whitelist-edit {
656
+ display: block;
657
+ }
658
+ table.whitelist-table .edit-mode span.whitelist-edit,
659
+ table.whitelist-table .edit-mode input.whitelist-edit {
660
+ display: inline;
661
+ }
662
+
663
+ .wf-pad-small {
664
+ margin:8px 0;
665
+ }
666
+ #wf-lt-listings {
667
+ margin:0 0 0;
668
+ }
669
+ #wf-lt-listings a {
670
+ cursor: pointer;
671
+ text-decoration: underline;
672
+ }
673
+ .wfActionBlocked {
674
+ background-color: #fff6f6;
675
+ }
676
+
677
+ [class*="span"] {
678
+ float: left;
679
+ min-height: 1px;
680
+ margin-left: 30px;
681
+ }
682
+ .row-fluid {
683
+ width: 100%;
684
+ *zoom: 1;
685
+ }
686
+ .row-fluid:before,
687
+ .row-fluid:after {
688
+ display: table;
689
+ line-height: 0;
690
+ content: "";
691
+ }
692
+ .row-fluid:after {
693
+ clear: both;
694
+ }
695
+ .row-fluid [class*="span"] {
696
+ display: block;
697
+ float: left;
698
+ width: 100%;
699
+ min-height: 30px;
700
+ margin-left: 2.564102564102564%;
701
+ *margin-left: 2.5109110747408616%;
702
+ -webkit-box-sizing: border-box;
703
+ -moz-box-sizing: border-box;
704
+ box-sizing: border-box;
705
+ }
706
+ .row-fluid [class*="span"]:first-child {
707
+ margin-left: 0;
708
+ }
709
+ .row-fluid .controls-row [class*="span"] + [class*="span"] {
710
+ margin-left: 2.564102564102564%;
711
+ }
712
+ .row-fluid .span12 {
713
+ width: 100%;
714
+ *width: 99.94680851063829%;
715
+ }
716
+ .row-fluid .span11 {
717
+ width: 91.45299145299145%;
718
+ *width: 91.39979996362975%;
719
+ }
720
+ .row-fluid .span10 {
721
+ width: 82.90598290598291%;
722
+ *width: 82.8527914166212%;
723
+ }
724
+ .row-fluid .span9 {
725
+ width: 74.35897435897436%;
726
+ *width: 74.30578286961266%;
727
+ }
728
+ .row-fluid .span8 {
729
+ width: 65.81196581196582%;
730
+ *width: 65.75877432260411%;
731
+ }
732
+ .row-fluid .span7 {
733
+ width: 57.26495726495726%;
734
+ *width: 57.21176577559556%;
735
+ }
736
+ .row-fluid .span6 {
737
+ width: 48.717948717948715%;
738
+ *width: 48.664757228587014%;
739
+ }
740
+ .row-fluid .span5 {
741
+ width: 40.17094017094017%;
742
+ *width: 40.11774868157847%;
743
+ }
744
+ .row-fluid .span4 {
745
+ width: 31.623931623931625%;
746
+ *width: 31.570740134569924%;
747
+ }
748
+ .row-fluid .span3 {
749
+ width: 23.076923076923077%;
750
+ *width: 23.023731587561375%;
751
+ }
752
+ .row-fluid .span2 {
753
+ width: 14.52991452991453%;
754
+ *width: 14.476723040552828%;
755
+ }
756
+ .row-fluid .span1 {
757
+ width: 5.982905982905983%;
758
+ *width: 5.929714493544281%;
759
+ }
760
+ .row-fluid .offset12 {
761
+ margin-left: 105.12820512820512%;
762
+ *margin-left: 105.02182214948171%;
763
+ }
764
+ .row-fluid .offset12:first-child {
765
+ margin-left: 102.56410256410257%;
766
+ *margin-left: 102.45771958537915%;
767
+ }
768
+ .row-fluid .offset11 {
769
+ margin-left: 96.58119658119658%;
770
+ *margin-left: 96.47481360247316%;
771
+ }
772
+ .row-fluid .offset11:first-child {
773
+ margin-left: 94.01709401709402%;
774
+ *margin-left: 93.91071103837061%;
775
+ }
776
+ .row-fluid .offset10 {
777
+ margin-left: 88.03418803418803%;
778
+ *margin-left: 87.92780505546462%;
779
+ }
780
+ .row-fluid .offset10:first-child {
781
+ margin-left: 85.47008547008548%;
782
+ *margin-left: 85.36370249136206%;
783
+ }
784
+ .row-fluid .offset9 {
785
+ margin-left: 79.48717948717949%;
786
+ *margin-left: 79.38079650845607%;
787
+ }
788
+ .row-fluid .offset9:first-child {
789
+ margin-left: 76.92307692307693%;
790
+ *margin-left: 76.81669394435352%;
791
+ }
792
+ .row-fluid .offset8 {
793
+ margin-left: 70.94017094017094%;
794
+ *margin-left: 70.83378796144753%;
795
+ }
796
+ .row-fluid .offset8:first-child {
797
+ margin-left: 68.37606837606839%;
798
+ *margin-left: 68.26968539734497%;
799
+ }
800
+ .row-fluid .offset7 {
801
+ margin-left: 62.393162393162385%;
802
+ *margin-left: 62.28677941443899%;
803
+ }
804
+ .row-fluid .offset7:first-child {
805
+ margin-left: 59.82905982905982%;
806
+ *margin-left: 59.72267685033642%;
807
+ }
808
+ .row-fluid .offset6 {
809
+ margin-left: 53.84615384615384%;
810
+ *margin-left: 53.739770867430444%;
811
+ }
812
+ .row-fluid .offset6:first-child {
813
+ margin-left: 51.28205128205128%;
814
+ *margin-left: 51.175668303327875%;
815
+ }
816
+ .row-fluid .offset5 {
817
+ margin-left: 45.299145299145295%;
818
+ *margin-left: 45.1927623204219%;
819
+ }
820
+ .row-fluid .offset5:first-child {
821
+ margin-left: 42.73504273504273%;
822
+ *margin-left: 42.62865975631933%;
823
+ }
824
+ .row-fluid .offset4 {
825
+ margin-left: 36.75213675213675%;
826
+ *margin-left: 36.645753773413354%;
827
+ }
828
+ .row-fluid .offset4:first-child {
829
+ margin-left: 34.18803418803419%;
830
+ *margin-left: 34.081651209310785%;
831
+ }
832
+ .row-fluid .offset3 {
833
+ margin-left: 28.205128205128204%;
834
+ *margin-left: 28.0987452264048%;
835
+ }
836
+ .row-fluid .offset3:first-child {
837
+ margin-left: 25.641025641025642%;
838
+ *margin-left: 25.53464266230224%;
839
+ }
840
+ .row-fluid .offset2 {
841
+ margin-left: 19.65811965811966%;
842
+ *margin-left: 19.551736679396257%;
843
+ }
844
+ .row-fluid .offset2:first-child {
845
+ margin-left: 17.094017094017094%;
846
+ *margin-left: 16.98763411529369%;
847
+ }
848
+ .row-fluid .offset1 {
849
+ margin-left: 11.11111111111111%;
850
+ *margin-left: 11.004728132387708%;
851
+ }
852
+ .row-fluid .offset1:first-child {
853
+ margin-left: 8.547008547008547%;
854
+ *margin-left: 8.440625568285142%;
855
+ }
856
+
857
+
858
+ .highlighted {
859
+ -webkit-animation-duration: 1s;
860
+ animation-duration: 1s;
861
+ -webkit-animation-fill-mode: both;
862
+ animation-fill-mode: both;
863
+ -webkit-animation-timing-function: ease-out;
864
+ animation-timing-function: ease-out;
865
+ }
866
+
867
+ @-webkit-keyframes highlighted {
868
+ 0% {
869
+ opacity: 0;
870
+ background-color: #ffeaa0;
871
+ }
872
+ 100% {
873
+ opacity: 1;
874
+ background-color: #ffffff;
875
+ }
876
+ }
877
+ @keyframes highlighted {
878
+ 0% {
879
+ opacity: 0;
880
+ background-color: #ffeaa0;
881
+ }
882
+ 100% {
883
+ opacity: 1;
884
+ background-color: #ffffff;
885
+ }
886
+ }
887
+ @-webkit-keyframes highlightedBlocked {
888
+ 0% {
889
+ opacity: 0;
890
+ background-color: #ffeaa0;
891
+ }
892
+ 100% {
893
+ opacity: 1;
894
+ background-color: #fff6f6;
895
+ }
896
+ }
897
+ @keyframes highlightedBlocked {
898
+ 0% {
899
+ opacity: 0;
900
+ background-color: #ffeaa0;
901
+ }
902
+ 100% {
903
+ opacity: 1;
904
+ background-color: #fff6f6;
905
+ }
906
+ }
907
+ .highlighted {
908
+ -webkit-animation-name: highlighted;
909
+ animation-name: highlighted;
910
+ }
911
+ .highlighted.wfActionBlocked {
912
+ -webkit-animation-name: highlightedBlocked;
913
+ animation-name: highlightedBlocked;
914
+ }
915
+
916
+ #wf-lt-preset-filters {
917
+ min-width: 250px;
918
+ }
919
+ #wf-lt-advanced-filters > table {
920
+ width: 100%;
921
+ }
922
+ #wf-lt-advanced-filters > table > tr > td {
923
+ vertical-align: top;
924
+ }
925
+ .wf-lt-url {
926
+ white-space: nowrap;
927
+ }
928
+
929
+ #input-wafStatus,
930
+ #input-wafStatus option,
931
+ .select2-container--default {
932
+ font-size: 18px;
933
+ }
934
+ .wafStatus-enabled,
935
+ .wafStatus-learning-mode,
936
+ .wafStatus-disabled,
937
+ .wafStatus-enabled.select2-container--default .select2-selection--single .select2-selection__rendered,
938
+ .wafStatus-learning-mode.select2-container--default .select2-selection--single .select2-selection__rendered,
939
+ .wafStatus-disabled.select2-container--default .select2-selection--single .select2-selection__rendered {
940
+ color: #ffffff;
941
+ }
942
+ .wafStatus-learning-mode.select2-container--default .select2-selection--single .select2-selection__rendered {
943
+ /*color: #484d6a;*/
944
+ }
945
+ #waf-config-form .select2-container--default .select2-selection--single {
946
+ padding: 4px;
947
+ text-shadow: 0 0 3px #000000;
948
+ font-weight: bold;
949
+ border-radius: 3px;
950
+ }
951
+ #waf-config-form .select2-container .select2-selection--single {
952
+ height: auto;
953
+ }
954
+ /*.wafStatus-enabled,*/
955
+ .wafStatus-enabled.select2-container--default .select2-selection--single {
956
+ background-color: #61e157;
957
+ border-color: #43ad3f;
958
+ }
959
+ /*.wafStatus-learning-mode,*/
960
+ .wafStatus-learning-mode.select2-container--default .select2-selection--single {
961
+ background-color: #ffe674;
962
+ border-color: #e5ae35;
963
+ }
964
+ /*.wafStatus-disabled,*/
965
+ .wafStatus-disabled.select2-container--default .select2-selection--single {
966
+ background-color: #ff6d69;
967
+ border-color: #dd422c;
968
+ }
969
+ #waf-config-form .select2-container--default .select2-selection--single .select2-selection__arrow {
970
+ height: 100%;
971
+ top: 0;
972
+ }
973
+ .wafStatus-enabled.select2-container--default .select2-selection--single .select2-selection__arrow b,
974
+ .wafStatus-learning-mode.select2-container--default .select2-selection--single .select2-selection__arrow b,
975
+ .wafStatus-disabled.select2-container--default .select2-selection--single .select2-selection__arrow b {
976
+ border-color: #ffffff transparent transparent;
977
+ }
978
+ .wafStatus-enabled.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b,
979
+ .wafStatus-learning-mode.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b,
980
+ .wafStatus-disabled.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
981
+ border-color: transparent transparent #ffffff;
982
+ }
983
+ .wafStatus-description {
984
+ display:none;
985
+ max-width: 500px;
986
+ font-style: italic;
987
+ font-size: 14px;
988
+ line-height: 1.3;
989
+ }
990
+
991
+ pre.wf-pre {
992
+ margin:8px 0 20px;
993
+ }
994
+ pre.wf-pre {
995
+ padding: 12px;
996
+ background: #ffffff;
997
+ border: 1px solid #999999;
998
+ overflow: auto;
999
+ }
css/select2.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle;}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none;}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px;}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none;}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap;}.select2-container .select2-search--inline{float:left;}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none;}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051;}.select2-results{display:block;}.select2-results__options{list-style:none;margin:0;padding:0;}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none;}.select2-results__option[aria-selected]{cursor:pointer;}.select2-container--open .select2-dropdown{left:0;}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0;}.select2-search--dropdown{display:block;padding:4px;}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box;}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none;}.select2-search--dropdown.select2-search--hide{display:none;}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0);}.select2-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px;}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px;}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999;}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px;}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0;}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left;}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto;}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default;}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none;}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px;}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%;}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left;}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px;}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px;}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333;}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder{float:right;}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto;}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto;}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0;}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default;}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none;}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0;}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0;}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa;}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto;}.select2-container--default .select2-results__option[role=group]{padding:0;}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999;}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd;}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em;}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0;}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em;}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em;}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em;}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em;}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em;}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white;}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px;}.select2-container--classic .select2-selection--single{background-color:#f6f6f6;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #ffffff 50%, #eeeeee 100%);background-image:-o-linear-gradient(top, #ffffff 50%, #eeeeee 100%);background-image:linear-gradient(to bottom, #ffffff 50%, #eeeeee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb;}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px;}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px;}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999;}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);background-image:-o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);background-image:linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#cccccc', GradientType=0);}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0;}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left;}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto;}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb;}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none;}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px;}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #ffffff 0%, #eeeeee 50%);background-image:-o-linear-gradient(top, #ffffff 0%, #eeeeee 50%);background-image:linear-gradient(to bottom, #ffffff 0%, #eeeeee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eeeeee 50%, #ffffff 100%);background-image:-o-linear-gradient(top, #eeeeee 50%, #ffffff 100%);background-image:linear-gradient(to bottom, #eeeeee 50%, #ffffff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0;}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb;}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px;}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none;}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px;}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px;}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555;}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto;}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto;}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb;}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0;}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0;}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;}.select2-container--classic .select2-dropdown{background-color:white;border:1px solid transparent;}.select2-container--classic .select2-dropdown--above{border-bottom:none;}.select2-container--classic .select2-dropdown--below{border-top:none;}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto;}.select2-container--classic .select2-results__option[role=group]{padding:0;}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey;}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:white;}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px;}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb;}
js/admin.js CHANGED
@@ -40,6 +40,8 @@
40
  passwdAuditUpdateInt: false,
41
  _windowHasFocus: true,
42
  serverTimestampOffset: 0,
 
 
43
 
44
  init: function() {
45
  this.nonce = WordfenceAdminVars.firstNonce;
@@ -55,6 +57,28 @@
55
  self._windowHasFocus = true;
56
  }).focus();
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  $(document).focus();
59
 
60
  // (docs|support).wordfence.com GA links
@@ -87,6 +111,12 @@
87
  if (this.needTour()) {
88
  this.scanTourStart();
89
  }
 
 
 
 
 
 
90
  } else if (jQuery('#wordfenceMode_activity').length > 0) {
91
  this.mode = 'activity';
92
  this.setupSwitches('wfLiveTrafficOnOff', 'liveTrafficEnabled', function() {
@@ -126,7 +156,7 @@
126
  if (this.needTour()) {
127
  this.tour('wfContentBasicOptions', 'wfMarkerBasicOptions', 'top', 'left', "Learn about Live Traffic Options", function() {
128
  self.tour('wfContentLiveTrafficOptions', 'wfMarkerLiveTrafficOptions', 'bottom', 'left', "Learn about Scanning Options", function() {
129
- self.tour('wfContentScansToInclude', 'wfMarkerScansToInclude', 'bottom', 'left', "Learn about Firewall Rules", function() {
130
  self.tour('wfContentFirewallRules', 'wfMarkerFirewallRules', 'bottom', 'left', "Learn about Login Security", function() {
131
  self.tour('wfContentLoginSecurity', 'wfMarkerLoginSecurity', 'bottom', 'left', "Learn about Other Options", function() {
132
  self.tour('wfContentOtherOptions', 'wfMarkerOtherOptions', 'bottom', 'left', false, false);
@@ -240,7 +270,7 @@
240
  this.ajax('wordfence_sendTestEmail', {email: email}, function(res) {
241
  if (res.result) {
242
  self.colorbox('400px', "Test Email Sent", "Your test email was sent to the requested email address. The result we received from the WordPress wp_mail() function was: " +
243
- res.result + "<br /><br />A 'True' result means WordPress thinks the mail was sent without errors. A 'False' result means that WordPress encountered an error sending your mail. Note that it's possible to get a 'True' response with an error elsewhere in your mail system that may cause emails to not be delivered.");
244
  }
245
  });
246
  },
@@ -278,8 +308,8 @@
278
  var self = this;
279
  this.tour('wfWelcomeContent1', 'wfHeading', 'top', 'left', "Continue the Tour", function() {
280
  self.tour('wfWelcomeContent2', 'wfHeading', 'top', 'left', "Learn how to use Wordfence", function() {
281
- self.tour('wfWelcomeContent3', 'wfHeading', 'top', 'left', "Learn about Live Traffic", function() {
282
- self.tourRedir('WordfenceActivity');
283
  });
284
  });
285
  });
@@ -388,9 +418,34 @@
388
  this.lastALogCtime = res.items[res.items.length - 1].ctime;
389
  this.processActQueue(res.currentScanID);
390
  }
 
 
 
391
  }
392
  this.activityLogUpdatePending = false;
393
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  processActQueue: function(currentScanID) {
395
  if (this.activityQueue.length > 0) {
396
  this.addActItem(this.activityQueue.shift());
@@ -527,17 +582,23 @@
527
  var self = this;
528
  var alsoGet = '';
529
  var otherParams = '';
530
- if (this.mode == 'activity' && /^(?:404|hit|human|ruser|gCrawler|crawler|loginLogout)$/.test(this.activityMode)) {
 
 
 
 
 
 
 
 
531
  alsoGet = 'logList_' + this.activityMode;
532
  otherParams = this.newestActivityTime;
533
  } else if (this.mode == 'perfStats') {
534
  alsoGet = 'perfStats';
535
  otherParams = this.newestActivityTime;
536
  }
537
- this.ajax('wordfence_ticker', {
538
- alsoGet: alsoGet,
539
- otherParams: otherParams
540
- }, function(res) {
541
  self.handleTickerReturn(res);
542
  }, function() {
543
  self.tickerUpdatePending = false;
@@ -557,8 +618,19 @@
557
  }
558
  var haveEvents, newElem;
559
  this.serverTimestampOffset = (new Date().getTime() / 1000) - res.serverTime;
 
560
 
561
- if (this.mode == 'activity') {
 
 
 
 
 
 
 
 
 
 
562
  if (res.alsoGet != 'logList_' + this.activityMode) {
563
  return;
564
  } //user switched panels since ajax request started
@@ -624,6 +696,7 @@
624
  }
625
  },
626
  reverseLookupIPs: function() {
 
627
  var ips = [];
628
  jQuery('.wfReverseLookup').each(function(idx, elem) {
629
  var txt = jQuery(elem).text();
@@ -653,7 +726,7 @@
653
  for (var ip in res.ips) {
654
  if (txt == ip) {
655
  if (res.ips[ip]) {
656
- jQuery(elem).html('<strong>Hostname:</strong>&nbsp;' + res.ips[ip]);
657
  } else {
658
  jQuery(elem).html('');
659
  }
@@ -674,31 +747,20 @@
674
  });
675
  },
676
  startScan: function() {
 
 
 
 
677
  var scanReqAnimation = setInterval(function() {
678
- var str = jQuery('#wfStartScanButton1').prop('value');
679
- var ch = str.charAt(str.length - 1);
680
- if (ch == '/') {
681
- ch = '-';
682
- }
683
- else if (ch == '-') {
684
- ch = '\\';
685
- }
686
- else if (ch == '\\') {
687
- ch = '|';
688
- }
689
- else if (ch == '|') {
690
- ch = '/';
691
- }
692
- else {
693
- ch = '/';
694
- }
695
- jQuery('#wfStartScanButton1,#wfStartScanButton2').prop('value', "Requesting a New Scan " + ch);
696
  }, 100);
697
  setTimeout(function(res) {
698
  clearInterval(scanReqAnimation);
699
- jQuery('#wfStartScanButton1,#wfStartScanButton2').prop('value', "Start a Wordfence Scan");
700
  }, 3000);
701
  this.ajax('wordfence_scan', {}, function(res) {
 
702
  });
703
  },
704
  displayPWAuditJobs: function(res) {
@@ -833,6 +895,16 @@
833
  data += '&';
834
  }
835
  data += 'action=' + action + '&nonce=' + this.nonce;
 
 
 
 
 
 
 
 
 
 
836
  } else if (typeof(data) == 'object') {
837
  data['action'] = action;
838
  data['nonce'] = this.nonce;
@@ -885,8 +957,19 @@
885
  this.colorboxOpen(elem[0], elem[1], elem[2]);
886
  },
887
  colorboxOpen: function(width, heading, body) {
 
888
  this.colorboxIsOpen = true;
889
- jQuery.colorbox({width: width, html: "<h3>" + heading + "</h3><p>" + body + "</p>"});
 
 
 
 
 
 
 
 
 
 
890
  },
891
  scanRunningMsg: function() {
892
  this.colorbox('400px', "A scan is running", "A scan is currently in progress. Please wait until it finishes before starting another scan.");
@@ -979,6 +1062,88 @@
979
  });
980
  }
981
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
982
  restoreFile: function(issueID) {
983
  var self = this;
984
  this.ajax('wordfence_restoreFile', {
@@ -999,6 +1164,46 @@
999
  });
1000
  }
1001
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1002
  deleteIssue: function(id) {
1003
  var self = this;
1004
  this.ajax('wordfence_deleteIssue', {id: id}, function(res) {
@@ -1475,7 +1680,7 @@
1475
  self.loadBlockRanges();
1476
  });
1477
  },
1478
- blockIP: function(IP, reason) {
1479
  var self = this;
1480
  this.ajax('wordfence_blockIP', {
1481
  IP: IP,
@@ -1485,6 +1690,7 @@
1485
  return;
1486
  } else {
1487
  self.reloadActivities();
 
1488
  }
1489
  });
1490
  },
@@ -1510,12 +1716,13 @@
1510
  self.staticTabChanged();
1511
  });
1512
  },
1513
- unblockIP: function(IP) {
1514
  var self = this;
1515
  this.ajax('wordfence_unblockIP', {
1516
  IP: IP
1517
  }, function(res) {
1518
  self.reloadActivities();
 
1519
  });
1520
  },
1521
  unblockNetwork: function(id) {
@@ -1646,6 +1853,22 @@
1646
  }
1647
  });
1648
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1649
  changeSecurityLevel: function() {
1650
  var level = jQuery('#securityLevel').val();
1651
  for (var k in WFSLevels[level].checkboxes) {
@@ -1668,8 +1891,8 @@
1668
  return;
1669
  }
1670
  this.colorbox('450px', "Please confirm", body +
1671
- '<br /><br /><center><input type="button" name="but1" value="Cancel" onclick="jQuery.colorbox.close();" />&nbsp;&nbsp;&nbsp;' +
1672
- '<input type="button" name="but2" value="Yes I\'m sure" onclick="jQuery.colorbox.close(); WFAD.confirmClearAllBlocked(\'' + op + '\');"><br />');
1673
  },
1674
  confirmClearAllBlocked: function(op) {
1675
  var self = this;
@@ -1735,7 +1958,7 @@
1735
  this.countryCodesToSave = codesArr.join(',');
1736
  if (ownCountryBlocked) {
1737
  this.colorbox('400px', "Please confirm blocking yourself", "You are about to block your own country. This could lead to you being locked out. Please make sure that your user profile on this machine has a current and valid email address and make sure you know what it is. That way if you are locked out, you can send yourself an unlock email. If you're sure you want to block your own country, click 'Confirm' below, otherwise click 'Cancel'.<br />" +
1738
- '<input type="button" name="but1" value="Confirm" onclick="jQuery.colorbox.close(); WFAD.confirmSaveCountryBlocking();" />&nbsp;<input type="button" name="but1" value="Cancel" onclick="jQuery.colorbox.close();" />');
1739
  } else {
1740
  this.confirmSaveCountryBlocking();
1741
  }
@@ -1908,7 +2131,7 @@
1908
  if (res.users && res.users.length > 0) {
1909
  for (var i = 0; i < res.users.length; i++) {
1910
  jQuery('<div id="twoFacCont_' + res.users[i].userID + '">' +
1911
- jQuery('#wfTwoFacUserTmpl').tmpl(res.users[i]).html() + '</div>').appendTo(jQuery('#wfTwoFacUsers'));
1912
  }
1913
  }
1914
  });
@@ -2083,6 +2306,43 @@
2083
  }
2084
  });
2085
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2086
  windowHasFocus: function() {
2087
  if (typeof document.hasFocus === 'function') {
2088
  return document.hasFocus();
@@ -2128,17 +2388,173 @@
2128
  if (!timestamp) {
2129
  timestamp = el.attr('data-timestamp');
2130
  }
2131
- var serverTime = (new Date().getTime() / 1000) - self.serverTimestampOffset;
2132
  var format = el.data('wfformat');
2133
  if (!format) {
2134
  format = el.attr('data-format');
2135
  }
2136
  el.html(self.showTimestamp(timestamp, serverTime, format));
2137
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2138
  }
2139
  };
2140
- window['WFAD'] = window['wordfenceAdmin'];
2141
 
 
2142
  setInterval(function() {
2143
  WFAD.updateTimeAgo();
2144
  }, 1000);
40
  passwdAuditUpdateInt: false,
41
  _windowHasFocus: true,
42
  serverTimestampOffset: 0,
43
+ serverMicrotime: 0,
44
+ wfLiveTraffic: null,
45
 
46
  init: function() {
47
  this.nonce = WordfenceAdminVars.firstNonce;
57
  self._windowHasFocus = true;
58
  }).focus();
59
 
60
+ $('.do-show').click(function() {
61
+ var $this = $(this);
62
+ $this.hide();
63
+ $($this.data('selector')).show();
64
+ return false;
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 {
72
+ self.colorbox('400px', "Error", "There was an error while sending the email.");
73
+ }
74
+ });
75
+ });
76
+
77
+ $('#sendByEmail').click(function() {
78
+ $('#sendByEmailForm').removeClass('hidden');
79
+ $(this).hide();
80
+ });
81
+
82
  $(document).focus();
83
 
84
  // (docs|support).wordfence.com GA links
111
  if (this.needTour()) {
112
  this.scanTourStart();
113
  }
114
+ } else if (jQuery('#wordfenceMode_waf').length > 0) {
115
+ if (this.needTour()) {
116
+ this.tour('wfWAFTour', 'wfHeading', 'top', 'left', "Learn about Live Traffic", function() {
117
+ self.tourRedir('WordfenceActivity');
118
+ });
119
+ }
120
  } else if (jQuery('#wordfenceMode_activity').length > 0) {
121
  this.mode = 'activity';
122
  this.setupSwitches('wfLiveTrafficOnOff', 'liveTrafficEnabled', function() {
156
  if (this.needTour()) {
157
  this.tour('wfContentBasicOptions', 'wfMarkerBasicOptions', 'top', 'left', "Learn about Live Traffic Options", function() {
158
  self.tour('wfContentLiveTrafficOptions', 'wfMarkerLiveTrafficOptions', 'bottom', 'left', "Learn about Scanning Options", function() {
159
+ self.tour('wfContentScansToInclude', 'wfMarkerScansToInclude', 'bottom', 'left', "Learn about Rate Limiting Rules", function() {
160
  self.tour('wfContentFirewallRules', 'wfMarkerFirewallRules', 'bottom', 'left', "Learn about Login Security", function() {
161
  self.tour('wfContentLoginSecurity', 'wfMarkerLoginSecurity', 'bottom', 'left', "Learn about Other Options", function() {
162
  self.tour('wfContentOtherOptions', 'wfMarkerOtherOptions', 'bottom', 'left', false, false);
270
  this.ajax('wordfence_sendTestEmail', {email: email}, function(res) {
271
  if (res.result) {
272
  self.colorbox('400px', "Test Email Sent", "Your test email was sent to the requested email address. The result we received from the WordPress wp_mail() function was: " +
273
+ res.result + "<br /><br />A 'True' result means WordPress thinks the mail was sent without errors. A 'False' result means that WordPress encountered an error sending your mail. Note that it's possible to get a 'True' response with an error elsewhere in your mail system that may cause emails to not be delivered.");
274
  }
275
  });
276
  },
308
  var self = this;
309
  this.tour('wfWelcomeContent1', 'wfHeading', 'top', 'left', "Continue the Tour", function() {
310
  self.tour('wfWelcomeContent2', 'wfHeading', 'top', 'left', "Learn how to use Wordfence", function() {
311
+ self.tour('wfWelcomeContent3', 'wfHeading', 'top', 'left', "Learn about the Firewall", function() {
312
+ self.tourRedir('WordfenceWAF');
313
  });
314
  });
315
  });
418
  this.lastALogCtime = res.items[res.items.length - 1].ctime;
419
  this.processActQueue(res.currentScanID);
420
  }
421
+ if (res.signatureUpdateTime) {
422
+ this.updateSignaturesTimestamp(res.signatureUpdateTime);
423
+ }
424
  }
425
  this.activityLogUpdatePending = false;
426
  },
427
+
428
+ updateSignaturesTimestamp: function(signatureUpdateTime) {
429
+ var date = new Date(signatureUpdateTime * 1000);
430
+
431
+ var dateString = date.toString();
432
+ if (date.toLocaleString) {
433
+ dateString = date.toLocaleString();
434
+ }
435
+
436
+ var sigTimestampEl = $('#wf-scan-sigs-last-update');
437
+ var newText = 'Last Updated: ' + dateString;
438
+ if (sigTimestampEl.text() !== newText) {
439
+ sigTimestampEl.text(newText)
440
+ .css({
441
+ 'opacity': 0
442
+ })
443
+ .animate({
444
+ 'opacity': 1
445
+ }, 500);
446
+ }
447
+ },
448
+
449
  processActQueue: function(currentScanID) {
450
  if (this.activityQueue.length > 0) {
451
  this.addActItem(this.activityQueue.shift());
582
  var self = this;
583
  var alsoGet = '';
584
  var otherParams = '';
585
+ var data = '';
586
+ if (this.mode == 'liveTraffic') {
587
+ alsoGet = 'liveTraffic';
588
+ otherParams = this.newestActivityTime;
589
+ data += this.wfLiveTraffic.getCurrentQueryString({
590
+ since: this.newestActivityTime
591
+ });
592
+
593
+ } else if (this.mode == 'activity' && /^(?:404|hit|human|ruser|gCrawler|crawler|loginLogout)$/.test(this.activityMode)) {
594
  alsoGet = 'logList_' + this.activityMode;
595
  otherParams = this.newestActivityTime;
596
  } else if (this.mode == 'perfStats') {
597
  alsoGet = 'perfStats';
598
  otherParams = this.newestActivityTime;
599
  }
600
+ data += '&alsoGet=' + encodeURIComponent(alsoGet) + '&otherParams=' + encodeURIComponent(otherParams);
601
+ this.ajax('wordfence_ticker', data, function(res) {
 
 
602
  self.handleTickerReturn(res);
603
  }, function() {
604
  self.tickerUpdatePending = false;
618
  }
619
  var haveEvents, newElem;
620
  this.serverTimestampOffset = (new Date().getTime() / 1000) - res.serverTime;
621
+ this.serverMicrotime = res.serverMicrotime;
622
 
623
+ if (this.mode == 'liveTraffic') {
624
+ if (res.events.length > 0) {
625
+ this.newestActivityTime = res.events[0]['ctime'];
626
+ }
627
+ if (typeof WFAD.wfLiveTraffic !== undefined) {
628
+ WFAD.wfLiveTraffic.prependListings(res.events, res);
629
+ this.reverseLookupIPs();
630
+ this.updateTimeAgo();
631
+ }
632
+
633
+ } else if (this.mode == 'activity') { // This mode is deprecated as of 6.1.0
634
  if (res.alsoGet != 'logList_' + this.activityMode) {
635
  return;
636
  } //user switched panels since ajax request started
696
  }
697
  },
698
  reverseLookupIPs: function() {
699
+ var self = this;
700
  var ips = [];
701
  jQuery('.wfReverseLookup').each(function(idx, elem) {
702
  var txt = jQuery(elem).text();
726
  for (var ip in res.ips) {
727
  if (txt == ip) {
728
  if (res.ips[ip]) {
729
+ jQuery(elem).html('<strong>Hostname:</strong>&nbsp;' + self.htmlEscape(res.ips[ip]));
730
  } else {
731
  jQuery(elem).html('');
732
  }
747
  });
748
  },
749
  startScan: function() {
750
+ var spinnerValues = [
751
+ '|', '/', '-', '\\'
752
+ ];
753
+ var count = 0;
754
  var scanReqAnimation = setInterval(function() {
755
+ var ch = spinnerValues[count++ % spinnerValues.length];
756
+ jQuery('#wfStartScanButton1,#wfStartScanButton2').html("Requesting a New Scan <span class='wf-spinner'>" + ch + "</span>");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
  }, 100);
758
  setTimeout(function(res) {
759
  clearInterval(scanReqAnimation);
760
+ jQuery('#wfStartScanButton1,#wfStartScanButton2').text("Start a Wordfence Scan");
761
  }, 3000);
762
  this.ajax('wordfence_scan', {}, function(res) {
763
+
764
  });
765
  },
766
  displayPWAuditJobs: function(res) {
895
  data += '&';
896
  }
897
  data += 'action=' + action + '&nonce=' + this.nonce;
898
+ } else if (typeof(data) == 'object' && data instanceof Array) {
899
+ // jQuery serialized form data
900
+ data.push({
901
+ name: 'action',
902
+ value: action
903
+ });
904
+ data.push({
905
+ name: 'nonce',
906
+ value: this.nonce
907
+ });
908
  } else if (typeof(data) == 'object') {
909
  data['action'] = action;
910
  data['nonce'] = this.nonce;
957
  this.colorboxOpen(elem[0], elem[1], elem[2]);
958
  },
959
  colorboxOpen: function(width, heading, body) {
960
+ var self = this;
961
  this.colorboxIsOpen = true;
962
+ jQuery.colorbox({
963
+ width: width,
964
+ html: "<h3>" + heading + "</h3><p>" + body + "</p>",
965
+ onClosed: function() {
966
+ self.colorboxClose();
967
+ }
968
+ });
969
+ },
970
+ colorboxClose: function() {
971
+ this.colorboxIsOpen = false;
972
+ jQuery.colorbox.close();
973
  },
974
  scanRunningMsg: function() {
975
  this.colorbox('400px', "A scan is running", "A scan is currently in progress. Please wait until it finishes before starting another scan.");
1062
  });
1063
  }
1064
  },
1065
+ fixFPD: function(issueID) {
1066
+ var self = this;
1067
+ var title = "Full Path Disclosure";
1068
+ issueID = parseInt(issueID);
1069
+
1070
+ this.ajax('wordfence_checkFalconHtaccess', {}, function(res) {
1071
+ if (res.ok) {
1072
+ self.colorbox("400px", title, 'We are about to change your <em>.htaccess</em> file. Please make a backup of this file proceeding'
1073
+ + '<br/>'
1074
+ + '<a href="' + WordfenceAdminVars.ajaxURL + '?action=wordfence_downloadHtaccess&nonce=' + self.nonce + '" onclick="jQuery(\'#wfFPDNextBut\').prop(\'disabled\', false); return true;">Click here to download a backup copy of your .htaccess file now</a><br /><br /><input type="button" name="but1" id="wfFPDNextBut" value="Click to fix .htaccess" disabled="disabled" onclick="WFAD.fixFPD_WriteHtAccess(' + issueID + ');" />');
1075
+ } else if (res.nginx) {
1076
+ self.colorbox("400px", title, 'You are using an Nginx web server and using a FastCGI processor like PHP5-FPM. You will need to manually modify your php.ini to disable <em>display_error</em>');
1077
+ } else if (res.err) {
1078
+ self.colorbox('400px', "We encountered a problem", "We can't modify your .htaccess file for you because: " + res.err);
1079
+ }
1080
+ });
1081
+ },
1082
+ fixFPD_WriteHtAccess: function(issueID) {
1083
+ var self = this;
1084
+ self.colorboxClose();
1085
+ this.ajax('wordfence_fixFPD', {
1086
+ issueID: issueID
1087
+ }, function(res) {
1088
+ if (res.ok) {
1089
+ self.loadIssues(function() {
1090
+ self.colorbox("400px", "File restored OK", "The Full Path disclosure issue has been fixed");
1091
+ });
1092
+ } else {
1093
+ self.loadIssues(function() {
1094
+ self.colorbox('400px', 'An error occurred', res.cerrorMsg);
1095
+ });
1096
+ }
1097
+ });
1098
+ },
1099
+
1100
+ _handleHtAccess: function(issueID, callback, title, nginx) {
1101
+ var self = this;
1102
+ return function(res) {
1103
+ if (res.ok) {
1104
+ self.colorbox("400px", title, 'We are about to change your <em>.htaccess</em> file. Please make a backup of this file proceeding'
1105
+ + '<br/>'
1106
+ + '<a id="dlButton" href="' + WordfenceAdminVars.ajaxURL + '?action=wordfence_downloadHtaccess&nonce=' + self.nonce + '">Click here to download a backup copy of your .htaccess file now</a>'
1107
+ + '<br /><br /><input type="button" name="but1" id="wfFPDNextBut" value="Click to fix .htaccess" disabled="disabled" />'
1108
+ );
1109
+ jQuery('#dlButton').click('click', function() {
1110
+ jQuery('#wfFPDNextBut').prop('disabled', false);
1111
+ });
1112
+ jQuery('#wfFPDNextBut').one('click', function() {
1113
+ self[callback](issueID);
1114
+ });
1115
+ } else if (res.nginx) {
1116
+ self.colorbox("400px", title, 'You are using an Nginx web server and using a FastCGI processor like PHP5-FPM. ' + nginx);
1117
+ } else if (res.err) {
1118
+ self.colorbox('400px', "We encountered a problem", "We can't modify your .htaccess file for you because: " + res.err);
1119
+ }
1120
+ };
1121
+ },
1122
+ _hideFile: function(issueID) {
1123
+ var self = this;
1124
+ var title = 'Modifying .htaccess';
1125
+ this.ajax('wordfence_hideFileHtaccess', {
1126
+ issueID: issueID
1127
+ }, function(res) {
1128
+ jQuery.colorbox.close();
1129
+ self.loadIssues(function() {
1130
+ if (res.ok) {
1131
+ self.colorbox("400px", title, 'Your .htaccess file has been updated successfully.');
1132
+ } else {
1133
+ self.colorbox("400px", title, 'We encountered a problem while trying to update your .htaccess file.');
1134
+ }
1135
+ });
1136
+ });
1137
+ },
1138
+ hideFile: function(issueID) {
1139
+ var self = this;
1140
+ var title = "Backup your .htaccess file";
1141
+ var nginx = "You will need to manually delete those files";
1142
+ issueID = parseInt(issueID, 10);
1143
+
1144
+ this.ajax('wordfence_checkFalconHtaccess', {}, this._handleHtAccess(issueID, '_hideFile', title, nginx));
1145
+ },
1146
+
1147
  restoreFile: function(issueID) {
1148
  var self = this;
1149
  this.ajax('wordfence_restoreFile', {
1164
  });
1165
  }
1166
  },
1167
+
1168
+ disableDirectoryListing: function(issueID) {
1169
+ var self = this;
1170
+ var title = "Disable Directory Listing";
1171
+ issueID = parseInt(issueID);
1172
+
1173
+ this.ajax('wordfence_checkFalconHtaccess', {}, function(res) {
1174
+ if (res.ok) {
1175
+ self.colorbox("400px", title, 'We are about to change your <em>.htaccess</em> file. Please make a backup of this file proceeding'
1176
+ + '<br/>'
1177
+ + '<a href="' + WordfenceAdminVars.ajaxURL + '?action=wordfence_downloadHtaccess&nonce=' + self.nonce + '" onclick="jQuery(\'#wf-htaccess-confirm\').prop(\'disabled\', false); return true;">Click here to download a backup copy of your .htaccess file now</a>' +
1178
+ '<br /><br />' +
1179
+ '<button class="button" type="button" id="wf-htaccess-confirm" disabled="disabled" onclick="WFAD.confirmDisableDirectoryListing(' + issueID + ');">Add code to .htaccess</button>');
1180
+ } else if (res.nginx) {
1181
+ self.colorbox('400px', "You are using Nginx as your web server. " +
1182
+ "You'll need to disable autoindexing in your nginx.conf. " +
1183
+ "See the <a target='_blank' href='http://nginx.org/en/docs/http/ngx_http_autoindex_module.html'>Nginx docs for more info</a> on how to do this.");
1184
+ } else if (res.err) {
1185
+ self.colorbox('400px', "We encountered a problem", "We can't modify your .htaccess file for you because: " + res.err);
1186
+ }
1187
+ });
1188
+ },
1189
+ confirmDisableDirectoryListing: function(issueID) {
1190
+ var self = this;
1191
+ this.colorboxClose();
1192
+ this.ajax('wordfence_disableDirectoryListing', {
1193
+ issueID: issueID
1194
+ }, function(res) {
1195
+ if (res.ok) {
1196
+ self.loadIssues(function() {
1197
+ self.colorbox("400px", "Directory Listing Disabled", "Directory listing has been disabled on your server.");
1198
+ });
1199
+ } else {
1200
+ //self.loadIssues(function() {
1201
+ // self.colorbox('400px', 'An error occurred', res.errorMsg);
1202
+ //});
1203
+ }
1204
+ });
1205
+ },
1206
+
1207
  deleteIssue: function(id) {
1208
  var self = this;
1209
  this.ajax('wordfence_deleteIssue', {id: id}, function(res) {
1680
  self.loadBlockRanges();
1681
  });
1682
  },
1683
+ blockIP: function(IP, reason, callback) {
1684
  var self = this;
1685
  this.ajax('wordfence_blockIP', {
1686
  IP: IP,
1690
  return;
1691
  } else {
1692
  self.reloadActivities();
1693
+ typeof callback === 'function' && callback();
1694
  }
1695
  });
1696
  },
1716
  self.staticTabChanged();
1717
  });
1718
  },
1719
+ unblockIP: function(IP, callback) {
1720
  var self = this;
1721
  this.ajax('wordfence_unblockIP', {
1722
  IP: IP
1723
  }, function(res) {
1724
  self.reloadActivities();
1725
+ typeof callback === 'function' && callback();
1726
  });
1727
  },
1728
  unblockNetwork: function(id) {
1853
  }
1854
  });
1855
  },
1856
+ saveDebuggingConfig: function() {
1857
+ var qstr = jQuery('#wfDebuggingConfigForm').serialize();
1858
+ var self = this;
1859
+ jQuery('.wfSavedMsg').hide();
1860
+ jQuery('.wfAjax24').show();
1861
+ this.ajax('wordfence_saveDebuggingConfig', qstr, function(res) {
1862
+ jQuery('.wfAjax24').hide();
1863
+ if (res.ok) {
1864
+ self.pulse('.wfSavedMsg');
1865
+ } else if (res.errorMsg) {
1866
+ return;
1867
+ } else {
1868
+ self.colorbox('400px', 'An error occurred', 'We encountered an error trying to save your changes.');
1869
+ }
1870
+ });
1871
+ },
1872
  changeSecurityLevel: function() {
1873
  var level = jQuery('#securityLevel').val();
1874
  for (var k in WFSLevels[level].checkboxes) {
1891
  return;
1892
  }
1893
  this.colorbox('450px', "Please confirm", body +
1894
+ '<br /><br /><center><input type="button" name="but1" value="Cancel" onclick="jQuery.colorbox.close();" />&nbsp;&nbsp;&nbsp;' +
1895
+ '<input type="button" name="but2" value="Yes I\'m sure" onclick="jQuery.colorbox.close(); WFAD.confirmClearAllBlocked(\'' + op + '\');"><br />');
1896
  },
1897
  confirmClearAllBlocked: function(op) {
1898
  var self = this;
1958
  this.countryCodesToSave = codesArr.join(',');
1959
  if (ownCountryBlocked) {
1960
  this.colorbox('400px', "Please confirm blocking yourself", "You are about to block your own country. This could lead to you being locked out. Please make sure that your user profile on this machine has a current and valid email address and make sure you know what it is. That way if you are locked out, you can send yourself an unlock email. If you're sure you want to block your own country, click 'Confirm' below, otherwise click 'Cancel'.<br />" +
1961
+ '<input type="button" name="but1" value="Confirm" onclick="jQuery.colorbox.close(); WFAD.confirmSaveCountryBlocking();" />&nbsp;<input type="button" name="but1" value="Cancel" onclick="jQuery.colorbox.close();" />');
1962
  } else {
1963
  this.confirmSaveCountryBlocking();
1964
  }
2131
  if (res.users && res.users.length > 0) {
2132
  for (var i = 0; i < res.users.length; i++) {
2133
  jQuery('<div id="twoFacCont_' + res.users[i].userID + '">' +
2134
+ jQuery('#wfTwoFacUserTmpl').tmpl(res.users[i]).html() + '</div>').appendTo(jQuery('#wfTwoFacUsers'));
2135
  }
2136
  }
2137
  });
2306
  }
2307
  });
2308
  },
2309
+
2310
+ deleteAdminUser: function(issueID) {
2311
+ var self = this;
2312
+ this.ajax('wordfence_deleteAdminUser', {
2313
+ issueID: issueID
2314
+ }, function(res) {
2315
+ if (res.ok) {
2316
+ self.loadIssues(function() {
2317
+ self.colorbox('400px', "Successfully deleted admin", "The admin user " +
2318
+ self.htmlEscape(res.user_login) + " was successfully deleted.");
2319
+ });
2320
+ } else if (res.errorMsg) {
2321
+ self.loadIssues(function() {
2322
+ self.colorbox('400px', 'An error occurred', res.errorMsg);
2323
+ });
2324
+ }
2325
+ });
2326
+ },
2327
+
2328
+ revokeAdminUser: function(issueID) {
2329
+ var self = this;
2330
+ this.ajax('wordfence_revokeAdminUser', {
2331
+ issueID: issueID
2332
+ }, function(res) {
2333
+ if (res.ok) {
2334
+ self.loadIssues(function() {
2335
+ self.colorbox('400px', "Successfully revoked admin", "All capabilties of admin user " +
2336
+ self.htmlEscape(res.user_login) + " were successfully revoked.");
2337
+ });
2338
+ } else if (res.errorMsg) {
2339
+ self.loadIssues(function() {
2340
+ self.colorbox('400px', 'An error occurred', res.errorMsg);
2341
+ });
2342
+ }
2343
+ });
2344
+ },
2345
+
2346
  windowHasFocus: function() {
2347
  if (typeof document.hasFocus === 'function') {
2348
  return document.hasFocus();
2388
  if (!timestamp) {
2389
  timestamp = el.attr('data-timestamp');
2390
  }
2391
+ var serverTime = self.serverMicrotime;
2392
  var format = el.data('wfformat');
2393
  if (!format) {
2394
  format = el.attr('data-format');
2395
  }
2396
  el.html(self.showTimestamp(timestamp, serverTime, format));
2397
  });
2398
+ },
2399
+
2400
+ wafData: {
2401
+ whitelistedURLParams: []
2402
+ },
2403
+
2404
+ wafConfigSave: function(action, data, onSuccess) {
2405
+ var self = this;
2406
+ if (typeof(data) == 'string') {
2407
+ if (data.length > 0) {
2408
+ data += '&';
2409
+ }
2410
+ data += 'wafConfigAction=' + action;
2411
+ } else if (typeof(data) == 'object' && data instanceof Array) {
2412
+ // jQuery serialized form data
2413
+ data.push({
2414
+ name: 'wafConfigAction',
2415
+ value: action
2416
+ });
2417
+ } else if (typeof(data) == 'object') {
2418
+ data['wafConfigAction'] = action;
2419
+ }
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') {
2428
+ return onSuccess.apply(this, arguments);
2429
+ }
2430
+ } else {
2431
+ self.colorbox('400px', 'Error saving Firewall configuration', 'There was an error saving the ' +
2432
+ 'Web Application Firewall configuration settings.');
2433
+ }
2434
+ });
2435
+ },
2436
+
2437
+ wafWhitelistURLAdd: function(url, param, onSuccess) {
2438
+ this.wafData.whitelistedURLParams.push({
2439
+ 'path': url,
2440
+ 'paramKey': param,
2441
+ 'ruleID': ['all']
2442
+ });
2443
+ var index = this.wafData.whitelistedURLParams.length;
2444
+ var inputPath = $('<input name="whitelistedURLParams[' + index + '][path]" type="hidden" />');
2445
+ var inputParam = $('<input name="whitelistedURLParams[' + index + '][paramKey]" type="hidden" />');
2446
+ var inputEnabled = $('<input name="whitelistedURLParams[' + index + '][enabled]" type="hidden" value="1" />');
2447
+ inputPath.val(url);
2448
+ inputParam.val(param);
2449
+ $('#waf-config-form').append(inputPath)
2450
+ .append(inputParam)
2451
+ .append(inputEnabled);
2452
+ this.wafConfigSave(onSuccess);
2453
+ inputPath.remove();
2454
+ inputParam.remove();
2455
+ inputEnabled.remove();
2456
+ },
2457
+
2458
+ wafConfigPageRender: function() {
2459
+ var whitelistedIPsEl = $('#waf-whitelisted-urls-tmpl').tmpl(this.wafData);
2460
+ $('#waf-whitelisted-urls-wrapper').html(whitelistedIPsEl);
2461
+
2462
+ var rulesEl = $('#waf-rules-tmpl').tmpl(this.wafData);
2463
+ $('#waf-rules-wrapper').html(rulesEl);
2464
+
2465
+ if (this.wafData['rulesLastUpdated']) {
2466
+ var date = new Date(this.wafData['rulesLastUpdated'] * 1000);
2467
+ this.renderWAFRulesLastUpdated(date);
2468
+ }
2469
+ },
2470
+
2471
+ renderWAFRulesLastUpdated: function(date) {
2472
+ var dateString = date.toString();
2473
+ if (date.toLocaleString) {
2474
+ dateString = date.toLocaleString();
2475
+ }
2476
+ $('#waf-rules-last-updated').text('Last Updated: ' + dateString)
2477
+ .css({
2478
+ 'opacity': 0
2479
+ })
2480
+ .animate({
2481
+ 'opacity': 1
2482
+ }, 500);
2483
+ },
2484
+
2485
+ wafUpdateRules: function(onSuccess) {
2486
+ var self = this;
2487
+ this.ajax('wordfence_updateWAFRules', {}, function(res) {
2488
+ self.wafData = res;
2489
+ self.wafConfigPageRender();
2490
+ if (!self.wafData['isPaid']) {
2491
+ self.colorbox('400px', 'Rules Updated', 'Your rules have been updated successfully. You are ' +
2492
+ 'currently using the the free version of Wordfence. ' +
2493
+ 'Upgrade to Wordfence premium to have your rules updated automatically as new threats emerge. ' +
2494
+ '<a href="https://www.wordfence.com/wafUpdateRules1/wordfence-signup/">Click here to purchase a premium API key</a>. ' +
2495
+ '<em>Note: Your rules will still update every 30 days as a free user.</em>');
2496
+ } else {
2497
+ self.colorbox('400px', 'Rules Updated', 'Your rules have been updated successfully.');
2498
+ }
2499
+ if (typeof onSuccess === 'function') {
2500
+ return onSuccess.apply(this, arguments);
2501
+ }
2502
+ });
2503
+ },
2504
+
2505
+ dateFormat: function(date) {
2506
+ if (date instanceof Date) {
2507
+ if (date.toLocaleString) {
2508
+ return date.toLocaleString();
2509
+ }
2510
+ return date.toString();
2511
+ }
2512
+ return date;
2513
+ },
2514
+
2515
+ wafAddBootstrap: function() {
2516
+ var self = this;
2517
+ this.ajax('wordfence_wafAddBootstrap', {}, function(res) {
2518
+ self.colorbox('400px', 'File Created', "");
2519
+ });
2520
+ },
2521
+
2522
+ wafConfigureAutoPrepend: function() {
2523
+ var self = this;
2524
+ self.colorbox("400px", 'Backup .htaccess before continuing', 'We are about to change your <em>.htaccess</em> file. Please make a backup of this file proceeding'
2525
+ + '<br/>'
2526
+ + '<a href="' + WordfenceAdminVars.ajaxURL + '?action=wordfence_downloadHtaccess&nonce=' + self.nonce + '" onclick="jQuery(\'#wf-htaccess-confirm\').prop(\'disabled\', false); return true;">Click here to download a backup copy of your .htaccess file now</a>' +
2527
+ '<br /><br />' +
2528
+ '<button class="button" type="button" id="wf-htaccess-confirm" disabled="disabled" onclick="WFAD.confirmWAFConfigureAutoPrepend();">Add code to .htaccess</button>');
2529
+ },
2530
+
2531
+ confirmWAFConfigureAutoPrepend: function() {
2532
+ var self = this;
2533
+ this.ajax('wordfence_wafConfigureAutoPrepend', {}, function(res) {
2534
+ self.colorbox('400px', '.htaccess Updated', "Your .htaccess has been updated successfully. Please " +
2535
+ "verify your site is functioning normally.");
2536
+ });
2537
+ },
2538
+
2539
+ base64_decode: function(s) {
2540
+ var e = {}, i, b = 0, c, x, l = 0, a, r = '', w = String.fromCharCode, L = s.length;
2541
+ var A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2542
+ for (i = 0; i < 64; i++) {
2543
+ e[A.charAt(i)] = i;
2544
+ }
2545
+ for (x = 0; x < L; x++) {
2546
+ c = e[s.charAt(x)];
2547
+ b = (b << 6) + c;
2548
+ l += 6;
2549
+ while (l >= 8) {
2550
+ ((a = (b >>> (l -= 8)) & 0xff) || (x < (L - 2))) && (r += w(a));
2551
+ }
2552
+ }
2553
+ return r;
2554
  }
2555
  };
 
2556
 
2557
+ window['WFAD'] = window['wordfenceAdmin'];
2558
  setInterval(function() {
2559
  WFAD.updateTimeAgo();
2560
  }, 1000);
js/admin.liveTraffic.js ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+
3
+ var LISTING_LIMIT = 50;
4
+
5
+ var LiveTrafficViewModel = function(listings, filters) {
6
+ var self = this;
7
+ var listingIDTable = {};
8
+ self.listings = ko.observableArray(listings);
9
+ self.listings.subscribe(function(items) {
10
+ listingIDTable = {};
11
+ for (var i = 0; i < items.length; i++) {
12
+ listingIDTable[items[i].id()] = 1;
13
+ }
14
+ //console.log(items);
15
+ });
16
+ self.hasListing = function(id) {
17
+ return id in listingIDTable;
18
+ };
19
+ self.filters = ko.observableArray(filters);
20
+
21
+ var urlGroupBy = new GroupByModel('url', 'URL');
22
+ var groupBys = [
23
+ new GroupByModel('type', 'Type'),
24
+ new GroupByModel('user_login', 'Username'),
25
+ new GroupByModel('statusCode', 'HTTP Response Code'),
26
+ new GroupByModel('action', 'Firewall Response', 'enum', ['ok', 'throttled', 'lockedOut', 'blocked', 'blocked:waf']),
27
+ new GroupByModel('ip', 'IP'),
28
+ urlGroupBy,
29
+ new GroupByModel('host', 'Host')
30
+ ];
31
+
32
+ self.presetFiltersOptions = ko.observableArray([
33
+ new PresetFilterModel('All Hits', "all", []),
34
+ new PresetFilterModel('Humans', "humans", [new ListingsFilterModel(self, 'type', 'human')]),
35
+ new PresetFilterModel('Registered Users', "users", [new ListingsFilterModel(self, 'userID', '0', '!=')]),
36
+ new PresetFilterModel('Crawlers', "crawlers", [new ListingsFilterModel(self, 'type', 'bot')]),
37
+ new PresetFilterModel('Google Crawlers', "google", [new ListingsFilterModel(self, 'isGoogle', '1')]),
38
+ new PresetFilterModel('Pages Not Found', "404s", [new ListingsFilterModel(self, 'statusCode', '404')]),
39
+ new PresetFilterModel('Logins and Logouts', "logins", [
40
+ new ListingsFilterModel(self, 'action', 'login', 'contains'),
41
+ new ListingsFilterModel(self, 'action', 'logout', 'contains')
42
+ ]),
43
+ //new PresetFilterModel('Top Consumers', "top_consumers", [new ListingsFilterModel(self, 'statusCode', '200')], urlGroupBy),
44
+ //new PresetFilterModel('Top 404s', "top_404s", [new ListingsFilterModel(self, 'statusCode', '404')], urlGroupBy),
45
+ new PresetFilterModel('Locked Out', "lockedOut", [new ListingsFilterModel(self, 'action', 'lockedOut')]),
46
+ new PresetFilterModel('Blocked', "blocked", [new ListingsFilterModel(self, 'action', 'blocked', 'contains')]),
47
+ new PresetFilterModel('Blocked By Firewall', "blocked:waf", [new ListingsFilterModel(self, 'action', 'blocked:waf')])
48
+ ]);
49
+
50
+ self.showAdvancedFilters = ko.observable(false);
51
+ self.showAdvancedFilters.subscribe(function(val) {
52
+ if (val && self.filters().length == 0) {
53
+ self.addFilter();
54
+ }
55
+ });
56
+
57
+ self.presetFiltersOptionsText = function(item) {
58
+ return item.text();
59
+ };
60
+
61
+ self.selectedPresetFilter = ko.observable();
62
+ self.selectedPresetFilter.subscribe(function(item) {
63
+ var clonedFilters = ko.toJS(item.filters());
64
+ var newFilters = [];
65
+ for (var i = 0; i < clonedFilters.length; i++) {
66
+ newFilters.push(new ListingsFilterModel(self, clonedFilters[i].param, clonedFilters[i].value, clonedFilters[i].operator));
67
+ }
68
+ self.filters(newFilters);
69
+ self.groupBy(item.groupBy());
70
+ });
71
+
72
+ self.filters.subscribe(function() {
73
+ self.checkQueryAndReloadListings();
74
+ });
75
+
76
+ self.addFilter = function() {
77
+ self.filters.push(new ListingsFilterModel(self));
78
+ };
79
+
80
+ self.removeFilter = function(item) {
81
+ self.filters.remove(item);
82
+ };
83
+
84
+ var currentFilterQuery = '';
85
+ var getURLEncodedFilters = function() {
86
+ var dataString = '';
87
+ ko.utils.arrayForEach(self.filters(), function(filter) {
88
+ if (filter.getValue() !== false) {
89
+ dataString += filter.urlEncoded() + '&';
90
+ }
91
+ });
92
+ var groupBy = self.groupBy();
93
+ if (groupBy) {
94
+ dataString += 'groupby=' + encodeURIComponent(groupBy.param()) + '&';
95
+ }
96
+ var startDate = self.startDate();
97
+ if (startDate) {
98
+ dataString += 'startDate=' + encodeURIComponent(startDate) + '&';
99
+ }
100
+ var endDate = self.endDate();
101
+ if (endDate) {
102
+ dataString += 'endDate=' + encodeURIComponent(endDate) + '&';
103
+ }
104
+ if (dataString.length > 1) {
105
+ return dataString.substring(0, dataString.length - 1);
106
+ }
107
+ return '';
108
+ };
109
+
110
+ self.filterGroupByOptions = ko.observableArray(groupBys);
111
+
112
+ self.filterGroupByOptionsText = function(item) {
113
+ return item.text() || item.param();
114
+ };
115
+
116
+ self.groupBy = ko.observable();
117
+ self.groupBy.subscribe(function() {
118
+ self.checkQueryAndReloadListings();
119
+ });
120
+
121
+ self.startDate = ko.observable();
122
+ self.startDate.subscribe(function() {
123
+ // console.log('start date change ' + self.startDate());
124
+ self.checkQueryAndReloadListings();
125
+ });
126
+
127
+ self.endDate = ko.observable();
128
+ self.endDate.subscribe(function() {
129
+ // console.log('end date change ' + self.endDate());
130
+ self.checkQueryAndReloadListings();
131
+ });
132
+
133
+ /**
134
+ * Pulls down fresh traffic data and resets the list.
135
+ *
136
+ * @param options
137
+ */
138
+ self.checkQueryAndReloadListings = function(options) {
139
+ if (currentFilterQuery !== getURLEncodedFilters()) {
140
+ self.reloadListings(options);
141
+ }
142
+ };
143
+ self.reloadListings = function(options) {
144
+ pullDownListings(options, function(listings) {
145
+ var newListings = [];
146
+ for (var i = 0; i < listings.length; i++) {
147
+ newListings.push(new ListingModel(listings[i]));
148
+ }
149
+ self.listings(newListings);
150
+ })
151
+ };
152
+
153
+ /**
154
+ * Used in the infinite scroll
155
+ */
156
+ self.loadNextListings = function(callback) {
157
+ var lastTimestamp = self.filters[0];
158
+ pullDownListings({
159
+ since: lastTimestamp,
160
+ limit: LISTING_LIMIT,
161
+ offset: self.listings().length
162
+ }, function() {
163
+ self.appendListings.apply(this, arguments);
164
+ typeof callback === 'function' && callback.apply(this, arguments);
165
+ });
166
+ };
167
+
168
+ self.getCurrentQueryString = function(options) {
169
+ var queryOptions = {
170
+ since: null,
171
+ limit: LISTING_LIMIT,
172
+ offset: 0
173
+ };
174
+ for (var prop in queryOptions) {
175
+ if (queryOptions.hasOwnProperty(prop) && options && prop in options) {
176
+ queryOptions[prop] = options[prop];
177
+ }
178
+ }
179
+ currentFilterQuery = getURLEncodedFilters();
180
+ var data = currentFilterQuery;
181
+ for (prop in queryOptions) {
182
+ if (queryOptions.hasOwnProperty(prop)) {
183
+ var val = queryOptions[prop];
184
+ if (val === null || val === undefined) {
185
+ val = '';
186
+ }
187
+ data += '&' + encodeURIComponent(prop) + '=' + encodeURIComponent(val);
188
+ }
189
+ }
190
+ return data;
191
+ };
192
+
193
+ var pullDownListings = function(options, callback) {
194
+ var data = self.getCurrentQueryString(options);
195
+
196
+ WFAD.ajax('wordfence_loadLiveTraffic', data, function(response) {
197
+ if (!response || !response.success) {
198
+ return;
199
+ }
200
+ callback && callback(response.data, response);
201
+ self.sql(response.sql);
202
+ });
203
+ };
204
+
205
+ self.prependListings = function(listings, response) {
206
+ for (var i = listings.length - 1; i >= 0; i--) {
207
+ // Prevent duplicates
208
+ if (self.hasListing(listings[i].id)) {
209
+ continue;
210
+ }
211
+ var listing = new ListingModel(listings[i]);
212
+ listing.highlighted(true);
213
+ self.listings.unshift(listing);
214
+ }
215
+
216
+ //self.listings.sort(function(a, b) {
217
+ // if (a.ctime() < b.ctime()) {
218
+ // return 1;
219
+ // } else if (a.ctime() > b.ctime()) {
220
+ // return -1;
221
+ // }
222
+ // return 0;
223
+ //});
224
+ };
225
+
226
+ self.appendListings = function(listings, response) {
227
+ var highlight = 3;
228
+ for (var i = 0; i < listings.length; i++) {
229
+ // Prevent duplicates
230
+ if (self.hasListing(listings[i].id)) {
231
+ continue;
232
+ }
233
+ var listing = new ListingModel(listings[i]);
234
+ listing.highlighted(highlight-- > 0);
235
+ self.listings.push(listing);
236
+ }
237
+
238
+ //self.listings.sort(function(a, b) {
239
+ // if (a.ctime() < b.ctime()) {
240
+ // return 1;
241
+ // } else if (a.ctime() > b.ctime()) {
242
+ // return -1;
243
+ // }
244
+ // return 0;
245
+ //});
246
+ };
247
+
248
+ self.whitelistWAFParamKey = function(path, paramKey, failedRules) {
249
+ WFAD.ajax('wordfence_whitelistWAFParamKey', {
250
+ path: path,
251
+ paramKey: paramKey,
252
+ failedRules: failedRules
253
+ }, function(response) {
254
+
255
+ });
256
+ };
257
+
258
+ /*
259
+ Blocking functions
260
+ */
261
+ self.unblockIP = function(item) {
262
+ WFAD.unblockIP(item.IP(), function() {
263
+ ko.utils.arrayForEach(self.listings(), function(listing) {
264
+ if (listing.IP() == item.IP()) {
265
+ listing.blocked(false);
266
+ }
267
+ });
268
+ });
269
+ };
270
+ self.unblockNetwork = function(item) {
271
+ WFAD.unblockNetwork(item.ipRangeID());
272
+ };
273
+ self.blockIP = function(item) {
274
+ WFAD.blockIP(item.IP(), 'Manual block by administrator', function() {
275
+ ko.utils.arrayForEach(self.listings(), function(listing) {
276
+ if (listing.IP() == item.IP()) {
277
+ listing.blocked(true);
278
+ }
279
+ });
280
+ });
281
+ };
282
+
283
+ // For debuggering-a-ding
284
+ self.sql = ko.observable('');
285
+ };
286
+
287
+ var ListingModel = function(data) {
288
+ var self = this;
289
+
290
+ self.id = ko.observable(0);
291
+ self.ctime = ko.observable(0);
292
+ self.IP = ko.observable('');
293
+ self.jsRun = ko.observable(0);
294
+ self.statusCode = ko.observable(200);
295
+ self.isGoogle = ko.observable(0);
296
+ self.userID = ko.observable(0);
297
+ self.newVisit = ko.observable(0);
298
+ self.URL = ko.observable('');
299
+ self.referer = ko.observable('');
300
+ self.UA = ko.observable('');
301
+ self.loc = ko.observable();
302
+ self.type = ko.observable('');
303
+ self.blocked = ko.observable(false);
304
+ self.rangeBlocked = ko.observable(false);
305
+ self.ipRangeID = ko.observable(-1);
306
+ self.extReferer = ko.observable();
307
+ self.browser = ko.observable();
308
+ self.user = ko.observable();
309
+ self.hitCount = ko.observable();
310
+ self.username = ko.observable('');
311
+
312
+ // New fields/columns
313
+ self.action = ko.observable('');
314
+ self.actionDescription = ko.observable(false);
315
+ self.actionData = ko.observable();
316
+
317
+ self.highlighted = ko.observable(false);
318
+ //self.highlighted.subscribe(function(val) {
319
+ // if (val) {
320
+ // _classes += ' highlighted';
321
+ // self.cssClasses(_classes);
322
+ // } else {
323
+ // _classes.replace(/ highlighted(\s*|$)/, ' ');
324
+ // self.cssClasses(_classes);
325
+ // }
326
+ //});
327
+
328
+ for (var prop in data) {
329
+ if (data.hasOwnProperty(prop)) {
330
+ self[prop] !== undefined && self[prop](data[prop]);
331
+ }
332
+ }
333
+
334
+ // Use the same format as these update.
335
+ self.timeAgo = ko.pureComputed(function() {
336
+ var serverTime = WFAD.serverMicrotime;
337
+ return $(WFAD.showTimestamp(this.ctime(), serverTime)).text();
338
+ }, self);
339
+
340
+ var formatBlockedParam = function(text, maxLength) {
341
+ maxLength = maxLength || 100;
342
+ if (text && text.length > maxLength) {
343
+ return text.substring(0, Math.round(maxLength)) + "\u2026";
344
+ // return text.substring(0, Math.round(maxLength / 2)) + " ... " + text.substring(text.length - Math.round(maxLength / 2));
345
+ }
346
+ return text;
347
+ };
348
+
349
+ self.displayURL = ko.pureComputed(function() {
350
+ return formatBlockedParam(self.URL(), 135);
351
+ });
352
+
353
+ self.firewallAction = ko.pureComputed(function() {
354
+ var desc = '';
355
+ switch (self.action()) {
356
+ case 'lockedOut':
357
+ return 'locked out from logging in';
358
+
359
+ case 'blocked:wordfence':
360
+ desc = self.actionDescription();
361
+ if (desc && desc.toLowerCase().indexOf('block') === 0) {
362
+ return 'b' + desc.substring(1);
363
+ }
364
+ return 'blocked for ' + desc;
365
+
366
+ case 'blocked:wfsn':
367
+ return 'blocked by the Wordfence Security Network';
368
+
369
+ case 'blocked:waf':
370
+ var data = self.actionData();
371
+ if (typeof data === 'object') {
372
+ var paramKey = WFAD.base64_decode(data.paramKey);
373
+ var paramValue = WFAD.base64_decode(data.paramValue);
374
+ // var category = data.category;
375
+
376
+ var matches = paramKey.match(/([a-z0-9_]+\.[a-z0-9_]+)(?:\[(.+?)\](.*))?/i);
377
+ desc = self.actionDescription();
378
+ if (matches) {
379
+ switch (matches[1]) {
380
+ case 'request.queryString':
381
+ desc = self.actionDescription() + ' in query string: ' + matches[2] + '=' + formatBlockedParam(encodeURIComponent(paramValue));
382
+ break;
383
+ case 'request.body':
384
+ desc = self.actionDescription() + ' in POST body: ' + matches[2] + '=' + formatBlockedParam(encodeURIComponent(paramValue));
385
+ break;
386
+ case 'request.cookie':
387
+ desc = self.actionDescription() + ' in cookie: ' + matches[2] + '=' + formatBlockedParam(encodeURIComponent(paramValue));
388
+ break;
389
+ case 'request.fileNames':
390
+ desc = 'a ' + self.actionDescription() + ' in file: ' + matches[2] + '=' + formatBlockedParam(encodeURIComponent(paramValue));
391
+ break;
392
+ }
393
+ }
394
+ if (desc) {
395
+ return 'blocked by firewall for ' + desc;
396
+ }
397
+ return 'blocked by firewall';
398
+ }
399
+ return 'blocked by firewall for ' + self.actionDescription();
400
+ }
401
+ return desc;
402
+ });
403
+
404
+ self.cssClasses = ko.pureComputed(function() {
405
+ var classes = 'wfActEvent';
406
+ if (self.statusCode() == 403) {
407
+ classes += ' wfActionBlocked';
408
+ }
409
+ if (self.statusCode() == 404) {
410
+ classes += ' wf404';
411
+ }
412
+ if (self.jsRun() == 1) {
413
+ classes += ' wfHuman';
414
+ }
415
+ if (self.actionData() && self.actionData().learningMode) {
416
+ classes += ' wfWAFLearningMode';
417
+ }
418
+ if (self.highlighted()) {
419
+ classes += ' highlighted';
420
+ }
421
+ return classes;
422
+ });
423
+ };
424
+
425
+ var ListingsFilterModel = function(viewModel, param, value, operator) {
426
+ var self = this;
427
+ self.viewModel = viewModel;
428
+ self.param = ko.observable('');
429
+ self.value = ko.observable('');
430
+ self.operator = ko.observable('');
431
+
432
+ self.param(param);
433
+ self.value(value);
434
+ self.operator(operator || '=');
435
+
436
+ var filterChanged = function() {
437
+ self.viewModel && self.viewModel.checkQueryAndReloadListings && self.viewModel.checkQueryAndReloadListings();
438
+ };
439
+ self.param.subscribe(filterChanged);
440
+ self.operator.subscribe(filterChanged);
441
+ self.value.subscribe(function(value) {
442
+ if (value instanceof FilterParamEnumOptionModel && value.operator()) {
443
+ self.selectedFilterOperatorOptionValue(value.operator());
444
+ }
445
+ filterChanged();
446
+ });
447
+
448
+ var equalsOperator = new FilterOperatorModel('=');
449
+ var notEqualsOperator = new FilterOperatorModel('!=', '\u2260');
450
+ var containsOperator = new FilterOperatorModel('contains');
451
+ var matchOperator = new FilterOperatorModel('match');
452
+ self.filterOperatorOptions = ko.observableArray([
453
+ equalsOperator,
454
+ notEqualsOperator,
455
+ containsOperator,
456
+ matchOperator
457
+ ]);
458
+
459
+ self.filterParamOptions = ko.observableArray([
460
+ new FilterParamModel('type', 'Type', 'enum', [
461
+ new FilterParamEnumOptionModel('human', 'Human'),
462
+ new FilterParamEnumOptionModel('bot', 'Bot')
463
+ ]),
464
+ new FilterParamModel('user_login', 'Username'),
465
+ new FilterParamModel('userID', 'UserID'),
466
+ new FilterParamModel('isGoogle', 'Google Bot', 'bool'),
467
+ new FilterParamModel('ip', 'IP'),
468
+ new FilterParamModel('ua', 'User Agent'),
469
+ new FilterParamModel('referer', 'Referer'),
470
+ new FilterParamModel('url', 'URL'),
471
+ new FilterParamModel('statusCode', 'HTTP Response Code'),
472
+ new FilterParamModel('action', 'Firewall Response', 'enum', [
473
+ new FilterParamEnumOptionModel('', 'OK'),
474
+ new FilterParamEnumOptionModel('throttled', 'Throttled'),
475
+ new FilterParamEnumOptionModel('lockedOut', 'Locked Out'),
476
+ new FilterParamEnumOptionModel('blocked', 'Blocked', containsOperator),
477
+ new FilterParamEnumOptionModel('blocked:waf', 'Blocked WAF')
478
+ ]),
479
+ new FilterParamModel('action', 'Logins', 'enum', [
480
+ new FilterParamEnumOptionModel('loginOK', 'Logged In'),
481
+ new FilterParamEnumOptionModel('loginFail', 'Failed Login'),
482
+ new FilterParamEnumOptionModel('loginFailInvalidUsername', 'Failed Login: Invalid Username'),
483
+ new FilterParamEnumOptionModel('loginFailValidUsername', 'Failed Login: Valid Username')
484
+ ]),
485
+ new FilterParamModel('action', 'Security Event')
486
+ ]);
487
+
488
+ self.filterParamOptionsText = function(item) {
489
+ return item.text() || item.param();
490
+ };
491
+
492
+ self.selectedFilterParamOptionValue = ko.observable();
493
+ self.selectedFilterParamOptionValue.subscribe(function(item) {
494
+ self.param(item && item.param ? item.param() : '');
495
+ });
496
+
497
+ ko.utils.arrayForEach(self.filterParamOptions(), function(item) {
498
+ if (self.param() == item.param()) {
499
+ switch (item.type()) {
500
+ case 'enum':
501
+ // console.log(self.param(), item.param(), self.value(), values);
502
+ switch (self.operator()) {
503
+ case '=':
504
+ ko.utils.arrayForEach(item.values(), function(enumOption) {
505
+ if (enumOption.value() == self.value()) {
506
+ self.selectedFilterParamOptionValue(item);
507
+ }
508
+ });
509
+ break;
510
+ }
511
+ break;
512
+
513
+ default:
514
+ self.selectedFilterParamOptionValue(item);
515
+ break;
516
+ }
517
+ }
518
+ });
519
+
520
+ self.filterOperatorOptionsText = function(item) {
521
+ return item.text() || item.operator();
522
+ };
523
+
524
+ self.selectedFilterOperatorOptionValue = ko.observable();
525
+ self.selectedFilterOperatorOptionValue.subscribe(function(item) {
526
+ self.operator(item.operator());
527
+ });
528
+
529
+ ko.utils.arrayForEach(self.filterOperatorOptions(), function(item) {
530
+ if (self.operator() == item.operator()) {
531
+ self.selectedFilterOperatorOptionValue(item);
532
+ }
533
+ });
534
+
535
+ self.getValue = function() {
536
+ var value = self.value() instanceof FilterParamEnumOptionModel ? self.value().value() : self.value();
537
+ return (typeof value === 'string' || typeof value === 'number') ? value : false;
538
+ };
539
+ self.urlEncoded = function() {
540
+ var value = self.getValue();
541
+ return 'param[]=' + encodeURIComponent(self.param()) + '&value[]=' + encodeURIComponent(value) +
542
+ '&operator[]=' + encodeURIComponent(self.operator());
543
+ };
544
+ };
545
+
546
+ var PresetFilterModel = function(text, value, filters, groupBy) {
547
+ this.text = ko.observable('');
548
+ this.value = ko.observable('');
549
+ this.filters = ko.observableArray(filters);
550
+ this.groupBy = ko.observable(groupBy);
551
+
552
+ this.text(text);
553
+ this.value(value);
554
+ };
555
+
556
+ var FilterParamModel = function(param, text, type, values) {
557
+ this.text = ko.observable('');
558
+ this.param = ko.observable('');
559
+ this.type = ko.observable('');
560
+ this.values = ko.observableArray(values);
561
+
562
+ this.text(text);
563
+ this.param(param);
564
+ this.type(type || 'text');
565
+
566
+ this.optionsText = function(item) {
567
+ if (item instanceof FilterParamEnumOptionModel) {
568
+ return item.label() || item.value();
569
+ }
570
+ return item;
571
+ }
572
+ };
573
+
574
+ var FilterParamEnumOptionModel = function(value, label, operator) {
575
+ this.value = ko.observable('');
576
+ this.label = ko.observable('');
577
+ this.operator = ko.observable('');
578
+
579
+ this.value = ko.observable(value);
580
+ this.label = ko.observable(label);
581
+ this.operator = ko.observable(operator);
582
+
583
+ this.toString = function() {
584
+ return this.value();
585
+ }
586
+ };
587
+
588
+ var FilterOperatorModel = function(operator, text) {
589
+ this.text = ko.observable('');
590
+ this.operator = ko.observable('');
591
+
592
+ this.text(text);
593
+ this.operator(operator);
594
+ };
595
+
596
+ var GroupByModel = function(param, text) {
597
+ this.text = ko.observable('');
598
+ this.param = ko.observable('');
599
+
600
+ this.text(text);
601
+ this.param(param);
602
+ };
603
+
604
+ ko.bindingHandlers.datetimepicker = {
605
+ init: function(element, valueAccessor, allBindingsAccessor) {
606
+ //initialize datepicker with some optional options
607
+ var options = allBindingsAccessor().datepickerOptions || {},
608
+ $el = $(element);
609
+
610
+ $el.datetimepicker(options);
611
+
612
+ //handle the field changing by registering datepicker's changeDate event
613
+ ko.utils.registerEventHandler(element, "changeDate", function() {
614
+ var observable = valueAccessor();
615
+ observable($el.datetimepicker("getDate"));
616
+ });
617
+
618
+ //handle disposal (if KO removes by the template binding)
619
+ ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
620
+ $el.datetimepicker("destroy");
621
+ });
622
+
623
+ },
624
+ update: function(element, valueAccessor) {
625
+ var value = ko.utils.unwrapObservable(valueAccessor()),
626
+ $el = $(element);
627
+
628
+ //handle date data coming via json from Microsoft
629
+ if (String(value).indexOf('/Date(') == 0) {
630
+ value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
631
+ }
632
+
633
+ var current = $el.datetimepicker("getDate");
634
+
635
+ if (value - current !== 0) {
636
+ $el.datetimepicker("setDate", value);
637
+ }
638
+ }
639
+ };
640
+
641
+
642
+ $(function() {
643
+ var liveTrafficWrapper = $('#wf-live-traffic');
644
+ WFAD.wfLiveTraffic = new LiveTrafficViewModel();
645
+ ko.applyBindings(WFAD.wfLiveTraffic, liveTrafficWrapper.get(0));
646
+ liveTrafficWrapper.find('form').submit();
647
+ WFAD.mode = 'liveTraffic';
648
+
649
+ var legend = $('#wf-live-traffic-legend');
650
+ var adminBar = $('#wpadminbar');
651
+
652
+ var hasScrolled = false;
653
+ var loadingListings = false;
654
+ $(window).on('scroll', function() {
655
+ var win = $(this);
656
+ if (liveTrafficWrapper.offset().top < win.scrollTop() + adminBar.outerHeight() + 20) {
657
+ legend.addClass('sticky');
658
+ } else {
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;
665
+ if (hasScrolled && !loadingListings && currentScrollBottom >= scrollThreshold) {
666
+ // console.log('infinite scroll');
667
+
668
+ loadingListings = true;
669
+ hasScrolled = false;
670
+ WFAD.wfLiveTraffic.loadNextListings(function() {
671
+ loadingListings = false;
672
+ });
673
+ } else if (currentScrollBottom < scrollThreshold) {
674
+ hasScrolled = true;
675
+ // console.log('no infinite scroll');
676
+ }
677
+ });
678
+ });
679
+ })
680
+ (jQuery);
js/jquery-ui-timepicker-addon.js ADDED
@@ -0,0 +1,2245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*! jQuery Timepicker Addon - v1.5.3 - 2015-04-19
2
+ * http://trentrichardson.com/examples/timepicker
3
+ * Copyright (c) 2015 Trent Richardson; Licensed MIT */
4
+ (function (factory) {
5
+ if (typeof define === 'function' && define.amd) {
6
+ define(['jquery', 'jquery.ui'], factory);
7
+ } else {
8
+ factory(jQuery);
9
+ }
10
+ }(function ($) {
11
+
12
+ /*
13
+ * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded"
14
+ */
15
+ $.ui.timepicker = $.ui.timepicker || {};
16
+ if ($.ui.timepicker.version) {
17
+ return;
18
+ }
19
+
20
+ /*
21
+ * Extend jQueryUI, get it started with our version number
22
+ */
23
+ $.extend($.ui, {
24
+ timepicker: {
25
+ version: "1.5.3"
26
+ }
27
+ });
28
+
29
+ /*
30
+ * Timepicker manager.
31
+ * Use the singleton instance of this class, $.timepicker, to interact with the time picker.
32
+ * Settings for (groups of) time pickers are maintained in an instance object,
33
+ * allowing multiple different settings on the same page.
34
+ */
35
+ var Timepicker = function () {
36
+ this.regional = []; // Available regional settings, indexed by language code
37
+ this.regional[''] = { // Default regional settings
38
+ currentText: 'Now',
39
+ closeText: 'Done',
40
+ amNames: ['AM', 'A'],
41
+ pmNames: ['PM', 'P'],
42
+ timeFormat: 'HH:mm',
43
+ timeSuffix: '',
44
+ timeOnlyTitle: 'Choose Time',
45
+ timeText: 'Time',
46
+ hourText: 'Hour',
47
+ minuteText: 'Minute',
48
+ secondText: 'Second',
49
+ millisecText: 'Millisecond',
50
+ microsecText: 'Microsecond',
51
+ timezoneText: 'Time Zone',
52
+ isRTL: false
53
+ };
54
+ this._defaults = { // Global defaults for all the datetime picker instances
55
+ showButtonPanel: true,
56
+ timeOnly: false,
57
+ timeOnlyShowDate: false,
58
+ showHour: null,
59
+ showMinute: null,
60
+ showSecond: null,
61
+ showMillisec: null,
62
+ showMicrosec: null,
63
+ showTimezone: null,
64
+ showTime: true,
65
+ stepHour: 1,
66
+ stepMinute: 1,
67
+ stepSecond: 1,
68
+ stepMillisec: 1,
69
+ stepMicrosec: 1,
70
+ hour: 0,
71
+ minute: 0,
72
+ second: 0,
73
+ millisec: 0,
74
+ microsec: 0,
75
+ timezone: null,
76
+ hourMin: 0,
77
+ minuteMin: 0,
78
+ secondMin: 0,
79
+ millisecMin: 0,
80
+ microsecMin: 0,
81
+ hourMax: 23,
82
+ minuteMax: 59,
83
+ secondMax: 59,
84
+ millisecMax: 999,
85
+ microsecMax: 999,
86
+ minDateTime: null,
87
+ maxDateTime: null,
88
+ maxTime: null,
89
+ minTime: null,
90
+ onSelect: null,
91
+ hourGrid: 0,
92
+ minuteGrid: 0,
93
+ secondGrid: 0,
94
+ millisecGrid: 0,
95
+ microsecGrid: 0,
96
+ alwaysSetTime: true,
97
+ separator: ' ',
98
+ altFieldTimeOnly: true,
99
+ altTimeFormat: null,
100
+ altSeparator: null,
101
+ altTimeSuffix: null,
102
+ altRedirectFocus: true,
103
+ pickerTimeFormat: null,
104
+ pickerTimeSuffix: null,
105
+ showTimepicker: true,
106
+ timezoneList: null,
107
+ addSliderAccess: false,
108
+ sliderAccessArgs: null,
109
+ controlType: 'slider',
110
+ oneLine: false,
111
+ defaultValue: null,
112
+ parse: 'strict',
113
+ afterInject: null
114
+ };
115
+ $.extend(this._defaults, this.regional['']);
116
+ };
117
+
118
+ $.extend(Timepicker.prototype, {
119
+ $input: null,
120
+ $altInput: null,
121
+ $timeObj: null,
122
+ inst: null,
123
+ hour_slider: null,
124
+ minute_slider: null,
125
+ second_slider: null,
126
+ millisec_slider: null,
127
+ microsec_slider: null,
128
+ timezone_select: null,
129
+ maxTime: null,
130
+ minTime: null,
131
+ hour: 0,
132
+ minute: 0,
133
+ second: 0,
134
+ millisec: 0,
135
+ microsec: 0,
136
+ timezone: null,
137
+ hourMinOriginal: null,
138
+ minuteMinOriginal: null,
139
+ secondMinOriginal: null,
140
+ millisecMinOriginal: null,
141
+ microsecMinOriginal: null,
142
+ hourMaxOriginal: null,
143
+ minuteMaxOriginal: null,
144
+ secondMaxOriginal: null,
145
+ millisecMaxOriginal: null,
146
+ microsecMaxOriginal: null,
147
+ ampm: '',
148
+ formattedDate: '',
149
+ formattedTime: '',
150
+ formattedDateTime: '',
151
+ timezoneList: null,
152
+ units: ['hour', 'minute', 'second', 'millisec', 'microsec'],
153
+ support: {},
154
+ control: null,
155
+
156
+ /*
157
+ * Override the default settings for all instances of the time picker.
158
+ * @param {Object} settings object - the new settings to use as defaults (anonymous object)
159
+ * @return {Object} the manager object
160
+ */
161
+ setDefaults: function (settings) {
162
+ extendRemove(this._defaults, settings || {});
163
+ return this;
164
+ },
165
+
166
+ /*
167
+ * Create a new Timepicker instance
168
+ */
169
+ _newInst: function ($input, opts) {
170
+ var tp_inst = new Timepicker(),
171
+ inlineSettings = {},
172
+ fns = {},
173
+ overrides, i;
174
+
175
+ for (var attrName in this._defaults) {
176
+ if (this._defaults.hasOwnProperty(attrName)) {
177
+ var attrValue = $input.attr('time:' + attrName);
178
+ if (attrValue) {
179
+ try {
180
+ inlineSettings[attrName] = eval(attrValue);
181
+ } catch (err) {
182
+ inlineSettings[attrName] = attrValue;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ overrides = {
189
+ beforeShow: function (input, dp_inst) {
190
+ if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) {
191
+ return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst);
192
+ }
193
+ },
194
+ onChangeMonthYear: function (year, month, dp_inst) {
195
+ // Update the time as well : this prevents the time from disappearing from the $input field.
196
+ // tp_inst._updateDateTime(dp_inst);
197
+ if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) {
198
+ tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
199
+ }
200
+ },
201
+ onClose: function (dateText, dp_inst) {
202
+ if (tp_inst.timeDefined === true && $input.val() !== '') {
203
+ tp_inst._updateDateTime(dp_inst);
204
+ }
205
+ if ($.isFunction(tp_inst._defaults.evnts.onClose)) {
206
+ tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst);
207
+ }
208
+ }
209
+ };
210
+ for (i in overrides) {
211
+ if (overrides.hasOwnProperty(i)) {
212
+ fns[i] = opts[i] || null;
213
+ }
214
+ }
215
+
216
+ tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, {
217
+ evnts: fns,
218
+ timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
219
+ });
220
+ tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) {
221
+ return val.toUpperCase();
222
+ });
223
+ tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) {
224
+ return val.toUpperCase();
225
+ });
226
+
227
+ // detect which units are supported
228
+ tp_inst.support = detectSupport(
229
+ tp_inst._defaults.timeFormat +
230
+ (tp_inst._defaults.pickerTimeFormat ? tp_inst._defaults.pickerTimeFormat : '') +
231
+ (tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : ''));
232
+
233
+ // controlType is string - key to our this._controls
234
+ if (typeof(tp_inst._defaults.controlType) === 'string') {
235
+ if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') {
236
+ tp_inst._defaults.controlType = 'select';
237
+ }
238
+ tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType];
239
+ }
240
+ // controlType is an object and must implement create, options, value methods
241
+ else {
242
+ tp_inst.control = tp_inst._defaults.controlType;
243
+ }
244
+
245
+ // prep the timezone options
246
+ var timezoneList = [-720, -660, -600, -570, -540, -480, -420, -360, -300, -270, -240, -210, -180, -120, -60,
247
+ 0, 60, 120, 180, 210, 240, 270, 300, 330, 345, 360, 390, 420, 480, 525, 540, 570, 600, 630, 660, 690, 720, 765, 780, 840];
248
+ if (tp_inst._defaults.timezoneList !== null) {
249
+ timezoneList = tp_inst._defaults.timezoneList;
250
+ }
251
+ var tzl = timezoneList.length, tzi = 0, tzv = null;
252
+ if (tzl > 0 && typeof timezoneList[0] !== 'object') {
253
+ for (; tzi < tzl; tzi++) {
254
+ tzv = timezoneList[tzi];
255
+ timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) };
256
+ }
257
+ }
258
+ tp_inst._defaults.timezoneList = timezoneList;
259
+
260
+ // set the default units
261
+ tp_inst.timezone = tp_inst._defaults.timezone !== null ? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) :
262
+ ((new Date()).getTimezoneOffset() * -1);
263
+ tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin ? tp_inst._defaults.hourMin :
264
+ tp_inst._defaults.hour > tp_inst._defaults.hourMax ? tp_inst._defaults.hourMax : tp_inst._defaults.hour;
265
+ tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin ? tp_inst._defaults.minuteMin :
266
+ tp_inst._defaults.minute > tp_inst._defaults.minuteMax ? tp_inst._defaults.minuteMax : tp_inst._defaults.minute;
267
+ tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin ? tp_inst._defaults.secondMin :
268
+ tp_inst._defaults.second > tp_inst._defaults.secondMax ? tp_inst._defaults.secondMax : tp_inst._defaults.second;
269
+ tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin ? tp_inst._defaults.millisecMin :
270
+ tp_inst._defaults.millisec > tp_inst._defaults.millisecMax ? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec;
271
+ tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin ? tp_inst._defaults.microsecMin :
272
+ tp_inst._defaults.microsec > tp_inst._defaults.microsecMax ? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec;
273
+ tp_inst.ampm = '';
274
+ tp_inst.$input = $input;
275
+
276
+ if (tp_inst._defaults.altField) {
277
+ tp_inst.$altInput = $(tp_inst._defaults.altField);
278
+ if (tp_inst._defaults.altRedirectFocus === true) {
279
+ tp_inst.$altInput.css({
280
+ cursor: 'pointer'
281
+ }).focus(function () {
282
+ $input.trigger("focus");
283
+ });
284
+ }
285
+ }
286
+
287
+ if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) {
288
+ tp_inst._defaults.minDate = new Date();
289
+ }
290
+ if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) {
291
+ tp_inst._defaults.maxDate = new Date();
292
+ }
293
+
294
+ // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
295
+ if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) {
296
+ tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
297
+ }
298
+ if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) {
299
+ tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
300
+ }
301
+ if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) {
302
+ tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
303
+ }
304
+ if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) {
305
+ tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
306
+ }
307
+ tp_inst.$input.bind('focus', function () {
308
+ tp_inst._onFocus();
309
+ });
310
+
311
+ return tp_inst;
312
+ },
313
+
314
+ /*
315
+ * add our sliders to the calendar
316
+ */
317
+ _addTimePicker: function (dp_inst) {
318
+ var currDT = $.trim((this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val());
319
+
320
+ this.timeDefined = this._parseTime(currDT);
321
+ this._limitMinMaxDateTime(dp_inst, false);
322
+ this._injectTimePicker();
323
+ this._afterInject();
324
+ },
325
+
326
+ /*
327
+ * parse the time string from input value or _setTime
328
+ */
329
+ _parseTime: function (timeString, withDate) {
330
+ if (!this.inst) {
331
+ this.inst = $.datepicker._getInst(this.$input[0]);
332
+ }
333
+
334
+ if (withDate || !this._defaults.timeOnly) {
335
+ var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
336
+ try {
337
+ var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults);
338
+ if (!parseRes.timeObj) {
339
+ return false;
340
+ }
341
+ $.extend(this, parseRes.timeObj);
342
+ } catch (err) {
343
+ $.timepicker.log("Error parsing the date/time string: " + err +
344
+ "\ndate/time string = " + timeString +
345
+ "\ntimeFormat = " + this._defaults.timeFormat +
346
+ "\ndateFormat = " + dp_dateFormat);
347
+ return false;
348
+ }
349
+ return true;
350
+ } else {
351
+ var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults);
352
+ if (!timeObj) {
353
+ return false;
354
+ }
355
+ $.extend(this, timeObj);
356
+ return true;
357
+ }
358
+ },
359
+
360
+ /*
361
+ * Handle callback option after injecting timepicker
362
+ */
363
+ _afterInject: function() {
364
+ var o = this.inst.settings;
365
+ if ($.isFunction(o.afterInject)) {
366
+ o.afterInject.call(this);
367
+ }
368
+ },
369
+
370
+ /*
371
+ * generate and inject html for timepicker into ui datepicker
372
+ */
373
+ _injectTimePicker: function () {
374
+ var $dp = this.inst.dpDiv,
375
+ o = this.inst.settings,
376
+ tp_inst = this,
377
+ litem = '',
378
+ uitem = '',
379
+ show = null,
380
+ max = {},
381
+ gridSize = {},
382
+ size = null,
383
+ i = 0,
384
+ l = 0;
385
+
386
+ // Prevent displaying twice
387
+ if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) {
388
+ var noDisplay = ' ui_tpicker_unit_hide',
389
+ html = '<div class="ui-timepicker-div' + (o.isRTL ? ' ui-timepicker-rtl' : '') + (o.oneLine && o.controlType === 'select' ? ' ui-timepicker-oneLine' : '') + '"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' +
390
+ '<dd class="ui_tpicker_time '+ ((o.showTime) ? '' : noDisplay) + '"></dd>';
391
+
392
+ // Create the markup
393
+ for (i = 0, l = this.units.length; i < l; i++) {
394
+ litem = this.units[i];
395
+ uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1);
396
+ show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem];
397
+
398
+ // Added by Peter Medeiros:
399
+ // - Figure out what the hour/minute/second max should be based on the step values.
400
+ // - Example: if stepMinute is 15, then minMax is 45.
401
+ max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10);
402
+ gridSize[litem] = 0;
403
+
404
+ html += '<dt class="ui_tpicker_' + litem + '_label' + (show ? '' : noDisplay) + '">' + o[litem + 'Text'] + '</dt>' +
405
+ '<dd class="ui_tpicker_' + litem + (show ? '' : noDisplay) + '"><div class="ui_tpicker_' + litem + '_slider' + (show ? '' : noDisplay) + '"></div>';
406
+
407
+ if (show && o[litem + 'Grid'] > 0) {
408
+ html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
409
+
410
+ if (litem === 'hour') {
411
+ for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) {
412
+ gridSize[litem]++;
413
+ var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o);
414
+ html += '<td data-for="' + litem + '">' + tmph + '</td>';
415
+ }
416
+ }
417
+ else {
418
+ for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) {
419
+ gridSize[litem]++;
420
+ html += '<td data-for="' + litem + '">' + ((m < 10) ? '0' : '') + m + '</td>';
421
+ }
422
+ }
423
+
424
+ html += '</tr></table></div>';
425
+ }
426
+ html += '</dd>';
427
+ }
428
+
429
+ // Timezone
430
+ var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone;
431
+ html += '<dt class="ui_tpicker_timezone_label' + (showTz ? '' : noDisplay) + '">' + o.timezoneText + '</dt>';
432
+ html += '<dd class="ui_tpicker_timezone' + (showTz ? '' : noDisplay) + '"></dd>';
433
+
434
+ // Create the elements from string
435
+ html += '</dl></div>';
436
+ var $tp = $(html);
437
+
438
+ // if we only want time picker...
439
+ if (o.timeOnly === true) {
440
+ $tp.prepend('<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + '</div>');
441
+ $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
442
+ }
443
+
444
+ // add sliders, adjust grids, add events
445
+ for (i = 0, l = tp_inst.units.length; i < l; i++) {
446
+ litem = tp_inst.units[i];
447
+ uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1);
448
+ show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem];
449
+
450
+ // add the slider
451
+ tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]);
452
+
453
+ // adjust the grid and add click event
454
+ if (show && o[litem + 'Grid'] > 0) {
455
+ size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']);
456
+ $tp.find('.ui_tpicker_' + litem + ' table').css({
457
+ width: size + "%",
458
+ marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"),
459
+ marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0',
460
+ borderCollapse: 'collapse'
461
+ }).find("td").click(function (e) {
462
+ var $t = $(this),
463
+ h = $t.html(),
464
+ n = parseInt(h.replace(/[^0-9]/g), 10),
465
+ ap = h.replace(/[^apm]/ig),
466
+ f = $t.data('for'); // loses scope, so we use data-for
467
+
468
+ if (f === 'hour') {
469
+ if (ap.indexOf('p') !== -1 && n < 12) {
470
+ n += 12;
471
+ }
472
+ else {
473
+ if (ap.indexOf('a') !== -1 && n === 12) {
474
+ n = 0;
475
+ }
476
+ }
477
+ }
478
+
479
+ tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n);
480
+
481
+ tp_inst._onTimeChange();
482
+ tp_inst._onSelectHandler();
483
+ }).css({
484
+ cursor: 'pointer',
485
+ width: (100 / gridSize[litem]) + '%',
486
+ textAlign: 'center',
487
+ overflow: 'hidden'
488
+ });
489
+ } // end if grid > 0
490
+ } // end for loop
491
+
492
+ // Add timezone options
493
+ this.timezone_select = $tp.find('.ui_tpicker_timezone').append('<select></select>').find("select");
494
+ $.fn.append.apply(this.timezone_select,
495
+ $.map(o.timezoneList, function (val, idx) {
496
+ return $("<option />").val(typeof val === "object" ? val.value : val).text(typeof val === "object" ? val.label : val);
497
+ }));
498
+ if (typeof(this.timezone) !== "undefined" && this.timezone !== null && this.timezone !== "") {
499
+ var local_timezone = (new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12)).getTimezoneOffset() * -1;
500
+ if (local_timezone === this.timezone) {
501
+ selectLocalTimezone(tp_inst);
502
+ } else {
503
+ this.timezone_select.val(this.timezone);
504
+ }
505
+ } else {
506
+ if (typeof(this.hour) !== "undefined" && this.hour !== null && this.hour !== "") {
507
+ this.timezone_select.val(o.timezone);
508
+ } else {
509
+ selectLocalTimezone(tp_inst);
510
+ }
511
+ }
512
+ this.timezone_select.change(function () {
513
+ tp_inst._onTimeChange();
514
+ tp_inst._onSelectHandler();
515
+ tp_inst._afterInject();
516
+ });
517
+ // End timezone options
518
+
519
+ // inject timepicker into datepicker
520
+ var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
521
+ if ($buttonPanel.length) {
522
+ $buttonPanel.before($tp);
523
+ } else {
524
+ $dp.append($tp);
525
+ }
526
+
527
+ this.$timeObj = $tp.find('.ui_tpicker_time');
528
+
529
+ if (this.inst !== null) {
530
+ var timeDefined = this.timeDefined;
531
+ this._onTimeChange();
532
+ this.timeDefined = timeDefined;
533
+ }
534
+
535
+ // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
536
+ if (this._defaults.addSliderAccess) {
537
+ var sliderAccessArgs = this._defaults.sliderAccessArgs,
538
+ rtl = this._defaults.isRTL;
539
+ sliderAccessArgs.isRTL = rtl;
540
+
541
+ setTimeout(function () { // fix for inline mode
542
+ if ($tp.find('.ui-slider-access').length === 0) {
543
+ $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs);
544
+
545
+ // fix any grids since sliders are shorter
546
+ var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true);
547
+ if (sliderAccessWidth) {
548
+ $tp.find('table:visible').each(function () {
549
+ var $g = $(this),
550
+ oldWidth = $g.outerWidth(),
551
+ oldMarginLeft = $g.css(rtl ? 'marginRight' : 'marginLeft').toString().replace('%', ''),
552
+ newWidth = oldWidth - sliderAccessWidth,
553
+ newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%',
554
+ css = { width: newWidth, marginRight: 0, marginLeft: 0 };
555
+ css[rtl ? 'marginRight' : 'marginLeft'] = newMarginLeft;
556
+ $g.css(css);
557
+ });
558
+ }
559
+ }
560
+ }, 10);
561
+ }
562
+ // end slideAccess integration
563
+
564
+ tp_inst._limitMinMaxDateTime(this.inst, true);
565
+ }
566
+ },
567
+
568
+ /*
569
+ * This function tries to limit the ability to go outside the
570
+ * min/max date range
571
+ */
572
+ _limitMinMaxDateTime: function (dp_inst, adjustSliders) {
573
+ var o = this._defaults,
574
+ dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);
575
+
576
+ if (!this._defaults.showTimepicker) {
577
+ return;
578
+ } // No time so nothing to check here
579
+
580
+ if ($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date) {
581
+ var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
582
+ minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);
583
+
584
+ if (this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null || this.microsecMinOriginal === null) {
585
+ this.hourMinOriginal = o.hourMin;
586
+ this.minuteMinOriginal = o.minuteMin;
587
+ this.secondMinOriginal = o.secondMin;
588
+ this.millisecMinOriginal = o.millisecMin;
589
+ this.microsecMinOriginal = o.microsecMin;
590
+ }
591
+
592
+ if (dp_inst.settings.timeOnly || minDateTimeDate.getTime() === dp_date.getTime()) {
593
+ this._defaults.hourMin = minDateTime.getHours();
594
+ if (this.hour <= this._defaults.hourMin) {
595
+ this.hour = this._defaults.hourMin;
596
+ this._defaults.minuteMin = minDateTime.getMinutes();
597
+ if (this.minute <= this._defaults.minuteMin) {
598
+ this.minute = this._defaults.minuteMin;
599
+ this._defaults.secondMin = minDateTime.getSeconds();
600
+ if (this.second <= this._defaults.secondMin) {
601
+ this.second = this._defaults.secondMin;
602
+ this._defaults.millisecMin = minDateTime.getMilliseconds();
603
+ if (this.millisec <= this._defaults.millisecMin) {
604
+ this.millisec = this._defaults.millisecMin;
605
+ this._defaults.microsecMin = minDateTime.getMicroseconds();
606
+ } else {
607
+ if (this.microsec < this._defaults.microsecMin) {
608
+ this.microsec = this._defaults.microsecMin;
609
+ }
610
+ this._defaults.microsecMin = this.microsecMinOriginal;
611
+ }
612
+ } else {
613
+ this._defaults.millisecMin = this.millisecMinOriginal;
614
+ this._defaults.microsecMin = this.microsecMinOriginal;
615
+ }
616
+ } else {
617
+ this._defaults.secondMin = this.secondMinOriginal;
618
+ this._defaults.millisecMin = this.millisecMinOriginal;
619
+ this._defaults.microsecMin = this.microsecMinOriginal;
620
+ }
621
+ } else {
622
+ this._defaults.minuteMin = this.minuteMinOriginal;
623
+ this._defaults.secondMin = this.secondMinOriginal;
624
+ this._defaults.millisecMin = this.millisecMinOriginal;
625
+ this._defaults.microsecMin = this.microsecMinOriginal;
626
+ }
627
+ } else {
628
+ this._defaults.hourMin = this.hourMinOriginal;
629
+ this._defaults.minuteMin = this.minuteMinOriginal;
630
+ this._defaults.secondMin = this.secondMinOriginal;
631
+ this._defaults.millisecMin = this.millisecMinOriginal;
632
+ this._defaults.microsecMin = this.microsecMinOriginal;
633
+ }
634
+ }
635
+
636
+ if ($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date) {
637
+ var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
638
+ maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);
639
+
640
+ if (this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null || this.millisecMaxOriginal === null) {
641
+ this.hourMaxOriginal = o.hourMax;
642
+ this.minuteMaxOriginal = o.minuteMax;
643
+ this.secondMaxOriginal = o.secondMax;
644
+ this.millisecMaxOriginal = o.millisecMax;
645
+ this.microsecMaxOriginal = o.microsecMax;
646
+ }
647
+
648
+ if (dp_inst.settings.timeOnly || maxDateTimeDate.getTime() === dp_date.getTime()) {
649
+ this._defaults.hourMax = maxDateTime.getHours();
650
+ if (this.hour >= this._defaults.hourMax) {
651
+ this.hour = this._defaults.hourMax;
652
+ this._defaults.minuteMax = maxDateTime.getMinutes();
653
+ if (this.minute >= this._defaults.minuteMax) {
654
+ this.minute = this._defaults.minuteMax;
655
+ this._defaults.secondMax = maxDateTime.getSeconds();
656
+ if (this.second >= this._defaults.secondMax) {
657
+ this.second = this._defaults.secondMax;
658
+ this._defaults.millisecMax = maxDateTime.getMilliseconds();
659
+ if (this.millisec >= this._defaults.millisecMax) {
660
+ this.millisec = this._defaults.millisecMax;
661
+ this._defaults.microsecMax = maxDateTime.getMicroseconds();
662
+ } else {
663
+ if (this.microsec > this._defaults.microsecMax) {
664
+ this.microsec = this._defaults.microsecMax;
665
+ }
666
+ this._defaults.microsecMax = this.microsecMaxOriginal;
667
+ }
668
+ } else {
669
+ this._defaults.millisecMax = this.millisecMaxOriginal;
670
+ this._defaults.microsecMax = this.microsecMaxOriginal;
671
+ }
672
+ } else {
673
+ this._defaults.secondMax = this.secondMaxOriginal;
674
+ this._defaults.millisecMax = this.millisecMaxOriginal;
675
+ this._defaults.microsecMax = this.microsecMaxOriginal;
676
+ }
677
+ } else {
678
+ this._defaults.minuteMax = this.minuteMaxOriginal;
679
+ this._defaults.secondMax = this.secondMaxOriginal;
680
+ this._defaults.millisecMax = this.millisecMaxOriginal;
681
+ this._defaults.microsecMax = this.microsecMaxOriginal;
682
+ }
683
+ } else {
684
+ this._defaults.hourMax = this.hourMaxOriginal;
685
+ this._defaults.minuteMax = this.minuteMaxOriginal;
686
+ this._defaults.secondMax = this.secondMaxOriginal;
687
+ this._defaults.millisecMax = this.millisecMaxOriginal;
688
+ this._defaults.microsecMax = this.microsecMaxOriginal;
689
+ }
690
+ }
691
+
692
+ if (dp_inst.settings.minTime!==null) {
693
+ var tempMinTime=new Date("01/01/1970 " + dp_inst.settings.minTime);
694
+ if (this.hour<tempMinTime.getHours()) {
695
+ this.hour=this._defaults.hourMin=tempMinTime.getHours();
696
+ this.minute=this._defaults.minuteMin=tempMinTime.getMinutes();
697
+ } else if (this.hour===tempMinTime.getHours() && this.minute<tempMinTime.getMinutes()) {
698
+ this.minute=this._defaults.minuteMin=tempMinTime.getMinutes();
699
+ } else {
700
+ if (this._defaults.hourMin<tempMinTime.getHours()) {
701
+ this._defaults.hourMin=tempMinTime.getHours();
702
+ this._defaults.minuteMin=tempMinTime.getMinutes();
703
+ } else if (this._defaults.hourMin===tempMinTime.getHours()===this.hour && this._defaults.minuteMin<tempMinTime.getMinutes()) {
704
+ this._defaults.minuteMin=tempMinTime.getMinutes();
705
+ } else {
706
+ this._defaults.minuteMin=0;
707
+ }
708
+ }
709
+ }
710
+
711
+ if (dp_inst.settings.maxTime!==null) {
712
+ var tempMaxTime=new Date("01/01/1970 " + dp_inst.settings.maxTime);
713
+ if (this.hour>tempMaxTime.getHours()) {
714
+ this.hour=this._defaults.hourMax=tempMaxTime.getHours();
715
+ this.minute=this._defaults.minuteMax=tempMaxTime.getMinutes();
716
+ } else if (this.hour===tempMaxTime.getHours() && this.minute>tempMaxTime.getMinutes()) {
717
+ this.minute=this._defaults.minuteMax=tempMaxTime.getMinutes();
718
+ } else {
719
+ if (this._defaults.hourMax>tempMaxTime.getHours()) {
720
+ this._defaults.hourMax=tempMaxTime.getHours();
721
+ this._defaults.minuteMax=tempMaxTime.getMinutes();
722
+ } else if (this._defaults.hourMax===tempMaxTime.getHours()===this.hour && this._defaults.minuteMax>tempMaxTime.getMinutes()) {
723
+ this._defaults.minuteMax=tempMaxTime.getMinutes();
724
+ } else {
725
+ this._defaults.minuteMax=59;
726
+ }
727
+ }
728
+ }
729
+
730
+ if (adjustSliders !== undefined && adjustSliders === true) {
731
+ var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)), 10),
732
+ minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)), 10),
733
+ secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)), 10),
734
+ millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)), 10),
735
+ microsecMax = parseInt((this._defaults.microsecMax - ((this._defaults.microsecMax - this._defaults.microsecMin) % this._defaults.stepMicrosec)), 10);
736
+
737
+ if (this.hour_slider) {
738
+ this.control.options(this, this.hour_slider, 'hour', { min: this._defaults.hourMin, max: hourMax, step: this._defaults.stepHour });
739
+ this.control.value(this, this.hour_slider, 'hour', this.hour - (this.hour % this._defaults.stepHour));
740
+ }
741
+ if (this.minute_slider) {
742
+ this.control.options(this, this.minute_slider, 'minute', { min: this._defaults.minuteMin, max: minMax, step: this._defaults.stepMinute });
743
+ this.control.value(this, this.minute_slider, 'minute', this.minute - (this.minute % this._defaults.stepMinute));
744
+ }
745
+ if (this.second_slider) {
746
+ this.control.options(this, this.second_slider, 'second', { min: this._defaults.secondMin, max: secMax, step: this._defaults.stepSecond });
747
+ this.control.value(this, this.second_slider, 'second', this.second - (this.second % this._defaults.stepSecond));
748
+ }
749
+ if (this.millisec_slider) {
750
+ this.control.options(this, this.millisec_slider, 'millisec', { min: this._defaults.millisecMin, max: millisecMax, step: this._defaults.stepMillisec });
751
+ this.control.value(this, this.millisec_slider, 'millisec', this.millisec - (this.millisec % this._defaults.stepMillisec));
752
+ }
753
+ if (this.microsec_slider) {
754
+ this.control.options(this, this.microsec_slider, 'microsec', { min: this._defaults.microsecMin, max: microsecMax, step: this._defaults.stepMicrosec });
755
+ this.control.value(this, this.microsec_slider, 'microsec', this.microsec - (this.microsec % this._defaults.stepMicrosec));
756
+ }
757
+ }
758
+
759
+ },
760
+
761
+ /*
762
+ * when a slider moves, set the internal time...
763
+ * on time change is also called when the time is updated in the text field
764
+ */
765
+ _onTimeChange: function () {
766
+ if (!this._defaults.showTimepicker) {
767
+ return;
768
+ }
769
+ var hour = (this.hour_slider) ? this.control.value(this, this.hour_slider, 'hour') : false,
770
+ minute = (this.minute_slider) ? this.control.value(this, this.minute_slider, 'minute') : false,
771
+ second = (this.second_slider) ? this.control.value(this, this.second_slider, 'second') : false,
772
+ millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider, 'millisec') : false,
773
+ microsec = (this.microsec_slider) ? this.control.value(this, this.microsec_slider, 'microsec') : false,
774
+ timezone = (this.timezone_select) ? this.timezone_select.val() : false,
775
+ o = this._defaults,
776
+ pickerTimeFormat = o.pickerTimeFormat || o.timeFormat,
777
+ pickerTimeSuffix = o.pickerTimeSuffix || o.timeSuffix;
778
+
779
+ if (typeof(hour) === 'object') {
780
+ hour = false;
781
+ }
782
+ if (typeof(minute) === 'object') {
783
+ minute = false;
784
+ }
785
+ if (typeof(second) === 'object') {
786
+ second = false;
787
+ }
788
+ if (typeof(millisec) === 'object') {
789
+ millisec = false;
790
+ }
791
+ if (typeof(microsec) === 'object') {
792
+ microsec = false;
793
+ }
794
+ if (typeof(timezone) === 'object') {
795
+ timezone = false;
796
+ }
797
+
798
+ if (hour !== false) {
799
+ hour = parseInt(hour, 10);
800
+ }
801
+ if (minute !== false) {
802
+ minute = parseInt(minute, 10);
803
+ }
804
+ if (second !== false) {
805
+ second = parseInt(second, 10);
806
+ }
807
+ if (millisec !== false) {
808
+ millisec = parseInt(millisec, 10);
809
+ }
810
+ if (microsec !== false) {
811
+ microsec = parseInt(microsec, 10);
812
+ }
813
+ if (timezone !== false) {
814
+ timezone = timezone.toString();
815
+ }
816
+
817
+ var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0];
818
+
819
+ // If the update was done in the input field, the input field should not be updated.
820
+ // If the update was done using the sliders, update the input field.
821
+ var hasChanged = (
822
+ hour !== parseInt(this.hour,10) || // sliders should all be numeric
823
+ minute !== parseInt(this.minute,10) ||
824
+ second !== parseInt(this.second,10) ||
825
+ millisec !== parseInt(this.millisec,10) ||
826
+ microsec !== parseInt(this.microsec,10) ||
827
+ (this.ampm.length > 0 && (hour < 12) !== ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) ||
828
+ (this.timezone !== null && timezone !== this.timezone.toString()) // could be numeric or "EST" format, so use toString()
829
+ );
830
+
831
+ if (hasChanged) {
832
+
833
+ if (hour !== false) {
834
+ this.hour = hour;
835
+ }
836
+ if (minute !== false) {
837
+ this.minute = minute;
838
+ }
839
+ if (second !== false) {
840
+ this.second = second;
841
+ }
842
+ if (millisec !== false) {
843
+ this.millisec = millisec;
844
+ }
845
+ if (microsec !== false) {
846
+ this.microsec = microsec;
847
+ }
848
+ if (timezone !== false) {
849
+ this.timezone = timezone;
850
+ }
851
+
852
+ if (!this.inst) {
853
+ this.inst = $.datepicker._getInst(this.$input[0]);
854
+ }
855
+
856
+ this._limitMinMaxDateTime(this.inst, true);
857
+ }
858
+ if (this.support.ampm) {
859
+ this.ampm = ampm;
860
+ }
861
+
862
+ // Updates the time within the timepicker
863
+ this.formattedTime = $.datepicker.formatTime(o.timeFormat, this, o);
864
+ if (this.$timeObj) {
865
+ if (pickerTimeFormat === o.timeFormat) {
866
+ this.$timeObj.text(this.formattedTime + pickerTimeSuffix);
867
+ }
868
+ else {
869
+ this.$timeObj.text($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix);
870
+ }
871
+ }
872
+
873
+ this.timeDefined = true;
874
+ if (hasChanged) {
875
+ this._updateDateTime();
876
+ //this.$input.focus(); // may automatically open the picker on setDate
877
+ }
878
+ },
879
+
880
+ /*
881
+ * call custom onSelect.
882
+ * bind to sliders slidestop, and grid click.
883
+ */
884
+ _onSelectHandler: function () {
885
+ var onSelect = this._defaults.onSelect || this.inst.settings.onSelect;
886
+ var inputEl = this.$input ? this.$input[0] : null;
887
+ if (onSelect && inputEl) {
888
+ onSelect.apply(inputEl, [this.formattedDateTime, this]);
889
+ }
890
+ },
891
+
892
+ /*
893
+ * update our input with the new date time..
894
+ */
895
+ _updateDateTime: function (dp_inst) {
896
+ dp_inst = this.inst || dp_inst;
897
+ var dtTmp = (dp_inst.currentYear > 0?
898
+ new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay) :
899
+ new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
900
+ dt = $.datepicker._daylightSavingAdjust(dtTmp),
901
+ //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
902
+ //dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay)),
903
+ dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
904
+ formatCfg = $.datepicker._getFormatConfig(dp_inst),
905
+ timeAvailable = dt !== null && this.timeDefined;
906
+ this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
907
+ var formattedDateTime = this.formattedDate;
908
+
909
+ // if a slider was changed but datepicker doesn't have a value yet, set it
910
+ if (dp_inst.lastVal === "") {
911
+ dp_inst.currentYear = dp_inst.selectedYear;
912
+ dp_inst.currentMonth = dp_inst.selectedMonth;
913
+ dp_inst.currentDay = dp_inst.selectedDay;
914
+ }
915
+
916
+ /*
917
+ * remove following lines to force every changes in date picker to change the input value
918
+ * Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker.
919
+ * If the user manually empty the value in the input field, the date picker will never change selected value.
920
+ */
921
+ //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) {
922
+ // return;
923
+ //}
924
+
925
+ if (this._defaults.timeOnly === true && this._defaults.timeOnlyShowDate === false) {
926
+ formattedDateTime = this.formattedTime;
927
+ } else if ((this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) || (this._defaults.timeOnly === true && this._defaults.timeOnlyShowDate === true)) {
928
+ formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
929
+ }
930
+
931
+ this.formattedDateTime = formattedDateTime;
932
+
933
+ if (!this._defaults.showTimepicker) {
934
+ this.$input.val(this.formattedDate);
935
+ } else if (this.$altInput && this._defaults.timeOnly === false && this._defaults.altFieldTimeOnly === true) {
936
+ this.$altInput.val(this.formattedTime);
937
+ this.$input.val(this.formattedDate);
938
+ } else if (this.$altInput) {
939
+ this.$input.val(formattedDateTime);
940
+ var altFormattedDateTime = '',
941
+ altSeparator = this._defaults.altSeparator !== null ? this._defaults.altSeparator : this._defaults.separator,
942
+ altTimeSuffix = this._defaults.altTimeSuffix !== null ? this._defaults.altTimeSuffix : this._defaults.timeSuffix;
943
+
944
+ if (!this._defaults.timeOnly) {
945
+ if (this._defaults.altFormat) {
946
+ altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg);
947
+ }
948
+ else {
949
+ altFormattedDateTime = this.formattedDate;
950
+ }
951
+
952
+ if (altFormattedDateTime) {
953
+ altFormattedDateTime += altSeparator;
954
+ }
955
+ }
956
+
957
+ if (this._defaults.altTimeFormat !== null) {
958
+ altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix;
959
+ }
960
+ else {
961
+ altFormattedDateTime += this.formattedTime + altTimeSuffix;
962
+ }
963
+ this.$altInput.val(altFormattedDateTime);
964
+ } else {
965
+ this.$input.val(formattedDateTime);
966
+ }
967
+
968
+ this.$input.trigger("change");
969
+ },
970
+
971
+ _onFocus: function () {
972
+ if (!this.$input.val() && this._defaults.defaultValue) {
973
+ this.$input.val(this._defaults.defaultValue);
974
+ var inst = $.datepicker._getInst(this.$input.get(0)),
975
+ tp_inst = $.datepicker._get(inst, 'timepicker');
976
+ if (tp_inst) {
977
+ if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) {
978
+ try {
979
+ $.datepicker._updateDatepicker(inst);
980
+ } catch (err) {
981
+ $.timepicker.log(err);
982
+ }
983
+ }
984
+ }
985
+ }
986
+ },
987
+
988
+ /*
989
+ * Small abstraction to control types
990
+ * We can add more, just be sure to follow the pattern: create, options, value
991
+ */
992
+ _controls: {
993
+ // slider methods
994
+ slider: {
995
+ create: function (tp_inst, obj, unit, val, min, max, step) {
996
+ var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60
997
+ return obj.prop('slide', null).slider({
998
+ orientation: "horizontal",
999
+ value: rtl ? val * -1 : val,
1000
+ min: rtl ? max * -1 : min,
1001
+ max: rtl ? min * -1 : max,
1002
+ step: step,
1003
+ slide: function (event, ui) {
1004
+ tp_inst.control.value(tp_inst, $(this), unit, rtl ? ui.value * -1 : ui.value);
1005
+ tp_inst._onTimeChange();
1006
+ },
1007
+ stop: function (event, ui) {
1008
+ tp_inst._onSelectHandler();
1009
+ }
1010
+ });
1011
+ },
1012
+ options: function (tp_inst, obj, unit, opts, val) {
1013
+ if (tp_inst._defaults.isRTL) {
1014
+ if (typeof(opts) === 'string') {
1015
+ if (opts === 'min' || opts === 'max') {
1016
+ if (val !== undefined) {
1017
+ return obj.slider(opts, val * -1);
1018
+ }
1019
+ return Math.abs(obj.slider(opts));
1020
+ }
1021
+ return obj.slider(opts);
1022
+ }
1023
+ var min = opts.min,
1024
+ max = opts.max;
1025
+ opts.min = opts.max = null;
1026
+ if (min !== undefined) {
1027
+ opts.max = min * -1;
1028
+ }
1029
+ if (max !== undefined) {
1030
+ opts.min = max * -1;
1031
+ }
1032
+ return obj.slider(opts);
1033
+ }
1034
+ if (typeof(opts) === 'string' && val !== undefined) {
1035
+ return obj.slider(opts, val);
1036
+ }
1037
+ return obj.slider(opts);
1038
+ },
1039
+ value: function (tp_inst, obj, unit, val) {
1040
+ if (tp_inst._defaults.isRTL) {
1041
+ if (val !== undefined) {
1042
+ return obj.slider('value', val * -1);
1043
+ }
1044
+ return Math.abs(obj.slider('value'));
1045
+ }
1046
+ if (val !== undefined) {
1047
+ return obj.slider('value', val);
1048
+ }
1049
+ return obj.slider('value');
1050
+ }
1051
+ },
1052
+ // select methods
1053
+ select: {
1054
+ create: function (tp_inst, obj, unit, val, min, max, step) {
1055
+ var sel = '<select class="ui-timepicker-select ui-state-default ui-corner-all" data-unit="' + unit + '" data-min="' + min + '" data-max="' + max + '" data-step="' + step + '">',
1056
+ format = tp_inst._defaults.pickerTimeFormat || tp_inst._defaults.timeFormat;
1057
+
1058
+ for (var i = min; i <= max; i += step) {
1059
+ sel += '<option value="' + i + '"' + (i === val ? ' selected' : '') + '>';
1060
+ if (unit === 'hour') {
1061
+ sel += $.datepicker.formatTime($.trim(format.replace(/[^ht ]/ig, '')), {hour: i}, tp_inst._defaults);
1062
+ }
1063
+ else if (unit === 'millisec' || unit === 'microsec' || i >= 10) { sel += i; }
1064
+ else {sel += '0' + i.toString(); }
1065
+ sel += '</option>';
1066
+ }
1067
+ sel += '</select>';
1068
+
1069
+ obj.children('select').remove();
1070
+
1071
+ $(sel).appendTo(obj).change(function (e) {
1072
+ tp_inst._onTimeChange();
1073
+ tp_inst._onSelectHandler();
1074
+ tp_inst._afterInject();
1075
+ });
1076
+
1077
+ return obj;
1078
+ },
1079
+ options: function (tp_inst, obj, unit, opts, val) {
1080
+ var o = {},
1081
+ $t = obj.children('select');
1082
+ if (typeof(opts) === 'string') {
1083
+ if (val === undefined) {
1084
+ return $t.data(opts);
1085
+ }
1086
+ o[opts] = val;
1087
+ }
1088
+ else { o = opts; }
1089
+ return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min>=0 ? o.min : $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step'));
1090
+ },
1091
+ value: function (tp_inst, obj, unit, val) {
1092
+ var $t = obj.children('select');
1093
+ if (val !== undefined) {
1094
+ return $t.val(val);
1095
+ }
1096
+ return $t.val();
1097
+ }
1098
+ }
1099
+ } // end _controls
1100
+
1101
+ });
1102
+
1103
+ $.fn.extend({
1104
+ /*
1105
+ * shorthand just to use timepicker.
1106
+ */
1107
+ timepicker: function (o) {
1108
+ o = o || {};
1109
+ var tmp_args = Array.prototype.slice.call(arguments);
1110
+
1111
+ if (typeof o === 'object') {
1112
+ tmp_args[0] = $.extend(o, {
1113
+ timeOnly: true
1114
+ });
1115
+ }
1116
+
1117
+ return $(this).each(function () {
1118
+ $.fn.datetimepicker.apply($(this), tmp_args);
1119
+ });
1120
+ },
1121
+
1122
+ /*
1123
+ * extend timepicker to datepicker
1124
+ */
1125
+ datetimepicker: function (o) {
1126
+ o = o || {};
1127
+ var tmp_args = arguments;
1128
+
1129
+ if (typeof(o) === 'string') {
1130
+ if (o === 'getDate' || (o === 'option' && tmp_args.length === 2 && typeof (tmp_args[1]) === 'string')) {
1131
+ return $.fn.datepicker.apply($(this[0]), tmp_args);
1132
+ } else {
1133
+ return this.each(function () {
1134
+ var $t = $(this);
1135
+ $t.datepicker.apply($t, tmp_args);
1136
+ });
1137
+ }
1138
+ } else {
1139
+ return this.each(function () {
1140
+ var $t = $(this);
1141
+ $t.datepicker($.timepicker._newInst($t, o)._defaults);
1142
+ });
1143
+ }
1144
+ }
1145
+ });
1146
+
1147
+ /*
1148
+ * Public Utility to parse date and time
1149
+ */
1150
+ $.datepicker.parseDateTime = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
1151
+ var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings);
1152
+ if (parseRes.timeObj) {
1153
+ var t = parseRes.timeObj;
1154
+ parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec);
1155
+ parseRes.date.setMicroseconds(t.microsec);
1156
+ }
1157
+
1158
+ return parseRes.date;
1159
+ };
1160
+
1161
+ /*
1162
+ * Public utility to parse time
1163
+ */
1164
+ $.datepicker.parseTime = function (timeFormat, timeString, options) {
1165
+ var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {}),
1166
+ iso8601 = (timeFormat.replace(/\'.*?\'/g, '').indexOf('Z') !== -1);
1167
+
1168
+ // Strict parse requires the timeString to match the timeFormat exactly
1169
+ var strictParse = function (f, s, o) {
1170
+
1171
+ // pattern for standard and localized AM/PM markers
1172
+ var getPatternAmpm = function (amNames, pmNames) {
1173
+ var markers = [];
1174
+ if (amNames) {
1175
+ $.merge(markers, amNames);
1176
+ }
1177
+ if (pmNames) {
1178
+ $.merge(markers, pmNames);
1179
+ }
1180
+ markers = $.map(markers, function (val) {
1181
+ return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&');
1182
+ });
1183
+ return '(' + markers.join('|') + ')?';
1184
+ };
1185
+
1186
+ // figure out position of time elements.. cause js cant do named captures
1187
+ var getFormatPositions = function (timeFormat) {
1188
+ var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|z|'.*?')/g),
1189
+ orders = {
1190
+ h: -1,
1191
+ m: -1,
1192
+ s: -1,
1193
+ l: -1,
1194
+ c: -1,
1195
+ t: -1,
1196
+ z: -1
1197
+ };
1198
+
1199
+ if (finds) {
1200
+ for (var i = 0; i < finds.length; i++) {
1201
+ if (orders[finds[i].toString().charAt(0)] === -1) {
1202
+ orders[finds[i].toString().charAt(0)] = i + 1;
1203
+ }
1204
+ }
1205
+ }
1206
+ return orders;
1207
+ };
1208
+
1209
+ var regstr = '^' + f.toString()
1210
+ .replace(/([hH]{1,2}|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) {
1211
+ var ml = match.length;
1212
+ switch (match.charAt(0).toLowerCase()) {
1213
+ case 'h':
1214
+ return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})';
1215
+ case 'm':
1216
+ return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})';
1217
+ case 's':
1218
+ return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})';
1219
+ case 'l':
1220
+ return '(\\d?\\d?\\d)';
1221
+ case 'c':
1222
+ return '(\\d?\\d?\\d)';
1223
+ case 'z':
1224
+ return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?';
1225
+ case 't':
1226
+ return getPatternAmpm(o.amNames, o.pmNames);
1227
+ default: // literal escaped in quotes
1228
+ return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?';
1229
+ }
1230
+ })
1231
+ .replace(/\s/g, '\\s?') +
1232
+ o.timeSuffix + '$',
1233
+ order = getFormatPositions(f),
1234
+ ampm = '',
1235
+ treg;
1236
+
1237
+ treg = s.match(new RegExp(regstr, 'i'));
1238
+
1239
+ var resTime = {
1240
+ hour: 0,
1241
+ minute: 0,
1242
+ second: 0,
1243
+ millisec: 0,
1244
+ microsec: 0
1245
+ };
1246
+
1247
+ if (treg) {
1248
+ if (order.t !== -1) {
1249
+ if (treg[order.t] === undefined || treg[order.t].length === 0) {
1250
+ ampm = '';
1251
+ resTime.ampm = '';
1252
+ } else {
1253
+ ampm = $.inArray(treg[order.t].toUpperCase(), $.map(o.amNames, function (x,i) { return x.toUpperCase(); })) !== -1 ? 'AM' : 'PM';
1254
+ resTime.ampm = o[ampm === 'AM' ? 'amNames' : 'pmNames'][0];
1255
+ }
1256
+ }
1257
+
1258
+ if (order.h !== -1) {
1259
+ if (ampm === 'AM' && treg[order.h] === '12') {
1260
+ resTime.hour = 0; // 12am = 0 hour
1261
+ } else {
1262
+ if (ampm === 'PM' && treg[order.h] !== '12') {
1263
+ resTime.hour = parseInt(treg[order.h], 10) + 12; // 12pm = 12 hour, any other pm = hour + 12
1264
+ } else {
1265
+ resTime.hour = Number(treg[order.h]);
1266
+ }
1267
+ }
1268
+ }
1269
+
1270
+ if (order.m !== -1) {
1271
+ resTime.minute = Number(treg[order.m]);
1272
+ }
1273
+ if (order.s !== -1) {
1274
+ resTime.second = Number(treg[order.s]);
1275
+ }
1276
+ if (order.l !== -1) {
1277
+ resTime.millisec = Number(treg[order.l]);
1278
+ }
1279
+ if (order.c !== -1) {
1280
+ resTime.microsec = Number(treg[order.c]);
1281
+ }
1282
+ if (order.z !== -1 && treg[order.z] !== undefined) {
1283
+ resTime.timezone = $.timepicker.timezoneOffsetNumber(treg[order.z]);
1284
+ }
1285
+
1286
+
1287
+ return resTime;
1288
+ }
1289
+ return false;
1290
+ };// end strictParse
1291
+
1292
+ // First try JS Date, if that fails, use strictParse
1293
+ var looseParse = function (f, s, o) {
1294
+ try {
1295
+ var d = new Date('2012-01-01 ' + s);
1296
+ if (isNaN(d.getTime())) {
1297
+ d = new Date('2012-01-01T' + s);
1298
+ if (isNaN(d.getTime())) {
1299
+ d = new Date('01/01/2012 ' + s);
1300
+ if (isNaN(d.getTime())) {
1301
+ throw "Unable to parse time with native Date: " + s;
1302
+ }
1303
+ }
1304
+ }
1305
+
1306
+ return {
1307
+ hour: d.getHours(),
1308
+ minute: d.getMinutes(),
1309
+ second: d.getSeconds(),
1310
+ millisec: d.getMilliseconds(),
1311
+ microsec: d.getMicroseconds(),
1312
+ timezone: d.getTimezoneOffset() * -1
1313
+ };
1314
+ }
1315
+ catch (err) {
1316
+ try {
1317
+ return strictParse(f, s, o);
1318
+ }
1319
+ catch (err2) {
1320
+ $.timepicker.log("Unable to parse \ntimeString: " + s + "\ntimeFormat: " + f);
1321
+ }
1322
+ }
1323
+ return false;
1324
+ }; // end looseParse
1325
+
1326
+ if (typeof o.parse === "function") {
1327
+ return o.parse(timeFormat, timeString, o);
1328
+ }
1329
+ if (o.parse === 'loose') {
1330
+ return looseParse(timeFormat, timeString, o);
1331
+ }
1332
+ return strictParse(timeFormat, timeString, o);
1333
+ };
1334
+
1335
+ /**
1336
+ * Public utility to format the time
1337
+ * @param {string} format format of the time
1338
+ * @param {Object} time Object not a Date for timezones
1339
+ * @param {Object} [options] essentially the regional[].. amNames, pmNames, ampm
1340
+ * @returns {string} the formatted time
1341
+ */
1342
+ $.datepicker.formatTime = function (format, time, options) {
1343
+ options = options || {};
1344
+ options = $.extend({}, $.timepicker._defaults, options);
1345
+ time = $.extend({
1346
+ hour: 0,
1347
+ minute: 0,
1348
+ second: 0,
1349
+ millisec: 0,
1350
+ microsec: 0,
1351
+ timezone: null
1352
+ }, time);
1353
+
1354
+ var tmptime = format,
1355
+ ampmName = options.amNames[0],
1356
+ hour = parseInt(time.hour, 10);
1357
+
1358
+ if (hour > 11) {
1359
+ ampmName = options.pmNames[0];
1360
+ }
1361
+
1362
+ tmptime = tmptime.replace(/(?:HH?|hh?|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) {
1363
+ switch (match) {
1364
+ case 'HH':
1365
+ return ('0' + hour).slice(-2);
1366
+ case 'H':
1367
+ return hour;
1368
+ case 'hh':
1369
+ return ('0' + convert24to12(hour)).slice(-2);
1370
+ case 'h':
1371
+ return convert24to12(hour);
1372
+ case 'mm':
1373
+ return ('0' + time.minute).slice(-2);
1374
+ case 'm':
1375
+ return time.minute;
1376
+ case 'ss':
1377
+ return ('0' + time.second).slice(-2);
1378
+ case 's':
1379
+ return time.second;
1380
+ case 'l':
1381
+ return ('00' + time.millisec).slice(-3);
1382
+ case 'c':
1383
+ return ('00' + time.microsec).slice(-3);
1384
+ case 'z':
1385
+ return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, false);
1386
+ case 'Z':
1387
+ return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, true);
1388
+ case 'T':
1389
+ return ampmName.charAt(0).toUpperCase();
1390
+ case 'TT':
1391
+ return ampmName.toUpperCase();
1392
+ case 't':
1393
+ return ampmName.charAt(0).toLowerCase();
1394
+ case 'tt':
1395
+ return ampmName.toLowerCase();
1396
+ default:
1397
+ return match.replace(/'/g, "");
1398
+ }
1399
+ });
1400
+
1401
+ return tmptime;
1402
+ };
1403
+
1404
+ /*
1405
+ * the bad hack :/ override datepicker so it doesn't close on select
1406
+ // inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378
1407
+ */
1408
+ $.datepicker._base_selectDate = $.datepicker._selectDate;
1409
+ $.datepicker._selectDate = function (id, dateStr) {
1410
+ var inst = this._getInst($(id)[0]),
1411
+ tp_inst = this._get(inst, 'timepicker'),
1412
+ was_inline;
1413
+
1414
+ if (tp_inst && inst.settings.showTimepicker) {
1415
+ tp_inst._limitMinMaxDateTime(inst, true);
1416
+ was_inline = inst.inline;
1417
+ inst.inline = inst.stay_open = true;
1418
+ //This way the onSelect handler called from calendarpicker get the full dateTime
1419
+ this._base_selectDate(id, dateStr);
1420
+ inst.inline = was_inline;
1421
+ inst.stay_open = false;
1422
+ this._notifyChange(inst);
1423
+ this._updateDatepicker(inst);
1424
+ } else {
1425
+ this._base_selectDate(id, dateStr);
1426
+ }
1427
+ };
1428
+
1429
+ /*
1430
+ * second bad hack :/ override datepicker so it triggers an event when changing the input field
1431
+ * and does not redraw the datepicker on every selectDate event
1432
+ */
1433
+ $.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker;
1434
+ $.datepicker._updateDatepicker = function (inst) {
1435
+
1436
+ // don't popup the datepicker if there is another instance already opened
1437
+ var input = inst.input[0];
1438
+ if ($.datepicker._curInst && $.datepicker._curInst !== inst && $.datepicker._datepickerShowing && $.datepicker._lastInput !== input) {
1439
+ return;
1440
+ }
1441
+
1442
+ if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) {
1443
+
1444
+ this._base_updateDatepicker(inst);
1445
+
1446
+ // Reload the time control when changing something in the input text field.
1447
+ var tp_inst = this._get(inst, 'timepicker');
1448
+ if (tp_inst) {
1449
+ tp_inst._addTimePicker(inst);
1450
+ }
1451
+ }
1452
+ };
1453
+
1454
+ /*
1455
+ * third bad hack :/ override datepicker so it allows spaces and colon in the input field
1456
+ */
1457
+ $.datepicker._base_doKeyPress = $.datepicker._doKeyPress;
1458
+ $.datepicker._doKeyPress = function (event) {
1459
+ var inst = $.datepicker._getInst(event.target),
1460
+ tp_inst = $.datepicker._get(inst, 'timepicker');
1461
+
1462
+ if (tp_inst) {
1463
+ if ($.datepicker._get(inst, 'constrainInput')) {
1464
+ var ampm = tp_inst.support.ampm,
1465
+ tz = tp_inst._defaults.showTimezone !== null ? tp_inst._defaults.showTimezone : tp_inst.support.timezone,
1466
+ dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')),
1467
+ datetimeChars = tp_inst._defaults.timeFormat.toString()
1468
+ .replace(/[hms]/g, '')
1469
+ .replace(/TT/g, ampm ? 'APM' : '')
1470
+ .replace(/Tt/g, ampm ? 'AaPpMm' : '')
1471
+ .replace(/tT/g, ampm ? 'AaPpMm' : '')
1472
+ .replace(/T/g, ampm ? 'AP' : '')
1473
+ .replace(/tt/g, ampm ? 'apm' : '')
1474
+ .replace(/t/g, ampm ? 'ap' : '') +
1475
+ " " + tp_inst._defaults.separator +
1476
+ tp_inst._defaults.timeSuffix +
1477
+ (tz ? tp_inst._defaults.timezoneList.join('') : '') +
1478
+ (tp_inst._defaults.amNames.join('')) + (tp_inst._defaults.pmNames.join('')) +
1479
+ dateChars,
1480
+ chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode);
1481
+ return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1);
1482
+ }
1483
+ }
1484
+
1485
+ return $.datepicker._base_doKeyPress(event);
1486
+ };
1487
+
1488
+ /*
1489
+ * Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField
1490
+ * Update any alternate field to synchronise with the main field.
1491
+ */
1492
+ $.datepicker._base_updateAlternate = $.datepicker._updateAlternate;
1493
+ $.datepicker._updateAlternate = function (inst) {
1494
+ var tp_inst = this._get(inst, 'timepicker');
1495
+ if (tp_inst) {
1496
+ var altField = tp_inst._defaults.altField;
1497
+ if (altField) { // update alternate field too
1498
+ var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat,
1499
+ date = this._getDate(inst),
1500
+ formatCfg = $.datepicker._getFormatConfig(inst),
1501
+ altFormattedDateTime = '',
1502
+ altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator,
1503
+ altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix,
1504
+ altTimeFormat = tp_inst._defaults.altTimeFormat !== null ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat;
1505
+
1506
+ altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix;
1507
+ if (!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly && date !== null) {
1508
+ if (tp_inst._defaults.altFormat) {
1509
+ altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, date, formatCfg) + altSeparator + altFormattedDateTime;
1510
+ }
1511
+ else {
1512
+ altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime;
1513
+ }
1514
+ }
1515
+ $(altField).val( inst.input.val() ? altFormattedDateTime : "");
1516
+ }
1517
+ }
1518
+ else {
1519
+ $.datepicker._base_updateAlternate(inst);
1520
+ }
1521
+ };
1522
+
1523
+ /*
1524
+ * Override key up event to sync manual input changes.
1525
+ */
1526
+ $.datepicker._base_doKeyUp = $.datepicker._doKeyUp;
1527
+ $.datepicker._doKeyUp = function (event) {
1528
+ var inst = $.datepicker._getInst(event.target),
1529
+ tp_inst = $.datepicker._get(inst, 'timepicker');
1530
+
1531
+ if (tp_inst) {
1532
+ if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) {
1533
+ try {
1534
+ $.datepicker._updateDatepicker(inst);
1535
+ } catch (err) {
1536
+ $.timepicker.log(err);
1537
+ }
1538
+ }
1539
+ }
1540
+
1541
+ return $.datepicker._base_doKeyUp(event);
1542
+ };
1543
+
1544
+ /*
1545
+ * override "Today" button to also grab the time.
1546
+ */
1547
+ $.datepicker._base_gotoToday = $.datepicker._gotoToday;
1548
+ $.datepicker._gotoToday = function (id) {
1549
+ var inst = this._getInst($(id)[0]),
1550
+ $dp = inst.dpDiv;
1551
+ var tp_inst = this._get(inst, 'timepicker');
1552
+ selectLocalTimezone(tp_inst);
1553
+ var now = new Date();
1554
+ this._setTime(inst, now);
1555
+ this._setDate(inst, now);
1556
+ this._base_gotoToday(id);
1557
+ };
1558
+
1559
+ /*
1560
+ * Disable & enable the Time in the datetimepicker
1561
+ */
1562
+ $.datepicker._disableTimepickerDatepicker = function (target) {
1563
+ var inst = this._getInst(target);
1564
+ if (!inst) {
1565
+ return;
1566
+ }
1567
+
1568
+ var tp_inst = this._get(inst, 'timepicker');
1569
+ $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
1570
+ if (tp_inst) {
1571
+ inst.settings.showTimepicker = false;
1572
+ tp_inst._defaults.showTimepicker = false;
1573
+ tp_inst._updateDateTime(inst);
1574
+ }
1575
+ };
1576
+
1577
+ $.datepicker._enableTimepickerDatepicker = function (target) {
1578
+ var inst = this._getInst(target);
1579
+ if (!inst) {
1580
+ return;
1581
+ }
1582
+
1583
+ var tp_inst = this._get(inst, 'timepicker');
1584
+ $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
1585
+ if (tp_inst) {
1586
+ inst.settings.showTimepicker = true;
1587
+ tp_inst._defaults.showTimepicker = true;
1588
+ tp_inst._addTimePicker(inst); // Could be disabled on page load
1589
+ tp_inst._updateDateTime(inst);
1590
+ }
1591
+ };
1592
+
1593
+ /*
1594
+ * Create our own set time function
1595
+ */
1596
+ $.datepicker._setTime = function (inst, date) {
1597
+ var tp_inst = this._get(inst, 'timepicker');
1598
+ if (tp_inst) {
1599
+ var defaults = tp_inst._defaults;
1600
+
1601
+ // calling _setTime with no date sets time to defaults
1602
+ tp_inst.hour = date ? date.getHours() : defaults.hour;
1603
+ tp_inst.minute = date ? date.getMinutes() : defaults.minute;
1604
+ tp_inst.second = date ? date.getSeconds() : defaults.second;
1605
+ tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec;
1606
+ tp_inst.microsec = date ? date.getMicroseconds() : defaults.microsec;
1607
+
1608
+ //check if within min/max times..
1609
+ tp_inst._limitMinMaxDateTime(inst, true);
1610
+
1611
+ tp_inst._onTimeChange();
1612
+ tp_inst._updateDateTime(inst);
1613
+ }
1614
+ };
1615
+
1616
+ /*
1617
+ * Create new public method to set only time, callable as $().datepicker('setTime', date)
1618
+ */
1619
+ $.datepicker._setTimeDatepicker = function (target, date, withDate) {
1620
+ var inst = this._getInst(target);
1621
+ if (!inst) {
1622
+ return;
1623
+ }
1624
+
1625
+ var tp_inst = this._get(inst, 'timepicker');
1626
+
1627
+ if (tp_inst) {
1628
+ this._setDateFromField(inst);
1629
+ var tp_date;
1630
+ if (date) {
1631
+ if (typeof date === "string") {
1632
+ tp_inst._parseTime(date, withDate);
1633
+ tp_date = new Date();
1634
+ tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
1635
+ tp_date.setMicroseconds(tp_inst.microsec);
1636
+ } else {
1637
+ tp_date = new Date(date.getTime());
1638
+ tp_date.setMicroseconds(date.getMicroseconds());
1639
+ }
1640
+ if (tp_date.toString() === 'Invalid Date') {
1641
+ tp_date = undefined;
1642
+ }
1643
+ this._setTime(inst, tp_date);
1644
+ }
1645
+ }
1646
+
1647
+ };
1648
+
1649
+ /*
1650
+ * override setDate() to allow setting time too within Date object
1651
+ */
1652
+ $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker;
1653
+ $.datepicker._setDateDatepicker = function (target, _date) {
1654
+ var inst = this._getInst(target);
1655
+ var date = _date;
1656
+ if (!inst) {
1657
+ return;
1658
+ }
1659
+
1660
+ if (typeof(_date) === 'string') {
1661
+ date = new Date(_date);
1662
+ if (!date.getTime()) {
1663
+ this._base_setDateDatepicker.apply(this, arguments);
1664
+ date = $(target).datepicker('getDate');
1665
+ }
1666
+ }
1667
+
1668
+ var tp_inst = this._get(inst, 'timepicker');
1669
+ var tp_date;
1670
+ if (date instanceof Date) {
1671
+ tp_date = new Date(date.getTime());
1672
+ tp_date.setMicroseconds(date.getMicroseconds());
1673
+ } else {
1674
+ tp_date = date;
1675
+ }
1676
+
1677
+ // This is important if you are using the timezone option, javascript's Date
1678
+ // object will only return the timezone offset for the current locale, so we
1679
+ // adjust it accordingly. If not using timezone option this won't matter..
1680
+ // If a timezone is different in tp, keep the timezone as is
1681
+ if (tp_inst && tp_date) {
1682
+ // look out for DST if tz wasn't specified
1683
+ if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) {
1684
+ tp_inst.timezone = tp_date.getTimezoneOffset() * -1;
1685
+ }
1686
+ date = $.timepicker.timezoneAdjust(date, tp_inst.timezone);
1687
+ tp_date = $.timepicker.timezoneAdjust(tp_date, tp_inst.timezone);
1688
+ }
1689
+
1690
+ this._updateDatepicker(inst);
1691
+ this._base_setDateDatepicker.apply(this, arguments);
1692
+ this._setTimeDatepicker(target, tp_date, true);
1693
+ };
1694
+
1695
+ /*
1696
+ * override getDate() to allow getting time too within Date object
1697
+ */
1698
+ $.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker;
1699
+ $.datepicker._getDateDatepicker = function (target, noDefault) {
1700
+ var inst = this._getInst(target);
1701
+ if (!inst) {
1702
+ return;
1703
+ }
1704
+
1705
+ var tp_inst = this._get(inst, 'timepicker');
1706
+
1707
+ if (tp_inst) {
1708
+ // if it hasn't yet been defined, grab from field
1709
+ if (inst.lastVal === undefined) {
1710
+ this._setDateFromField(inst, noDefault);
1711
+ }
1712
+
1713
+ var date = this._getDate(inst);
1714
+ var currDT = $.trim((tp_inst.$altInput && tp_inst._defaults.altFieldTimeOnly) ? tp_inst.$input.val() + ' ' + tp_inst.$altInput.val() : tp_inst.$input.val());
1715
+ if (date && tp_inst._parseTime(currDT, !inst.settings.timeOnly)) {
1716
+ date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
1717
+ date.setMicroseconds(tp_inst.microsec);
1718
+
1719
+ // This is important if you are using the timezone option, javascript's Date
1720
+ // object will only return the timezone offset for the current locale, so we
1721
+ // adjust it accordingly. If not using timezone option this won't matter..
1722
+ if (tp_inst.timezone != null) {
1723
+ // look out for DST if tz wasn't specified
1724
+ if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) {
1725
+ tp_inst.timezone = date.getTimezoneOffset() * -1;
1726
+ }
1727
+ date = $.timepicker.timezoneAdjust(date, tp_inst.timezone);
1728
+ }
1729
+ }
1730
+ return date;
1731
+ }
1732
+ return this._base_getDateDatepicker(target, noDefault);
1733
+ };
1734
+
1735
+ /*
1736
+ * override parseDate() because UI 1.8.14 throws an error about "Extra characters"
1737
+ * An option in datapicker to ignore extra format characters would be nicer.
1738
+ */
1739
+ $.datepicker._base_parseDate = $.datepicker.parseDate;
1740
+ $.datepicker.parseDate = function (format, value, settings) {
1741
+ var date;
1742
+ try {
1743
+ date = this._base_parseDate(format, value, settings);
1744
+ } catch (err) {
1745
+ // Hack! The error message ends with a colon, a space, and
1746
+ // the "extra" characters. We rely on that instead of
1747
+ // attempting to perfectly reproduce the parsing algorithm.
1748
+ if (err.indexOf(":") >= 0) {
1749
+ date = this._base_parseDate(format, value.substring(0, value.length - (err.length - err.indexOf(':') - 2)), settings);
1750
+ $.timepicker.log("Error parsing the date string: " + err + "\ndate string = " + value + "\ndate format = " + format);
1751
+ } else {
1752
+ throw err;
1753
+ }
1754
+ }
1755
+ return date;
1756
+ };
1757
+
1758
+ /*
1759
+ * override formatDate to set date with time to the input
1760
+ */
1761
+ $.datepicker._base_formatDate = $.datepicker._formatDate;
1762
+ $.datepicker._formatDate = function (inst, day, month, year) {
1763
+ var tp_inst = this._get(inst, 'timepicker');
1764
+ if (tp_inst) {
1765
+ tp_inst._updateDateTime(inst);
1766
+ return tp_inst.$input.val();
1767
+ }
1768
+ return this._base_formatDate(inst);
1769
+ };
1770
+
1771
+ /*
1772
+ * override options setter to add time to maxDate(Time) and minDate(Time). MaxDate
1773
+ */
1774
+ $.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker;
1775
+ $.datepicker._optionDatepicker = function (target, name, value) {
1776
+ var inst = this._getInst(target),
1777
+ name_clone;
1778
+ if (!inst) {
1779
+ return null;
1780
+ }
1781
+
1782
+ var tp_inst = this._get(inst, 'timepicker');
1783
+ if (tp_inst) {
1784
+ var min = null,
1785
+ max = null,
1786
+ onselect = null,
1787
+ overrides = tp_inst._defaults.evnts,
1788
+ fns = {},
1789
+ prop,
1790
+ ret,
1791
+ oldVal,
1792
+ $target;
1793
+ if (typeof name === 'string') { // if min/max was set with the string
1794
+ if (name === 'minDate' || name === 'minDateTime') {
1795
+ min = value;
1796
+ } else if (name === 'maxDate' || name === 'maxDateTime') {
1797
+ max = value;
1798
+ } else if (name === 'onSelect') {
1799
+ onselect = value;
1800
+ } else if (overrides.hasOwnProperty(name)) {
1801
+ if (typeof (value) === 'undefined') {
1802
+ return overrides[name];
1803
+ }
1804
+ fns[name] = value;
1805
+ name_clone = {}; //empty results in exiting function after overrides updated
1806
+ }
1807
+ } else if (typeof name === 'object') { //if min/max was set with the JSON
1808
+ if (name.minDate) {
1809
+ min = name.minDate;
1810
+ } else if (name.minDateTime) {
1811
+ min = name.minDateTime;
1812
+ } else if (name.maxDate) {
1813
+ max = name.maxDate;
1814
+ } else if (name.maxDateTime) {
1815
+ max = name.maxDateTime;
1816
+ }
1817
+ for (prop in overrides) {
1818
+ if (overrides.hasOwnProperty(prop) && name[prop]) {
1819
+ fns[prop] = name[prop];
1820
+ }
1821
+ }
1822
+ }
1823
+ for (prop in fns) {
1824
+ if (fns.hasOwnProperty(prop)) {
1825
+ overrides[prop] = fns[prop];
1826
+ if (!name_clone) { name_clone = $.extend({}, name); }
1827
+ delete name_clone[prop];
1828
+ }
1829
+ }
1830
+ if (name_clone && isEmptyObject(name_clone)) { return; }
1831
+ if (min) { //if min was set
1832
+ if (min === 0) {
1833
+ min = new Date();
1834
+ } else {
1835
+ min = new Date(min);
1836
+ }
1837
+ tp_inst._defaults.minDate = min;
1838
+ tp_inst._defaults.minDateTime = min;
1839
+ } else if (max) { //if max was set
1840
+ if (max === 0) {
1841
+ max = new Date();
1842
+ } else {
1843
+ max = new Date(max);
1844
+ }
1845
+ tp_inst._defaults.maxDate = max;
1846
+ tp_inst._defaults.maxDateTime = max;
1847
+ } else if (onselect) {
1848
+ tp_inst._defaults.onSelect = onselect;
1849
+ }
1850
+
1851
+ // Datepicker will override our date when we call _base_optionDatepicker when
1852
+ // calling minDate/maxDate, so we will first grab the value, call
1853
+ // _base_optionDatepicker, then set our value back.
1854
+ if(min || max){
1855
+ $target = $(target);
1856
+ oldVal = $target.datetimepicker('getDate');
1857
+ ret = this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value);
1858
+ $target.datetimepicker('setDate', oldVal);
1859
+ return ret;
1860
+ }
1861
+ }
1862
+ if (value === undefined) {
1863
+ return this._base_optionDatepicker.call($.datepicker, target, name);
1864
+ }
1865
+ return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value);
1866
+ };
1867
+
1868
+ /*
1869
+ * jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype,
1870
+ * it will return false for all objects
1871
+ */
1872
+ var isEmptyObject = function (obj) {
1873
+ var prop;
1874
+ for (prop in obj) {
1875
+ if (obj.hasOwnProperty(prop)) {
1876
+ return false;
1877
+ }
1878
+ }
1879
+ return true;
1880
+ };
1881
+
1882
+ /*
1883
+ * jQuery extend now ignores nulls!
1884
+ */
1885
+ var extendRemove = function (target, props) {
1886
+ $.extend(target, props);
1887
+ for (var name in props) {
1888
+ if (props[name] === null || props[name] === undefined) {
1889
+ target[name] = props[name];
1890
+ }
1891
+ }
1892
+ return target;
1893
+ };
1894
+
1895
+ /*
1896
+ * Determine by the time format which units are supported
1897
+ * Returns an object of booleans for each unit
1898
+ */
1899
+ var detectSupport = function (timeFormat) {
1900
+ var tf = timeFormat.replace(/'.*?'/g, '').toLowerCase(), // removes literals
1901
+ isIn = function (f, t) { // does the format contain the token?
1902
+ return f.indexOf(t) !== -1 ? true : false;
1903
+ };
1904
+ return {
1905
+ hour: isIn(tf, 'h'),
1906
+ minute: isIn(tf, 'm'),
1907
+ second: isIn(tf, 's'),
1908
+ millisec: isIn(tf, 'l'),
1909
+ microsec: isIn(tf, 'c'),
1910
+ timezone: isIn(tf, 'z'),
1911
+ ampm: isIn(tf, 't') && isIn(timeFormat, 'h'),
1912
+ iso8601: isIn(timeFormat, 'Z')
1913
+ };
1914
+ };
1915
+
1916
+ /*
1917
+ * Converts 24 hour format into 12 hour
1918
+ * Returns 12 hour without leading 0
1919
+ */
1920
+ var convert24to12 = function (hour) {
1921
+ hour %= 12;
1922
+
1923
+ if (hour === 0) {
1924
+ hour = 12;
1925
+ }
1926
+
1927
+ return String(hour);
1928
+ };
1929
+
1930
+ var computeEffectiveSetting = function (settings, property) {
1931
+ return settings && settings[property] ? settings[property] : $.timepicker._defaults[property];
1932
+ };
1933
+
1934
+ /*
1935
+ * Splits datetime string into date and time substrings.
1936
+ * Throws exception when date can't be parsed
1937
+ * Returns {dateString: dateString, timeString: timeString}
1938
+ */
1939
+ var splitDateTime = function (dateTimeString, timeSettings) {
1940
+ // The idea is to get the number separator occurrences in datetime and the time format requested (since time has
1941
+ // fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split.
1942
+ var separator = computeEffectiveSetting(timeSettings, 'separator'),
1943
+ format = computeEffectiveSetting(timeSettings, 'timeFormat'),
1944
+ timeParts = format.split(separator), // how many occurrences of separator may be in our format?
1945
+ timePartsLen = timeParts.length,
1946
+ allParts = dateTimeString.split(separator),
1947
+ allPartsLen = allParts.length;
1948
+
1949
+ if (allPartsLen > 1) {
1950
+ return {
1951
+ dateString: allParts.splice(0, allPartsLen - timePartsLen).join(separator),
1952
+ timeString: allParts.splice(0, timePartsLen).join(separator)
1953
+ };
1954
+ }
1955
+
1956
+ return {
1957
+ dateString: dateTimeString,
1958
+ timeString: ''
1959
+ };
1960
+ };
1961
+
1962
+ /*
1963
+ * Internal function to parse datetime interval
1964
+ * Returns: {date: Date, timeObj: Object}, where
1965
+ * date - parsed date without time (type Date)
1966
+ * timeObj = {hour: , minute: , second: , millisec: , microsec: } - parsed time. Optional
1967
+ */
1968
+ var parseDateTimeInternal = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
1969
+ var date,
1970
+ parts,
1971
+ parsedTime;
1972
+
1973
+ parts = splitDateTime(dateTimeString, timeSettings);
1974
+ date = $.datepicker._base_parseDate(dateFormat, parts.dateString, dateSettings);
1975
+
1976
+ if (parts.timeString === '') {
1977
+ return {
1978
+ date: date
1979
+ };
1980
+ }
1981
+
1982
+ parsedTime = $.datepicker.parseTime(timeFormat, parts.timeString, timeSettings);
1983
+
1984
+ if (!parsedTime) {
1985
+ throw 'Wrong time format';
1986
+ }
1987
+
1988
+ return {
1989
+ date: date,
1990
+ timeObj: parsedTime
1991
+ };
1992
+ };
1993
+
1994
+ /*
1995
+ * Internal function to set timezone_select to the local timezone
1996
+ */
1997
+ var selectLocalTimezone = function (tp_inst, date) {
1998
+ if (tp_inst && tp_inst.timezone_select) {
1999
+ var now = date || new Date();
2000
+ tp_inst.timezone_select.val(-now.getTimezoneOffset());
2001
+ }
2002
+ };
2003
+
2004
+ /*
2005
+ * Create a Singleton Instance
2006
+ */
2007
+ $.timepicker = new Timepicker();
2008
+
2009
+ /**
2010
+ * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5)
2011
+ * @param {number} tzMinutes if not a number, less than -720 (-1200), or greater than 840 (+1400) this value is returned
2012
+ * @param {boolean} iso8601 if true formats in accordance to iso8601 "+12:45"
2013
+ * @return {string}
2014
+ */
2015
+ $.timepicker.timezoneOffsetString = function (tzMinutes, iso8601) {
2016
+ if (isNaN(tzMinutes) || tzMinutes > 840 || tzMinutes < -720) {
2017
+ return tzMinutes;
2018
+ }
2019
+
2020
+ var off = tzMinutes,
2021
+ minutes = off % 60,
2022
+ hours = (off - minutes) / 60,
2023
+ iso = iso8601 ? ':' : '',
2024
+ tz = (off >= 0 ? '+' : '-') + ('0' + Math.abs(hours)).slice(-2) + iso + ('0' + Math.abs(minutes)).slice(-2);
2025
+
2026
+ if (tz === '+00:00') {
2027
+ return 'Z';
2028
+ }
2029
+ return tz;
2030
+ };
2031
+
2032
+ /**
2033
+ * Get the number in minutes that represents a timezone string
2034
+ * @param {string} tzString formatted like "+0500", "-1245", "Z"
2035
+ * @return {number} the offset minutes or the original string if it doesn't match expectations
2036
+ */
2037
+ $.timepicker.timezoneOffsetNumber = function (tzString) {
2038
+ var normalized = tzString.toString().replace(':', ''); // excuse any iso8601, end up with "+1245"
2039
+
2040
+ if (normalized.toUpperCase() === 'Z') { // if iso8601 with Z, its 0 minute offset
2041
+ return 0;
2042
+ }
2043
+
2044
+ if (!/^(\-|\+)\d{4}$/.test(normalized)) { // possibly a user defined tz, so just give it back
2045
+ return tzString;
2046
+ }
2047
+
2048
+ return ((normalized.substr(0, 1) === '-' ? -1 : 1) * // plus or minus
2049
+ ((parseInt(normalized.substr(1, 2), 10) * 60) + // hours (converted to minutes)
2050
+ parseInt(normalized.substr(3, 2), 10))); // minutes
2051
+ };
2052
+
2053
+ /**
2054
+ * No way to set timezone in js Date, so we must adjust the minutes to compensate. (think setDate, getDate)
2055
+ * @param {Date} date
2056
+ * @param {string} toTimezone formatted like "+0500", "-1245"
2057
+ * @return {Date}
2058
+ */
2059
+ $.timepicker.timezoneAdjust = function (date, toTimezone) {
2060
+ var toTz = $.timepicker.timezoneOffsetNumber(toTimezone);
2061
+ if (!isNaN(toTz)) {
2062
+ date.setMinutes(date.getMinutes() + -date.getTimezoneOffset() - toTz);
2063
+ }
2064
+ return date;
2065
+ };
2066
+
2067
+ /**
2068
+ * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to
2069
+ * enforce date range limits.
2070
+ * n.b. The input value must be correctly formatted (reformatting is not supported)
2071
+ * @param {Element} startTime
2072
+ * @param {Element} endTime
2073
+ * @param {Object} options Options for the timepicker() call
2074
+ * @return {jQuery}
2075
+ */
2076
+ $.timepicker.timeRange = function (startTime, endTime, options) {
2077
+ return $.timepicker.handleRange('timepicker', startTime, endTime, options);
2078
+ };
2079
+
2080
+ /**
2081
+ * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to
2082
+ * enforce date range limits.
2083
+ * @param {Element} startTime
2084
+ * @param {Element} endTime
2085
+ * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`,
2086
+ * a boolean value that can be used to reformat the input values to the `dateFormat`.
2087
+ * @param {string} method Can be used to specify the type of picker to be added
2088
+ * @return {jQuery}
2089
+ */
2090
+ $.timepicker.datetimeRange = function (startTime, endTime, options) {
2091
+ $.timepicker.handleRange('datetimepicker', startTime, endTime, options);
2092
+ };
2093
+
2094
+ /**
2095
+ * Calls `datepicker` on the `startTime` and `endTime` elements, and configures them to
2096
+ * enforce date range limits.
2097
+ * @param {Element} startTime
2098
+ * @param {Element} endTime
2099
+ * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`,
2100
+ * a boolean value that can be used to reformat the input values to the `dateFormat`.
2101
+ * @return {jQuery}
2102
+ */
2103
+ $.timepicker.dateRange = function (startTime, endTime, options) {
2104
+ $.timepicker.handleRange('datepicker', startTime, endTime, options);
2105
+ };
2106
+
2107
+ /**
2108
+ * Calls `method` on the `startTime` and `endTime` elements, and configures them to
2109
+ * enforce date range limits.
2110
+ * @param {string} method Can be used to specify the type of picker to be added
2111
+ * @param {Element} startTime
2112
+ * @param {Element} endTime
2113
+ * @param {Object} options Options for the `timepicker()` call. Also supports `reformat`,
2114
+ * a boolean value that can be used to reformat the input values to the `dateFormat`.
2115
+ * @return {jQuery}
2116
+ */
2117
+ $.timepicker.handleRange = function (method, startTime, endTime, options) {
2118
+ options = $.extend({}, {
2119
+ minInterval: 0, // min allowed interval in milliseconds
2120
+ maxInterval: 0, // max allowed interval in milliseconds
2121
+ start: {}, // options for start picker
2122
+ end: {} // options for end picker
2123
+ }, options);
2124
+
2125
+ // for the mean time this fixes an issue with calling getDate with timepicker()
2126
+ var timeOnly = false;
2127
+ if(method === 'timepicker'){
2128
+ timeOnly = true;
2129
+ method = 'datetimepicker';
2130
+ }
2131
+
2132
+ function checkDates(changed, other) {
2133
+ var startdt = startTime[method]('getDate'),
2134
+ enddt = endTime[method]('getDate'),
2135
+ changeddt = changed[method]('getDate');
2136
+
2137
+ if (startdt !== null) {
2138
+ var minDate = new Date(startdt.getTime()),
2139
+ maxDate = new Date(startdt.getTime());
2140
+
2141
+ minDate.setMilliseconds(minDate.getMilliseconds() + options.minInterval);
2142
+ maxDate.setMilliseconds(maxDate.getMilliseconds() + options.maxInterval);
2143
+
2144
+ if (options.minInterval > 0 && minDate > enddt) { // minInterval check
2145
+ endTime[method]('setDate', minDate);
2146
+ }
2147
+ else if (options.maxInterval > 0 && maxDate < enddt) { // max interval check
2148
+ endTime[method]('setDate', maxDate);
2149
+ }
2150
+ else if (startdt > enddt) {
2151
+ other[method]('setDate', changeddt);
2152
+ }
2153
+ }
2154
+ }
2155
+
2156
+ function selected(changed, other, option) {
2157
+ if (!changed.val()) {
2158
+ return;
2159
+ }
2160
+ var date = changed[method].call(changed, 'getDate');
2161
+ if (date !== null && options.minInterval > 0) {
2162
+ if (option === 'minDate') {
2163
+ date.setMilliseconds(date.getMilliseconds() + options.minInterval);
2164
+ }
2165
+ if (option === 'maxDate') {
2166
+ date.setMilliseconds(date.getMilliseconds() - options.minInterval);
2167
+ }
2168
+ }
2169
+
2170
+ if (date.getTime) {
2171
+ other[method].call(other, 'option', option, date);
2172
+ }
2173
+ }
2174
+
2175
+ $.fn[method].call(startTime, $.extend({
2176
+ timeOnly: timeOnly,
2177
+ onClose: function (dateText, inst) {
2178
+ checkDates($(this), endTime);
2179
+ },
2180
+ onSelect: function (selectedDateTime) {
2181
+ selected($(this), endTime, 'minDate');
2182
+ }
2183
+ }, options, options.start));
2184
+ $.fn[method].call(endTime, $.extend({
2185
+ timeOnly: timeOnly,
2186
+ onClose: function (dateText, inst) {
2187
+ checkDates($(this), startTime);
2188
+ },
2189
+ onSelect: function (selectedDateTime) {
2190
+ selected($(this), startTime, 'maxDate');
2191
+ }
2192
+ }, options, options.end));
2193
+
2194
+ checkDates(startTime, endTime);
2195
+
2196
+ selected(startTime, endTime, 'minDate');
2197
+ selected(endTime, startTime, 'maxDate');
2198
+
2199
+ return $([startTime.get(0), endTime.get(0)]);
2200
+ };
2201
+
2202
+ /**
2203
+ * Log error or data to the console during error or debugging
2204
+ * @param {Object} err pass any type object to log to the console during error or debugging
2205
+ * @return {void}
2206
+ */
2207
+ $.timepicker.log = function () {
2208
+ if (window.console) {
2209
+ window.console.log.apply(window.console, Array.prototype.slice.call(arguments));
2210
+ }
2211
+ };
2212
+
2213
+ /*
2214
+ * Add util object to allow access to private methods for testability.
2215
+ */
2216
+ $.timepicker._util = {
2217
+ _extendRemove: extendRemove,
2218
+ _isEmptyObject: isEmptyObject,
2219
+ _convert24to12: convert24to12,
2220
+ _detectSupport: detectSupport,
2221
+ _selectLocalTimezone: selectLocalTimezone,
2222
+ _computeEffectiveSetting: computeEffectiveSetting,
2223
+ _splitDateTime: splitDateTime,
2224
+ _parseDateTimeInternal: parseDateTimeInternal
2225
+ };
2226
+
2227
+ /*
2228
+ * Microsecond support
2229
+ */
2230
+ if (!Date.prototype.getMicroseconds) {
2231
+ Date.prototype.microseconds = 0;
2232
+ Date.prototype.getMicroseconds = function () { return this.microseconds; };
2233
+ Date.prototype.setMicroseconds = function (m) {
2234
+ this.setMilliseconds(this.getMilliseconds() + Math.floor(m / 1000));
2235
+ this.microseconds = m % 1000;
2236
+ return this;
2237
+ };
2238
+ }
2239
+
2240
+ /*
2241
+ * Keep up with the version
2242
+ */
2243
+ $.timepicker.version = "1.5.3";
2244
+
2245
+ }));
js/knockout-3.3.0.js ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Knockout JavaScript library v3.3.0
3
+ * (c) Steven Sanderson - http://knockoutjs.com/
4
+ * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
+ */
6
+
7
+ (function() {(function(p){var y=this||(0,eval)("this"),w=y.document,M=y.navigator,u=y.jQuery,E=y.JSON;(function(p){"function"===typeof define&&define.amd?define(["exports","require"],p):"function"===typeof require&&"object"===typeof exports&&"object"===typeof module?p(module.exports||exports):p(y.ko={})})(function(N,O){function J(a,d){return null===a||typeof a in Q?a===d:!1}function R(a,d){var c;return function(){c||(c=setTimeout(function(){c=p;a()},d))}}function S(a,d){var c;return function(){clearTimeout(c);
8
+ c=setTimeout(a,d)}}function K(b,d,c,e){a.d[b]={init:function(b,k,h,l,g){var m,x;a.w(function(){var q=a.a.c(k()),n=!c!==!q,r=!x;if(r||d||n!==m)r&&a.Z.oa()&&(x=a.a.la(a.e.childNodes(b),!0)),n?(r||a.e.T(b,a.a.la(x)),a.Ja(e?e(g,q):g,b)):a.e.ma(b),m=n},null,{q:b});return{controlsDescendantBindings:!0}}};a.h.ka[b]=!1;a.e.R[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,d){for(var c=b.split("."),e=a,f=0;f<c.length-1;f++)e=e[c[f]];e[c[c.length-1]]=d};a.D=function(a,d,c){a[d]=c};a.version="3.3.0";
9
+ a.b("version",a.version);a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function d(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function c(a,b){a.__proto__=b;return a}function e(b,c,g,d){var e=b[c].match(m)||[];a.a.o(g.match(m),function(b){a.a.ga(e,b,d)});b[c]=e.join(" ")}var f={__proto__:[]}instanceof Array,k={},h={};k[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];k.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");
10
+ b(k,function(a,b){if(b.length)for(var c=0,g=b.length;c<g;c++)h[b[c]]=a});var l={propertychange:!0},g=w&&function(){for(var a=3,b=w.createElement("div"),c=b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:p}(),m=/\S+/g;return{Bb:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],o:function(a,b){for(var c=0,g=a.length;c<g;c++)b(a[c],c)},m:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,
11
+ b);for(var c=0,g=a.length;c<g;c++)if(a[c]===b)return c;return-1},vb:function(a,b,c){for(var g=0,d=a.length;g<d;g++)if(b.call(c,a[g],g))return a[g];return null},ya:function(b,c){var g=a.a.m(b,c);0<g?b.splice(g,1):0===g&&b.shift()},wb:function(b){b=b||[];for(var c=[],g=0,d=b.length;g<d;g++)0>a.a.m(c,b[g])&&c.push(b[g]);return c},Ka:function(a,b){a=a||[];for(var c=[],g=0,d=a.length;g<d;g++)c.push(b(a[g],g));return c},xa:function(a,b){a=a||[];for(var c=[],g=0,d=a.length;g<d;g++)b(a[g],g)&&c.push(a[g]);
12
+ return c},ia:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,g=b.length;c<g;c++)a.push(b[c]);return a},ga:function(b,c,g){var d=a.a.m(a.a.cb(b),c);0>d?g&&b.push(c):g||b.splice(d,1)},za:f,extend:d,Fa:c,Ga:f?c:d,A:b,pa:function(a,b){if(!a)return a;var c={},g;for(g in a)a.hasOwnProperty(g)&&(c[g]=b(a[g],g,a));return c},Ra:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},Jb:function(b){b=a.a.O(b);for(var c=(b[0]&&b[0].ownerDocument||w).createElement("div"),g=0,d=b.length;g<
13
+ d;g++)c.appendChild(a.S(b[g]));return c},la:function(b,c){for(var g=0,d=b.length,e=[];g<d;g++){var m=b[g].cloneNode(!0);e.push(c?a.S(m):m)}return e},T:function(b,c){a.a.Ra(b);if(c)for(var g=0,d=c.length;g<d;g++)b.appendChild(c[g])},Qb:function(b,c){var g=b.nodeType?[b]:b;if(0<g.length){for(var d=g[0],e=d.parentNode,m=0,f=c.length;m<f;m++)e.insertBefore(c[m],d);m=0;for(f=g.length;m<f;m++)a.removeNode(g[m])}},na:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==
14
+ b;)a.splice(0,1);if(1<a.length){var c=a[0],g=a[a.length-1];for(a.length=0;c!==g;)if(a.push(c),c=c.nextSibling,!c)return;a.push(g)}}return a},Sb:function(a,b){7>g?a.setAttribute("selected",b):a.selected=b},ib:function(a){return null===a||a===p?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},Dc:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},jc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?
15
+ a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=b;)a=a.parentNode;return!!a},Qa:function(b){return a.a.jc(b,b.ownerDocument.documentElement)},tb:function(b){return!!a.a.vb(b,a.a.Qa)},v:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},n:function(b,c,d){var m=g&&l[c];if(!m&&u)u(b).bind(c,d);else if(m||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var e=function(a){d.call(b,a)},f="on"+c;b.attachEvent(f,e);a.a.C.fa(b,
16
+ function(){b.detachEvent(f,e)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,d,!1)},qa:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var g;"input"===a.a.v(b)&&b.type&&"click"==c.toLowerCase()?(g=b.type,g="checkbox"==g||"radio"==g):g=!1;if(u&&!g)u(b).trigger(c);else if("function"==typeof w.createEvent)if("function"==typeof b.dispatchEvent)g=w.createEvent(h[c]||"HTMLEvents"),g.initEvent(c,
17
+ !0,!0,y,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(g);else throw Error("The supplied element doesn't support dispatchEvent");else if(g&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");},c:function(b){return a.F(b)?b():b},cb:function(b){return a.F(b)?b.B():b},Ia:function(b,c,g){var d;c&&("object"===typeof b.classList?(d=b.classList[g?"add":"remove"],a.a.o(c.match(m),function(a){d.call(b.classList,a)})):"string"===
18
+ typeof b.className.baseVal?e(b.className,"baseVal",c,g):e(b,"className",c,g))},Ha:function(b,c){var g=a.a.c(c);if(null===g||g===p)g="";var d=a.e.firstChild(b);!d||3!=d.nodeType||a.e.nextSibling(d)?a.e.T(b,[b.ownerDocument.createTextNode(g)]):d.data=g;a.a.mc(b)},Rb:function(a,b){a.name=b;if(7>=g)try{a.mergeAttributes(w.createElement("<input name='"+a.name+"'/>"),!1)}catch(c){}},mc:function(a){9<=g&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},kc:function(a){if(g){var b=a.style.width;
19
+ a.style.width=0;a.style.width=b}},Bc:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var g=[],d=b;d<=c;d++)g.push(d);return g},O:function(a){for(var b=[],c=0,g=a.length;c<g;c++)b.push(a[c]);return b},Hc:6===g,Ic:7===g,M:g,Db:function(b,c){for(var g=a.a.O(b.getElementsByTagName("input")).concat(a.a.O(b.getElementsByTagName("textarea"))),d="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},m=[],e=g.length-1;0<=e;e--)d(g[e])&&m.push(g[e]);return m},yc:function(b){return"string"==
20
+ typeof b&&(b=a.a.ib(b))?E&&E.parse?E.parse(b):(new Function("return "+b))():null},jb:function(b,c,g){if(!E||!E.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");return E.stringify(a.a.c(b),c,g)},zc:function(c,g,d){d=d||{};var m=d.params||{},e=d.includeFields||this.Bb,f=c;if("object"==typeof c&&"form"===a.a.v(c))for(var f=c.action,
21
+ l=e.length-1;0<=l;l--)for(var k=a.a.Db(c,e[l]),h=k.length-1;0<=h;h--)m[k[h].name]=k[h].value;g=a.a.c(g);var s=w.createElement("form");s.style.display="none";s.action=f;s.method="post";for(var p in g)c=w.createElement("input"),c.type="hidden",c.name=p,c.value=a.a.jb(a.a.c(g[p])),s.appendChild(c);b(m,function(a,b){var c=w.createElement("input");c.type="hidden";c.name=a;c.value=b;s.appendChild(c)});w.body.appendChild(s);d.submitter?d.submitter(s):s.submit();setTimeout(function(){s.parentNode.removeChild(s)},
22
+ 0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.o);a.b("utils.arrayFirst",a.a.vb);a.b("utils.arrayFilter",a.a.xa);a.b("utils.arrayGetDistinctValues",a.a.wb);a.b("utils.arrayIndexOf",a.a.m);a.b("utils.arrayMap",a.a.Ka);a.b("utils.arrayPushAll",a.a.ia);a.b("utils.arrayRemoveItem",a.a.ya);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",a.a.Bb);a.b("utils.getFormFields",a.a.Db);a.b("utils.peekObservable",a.a.cb);a.b("utils.postJson",a.a.zc);a.b("utils.parseJson",a.a.yc);a.b("utils.registerEventHandler",
23
+ a.a.n);a.b("utils.stringifyJson",a.a.jb);a.b("utils.range",a.a.Bc);a.b("utils.toggleDomNodeCssClass",a.a.Ia);a.b("utils.triggerEvent",a.a.qa);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.A);a.b("utils.addOrRemoveItem",a.a.ga);a.b("utils.setTextContent",a.a.Ha);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=function(a){var d=this;if(1===arguments.length)return function(){return d.apply(a,arguments)};var c=Array.prototype.slice.call(arguments,1);return function(){var e=
24
+ c.slice(0);e.push.apply(e,arguments);return d.apply(a,e)}});a.a.f=new function(){function a(b,k){var h=b[c];if(!h||"null"===h||!e[h]){if(!k)return p;h=b[c]="ko"+d++;e[h]={}}return e[h]}var d=0,c="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===p?p:e[d]},set:function(c,d,e){if(e!==p||a(c,!1)!==p)a(c,!0)[d]=e},clear:function(a){var b=a[c];return b?(delete e[b],a[c]=null,!0):!1},I:function(){return d++ +c}}};a.b("utils.domData",a.a.f);a.b("utils.domData.clear",a.a.f.clear);
25
+ a.a.C=new function(){function b(b,d){var e=a.a.f.get(b,c);e===p&&d&&(e=[],a.a.f.set(b,c,e));return e}function d(c){var e=b(c,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](c);a.a.f.clear(c);a.a.C.cleanExternalData(c);if(f[c.nodeType])for(e=c.firstChild;c=e;)e=c.nextSibling,8===c.nodeType&&d(c)}var c=a.a.f.I(),e={1:!0,8:!0,9:!0},f={1:!0,9:!0};return{fa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},Pb:function(d,e){var f=b(d,!1);f&&(a.a.ya(f,
26
+ e),0==f.length&&a.a.f.set(d,c,p))},S:function(b){if(e[b.nodeType]&&(d(b),f[b.nodeType])){var c=[];a.a.ia(c,b.getElementsByTagName("*"));for(var l=0,g=c.length;l<g;l++)d(c[l])}return b},removeNode:function(b){a.S(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){u&&"function"==typeof u.cleanData&&u.cleanData([a])}}};a.S=a.a.C.S;a.removeNode=a.a.C.removeNode;a.b("cleanNode",a.S);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.C);a.b("utils.domNodeDisposal.addDisposeCallback",
27
+ a.a.C.fa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.C.Pb);(function(){a.a.ca=function(b,d){var c;if(u)if(u.parseHTML)c=u.parseHTML(b,d)||[];else{if((c=u.clean([b],d))&&c[0]){for(var e=c[0];e.parentNode&&11!==e.parentNode.nodeType;)e=e.parentNode;e.parentNode&&e.parentNode.removeChild(e)}}else{(e=d)||(e=w);c=e.parentWindow||e.defaultView||y;var f=a.a.ib(b).toLowerCase(),e=e.createElement("div"),f=f.match(/^<(thead|tbody|tfoot)/)&&[1,"<table>","</table>"]||!f.indexOf("<tr")&&[2,"<table><tbody>",
28
+ "</tbody></table>"]||(!f.indexOf("<td")||!f.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||[0,"",""],k="ignored<div>"+f[1]+b+f[2]+"</div>";for("function"==typeof c.innerShiv?e.appendChild(c.innerShiv(k)):e.innerHTML=k;f[0]--;)e=e.lastChild;c=a.a.O(e.lastChild.childNodes)}return c};a.a.gb=function(b,d){a.a.Ra(b);d=a.a.c(d);if(null!==d&&d!==p)if("string"!=typeof d&&(d=d.toString()),u)u(b).html(d);else for(var c=a.a.ca(d,b.ownerDocument),e=0;e<c.length;e++)b.appendChild(c[e])}})();
29
+ a.b("utils.parseHtmlFragment",a.a.ca);a.b("utils.setHtml",a.a.gb);a.H=function(){function b(c,d){if(c)if(8==c.nodeType){var f=a.H.Lb(c.nodeValue);null!=f&&d.push({ic:c,wc:f})}else if(1==c.nodeType)for(var f=0,k=c.childNodes,h=k.length;f<h;f++)b(k[f],d)}var d={};return{$a:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);
30
+ d[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},Wb:function(a,b){var f=d[a];if(f===p)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),!0}finally{delete d[a]}},Xb:function(c,d){var f=[];b(c,f);for(var k=0,h=f.length;k<h;k++){var l=f[k].ic,g=[l];d&&a.a.ia(g,d);a.H.Wb(f[k].wc,g);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},Lb:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.H);
31
+ a.b("memoization.memoize",a.H.$a);a.b("memoization.unmemoize",a.H.Wb);a.b("memoization.parseMemoText",a.H.Lb);a.b("memoization.unmemoizeDomNodeAndDescendants",a.H.Xb);a.Sa={throttle:function(b,d){b.throttleEvaluation=d;var c=null;return a.j({read:b,write:function(a){clearTimeout(c);c=setTimeout(function(){b(a)},d)}})},rateLimit:function(a,d){var c,e,f;"number"==typeof d?c=d:(c=d.timeout,e=d.method);f="notifyWhenChangesStop"==e?S:R;a.Za(function(a){return f(a,c)})},notify:function(a,d){a.equalityComparer=
32
+ "always"==d?null:J}};var Q={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.Sa);a.Ub=function(b,d,c){this.da=b;this.La=d;this.hc=c;this.Gb=!1;a.D(this,"dispose",this.p)};a.Ub.prototype.p=function(){this.Gb=!0;this.hc()};a.Q=function(){a.a.Ga(this,a.Q.fn);this.G={};this.rb=1};var z={U:function(b,d,c){var e=this;c=c||"change";var f=new a.Ub(e,d?b.bind(d):b,function(){a.a.ya(e.G[c],f);e.ua&&e.ua(c)});e.ja&&e.ja(c);e.G[c]||(e.G[c]=[]);e.G[c].push(f);return f},notifySubscribers:function(b,
33
+ d){d=d||"change";"change"===d&&this.Yb();if(this.Ba(d))try{a.k.xb();for(var c=this.G[d].slice(0),e=0,f;f=c[e];++e)f.Gb||f.La(b)}finally{a.k.end()}},Aa:function(){return this.rb},pc:function(a){return this.Aa()!==a},Yb:function(){++this.rb},Za:function(b){var d=this,c=a.F(d),e,f,k;d.ta||(d.ta=d.notifySubscribers,d.notifySubscribers=function(a,b){b&&"change"!==b?"beforeChange"===b?d.pb(a):d.ta(a,b):d.qb(a)});var h=b(function(){c&&k===d&&(k=d());e=!1;d.Wa(f,k)&&d.ta(f=k)});d.qb=function(a){e=!0;k=a;
34
+ h()};d.pb=function(a){e||(f=a,d.ta(a,"beforeChange"))}},Ba:function(a){return this.G[a]&&this.G[a].length},nc:function(b){if(b)return this.G[b]&&this.G[b].length||0;var d=0;a.a.A(this.G,function(a,b){d+=b.length});return d},Wa:function(a,d){return!this.equalityComparer||!this.equalityComparer(a,d)},extend:function(b){var d=this;b&&a.a.A(b,function(b,e){var f=a.Sa[b];"function"==typeof f&&(d=f(d,e)||d)});return d}};a.D(z,"subscribe",z.U);a.D(z,"extend",z.extend);a.D(z,"getSubscriptionsCount",z.nc);
35
+ a.a.za&&a.a.Fa(z,Function.prototype);a.Q.fn=z;a.Hb=function(a){return null!=a&&"function"==typeof a.U&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.Q);a.b("isSubscribable",a.Hb);a.Z=a.k=function(){function b(a){c.push(e);e=a}function d(){e=c.pop()}var c=[],e,f=0;return{xb:b,end:d,Ob:function(b){if(e){if(!a.Hb(b))throw Error("Only subscribable things can act as dependencies");e.La(b,b.ac||(b.ac=++f))}},u:function(a,c,e){try{return b(),a.apply(c,e||[])}finally{d()}},oa:function(){if(e)return e.w.oa()},
36
+ Ca:function(){if(e)return e.Ca}}}();a.b("computedContext",a.Z);a.b("computedContext.getDependenciesCount",a.Z.oa);a.b("computedContext.isInitial",a.Z.Ca);a.b("computedContext.isSleeping",a.Z.Jc);a.b("ignoreDependencies",a.Gc=a.k.u);a.r=function(b){function d(){if(0<arguments.length)return d.Wa(c,arguments[0])&&(d.X(),c=arguments[0],d.W()),this;a.k.Ob(d);return c}var c=b;a.Q.call(d);a.a.Ga(d,a.r.fn);d.B=function(){return c};d.W=function(){d.notifySubscribers(c)};d.X=function(){d.notifySubscribers(c,
37
+ "beforeChange")};a.D(d,"peek",d.B);a.D(d,"valueHasMutated",d.W);a.D(d,"valueWillMutate",d.X);return d};a.r.fn={equalityComparer:J};var H=a.r.Ac="__ko_proto__";a.r.fn[H]=a.r;a.a.za&&a.a.Fa(a.r.fn,a.Q.fn);a.Ta=function(b,d){return null===b||b===p||b[H]===p?!1:b[H]===d?!0:a.Ta(b[H],d)};a.F=function(b){return a.Ta(b,a.r)};a.Da=function(b){return"function"==typeof b&&b[H]===a.r||"function"==typeof b&&b[H]===a.j&&b.qc?!0:!1};a.b("observable",a.r);a.b("isObservable",a.F);a.b("isWriteableObservable",a.Da);
38
+ a.b("isWritableObservable",a.Da);a.ba=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.r(b);a.a.Ga(b,a.ba.fn);return b.extend({trackArrayChanges:!0})};a.ba.fn={remove:function(b){for(var d=this.B(),c=[],e="function"!=typeof b||a.F(b)?function(a){return a===b}:b,f=0;f<d.length;f++){var k=d[f];e(k)&&(0===c.length&&this.X(),c.push(k),d.splice(f,1),f--)}c.length&&this.W();return c},
39
+ removeAll:function(b){if(b===p){var d=this.B(),c=d.slice(0);this.X();d.splice(0,d.length);this.W();return c}return b?this.remove(function(c){return 0<=a.a.m(b,c)}):[]},destroy:function(b){var d=this.B(),c="function"!=typeof b||a.F(b)?function(a){return a===b}:b;this.X();for(var e=d.length-1;0<=e;e--)c(d[e])&&(d[e]._destroy=!0);this.W()},destroyAll:function(b){return b===p?this.destroy(function(){return!0}):b?this.destroy(function(d){return 0<=a.a.m(b,d)}):[]},indexOf:function(b){var d=this();return a.a.m(d,
40
+ b)},replace:function(a,d){var c=this.indexOf(a);0<=c&&(this.X(),this.B()[c]=d,this.W())}};a.a.o("pop push reverse shift sort splice unshift".split(" "),function(b){a.ba.fn[b]=function(){var a=this.B();this.X();this.yb(a,b,arguments);a=a[b].apply(a,arguments);this.W();return a}});a.a.o(["slice"],function(b){a.ba.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.a.za&&a.a.Fa(a.ba.fn,a.r.fn);a.b("observableArray",a.ba);a.Sa.trackArrayChanges=function(b){function d(){if(!c){c=!0;var g=
41
+ b.notifySubscribers;b.notifySubscribers=function(a,b){b&&"change"!==b||++k;return g.apply(this,arguments)};var d=[].concat(b.B()||[]);e=null;f=b.U(function(c){c=[].concat(c||[]);if(b.Ba("arrayChange")){var g;if(!e||1<k)e=a.a.Ma(d,c,{sparse:!0});g=e}d=c;e=null;k=0;g&&g.length&&b.notifySubscribers(g,"arrayChange")})}}if(!b.yb){var c=!1,e=null,f,k=0,h=b.ja,l=b.ua;b.ja=function(a){h&&h.call(b,a);"arrayChange"===a&&d()};b.ua=function(a){l&&l.call(b,a);"arrayChange"!==a||b.Ba("arrayChange")||(f.p(),c=!1)};
42
+ b.yb=function(b,d,f){function l(a,b,c){return h[h.length]={status:a,value:b,index:c}}if(c&&!k){var h=[],r=b.length,v=f.length,t=0;switch(d){case "push":t=r;case "unshift":for(d=0;d<v;d++)l("added",f[d],t+d);break;case "pop":t=r-1;case "shift":r&&l("deleted",b[t],t);break;case "splice":d=Math.min(Math.max(0,0>f[0]?r+f[0]:f[0]),r);for(var r=1===v?r:Math.min(d+(f[1]||0),r),v=d+v-2,t=Math.max(r,v),G=[],A=[],p=2;d<t;++d,++p)d<r&&A.push(l("deleted",b[d],d)),d<v&&G.push(l("added",f[p],d));a.a.Cb(A,G);break;
43
+ default:return}e=h}}}};a.w=a.j=function(b,d,c){function e(a,b,c){if(I&&b===g)throw Error("A 'pure' computed must not be called recursively");B[a]=c;c.sa=F++;c.ea=b.Aa()}function f(){var a,b;for(a in B)if(B.hasOwnProperty(a)&&(b=B[a],b.da.pc(b.ea)))return!0}function k(){!s&&B&&a.a.A(B,function(a,b){b.p&&b.p()});B=null;F=0;G=!0;s=r=!1}function h(){var a=g.throttleEvaluation;a&&0<=a?(clearTimeout(z),z=setTimeout(function(){l(!0)},a)):g.nb?g.nb():l(!0)}function l(b){if(!v&&!G){if(y&&y()){if(!t){w();return}}else t=
44
+ !1;v=!0;try{var c=B,m=F,f=I?p:!F;a.k.xb({La:function(a,b){G||(m&&c[b]?(e(b,a,c[b]),delete c[b],--m):B[b]||e(b,a,s?{da:a}:a.U(h)))},w:g,Ca:f});B={};F=0;try{var l=d?A.call(d):A()}finally{a.k.end(),m&&!s&&a.a.A(c,function(a,b){b.p&&b.p()}),r=!1}g.Wa(n,l)&&(s||q(n,"beforeChange"),n=l,s?g.Yb():b&&q(n));f&&q(n,"awake")}finally{v=!1}F||w()}}function g(){if(0<arguments.length){if("function"===typeof C)C.apply(d,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
45
+ return this}a.k.Ob(g);(r||s&&f())&&l();return n}function m(){(r&&!F||s&&f())&&l();return n}function x(){return r||0<F}function q(a,b){g.notifySubscribers(a,b)}var n,r=!0,v=!1,t=!1,G=!1,A=b,I=!1,s=!1;A&&"object"==typeof A?(c=A,A=c.read):(c=c||{},A||(A=c.read));if("function"!=typeof A)throw Error("Pass a function that returns the value of the ko.computed");var C=c.write,D=c.disposeWhenNodeIsRemoved||c.q||null,u=c.disposeWhen||c.Pa,y=u,w=k,B={},F=0,z=null;d||(d=c.owner);a.Q.call(g);a.a.Ga(g,a.j.fn);
46
+ g.B=m;g.oa=function(){return F};g.qc="function"===typeof C;g.p=function(){w()};g.$=x;var T=g.Za;g.Za=function(a){T.call(g,a);g.nb=function(){g.pb(n);r=!0;g.qb(g)}};c.pure?(s=I=!0,g.ja=function(b){if(!G&&s&&"change"==b){s=!1;if(r||f())B=null,F=0,r=!0,l();else{var c=[];a.a.A(B,function(a,b){c[b.sa]=a});a.a.o(c,function(a,b){var c=B[a],g=c.da.U(h);g.sa=b;g.ea=c.ea;B[a]=g})}G||q(n,"awake")}},g.ua=function(b){G||"change"!=b||g.Ba("change")||(a.a.A(B,function(a,b){b.p&&(B[a]={da:b.da,sa:b.sa,ea:b.ea},b.p())}),
47
+ s=!0,q(p,"asleep"))},g.bc=g.Aa,g.Aa=function(){s&&(r||f())&&l();return g.bc()}):c.deferEvaluation&&(g.ja=function(a){"change"!=a&&"beforeChange"!=a||m()});a.D(g,"peek",g.B);a.D(g,"dispose",g.p);a.D(g,"isActive",g.$);a.D(g,"getDependenciesCount",g.oa);D&&(t=!0,D.nodeType&&(y=function(){return!a.a.Qa(D)||u&&u()}));s||c.deferEvaluation||l();D&&x()&&D.nodeType&&(w=function(){a.a.C.Pb(D,w);k()},a.a.C.fa(D,w));return g};a.sc=function(b){return a.Ta(b,a.j)};z=a.r.Ac;a.j[z]=a.r;a.j.fn={equalityComparer:J};
48
+ a.j.fn[z]=a.j;a.a.za&&a.a.Fa(a.j.fn,a.Q.fn);a.b("dependentObservable",a.j);a.b("computed",a.j);a.b("isComputed",a.sc);a.Nb=function(b,d){if("function"===typeof b)return a.w(b,d,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.w(b,d)};a.b("pureComputed",a.Nb);(function(){function b(a,f,k){k=k||new c;a=f(a);if("object"!=typeof a||null===a||a===p||a instanceof Date||a instanceof String||a instanceof Number||a instanceof Boolean)return a;var h=a instanceof Array?[]:{};k.save(a,h);d(a,function(c){var g=
49
+ f(a[c]);switch(typeof g){case "boolean":case "number":case "string":case "function":h[c]=g;break;case "object":case "undefined":var d=k.get(g);h[c]=d!==p?d:b(g,f,k)}});return h}function d(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function c(){this.keys=[];this.mb=[]}a.Vb=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");return b(c,function(b){for(var c=0;a.F(b)&&
50
+ 10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.Vb(b);return a.a.jb(b,c,d)};c.prototype={save:function(b,c){var d=a.a.m(this.keys,b);0<=d?this.mb[d]=c:(this.keys.push(b),this.mb.push(c))},get:function(b){b=a.a.m(this.keys,b);return 0<=b?this.mb[b]:p}}})();a.b("toJS",a.Vb);a.b("toJSON",a.toJSON);(function(){a.i={s:function(b){switch(a.a.v(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.f.get(b,a.d.options.ab):7>=a.a.M?b.getAttributeNode("value")&&b.getAttributeNode("value").specified?
51
+ b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.i.s(b.options[b.selectedIndex]):p;default:return b.value}},Y:function(b,d,c){switch(a.a.v(b)){case "option":switch(typeof d){case "string":a.a.f.set(b,a.d.options.ab,p);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=d;break;default:a.a.f.set(b,a.d.options.ab,d),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof d?d:""}break;case "select":if(""===d||null===d)d=p;for(var e=-1,f=0,k=b.options.length,
52
+ h;f<k;++f)if(h=a.i.s(b.options[f]),h==d||""==h&&d===p){e=f;break}if(c||0<=e||d===p&&1<b.size)b.selectedIndex=e;break;default:if(null===d||d===p)d="";b.value=d}}}})();a.b("selectExtensions",a.i);a.b("selectExtensions.readValue",a.i.s);a.b("selectExtensions.writeValue",a.i.Y);a.h=function(){function b(b){b=a.a.ib(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),x,h=[],n=0;if(d){d.push(",");for(var r=0,v;v=d[r];++r){var t=v.charCodeAt(0);if(44===t){if(0>=n){c.push(x&&h.length?{key:x,
53
+ value:h.join("")}:{unknown:x||h.join("")});x=n=0;h=[];continue}}else if(58===t){if(!n&&!x&&1===h.length){x=h.pop();continue}}else 47===t&&r&&1<v.length?(t=d[r-1].match(f))&&!k[t[0]]&&(b=b.substr(b.indexOf(v)+1),d=b.match(e),d.push(","),r=-1,v="/"):40===t||123===t||91===t?++n:41===t||125===t||93===t?--n:x||h.length||34!==t&&39!==t||(v=v.slice(1,-1));h.push(v)}}return c}var d=["true","false","null","undefined"],c=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]",
54
+ "g"),f=/[\])"'A-Za-z0-9_$]+$/,k={"in":1,"return":1,"typeof":1},h={};return{ka:[],V:h,bb:b,Ea:function(e,g){function m(b,g){var e;if(!r){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(g=l.preprocess(g,b,m)))return;if(l=h[b])e=g,0<=a.a.m(d,e)?e=!1:(l=e.match(c),e=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:e),l=e;l&&k.push("'"+b+"':function(_z){"+e+"=_z}")}n&&(g="function(){return "+g+" }");f.push("'"+b+"':"+g)}g=g||{};var f=[],k=[],n=g.valueAccessors,r=g.bindingParams,v="string"===typeof e?b(e):e;
55
+ a.a.o(v,function(a){m(a.key||a.unknown,a.value)});k.length&&m("_ko_property_writers","{"+k.join(",")+" }");return f.join(",")},vc:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},ra:function(b,c,d,e,f){if(b&&a.F(b))!a.Da(b)||f&&b.B()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",a.h.ka);a.b("expressionRewriting.parseObjectLiteral",a.h.bb);a.b("expressionRewriting.preProcessBindings",
56
+ a.h.Ea);a.b("expressionRewriting._twoWayBindings",a.h.V);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Ea);(function(){function b(a){return 8==a.nodeType&&k.test(f?a.text:a.nodeValue)}function d(a){return 8==a.nodeType&&h.test(f?a.text:a.nodeValue)}function c(a,c){for(var e=a,f=1,l=[];e=e.nextSibling;){if(d(e)&&(f--,0===f))return l;l.push(e);b(e)&&f++}if(!c)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,
57
+ b){var d=c(a,b);return d?0<d.length?d[d.length-1].nextSibling:a.nextSibling:null}var f=w&&"\x3c!--test--\x3e"===w.createComment("test").text,k=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,h=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.e={R:{},childNodes:function(a){return b(a)?c(a):a.childNodes},ma:function(c){if(b(c)){c=a.e.childNodes(c);for(var d=0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.Ra(c)},T:function(c,d){if(b(c)){a.e.ma(c);for(var e=c.nextSibling,
58
+ f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],e)}else a.a.T(c,d)},Mb:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},Fb:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.e.Mb(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||d(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&
59
+ d(a.nextSibling)?null:a.nextSibling},oc:b,Fc:function(a){return(a=(f?a.text:a.nodeValue).match(k))?a[1]:null},Kb:function(c){if(l[a.a.v(c)]){var m=c.firstChild;if(m){do if(1===m.nodeType){var f;f=m.firstChild;var h=null;if(f){do if(h)h.push(f);else if(b(f)){var k=e(f,!0);k?f=k:h=[f]}else d(f)&&(h=[f]);while(f=f.nextSibling)}if(f=h)for(h=m.nextSibling,k=0;k<f.length;k++)h?c.insertBefore(f[k],h):c.appendChild(f[k])}while(m=m.nextSibling)}}}}})();a.b("virtualElements",a.e);a.b("virtualElements.allowedBindings",
60
+ a.e.R);a.b("virtualElements.emptyNode",a.e.ma);a.b("virtualElements.insertAfter",a.e.Fb);a.b("virtualElements.prepend",a.e.Mb);a.b("virtualElements.setDomNodeChildren",a.e.T);(function(){a.L=function(){this.ec={}};a.a.extend(a.L.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.e.oc(b);default:return!1}},getBindings:function(b,d){var c=this.getBindingsString(b,d),c=c?this.parseBindingsString(c,
61
+ d,b):null;return a.g.sb(c,b,d,!1)},getBindingAccessors:function(b,d){var c=this.getBindingsString(b,d),c=c?this.parseBindingsString(c,d,b,{valueAccessors:!0}):null;return a.g.sb(c,b,d,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.e.Fc(b);default:return null}},parseBindingsString:function(b,d,c,e){try{var f=this.ec,k=b+(e&&e.valueAccessors||""),h;if(!(h=f[k])){var l,g="with($context){with($data||{}){return{"+a.h.Ea(b,e)+"}}}";l=new Function("$context",
62
+ "$element",g);h=f[k]=l}return h(d,c)}catch(m){throw m.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+m.message,m;}}});a.L.instance=new a.L})();a.b("bindingProvider",a.L);(function(){function b(a){return function(){return a}}function d(a){return a()}function c(b){return a.a.pa(a.k.u(b),function(a,c){return function(){return b()[c]}})}function e(d,g,e){return"function"===typeof d?c(d.bind(null,g,e)):a.a.pa(d,b)}function f(a,b){return c(this.getBindings.bind(this,a,b))}function k(b,
63
+ c,d){var g,e=a.e.firstChild(c),f=a.L.instance,m=f.preprocessNode;if(m){for(;g=e;)e=a.e.nextSibling(g),m.call(f,g);e=a.e.firstChild(c)}for(;g=e;)e=a.e.nextSibling(g),h(b,g,d)}function h(b,c,d){var e=!0,f=1===c.nodeType;f&&a.e.Kb(c);if(f&&d||a.L.instance.nodeHasBindings(c))e=g(c,null,b,d).shouldBindDescendants;e&&!x[a.a.v(c)]&&k(b,c,!f)}function l(b){var c=[],d={},g=[];a.a.A(b,function I(e){if(!d[e]){var f=a.getBindingHandler(e);f&&(f.after&&(g.push(e),a.a.o(f.after,function(c){if(b[c]){if(-1!==a.a.m(g,
64
+ c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+g.join(", "));I(c)}}),g.length--),c.push({key:e,Eb:f}));d[e]=!0}});return c}function g(b,c,g,e){var m=a.a.f.get(b,q);if(!c){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.f.set(b,q,!0)}!m&&e&&a.Tb(b,g);var h;if(c&&"function"!==typeof c)h=c;else{var k=a.L.instance,x=k.getBindingAccessors||f,n=a.j(function(){(h=c?c(g,b):x.call(k,b,g))&&g.K&&g.K();return h},null,{q:b});
65
+ h&&n.$()||(n=null)}var u;if(h){var w=n?function(a){return function(){return d(n()[a])}}:function(a){return h[a]},y=function(){return a.a.pa(n?n():h,d)};y.get=function(a){return h[a]&&d(w(a))};y.has=function(a){return a in h};e=l(h);a.a.o(e,function(c){var d=c.Eb.init,e=c.Eb.update,f=c.key;if(8===b.nodeType&&!a.e.R[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.k.u(function(){var a=d(b,w(f),y,g.$data,g);if(a&&a.controlsDescendantBindings){if(u!==
66
+ p)throw Error("Multiple bindings ("+u+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=f}}),"function"==typeof e&&a.j(function(){e(b,w(f),y,g.$data,g)},null,{q:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+h[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:u===p}}function m(b){return b&&b instanceof a.N?b:new a.N(b)}a.d={};var x={script:!0,textarea:!0};a.getBindingHandler=function(b){return a.d[b]};
67
+ a.N=function(b,c,d,g){var e=this,f="function"==typeof b&&!a.F(b),m,l=a.j(function(){var m=f?b():b,h=a.a.c(m);c?(c.K&&c.K(),a.a.extend(e,c),l&&(e.K=l)):(e.$parents=[],e.$root=h,e.ko=a);e.$rawData=m;e.$data=h;d&&(e[d]=h);g&&g(e,c,h);return e.$data},null,{Pa:function(){return m&&!a.a.tb(m)},q:!0});l.$()&&(e.K=l,l.equalityComparer=null,m=[],l.Zb=function(b){m.push(b);a.a.C.fa(b,function(b){a.a.ya(m,b);m.length||(l.p(),e.K=l=p)})})};a.N.prototype.createChildContext=function(b,c,d){return new a.N(b,this,
68
+ c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.N.prototype.extend=function(b){return new a.N(this.K||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};var q=a.a.f.I(),n=a.a.f.I();a.Tb=function(b,c){if(2==arguments.length)a.a.f.set(b,n,c),c.K&&c.K.Zb(b);else return a.a.f.get(b,n)};a.va=function(b,c,d){1===b.nodeType&&a.e.Kb(b);return g(b,c,m(d),!0)};a.cc=function(b,
69
+ c,d){d=m(d);return a.va(b,e(c,d,b),d)};a.Ja=function(a,b){1!==b.nodeType&&8!==b.nodeType||k(m(a),b,!0)};a.ub=function(a,b){!u&&y.jQuery&&(u=y.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||y.document.body;h(m(a),b,!0)};a.Oa=function(b){switch(b.nodeType){case 1:case 8:var c=a.Tb(b);if(c)return c;if(b.parentNode)return a.Oa(b.parentNode)}return p};a.gc=function(b){return(b=a.Oa(b))?
70
+ b.$data:p};a.b("bindingHandlers",a.d);a.b("applyBindings",a.ub);a.b("applyBindingsToDescendants",a.Ja);a.b("applyBindingAccessorsToNode",a.va);a.b("applyBindingsToNode",a.cc);a.b("contextFor",a.Oa);a.b("dataFor",a.gc)})();(function(b){function d(d,e){var g=f.hasOwnProperty(d)?f[d]:b,m;g?g.U(e):(g=f[d]=new a.Q,g.U(e),c(d,function(a,b){var c=!(!b||!b.synchronous);k[d]={definition:a,tc:c};delete f[d];m||c?g.notifySubscribers(a):setTimeout(function(){g.notifySubscribers(a)},0)}),m=!0)}function c(a,b){e("getConfig",
71
+ [a],function(c){c?e("loadComponent",[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,g,f){f||(f=a.g.loaders.slice(0));var k=f.shift();if(k){var q=k[c];if(q){var n=!1;if(q.apply(k,d.concat(function(a){n?g(null):null!==a?g(a):e(c,d,g,f)}))!==b&&(n=!0,!k.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,g,f)}else g(null)}var f={},k={};a.g={get:function(c,e){var g=k.hasOwnProperty(c)?k[c]:
72
+ b;g?g.tc?a.k.u(function(){e(g.definition)}):setTimeout(function(){e(g.definition)},0):d(c,e)},zb:function(a){delete k[a]},ob:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.zb)})();(function(){function b(b,c,d,e){function k(){0===--v&&e(h)}var h={},v=2,t=d.template;d=d.viewModel;t?f(c,t,function(c){a.g.ob("loadTemplate",[b,c],function(a){h.template=a;k()})}):k();d?f(c,d,function(c){a.g.ob("loadViewModel",[b,c],function(a){h[l]=a;k()})}):
73
+ k()}function d(a,b,c){if("function"===typeof b)c(function(a){return new b(a)});else if("function"===typeof b[l])c(b[l]);else if("instance"in b){var e=b.instance;c(function(){return e})}else"viewModel"in b?d(a,b.viewModel,c):a("Unknown viewModel value: "+b)}function c(b){switch(a.a.v(b)){case "script":return a.a.ca(b.text);case "textarea":return a.a.ca(b.value);case "template":if(e(b.content))return a.a.la(b.content.childNodes)}return a.a.la(b.childNodes)}function e(a){return y.DocumentFragment?a instanceof
74
+ DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?O||y.require?(O||y.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function k(a){return function(b){throw Error("Component '"+a+"': "+b);}}var h={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.Xa(b))throw Error("Component "+b+" is already registered");h[b]=c};a.g.Xa=function(a){return a in h};a.g.Ec=function(b){delete h[b];a.g.zb(b)};a.g.Ab={getConfig:function(a,
75
+ b){b(h.hasOwnProperty(a)?h[a]:null)},loadComponent:function(a,c,d){var e=k(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,d,f){b=k(b);if("string"===typeof d)f(a.a.ca(d));else if(d instanceof Array)f(d);else if(e(d))f(a.a.O(d.childNodes));else if(d.element)if(d=d.element,y.HTMLElement?d instanceof HTMLElement:d&&d.tagName&&1===d.nodeType)f(c(d));else if("string"===typeof d){var l=w.getElementById(d);l?f(c(l)):b("Cannot find element with ID "+d)}else b("Unknown element type: "+d);else b("Unknown template value: "+
76
+ d)},loadViewModel:function(a,b,c){d(k(a),b,c)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.Xa);a.b("components.unregister",a.g.Ec);a.b("components.defaultLoader",a.g.Ab);a.g.loaders.push(a.g.Ab);a.g.$b=h})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=d.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.pa(f,function(d){return a.w(d,null,{q:b})}),k=a.a.pa(f,function(d){var e=d.B();return d.$()?a.w({read:function(){return a.a.c(d())},
77
+ write:a.Da(e)&&function(a){d()(a)},q:b}):e});k.hasOwnProperty("$raw")||(k.$raw=f);return k}return{$raw:{}}}a.g.getComponentNameForNode=function(b){b=a.a.v(b);return a.g.Xa(b)&&b};a.g.sb=function(c,d,f,k){if(1===d.nodeType){var h=a.g.getComponentNameForNode(d);if(h){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');var l={name:h,params:b(d,f)};c.component=k?function(){return l}:l}}return c};var d=new a.L;9>a.a.M&&(a.g.register=function(a){return function(b){w.createElement(b);
78
+ return a.apply(this,arguments)}}(a.g.register),w.createDocumentFragment=function(b){return function(){var d=b(),f=a.g.$b,k;for(k in f)f.hasOwnProperty(k)&&d.createElement(k);return d}}(w.createDocumentFragment))})();(function(b){function d(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.la(c);a.e.T(d,b)}function c(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,k,h,l,g){function m(){var a=x&&
79
+ x.dispose;"function"===typeof a&&a.call(x);q=null}var x,q,n=a.a.O(a.e.childNodes(f));a.a.C.fa(f,m);a.w(function(){var l=a.a.c(k()),h,t;"string"===typeof l?h=l:(h=a.a.c(l.name),t=a.a.c(l.params));if(!h)throw Error("No component name specified");var p=q=++e;a.g.get(h,function(e){if(q===p){m();if(!e)throw Error("Unknown component '"+h+"'");d(h,e,f);var l=c(e,f,n,t);e=g.createChildContext(l,b,function(a){a.$component=l;a.$componentTemplateNodes=n});x=l;a.Ja(e,f)}})},null,{q:f});return{controlsDescendantBindings:!0}}};
80
+ a.e.R.component=!0})();var P={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,d){var c=a.a.c(d())||{};a.a.A(c,function(c,d){d=a.a.c(d);var k=!1===d||null===d||d===p;k&&b.removeAttribute(c);8>=a.a.M&&c in P?(c=P[c],k?b.removeAttribute(c):b[c]=d):k||b.setAttribute(c,d.toString());"name"===c&&a.a.Rb(b,k?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,d,c){function e(){var e=b.checked,f=x?k():e;if(!a.Z.Ca()&&(!l||e)){var h=a.k.u(d);g?m!==f?(e&&(a.a.ga(h,
81
+ f,!0),a.a.ga(h,m,!1)),m=f):a.a.ga(h,f,e):a.h.ra(h,c,"checked",f,!0)}}function f(){var c=a.a.c(d());b.checked=g?0<=a.a.m(c,k()):h?c:k()===c}var k=a.Nb(function(){return c.has("checkedValue")?a.a.c(c.get("checkedValue")):c.has("value")?a.a.c(c.get("value")):b.value}),h="checkbox"==b.type,l="radio"==b.type;if(h||l){var g=h&&a.a.c(d())instanceof Array,m=g?k():p,x=l||g;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.w(e,null,{q:b});a.a.n(b,"click",e);a.w(f,null,{q:b})}}};a.h.V.checked=!0;a.d.checkedValue=
82
+ {update:function(b,d){b.value=a.a.c(d())}}})();a.d.css={update:function(b,d){var c=a.a.c(d());null!==c&&"object"==typeof c?a.a.A(c,function(c,d){d=a.a.c(d);a.a.Ia(b,c,d)}):(c=String(c||""),a.a.Ia(b,b.__ko__cssValue,!1),b.__ko__cssValue=c,a.a.Ia(b,c,!0))}};a.d.enable={update:function(b,d){var c=a.a.c(d());c&&b.disabled?b.removeAttribute("disabled"):c||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,d){a.d.enable.update(b,function(){return!a.a.c(d())})}};a.d.event={init:function(b,d,c,
83
+ e,f){var k=d()||{};a.a.A(k,function(h){"string"==typeof h&&a.a.n(b,h,function(b){var g,m=d()[h];if(m){try{var k=a.a.O(arguments);e=f.$data;k.unshift(e);g=m.apply(e,k)}finally{!0!==g&&(b.preventDefault?b.preventDefault():b.returnValue=!1)}!1===c.get(h+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={Ib:function(b){return function(){var d=b(),c=a.a.cb(d);if(!c||"number"==typeof c.length)return{foreach:d,templateEngine:a.P.Va};a.a.c(d);return{foreach:c.data,as:c.as,
84
+ includeDestroyed:c.includeDestroyed,afterAdd:c.afterAdd,beforeRemove:c.beforeRemove,afterRender:c.afterRender,beforeMove:c.beforeMove,afterMove:c.afterMove,templateEngine:a.P.Va}}},init:function(b,d){return a.d.template.init(b,a.d.foreach.Ib(d))},update:function(b,d,c,e,f){return a.d.template.update(b,a.d.foreach.Ib(d),c,e,f)}};a.h.ka.foreach=!1;a.e.R.foreach=!0;a.d.hasfocus={init:function(b,d,c){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(m){g=
85
+ f.body}e=g===b}f=d();a.h.ra(f,c,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),k=e.bind(null,!1);a.a.n(b,"focus",f);a.a.n(b,"focusin",f);a.a.n(b,"blur",k);a.a.n(b,"focusout",k)},update:function(b,d){var c=!!a.a.c(d());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===c||(c?b.focus():b.blur(),a.k.u(a.a.qa,null,[b,c?"focusin":"focusout"]))}};a.h.V.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.V.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},
86
+ update:function(b,d){a.a.gb(b,d())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,d){return a.createChildContext(d)});var L={};a.d.options={init:function(b){if("select"!==a.a.v(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,d,c){function e(){return a.a.xa(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function k(d,e){if(r&&
87
+ m)a.i.Y(b,a.a.c(c.get("value")),!0);else if(n.length){var g=0<=a.a.m(n,a.i.s(e[0]));a.a.Sb(e[0],g);r&&!g&&a.k.u(a.a.qa,null,[b,"change"])}}var h=b.multiple,l=0!=b.length&&h?b.scrollTop:null,g=a.a.c(d()),m=c.get("valueAllowUnset")&&c.has("value"),x=c.get("optionsIncludeDestroyed");d={};var q,n=[];m||(h?n=a.a.Ka(e(),a.i.s):0<=b.selectedIndex&&n.push(a.i.s(b.options[b.selectedIndex])));g&&("undefined"==typeof g.length&&(g=[g]),q=a.a.xa(g,function(b){return x||b===p||null===b||!a.a.c(b._destroy)}),c.has("optionsCaption")&&
88
+ (g=a.a.c(c.get("optionsCaption")),null!==g&&g!==p&&q.unshift(L)));var r=!1;d.beforeRemove=function(a){b.removeChild(a)};g=k;c.has("optionsAfterRender")&&"function"==typeof c.get("optionsAfterRender")&&(g=function(b,d){k(0,d);a.k.u(c.get("optionsAfterRender"),null,[d[0],b!==L?b:p])});a.a.fb(b,q,function(d,e,g){g.length&&(n=!m&&g[0].selected?[a.i.s(g[0])]:[],r=!0);e=b.ownerDocument.createElement("option");d===L?(a.a.Ha(e,c.get("optionsCaption")),a.i.Y(e,p)):(g=f(d,c.get("optionsValue"),d),a.i.Y(e,a.a.c(g)),
89
+ d=f(d,c.get("optionsText"),g),a.a.Ha(e,d));return[e]},d,g);a.k.u(function(){m?a.i.Y(b,a.a.c(c.get("value")),!0):(h?n.length&&e().length<n.length:n.length&&0<=b.selectedIndex?a.i.s(b.options[b.selectedIndex])!==n[0]:n.length||0<=b.selectedIndex)&&a.a.qa(b,"change")});a.a.kc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.ab=a.a.f.I();a.d.selectedOptions={after:["options","foreach"],init:function(b,d,c){a.a.n(b,"change",function(){var e=d(),f=[];a.a.o(b.getElementsByTagName("option"),
90
+ function(b){b.selected&&f.push(a.i.s(b))});a.h.ra(e,c,"selectedOptions",f)})},update:function(b,d){if("select"!=a.a.v(b))throw Error("values binding applies only to SELECT elements");var c=a.a.c(d());c&&"number"==typeof c.length&&a.a.o(b.getElementsByTagName("option"),function(b){var d=0<=a.a.m(c,a.i.s(b));a.a.Sb(b,d)})}};a.h.V.selectedOptions=!0;a.d.style={update:function(b,d){var c=a.a.c(d()||{});a.a.A(c,function(c,d){d=a.a.c(d);if(null===d||d===p||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,
91
+ d,c,e,f){if("function"!=typeof d())throw Error("The value for a submit binding must be a function");a.a.n(b,"submit",function(a){var c,e=d();try{c=e.call(f.$data,b)}finally{!0!==c&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,d){a.a.Ha(b,d())}};a.e.R.text=!0;(function(){if(y&&y.navigator)var b=function(a){if(a)return parseFloat(a[1])},d=y.opera&&y.opera.version&&parseInt(y.opera.version()),c=y.navigator.userAgent,
92
+ e=b(c.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(c.match(/Firefox\/([^ ]*)/));if(10>a.a.M)var k=a.a.f.I(),h=a.a.f.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.f.get(c,h))&&c(b)},g=function(b,c){var d=b.ownerDocument;a.a.f.get(d,k)||(a.a.f.set(d,k,!0),a.a.n(d,"selectionchange",l));a.a.f.set(b,h,c)};a.d.textInput={init:function(b,c,l){function h(c,d){a.a.n(b,c,d)}function k(){var d=a.a.c(c());if(null===d||d===p)d="";w!==p&&d===w?setTimeout(k,4):b.value!==d&&(u=d,b.value=d)}function v(){A||
93
+ (w=b.value,A=setTimeout(t,4))}function t(){clearTimeout(A);w=A=p;var d=b.value;u!==d&&(u=d,a.h.ra(c(),l,"textInput",d))}var u=b.value,A,w;10>a.a.M?(h("propertychange",function(a){"value"===a.propertyName&&t()}),8==a.a.M&&(h("keyup",t),h("keydown",t)),8<=a.a.M&&(g(b,t),h("dragend",v))):(h("input",t),5>e&&"textarea"===a.a.v(b)?(h("keydown",v),h("paste",v),h("cut",v)):11>d?h("keydown",v):4>f&&(h("DOMAutoComplete",t),h("dragdrop",t),h("drop",t)));h("change",t);a.w(k,null,{q:b})}};a.h.V.textInput=!0;a.d.textinput=
94
+ {preprocess:function(a,b,c){c("textInput",a)}}})();a.d.uniqueName={init:function(b,d){if(d()){var c="ko_unique_"+ ++a.d.uniqueName.fc;a.a.Rb(b,c)}}};a.d.uniqueName.fc=0;a.d.value={after:["options","foreach"],init:function(b,d,c){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=c.get("valueUpdate"),k=!1,h=null;f&&("string"==typeof f&&(f=[f]),a.a.ia(e,f),e=a.a.wb(e));var l=function(){h=null;k=!1;var e=d(),g=a.i.s(b);a.h.ra(e,c,"value",g)};!a.a.M||"input"!=
95
+ b.tagName.toLowerCase()||"text"!=b.type||"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.m(e,"propertychange")||(a.a.n(b,"propertychange",function(){k=!0}),a.a.n(b,"focus",function(){k=!1}),a.a.n(b,"blur",function(){k&&l()}));a.a.o(e,function(c){var d=l;a.a.Dc(c,"after")&&(d=function(){h=a.i.s(b);setTimeout(l,0)},c=c.substring(5));a.a.n(b,c,d)});var g=function(){var e=a.a.c(d()),f=a.i.s(b);if(null!==h&&e===h)setTimeout(g,0);else if(e!==f)if("select"===a.a.v(b)){var l=c.get("valueAllowUnset"),
96
+ f=function(){a.i.Y(b,e,l)};f();l||e===a.i.s(b)?setTimeout(f,0):a.k.u(a.a.qa,null,[b,"change"])}else a.i.Y(b,e)};a.w(g,null,{q:b})}else a.va(b,{checkedValue:d})},update:function(){}};a.h.V.value=!0;a.d.visible={update:function(b,d){var c=a.a.c(d()),e="none"!=b.style.display;c&&!e?b.style.display="":!c&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(d,c,e,f,k){return a.d.event.init.call(this,d,function(){var a={};a[b]=c();return a},e,f,k)}}})("click");a.J=function(){};a.J.prototype.renderTemplateSource=
97
+ function(){throw Error("Override renderTemplateSource");};a.J.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.J.prototype.makeTemplateSource=function(b,d){if("string"==typeof b){d=d||w;var c=d.getElementById(b);if(!c)throw Error("Cannot find template with ID "+b);return new a.t.l(c)}if(1==b.nodeType||8==b.nodeType)return new a.t.ha(b);throw Error("Unknown template type: "+b);};a.J.prototype.renderTemplate=function(a,d,c,e){a=this.makeTemplateSource(a,
98
+ e);return this.renderTemplateSource(a,d,c,e)};a.J.prototype.isTemplateRewritten=function(a,d){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,d).data("isRewritten")};a.J.prototype.rewriteTemplate=function(a,d,c){a=this.makeTemplateSource(a,c);d=d(a.text());a.text(d);a.data("isRewritten",!0)};a.b("templateEngine",a.J);a.kb=function(){function b(b,c,d,h){b=a.h.bb(b);for(var l=a.h.ka,g=0;g<b.length;g++){var m=b[g].key;if(l.hasOwnProperty(m)){var x=l[m];if("function"===typeof x){if(m=
99
+ x(b[g].value))throw Error(m);}else if(!x)throw Error("This template engine does not support the '"+m+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Ea(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return h.createJavaScriptEvaluatorBlock(d)+c}var d=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,c=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{lc:function(b,
100
+ c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.kb.xc(b,c)},d)},xc:function(a,f){return a.replace(d,function(a,c,d,e,m){return b(m,c,d,f)}).replace(c,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},dc:function(b,c){return a.H.$a(function(d,h){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.va(l,b,h)})}}}();a.b("__tr_ambtns",a.kb.dc);(function(){a.t={};a.t.l=function(a){this.l=a};a.t.l.prototype.text=function(){var b=a.a.v(this.l),b="script"===b?"text":
101
+ "textarea"===b?"value":"innerHTML";if(0==arguments.length)return this.l[b];var d=arguments[0];"innerHTML"===b?a.a.gb(this.l,d):this.l[b]=d};var b=a.a.f.I()+"_";a.t.l.prototype.data=function(c){if(1===arguments.length)return a.a.f.get(this.l,b+c);a.a.f.set(this.l,b+c,arguments[1])};var d=a.a.f.I();a.t.ha=function(a){this.l=a};a.t.ha.prototype=new a.t.l;a.t.ha.prototype.text=function(){if(0==arguments.length){var b=a.a.f.get(this.l,d)||{};b.lb===p&&b.Na&&(b.lb=b.Na.innerHTML);return b.lb}a.a.f.set(this.l,
102
+ d,{lb:arguments[0]})};a.t.l.prototype.nodes=function(){if(0==arguments.length)return(a.a.f.get(this.l,d)||{}).Na;a.a.f.set(this.l,d,{Na:arguments[0]})};a.b("templateSources",a.t);a.b("templateSources.domElement",a.t.l);a.b("templateSources.anonymousTemplate",a.t.ha)})();(function(){function b(b,c,d){var e;for(c=a.e.nextSibling(c);b&&(e=b)!==c;)b=a.e.nextSibling(e),d(e,b)}function d(c,d){if(c.length){var e=c[0],f=c[c.length-1],h=e.parentNode,k=a.L.instance,r=k.preprocessNode;if(r){b(e,f,function(a,
103
+ b){var c=a.previousSibling,d=r.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.na(c,h))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.ub(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.H.Xb(b,[d])});a.a.na(c,h)}}function c(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,e,f,h,q){q=q||{};var n=(b&&c(b)||f||{}).ownerDocument,r=q.templateEngine||k;a.kb.lc(f,r,n);f=r.renderTemplate(f,h,q,n);if("number"!=
104
+ typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");n=!1;switch(e){case "replaceChildren":a.e.T(b,f);n=!0;break;case "replaceNode":a.a.Qb(b,f);n=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}n&&(d(f,h),q.afterRender&&a.k.u(q.afterRender,null,[f,h.$data]));return f}function f(b,c,d){return a.F(b)?b():"function"===typeof b?b(c,d):b}var k;a.hb=function(b){if(b!=p&&!(b instanceof a.J))throw Error("templateEngine must inherit from ko.templateEngine");
105
+ k=b};a.eb=function(b,d,h,x,q){h=h||{};if((h.templateEngine||k)==p)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(x){var n=c(x);return a.j(function(){var k=d&&d instanceof a.N?d:new a.N(a.a.c(d)),p=f(b,k.$data,k),k=e(x,q,p,k,h);"replaceNode"==q&&(x=k,n=c(x))},null,{Pa:function(){return!n||!a.a.Qa(n)},q:n&&"replaceNode"==q?n.parentNode:n})}return a.H.$a(function(c){a.eb(b,d,h,c,"replaceNode")})};a.Cc=function(b,c,h,k,q){function n(a,b){d(b,v);h.afterRender&&
106
+ h.afterRender(b,a);v=null}function r(a,c){v=q.createChildContext(a,h.as,function(a){a.$index=c});var d=f(b,a,v);return e(null,"ignoreTargetNode",d,v,h)}var v;return a.j(function(){var b=a.a.c(c)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.xa(b,function(b){return h.includeDestroyed||b===p||null===b||!a.a.c(b._destroy)});a.k.u(a.a.fb,null,[k,b,r,h,n])},null,{q:k})};var h=a.a.f.I();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.e.ma(b);else{if("nodes"in d){if(d=
107
+ d.nodes||[],a.F(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.e.childNodes(b);d=a.a.Jb(d);(new a.t.ha(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var k=c(),r;c=a.a.c(k);d=!0;e=null;"string"==typeof c?c={}:(k=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in c&&(d=!a.a.c(c.ifnot)),r=a.a.c(c.data));"foreach"in c?e=a.Cc(k||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.createChildContext(r,c.as):f,e=a.eb(k||b,f,c,b)):a.e.ma(b);f=
108
+ e;(r=a.a.f.get(b,h))&&"function"==typeof r.p&&r.p();a.a.f.set(b,h,f&&f.$()?f:p)}};a.h.ka.template=function(b){b=a.h.bb(b);return 1==b.length&&b[0].unknown||a.h.vc(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.e.R.template=!0})();a.b("setTemplateEngine",a.hb);a.b("renderTemplate",a.eb);a.a.Cb=function(a,d,c){if(a.length&&d.length){var e,f,k,h,l;for(e=f=0;(!c||e<c)&&(h=a[f]);++f){for(k=0;l=d[k];++k)if(h.value===l.value){h.moved=l.index;l.moved=
109
+ h.index;d.splice(k,1);e=k=0;break}e+=k}}};a.a.Ma=function(){function b(b,c,e,f,k){var h=Math.min,l=Math.max,g=[],m,p=b.length,q,n=c.length,r=n-p||1,v=p+n+1,t,u,w;for(m=0;m<=p;m++)for(u=t,g.push(t=[]),w=h(n,m+r),q=l(0,m-1);q<=w;q++)t[q]=q?m?b[m-1]===c[q-1]?u[q-1]:h(u[q]||v,t[q-1]||v)+1:q+1:m+1;h=[];l=[];r=[];m=p;for(q=n;m||q;)n=g[m][q]-1,q&&n===g[m][q-1]?l.push(h[h.length]={status:e,value:c[--q],index:q}):m&&n===g[m-1][q]?r.push(h[h.length]={status:f,value:b[--m],index:m}):(--q,--m,k.sparse||h.push({status:"retained",
110
+ value:c[q]}));a.a.Cb(l,r,10*p);return h.reverse()}return function(a,c,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];c=c||[];return a.length<=c.length?b(a,c,"added","deleted",e):b(c,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.Ma);(function(){function b(b,d,f,k,h){var l=[],g=a.j(function(){var g=d(f,h,a.a.na(l,b))||[];0<l.length&&(a.a.Qb(l,g),k&&a.k.u(k,null,[f,g,h]));l.length=0;a.a.ia(l,g)},null,{q:b,Pa:function(){return!a.a.tb(l)}});return{aa:l,j:g.$()?g:p}}var d=a.a.f.I();
111
+ a.a.fb=function(c,e,f,k,h){function l(b,d){s=u[d];t!==d&&(z[b]=s);s.Ua(t++);a.a.na(s.aa,c);r.push(s);y.push(s)}function g(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.o(c[d].aa,function(a){b(a,d,c[d].wa)})}e=e||[];k=k||{};var m=a.a.f.get(c,d)===p,u=a.a.f.get(c,d)||[],q=a.a.Ka(u,function(a){return a.wa}),n=a.a.Ma(q,e,k.dontLimitMoves),r=[],v=0,t=0,w=[],y=[];e=[];for(var z=[],q=[],s,C=0,D,E;D=n[C];C++)switch(E=D.moved,D.status){case "deleted":E===p&&(s=u[v],s.j&&s.j.p(),w.push.apply(w,a.a.na(s.aa,
112
+ c)),k.beforeRemove&&(e[C]=s,y.push(s)));v++;break;case "retained":l(C,v++);break;case "added":E!==p?l(C,E):(s={wa:D.value,Ua:a.r(t++)},r.push(s),y.push(s),m||(q[C]=s))}g(k.beforeMove,z);a.a.o(w,k.beforeRemove?a.S:a.removeNode);for(var C=0,m=a.e.firstChild(c),H;s=y[C];C++){s.aa||a.a.extend(s,b(c,f,s.wa,h,s.Ua));for(v=0;n=s.aa[v];m=n.nextSibling,H=n,v++)n!==m&&a.e.Fb(c,n,H);!s.rc&&h&&(h(s.wa,s.aa,s.Ua),s.rc=!0)}g(k.beforeRemove,e);g(k.afterMove,z);g(k.afterAdd,q);a.a.f.set(c,d,r)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",
113
+ a.a.fb);a.P=function(){this.allowTemplateRewriting=!1};a.P.prototype=new a.J;a.P.prototype.renderTemplateSource=function(b,d,c,e){if(d=(9>a.a.M?0:b.nodes)?b.nodes():null)return a.a.O(d.cloneNode(!0).childNodes);b=b.text();return a.a.ca(b,e)};a.P.Va=new a.P;a.hb(a.P.Va);a.b("nativeTemplateEngine",a.P);(function(){a.Ya=function(){var a=this.uc=function(){if(!u||!u.tmpl)return 0;try{if(0<=u.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,
114
+ e,f,k){k=k||w;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var h=b.data("precompiled");h||(h=b.text()||"",h=u.template(null,"{{ko_with $item.koBindingContext}}"+h+"{{/ko_with}}"),b.data("precompiled",h));b=[e.$data];e=u.extend({koBindingContext:e},f.templateOptions);e=u.tmpl(h,b,e);e.appendTo(k.createElement("div"));u.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+
115
+ a+" })()) }}"};this.addTemplate=function(a,b){w.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(u.tmpl.tag.ko_code={open:"__.push($1 || '');"},u.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.Ya.prototype=new a.J;var b=new a.Ya;0<b.uc&&a.hb(b);a.b("jqueryTmplTemplateEngine",a.Ya)})()})})();})();
js/select2.min.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+ /*! Select2 4.0.0 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(n=n.slice(0,n.length-1),a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.concat(a),k=0;k<a.length;k+=1)if(m=a[k],"."===m)a.splice(k,1),k-=1;else if(".."===m){if(1===k&&(".."===a[2]||".."===a[0]))break;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){return n.apply(b,v.call(arguments,0).concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(a){if(e(r,a)){var c=r[a];delete r[a],t[a]=!0,m.apply(b,c)}if(!e(q,a)&&!e(t,a))throw new Error("No "+a);return q[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(a,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||a,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n<c.length;n+=1)if(m=o(c[n],f),k=m.f,"require"===k)u[n]=p.require(a);else if("exports"===k)u[n]=p.exports(a),s=!0;else if("module"===k)h=u[n]=p.module(a);else if(e(q,k)||e(r,k)||e(t,k))u[n]=j(k);else{if(!m.p)throw new Error(a+" missing "+k);m.p.load(m.n,g(f,!0),i(k),{}),u[n]=q[k]}l=d?d.apply(q[a],u):void 0,a&&(h&&h.exports!==b&&h.exports!==q[a]?q[a]=h.exports:l===b&&s||(q[a]=l))}else a&&(q[a]=d)},a=c=n=function(a,c,d,e,f){if("string"==typeof a)return p[a]?p[a](c):j(o(a,c).f);if(!a.splice){if(s=a,s.deps&&n(s.deps,s.callback),!c)return;c.splice?(a=c,c=d,d=null):a=b}return c=c||function(){},"function"==typeof d&&(d=e,e=f),e?m(b,a,c,d):setTimeout(function(){m(b,a,c,d)},4),n},n.config=function(a){return n(a)},a._defined=q,d=function(a,b,c){b.splice||(c=b,b=[]),e(q,a)||e(r,a)||(r[a]=[a,b,c])},d.amd={jQuery:!0}}(),b.requirejs=a,b.require=c,b.define=d}}(),b.define("almond",function(){}),b.define("jquery",[],function(){var b=a||$;return null==b&&console&&console.error&&console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page."),b}),b.define("select2/utils",["jquery"],function(a){function b(a){var b=a.prototype,c=[];for(var d in b){var e=b[d];"function"==typeof e&&"constructor"!==d&&c.push(d)}return c}var c={};c.Extend=function(a,b){function c(){this.constructor=a}var d={}.hasOwnProperty;for(var e in b)d.call(b,e)&&(a[e]=b[e]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},c.Decorate=function(a,c){function d(){var b=Array.prototype.unshift,d=c.prototype.constructor.length,e=a.prototype.constructor;d>0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h<g.length;h++){var i=g[h];d.prototype[i]=a.prototype[i]}for(var j=(function(a){var b=function(){};a in d.prototype&&(b=d.prototype[a]);var e=c.prototype[a];return function(){var a=Array.prototype.unshift;return a.call(arguments,b),e.apply(this,arguments)}}),k=0;k<f.length;k++){var l=f[k];d.prototype[l]=j(l)}return d};var d=function(){this.listeners={}};return d.prototype.on=function(a,b){this.listeners=this.listeners||{},a in this.listeners?this.listeners[a].push(b):this.listeners[a]=[b]},d.prototype.trigger=function(a){var b=Array.prototype.slice;this.listeners=this.listeners||{},a in this.listeners&&this.invoke(this.listeners[a],b.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},d.prototype.invoke=function(a,b){for(var c=0,d=a.length;d>c;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;a>c;c++){var d=Math.floor(36*Math.random());b+=d.toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e<c.length;e++){var f=c[e];f=f.substring(0,1).toLowerCase()+f.substring(1),f in d||(d[f]={}),e==c.length-1&&(d[f]=a[b]),d=d[f]}delete a[b]}}return a},c.hasScroll=function(b,c){var d=a(c),e=c.style.overflowX,f=c.style.overflowY;return e!==f||"hidden"!==f&&"visible"!==f?"scroll"===e||"scroll"===f?!0:d.innerHeight()<c.scrollHeight||d.innerWidth()<c.scrollWidth:!1},c.escapeMarkup=function(a){var b={"\\":"&#92;","&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#47;"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<ul class="select2-results__options" role="tree"></ul>');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('<li role="treeitem" class="select2-results__option"></li>'),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),this.$results.append(d)},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c<a.results.length;c++){var d=a.results[c],e=this.option(d);b.push(e)}this.$results.append(b)},c.prototype.position=function(a,b){var c=b.find(".select2-results");c.append(a)},c.prototype.sort=function(a){var b=this.options.get("sorter");return b(a)},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()}),e=b.$results.find(".select2-results__option[aria-selected]");e.each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("aria-selected","true"):b.attr("aria-selected","false")});var f=e.filter("[aria-selected=true]");f.length>0?f.first().trigger("mouseenter"):e.first().trigger("mouseenter")})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"treeitem","aria-selected":"false"};b.disabled&&(delete d["aria-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["aria-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d.role="group",d["aria-label"]=b.text,delete d["aria-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";{a(h)}this.template(b,h);for(var i=[],j=0;j<b.children.length;j++){var k=b.children[j],l=this.option(k);i.push(l)}var m=a("<ul></ul>",{"class":"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b){var c=this,d=b.id+"-results";this.$results.attr("id",d),b.on("results:all",function(a){c.clear(),c.append(a.data),b.isOpen()&&c.setClasses()}),b.on("results:append",function(a){c.append(a.data),b.isOpen()&&c.setClasses()}),b.on("query",function(a){c.showLoading(a)}),b.on("select",function(){b.isOpen()&&c.setClasses()}),b.on("unselect",function(){b.isOpen()&&c.setClasses()}),b.on("open",function(){c.$results.attr("aria-expanded","true"),c.$results.attr("aria-hidden","false"),c.setClasses(),c.ensureHighlightVisible()}),b.on("close",function(){c.$results.attr("aria-expanded","false"),c.$results.attr("aria-hidden","true"),c.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=c.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=c.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("aria-selected")?c.trigger("close"):c.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=c.getHighlightedResults(),b=c.$results.find("[aria-selected]"),d=b.index(a);if(0!==d){var e=d-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=c.$results.offset().top,h=f.offset().top,i=c.$results.scrollTop()+(h-g);0===e?c.$results.scrollTop(0):0>h-g&&c.$results.scrollTop(i)}}),b.on("results:next",function(){var a=c.getHighlightedResults(),b=c.$results.find("[aria-selected]"),d=b.index(a),e=d+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=c.$results.offset().top+c.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=c.$results.scrollTop()+h-g;0===e?c.$results.scrollTop(0):h>g&&c.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted")}),b.on("results:message",function(a){c.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=c.$results.scrollTop(),d=c.$results.get(0).scrollHeight-c.$results.scrollTop()+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&d<=c.$results.height();e?(c.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(c.$results.scrollTop(c.$results.get(0).scrollHeight-c.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[aria-selected]",function(b){var d=a(this),e=d.data("data");return"true"===d.attr("aria-selected")?void(c.options.get("multiple")?c.trigger("unselect",{originalEvent:b,data:e}):c.trigger("close")):void c.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(){var b=a(this).data("data");c.getHighlightedResults().removeClass("select2-results__option--highlighted"),c.trigger("results:focus",{data:b,element:a(this)})})},c.prototype.getHighlightedResults=function(){var a=this.$results.find(".select2-results__option--highlighted");return a},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),2>=c?this.$results.scrollTop(0):(g>this.$results.outerHeight()||0>g)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){var a={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46};return a}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('<span class="select2-selection" role="combobox" aria-autocomplete="list" aria-haspopup="true" aria-expanded="false"></span>');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a){var b=this,d=(a.id+"-container",a.id+"-results");this.container=a,this.$selection.on("focus",function(a){b.trigger("focus",a)}),this.$selection.on("blur",function(a){b.trigger("blur",a)}),this.$selection.on("keydown",function(a){b.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){b.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){b.update(a.data)}),a.on("open",function(){b.$selection.attr("aria-expanded","true"),b.$selection.attr("aria-owns",d),b._attachCloseHandler(a)}),a.on("close",function(){b.$selection.attr("aria-expanded","false"),b.$selection.removeAttr("aria-activedescendant"),b.$selection.removeAttr("aria-owns"),b.$selection.focus(),b._detachCloseHandler(a)}),a.on("enable",function(){b.$selection.attr("tabindex",b._tabindex)}),a.on("disable",function(){b.$selection.attr("tabindex","-1")})},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2"),e=a(".select2.select2-container--open");e.each(function(){var b=a(this);if(this!=d[0]){var c=b.data("element");c.select2("close")}})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){var c=b.find(".selection");c.append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c){function d(){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html('<span class="select2-selection__rendered"></span><span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span>'),a},d.prototype.bind=function(a){var b=this;d.__super__.bind.apply(this,arguments);var c=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",c),this.$selection.attr("aria-labelledby",c),this.$selection.on("mousedown",function(a){1===a.which&&b.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(){}),this.$selection.on("blur",function(){}),a.on("selection:update",function(a){b.update(a.data)})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a){var b=this.options.get("templateSelection"),c=this.options.get("escapeMarkup");return c(b(a))},d.prototype.selectionContainer=function(){return a("<span></span>")},d.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.display(b),d=this.$selection.find(".select2-selection__rendered");d.empty().append(c),d.prop("title",b.title||b.text)},d}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('<ul class="select2-selection__rendered"></ul>'),a},d.prototype.bind=function(){var b=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){b.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(c){var d=a(this),e=d.parent(),f=e.data("data");b.trigger("unselect",{originalEvent:c,data:f})})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a){var b=this.options.get("templateSelection"),c=this.options.get("escapeMarkup");return c(b(a))},d.prototype.selectionContainer=function(){var b=a('<li class="select2-selection__choice"><span class="select2-selection__choice__remove" role="presentation">&times;</span></li>');return b},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d<a.length;d++){var e=a[d],f=this.display(e),g=this.selectionContainer();g.append(f),g.prop("title",e.title||e.text),g.data("data",e),b.push(g)}var h=this.$selection.find(".select2-selection__rendered");c.appendMany(h,b)}},d}),b.define("select2/selection/placeholder",["../utils"],function(){function a(a,b,c){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c)}return a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.createPlaceholder=function(a,b){var c=this.selectionContainer();return c.html(this.display(b)),c.addClass("select2-selection__placeholder").removeClass("select2-selection__choice"),c},a.prototype.update=function(a,b){var c=1==b.length&&b[0].id!=this.placeholder.id,d=b.length>1;if(d||c)return a.call(this,b);this.clear();var e=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(e)},a}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e<d.length;e++){var f={data:d[e]};if(this.trigger("unselect",f),f.prevented)return}this.$element.val(this.placeholder.id).trigger("change"),this.trigger("toggle")}}},c.prototype._handleKeyboardClear=function(a,c,d){d.isOpen()||(c.which==b.DELETE||c.which==b.BACKSPACE)&&this._handleClear(c)},c.prototype.update=function(b,c){if(b.call(this,c),!(this.$selection.find(".select2-selection__placeholder").length>0||0===c.length)){var d=a('<span class="select2-selection__clear">&times;</span>');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('<li class="select2-search select2-search--inline"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" /></li>');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return d},d.prototype.bind=function(a,b,d){var e=this;a.call(this,b,d),b.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus()}),b.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val(""),e.$search.focus()}),b.on("enable",function(){e.$search.prop("disabled",!1)}),b.on("disable",function(){e.$search.prop("disabled",!0)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e.trigger("blur",a)}),this.$selection.on("keydown",".select2-search--inline",function(a){a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented();var b=a.which;if(b===c.BACKSPACE&&""===e.$search.val()){var d=e.$searchContainer.prev(".select2-selection__choice");if(d.length>0){var f=d.data("data");e.searchRemoveChoice(f),a.preventDefault()}}}),this.$selection.on("input",".select2-search--inline",function(){e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input",".select2-search--inline",function(a){e.handleSearch(a)})},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.trigger("open"),this.$search.val(b.text+" ")},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{var b=this.$search.val().length+1;a=.75*b+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){var a={"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"};return a}),b.define("select2/data/base",["../utils"],function(a){function b(){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),d+=null!=c.id?"-"+c.id.toString():"-"+a.generateChars(4)},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f<a.length;f++){var g=a[f].id;-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")});else{var d=a.id;this.$element.val(d),this.$element.trigger("change")}},d.prototype.unselect=function(a){var b=this;if(this.$element.prop("multiple"))return a.selected=!1,c(a.element).is("option")?(a.element.selected=!1,void this.$element.trigger("change")):void this.current(function(d){for(var e=[],f=0;f<d.length;f++){var g=d[f].id;g!==a.id&&-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")})},d.prototype.bind=function(a){var b=this;this.container=a,a.on("select",function(a){b.select(a.data)}),a.on("unselect",function(a){b.unselect(a.data)})},d.prototype.destroy=function(){this.$element.find("*").each(function(){c.removeData(this,"data")})},d.prototype.query=function(a,b){var d=[],e=this,f=this.$element.children();f.each(function(){var b=c(this);if(b.is("option")||b.is("optgroup")){var f=e.item(b),g=e.matches(a,f);null!==g&&d.push(g)}}),b({results:d})},d.prototype.addOptions=function(a){b.appendMany(this.$element,a)},d.prototype.option=function(a){var b;a.children?(b=document.createElement("optgroup"),b.label=a.text):(b=document.createElement("option"),void 0!==b.textContent?b.textContent=a.text:b.innerText=a.text),a.id&&(b.value=a.id),a.disabled&&(b.disabled=!0),a.selected&&(b.selected=!0),a.title&&(b.title=a.title);var d=c(b),e=this._normalizeItem(a);return e.element=b,c.data(b,"data",e),d},d.prototype.item=function(a){var b={};
2
+ if(b=c.data(a[0],"data"),null!=b)return b;if(a.is("option"))b={id:a.val(),text:a.text(),disabled:a.prop("disabled"),selected:a.prop("selected"),title:a.prop("title")};else if(a.is("optgroup")){b={text:a.prop("label"),children:[],title:a.prop("title")};for(var d=a.children("option"),e=[],f=0;f<d.length;f++){var g=c(d[f]),h=this.item(g);e.push(h)}b.children=e}return b=this._normalizeItem(b),b.element=a[0],c.data(a[0],"data",b),b},d.prototype._normalizeItem=function(a){c.isPlainObject(a)||(a={id:a,text:a}),a=c.extend({},{text:""},a);var b={selected:!1,disabled:!1};return null!=a.id&&(a.id=a.id.toString()),null!=a.text&&(a.text=a.text.toString()),null==a._resultId&&a.id&&null!=this.container&&(a._resultId=this.generateResultId(this.container,a)),c.extend({},b,a)},d.prototype.matches=function(a,b){var c=this.options.get("matcher");return c(a,b)},d}),b.define("select2/data/array",["./select","../utils","jquery"],function(a,b,c){function d(a,b){var c=b.get("data")||[];d.__super__.constructor.call(this,a,b),this.addOptions(this.convertToOptions(c))}return b.Extend(d,a),d.prototype.select=function(a){var b=this.$element.find("option").filter(function(b,c){return c.value==a.id.toString()});0===b.length&&(b=this.option(a),this.addOptions(b)),d.__super__.select.call(this,a)},d.prototype.convertToOptions=function(a){function d(a){return function(){return c(this).val()==a.id}}for(var e=this,f=this.$element.find("option"),g=f.map(function(){return e.item(c(this)).id}).get(),h=[],i=0;i<a.length;i++){var j=this._normalizeItem(a[i]);if(c.inArray(j.id,g)>=0){var k=f.filter(d(j)),l=this.item(k),m=(c.extend(!0,{},l,j),this.option(l));k.replaceWith(m)}else{var n=this.option(j);if(j.children){var o=this.convertToOptions(j.children);b.appendMany(n,o)}h.push(n)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(b,c){this.ajaxOptions=this._applyDefaults(c.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),a.__super__.constructor.call(this,b,c)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return{q:a.term}},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url(a)),"function"==typeof f.data&&(f.data=f.data(a)),this.ajaxOptions.delay&&""!==a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");if(void 0!==f&&(this.createTag=f),b.call(this,c,d),a.isArray(e))for(var g=0;g<e.length;g++){var h=e[g],i=this._normalizeItem(h),j=this.option(i);this.$element.append(j)}}return b.prototype.query=function(a,b,c){function d(a,f){for(var g=a.results,h=0;h<g.length;h++){var i=g[h],j=null!=i.children&&!d({results:i.children},!0),k=i.text===b.term;if(k||j)return f?!1:(a.data=g,void c(a))}if(f)return!0;var l=e.createTag(b);if(null!=l){var m=e.option(l);m.attr("data-select2-tag",!0),e.addOptions([m]),e.insertTag(g,l)}a.results=g,c(a)}var e=this;return this._removeOldTags(),null==b.term||null!=b.page?void a.call(this,b,c):void a.call(this,b,d)},b.prototype.createTag=function(b,c){var d=a.trim(c.term);return""===d?null:{id:d,text:d}},b.prototype.insertTag=function(a,b,c){b.unshift(c)},b.prototype._removeOldTags=function(){var b=(this._lastTag,this.$element.find("option[data-select2-tag]"));b.each(function(){this.selected||a(this).remove()})},b}),b.define("select2/data/tokenizer",["jquery"],function(a){function b(a,b,c){var d=c.get("tokenizer");void 0!==d&&(this.tokenizer=d),a.call(this,b,c)}return b.prototype.bind=function(a,b,c){a.call(this,b,c),this.$search=b.dropdown.$search||b.selection.$search||c.find(".select2-search__field")},b.prototype.query=function(a,b,c){function d(a){e.select(a)}var e=this;b.term=b.term||"";var f=this.tokenizer(b,this.options,d);f.term!==b.term&&(this.$search.length&&(this.$search.val(f.term),this.$search.focus()),b.term=f.term),a.call(this,b,c)},b.prototype.tokenizer=function(b,c,d,e){for(var f=d.get("tokenSeparators")||[],g=c.term,h=0,i=this.createTag||function(a){return{id:a.term,text:a.term}};h<g.length;){var j=g[h];if(-1!==a.inArray(j,f)){var k=g.substr(0,h),l=a.extend({},c,{term:k}),m=i(l);e(m),g=g.substr(h+1)||"",h=0}else h++}return{term:g}},b}),b.define("select2/data/minimumInputLength",[],function(){function a(a,b,c){this.minimumInputLength=c.get("minimumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",b.term.length<this.minimumInputLength?void this.trigger("results:message",{message:"inputTooShort",args:{minimum:this.minimumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumInputLength",[],function(){function a(a,b,c){this.maximumInputLength=c.get("maximumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",this.maximumInputLength>0&&b.term.length>this.maximumInputLength?void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;return d.maximumSelectionLength>0&&f>=d.maximumSelectionLength?void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}}):void a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<span class="select2-dropdown"><span class="select2-results"></span></span>');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.position=function(){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a){function b(){}return b.prototype.render=function(b){var c=b.call(this),d=a('<span class="select2-search select2-search--dropdown"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" /></span>');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val("")}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){var b=e.showSearch(a);b?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}})},b.prototype.handleSearch=function(){if(!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},b.prototype.showSearch=function(){return!0},b}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){var c=e.$results.offset().top+e.$results.outerHeight(!1),d=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1);c+50>=d&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('<li class="option load-more" role="treeitem"></li>'),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(a,b,c){this.$dropdownParent=c.get("dropdownParent")||document.body,a.call(this,b,c)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a("<span></span>"),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c){var d=this,e="scroll.select2."+c.id,f="resize.select2."+c.id,g="orientationchange.select2."+c.id,h=this.$container.parents().filter(b.hasScroll);h.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),h.on(e,function(){var b=a(this).data("select2-scroll-position");a(this).scrollTop(b.y)}),a(window).on(e+" "+f+" "+g,function(){d._positionDropdown(),d._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c){var d="scroll.select2."+c.id,e="resize.select2."+c.id,f="orientationchange.select2."+c.id,g=this.$container.parents().filter(b.hasScroll);g.off(d),a(window).off(d+" "+e+" "+f)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=(this.$container.position(),this.$container.offset());f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.top<f.top-h.height,k=i.bottom>f.bottom+h.height,l={left:f.left,top:g.bottom};c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){this.$dropdownContainer.width();var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d<b.length;d++){var e=b[d];e.children?c+=a(e.children):c++}return c}function b(a,b,c,d){this.minimumResultsForSearch=c.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),a.call(this,b,c,d)}return b.prototype.showSearch=function(b,c){return a(c.data.results)<this.minimumResultsForSearch?!1:b.call(this,c)},b}),b.define("select2/dropdown/selectOnClose",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("close",function(){d._handleSelectOnClose()})},a.prototype._handleSelectOnClose=function(){var a=this.getHighlightedResults();a.length<1||this.trigger("select",{data:a.data("data")})},a}),b.define("select2/dropdown/closeOnSelect",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("select",function(a){d._selectTriggered(a)}),b.on("unselect",function(a){d._selectTriggered(a)})},a.prototype._selectTriggered=function(a,b){var c=b.originalEvent;c&&c.ctrlKey||this.trigger("close")},a}),b.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(a){var b=a.input.length-a.maximum,c="Please delete "+b+" character";return 1!=b&&(c+="s"),c},inputTooShort:function(a){var b=a.minimum-a.input.length,c="Please enter "+b+" or more characters";return c},loadingMore:function(){return"Loading more results…"},maximumSelected:function(a){var b="You can only select "+a.maximum+" item";return 1!=a.maximum&&(b+="s"),b},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),b.define("select2/defaults",["jquery","require","./results","./selection/single","./selection/multiple","./selection/placeholder","./selection/allowClear","./selection/search","./selection/eventRelay","./utils","./translation","./diacritics","./data/select","./data/array","./data/ajax","./data/tags","./data/tokenizer","./data/minimumInputLength","./data/maximumInputLength","./data/maximumSelectionLength","./dropdown","./dropdown/search","./dropdown/hidePlaceholder","./dropdown/infiniteScroll","./dropdown/attachBody","./dropdown/minimumResultsForSearch","./dropdown/selectOnClose","./dropdown/closeOnSelect","./i18n/en"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C){function D(){this.reset()}D.prototype.apply=function(l){if(l=a.extend({},this.defaults,l),null==l.dataAdapter){if(l.dataAdapter=null!=l.ajax?o:null!=l.data?n:m,l.minimumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),(null!=l.tokenSeparators||null!=l.tokenizer)&&(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.selectionAdapter=l.multiple?e:d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L<K.length;L++){var M=K[L],N={};try{N=k.loadPath(M)}catch(O){try{M=this.defaults.amdLanguageBase+M,N=k.loadPath(M)}catch(P){l.debug&&window.console&&console.warn&&console.warn('Select2: The language file for "'+M+'" could not be automatically loaded. A fallback will be used instead.');continue}}J.extend(N)}l.translations=J}else{var Q=k.loadPath(this.defaults.amdLanguageBase+"en"),R=new k(l.language);R.extend(Q),l.translations=R}return l},D.prototype.reset=function(){function b(a){function b(a){return l[a]||a}return a.replace(/[^\u0000-\u007E]/g,b)}function c(d,e){if(""===a.trim(d.term))return e;if(e.children&&e.children.length>0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){var h=e.children[g],i=c(d,h);null==i&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var j=b(e.text).toUpperCase(),k=b(d.term).toUpperCase();return j.indexOf(k)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)};var E=new D;return E}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(this.options.dir=a.prop("dir")?a.prop("dir"):a.closest("[dir]").prop("dir")?a.closest("[dir]").prop("dir"):"ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return 0>=e?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;i>h;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this._sync=c.bind(this._syncAttributes,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._sync);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._sync)}),this._observer.observe(this.$element[0],{attributes:!0,subtree:!1})):this.$element[0].addEventListener&&this.$element[0].addEventListener("DOMAttrModified",b._sync,!1)},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("focus",function(){a.$container.addClass("select2-container--focus")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open"),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ENTER?(a.trigger("results:select"),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle"),b.preventDefault()):c===d.UP?(a.trigger("results:previous"),b.preventDefault()):c===d.DOWN?(a.trigger("results:next"),b.preventDefault()):(c===d.ESC||c===d.TAB)&&(a.close(),b.preventDefault()):(c===d.ENTER||c===d.SPACE||(c===d.DOWN||c===d.UP)&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable")):this.trigger("enable")},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||(this.trigger("query",{}),this.trigger("open"))},e.prototype.close=function(){this.isOpen()&&this.trigger("close")},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),(null==a||0===a.length)&&(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._sync),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&this.$element[0].removeEventListener("DOMAttrModified",this._sync,!1),this._sync=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('<span class="select2 select2-container"><span class="selection"></span><span class="dropdown-wrapper" aria-hidden="true"></span></span>');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("jquery.select2",["jquery","require","./select2/core","./select2/defaults"],function(a,b,c,d){if(b("jquery.mousewheel"),null==a.fn.select2){var e=["open","close","destroy"];a.fn.select2=function(b){if(b=b||{},"object"==typeof b)return this.each(function(){{var d=a.extend({},b,!0);new c(a(this),d)}}),this;if("string"==typeof b){var d=this.data("select2");null==d&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2.");var f=Array.prototype.slice.call(arguments,1),g=d[b](f);return a.inArray(b,e)>-1?this:g}throw new Error("Invalid arguments for Select2: "+b)}}return null==a.fn.select2.defaults&&(a.fn.select2.defaults=d),c}),b.define("jquery.mousewheel",["jquery"],function(a){return a}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,c});
lib/compat.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!function_exists('str_getcsv')) {
4
+
5
+ function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = null, $eol = null) {
6
+ $temp = fopen("php://memory", "rw");
7
+ fwrite($temp, $input);
8
+ fseek($temp, 0);
9
+ $r = array();
10
+ while (($data = fgetcsv($temp, 0, $delimiter, $enclosure, $escape)) !== false) {
11
+ $r[] = $data;
12
+ }
13
+ fclose($temp);
14
+ return $r;
15
+ }
16
+
17
+ }
lib/dashboard.php CHANGED
@@ -32,7 +32,6 @@
32
  <?php if(wfConfig::get('scansEnabled_themes')){ ?><tr><td style="padding-right: 20px;">Scan Themes:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
33
  <?php if(wfConfig::get('scansEnabled_plugins')){ ?><tr><td style="padding-right: 20px;">Scan Plugins:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
34
  <?php if(wfConfig::get('scansEnabled_fileContents')){ ?><tr><td style="padding-right: 20px;">Scan Other Files:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
35
- <?php if(wfConfig::get('scansEnabled_database')){ ?><tr><td style="padding-right: 20px;">Scan Database:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
36
  <?php if(wfConfig::get('scansEnabled_posts')){ ?><tr><td style="padding-right: 20px;">Scan Posts:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
37
  <?php if(wfConfig::get('scansEnabled_comments')){ ?><tr><td style="padding-right: 20px;">Scan Comments:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
38
  <?php if(wfConfig::get('scansEnabled_oldVersions')){ ?><tr><td style="padding-right: 20px;">Scan for Old Software:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
32
  <?php if(wfConfig::get('scansEnabled_themes')){ ?><tr><td style="padding-right: 20px;">Scan Themes:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
33
  <?php if(wfConfig::get('scansEnabled_plugins')){ ?><tr><td style="padding-right: 20px;">Scan Plugins:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
34
  <?php if(wfConfig::get('scansEnabled_fileContents')){ ?><tr><td style="padding-right: 20px;">Scan Other Files:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
 
35
  <?php if(wfConfig::get('scansEnabled_posts')){ ?><tr><td style="padding-right: 20px;">Scan Posts:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
36
  <?php if(wfConfig::get('scansEnabled_comments')){ ?><tr><td style="padding-right: 20px;">Scan Comments:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
37
  <?php if(wfConfig::get('scansEnabled_oldVersions')){ ?><tr><td style="padding-right: 20px;">Scan for Old Software:</td><td style="color: #0F0;">&#10004;</td></tr> <?php } ?>
lib/email_newIssues.php CHANGED
@@ -17,7 +17,13 @@
17
  <?php foreach($issues as $i){ if($i['severity'] == 1){ ?>
18
  <p>* <?php echo htmlspecialchars($i['shortMsg']) ?></p>
19
  <?php if (!empty($i['tmplData']['badURL'])): ?>
20
- <p><img src="<?php echo sprintf("http://noc1.wordfence.com/v2.14/?v=%s&s=%s&k=%s&action=image&txt=%s", rawurlencode(wfUtils::getWPVersion()), rawurlencode(home_url()), rawurlencode(wfConfig::get('apiKey')), rawurlencode(base64_encode($i['tmplData']['badURL']))) ?>" alt="" /></p>
 
 
 
 
 
 
21
  <?php endif ?>
22
 
23
  <?php } } } ?>
@@ -33,11 +39,13 @@
33
 
34
  <?php if(! $isPaid){ ?>
35
  <p>NOTE: You are using the free version of Wordfence. Upgrade to Premium today for less than $5 per month!</p>
 
36
  <ul>
37
- <li>Advanced features like IP reputation monitoring, country blocking, an advanced comment spam filter and cell phone sign-in give you the best protection available</li>
 
38
  <li>Remote, frequent and scheduled scans</li>
39
  <li>Access to Premium Support</li>
40
- <li>Discounts of up to 90% for multiyear and multi-license purchases</li>
41
  </ul>
42
 
43
  <p>
17
  <?php foreach($issues as $i){ if($i['severity'] == 1){ ?>
18
  <p>* <?php echo htmlspecialchars($i['shortMsg']) ?></p>
19
  <?php if (!empty($i['tmplData']['badURL'])): ?>
20
+ <p><img src="<?php echo WORDFENCE_API_URL_BASE_NONSEC . "?" . http_build_query(array(
21
+ 'v' => wfUtils::getWPVersion(),
22
+ 's' => home_url(),
23
+ 'k' => wfConfig::get('apiKey'),
24
+ 'action' => 'image',
25
+ 'txt' => base64_encode($i['tmplData']['badURL'])
26
+ ), '', '&') ?>" alt="" /></p>
27
  <?php endif ?>
28
 
29
  <?php } } } ?>
39
 
40
  <?php if(! $isPaid){ ?>
41
  <p>NOTE: You are using the free version of Wordfence. Upgrade to Premium today for less than $5 per month!</p>
42
+
43
  <ul>
44
+ <li>Receive real-time Firewall and Scan engine rule updates for protection as threats emerge</li>
45
+ <li>Other advanced features like IP reputation monitoring, country blocking, an advanced comment spam filter and cell phone sign-in give you the best protection available</li>
46
  <li>Remote, frequent and scheduled scans</li>
47
  <li>Access to Premium Support</li>
48
+ <li>Discounts of up to 75% for multiyear and multi-license purchases</li>
49
  </ul>
50
 
51
  <p>
lib/menu_activity.php CHANGED
@@ -1,14 +1,16 @@
1
- <div class="wordfenceModeElem" id="wordfenceMode_activity"></div>
2
  <div class="wrap wordfence">
3
  <?php require('menuHeader.php'); ?>
4
 
5
  <h2 id="wfHeading">
6
  <div style="float: left;">
7
- Your Site Activity in Real-Time
8
  </div>
9
  <div class="wordfenceWrap" style="margin: 5px 0 0 15px; float: left;">
10
  <div class="wfOnOffSwitch" id="wfOnOffSwitchID">
11
- <input type="checkbox" name="wfOnOffSwitch" class="wfOnOffSwitch-checkbox" id="wfLiveTrafficOnOff" <?php if(wfConfig::liveTrafficEnabled()){ echo ' checked '; } ?>>
 
 
 
12
  <label class="wfOnOffSwitch-label" for="wfLiveTrafficOnOff">
13
  <div class="wfOnOffSwitch-inner"></div>
14
  <div class="wfOnOffSwitch-switch"></div>
@@ -16,224 +18,429 @@
16
  </div>
17
  </div>
18
  </h2>
19
- <br clear="both" />
20
- <a href="http://docs.wordfence.com/en/Live_traffic" target="_blank" class="wfhelp"></a><a href="http://docs.wordfence.com/en/Live_traffic" target="_blank">Learn more about Wordfence Live Traffic</a>
 
 
21
  <div class="wordfenceLive">
22
  <table border="0" cellpadding="0" cellspacing="0">
23
- <tr><td><h2>Wordfence Live Activity:</h2></td><td id="wfLiveStatus"></td></tr>
 
 
 
24
  </table>
25
  </div>
26
  <div class="wordfenceWrap">
27
- <div>
28
- <?php if(! wfConfig::liveTrafficEnabled()){ ?>
29
  <div style="color: #F00;">
30
  Live Traffic is disabled.
31
- <?php if(wfConfig::get('cacheType') == 'falcon'){ ?>This is done to improve performance because you have Wordfence Falcon Engine enabled.<?php } ?>
32
  </div>
33
- <?php } ?>
34
- <div id="wfTabs">
35
- <?php if(wfConfig::liveTrafficEnabled()){ ?>
36
- <a href="#" class="wfTab1 wfTabSwitch selected" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_hit', function(){ WFAD.activityTabChanged(); }); return false;">All Hits</a>
37
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_human', function(){ WFAD.activityTabChanged(); }); return false;">Humans</a>
38
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_ruser', function(){ WFAD.activityTabChanged(); }); return false;">Registered Users</a>
39
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_crawler', function(){ WFAD.activityTabChanged(); }); return false;">Crawlers</a>
40
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_gCrawler', function(){ WFAD.activityTabChanged(); }); return false;">Google Crawlers</a>
41
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_404', function(){ WFAD.activityTabChanged(); }); return false;">Pages Not Found</a>
42
- <?php } ?>
43
- <a href="#" id="wfLoginLogoutTab" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_loginLogout', function(){ WFAD.activityTabChanged(); }); return false;">Logins and Logouts</a>
44
- <?php if(wfConfig::liveTrafficEnabled()){ ?>
45
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_topLeechers', function(){ WFAD.staticTabChanged(); }); return false;">Top Consumers</a>
46
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchTab(this, 'wfTab1', 'wfDataPanel', 'wfActivity_topScanners', function(){ WFAD.staticTabChanged(); }); return false;">Top 404s</a>
47
- <?php } ?>
48
- </div>
49
- <div class="wfTabsContainer">
50
- <div id="wfActivity_hit" class="wfDataPanel"><div class="wfLoadingWhite32"></div></div>
51
- <div id="wfActivity_human" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
52
- <div id="wfActivity_ruser" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
53
- <div id="wfActivity_crawler" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
54
- <div id="wfActivity_gCrawler" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
55
- <div id="wfActivity_404" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
56
- <div id="wfActivity_loginLogout" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
57
- <div id="wfActivity_topScanners" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
58
- <div id="wfActivity_topLeechers" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
59
- <div id="wfActivity_blockedIPs" class="wfDataPanel" style="display: none;"><div class="wfLoadingWhite32"></div></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </div>
61
- </div>
62
  </div>
63
  </div>
64
 
65
- <script type="text/x-jquery-template" id="wfLeechersTmpl">
66
- <div>
67
- <div style="border-bottom: 1px solid #CCC; padding-bottom: 10px; margin-bottom: 10px;">
68
- <table border="0" style="width: 100%">
69
- {{each(idx, elem) results}}
70
- <tr><td>
71
- <div>
72
- {{if loc}}
73
- <img src="//www.wordfence.com/images/flags/${loc.countryCode.toLowerCase()}.png" width="16" height="11" alt="${loc.countryName}" title="${loc.countryName}" class="wfFlag" />
74
- <a href="http://maps.google.com/maps?q=${loc.lat},${loc.lon}&z=6" target="_blank">{{if loc.city}}${loc.city}, {{/if}}${loc.countryName}</a>
75
- {{else}}
76
- An unknown location at IP <a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a>
77
- {{/if}}
78
- </div>
79
- <div>
80
- <strong>IP:</strong>&nbsp;<a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a>
81
- {{if elem.blocked}}
82
- [<a href="#" onclick="WFAD.unblockIP('${IP}'); return false;">unblock</a>]
83
- {{else}}
84
- [<a href="#" onclick="WFAD.blockIP('${IP}', 'Manual block by administrator'); return false;">block</a>]
85
- {{/if}}
86
- </div>
87
- <div>
88
- <span class="wfReverseLookup"><span style="display:none;">${elem.IP}</span></span>
89
- </div>
90
- <div>
91
- <span class="wfTimeAgo wfTimeAgo-timestamp" data-timestamp="${elem.timestamp}">Last hit was ${elem.timeAgo} ago.</span>
92
- </div>
93
- </td>
94
- <td style="font-size: 28px; color: #999;">
95
- ${elem.totalHits} hits
96
- </td>
97
- </tr>
98
- {{/each}}
99
- </table>
100
- </div>
101
- </div>
102
- </script>
103
- <script type="text/x-jquery-template" id="wfLoginLogoutEventTmpl">
104
- <div style="display: none;">
105
- <div class="wfActEvent" id="wfActEvent_${id}">
106
- <div>
107
- {{if loc}}
108
- <img src="//www.wordfence.com/images/flags/${loc.countryCode.toLowerCase()}.png" width="16" height="11" alt="${loc.countryName}" title="${loc.countryName}" class="wfFlag" />
109
- <a href="http://maps.google.com/maps?q=${loc.lat},${loc.lon}&z=6" target="_blank">{{if loc.city}}${loc.city}, {{/if}}${loc.countryName}</a>
110
- {{else}}
111
- An unknown location at IP ${IP}
112
- {{/if}}
113
- {{if action == 'loginOK'}}
114
- logged in successfully as <strong>"${username}"</strong>
115
- {{else action == 'logout'}}
116
- logged out as <strong>"${username}"</strong>
117
- {{else action == 'loginFailValidUsername'}}
118
- attempted a failed login as <strong>"${username}"</strong>.
119
- {{else action == 'loginFailInvalidUsername'}}
120
- attempted a failed login using an invalid username <strong>"${username}"</strong>.
121
- {{/if}}
122
- </div>
123
- <div>
124
- <strong>IP:</strong> <a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a>&nbsp;
125
- {{if blocked}}
126
- [<a href="#" onclick="WFAD.unblockIP('${IP}'); return false;">unblock</a>]
127
- {{else}}
128
- [<a href="#" onclick="WFAD.blockIP('${IP}', 'Manual block by administrator'); return false;">block</a>]
129
- {{/if}}
130
- </div>
131
- <div>
132
- <span class="wfReverseLookup"><span style="display:none;">${IP}</span></span>
133
- </div>
134
- <div>
135
- <span class="wfTimeAgo wfTimeAgo-timestamp">${timeAgo} ago</span>
136
- </div>
137
- </div>
138
- </div>
139
- </script>
140
- <script type="text/x-jquery-template" id="wfHitsEventTmpl">
141
- <div style="display: none;">
142
- <div class="wfActEvent" id="wfActEvent_${id}">
143
- <table border="0" cellpadding="1" cellspacing="0">
144
- <tr>
145
- <td>
146
- {{if user}}
147
- <span class="wfAvatar">{{html user.avatar}}</span>
148
- <a href="${user.editLink}" target="_blank">${user.display_name}</a>
149
- {{/if}}
150
- {{if loc}}
151
- {{if user}}in {{/if}}
152
- <img src="//www.wordfence.com/images/flags/${loc.countryCode.toLowerCase()}.png" width="16" height="11" alt="${loc.countryName}" title="${loc.countryName}" class="wfFlag" />
153
- <a href="http://maps.google.com/maps?q=${loc.lat},${loc.lon}&z=6" target="_blank">{{if loc.city}}${loc.city}, {{/if}}${loc.countryName}</a>
154
- {{else}}
155
- An unknown location at IP <a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a>
156
- {{/if}}
157
- {{if referer}}
158
- {{if extReferer}}
159
- arrived from <a href="${referer}" target="_blank" style="color: #A00; font-weight: bold;">${referer}</a> and
160
- {{else}}
161
- left <a href="${referer}" target="_blank" style="color: #999; font-weight: normal;">${referer}</a> and
162
- {{/if}}
163
- {{/if}}
164
- {{if is404 == '1'}}
165
- tried to access <span style="color: #F00;">non-existent page</span>
166
- {{else}}
167
- visited
168
- {{/if}}
169
- <a href="${URL}" target="_blank">${URL}</a>
170
- </td></tr>
171
- <tr><td><span class="wfTimeAgo wfTimeAgo-timestamp">${timeAgo} ago</span>&nbsp;&nbsp; <strong>IP:</strong> <a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a>
172
- {{if blocked}}
173
- [<a href="#" onclick="WFAD.unblockIP('${IP}'); return false;">unblock</a>]
174
- {{else rangeBlocked}}
175
- [<a href="#" onclick="WFAD.unblockNetwork('${ipRangeID}'); return false;">unblock this range</a>]
176
- {{else}}
177
- [<a href="#" onclick="WFAD.blockIP('${IP}', 'Manual block by administrator'); return false;">block</a>]
178
- {{/if}}
179
- &nbsp;<span class="wfReverseLookup"><span style="display:none;">${IP}</span></span>
180
- </td></tr>
181
- {{if browser && browser.browser != 'Default Browser'}}<tr><td><strong>Browser:</strong> ${browser.browser}{{if browser.version}} version ${browser.version}{{/if}}{{if browser.platform && browser.platform != 'unknown'}} running on ${browser.platform}{{/if}}</td></tr>{{/if}}
182
- <tr><td style="color: #AAA;">${UA}</td></tr>
183
- <tr><td>
184
- {{if blocked}}
185
- [<a href="#" onclick="WFAD.unblockIP('${IP}'); return false;">Unblock this IP</a>]
186
- {{else rangeBlocked}}
187
- [<a href="#" onclick="WFAD.unblockNetwork('${ipRangeID}'); return false;">Unblock this range</a>]
188
- {{else}}
189
- [<a href="#" onclick="WFAD.blockIP('${IP}', 'Manual block by administrator'); return false;">Block this IP</a>]
190
- {{/if}}
191
- &nbsp;&nbsp;&mdash;&nbsp;&nbsp;
192
- [<a href="admin.php?page=WordfenceWhois&whoisval=${IP}&wfnetworkblock=1">Block this network</a>]
193
- &nbsp;&nbsp;&mdash;&nbsp;&nbsp;
194
- [<a href="admin.php?page=WordfenceWhois&whoisval=${IP}">Run WHOIS on ${IP}</a>]
195
- &nbsp;&nbsp;&mdash;&nbsp;&nbsp;
196
- [<a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">See recent traffic</a>]
197
- <tr><td></td></tr>
198
- </table>
199
- </div>
200
- </div>
201
- </script>
202
  <script type="text/x-jquery-template" id="wfWelcomeContent3">
203
- <div>
204
- <h3>Welcome to ALL Your Site Visits, Live!</h3>
205
- <strong><p>Traffic you've never seen before</p></strong>
206
- <p>
207
- Google Analytics and other Javascript analytics packages can't show you crawlers, RSS feed readers, hack attempts and other non-human traffic that hits your site.
208
- Wordfence runs on your server and shows you, in real-time, all the traffic that is hitting your server right now, including those non-human crawlers, feed readers and hackers that Analytics can't track.
209
- </p>
210
- <strong><p>Separated into the important categories</p></strong>
211
- <p>
212
- You'll notice we have divided your traffic into tabs. These include an "All Hits" tab to simply view everything that is hitting your server right now.
213
- We then sub-divide that into Human traffic, your site members, crawlers - which we further break down into Google crawlers.
214
- </p>
215
- <p>
216
- <strong>How to use this page when your site is being attacked</strong>
217
- </p>
218
- <p>
219
- Start by looking at "All Hits" because you may notice that a single IP address is generating most of your traffic.
220
- This could be a denial of service attack, someone stealing your content or a hacker probing for weaknesses.
221
- If you see a suspicious pattern, simply block that IP address. If they attack from a different IP on the same network, simply block that network.
222
- You can also run a WHOIS on any IP address to find the host and report abuse via email.
223
- </p>
224
- <p>
225
- If you don't see any clear patterns of attack, take a look at "Top 404s" which will show you IP addresses that are generating excessive page not found errors.
226
- It's common for an attacker probing for weaknesses to generate a lot of page not found errors. If you see one IP
227
- address that is generating many of these requests, and it's not Google or another trusted crawler, then you should consider
228
- blocking them.
229
- </p>
230
- <p>
231
- Next look at "Logins and Logouts". If you see a large number of failed logins from an IP address, block them if you don't recognize who they are.
232
- </p>
233
- <p>
234
- Finally, take a look at "Top Consumers". These are the top IP addresses who are "consuming" or accessing most of your content.
235
- If you're trying to protect yourself against a content thief, this is the first place to look.
236
- </p>
237
 
238
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  </script>
 
1
  <div class="wrap wordfence">
2
  <?php require('menuHeader.php'); ?>
3
 
4
  <h2 id="wfHeading">
5
  <div style="float: left;">
6
+ Your Site Activity in Real-Time
7
  </div>
8
  <div class="wordfenceWrap" style="margin: 5px 0 0 15px; float: left;">
9
  <div class="wfOnOffSwitch" id="wfOnOffSwitchID">
10
+ <input type="checkbox" name="wfOnOffSwitch" class="wfOnOffSwitch-checkbox"
11
+ id="wfLiveTrafficOnOff" <?php if (wfConfig::liveTrafficEnabled()) {
12
+ echo ' checked ';
13
+ } ?>>
14
  <label class="wfOnOffSwitch-label" for="wfLiveTrafficOnOff">
15
  <div class="wfOnOffSwitch-inner"></div>
16
  <div class="wfOnOffSwitch-switch"></div>
18
  </div>
19
  </div>
20
  </h2>
21
+ <a href="http://docs.wordfence.com/en/Live_traffic" target="_blank" class="wfhelp"></a><a
22
+ href="http://docs.wordfence.com/en/Live_traffic" target="_blank">Learn more about Wordfence Live Traffic</a>
23
+
24
+ <div class="wordfenceModeElem" id="wordfenceMode_activity"></div>
25
  <div class="wordfenceLive">
26
  <table border="0" cellpadding="0" cellspacing="0">
27
+ <tr>
28
+ <td><h2>Wordfence Live Activity:</h2></td>
29
+ <td id="wfLiveStatus"></td>
30
+ </tr>
31
  </table>
32
  </div>
33
  <div class="wordfenceWrap">
34
+
35
+ <?php if (!wfConfig::liveTrafficEnabled()): ?>
36
  <div style="color: #F00;">
37
  Live Traffic is disabled.
38
+ <?php if (wfConfig::get('cacheType') == 'falcon') { ?>This is done to improve performance because you have Wordfence Falcon Engine enabled.<?php } ?>
39
  </div>
40
+ <?php else: ?>
41
+ <div id="wf-live-traffic" class="wfTabsContainer">
42
+
43
+ <div id="wf-live-traffic-legend">
44
+ <ul>
45
+ <li class="wfHuman">Human</li>
46
+ <li class="wfBot">Bot</li>
47
+ <li class="wfNotice">Warning</li>
48
+ <li class="wfBlocked">Blocked</li>
49
+ </ul>
50
+ </div>
51
+
52
+ <form data-bind="submit: reloadListings">
53
+
54
+ <?php if (defined('WP_DEBUG') && WP_DEBUG): ?>
55
+ <pre data-bind="text: 'DEBUG: ' + sql(), visible: sql"></pre>
56
+ <?php endif ?>
57
+
58
+ <div class="wfActEvent">
59
+ <h2 style="float: left;padding: 0;margin: 0 10px 0 0;">Filter Traffic: </h2>
60
+
61
+ <select id="wf-lt-preset-filters" data-bind="options: presetFiltersOptions, optionsText: presetFiltersOptionsText,
62
+ value: selectedPresetFilter">
63
+ </select>
64
+ &nbsp;&nbsp;
65
+ <label>
66
+ <input data-bind="checked: showAdvancedFilters" type="checkbox">
67
+ Show Advanced Filters
68
+ </label>
69
+ </div>
70
+
71
+ <div class="wfActEvent" data-bind="visible: showAdvancedFilters" id="wf-lt-advanced-filters">
72
+ <table>
73
+ <tr>
74
+ <td>
75
+ <table>
76
+ <tbody data-bind="foreach: filters">
77
+ <tr>
78
+ <td>
79
+ <select name="param[]" class="wf-lt-advanced-filters-param" data-bind="options: filterParamOptions,
80
+ optionsText: filterParamOptionsText, value: selectedFilterParamOptionValue, optionsCaption: 'Filter...'"></select>
81
+ </td>
82
+ <td data-bind="visible: selectedFilterParamOptionValue() && selectedFilterParamOptionValue().type() != 'bool'">
83
+ <select name="operator[]" class="wf-lt-advanced-filters-operator"
84
+ data-bind="options: filterOperatorOptions,
85
+ optionsText: filterOperatorOptionsText, value: selectedFilterOperatorOptionValue"></select>
86
+ </td>
87
+ <td data-bind="attr: {colSpan: (selectedFilterParamOptionValue() &&
88
+ selectedFilterParamOptionValue().type() == 'bool' ? 2 : 1)}"
89
+ class="wf-lt-advanced-filters-value-cell">
90
+
91
+ <span
92
+ data-bind="if: selectedFilterParamOptionValue() && selectedFilterParamOptionValue().type() == 'enum'">
93
+ <select
94
+ data-bind="options: selectedFilterParamOptionValue().values,
95
+ optionsText: selectedFilterParamOptionValue().optionsText,
96
+ value: value"></select>
97
+ </span>
98
+
99
+ <span
100
+ data-bind="if: selectedFilterParamOptionValue() && selectedFilterParamOptionValue().type() == 'text'">
101
+ <input data-bind="value: value" type="text"/>
102
+ </span>
103
+
104
+ <span
105
+ data-bind="if: selectedFilterParamOptionValue() && selectedFilterParamOptionValue().type() == 'bool'">
106
+ <label>Yes <input data-bind="checked: value" type="radio"
107
+ value="1"></label>
108
+ <label>No <input data-bind="checked: value" type="radio"
109
+ value="0"></label>
110
+ </span>
111
+
112
+ </td>
113
+ <td>
114
+ <button data-bind="click: $root.removeFilter" type="button"
115
+ class="button">
116
+ Remove
117
+ </button>
118
+ </td>
119
+ </tr>
120
+ </tbody>
121
+ <tbody>
122
+ <tr>
123
+ <td colspan="3">
124
+ <div class="wf-pad-small">
125
+ <button type="button" class="button" data-bind="click: addFilter">
126
+ Add Filter
127
+ </button>
128
+ </div>
129
+ </td>
130
+ </tr>
131
+ </tbody>
132
+ </table>
133
+ </td>
134
+ <td>
135
+ <table>
136
+ <tbody>
137
+ <tr>
138
+ <td>
139
+ <label for="wf-live-traffic-from">From:&nbsp;</label>
140
+ </td>
141
+ <td><input placeholder="Start date" id="wf-live-traffic-from" type="text"
142
+ class="wf-datetime"
143
+ data-bind="value: startDate, datetimepicker: null, datepickerOptions: { timeFormat: 'hh:mm tt z' }">
144
+ </td>
145
+ <td>
146
+ <button data-bind="click: startDate('')" class="button small"
147
+ type="button">
148
+ Clear
149
+ </button>
150
+ </td>
151
+ </tr>
152
+ <tr>
153
+ <td>
154
+ <label for="wf-live-traffic-to">To:&nbsp;</label>
155
+ </td>
156
+ <td><input placeholder="End date" id="wf-live-traffic-to" type="text"
157
+ class="wf-datetime"
158
+ data-bind="value: endDate, datetimepicker: null, datepickerOptions: { timeFormat: 'hh:mm tt z' }">
159
+ </td>
160
+ <td>
161
+ <button data-bind="click: endDate('')" class="button small"
162
+ type="button">
163
+ Clear
164
+ </button>
165
+ </td>
166
+ </tr>
167
+ <tr>
168
+ <td>
169
+ <label for="wf-live-traffic-group-by">Group&nbsp;By:&nbsp;</label>
170
+ </td>
171
+ <td>
172
+ <select id="wf-live-traffic-group-by" name="groupby"
173
+ class="wf-lt-advanced-filters-groupby"
174
+ data-bind="options: filterGroupByOptions,
175
+ optionsText: filterGroupByOptionsText, value: groupBy, optionsCaption: 'None'"></select>
176
+ </td>
177
+ </tr>
178
+ </tbody>
179
+ </table>
180
+ </td>
181
+ </tr>
182
+ </table>
183
+ </div>
184
+ </form>
185
+
186
+ <table data-bind="if: groupBy()" border="0" style="width: 100%">
187
+ <tbody data-bind="foreach: listings">
188
+ <tr>
189
+ <td>
190
+ <div data-bind="if: loc()">
191
+ <img data-bind="attr: { src: '//www.wordfence.com/images/flags/' + loc().countryCode.toLowerCase() + '.png',
192
+ alt: loc().countryName, title: loc().countryName }" width="16" height="11"
193
+ class="wfFlag"/>
194
+ <a data-bind="text: (loc().city ? loc().city + ', ' : '') + loc().countryName,
195
+ attr: { href: 'http://maps.google.com/maps?q=' + loc().lat + ',' + loc().lon + '&z=6' }"
196
+ target="_blank"></a>
197
+ </div>
198
+ <div data-bind="if: !loc()">
199
+ An unknown location at IP <a
200
+ data-bind="text: IP, attr: { href: WFAD.makeIPTrafLink(IP()) }" target="_blank"></a>
201
+ </div>
202
+
203
+ <div>
204
+ <strong>IP:</strong>&nbsp;<a
205
+ data-bind="text: IP, attr: { href: WFAD.makeIPTrafLink(IP()) }" target="_blank"></a>
206
+ <span data-bind="if: blocked()">
207
+ [<a data-bind="click: $root.unblockIP">unblock</a>]
208
+ </span>
209
+ <span data-bind="if: rangeBlocked()">
210
+ [<a data-bind="click: $root.unblockNetwork">unblock this range</a>]
211
+ </span>
212
+ <span data-bind="if: !blocked() && !rangeBlocked()">
213
+ [<a data-bind="click: $root.blockIP">block</a>]
214
+ </span>
215
+ </div>
216
+ <div>
217
+ &nbsp;<span class="wfReverseLookup"><span data-bind="text: IP"
218
+ style="display:none;"></span></span>
219
+ </div>
220
+ <div>
221
+ <span
222
+ data-bind="attr: { 'data-timestamp': ctime, text: 'Last hit was ' + ctime() + ' ago.' }"
223
+ class="wfTimeAgo wfTimeAgo-timestamp"></span>
224
+ </div>
225
+ </td>
226
+ <td style="font-size: 28px; color: #999;">
227
+ <span data-bind="text: hitCount"></span> hits
228
+ </td>
229
+ </tr>
230
+ </tbody>
231
+ </table>
232
+
233
+ <div data-bind="if: !groupBy()">
234
+ <div id="wf-lt-listings" data-bind="foreach: listings">
235
+ <div data-bind="attr: { id: ('wfActEvent_' + id()), 'class': cssClasses }">
236
+ <table border="0" cellpadding="1" cellspacing="0">
237
+ <tr>
238
+ <td>
239
+ <span data-bind="if: action() != 'loginOK' && user()">
240
+ <span data-bind="html: user.avatar" class="wfAvatar"></span>
241
+ <a data-bind="attr: { href: user.editLink }, text: user().display_name"
242
+ target="_blank"></a>
243
+ </span>
244
+ <span data-bind="if: loc()">
245
+ <span data-bind="if: action() != 'loginOK' && user()"> in</span>
246
+ <img data-bind="attr: { src: '//www.wordfence.com/images/flags/' + loc().countryCode.toLowerCase() + '.png',
247
+ alt: loc().countryName, title: loc().countryName }" width="16"
248
+ height="11"
249
+ class="wfFlag"/>
250
+ <a data-bind="text: (loc().city ? loc().city + ', ' : '') + loc().countryName,
251
+ attr: { href: 'http://maps.google.com/maps?q=' + loc().lat + ',' + loc().lon + '&z=6' }"
252
+ target="_blank"></a>
253
+ </span>
254
+ <span data-bind="if: !loc()">
255
+ <span
256
+ data-bind="text: action() != 'loginOK' && user() ? 'at an' : 'An'"></span> unknown location at IP <a
257
+ data-bind="text: IP, attr: { href: WFAD.makeIPTrafLink(IP()) }"
258
+ target="_blank"></a>
259
+ </span>
260
+ <span data-bind="if: referer()">
261
+ <span data-bind="if: extReferer()">
262
+ arrived from <a data-bind="text: referer, attr: { href: referer }"
263
+ target="_blank"
264
+ style="color: #A00; font-weight: bold;"></a> and
265
+ </span>
266
+ <span data-bind="if: !extReferer()">
267
+ left <a data-bind="text: referer, attr: { href: referer }"
268
+ target="_blank"
269
+ style="color: #999; font-weight: normal;"></a> and
270
+ </span>
271
+ </span>
272
+ <span data-bind="if: statusCode() == 404">
273
+ tried to access <span style="color: #F00;">non-existent page</span>
274
+ </span>
275
+
276
+ <span data-bind="if: statusCode() == 200 && !action()">
277
+ visited
278
+ </span>
279
+ <span data-bind="if: statusCode() == 403">
280
+ was <span data-bind="text: firewallAction" style="color: #F00;"></span> at
281
+ </span>
282
+
283
+ <span data-bind="if: action() == 'loginOK'">
284
+ logged in successfully as "<strong data-bind="text: username"></strong>".
285
+ </span>
286
+ <span data-bind="if: action() == 'logout'">
287
+ logged out successfully.
288
+ </span>
289
+ <span data-bind="if: action() == 'lostPassword'">
290
+ requested a password reset.
291
+ </span>
292
+ <span data-bind="if: action() == 'loginFailValidUsername'">
293
+ attempted a failed login as "<strong data-bind="text: username"></strong>".
294
+ </span>
295
+ <span data-bind="if: action() == 'loginFailInvalidUsername'">
296
+ attempted a failed login using an invalid username "<strong
297
+ data-bind="text: username"></strong>".
298
+ </span>
299
+ <span data-bind="if: action() == 'user:passwordReset'">
300
+ changed their password.
301
+ </span>
302
+ <a class="wf-lt-url"
303
+ data-bind="text: displayURL, attr: { href: URL, title: URL }"
304
+ target="_blank"></a>
305
+ </td>
306
+ </tr>
307
+ <tr>
308
+ <td><span data-bind="text: timeAgo, attr: { 'data-timestamp': ctime }"
309
+ class="wfTimeAgo wfTimeAgo-timestamp"></span>&nbsp;&nbsp;
310
+ <strong>IP:</strong> <a
311
+ data-bind="attr: { href: WFAD.makeIPTrafLink(IP()) }, text: IP"
312
+ target="_blank"></a>
313
+ <span data-bind="if: blocked()">
314
+ [<a data-bind="click: $root.unblockIP">unblock</a>]
315
+ </span>
316
+ <span data-bind="if: rangeBlocked()">
317
+ [<a data-bind="click: $root.unblockNetwork">unblock this range</a>]
318
+ </span>
319
+ <span data-bind="if: !blocked() && !rangeBlocked()">
320
+ [<a data-bind="click: $root.blockIP">block</a>]
321
+ </span>
322
+ &nbsp;
323
+ <span class="wfReverseLookup">
324
+ <span data-bind="text: IP"
325
+ style="display:none;"></span>
326
+ </span>
327
+ </td>
328
+ </tr>
329
+
330
+ <tr data-bind="if: browser() && browser().browser != 'Default Browser'">
331
+ <td>
332
+ <strong>Browser:</strong>
333
+ <span data-bind="text: browser().browser +
334
+ (browser().version ? ' version ' + browser().version : '') +
335
+ (browser().platform && browser().platform != 'unknown' ? ' running on ' + browser().platform : '')
336
+ ">
337
+ </span>
338
+ </td>
339
+ </tr>
340
+ <tr>
341
+ <td data-bind="text: UA" style="color: #AAA;"></td>
342
+ </tr>
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>
388
+ </tr>
389
+ </table>
390
+ </div>
391
+ </div>
392
+ </div>
393
+ <div data-bind="if: !listings">
394
+ No events to report yet.
395
+ </div>
396
  </div>
397
+ <?php endif ?>
398
  </div>
399
  </div>
400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  <script type="text/x-jquery-template" id="wfWelcomeContent3">
402
+ <div>
403
+ <h3>Welcome to ALL Your Site Visits, Live!</h3>
404
+ <strong><p>Traffic you've never seen before</p></strong>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
 
406
+ <p>
407
+ Google Analytics and other Javascript analytics packages can't show you crawlers, RSS feed readers, hack
408
+ attempts and other non-human traffic that hits your site.
409
+ Wordfence runs on your server and shows you, in real-time, all the traffic that is hitting your server right
410
+ now, including those non-human crawlers, feed readers and hackers that Analytics can't track.
411
+ </p>
412
+ <strong><p>Separated into the important categories</p></strong>
413
+
414
+ <p>
415
+ You'll notice that you can filter traffic. The options include "All Hits" to simply view everything that is
416
+ hitting your server right now. We then sub-divide that into human visits, your site members, crawlers -
417
+ which we further break down into Google crawlers - and various other choices.
418
+ </p>
419
+
420
+ <p>
421
+ <strong>How to use this page when your site is being attacked</strong>
422
+ </p>
423
+
424
+ <p>
425
+ Start by looking at "All Hits" because you may notice that a single IP address is generating most of your
426
+ traffic.
427
+ This could be a denial of service attack, someone stealing your content or a hacker probing for weaknesses.
428
+ If you see a suspicious pattern, simply block that IP address. If they attack from a different IP on the
429
+ same network, simply block that network.
430
+ You can also run a WHOIS on any IP address to find the host and report abuse via email.
431
+ </p>
432
+
433
+ <p>
434
+ If you don't see any clear patterns of attack, take a look at "Pages Not Found" which will show you IP
435
+ addresses that are generating excessive page not found errors. It's common for an attacker probing for
436
+ weaknesses to generate a lot of these errors. If you see one IP address that is generating many of these
437
+ requests, and it's not Google or another trusted crawler, then you should consider blocking them.
438
+ </p>
439
+
440
+ <p>
441
+ Next look at "Logins and Logouts". If you see a large number of failed logins from an IP address, block them
442
+ if you don't recognize who they are.
443
+ </p>
444
+
445
+ </div>
446
  </script>
lib/menu_countryBlocking.php CHANGED
@@ -11,16 +11,16 @@ WFAD.countryMap = <?php echo json_encode($wfBulkCountries); ?>;
11
  <?php if(! wfConfig::get('isPaid')){ ?>
12
  <div class="wf-premium-callout" style="margin: 20px">
13
  <h3>Country Blocking is only available to Premium Members</h3>
14
- <p>Country Blocking is a premium feature because we have licensed a very accurate commercial geolocation
15
- database to provide this feature. Upgrade to Premium today:</p>
 
16
  <ul>
17
- <li>You can upgrade now for less than $5 per month</li>
18
- <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, advanced
19
- scanning options and cell phone sign-in give you the best protection available
20
- </li>
21
  <li>Access to Premium Support</li>
22
- <li>Discounts of up to 90% available for multiyear and multi-license purchases</li>
23
  </ul>
 
24
  <p class="center"><a class="button button-primary"
25
  href="https://www.wordfence.com/gnl1countryBlock1/wordfence-signup/">Get Premium</a></p>
26
  </div>
11
  <?php if(! wfConfig::get('isPaid')){ ?>
12
  <div class="wf-premium-callout" style="margin: 20px">
13
  <h3>Country Blocking is only available to Premium Members</h3>
14
+ <p>Country blocking is a premium feature that lets you block attacks or malicious activity that originates in a specific country</p>
15
+
16
+ <p>Upgrade to Premium today for less than $5 per month:</p>
17
  <ul>
18
+ <li>Receive real-time Firewall and Scan engine rule updates for protection as threats emerge</li>
19
+ <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, advanced scanning options and cell phone sign-in give you the best protection available</li>
 
 
20
  <li>Access to Premium Support</li>
21
+ <li>Discounts of up to 75% available for multiyear and multi-license purchases</li>
22
  </ul>
23
+
24
  <p class="center"><a class="button button-primary"
25
  href="https://www.wordfence.com/gnl1countryBlock1/wordfence-signup/">Get Premium</a></p>
26
  </div>
lib/menu_diagnostic.php ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $diagnostic = new wfDiagnostic;
3
+ $plugins = get_plugins();
4
+ $activePlugins = array_flip(get_option('active_plugins'));
5
+ $activeNetworkPlugins = is_multisite() ? array_flip(wp_get_active_network_plugins()) : array();
6
+ $muPlugins = get_mu_plugins();
7
+ $themes = wp_get_themes();
8
+ $currentTheme = wp_get_theme();
9
+ $cols = 3;
10
+
11
+ $w = new wfConfig();
12
+ ?>
13
+
14
+ <div class="wrap wordfence">
15
+ <?php require('menuHeader.php'); ?>
16
+ <h2 id="wfHeading">
17
+ Diagnostics
18
+ </h2>
19
+ <br clear="both"/>
20
+
21
+ <form id="wfConfigForm">
22
+ <table class="wf-table"<?php echo !empty($inEmail) ? ' border=1' : '' ?>>
23
+ <?php foreach ($diagnostic->getResults() as $title => $tests): ?>
24
+ <tbody class="thead">
25
+ <tr>
26
+ <th colspan="<?php echo $cols ?>"><?php echo esc_html($title) ?></th>
27
+ </tr>
28
+ </tbody>
29
+ <tbody>
30
+ <?php foreach ($tests as $result): ?>
31
+ <tr>
32
+ <td style="width: 75%;"
33
+ colspan="<?php echo $cols - 1 ?>"><?php echo wp_kses($result['label'], array(
34
+ 'code' => array(),
35
+ 'strong' => array(),
36
+ 'em' => array(),
37
+ 'a' => array('href' => true),
38
+ )) ?></td>
39
+ <?php if ($result['test']): ?>
40
+ <td class="success"><?php echo esc_html($result['message']) ?></td>
41
+ <?php else: ?>
42
+ <td class="error"><?php echo esc_html($result['message']) ?></td>
43
+ <?php endif ?>
44
+ </tr>
45
+ <?php endforeach ?>
46
+ </tbody>
47
+ <tbody class="empty-row">
48
+ <tr>
49
+ <td colspan="<?php echo $cols ?>"></td>
50
+ </tr>
51
+ </tbody>
52
+ <?php endforeach ?>
53
+
54
+ <tbody class="thead">
55
+ <tr>
56
+ <th>IPs</th>
57
+ <th>Value</th>
58
+ <th>Used</th>
59
+ </tr>
60
+ </tbody>
61
+ <tbody>
62
+ <?php
63
+ $howGet = wfConfig::get('howGetIPs', false);
64
+ list($currentIP, $currentServerVarForIP) = wfUtils::getIPAndServerVarible();
65
+ foreach (array(
66
+ 'REMOTE_ADDR' => 'REMOTE_ADDR',
67
+ 'HTTP_CF_CONNECTING_IP' => 'CF-Connecting-IP',
68
+ 'HTTP_X_REAL_IP' => 'X-Real-IP',
69
+ 'HTTP_X_FORWARDED_FOR' => 'X-Forwarded-For',
70
+ ) as $variable => $label): ?>
71
+ <tr>
72
+ <td><?php echo $label ?></td>
73
+ <td><?php echo esc_html(array_key_exists($variable, $_SERVER) ? $_SERVER[$variable] : '(not set)') ?></td>
74
+ <?php if ($currentServerVarForIP && $currentServerVarForIP === $variable): ?>
75
+ <td class="success">In use</td>
76
+ <?php elseif ($howGet === $variable): ?>
77
+ <td class="error">Configured, but not valid</td>
78
+ <?php else: ?>
79
+ <td></td>
80
+ <?php endif ?>
81
+ </tr>
82
+ <?php endforeach ?>
83
+ </tbody>
84
+ <tbody class="empty-row">
85
+ <tr>
86
+ <td colspan="<?php echo $cols ?>"></td>
87
+ </tr>
88
+ </tbody>
89
+
90
+ <tbody class="thead">
91
+ <tr>
92
+ <th colspan="<?php echo $cols ?>">WordPress Plugins</th>
93
+ </tr>
94
+ </tbody>
95
+ <tbody>
96
+ <?php foreach ($plugins as $plugin => $pluginData): ?>
97
+ <tr>
98
+ <td colspan="<?php echo $cols - 1 ?>"><strong><?php echo esc_html($pluginData['Name']) ?></strong>
99
+ <?php if (!empty($pluginData['Version'])): ?>
100
+ - Version <?php echo esc_html($pluginData['Version']) ?>
101
+ <?php endif ?>
102
+ </td>
103
+ <?php if (array_key_exists(trailingslashit(WP_PLUGIN_DIR) . $plugin, $activeNetworkPlugins)): ?>
104
+ <td class="success">Network Activated</td>
105
+ <?php elseif (array_key_exists($plugin, $activePlugins)): ?>
106
+ <td class="success">Active</td>
107
+ <?php else: ?>
108
+ <td class="inactive">Inactive</td>
109
+ <?php endif ?>
110
+ </tr>
111
+ <?php endforeach ?>
112
+ </tbody>
113
+
114
+ <tbody class="empty-row">
115
+ <tr>
116
+ <td colspan="<?php echo $cols ?>"></td>
117
+ </tr>
118
+ </tbody>
119
+ <tbody class="thead">
120
+ <tr>
121
+ <th colspan="<?php echo $cols ?>">Must-Use WordPress Plugins</th>
122
+ </tr>
123
+ </tbody>
124
+ <?php if (!empty($muPlugins)): ?>
125
+ <tbody>
126
+ <?php foreach ($muPlugins as $plugin => $pluginData): ?>
127
+ <tr>
128
+ <td colspan="<?php echo $cols - 1 ?>">
129
+ <strong><?php echo esc_html($pluginData['Name']) ?></strong>
130
+ <?php if (!empty($pluginData['Version'])): ?>
131
+ - Version <?php echo esc_html($pluginData['Version']) ?>
132
+ <?php endif ?>
133
+ </td>
134
+ <td class="success">Active</td>
135
+ </tr>
136
+ <?php endforeach ?>
137
+ </tbody>
138
+ <?php else: ?>
139
+ <tbody>
140
+ <tr>
141
+ <td colspan="<?php echo $cols ?>">No MU-Plugins</td>
142
+ </tr>
143
+ </tbody>
144
+
145
+ <?php endif ?>
146
+
147
+ <tbody class="empty-row">
148
+ <tr>
149
+ <td colspan="<?php echo $cols ?>"></td>
150
+ </tr>
151
+ </tbody>
152
+ <tbody class="thead">
153
+ <tr>
154
+ <th colspan="<?php echo $cols ?>">Themes</th>
155
+ </tr>
156
+ </tbody>
157
+ <?php if (!empty($themes)): ?>
158
+ <tbody>
159
+ <?php foreach ($themes as $theme => $themeData): ?>
160
+ <tr>
161
+ <td colspan="<?php echo $cols - 1 ?>">
162
+ <strong><?php echo esc_html($themeData['Name']) ?></strong>
163
+ Version <?php echo esc_html($themeData['Version']) ?></td>
164
+ <?php if ($currentTheme instanceof WP_Theme && $theme === $currentTheme->get_stylesheet()): ?>
165
+ <td class="success">Active</td>
166
+ <?php else: ?>
167
+ <td class="inactive">Inactive</td>
168
+ <?php endif ?>
169
+ </tr>
170
+ <?php endforeach ?>
171
+ </tbody>
172
+ <?php else: ?>
173
+ <tbody>
174
+ <tr>
175
+ <td colspan="<?php echo $cols ?>">No MU-Plugins</td>
176
+ </tr>
177
+ </tbody>
178
+
179
+ <?php endif ?>
180
+
181
+ <tbody class="empty-row">
182
+ <tr>
183
+ <td colspan="<?php echo $cols ?>"></td>
184
+ </tr>
185
+ </tbody>
186
+ <tbody class="thead">
187
+ <tr>
188
+ <th colspan="<?php echo $cols ?>">Cron Jobs</th>
189
+ </tr>
190
+ </tbody>
191
+ <tbody>
192
+ <?php
193
+ $cron = _get_cron_array();
194
+
195
+ foreach ($cron as $timestamp => $values) {
196
+ if (is_array($values)) {
197
+ foreach ($values as $cron_job => $v) {
198
+ if (is_numeric($timestamp)) {
199
+ ?>
200
+ <tr>
201
+ <td colspan="<?php echo $cols - 1 ?>"><?php echo esc_html(date('r', $timestamp)) ?></td>
202
+ <td><?php echo esc_html($cron_job) ?></td>
203
+ </tr>
204
+ <?php
205
+ }
206
+ }
207
+ }
208
+ }
209
+ ?>
210
+ </tbody>
211
+ </table>
212
+ <?php
213
+ $wfdb = new wfDB();
214
+ $q = $wfdb->querySelect("show table status");
215
+ if ($q):
216
+ $databaseCols = count($q[0]);
217
+ ?>
218
+ <div style="max-width: 100%; overflow: auto; padding: 1px;">
219
+ <table class="wf-table"<?php echo !empty($inEmail) ? ' border=1' : '' ?>>
220
+ <tbody class="empty-row">
221
+ <tr>
222
+ <td colspan="<?php echo $databaseCols ?>"></td>
223
+ </tr>
224
+ </tbody>
225
+ <tbody class="thead">
226
+ <tr>
227
+ <th colspan="<?php echo $databaseCols ?>">Database Tables</th>
228
+ </tr>
229
+ </tbody>
230
+ <tbody class="thead thead-subhead" style="font-size: 85%">
231
+ <?php
232
+ $val = array_shift($q);
233
+ ?>
234
+ <tr>
235
+ <?php foreach ($val as $tkey => $tval): ?>
236
+ <th><?php echo esc_html($tkey) ?></th>
237
+ <?php endforeach; ?>
238
+ </tr>
239
+ </tbody>
240
+ <tbody style="font-size: 85%">
241
+ <?php
242
+ foreach ($q as $val): ?>
243
+ <tr>
244
+ <?php foreach ($val as $tkey => $tval): ?>
245
+ <td><?php echo esc_html($tval) ?></td>
246
+ <?php endforeach; ?>
247
+ </tr>
248
+ <?php endforeach; ?>
249
+ </tbody>
250
+
251
+ </table>
252
+ </div>
253
+ <?php endif ?>
254
+ </form>
255
+ </div>
256
+
257
+ <?php if (!empty($inEmail)): ?>
258
+ <?php phpinfo(); ?>
259
+ <?php endif ?>
260
+
261
+ <?php if (!empty($emailForm)): ?>
262
+ <h3>Other Tests</h3>
263
+
264
+ <ul>
265
+ <li>
266
+ <a href="<?php echo wfUtils::siteURLRelative(); ?>?_wfsf=sysinfo&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>"
267
+ target="_blank">Click to view your system's configuration in a new window</a>
268
+ <a href="http://docs.wordfence.com/en/Wordfence_options#Click_to_view_your_system.27s_configuration_in_a_new_window"
269
+ target="_blank" class="wfhelp"></a></li>
270
+ <li>
271
+ <a href="<?php echo wfUtils::siteURLRelative(); ?>?_wfsf=testmem&nonce=<?php echo wp_create_nonce('wp-ajax'); ?>"
272
+ target="_blank">Test your WordPress host's available memory</a>
273
+ <a href="http://docs.wordfence.com/en/Wordfence_options#Test_your_WordPress_host.27s_available_memory"
274
+ target="_blank" class="wfhelp"></a>
275
+ </li>
276
+ <li>
277
+ Send a test email from this WordPress server to an email address:<a
278
+ href="http://docs.wordfence.com/en/Wordfence_options#Send_a_test_email_from_this_WordPress_server_to_an_email_address"
279
+ target="_blank" class="wfhelp"></a>
280
+ <input type="text" id="testEmailDest" value="" size="20" maxlength="255" class="wfConfigElem"/>
281
+ <input class="button" type="button" value="Send Test Email"
282
+ onclick="WFAD.sendTestEmail(jQuery('#testEmailDest').val());"/>
283
+ </li>
284
+ </ul>
285
+
286
+ <div id="sendByEmailThanks" class="hidden">
287
+ <h3>Thanks for sending your diagnostic page over email</h3>
288
+ </div>
289
+ <div id="sendByEmailDiv">
290
+ <h3>Send Report by Email</h3>
291
+
292
+ <div id="sendByEmailForm" class="hidden">
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>
301
+ <input class="button" type="submit" id="sendByEmail" value="Send Report by Email"/>
302
+ </div>
303
+
304
+ <?php if (!WFWAF_SUBDIRECTORY_INSTALL): ?>
305
+ <div id="updateWAFRules">
306
+ <h3>Firewall Rules</h3>
307
+
308
+ <p>
309
+ <button type="button" onclick="WFAD.wafUpdateRules()" class="button button-primary">
310
+ Manually refresh firewall rules
311
+ </button>
312
+ <!-- <em id="waf-rules-last-updated"></em>-->
313
+ </p>
314
+ <?php
315
+ try {
316
+ $lastUpdated = wfWAF::getInstance()->getStorageEngine()->getConfig('rulesLastUpdated');
317
+ } catch (wfWAFStorageFileException $e) {
318
+ error_log($e->getMessage());
319
+ }
320
+ if (!empty($lastUpdated)): ?>
321
+ <script>
322
+ var lastUpdated = <?php echo (int) $lastUpdated ?>;
323
+ WFAD.renderWAFRulesLastUpdated(new Date(lastUpdated * 1000));
324
+ </script>
325
+ <?php endif ?>
326
+
327
+ </div>
328
+ <?php endif ?>
329
+
330
+ <h3>Debugging Options</h3>
331
+ <form action="#" id="wfDebuggingConfigForm">
332
+ <table class="wfConfigForm">
333
+ <tr>
334
+ <th>Add a debugging comment to HTML source of cached pages.<a
335
+ href="http://docs.wordfence.com/en/Wordfence_options#Add_a_debugging_comment_to_HTML_source_of_cached_pages"
336
+ target="_blank" class="wfhelp"></a></th>
337
+ <td><input type="checkbox" id="addCacheComment" class="wfConfigElem" name="addCacheComment"
338
+ value="1" <?php $w->cb('addCacheComment'); ?> />
339
+ </td>
340
+ </tr>
341
+
342
+ <tr>
343
+ <th>Enable debugging mode (increases database load)<a
344
+ href="http://docs.wordfence.com/en/Wordfence_options#Enable_debugging_mode_.28increases_database_load.29"
345
+ target="_blank" class="wfhelp"></a></th>
346
+ <td><input type="checkbox" id="debugOn" class="wfConfigElem" name="debugOn"
347
+ value="1" <?php $w->cb('debugOn'); ?> /></td>
348
+ </tr>
349
+
350
+ <tr>
351
+ <th>Start all scans remotely<a
352
+ href="http://docs.wordfence.com/en/Wordfence_options#Start_all_scans_remotely"
353
+ target="_blank" class="wfhelp"></a></th>
354
+ <td><input type="checkbox" id="startScansRemotely" class="wfConfigElem" name="startScansRemotely"
355
+ value="1" <?php $w->cb('startScansRemotely'); ?> />
356
+ (Try this if your scans aren't starting and your site is publicly accessible)
357
+ </td>
358
+ </tr>
359
+ <tr>
360
+ <th>Disable config caching<a
361
+ href="http://docs.wordfence.com/en/Wordfence_options#Disable_config_caching" target="_blank"
362
+ class="wfhelp"></a></th>
363
+ <td><input type="checkbox" id="disableConfigCaching" class="wfConfigElem"
364
+ name="disableConfigCaching" value="1" <?php $w->cb('disableConfigCaching'); ?> />
365
+ (Try this if your options aren't saving)
366
+ </td>
367
+ </tr>
368
+
369
+ <tr>
370
+ <th><label for="ssl_verify">Enable SSL Verification</label><a
371
+ href="http://docs.wordfence.com/en/Wordfence_options#Enable_SSL_Verification"
372
+ target="_blank" class="wfhelp"></a>
373
+ </th>
374
+ <td style="vertical-align: top;"><input type="checkbox" id="ssl_verify" class="wfConfigElem"
375
+ name="ssl_verify"
376
+ value="1" <?php $w->cb('ssl_verify'); ?> />
377
+ (Disable this if you are <strong><em>consistently</em></strong> unable to connect to the Wordfence
378
+ servers.)
379
+ </td>
380
+ </tr>
381
+
382
+ <tr>
383
+ <th><label for="betaThreatDefenseFeed">Enable beta threat defense feed</label></th>
384
+ <td style="vertical-align: top;"><input type="checkbox" id="betaThreatDefenseFeed"
385
+ class="wfConfigElem"
386
+ name="betaThreatDefenseFeed"
387
+ value="1" <?php $w->cb('betaThreatDefenseFeed'); ?> />
388
+ </td>
389
+ </tr>
390
+
391
+ </table>
392
+ <br>
393
+ <table border="0" cellpadding="0" cellspacing="0">
394
+ <tr>
395
+ <td><input type="button" id="button1" name="button1" class="button-primary" value="Save Changes"
396
+ onclick="WFAD.saveDebuggingConfig();"/></td>
397
+ <td style="height: 24px;">
398
+ <div class="wfAjax24"></div>
399
+ <span class="wfSavedMsg">&nbsp;Your changes have been saved!</span></td>
400
+ </tr>
401
+ </table>
402
+ </form>
403
+
404
+ <?php endif ?>
lib/menu_options.php CHANGED
@@ -60,12 +60,11 @@ $w = new wfConfig();
60
  <div class="wf-premium-callout">
61
  <h3>Upgrade to Wordfence Premium today for less than $5 per month</h3>
62
  <ul>
63
- <li>Advanced features like IP reputation monitoring, country blocking, an advanced
64
- comment spam filter and cell phone sign-in give you the best protection available
65
- </li>
66
  <li>Remote, frequent and scheduled scans</li>
67
  <li>Access to Premium Support</li>
68
- <li>Discounts of up to 90% for multiyear and multi-license purchases</li>
69
  </ul>
70
  <p class="center">
71
  <a class="button button-primary"
@@ -80,13 +79,13 @@ $w = new wfConfig();
80
  target="_blank" class="wfhelp"></a></h2></td>
81
  </tr>
82
  <tr>
83
- <th class="wfConfigEnable">Enable firewall<a
84
- href="http://docs.wordfence.com/en/Wordfence_options#Enable_Firewall" target="_blank"
85
  class="wfhelp"></a></th>
86
  <td><input type="checkbox" id="firewallEnabled" class="wfConfigElem" name="firewallEnabled"
87
  value="1" <?php $w->cb( 'firewallEnabled' ); ?> />&nbsp;<span
88
- style="color: #F00;">NOTE:</span> This checkbox enables ALL firewall functions including IP,
89
- country and advanced blocking and the "Firewall Rules" below.
90
  </td>
91
  </tr>
92
  <tr>
@@ -407,6 +406,11 @@ $w = new wfConfig();
407
  <td><input type="text" name="liveTraf_ignoreUA" id="liveTraf_ignoreUA"
408
  value="<?php $w->f( 'liveTraf_ignoreUA' ); ?>"/></td>
409
  </tr>
 
 
 
 
 
410
  <tr>
411
  <td colspan="2">
412
  <div class="wfMarker" id="wfMarkerScansToInclude"></div>
@@ -441,6 +445,30 @@ $w = new wfConfig();
441
  name="scansEnabled_heartbleed" value="1" <?php $w->cb( 'scansEnabled_heartbleed' ); ?> />
442
  </td>
443
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  <tr>
445
  <th>Scan core files against repository versions for changes<a
446
  href="http://docs.wordfence.com/en/Wordfence_options#Scan_core_files_against_repository_version_for_changes"
@@ -476,15 +504,16 @@ $w = new wfConfig();
476
  target="_blank" class="wfhelp"></a></th>
477
  <td><input type="checkbox" id="scansEnabled_fileContents" class="wfConfigElem"
478
  name="scansEnabled_fileContents"
479
- value="1" <?php $w->cb( 'scansEnabled_fileContents' ); ?>/></td>
 
 
 
480
  </tr>
481
- <tr>
482
- <th>Scan database for backdoors, trojans and suspicious code<a
483
- href="http://docs.wordfence.com/en/Wordfence_options#Scan_database_for_backdoors.2C_trojans_and_suspicious_code"
484
- target="_blank" class="wfhelp"></a></th>
485
- <td><input type="checkbox" id="scansEnabled_database" class="wfConfigElem"
486
- name="scansEnabled_database"
487
- value="1" <?php $w->cb( 'scansEnabled_database' ); ?>/></td>
488
  </tr>
489
  <tr>
490
  <th>Scan posts for known dangerous URLs and suspicious content<a
@@ -508,6 +537,14 @@ $w = new wfConfig();
508
  name="scansEnabled_oldVersions"
509
  value="1" <?php $w->cb( 'scansEnabled_oldVersions' ); ?>/></td>
510
  </tr>
 
 
 
 
 
 
 
 
511
  <tr>
512
  <th>Check the strength of passwords<a
513
  href="http://docs.wordfence.com/en/Wordfence_options#Check_the_strength_of_passwords"
@@ -564,8 +601,8 @@ $w = new wfConfig();
564
  <tr>
565
  <td colspan="2">
566
  <div class="wfMarker" id="wfMarkerFirewallRules"></div>
567
- <h3 class="wfConfigHeading">Firewall Rules<a
568
- href="http://docs.wordfence.com/en/Wordfence_options#Firewall_Rules" target="_blank"
569
  class="wfhelp"></a></h3>
570
  </td>
571
  </tr>
@@ -810,9 +847,11 @@ $w = new wfConfig();
810
  <th>Immediately block the IP of users who try to sign in as these usernames<a
811
  href="http://docs.wordfence.com/en/Wordfence_options#Immediately_block_the_IP_of_users_who_try_to_sign_in_as_these_usernames"
812
  target="_blank" class="wfhelp"></a></th>
813
- <td><input type="text" name="loginSec_userBlacklist" id="loginSec_userBlacklist"
814
- value="<?php $w->f( 'loginSec_userBlacklist' ); ?>" size="40"/>&nbsp;(Comma
815
- separated. Existing users won't be blocked.)
 
 
816
  </td>
817
  </tr>
818
  <tr>
@@ -933,13 +972,6 @@ $w = new wfConfig();
933
  browser traffic but slow scan starts, live traffic &amp; status updates.
934
  </td>
935
  </tr>
936
- <tr>
937
- <th>Enable debugging mode (increases database load)<a
938
- href="http://docs.wordfence.com/en/Wordfence_options#Enable_debugging_mode_.28increases_database_load.29"
939
- target="_blank" class="wfhelp"></a></th>
940
- <td><input type="checkbox" id="debugOn" class="wfConfigElem" name="debugOn"
941
- value="1" <?php $w->cb( 'debugOn' ); ?> /></td>
942
- </tr>
943
  <tr>
944
  <th>Delete Wordfence tables and data on deactivation?<a
945
  href="http://docs.wordfence.com/en/Wordfence_options#Delete_Wordfence_tables_and_data_on_deactivation.3F"
@@ -958,35 +990,6 @@ $w = new wfConfig();
958
  will appear to be new visits)
959
  </td>
960
  </tr>
961
- <tr>
962
- <th>Start all scans remotely<a
963
- href="http://docs.wordfence.com/en/Wordfence_options#Start_all_scans_remotely"
964
- target="_blank" class="wfhelp"></a></th>
965
- <td><input type="checkbox" id="startScansRemotely" class="wfConfigElem" name="startScansRemotely"
966
- value="1" <?php $w->cb( 'startScansRemotely' ); ?> />(Try this if your scans aren't
967
- starting and your site is publicly accessible)
968
- </td>
969
- </tr>
970
- <tr>
971
- <th>Disable config caching<a
972
- href="http://docs.wordfence.com/en/Wordfence_options#Disable_config_caching" target="_blank"
973
- class="wfhelp"></a></th>
974
- <td><input type="checkbox" id="disableConfigCaching" class="wfConfigElem"
975
- name="disableConfigCaching" value="1" <?php $w->cb( 'disableConfigCaching' ); ?> />(Try
976
- this if your options aren't saving)
977
- </td>
978
- </tr>
979
- <tr>
980
- <th>Add a debugging comment to HTML source of cached pages.<a
981
- href="http://docs.wordfence.com/en/Wordfence_options#Add_a_debugging_comment_to_HTML_source_of_cached_pages"
982
- target="_blank" class="wfhelp"></a></th>
983
- <td><input type="checkbox" id="addCacheComment" class="wfConfigElem" name="addCacheComment"
984
- value="1" <?php $w->cb( 'addCacheComment' ); ?> />
985
- <?php if ($w->get('allowHTTPSCaching')): ?>
986
- <input type="hidden" name="allowHTTPSCaching" value="1"/>
987
- <?php endif ?>
988
- </td>
989
- </tr>
990
  <tr>
991
  <th><label for="disableCodeExecutionUploads">Disable Code Execution for Uploads directory</label><a
992
  href="http://docs.wordfence.com/en/Wordfence_options#Disable_Code_Execution_for_Uploads_directory"
@@ -995,60 +998,6 @@ $w = new wfConfig();
995
  name="disableCodeExecutionUploads"
996
  value="1" <?php $w->cb( 'disableCodeExecutionUploads' ); ?> /></td>
997
  </tr>
998
- <tr>
999
- <th><label for="ssl_verify">Enable SSL Verification</label><a
1000
- href="http://docs.wordfence.com/en/Wordfence_options#Enable_SSL_Verification"
1001
- target="_blank" class="wfhelp"></a>
1002
- </th>
1003
- <td style="vertical-align: top;"><input type="checkbox" id="ssl_verify" class="wfConfigElem"
1004
- name="ssl_verify"
1005
- value="1" <?php $w->cb( 'ssl_verify' ); ?> />
1006
- (Disable this if you are <strong><em>consistently</em></strong> unable to connect to the Wordfence servers.)
1007
- </td>
1008
- </tr>
1009
- <tr>
1010
- <th colspan="2"><a
1011
- href="<?php echo wfUtils::siteURLRelative(); ?>?_wfsf=conntest&nonce=<?php echo wp_create_nonce( 'wp-ajax' ); ?>"
1012
- target="_blank">Click to test connectivity to the Wordfence API servers</a><a
1013
- href="http://docs.wordfence.com/en/Wordfence_options#Click_to_test_connectivity_to_the_Wordfence_API_servers"
1014
- target="_blank" class="wfhelp"></a></th>
1015
- </tr>
1016
- <tr>
1017
- <th colspan="2"><a
1018
- href="<?php echo wfUtils::siteURLRelative(); ?>?_wfsf=sysinfo&nonce=<?php echo wp_create_nonce( 'wp-ajax' ); ?>"
1019
- target="_blank">Click to view your system's configuration in a new window</a><a
1020
- href="http://docs.wordfence.com/en/Wordfence_options#Click_to_view_your_system.27s_configuration_in_a_new_window"
1021
- target="_blank" class="wfhelp"></a></th>
1022
- </tr>
1023
- <tr>
1024
- <th colspan="2"><a
1025
- href="<?php echo wfUtils::siteURLRelative(); ?>?_wfsf=cronview&nonce=<?php echo wp_create_nonce( 'wp-ajax' ); ?>"
1026
- target="_blank">Click to view your systems scheduled jobs in a new window</a><a
1027
- href="http://docs.wordfence.com/en/Wordfence_options#Click_to_view_your_system.27s_scheduled_jobs_in_a_new_window"
1028
- target="_blank" class="wfhelp"></a></th>
1029
- </tr>
1030
- <tr>
1031
- <th colspan="2"><a
1032
- href="<?php echo wfUtils::siteURLRelative(); ?>?_wfsf=dbview&nonce=<?php echo wp_create_nonce( 'wp-ajax' ); ?>"
1033
- target="_blank">Click to see a list of your system's database tables in a new window</a><a
1034
- href="http://docs.wordfence.com/en/Wordfence_options#Click_to_see_a_list_of_your_system.27s_database_tables_in_a_new_window"
1035
- target="_blank" class="wfhelp"></a></th>
1036
- </tr>
1037
- <tr>
1038
- <th colspan="2"><a
1039
- href="<?php echo wfUtils::siteURLRelative(); ?>?_wfsf=testmem&nonce=<?php echo wp_create_nonce( 'wp-ajax' ); ?>"
1040
- target="_blank">Test your WordPress host's available memory</a><a
1041
- href="http://docs.wordfence.com/en/Wordfence_options#Test_your_WordPress_host.27s_available_memory"
1042
- target="_blank" class="wfhelp"></a></th>
1043
- </tr>
1044
- <tr>
1045
- <th>Send a test email from this WordPress server to an email address:<a
1046
- href="http://docs.wordfence.com/en/Wordfence_options#Send_a_test_email_from_this_WordPress_server_to_an_email_address"
1047
- target="_blank" class="wfhelp"></a></th>
1048
- <td><input type="text" id="testEmailDest" value="" size="20" maxlength="255" class="wfConfigElem"/>
1049
- <input type="button" value="Send Test Email"
1050
- onclick="WFAD.sendTestEmail(jQuery('#testEmailDest').val());"/></td>
1051
- </tr>
1052
 
1053
  <tr>
1054
  <td colspan="2">
@@ -1143,7 +1092,7 @@ $w = new wfConfig();
1143
  </script>
1144
  <script type="text/x-jquery-template" id="wfContentFirewallRules">
1145
  <div>
1146
- <h3>Firewall Rules</h3>
1147
 
1148
  <p>
1149
  <strong>NOTE:</strong> Before modifying these rules, make sure you have access to the email address
60
  <div class="wf-premium-callout">
61
  <h3>Upgrade to Wordfence Premium today for less than $5 per month</h3>
62
  <ul>
63
+ <li>Receive real-time Firewall and Scan engine rule updates for protection as threats emerge</li>
64
+ <li>Advanced features like IP reputation monitoring, country blocking, an advanced comment spam filter and cell phone sign-in give you the best protection available</li>
 
65
  <li>Remote, frequent and scheduled scans</li>
66
  <li>Access to Premium Support</li>
67
+ <li>Discounts of up to 75% for multiyear and multi-license purchases</li>
68
  </ul>
69
  <p class="center">
70
  <a class="button button-primary"
79
  target="_blank" class="wfhelp"></a></h2></td>
80
  </tr>
81
  <tr>
82
+ <th class="wfConfigEnable">Enable Rate Limiting and Advanced Blocking<a
83
+ href="https://docs.wordfence.com/en/Wordfence_options#Enable_Rate_Limiting_and_Advanced_Blocking" target="_blank"
84
  class="wfhelp"></a></th>
85
  <td><input type="checkbox" id="firewallEnabled" class="wfConfigElem" name="firewallEnabled"
86
  value="1" <?php $w->cb( 'firewallEnabled' ); ?> />&nbsp;<span
87
+ style="color: #F00;">NOTE:</span> This checkbox enables ALL blocking/throttling functions including IP,
88
+ country and advanced blocking and the "Rate Limiting Rules" below.
89
  </td>
90
  </tr>
91
  <tr>
406
  <td><input type="text" name="liveTraf_ignoreUA" id="liveTraf_ignoreUA"
407
  value="<?php $w->f( 'liveTraf_ignoreUA' ); ?>"/></td>
408
  </tr>
409
+ <tr>
410
+ <th>Amount of Live Traffic data to store (number of rows):</th>
411
+ <td><input type="text" name="liveTraf_maxRows" id="liveTraf_maxRows"
412
+ value="<?php $w->f( 'liveTraf_maxRows' ); ?>"/></td>
413
+ </tr>
414
  <tr>
415
  <td colspan="2">
416
  <div class="wfMarker" id="wfMarkerScansToInclude"></div>
445
  name="scansEnabled_heartbleed" value="1" <?php $w->cb( 'scansEnabled_heartbleed' ); ?> />
446
  </td>
447
  </tr>
448
+ <tr>
449
+ <th>Scan for publically accessible configuration, backup, or log files<a
450
+ href="http://docs.wordfence.com/en/Wordfence_options#Configuration_Readable"
451
+ target="_blank" class="wfhelp"></a></th>
452
+ <td><input type="checkbox" id="scansEnabled_checkReadableConfig" class="wfConfigElem"
453
+ name="scansEnabled_checkReadableConfig" value="1" <?php $w->cb( 'scansEnabled_checkReadableConfig' ); ?> />
454
+ </td>
455
+ </tr>
456
+ <!-- <tr>-->
457
+ <!-- <th>Scan for Full Path Disclosure?<a-->
458
+ <!-- href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_Full_Path_Disclosure"-->
459
+ <!-- target="_blank" class="wfhelp"></a></th>-->
460
+ <!-- <td><input type="checkbox" id="scansEnabled_wpscan_fullPathDisclosure" class="wfConfigElem"-->
461
+ <!-- name="scansEnabled_wpscan_fullPathDisclosure" value="1" --><?php //$w->cb( 'scansEnabled_wpscan_fullPathDisclosure' ); ?><!-- />-->
462
+ <!-- </td>-->
463
+ <!-- </tr>-->
464
+ <!-- <tr>-->
465
+ <!-- <th>Scan for Directory Listing?<a-->
466
+ <!-- href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_Directory_Listing"-->
467
+ <!-- target="_blank" class="wfhelp"></a></th>-->
468
+ <!-- <td><input type="checkbox" id="scansEnabled_wpscan_directoryListingEnabled" class="wfConfigElem"-->
469
+ <!-- name="scansEnabled_wpscan_directoryListingEnabled" value="1" --><?php //$w->cb( 'scansEnabled_wpscan_directoryListingEnabled' ); ?><!-- />-->
470
+ <!-- </td>-->
471
+ <!-- </tr>-->
472
  <tr>
473
  <th>Scan core files against repository versions for changes<a
474
  href="http://docs.wordfence.com/en/Wordfence_options#Scan_core_files_against_repository_version_for_changes"
504
  target="_blank" class="wfhelp"></a></th>
505
  <td><input type="checkbox" id="scansEnabled_fileContents" class="wfConfigElem"
506
  name="scansEnabled_fileContents"
507
+ value="1" <?php $w->cb( 'scansEnabled_fileContents' ); ?>/>
508
+
509
+ <a href="#add-more-rules" class="do-show" data-selector="#scan_include_extra">+ Add additional signatures</a>
510
+ </td>
511
  </tr>
512
+ <tr class="hidden" id="scan_include_extra">
513
+ <th style="vertical-align: top;">Additional scan signatures</th>
514
+ <td><textarea class="wfConfigElement" cols="40" rows="4"
515
+ name="scan_include_extra"><?php echo $w->getHTML('scan_include_extra'); ?></textarea>
516
+ </td>
 
 
517
  </tr>
518
  <tr>
519
  <th>Scan posts for known dangerous URLs and suspicious content<a
537
  name="scansEnabled_oldVersions"
538
  value="1" <?php $w->cb( 'scansEnabled_oldVersions' ); ?>/></td>
539
  </tr>
540
+ <tr>
541
+ <th>Scan for admin users created outside of WordPress<a
542
+ href="http://docs.wordfence.com/en/Wordfence_options#Scan_for_admin_users_created_outside_of_WordPress"
543
+ target="_blank" class="wfhelp"></a></th>
544
+ <td><input type="checkbox" id="scansEnabled_suspiciousAdminUsers" class="wfConfigElem"
545
+ name="scansEnabled_suspiciousAdminUsers"
546
+ value="1" <?php $w->cb( 'scansEnabled_suspiciousAdminUsers' ); ?>/></td>
547
+ </tr>
548
  <tr>
549
  <th>Check the strength of passwords<a
550
  href="http://docs.wordfence.com/en/Wordfence_options#Check_the_strength_of_passwords"
601
  <tr>
602
  <td colspan="2">
603
  <div class="wfMarker" id="wfMarkerFirewallRules"></div>
604
+ <h3 class="wfConfigHeading">Rate Limiting Rules<a
605
+ href="http://docs.wordfence.com/en/Wordfence_options#Rate_Limiting_Rules" target="_blank"
606
  class="wfhelp"></a></h3>
607
  </td>
608
  </tr>
847
  <th>Immediately block the IP of users who try to sign in as these usernames<a
848
  href="http://docs.wordfence.com/en/Wordfence_options#Immediately_block_the_IP_of_users_who_try_to_sign_in_as_these_usernames"
849
  target="_blank" class="wfhelp"></a></th>
850
+ <td>
851
+ <textarea name="loginSec_userBlacklist" cols="40" rows="4" id="loginSec_userBlacklist"><?php
852
+ echo wfUtils::cleanupOneEntryPerLine($w->getHTML( 'loginSec_userBlacklist' ))
853
+ ?></textarea><br/>
854
+ (One per line. Existing users won't be blocked.)
855
  </td>
856
  </tr>
857
  <tr>
972
  browser traffic but slow scan starts, live traffic &amp; status updates.
973
  </td>
974
  </tr>
 
 
 
 
 
 
 
975
  <tr>
976
  <th>Delete Wordfence tables and data on deactivation?<a
977
  href="http://docs.wordfence.com/en/Wordfence_options#Delete_Wordfence_tables_and_data_on_deactivation.3F"
990
  will appear to be new visits)
991
  </td>
992
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
993
  <tr>
994
  <th><label for="disableCodeExecutionUploads">Disable Code Execution for Uploads directory</label><a
995
  href="http://docs.wordfence.com/en/Wordfence_options#Disable_Code_Execution_for_Uploads_directory"
998
  name="disableCodeExecutionUploads"
999
  value="1" <?php $w->cb( 'disableCodeExecutionUploads' ); ?> /></td>
1000
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
 
1002
  <tr>
1003
  <td colspan="2">
1092
  </script>
1093
  <script type="text/x-jquery-template" id="wfContentFirewallRules">
1094
  <div>
1095
+ <h3>Rate Limiting Rules</h3>
1096
 
1097
  <p>
1098
  <strong>NOTE:</strong> Before modifying these rules, make sure you have access to the email address
lib/menu_passwd.php CHANGED
@@ -8,14 +8,15 @@
8
  <?php if (!wfConfig::get('isPaid')) { ?>
9
  <div class="wf-premium-callout" style="margin: 20px 0 20px 20px; width: 700px;">
10
  <h3>Password Auditing is only available to Premium Members</h3>
11
- <p>Wordfence Password Auditing uses our high performance password auditing cluster to test the strength of your admin and user passwords. We securely simulate a high-performance password cracking attack on your password database and will alert you to weak passwords. We then provide a way to change weak passwords or alert members that they need to improve their password strength. Upgrade to Premium today:</p>
 
 
12
  <ul>
13
- <li>You can upgrade now for less than $5 per month</li>
14
  <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, advanced scanning options, cell phone sign-in and country blocking give you the best protection available</li>
15
  <li>Access to Premium Support</li>
16
- <li>Discounts of up to 90% available for multiyear and multi-license purchases</li>
17
  </ul>
18
-
19
  <p class="center"><a class="button button-primary" href="https://www.wordfence.com/gnl1pwAuditUp1/wordfence-signup/">Get Premium</a></p>
20
  </div>
21
  <?php } ?>
8
  <?php if (!wfConfig::get('isPaid')) { ?>
9
  <div class="wf-premium-callout" style="margin: 20px 0 20px 20px; width: 700px;">
10
  <h3>Password Auditing is only available to Premium Members</h3>
11
+ <p>Wordfence Password Auditing uses our high performance password auditing cluster to test the strength of your admin and user passwords. We securely simulate a high-performance password cracking attack on your password database and will alert you to weak passwords. We then provide a way to change weak passwords or alert members that they need to improve their password strength.</p>
12
+
13
+ <p>Upgrade to Premium today for less than $5 per month:</p>
14
  <ul>
15
+ <li>Receive real-time Firewall and Scan engine rule updates for protection as threats emerge</li>
16
  <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, advanced scanning options, cell phone sign-in and country blocking give you the best protection available</li>
17
  <li>Access to Premium Support</li>
18
+ <li>Discounts of up to 75% available for multiyear and multi-license purchases</li>
19
  </ul>
 
20
  <p class="center"><a class="button button-primary" href="https://www.wordfence.com/gnl1pwAuditUp1/wordfence-signup/">Get Premium</a></p>
21
  </div>
22
  <?php } ?>
lib/menu_scan.php CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  <div class="wordfenceModeElem" id="wordfenceMode_scan"></div>
2
  <div class="wrap wordfence">
3
  <?php require('menuHeader.php'); ?>
@@ -7,7 +10,7 @@
7
  <table border="0" cellpadding="0" cellspacing="0" style="width: 800px;">
8
  <tr>
9
  <td style="width: 250px; padding-top: 10px;">
10
- <input type="button" value="Start a Wordfence Scan" id="wfStartScanButton1" class="wfStartScanButton button-primary" onclick="wordfenceAdmin.startScan();" /><br />
11
  &nbsp;&nbsp;&nbsp;&nbsp;<a href="#" onclick="WFAD.killScan(); return false;" style="font-size: 10px; color: #AAA;">Click to kill the current scan.</a>
12
  </td>
13
  <td>
@@ -33,28 +36,42 @@
33
  </div>
34
  <?php } ?>
35
  </div></div></div>
36
- <?php if(wfConfig::get('isPaid')){ ?>
37
- <div style="margin: 0 0 20px 5px; width: 795px; font-weight: bold; color: #0A0;">
38
- Premium scanning enabled.
39
- </div>
 
 
 
 
 
40
  <?php } else { ?>
 
 
 
 
 
 
41
  <div class="wf-premium-callout" style="margin: 20px 0 20px 2px;width: 765px;">
42
- <h3>Upgrade to Wordfence Premium today for less than $5 per month</h3>
43
- <ul>
44
- <li>Advanced features like IP reputation monitoring, country blocking, an advanced comment spam
45
- filter and cell phone sign-in give you the best protection available
46
- </li>
47
- <li>Remote, frequent and scheduled scans</li>
48
- <li>Access to Premium Support</li>
49
- <li>Discounts of up to 90% for multiyear and multi-license purchases</li>
50
- </ul>
51
  <p class="center"><a class="button button-primary"
52
  href="https://www.wordfence.com/gnl1scanUpgrade/wordfence-signup/">
53
  Get Premium</a></p>
54
  </div>
55
 
56
-
57
  <?php } ?>
 
 
 
 
 
 
 
58
  <div class="consoleHead" style="margin-top: 20px;">
59
  <span class="consoleHeadText">Scan Detailed Activity</span>
60
  <a href="#" class="wfALogMailLink" onclick="WFAD.emailActivityLog(); return false;">Email activity log</a>
@@ -155,6 +172,140 @@
155
  </div>
156
  </div>
157
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  <script type="text/x-jquery-template" id="issueTmpl_wfThemeUpgrade">
159
  <div>
160
  <div class="wfIssue">
@@ -183,7 +334,7 @@
183
  <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreC'); return false;">Ignore this issue</a>
184
  {{/if}}
185
  {{if status == 'ignoreC' || status == 'ignoreP'}}
186
- <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreC'); return false;">Stop ignoring this issue</a>
187
  {{/if}}
188
  </div>
189
  </div>
@@ -793,6 +944,39 @@
793
  </div>
794
  </script>
795
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
 
797
 
798
 
@@ -807,7 +991,7 @@
807
  and you will see the scan details in the "Activity Log" above in a few seconds.
808
  </td></tr>
809
  <tr><td>
810
- <div class="wordfenceScanButton"><input type="button" value="Start a Wordfence Scan" id="wfStartScanButton2" class="wfStartScanButton button-primary" /></div>
811
  </td></tr>
812
  </table>
813
  </td>
@@ -822,7 +1006,6 @@
822
  <p>
823
  Wordfence is a robust and complete security system and performance enhancer for WordPress. It protects your WordPress site
824
  from security threats and keeps you off Google's SEO black-list by providing a firewall, brute force protection, continuous scanning and many other security enhancements.
825
- Wordfence will also make your site <strong>up to 50 times faster</strong> than a standard WordPress site by installing Falcon Engine, the high performance web engine available exclusively with Wordfence.
826
  </p>
827
  <p>
828
  Wordfence also detects if there are any security problems on
1
+ <?php
2
+ $sigUpdateTime = wfConfig::get('signatureUpdateTime');
3
+ ?>
4
  <div class="wordfenceModeElem" id="wordfenceMode_scan"></div>
5
  <div class="wrap wordfence">
6
  <?php require('menuHeader.php'); ?>
10
  <table border="0" cellpadding="0" cellspacing="0" style="width: 800px;">
11
  <tr>
12
  <td style="width: 250px; padding-top: 10px;">
13
+ <button type="button" id="wfStartScanButton1" class="wfStartScanButton button-primary" onclick="wordfenceAdmin.startScan();">Start a Wordfence Scan</button><br />
14
  &nbsp;&nbsp;&nbsp;&nbsp;<a href="#" onclick="WFAD.killScan(); return false;" style="font-size: 10px; color: #AAA;">Click to kill the current scan.</a>
15
  </td>
16
  <td>
36
  </div>
37
  <?php } ?>
38
  </div></div></div>
39
+ <?php if (wfConfig::get('isPaid')) { ?>
40
+ <?php if (wfConfig::get('scansEnabled_fileContents')): ?>
41
+ <div style="width: 800px; ">
42
+ <p class="wf-success">You are running the Premium version of the Threat Defense Feed which is
43
+ updated in real-time as new threats emerge.</p>
44
+ </div>
45
+ <?php else: ?>
46
+ <div class="wfSecure">Premium scanning enabled</div>
47
+ <?php endif ?>
48
  <?php } else { ?>
49
+ <?php if (wfConfig::get('scansEnabled_fileContents')): ?>
50
+ <p>You are running the Wordfence Community Scan signatures.
51
+ <!-- <em id="wf-scan-sigs-last-update"></em>-->
52
+ </p>
53
+ <?php endif ?>
54
+
55
  <div class="wf-premium-callout" style="margin: 20px 0 20px 2px;width: 765px;">
56
+ <h3>The Wordfence Scan alerts you if you've been hacked</h3>
57
+
58
+ <p>As new threats emerge, the Threat Defense Feed is updated to detect these new hacks. The Premium
59
+ version of the Threat Defense Feed is updated in real-time protecting you immediately. As a free
60
+ user <strong>you are receiving the community version</strong> of the feed which is updated 30 days later. Upgrade
61
+ now for less than $5 a month!</p>
 
 
 
62
  <p class="center"><a class="button button-primary"
63
  href="https://www.wordfence.com/gnl1scanUpgrade/wordfence-signup/">
64
  Get Premium</a></p>
65
  </div>
66
 
 
67
  <?php } ?>
68
+
69
+ <?php if ($sigUpdateTime ): ?>
70
+ <script>
71
+ WFAD.updateSignaturesTimestamp(<?php echo (int) $sigUpdateTime ?>);
72
+ </script>
73
+ <?php endif ?>
74
+
75
  <div class="consoleHead" style="margin-top: 20px;">
76
  <span class="consoleHeadText">Scan Detailed Activity</span>
77
  <a href="#" class="wfALogMailLink" onclick="WFAD.emailActivityLog(); return false;">Email activity log</a>
172
  </div>
173
  </div>
174
  </div>
175
+ <script type="text/x-jquery-template" id="issueTmpl_configReadable">
176
+ <div>
177
+ <div class="wfIssue">
178
+ <h2>${shortMsg}</h2>
179
+ <table border="0" class="wfIssue" cellspacing="0" cellpadding="0">
180
+ <tr>
181
+ <th>URL:</th>
182
+ <td><a href="${data.url}" target="_blank">${data.url}</a></td>
183
+ <tr>
184
+ <th>Severity:</th>
185
+ <td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td>
186
+ </tr>
187
+ <tr>
188
+ <th>Status</th>
189
+ <td>
190
+ {{if status == 'new' }}New{{/if}}
191
+ {{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
192
+ </td>
193
+ </tr>
194
+ </table>
195
+ <p>
196
+ {{html longMsg}}
197
+ </p>
198
+ <div class="wfIssueOptions">
199
+ <strong>Tools:</strong>
200
+ {{if data.fileExists}}
201
+ <a target="_blank" href="${WFAD.makeViewFileLink(data.file)}">View the file</a>
202
+ {{/if}}
203
+ <a href="#" onclick="WFAD.hideFile('${id}', 'delete'); return false;">Hide this file in <em>.htaccess</em></a>
204
+ {{if data.canDelete}}
205
+ <a href="#" onclick="WFAD.deleteFile('${id}'); return false;">Delete this file (can't be undone).</a>
206
+ <p>
207
+ <label><input type="checkbox" class="wfdelCheckbox" value="${id}" />&nbsp;Select for bulk delete</label>
208
+ </p>
209
+ {{/if}}
210
+ </div>
211
+ <div class="wfIssueOptions">
212
+ {{if status == 'new'}}
213
+ <strong>Resolve:</strong>
214
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">I have fixed this issue</a>
215
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreC'); return false;">Ignore this issue</a>
216
+ {{/if}}
217
+ {{if status == 'ignoreC' || status == 'ignoreP'}}
218
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">Stop ignoring this issue</a>
219
+ {{/if}}
220
+ </div>
221
+ </div>
222
+ </div>
223
+ </script>
224
+ <script type="text/x-jquery-template" id="issueTmpl_wpscan_fullPathDiscl">
225
+ <div>
226
+ <div class="wfIssue">
227
+ <h2>${shortMsg}</h2>
228
+ <p>
229
+ <table border="0" class="wfIssue" cellspacing="0" cellpadding="0">
230
+ <tr><th>URL:</th><td><a href="${data.url}" target="_blank">${data.url}</a></td>
231
+ <tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
232
+ <tr><th>Status</th><td>
233
+ {{if status == 'new' }}New{{/if}}
234
+ {{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
235
+ </td></tr>
236
+ </table>
237
+ </p>
238
+ <p>
239
+ {{html longMsg}}
240
+ </p>
241
+ <div class="wfIssueOptions">
242
+ {{if (status == 'new')}}
243
+ <strong>Resolve:</strong>
244
+ <?php if (!wfUtils::isNginx()): ?>
245
+ <a href="#" onclick="WFAD.fixFPD('${id}'); return false;">Fix this issue</a>
246
+ <?php endif ?>
247
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">I have fixed this issue</a>
248
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreC'); return false;">Ignore this issue</a>
249
+ {{/if}}
250
+ {{if status == 'ignoreC' || status == 'ignoreP'}}
251
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">Stop ignoring this issue</a>
252
+ {{/if}}
253
+ </div>
254
+ {{if (status == 'new')}}
255
+ <div class="wfIssueOptions">
256
+ <strong style="width: auto;">Manual Fix:</strong>
257
+ &nbsp;
258
+ Set <code>display_errors</code> to <code>Off</code> in your php.ini file.
259
+ </div>
260
+ {{/if}}
261
+
262
+ </div>
263
+ </div>
264
+ </script>
265
+ <script type="text/x-jquery-template" id="issueTmpl_wpscan_directoryList">
266
+ <div>
267
+ <div class="wfIssue">
268
+ <h2>${shortMsg}</h2>
269
+ <p>
270
+ <table border="0" class="wfIssue" cellspacing="0" cellpadding="0">
271
+ <tr><th>URL:</th><td><a href="${data.url}" target="_blank">${data.url}</a></td>
272
+ <tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
273
+ <tr><th>Status</th><td>
274
+ {{if status == 'new' }}New{{/if}}
275
+ {{if status == 'ignoreP' || status == 'ignoreC' }}Ignored{{/if}}
276
+ </td></tr>
277
+ </table>
278
+ </p>
279
+ <p>
280
+ {{html longMsg}}
281
+ </p>
282
+
283
+ <div class="wfIssueOptions">
284
+ {{if (status == 'new')}}
285
+ <strong>Resolve:</strong>
286
+ <?php if (!wfUtils::isNginx()): ?>
287
+ <a href="#" onclick="WFAD.disableDirectoryListing('${id}'); return false;">Fix this issue</a>
288
+ <?php endif ?>
289
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">I have fixed this issue</a>
290
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreC'); return false;">Ignore this issue</a>
291
+ {{/if}}
292
+ {{if status == 'ignoreC' || status == 'ignoreP'}}
293
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">Stop ignoring this issue</a>
294
+ {{/if}}
295
+ </div>
296
+ <?php if (!wfUtils::isNginx()): ?>
297
+ {{if (status == 'new')}}
298
+ <div class="wfIssueOptions">
299
+ <strong style="width: auto;">Manual Fix:</strong>
300
+ &nbsp;
301
+ Add <code>Options -Indexes</code> to your .htaccess file.
302
+ </div>
303
+ {{/if}}
304
+ <?php endif ?>
305
+
306
+ </div>
307
+ </div>
308
+ </script>
309
  <script type="text/x-jquery-template" id="issueTmpl_wfThemeUpgrade">
310
  <div>
311
  <div class="wfIssue">
334
  <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreC'); return false;">Ignore this issue</a>
335
  {{/if}}
336
  {{if status == 'ignoreC' || status == 'ignoreP'}}
337
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">Stop ignoring this issue</a>
338
  {{/if}}
339
  </div>
340
  </div>
944
  </div>
945
  </script>
946
 
947
+ <script type="text/x-jquery-template" id="issueTmpl_suspiciousAdminUsers">
948
+ <div>
949
+ <div class="wfIssue">
950
+ <h2>${shortMsg}</h2>
951
+ <p>
952
+ <table border="0" class="wfIssue" cellspacing="0" cellpadding="0">
953
+ <tr><th>Severity:</th><td>{{if severity == '1'}}Critical{{else}}Warning{{/if}}</td></tr>
954
+ <tr><th>Status</th><td>
955
+ {{if status == 'new' }}New{{/if}}
956
+ {{if status == 'ignoreC' }}This issue will be ignored until it changes.{{/if}}
957
+ {{if status == 'ignoreP' }}This issue is permanently ignored.{{/if}}
958
+ </td></tr>
959
+ </table>
960
+ </p>
961
+ <p>
962
+ {{html longMsg}}
963
+ </p>
964
+ <div class="wfIssueOptions">
965
+ {{if status == 'new'}}
966
+ <strong>Resolve:</strong>
967
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">I have fixed this issue</a>
968
+ <a href="#" onclick="WFAD.deleteAdminUser('${id}'); return false;">Delete this user</a>
969
+ <a href="#" onclick="WFAD.revokeAdminUser('${id}'); return false;">Revoke all capabilities from this user</a>
970
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'ignoreP'); return false;">Ignore this problem</a>
971
+ {{/if}}
972
+ {{if status == 'ignoreP' || status == 'ignoreC'}}
973
+ <a href="#" onclick="WFAD.updateIssueStatus('${id}', 'delete'); return false;">Stop ignoring this issue</a>
974
+ {{/if}}
975
+ </div>
976
+ </div>
977
+ </div>
978
+ </script>
979
+
980
 
981
 
982
 
991
  and you will see the scan details in the "Activity Log" above in a few seconds.
992
  </td></tr>
993
  <tr><td>
994
+ <div class="wordfenceScanButton"><button type="button" id="wfStartScanButton2" class="wfStartScanButton button-primary">Start a Wordfence Scan</button></div>
995
  </td></tr>
996
  </table>
997
  </td>
1006
  <p>
1007
  Wordfence is a robust and complete security system and performance enhancer for WordPress. It protects your WordPress site
1008
  from security threats and keeps you off Google's SEO black-list by providing a firewall, brute force protection, continuous scanning and many other security enhancements.
 
1009
  </p>
1010
  <p>
1011
  Wordfence also detects if there are any security problems on
lib/menu_scanSchedule.php CHANGED
@@ -5,17 +5,14 @@
5
  <?php if(! wfConfig::get('isPaid')){ ?>
6
  <div class="wf-premium-callout" style="margin: 20px;">
7
  <h3>Scan Scheduling is only available to Premium Members</h3>
8
- <p>Scan Scheduling is a premium feature because it places additional load on our scanning servers. Premium users
9
- can increase their WordPress protection by controlling scan frequency up to once per hour. Premium also
10
- allows you to control when Wordfence initiates a scan, selecting optimal times that don’t interfere with
11
- high-traffic or optimal usage of your site. Upgrade to Premium today:</p>
12
  <ul>
13
- <li>You can upgrade now for less than $5 per month</li>
14
- <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, country blocking
15
- and cell phone sign-in give you the best protection available
16
- </li>
17
  <li>Access to Premium Support</li>
18
- <li>Discounts of up to 90% available for multiyear and multi-license purchases</li>
19
  </ul>
20
  <p class="center"><a class="button button-primary"
21
  href="https://www.wordfence.com/gnl1scanSched1/wordfence-signup/">Get Premium</a></p>
5
  <?php if(! wfConfig::get('isPaid')){ ?>
6
  <div class="wf-premium-callout" style="margin: 20px;">
7
  <h3>Scan Scheduling is only available to Premium Members</h3>
8
+ <p>Premium users can increase their WordPress protection by controlling scan frequency up to once per hour. Premium also allows you to control when Wordfence initiates a scan, selecting optimal times that don’t interfere with high-traffic or optimal usage of your site.</p>
9
+
10
+ <p>Upgrade to Premium today for less than $5 per month:</p>
 
11
  <ul>
12
+ <li>Receive real-time Firewall and Scan engine rule updates for protection as threats emerge</li>
13
+ <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, country blocking and cell phone sign-in give you the best protection available</li>
 
 
14
  <li>Access to Premium Support</li>
15
+ <li>Discounts of up to 75% available for multiyear and multi-license purchases</li>
16
  </ul>
17
  <p class="center"><a class="button button-primary"
18
  href="https://www.wordfence.com/gnl1scanSched1/wordfence-signup/">Get Premium</a></p>
lib/menu_twoFactor.php CHANGED
@@ -5,21 +5,20 @@
5
  <?php if(! wfConfig::get('isPaid')){ ?>
6
  <div class="wf-premium-callout" style="margin: 20px 0 20px 20px; width: 700px;">
7
  <h3>Cellphone Sign-in is only available to Premium Members</h3>
 
 
 
 
8
 
9
- <p>This is a premium feature because we are charged per SMS we send when a user signs in. Upgrade to Premium
10
- today:</p>
11
  <ul>
12
- <li>You can upgrade now for less than $5 per month</li>
13
  <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, advanced
14
  scanning options and country blocking give you the best protection available
15
  </li>
16
  <li>Access to Premium Support</li>
17
- <li>Discounts of up to 90% available for multiyear and multi-license purchases</li>
18
  </ul>
19
- <p>Wordfence's Cellphone Sign-in uses a technique called "Two Factor Authentication" which is used by banks,
20
- government agencies and military world-wide as one of the most secure forms of remote system authentication.
21
- It's now available from Wordfence for your WordPress website. We recommend you enable Cellphone Sign-in for
22
- all Administrator level accounts.</p>
23
 
24
  <p class="center"><a class="button button-primary"
25
  href="https://www.wordfence.com/gnl1twoFac1/wordfence-signup/">Get Premium</a></p>
5
  <?php if(! wfConfig::get('isPaid')){ ?>
6
  <div class="wf-premium-callout" style="margin: 20px 0 20px 20px; width: 700px;">
7
  <h3>Cellphone Sign-in is only available to Premium Members</h3>
8
+ <p>Our Cellphone Sign-in uses a technique called "Two Factor Authentication" which is used by banks, government
9
+ agencies and military world-wide as one of the most secure forms of remote system authentication. It's now
10
+ available from Wordfence for your WordPress website. We recommend you enable Cellphone Sign-in for all
11
+ Administrator level accounts.</p>
12
 
13
+ <p>Upgrade to Premium today for less than $5 per month:</p>
 
14
  <ul>
15
+ <li>Receive real-time Firewall and Scan engine rule updates for protection as threats emerge</li>
16
  <li>Other advanced features like IP reputation monitoring, an advanced comment spam filter, advanced
17
  scanning options and country blocking give you the best protection available
18
  </li>
19
  <li>Access to Premium Support</li>
20
+ <li>Discounts of up to 75% available for multiyear and multi-license purchases</li>
21
  </ul>
 
 
 
 
22
 
23
  <p class="center"><a class="button button-primary"
24
  href="https://www.wordfence.com/gnl1twoFac1/wordfence-signup/">Get Premium</a></p>
lib/menu_waf.php ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $waf = wfWAF::getInstance();
3
+ $config = $waf->getStorageEngine();
4
+ /** @var array $wafData */
5
+ ?>
6
+ <div class="wrap" id="paidWrap">
7
+ <?php require('menuHeader.php'); ?>
8
+ <?php
9
+ $pageTitle = "Wordfence Web Application Firewall";
10
+ $helpLink = "http://docs.wordfence.com/en/WAF";
11
+ $helpLabel = "Learn more about the Wordfence Web Application Firewall";
12
+ include('pageTitle.php');
13
+ ?>
14
+ <div class="wordfenceModeElem" id="wordfenceMode_waf"></div>
15
+ <?php if (!empty($storageExceptionMessage)): ?>
16
+ <div style="font-weight: bold; margin: 20px 0px;;">
17
+ <?php echo wp_kses($storageExceptionMessage, 'post') ?>
18
+ </div>
19
+ <?php elseif (!empty($wafActionContent)): ?>
20
+ <?php echo $wafActionContent ?>
21
+
22
+ <p class="wf-notice"><em>If you cannot complete the setup process,
23
+ <a target="_blank" href="https://docs.wordfence.com/en/Web_Application_Firewall_Setup">click here for help</a>.</em></p>
24
+
25
+ <?php else: ?>
26
+
27
+ <?php if (!empty($configExceptionMessage)): ?>
28
+ <div style="font-weight: bold; margin: 20px 0px;;">
29
+ <?php echo wp_kses($configExceptionMessage, 'post') ?>
30
+ </div>
31
+ <?php endif ?>
32
+
33
+ <?php if (!wfConfig::get('isPaid')) { ?>
34
+ <div class="wf-premium-callout" style="margin: 20px 0 20px 2px;width: 700px;">
35
+ <h3>The Wordfence Firewall stops you from getting hacked</h3>
36
+
37
+ <p>As new threats emerge, the Threat Defense Feed is updated to protect you from new attacks. The
38
+ Premium version of the Threat Defense Feed is updated in real-time protecting you immediately. As a
39
+ free user <strong>you are receiving the community version</strong> of the feed which is updated 30 days later.
40
+ Upgrade now for less than $5 a month!</p>
41
+
42
+ <p class="center"><a class="button button-primary"
43
+ href="https://www.wordfence.com/wafOptions1/wordfence-signup/">
44
+ Get Premium</a></p>
45
+ </div>
46
+ <?php } else { ?>
47
+ <div class="wf-success">
48
+ You are running the Premium version of the Threat Defense Feed which is updated in real-time as new
49
+ threats emerge.
50
+ </div>
51
+ <?php } ?>
52
+
53
+
54
+ <?php if (WFWAF_SUBDIRECTORY_INSTALL): ?>
55
+ <div class="wf-notice">
56
+ You are currently running the Wordfence Web Application Firewall from another WordPress installation.
57
+ 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.
58
+ </div>
59
+ <?php else: ?>
60
+ <div class="wordfenceWrap" style="margin: 20px 20px 20px 30px;">
61
+ <form action="javascript:void(0)" id="waf-config-form">
62
+
63
+ <table class="wfConfigForm">
64
+ <tr>
65
+ <td><h2>Firewall Status:<a href="http://docs.wordfence.com/en/WAF#Firewall_Status"
66
+ target="_blank" class="wfhelp"></a></h2></td>
67
+ <td colspan="2">
68
+ <select style="width: 300px" name="wafStatus" id="input-wafStatus">
69
+ <option<?php echo $config->getConfig('wafStatus') == 'enabled' ? ' selected' : '' ?>
70
+ class="wafStatus-enabled" value="enabled">Enabled and Protecting
71
+ </option>
72
+ <option<?php echo $config->getConfig('wafStatus') == 'learning-mode' ? ' selected' : '' ?>
73
+ class="wafStatus-learning-mode" value="learning-mode">Learning Mode
74
+ </option>
75
+ <option<?php echo $config->getConfig('wafStatus') == 'disabled' ? ' selected' : '' ?>
76
+ class="wafStatus-disabled" value="disabled">Disabled
77
+ </option>
78
+ </select>
79
+ <script>
80
+ (function($) {
81
+ $('#input-wafStatus').val(<?php echo json_encode($config->getConfig('wafStatus')) ?>)
82
+ .on('change', function() {
83
+ var val = $(this).val();
84
+ $('.wafStatus-description').hide();
85
+ $('#wafStatus-' + val + '-description').show();
86
+ });
87
+ })(jQuery);
88
+ </script>
89
+ </td>
90
+ </tr>
91
+ <tr id="waf-learning-mode-grace-row">
92
+ <td></td>
93
+ <td>
94
+ <label>
95
+ <input type="checkbox" name="learningModeGracePeriodEnabled"
96
+ value="1"<?php echo $config->getConfig('learningModeGracePeriodEnabled') ? ' checked' : ''; ?>>
97
+ Automatically switch to Enabled Mode on:
98
+ </label>
99
+ </td>
100
+ <th>
101
+
102
+ <input type="text" name="learningModeGracePeriod" id="input-learningModeGracePeriod"
103
+ class="wf-datetime"
104
+ placeholder="Enabled until..."
105
+ data-value="<?php echo esc_attr($config->getConfig('learningModeGracePeriod') ? (int) $config->getConfig('learningModeGracePeriod') : '') ?>"
106
+ >
107
+ </th>
108
+ </tr>
109
+ <tr>
110
+ <td style="text-align: center">
111
+ <button type="submit" class="button button-primary">Save</button>
112
+ </td>
113
+ <td colspan="2">
114
+ <div class="wafStatus-description" id="wafStatus-enabled-description">
115
+ In this mode, the Wordfence Web Application Firewall is actively blocking requests
116
+ matching known attack patterns, and is actively protecting your site from attackers.
117
+ </div>
118
+ <div class="wafStatus-description" id="wafStatus-learning-mode-description">
119
+ When you first install the Wordfence Web Application Firewall, it will be in learning
120
+ mode. This allows
121
+ Wordfence to learn about your site so that we can understand how to protect it and how
122
+ to allow normal visitors through the firewall. We recommend you let Wordfence learn for
123
+ a week before you enable the firewall.
124
+ </div>
125
+ <div class="wafStatus-description" id="wafStatus-disabled-description">
126
+ In this mode, the Wordfence Web Application Firewall is functionally turned off and
127
+ does not run any of its rules or analyze the request in any way.
128
+ </div>
129
+ </td>
130
+ </tr>
131
+ <?php /* ?>
132
+ <tr>
133
+ <td>
134
+ <input type="checkbox" name="throttleServerSideAttacks" id="input-throttleServerSideAttacks"
135
+ value="1"<?php echo $config->getConfig('throttleServerSideAttacks') ? ' checked' : ''; ?>>
136
+ </td>
137
+ <th><label for="input-throttleServerSideAttacks">Throttle IPs that trip rules matching a server-side
138
+ vulnerability (SQLi, RCE, LFI, etc)</label></th>
139
+ </tr>
140
+ <?php */ ?>
141
+ </table>
142
+
143
+ <br>
144
+
145
+ <h2>Rules<a href="http://docs.wordfence.com/en/WAF#Rules" target="_blank" class="wfhelp"></a></h2>
146
+
147
+ <div id="waf-rules-wrapper"></div>
148
+
149
+ <p>
150
+ <?php if (wfConfig::get('isPaid')): ?>
151
+ You are running Wordfence Premium firewall rules.
152
+ <?php else: ?>
153
+ You are running Wordfence community firewall rules.
154
+ <?php endif ?>
155
+ <!-- <em id="waf-rules-last-updated"></em>-->
156
+ </p>
157
+
158
+ </form>
159
+
160
+ <br>
161
+
162
+ <h2>Whitelisted URLs<a href="http://docs.wordfence.com/en/WAF#Whitelisted_URLs" target="_blank"
163
+ class="wfhelp"></a></h2>
164
+
165
+ <p><em>The URL/parameters in this table will not be tested by the firewall. They are typically added
166
+ while the firewall is in Learning Mode or by an admin who identifies a particular action/request
167
+ is a false positive.</em></p>
168
+
169
+ <p id="whitelist-form">
170
+ <strong>Add Whitelisted URL/Param:</strong><br>
171
+ <label>
172
+ URL:
173
+ <input type="text" name="whitelistURL">
174
+ </label>
175
+ &nbsp;
176
+ <label>
177
+ Param:
178
+ <select name="whitelistParam">
179
+ <option value="request.body">POST Body</option>
180
+ <option value="request.cookies">Cookie</option>
181
+ <option value="request.fileNames">File Name</option>
182
+ <option value="request.headers">Header</option>
183
+ <option value="request.queryString">Query String</option>
184
+ </select>
185
+ </label>
186
+ &nbsp;
187
+ <label>
188
+ Param Name:
189
+ <input type="text" name="whitelistParamName">
190
+ </label>
191
+ <button type="button" class="button button-small" id="waf-whitelisted-urls-add">Add</button>
192
+ </p>
193
+
194
+ <div id="waf-whitelisted-urls-wrapper"></div>
195
+ </div>
196
+ <?php endif ?>
197
+ <?php endif ?>
198
+
199
+ </div>
200
+
201
+ <script type="text/x-jquery-template" id="waf-rules-tmpl">
202
+ <table class="wf-table">
203
+ <thead>
204
+ <tr>
205
+ <th style="width: 5%">Enabled</th>
206
+ <th>Category</th>
207
+ <th>Description</th>
208
+ </tr>
209
+ </thead>
210
+ <tbody>
211
+ {{each(idx, rule) rules}}
212
+ <tr>
213
+ <td style="text-align: center">
214
+ <input type="checkbox" name="ruleEnabled"
215
+ value="${rule.ruleID}" {{if (!disabledRules[rule.ruleID])}} checked{{/if}}>
216
+ </td>
217
+ <td>${rule.category}</td>
218
+ <td>${rule.description}</td>
219
+ </tr>
220
+ {{/each}}
221
+ {{if (rules.length == 0)}}
222
+ <tr>
223
+ <td colspan="4">No rules currently set.
224
+ <a href="#" onclick="WFAD.wafUpdateRules();return false;">Click here</a> to pull down the latest from
225
+ the Wordfence servers.
226
+ </td>
227
+ </tr>
228
+ {{/if}}
229
+ </tbody>
230
+ </table>
231
+ </script>
232
+ <script type="text/x-jquery-template" id="waf-whitelisted-urls-tmpl">
233
+ <table class="wf-table whitelist-table">
234
+ <thead>
235
+ <tr>
236
+ <th style="width: 5%;">Enabled</th>
237
+ <th>URL</th>
238
+ <th>Param</th>
239
+ <th>Created</th>
240
+ <th>Source</th>
241
+ <th>User</th>
242
+ <th>IP</th>
243
+ <th>Action</th>
244
+ </tr>
245
+ </thead>
246
+ <tbody>
247
+ {{each(idx, whitelistedURLParam) whitelistedURLParams}}
248
+ <tr data-index="${idx}">
249
+ <td style="text-align: center;">
250
+ <input name="replaceWhitelistedEnabled" type="hidden" value="${whitelistedURLParam.data.disabled}">
251
+ <input name="whitelistedEnabled" type="checkbox" value="1"
252
+ {{if (!whitelistedURLParam.data.disabled)}} checked{{/if}}>
253
+ </td>
254
+ <td>
255
+ <input name="replaceWhitelistedPath" type="hidden" value="${whitelistedURLParam.path}">
256
+ <span class="whitelist-display">${WFAD.base64_decode(whitelistedURLParam.path)}</span>
257
+ <input name="whitelistedPath" class="whitelist-edit whitelist-path" type="text"
258
+ value="${WFAD.base64_decode(whitelistedURLParam.path)}">
259
+ </td>
260
+ <td>
261
+ <input name="replaceWhitelistedParam" type="hidden" value="${whitelistedURLParam.paramKey}">
262
+ <span class="whitelist-display">${WFAD.base64_decode(whitelistedURLParam.paramKey)}</span>
263
+ <input name="whitelistedParam" class="whitelist-edit whitelist-param-key"
264
+ type="text" value="${WFAD.base64_decode(whitelistedURLParam.paramKey)}">
265
+ </td>
266
+ <td>
267
+ {{if (whitelistedURLParam.data.timestamp)}}
268
+ ${WFAD.dateFormat((new Date(whitelistedURLParam.data.timestamp * 1000)))}
269
+ {{else}}
270
+ -
271
+ {{/if}}
272
+ </td>
273
+ <td>
274
+ {{if (whitelistedURLParam.data.description)}}
275
+ ${whitelistedURLParam.data.description}
276
+ {{else}}
277
+ -
278
+ {{/if}}
279
+ </td>
280
+ <td>
281
+ {{if (whitelistedURLParam.data.userID)}}
282
+ {{if (whitelistedURLParam.data.username)}}
283
+ ${whitelistedURLParam.data.username}
284
+ {{else}}
285
+ ${whitelistedURLParam.data.userID}
286
+ {{/if}}
287
+ {{else}}
288
+ -
289
+ {{/if}}
290
+ </td>
291
+ <td>
292
+ {{if (whitelistedURLParam.data.ip)}}
293
+ ${whitelistedURLParam.data.ip}
294
+ {{else}}
295
+ -
296
+ {{/if}}
297
+ </td>
298
+ <td>
299
+ <span class="whitelist-display" style="white-space: nowrap">
300
+ <button type="button" class="button button-small whitelist-url-edit">Edit</button>
301
+ <button type="button" class="button button-small whitelist-url-delete">Delete</button>
302
+ </span>
303
+ <span class="whitelist-edit" style="white-space: nowrap">
304
+ <button type="button" class="button button-small whitelist-url-save">Save</button>
305
+ <button type="button" class="button button-small whitelist-url-cancel">Cancel</button>
306
+ </span>
307
+ </td>
308
+ </tr>
309
+ {{/each}}
310
+ {{if (whitelistedURLParams.length == 0)}}
311
+ <tr>
312
+ <td colspan="8">No whitelisted URLs currently set.</td>
313
+ </tr>
314
+ {{/if}}
315
+ </tbody>
316
+ </table>
317
+ </script>
318
+
319
+ <script type="text/javascript">
320
+ (function($) {
321
+ WFAD.wafData = <?php echo json_encode($wafData); ?>;
322
+ $('#waf-whitelisted-urls-add').on('click', function() {
323
+ var form = $('#whitelist-form');
324
+
325
+ var inputURL = form.find('[name=whitelistURL]');
326
+ var inputParam = form.find('[name=whitelistParam]');
327
+ var inputParamName = form.find('[name=whitelistParamName]');
328
+
329
+ var url = inputURL.val();
330
+ var param = inputParam.val();
331
+ var paramName = inputParamName.val();
332
+ if (url && param) {
333
+ WFAD.wafConfigSave('addWhitelist', {
334
+ whitelistedEnabled: 1,
335
+ whitelistedPath: url,
336
+ whitelistedParam: param + '[' + paramName + ']'
337
+ });
338
+ }
339
+ });
340
+
341
+ $('#input-wafStatus').on('change', function() {
342
+ var gracePeriodRow = $('#waf-learning-mode-grace-row');
343
+ if ($(this).val() == 'learning-mode') {
344
+ gracePeriodRow.show();
345
+ } else {
346
+ gracePeriodRow.hide();
347
+ }
348
+ }).triggerHandler('change');
349
+
350
+ $('#waf-config-form').on("submit", function() {
351
+ WFAD.wafConfigSave('config', $(this).serializeArray());
352
+ });
353
+ $(function() {
354
+ WFAD.wafConfigPageRender();
355
+
356
+ $('#input-wafStatus').select2({
357
+ minimumResultsForSearch: -1
358
+ }).on('change', function() {
359
+ var select = $(this);
360
+ var container = $($(this).data('select2').$container);
361
+ container.removeClass('wafStatus-enabled wafStatus-learning-mode wafStatus-disabled')
362
+ .addClass('wafStatus-' + select.val());
363
+ }).triggerHandler('change');
364
+
365
+ $('.wf-datetime').datetimepicker({
366
+ timeFormat: 'hh:mmtt z'
367
+ }).each(function() {
368
+ var el = $(this);
369
+ if (el.attr('data-value')) {
370
+ el.datetimepicker('setDate', new Date(el.attr('data-value') * 1000));
371
+ }
372
+ });
373
+
374
+ var learningModeGracePeriod = $('input[name=learningModeGracePeriod]');
375
+ $('input[name=learningModeGracePeriodEnabled]').on('click', function() {
376
+ if (this.value == '1' && this.checked) {
377
+ learningModeGracePeriod.attr('disabled', false);
378
+ if (!learningModeGracePeriod.val()) {
379
+ var date = new Date();
380
+ date.setDate(date.getDate() + 7);
381
+ learningModeGracePeriod.datetimepicker('setDate', date);
382
+ }
383
+ } else {
384
+ learningModeGracePeriod.attr('disabled', true);
385
+ learningModeGracePeriod.val('');
386
+ }
387
+ }).triggerHandler('click');
388
+
389
+ });
390
+
391
+ $(document).on('click', '.whitelist-url-edit', function() {
392
+ var tr = $(this).closest('tr');
393
+ tr.addClass('edit-mode');
394
+ });
395
+ $(document).on('click', '.whitelist-url-delete', function() {
396
+ if (confirm('Are you sure you\'d like to delete this URL?')) {
397
+ var tr = $(this).closest('tr');
398
+
399
+ var pathInput = tr.find('input.whitelist-path');
400
+ var paramInput = tr.find('input.whitelist-param-key');
401
+ WFAD.wafConfigSave('deleteWhitelist', {
402
+ deletedWhitelistedPath: pathInput.val(),
403
+ deletedWhitelistedParam: paramInput.val()
404
+ });
405
+ }
406
+ });
407
+ $(document).on('click', '.whitelist-url-save', function() {
408
+ var tr = $(this).closest('tr');
409
+
410
+ var oldWhitelistedPath = tr.find('input[name=replaceWhitelistedPath]');
411
+ var oldWhitelistedParam = tr.find('input[name=replaceWhitelistedParam]');
412
+ var oldWhitelistedEnabled = tr.find('input[name=replaceWhitelistedEnabled]');
413
+
414
+ var newWhitelistedPath = tr.find('input[name=whitelistedPath]');
415
+ var newWhitelistedParam = tr.find('input[name=whitelistedParam]');
416
+ var newWhitelistedEnabled = tr.find('input[name=whitelistedEnabled]');
417
+
418
+ WFAD.wafConfigSave('replaceWhitelist', {
419
+ oldWhitelistedPath: oldWhitelistedPath.val(),
420
+ oldWhitelistedParam: oldWhitelistedParam.val(),
421
+ oldWhitelistedEnabled: oldWhitelistedEnabled.val(),
422
+ newWhitelistedPath: newWhitelistedPath.val(),
423
+ newWhitelistedParam: newWhitelistedParam.val(),
424
+ newWhitelistedEnabled: newWhitelistedEnabled.val()
425
+ });
426
+ });
427
+ $(document).on('click', '.whitelist-url-cancel', function() {
428
+ var tr = $(this).closest('tr');
429
+ tr.removeClass('edit-mode');
430
+ });
431
+ $(document).on('click', 'input[name=whitelistedEnabled]', function() {
432
+ var tr = $(this).closest('tr');
433
+
434
+ var oldWhitelistedPath = tr.find('input[name=replaceWhitelistedPath]');
435
+ var oldWhitelistedParam = tr.find('input[name=replaceWhitelistedParam]');
436
+ var enabled = this.checked ? 1 : 0;
437
+
438
+ WFAD.wafConfigSave('enableWhitelist', {
439
+ whitelistedPath: oldWhitelistedPath.val(),
440
+ whitelistedParam: oldWhitelistedParam.val(),
441
+ whitelistedEnabled: enabled
442
+ });
443
+ });
444
+
445
+ $(document).on('click', 'input[name=ruleEnabled]', function() {
446
+ var enabled = this.checked ? 1 : 0;
447
+
448
+ WFAD.wafConfigSave('enableRule', {
449
+ ruleID: this.value,
450
+ ruleEnabled: enabled
451
+ });
452
+ });
453
+
454
+ })(jQuery);
455
+ </script>
456
+
457
+ <script type="text/x-jquery-template" id="wfWAFTour">
458
+ <div>
459
+ <h3>Wordfence Web Application Firewall</h3>
460
+ <p>The Wordfence Web Application Firewall filters out malicious requests before they reach your site. Once it
461
+ is enabled, it runs before WordPress itself, to filter attacks before plugins or themes can run any
462
+ potentially vulnerable code. As new threats emerge, the rules are updated in real-time from the Wordfence
463
+ servers for Premium members. Free users receive the community version of the rules which are updated
464
+ 30 days later.</p>
465
+
466
+ <?php if (!wfConfig::get('isPaid')): ?>
467
+ <p>If you would like to get real-time updates to firewall rules, please <a
468
+ href="https://www.wordfence.com/wafOptions2/wordfence-signup/">upgrade to our premium version</a>.
469
+ </p>
470
+ <?php endif ?>
471
+ </div>
472
+ </script>
lib/sysinfo.php CHANGED
@@ -10,7 +10,7 @@ ob_start();
10
  phpinfo(INFO_ALL);
11
  $out = ob_get_clean();
12
  $out = str_replace('width="600"','width="900"', $out);
13
- $out = preg_replace('/<hr.*?PHP Credits.*?<\/h1>/s', '', $out);
14
  $out = preg_replace('/<a [^>]+>/', '', $out);
15
  $out = preg_replace('/<\/a>/', '', $out);
16
  $out = preg_replace('/<title>[^<]*<\/title>/','', $out);
10
  phpinfo(INFO_ALL);
11
  $out = ob_get_clean();
12
  $out = str_replace('width="600"','width="900"', $out);
13
+ // $out = preg_replace('/<hr.*?PHP Credits.*?<\/h1>/s', '', $out);
14
  $out = preg_replace('/<a [^>]+>/', '', $out);
15
  $out = preg_replace('/<\/a>/', '', $out);
16
  $out = preg_replace('/<title>[^<]*<\/title>/','', $out);
lib/wf503.php CHANGED
@@ -6,10 +6,10 @@
6
  <p>Your access to this service has been temporarily limited. Please try again in a few minutes. (HTTP response code 503)</p>
7
  <p>Reason: <span style="color: #F00;"><?php echo $reason; ?></span></p>
8
  <p style="width: 600px;"><b>Important note for site admins: </b>If you are the administrator of this website note that your access has been limited because you broke one of the Wordfence firewall rules.
9
- The reason you access was limited is: <b>"<?php echo $reason; ?>"</b>.
10
  <br /><br />
11
  If this is a false positive, meaning that your access to your own site has been limited incorrectly, then you
12
- will need to regain access to your site, go to the Wordfence "options" page, go to the section for Firewall Rules and disable the rule that caused you to be blocked. For example,
13
  if you were blocked because it was detected that you are a fake Google crawler, then disable the rule that blocks fake google crawlers. Or if you were blocked because you
14
  were accessing your site too quickly, then increase the number of accesses allowed per minute.
15
  <br /><br />
6
  <p>Your access to this service has been temporarily limited. Please try again in a few minutes. (HTTP response code 503)</p>
7
  <p>Reason: <span style="color: #F00;"><?php echo $reason; ?></span></p>
8
  <p style="width: 600px;"><b>Important note for site admins: </b>If you are the administrator of this website note that your access has been limited because you broke one of the Wordfence firewall rules.
9
+ The reason your access was limited is: <b>"<?php echo $reason; ?>"</b>.
10
  <br /><br />
11
  If this is a false positive, meaning that your access to your own site has been limited incorrectly, then you
12
+ will need to regain access to your site, go to the Wordfence "options" page, go to the section for Rate Limiting Rules and disable the rule that caused you to be blocked. For example,
13
  if you were blocked because it was detected that you are a fake Google crawler, then disable the rule that blocks fake google crawlers. Or if you were blocked because you
14
  were accessing your site too quickly, then increase the number of accesses allowed per minute.
15
  <br /><br />
lib/wfAPI.php CHANGED
@@ -118,11 +118,12 @@ class wfAPI {
118
  }
119
  }
120
  return self::buildQuery(array(
121
- 'v' => $this->wordpressVersion,
122
- 's' => $siteurl,
123
- 'k' => $this->APIKey,
124
- 'openssl' => function_exists('openssl_verify') && defined('OPENSSL_VERSION_NUMBER') ? OPENSSL_VERSION_NUMBER : '0.0.0',
125
- 'phpv' => phpversion(),
 
126
  ));
127
  }
128
 
118
  }
119
  }
120
  return self::buildQuery(array(
121
+ 'v' => $this->wordpressVersion,
122
+ 's' => $siteurl,
123
+ 'k' => $this->APIKey,
124
+ 'openssl' => function_exists('openssl_verify') && defined('OPENSSL_VERSION_NUMBER') ? OPENSSL_VERSION_NUMBER : '0.0.0',
125
+ 'phpv' => phpversion(),
126
+ 'betaFeed' => (int) wfConfig::get('betaThreatDefenseFeed'),
127
  ));
128
  }
129
 
lib/wfConfig.php CHANGED
@@ -34,16 +34,18 @@ class wfConfig {
34
  "scansEnabled_plugins" => false,
35
  "scansEnabled_malware" => false,
36
  "scansEnabled_fileContents" => false,
37
- "scansEnabled_database" => false,
38
  "scansEnabled_posts" => false,
39
  "scansEnabled_comments" => false,
40
  "scansEnabled_passwds" => false,
41
  "scansEnabled_diskSpace" => false,
42
  "scansEnabled_options" => false,
 
 
43
  "scansEnabled_dns" => false,
44
  "scansEnabled_scanImages" => false,
45
  "scansEnabled_highSense" => false,
46
  "scansEnabled_oldVersions" => false,
 
47
  "firewallEnabled" => false,
48
  "blockFakeBots" => false,
49
  "autoBlockScanners" => false,
@@ -74,7 +76,8 @@ class wfConfig {
74
  ),
75
  "otherParams" => array(
76
  'securityLevel' => '0',
77
- "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
 
78
  "neverBlockBG" => "neverBlockVerified",
79
  "loginSec_countFailMins" => "5",
80
  "loginSec_lockoutMins" => "5",
@@ -124,16 +127,18 @@ class wfConfig {
124
  "scansEnabled_plugins" => false,
125
  "scansEnabled_malware" => true,
126
  "scansEnabled_fileContents" => true,
127
- "scansEnabled_database" => true,
128
  "scansEnabled_posts" => true,
129
  "scansEnabled_comments" => true,
130
  "scansEnabled_passwds" => true,
131
  "scansEnabled_diskSpace" => true,
132
  "scansEnabled_options" => true,
 
 
133
  "scansEnabled_dns" => true,
134
  "scansEnabled_scanImages" => false,
135
  "scansEnabled_highSense" => false,
136
  "scansEnabled_oldVersions" => true,
 
137
  "firewallEnabled" => true,
138
  "blockFakeBots" => false,
139
  "autoBlockScanners" => true,
@@ -165,6 +170,7 @@ class wfConfig {
165
  "otherParams" => array(
166
  'securityLevel' => '1',
167
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
 
168
  "neverBlockBG" => "neverBlockVerified",
169
  "loginSec_countFailMins" => "5",
170
  "loginSec_lockoutMins" => "5",
@@ -201,6 +207,7 @@ class wfConfig {
201
  "alertOn_adminLogin" => true,
202
  "alertOn_nonAdminLogin" => false,
203
  "liveTrafficEnabled" => true,
 
204
  "advancedCommentScanning" => false,
205
  "checkSpamIP" => false,
206
  "spamvertizeCheck" => false,
@@ -214,16 +221,18 @@ class wfConfig {
214
  "scansEnabled_plugins" => false,
215
  "scansEnabled_malware" => true,
216
  "scansEnabled_fileContents" => true,
217
- "scansEnabled_database" => true,
218
  "scansEnabled_posts" => true,
219
  "scansEnabled_comments" => true,
220
  "scansEnabled_passwds" => true,
221
  "scansEnabled_diskSpace" => true,
222
  "scansEnabled_options" => true,
 
 
223
  "scansEnabled_dns" => true,
224
  "scansEnabled_scanImages" => false,
225
  "scansEnabled_highSense" => false,
226
  "scansEnabled_oldVersions" => true,
 
227
  "firewallEnabled" => true,
228
  "blockFakeBots" => false,
229
  "autoBlockScanners" => true,
@@ -253,8 +262,10 @@ class wfConfig {
253
  'ssl_verify' => true,
254
  ),
255
  "otherParams" => array(
 
256
  'securityLevel' => '2',
257
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
 
258
  "neverBlockBG" => "neverBlockVerified",
259
  "loginSec_countFailMins" => "240",
260
  "loginSec_lockoutMins" => "240",
@@ -304,16 +315,18 @@ class wfConfig {
304
  "scansEnabled_plugins" => false,
305
  "scansEnabled_malware" => true,
306
  "scansEnabled_fileContents" => true,
307
- "scansEnabled_database" => true,
308
  "scansEnabled_posts" => true,
309
  "scansEnabled_comments" => true,
310
  "scansEnabled_passwds" => true,
311
  "scansEnabled_diskSpace" => true,
312
  "scansEnabled_options" => true,
 
 
313
  "scansEnabled_dns" => true,
314
  "scansEnabled_scanImages" => false,
315
  "scansEnabled_highSense" => false,
316
  "scansEnabled_oldVersions" => true,
 
317
  "firewallEnabled" => true,
318
  "blockFakeBots" => false,
319
  "autoBlockScanners" => true,
@@ -345,6 +358,7 @@ class wfConfig {
345
  "otherParams" => array(
346
  'securityLevel' => '3',
347
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
 
348
  "neverBlockBG" => "neverBlockVerified",
349
  "loginSec_countFailMins" => "1440",
350
  "loginSec_lockoutMins" => "1440",
@@ -394,16 +408,18 @@ class wfConfig {
394
  "scansEnabled_plugins" => false,
395
  "scansEnabled_malware" => true,
396
  "scansEnabled_fileContents" => true,
397
- "scansEnabled_database" => true,
398
  "scansEnabled_posts" => true,
399
  "scansEnabled_comments" => true,
400
  "scansEnabled_passwds" => true,
401
  "scansEnabled_diskSpace" => true,
402
  "scansEnabled_options" => true,
 
 
403
  "scansEnabled_dns" => true,
404
  "scansEnabled_scanImages" => false,
405
  "scansEnabled_highSense" => false,
406
  "scansEnabled_oldVersions" => true,
 
407
  "firewallEnabled" => true,
408
  "blockFakeBots" => true,
409
  "autoBlockScanners" => true,
@@ -435,6 +451,7 @@ class wfConfig {
435
  "otherParams" => array(
436
  'securityLevel' => '4',
437
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
 
438
  "neverBlockBG" => "neverBlockVerified",
439
  "loginSec_countFailMins" => "1440",
440
  "loginSec_lockoutMins" => "1440",
@@ -551,12 +568,20 @@ class wfConfig {
551
  return;
552
  }
553
 
 
 
 
 
 
 
 
 
554
  self::getDB()->queryWrite("insert into " . self::table() . " (name, val) values ('%s', '%s') ON DUPLICATE KEY UPDATE val='%s'", $key, $val, $val);
555
  self::$cache[$key] = $val;
556
  self::clearDiskCache();
557
  }
558
  private static function getCacheFile(){
559
- return wfUtils::getPluginBaseDir() . 'wordfence/tmp/configCache.php';
560
  }
561
  public static function clearDiskCache(){
562
  //When we write to the cache we just trash the whole cache on the first write. Second write won't get called because we've disabled the cache.
@@ -614,7 +639,7 @@ class wfConfig {
614
  }
615
  }
616
  $val = self::getDB()->querySingle("select val from " . self::table() . " where name='%s'", $key);
617
- if(self::$diskCacheDisabled){
618
  return $val;
619
  }
620
  wfConfig::$diskCache[$key] = isset($val) ? $val : '';
@@ -734,7 +759,7 @@ class wfConfig {
734
  }
735
  }
736
  private static function getPotentialTempDirs() {
737
- return array(wfUtils::getPluginBaseDir() . 'wordfence/tmp/', sys_get_temp_dir(), ABSPATH . 'wp-content/uploads/');
738
  }
739
  public static function f($key){
740
  echo esc_attr(self::get($key));
@@ -843,9 +868,9 @@ class wfConfig {
843
  wp_update_plugins();
844
  ob_start();
845
  $upgrader = new Plugin_Upgrader();
846
- $upret = $upgrader->upgrade('wordfence/wordfence.php');
847
  if($upret){
848
- $cont = file_get_contents(WP_PLUGIN_DIR . '/wordfence/wordfence.php');
849
  if(wfConfig::get('alertOn_update') == '1' && preg_match('/Version: (\d+\.\d+\.\d+)/', $cont, $matches) ){
850
  wordfence::alert("Wordfence Upgraded to version " . $matches[1], "Your Wordfence installation has been upgraded to version " . $matches[1], '127.0.0.1');
851
  }
34
  "scansEnabled_plugins" => false,
35
  "scansEnabled_malware" => false,
36
  "scansEnabled_fileContents" => false,
 
37
  "scansEnabled_posts" => false,
38
  "scansEnabled_comments" => false,
39
  "scansEnabled_passwds" => false,
40
  "scansEnabled_diskSpace" => false,
41
  "scansEnabled_options" => false,
42
+ "scansEnabled_wpscan_fullPathDisclosure" => true,
43
+ "scansEnabled_wpscan_directoryListingEnabled" => true,
44
  "scansEnabled_dns" => false,
45
  "scansEnabled_scanImages" => false,
46
  "scansEnabled_highSense" => false,
47
  "scansEnabled_oldVersions" => false,
48
+ "scansEnabled_suspiciousAdminUsers" => false,
49
  "firewallEnabled" => false,
50
  "blockFakeBots" => false,
51
  "autoBlockScanners" => false,
76
  ),
77
  "otherParams" => array(
78
  'securityLevel' => '0',
79
+ "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
80
+ 'liveTraf_maxRows' => 2000,
81
  "neverBlockBG" => "neverBlockVerified",
82
  "loginSec_countFailMins" => "5",
83
  "loginSec_lockoutMins" => "5",
127
  "scansEnabled_plugins" => false,
128
  "scansEnabled_malware" => true,
129
  "scansEnabled_fileContents" => true,
 
130
  "scansEnabled_posts" => true,
131
  "scansEnabled_comments" => true,
132
  "scansEnabled_passwds" => true,
133
  "scansEnabled_diskSpace" => true,
134
  "scansEnabled_options" => true,
135
+ "scansEnabled_wpscan_fullPathDisclosure" => true,
136
+ "scansEnabled_wpscan_directoryListingEnabled" => true,
137
  "scansEnabled_dns" => true,
138
  "scansEnabled_scanImages" => false,
139
  "scansEnabled_highSense" => false,
140
  "scansEnabled_oldVersions" => true,
141
+ "scansEnabled_suspiciousAdminUsers" => true,
142
  "firewallEnabled" => true,
143
  "blockFakeBots" => false,
144
  "autoBlockScanners" => true,
170
  "otherParams" => array(
171
  'securityLevel' => '1',
172
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
173
+ 'liveTraf_maxRows' => 2000,
174
  "neverBlockBG" => "neverBlockVerified",
175
  "loginSec_countFailMins" => "5",
176
  "loginSec_lockoutMins" => "5",
207
  "alertOn_adminLogin" => true,
208
  "alertOn_nonAdminLogin" => false,
209
  "liveTrafficEnabled" => true,
210
+ "scansEnabled_checkReadableConfig" => true,
211
  "advancedCommentScanning" => false,
212
  "checkSpamIP" => false,
213
  "spamvertizeCheck" => false,
221
  "scansEnabled_plugins" => false,
222
  "scansEnabled_malware" => true,
223
  "scansEnabled_fileContents" => true,
 
224
  "scansEnabled_posts" => true,
225
  "scansEnabled_comments" => true,
226
  "scansEnabled_passwds" => true,
227
  "scansEnabled_diskSpace" => true,
228
  "scansEnabled_options" => true,
229
+ "scansEnabled_wpscan_fullPathDisclosure" => true,
230
+ "scansEnabled_wpscan_directoryListingEnabled" => true,
231
  "scansEnabled_dns" => true,
232
  "scansEnabled_scanImages" => false,
233
  "scansEnabled_highSense" => false,
234
  "scansEnabled_oldVersions" => true,
235
+ "scansEnabled_suspiciousAdminUsers" => true,
236
  "firewallEnabled" => true,
237
  "blockFakeBots" => false,
238
  "autoBlockScanners" => true,
262
  'ssl_verify' => true,
263
  ),
264
  "otherParams" => array(
265
+ "scan_include_extra" => "",
266
  'securityLevel' => '2',
267
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
268
+ 'liveTraf_maxRows' => 2000,
269
  "neverBlockBG" => "neverBlockVerified",
270
  "loginSec_countFailMins" => "240",
271
  "loginSec_lockoutMins" => "240",
315
  "scansEnabled_plugins" => false,
316
  "scansEnabled_malware" => true,
317
  "scansEnabled_fileContents" => true,
 
318
  "scansEnabled_posts" => true,
319
  "scansEnabled_comments" => true,
320
  "scansEnabled_passwds" => true,
321
  "scansEnabled_diskSpace" => true,
322
  "scansEnabled_options" => true,
323
+ "scansEnabled_wpscan_fullPathDisclosure" => true,
324
+ "scansEnabled_wpscan_directoryListingEnabled" => true,
325
  "scansEnabled_dns" => true,
326
  "scansEnabled_scanImages" => false,
327
  "scansEnabled_highSense" => false,
328
  "scansEnabled_oldVersions" => true,
329
+ "scansEnabled_suspiciousAdminUsers" => true,
330
  "firewallEnabled" => true,
331
  "blockFakeBots" => false,
332
  "autoBlockScanners" => true,
358
  "otherParams" => array(
359
  'securityLevel' => '3',
360
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
361
+ 'liveTraf_maxRows' => 2000,
362
  "neverBlockBG" => "neverBlockVerified",
363
  "loginSec_countFailMins" => "1440",
364
  "loginSec_lockoutMins" => "1440",
408
  "scansEnabled_plugins" => false,
409
  "scansEnabled_malware" => true,
410
  "scansEnabled_fileContents" => true,
 
411
  "scansEnabled_posts" => true,
412
  "scansEnabled_comments" => true,
413
  "scansEnabled_passwds" => true,
414
  "scansEnabled_diskSpace" => true,
415
  "scansEnabled_options" => true,
416
+ "scansEnabled_wpscan_fullPathDisclosure" => true,
417
+ "scansEnabled_wpscan_directoryListingEnabled" => true,
418
  "scansEnabled_dns" => true,
419
  "scansEnabled_scanImages" => false,
420
  "scansEnabled_highSense" => false,
421
  "scansEnabled_oldVersions" => true,
422
+ "scansEnabled_suspiciousAdminUsers" => true,
423
  "firewallEnabled" => true,
424
  "blockFakeBots" => true,
425
  "autoBlockScanners" => true,
451
  "otherParams" => array(
452
  'securityLevel' => '4',
453
  "alertEmails" => "", "liveTraf_ignoreUsers" => "", "liveTraf_ignoreIPs" => "", "liveTraf_ignoreUA" => "", "apiKey" => "", "maxMem" => '256', 'scan_exclude' => '', 'whitelisted' => '', 'bannedURLs' => '', 'maxExecutionTime' => '', 'howGetIPs' => '', 'actUpdateInterval' => '', 'alert_maxHourly' => 0, 'loginSec_userBlacklist' => '',
454
+ 'liveTraf_maxRows' => 2000,
455
  "neverBlockBG" => "neverBlockVerified",
456
  "loginSec_countFailMins" => "1440",
457
  "loginSec_lockoutMins" => "1440",
568
  return;
569
  }
570
 
571
+ if ($key == 'apiKey' && wfWAF::getInstance() && !WFWAF_SUBDIRECTORY_INSTALL) {
572
+ try {
573
+ wfWAF::getInstance()->getStorageEngine()->setConfig('apiKey', $val);
574
+ } catch (wfWAFStorageFileException $e) {
575
+ error_log($e->getMessage());
576
+ }
577
+ }
578
+
579
  self::getDB()->queryWrite("insert into " . self::table() . " (name, val) values ('%s', '%s') ON DUPLICATE KEY UPDATE val='%s'", $key, $val, $val);
580
  self::$cache[$key] = $val;
581
  self::clearDiskCache();
582
  }
583
  private static function getCacheFile(){
584
+ return WORDFENCE_PATH . 'tmp/configCache.php';
585
  }
586
  public static function clearDiskCache(){
587
  //When we write to the cache we just trash the whole cache on the first write. Second write won't get called because we've disabled the cache.
639
  }
640
  }
641
  $val = self::getDB()->querySingle("select val from " . self::table() . " where name='%s'", $key);
642
+ if(self::$diskCacheDisabled){
643
  return $val;
644
  }
645
  wfConfig::$diskCache[$key] = isset($val) ? $val : '';
759
  }
760
  }
761
  private static function getPotentialTempDirs() {
762
+ return array(WORDFENCE_PATH . 'tmp/', sys_get_temp_dir(), ABSPATH . 'wp-content/uploads/');
763
  }
764
  public static function f($key){
765
  echo esc_attr(self::get($key));
868
  wp_update_plugins();
869
  ob_start();
870
  $upgrader = new Plugin_Upgrader();
871
+ $upret = $upgrader->upgrade(WORDFENCE_BASENAME);
872
  if($upret){
873
+ $cont = file_get_contents(WORDFENCE_FCPATH);
874
  if(wfConfig::get('alertOn_update') == '1' && preg_match('/Version: (\d+\.\d+\.\d+)/', $cont, $matches) ){
875
  wordfence::alert("Wordfence Upgraded to version " . $matches[1], "Your Wordfence installation has been upgraded to version " . $matches[1], '127.0.0.1');
876
  }
lib/wfCrawl.php CHANGED
@@ -47,20 +47,18 @@ class wfCrawl {
47
  return false;
48
  }
49
  }
50
- public static function isGooglebot(){
51
- $UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
52
- if(preg_match('/Googlebot\/\d\.\d/', $UA)){ // UA: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) or (rarely used): Googlebot/2.1 (+http://www.google.com/bot.html)
53
- return true;
54
  }
55
- return false;
56
  }
57
- public static function isGoogleCrawler($UA = null){
58
- if ($UA === null) {
59
- $UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
60
  }
61
-
62
- foreach(self::$googPat as $pat){
63
- if(preg_match($pat . 'i', $UA)){
64
  return true;
65
  }
66
  }
47
  return false;
48
  }
49
  }
50
+ public static function isGooglebot($userAgent = null){
51
+ if ($userAgent === null) {
52
+ $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
 
53
  }
54
+ return (bool) preg_match('/Googlebot\/\d\.\d/', $userAgent);
55
  }
56
+ public static function isGoogleCrawler($userAgent = null){
57
+ if ($userAgent === null) {
58
+ $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
59
  }
60
+ foreach (self::$googPat as $pat) {
61
+ if (preg_match($pat . 'i', $userAgent)) {
 
62
  return true;
63
  }
64
  }
lib/wfDB.php CHANGED
@@ -102,7 +102,166 @@ class wfDB {
102
  global $wpdb;
103
  return $wpdb->_real_escape($str);
104
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
 
 
108
  ?>
102
  global $wpdb;
103
  return $wpdb->_real_escape($str);
104
  }
105
+ }
106
+
107
+ abstract class wfModel {
108
+
109
+ private $data;
110
+ private $db;
111
+ private $dirty = false;
112
+
113
+ /**
114
+ * Column name of the primary key field.
115
+ *
116
+ * @return string
117
+ */
118
+ abstract public function getIDColumn();
119
+
120
+ /**
121
+ * Table name.
122
+ *
123
+ * @return mixed
124
+ */
125
+ abstract public function getTable();
126
+
127
+ /**
128
+ * Checks if this is a valid column in the table before setting data on the model.
129
+ *
130
+ * @param string $column
131
+ * @return boolean
132
+ */
133
+ abstract public function hasColumn($column);
134
 
135
+ /**
136
+ * wfModel constructor.
137
+ * @param array|int|string $data
138
+ */
139
+ public function __construct($data = array()) {
140
+ if (is_array($data) || is_object($data)) {
141
+ $this->setData($data);
142
+ } else if (is_numeric($data)) {
143
+ $this->fetchByID($data);
144
+ }
145
+ }
146
+
147
+ public function fetchByID($id) {
148
+ $id = absint($id);
149
+ $data = $this->getDB()->get_row($this->getDB()->prepare('SELECT * FROM ' . $this->getTable() .
150
+ ' WHERE ' . $this->getIDColumn() . ' = %d', $id));
151
+ if ($data) {
152
+ $this->setData($data);
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+
158
+ /**
159
+ * @return bool
160
+ */
161
+ public function save() {
162
+ if (!$this->dirty) {
163
+ return false;
164
+ }
165
+ $this->dirty = ($this->getPrimaryKey() ? $this->update() : $this->insert()) === false;
166
+ return !$this->dirty;
167
+ }
168
+
169
+ /**
170
+ * @return false|int
171
+ */
172
+ public function insert() {
173
+ $data = $this->getData();
174
+ unset($data[$this->getPrimaryKey()]);
175
+ $rowsAffected = $this->getDB()->insert($this->getTable(), $data);
176
+ $this->setPrimaryKey($this->getDB()->insert_id);
177
+ return $rowsAffected;
178
+ }
179
+
180
+ /**
181
+ * @return false|int
182
+ */
183
+ public function update() {
184
+ return $this->getDB()->update($this->getTable(), $this->getData(), array(
185
+ $this->getIDColumn() => $this->getPrimaryKey(),
186
+ ));
187
+ }
188
+
189
+ /**
190
+ * @param $name string
191
+ * @return mixed
192
+ */
193
+ public function __get($name) {
194
+ if (!$this->hasColumn($name)) {
195
+ return null;
196
+ }
197
+ return array_key_exists($name, $this->data) ? $this->data[$name] : null;
198
+ }
199
+
200
+ /**
201
+ * @param $name string
202
+ * @param $value mixed
203
+ */
204
+ public function __set($name, $value) {
205
+ if (!$this->hasColumn($name)) {
206
+ return;
207
+ }
208
+ $this->data[$name] = $value;
209
+ $this->dirty = true;
210
+ }
211
+
212
+ /**
213
+ * @return array
214
+ */
215
+ public function getData() {
216
+ return $this->data;
217
+ }
218
+
219
+ /**
220
+ * @param array $data
221
+ * @param bool $flagDirty
222
+ */
223
+ public function setData($data, $flagDirty = true) {
224
+ $this->data = array();
225
+ foreach ($data as $column => $value) {
226
+ if ($this->hasColumn($column)) {
227
+ $this->data[$column] = $value;
228
+ $this->dirty = (bool) $flagDirty;
229
+ }
230
+ }
231
+ }
232
+
233
+ /**
234
+ * @return wpdb
235
+ */
236
+ public function getDB() {
237
+ if ($this->db === null) {
238
+ global $wpdb;
239
+ $this->db = $wpdb;
240
+ }
241
+ return $this->db;
242
+ }
243
+
244
+ /**
245
+ * @param wpdb $db
246
+ */
247
+ public function setDB($db) {
248
+ $this->db = $db;
249
+ }
250
+
251
+ /**
252
+ * @return int
253
+ */
254
+ public function getPrimaryKey() {
255
+ return $this->{$this->getIDColumn()};
256
+ }
257
+
258
+ /**
259
+ * @param int $value
260
+ */
261
+ public function setPrimaryKey($value) {
262
+ $this->{$this->getIDColumn()} = $value;
263
+ }
264
  }
265
 
266
+
267
  ?>
lib/wfDiagnostic.php ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wfGrant
4
+ {
5
+ public $select = false;
6
+ public $update = false;
7
+ public $insert = false;
8
+ public $delete = false;
9
+ public $alter = false;
10
+ public $create = false;
11
+ public $drop = false;
12
+
13
+ public static function get()
14
+ {
15
+ static $instance;
16
+ if ($instance === null) {
17
+ $instance = new self;
18
+ }
19
+ return $instance;
20
+ }
21
+
22
+ private function __construct()
23
+ {
24
+ global $wpdb;
25
+ $rows = $wpdb->get_results("SHOW GRANTS FOR current_user()", ARRAY_N);
26
+
27
+ foreach ($rows as $row) {
28
+ preg_match("/GRANT (.+) ON (.+) TO/", $row[0], $matches);
29
+ foreach (explode(",", $matches[1]) as $permission) {
30
+ $permission = str_replace(" ", "_", trim(strtolower($permission)));
31
+ if ($permission === 'all_privileges') {
32
+ foreach ($this as $key => $value) {
33
+ $this->$key = true;
34
+ }
35
+ break 2;
36
+ }
37
+ $this->$permission = true;
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ class wfDiagnostic
44
+ {
45
+ protected $minVersion = array(
46
+ 'PHP' => '5.2.4',
47
+ 'cURL' => '1.0',
48
+ );
49
+
50
+ protected $description = array(
51
+ 'Filesystem' => array(
52
+ 'isTmpReadable' => 'Checking if web server can read from <code>~/plugins/wordfence/tmp</code>',
53
+ 'isTmpWritable' => 'Checking if web server can write to <code>~/plugins/wordfence/tmp</code>',
54
+ 'testWfCache' => 'Checking if web server can write to <code>~/wp-content/wfcache</code>',
55
+ ),
56
+ 'MySQL' => array(
57
+ 'userCanDelete' => 'Checking if MySQL user has <code>DELETE</code> privilege',
58
+ 'userCanInsert' => 'Checking if MySQL user has <code>INSERT</code> privilege',
59
+ 'userCanSelect' => 'Checking if MySQL user has <code>SELECT</code> privilege',
60
+ 'userCanCreate' => 'Checking if MySQL user has <code>CREATE TABLE</code> privilege',
61
+ 'userCanAlter' => 'Checking if MySQL user has <code>ALTER TABLE</code> privilege',
62
+ 'userCanDrop' => 'Checking if MySQL user has <code>DROP</code> privilege',
63
+ 'userCanTruncate' => 'Checking if MySQL user has <code>TRUNCATE</code> privilege',
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
+ ),
70
+ 'Connectivity' => array(
71
+ 'connectToServer1' => 'Connecting to Wordfence servers (http)',
72
+ 'connectToServer2' => 'Connecting to Wordfence servers (https)',
73
+ ),
74
+ // 'Configuration' => array(
75
+ // 'howGetIPs' => 'How does get IPs',
76
+ // ),
77
+ );
78
+
79
+ protected $results = array();
80
+
81
+ public function __construct()
82
+ {
83
+ foreach ($this->description as $title => $tests) {
84
+ $this->results[$title] = array();
85
+ foreach ($tests as $name => $description) {
86
+ $result = $this->$name();
87
+
88
+ if (is_bool($result)) {
89
+ $result = array(
90
+ 'test' => $result,
91
+ 'message' => $result ? 'OK' : 'FAIL',
92
+ );
93
+ }
94
+
95
+ $result['label'] = $description;
96
+
97
+ $this->results[$title][] = $result;
98
+ }
99
+ }
100
+ }
101
+
102
+ public function getResults()
103
+ {
104
+ return $this->results;
105
+ }
106
+
107
+ public function isTmpReadable() {
108
+ return is_readable(WORDFENCE_PATH . 'tmp');
109
+ }
110
+
111
+ public function isTmpWritable() {
112
+ return is_writable(WORDFENCE_PATH . 'tmp');
113
+ }
114
+
115
+ public function userCanInsert() {
116
+ return wfGrant::get()->insert;
117
+ }
118
+
119
+ public function testWfCache() {
120
+ $result = wfCache::cacheDirectoryTest();
121
+ return array(
122
+ 'test' => $result === false,
123
+ 'message' => is_string($result) ? $result : 'OK'
124
+ );
125
+ }
126
+
127
+ public function userCanDelete() {
128
+ return wfGrant::get()->delete;
129
+ }
130
+
131
+ public function userCanSelect() {
132
+ return wfGrant::get()->select;
133
+ }
134
+
135
+ public function userCanCreate() {
136
+ return wfGrant::get()->create;
137
+ }
138
+
139
+ public function userCanDrop() {
140
+ return wfGrant::get()->drop;
141
+ }
142
+
143
+ public function userCanTruncate() {
144
+ return wfGrant::get()->drop && wfGrant::get()->delete;
145
+ }
146
+
147
+ public function userCanAlter() {
148
+ return wfGrant::get()->alter;
149
+ }
150
+
151
+ public function phpVersion()
152
+ {
153
+ return array(
154
+ 'test' => version_compare(phpversion(), $this->minVersion['PHP'], '>='),
155
+ 'message' => phpversion(),
156
+ );
157
+ }
158
+
159
+ public function hasOpenSSL() {
160
+ return is_callable('openssl_open');
161
+ }
162
+
163
+ public function hasCurl() {
164
+ if (!is_callable('curl_version')) {
165
+ return false;
166
+ }
167
+ $version = curl_version();
168
+ return array(
169
+ 'test' => version_compare($version['version'], $this->minVersion['cURL'], '>='),
170
+ 'message' => $version['version'],
171
+ );
172
+ }
173
+
174
+ public function connectToServer1() {
175
+ return $this->_connectToServer('http');
176
+ }
177
+
178
+ public function connectToServer2() {
179
+ return $this->_connectToServer('https');
180
+ }
181
+
182
+ public function _connectToServer($protocol) {
183
+ $cronURL = admin_url('admin-ajax.php');
184
+ $cronURL = preg_replace('/^(https?:\/\/)/i', '://noc1.wordfence.com/scanptest/', $cronURL);
185
+ $cronURL .= '?action=wordfence_doScan&isFork=0&cronKey=47e9d1fa6a675b5999999333';
186
+ $cronURL = $protocol . $cronURL;
187
+ $result = wp_remote_post($cronURL, array(
188
+ 'timeout' => 10, //Must be less than max execution time or more than 2 HTTP children will be occupied by scan
189
+ 'blocking' => true, //Non-blocking seems to block anyway, so we use blocking
190
+ // This causes cURL to throw errors in some versions since WordPress uses its own certificate bundle ('CA certificate set, but certificate verification is disabled')
191
+ // 'sslverify' => false,
192
+ 'headers' => array()
193
+ ));
194
+ if( (! is_wp_error($result)) && $result['response']['code'] == 200 && strpos($result['body'], "scanptestok") !== false){
195
+ return true;
196
+ }
197
+
198
+ ob_start();
199
+ if(is_wp_error($result)){
200
+ echo "wp_remote_post() test to noc1.wordfence.com failed! Response was: " . $result->get_error_message() . "<br />\n";
201
+ } else {
202
+ echo "wp_remote_post() test to noc1.wordfence.com failed! Response was: " . $result['response']['code'] . " " . $result['response']['message'] . "<br />\n";
203
+ echo "This likely means that your hosting provider is blocking requests to noc1.wordfence.com or has set up a proxy that is not behaving itself.<br />\n";
204
+ echo "This additional info may help you diagnose the issue. The response headers we received were:<br />\n";
205
+ foreach($result['headers'] as $key => $value){
206
+ echo "$key => $value<br />\n";
207
+ }
208
+ }
209
+
210
+ return array(
211
+ 'test' => false,
212
+ 'message' => ob_get_clean()
213
+ );
214
+ }
215
+
216
+ public function howGetIPs()
217
+ {
218
+ $howGet = wfConfig::get('howGetIPs', false);
219
+ if ($howGet) {
220
+ if (empty($_SERVER[$howGet])) {
221
+ return array(
222
+ 'test' => false,
223
+ 'message' => 'We cannot read $_SERVER[' . $howGet . ']',
224
+ );
225
+ }
226
+ return array(
227
+ 'test' => true,
228
+ 'message' => $howGet,
229
+ );
230
+ }
231
+ foreach (array('HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR') as $test) {
232
+ if (!empty($_SERVER[$test])) {
233
+ return array(
234
+ 'test' => false,
235
+ 'message' => 'Should be: ' . $test
236
+ );
237
+ }
238
+ }
239
+ return array(
240
+ 'test' => true,
241
+ 'message' => 'REMOTE_ADDR',
242
+ );
243
+ }
244
+ }
245
+
lib/wfIssues.php CHANGED
@@ -178,8 +178,11 @@ class wfIssues {
178
  }
179
  }
180
  if ($issueList[$i]['type'] == 'database') {
181
- $prefix = $wpdb->get_blog_prefix($issueList[$i]['data']['site_id']);
182
- $issueList[$i]['data']['optionExists'] = $wpdb->get_var($wpdb->prepare("SELECT count(*) FROM {$prefix}options WHERE option_name = %s", $issueList[$i]['data']['option_name'])) > 0;
 
 
 
183
  }
184
  $issueList[$i]['issueIDX'] = $i;
185
  }
178
  }
179
  }
180
  if ($issueList[$i]['type'] == 'database') {
181
+ $issueList[$i]['data']['optionExists'] = false;
182
+ if (!empty($issueList[$i]['data']['site_id'])) {
183
+ $prefix = $wpdb->get_blog_prefix($issueList[$i]['data']['site_id']);
184
+ $issueList[$i]['data']['optionExists'] = $wpdb->get_var($wpdb->prepare("SELECT count(*) FROM {$prefix}options WHERE option_name = %s", $issueList[$i]['data']['option_name'])) > 0;
185
+ }
186
  }
187
  $issueList[$i]['issueIDX'] = $i;
188
  }
lib/wfLog.php CHANGED
@@ -3,12 +3,19 @@ require_once('wfDB.php');
3
  require_once('wfUtils.php');
4
  require_once('wfBrowscap.php');
5
  class wfLog {
 
6
  private $hitsTable = '';
7
  private $apiKey = '';
8
  private $wp_version = '';
9
  private $db = false;
10
  private $googlePattern = '/\.(?:googlebot\.com|google\.[a-z]{2,3}|google\.[a-z]{2}\.[a-z]{2}|1e100\.net)$/i';
11
  private static $gbSafeCache = array();
 
 
 
 
 
 
12
  public function __construct($apiKey, $wp_version){
13
  $this->apiKey = $apiKey;
14
  $this->wp_version = $wp_version;
@@ -25,6 +32,96 @@ class wfLog {
25
  $this->ipRangesTable = $wpdb->base_prefix . 'wfBlocksAdv';
26
  $this->perfTable = $wpdb->base_prefix . 'wfPerfLog';
27
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  public function logPerf($IP, $UA, $URL, $data){
29
  $IP = wfUtils::inet_pton($IP);
30
  $this->getDB()->queryWrite("insert into " . $this->perfTable . " (IP, userID, UA, URL, ctime, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, requestStart, responseStart, responseEnd, domReady, loaded) values (%s, %d, '%s', '%s', unix_timestamp(), %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)",
@@ -60,8 +157,18 @@ class wfLog {
60
  if ($action == 'loginFailValidUsername' && $userID == 0) {
61
  $action = 'loginFailInvalidUsername';
62
  }
 
 
 
 
 
 
 
 
 
63
  //Else userID stays 0 but we do log this even though the user doesn't exist.
64
- $this->getDB()->queryWrite("insert into " . $this->loginsTable . " (ctime, fail, action, username, userID, IP, UA) values (%f, %d, '%s', '%s', %s, %s, '%s')",
 
65
  sprintf('%.6f', microtime(true)),
66
  $fail,
67
  $action,
@@ -303,6 +410,12 @@ class wfLog {
303
 
304
  wfActivityReport::logBlockedIP($IP);
305
 
 
 
 
 
 
 
306
  wfCache::updateBlockedIPs('add');
307
  wfConfig::inc('totalIPsBlocked');
308
  return true;
@@ -318,6 +431,12 @@ class wfLog {
318
 
319
  wfActivityReport::logBlockedIP($IP);
320
 
 
 
 
 
 
 
321
  wfConfig::inc('totalIPsLocked');
322
  return true;
323
  }
@@ -418,29 +537,22 @@ class wfLog {
418
  }
419
  return $results;
420
  }
 
 
 
 
421
  public function logHit(){
422
- if(! wfConfig::liveTrafficEnabled()){ return; }
423
- $headers = array();
424
- foreach($_SERVER as $h=>$v){
425
- if(preg_match('/^HTTP_(.+)$/', $h, $matches) ){
426
- $headers[$matches[1]] = $v;
 
427
  }
428
  }
429
- $ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
430
- $this->getDB()->queryWrite("insert into " . $this->hitsTable . " (ctime, is404, isGoogle, IP, userID, newVisit, URL, referer, UA, jsRun) values (%f, %d, %d, %s, %s, %d, '%s', '%s', '%s', %d)",
431
- sprintf('%.6f', microtime(true)),
432
- (is_404() ? 1 : 0),
433
- (wfCrawl::isGoogleCrawler() ? 1 : 0),
434
- wfUtils::inet_pton(wfUtils::getIP()),
435
- $this->getCurrentUserID(),
436
- (wordfence::$newVisit ? 1 : 0),
437
- wfUtils::getRequestedURL(),
438
- (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''),
439
- $ua,
440
- (int) (isset($_COOKIE['wordfence_verifiedHuman']) && wp_verify_nonce($_COOKIE['wordfence_verifiedHuman'], 'wordfence_verifiedHuman' . $ua . wfUtils::getIP()))
441
- );
442
- return $this->getDB()->querySingle("select last_insert_id()");
443
  }
 
444
  public function getPerfStats($afterTime, $limit = 50){
445
  $serverTime = $this->getDB()->querySingle("select unix_timestamp()");
446
  $results = $this->getDB()->querySelect("select * from " . $this->perfTable . " where ctime > %f order by ctime desc limit %d", $afterTime, $limit);
@@ -479,7 +591,7 @@ class wfLog {
479
  return $results;
480
  }
481
  public function getHits($hitType /* 'hits' or 'logins' */, $type, $afterTime, $limit = 50, $IP = false){
482
- $serverTime = $this->getDB()->querySingle("select unix_timestamp()");
483
  $IPSQL = "";
484
  if($IP){
485
  $IPSQL = " and IP=%s ";
@@ -496,7 +608,7 @@ class wfLog {
496
  } else if($type == 'gCrawler'){
497
  $typeSQL = " and isGoogle = 1 ";
498
  } else if($type == '404'){
499
- $typeSQL = " and is404 = 1 ";
500
  } else if($type == 'human'){
501
  $typeSQL = " and jsRun = 1 ";
502
  } else if($type == 'ruser'){
@@ -505,17 +617,33 @@ class wfLog {
505
  wordfence::status(1, 'error', "Invalid log type to wfLog: $type");
506
  return false;
507
  }
508
- array_unshift($sqlArgs, "select * from " . $this->hitsTable . " where ctime > %f $IPSQL $typeSQL order by ctime desc limit %d");
 
 
509
  $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs);
510
 
511
  } else if($hitType == 'logins'){
512
- array_unshift($sqlArgs, "select * from " . $this->loginsTable . " where ctime > %f $IPSQL order by ctime desc limit %d");
 
 
513
  $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs );
514
 
515
  } else {
516
  wordfence::status(1, 'error', "getHits got invalid hitType: $hitType");
517
  return false;
518
  }
 
 
 
 
 
 
 
 
 
 
 
 
519
  $this->resolveIPs($results);
520
  $ourURL = parse_url(site_url());
521
  $ourHost = strtolower($ourURL['host']);
@@ -577,7 +705,7 @@ class wfLog {
577
  if( isset( $refURL['query'] ) ) {
578
  parse_str($refURL['query'], $queryVars);
579
  if(isset($queryVars[$q])){
580
- $res['searchTerms'] = $queryVars[$q];
581
  }
582
  }
583
  }
@@ -587,7 +715,7 @@ class wfLog {
587
  if ( isset( $referringPage ) && stristr( $referringPage['host'], 'google.' ) )
588
  {
589
  parse_str( $referringPage['query'], $queryVars );
590
- echo $queryVars['q']; // This is the search term used
591
  }
592
  }
593
  }
@@ -605,23 +733,23 @@ class wfLog {
605
  }
606
  }
607
 
608
-
609
  if($res['userID']){
610
  $ud = get_userdata($res['userID']);
611
  if($ud){
612
  $res['user'] = array(
613
  'editLink' => wfUtils::editUserLink($res['userID']),
614
- 'display_name' => $ud->display_name,
615
  'ID' => $res['userID']
616
- );
617
  $res['user']['avatar'] = get_avatar($res['userID'], 16);
618
  }
619
  } else {
620
  $res['user'] = false;
621
  }
622
  }
623
- return $results;
624
  }
 
625
  public function resolveIPs(&$results){
626
  if(sizeof($results) < 1){ return; }
627
  $IPs = array();
@@ -642,6 +770,9 @@ class wfLog {
642
  }
643
  }
644
  public function logHitOK(){
 
 
 
645
  if(is_admin()){ return false; } //Don't log admin pageviews
646
  if(isset($_SERVER['HTTP_USER_AGENT'])){
647
  if(preg_match('/WordPress\/' . $this->wp_version . '/i', $_SERVER['HTTP_USER_AGENT'])){ return false; } //Ignore requests generated by WP UA.
@@ -771,6 +902,7 @@ class wfLog {
771
  if($doBlock){
772
  $this->getDB()->queryWrite("update " . $this->ipRangesTable . " set totalBlocked = totalBlocked + 1, lastBlocked = unix_timestamp() where id=%d", $blockRec['id']);
773
  wfActivityReport::logBlockedIP($IP);
 
774
  $this->do503(3600, "Advanced blocking in effect.");
775
  }
776
  }
@@ -828,6 +960,7 @@ class wfLog {
828
  $this->redirect(wfConfig::get('cbl_redirURL'));
829
  }
830
  } else {
 
831
  $this->do503(3600, "Access from your area has been temporarily limited for security reasons");
832
  wfConfig::inc('totalCountryBlocked');
833
  }
@@ -895,6 +1028,15 @@ class wfLog {
895
  }
896
  }
897
  public function do503($secsToGo, $reason){
 
 
 
 
 
 
 
 
 
898
  wfConfig::inc('total503s');
899
  wfUtils::doNotCache();
900
  header('HTTP/1.1 503 Service Temporarily Unavailable');
@@ -1206,4 +1348,810 @@ class wfUserIPRange {
1206
  }
1207
  }
1208
 
1209
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  require_once('wfUtils.php');
4
  require_once('wfBrowscap.php');
5
  class wfLog {
6
+ public $canLogHit = true;
7
  private $hitsTable = '';
8
  private $apiKey = '';
9
  private $wp_version = '';
10
  private $db = false;
11
  private $googlePattern = '/\.(?:googlebot\.com|google\.[a-z]{2,3}|google\.[a-z]{2}\.[a-z]{2}|1e100\.net)$/i';
12
  private static $gbSafeCache = array();
13
+
14
+ /**
15
+ * @var wfRequestModel
16
+ */
17
+ private $currentRequest;
18
+
19
  public function __construct($apiKey, $wp_version){
20
  $this->apiKey = $apiKey;
21
  $this->wp_version = $wp_version;
32
  $this->ipRangesTable = $wpdb->base_prefix . 'wfBlocksAdv';
33
  $this->perfTable = $wpdb->base_prefix . 'wfPerfLog';
34
  }
35
+
36
+ public function initLogRequest() {
37
+ if ($this->currentRequest === null) {
38
+ $this->currentRequest = new wfRequestModel();
39
+
40
+ $this->currentRequest->ctime = sprintf('%.6f', microtime(true));
41
+ $this->currentRequest->statusCode = 200;
42
+ $this->currentRequest->isGoogle = (wfCrawl::isGoogleCrawler() ? 1 : 0);
43
+ $this->currentRequest->IP = wfUtils::inet_pton(wfUtils::getIP());
44
+ $this->currentRequest->userID = $this->getCurrentUserID();
45
+ $this->currentRequest->newVisit = (wordfence::$newVisit ? 1 : 0);
46
+ $this->currentRequest->URL = wfUtils::getRequestedURL();
47
+ $this->currentRequest->referer = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '');
48
+ $this->currentRequest->UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
49
+ $this->currentRequest->jsRun = 0;
50
+
51
+ if (!function_exists('wp_verify_nonce')) {
52
+ add_action('plugins_loaded', array($this, 'actionSetRequestJSEnabled'));
53
+ } else {
54
+ $this->actionSetRequestJSEnabled();
55
+ }
56
+
57
+ add_action('init', array($this, 'actionSetRequestOnInit'), 9999);
58
+
59
+ if (function_exists('register_shutdown_function')) {
60
+ register_shutdown_function(array($this, 'logHit'));
61
+ }
62
+ }
63
+ }
64
+
65
+ public function actionSetRequestJSEnabled() {
66
+ $UA = $this->currentRequest->UA;
67
+ $IP = wfUtils::getIP();
68
+ $jsRun = (int) (isset($_COOKIE['wordfence_verifiedHuman']) &&
69
+ $this->validateVerifiedHumanCookie($_COOKIE['wordfence_verifiedHuman'], $UA, $IP));
70
+ $this->currentRequest->jsRun = $jsRun;
71
+ }
72
+
73
+ /**
74
+ * CloudFlare's plugin changes $_SERVER['REMOTE_ADDR'] on init.
75
+ */
76
+ public function actionSetRequestOnInit() {
77
+ $this->currentRequest->IP = wfUtils::inet_pton(wfUtils::getIP());
78
+ $this->currentRequest->userID = $this->getCurrentUserID();
79
+ }
80
+
81
+ /**
82
+ * @param string $cookieVal
83
+ * @param string $ua
84
+ * @param string $ip
85
+ * @return string
86
+ */
87
+ public function validateVerifiedHumanCookie($cookieVal, $ua = null, $ip = null) {
88
+ if ($ua === null) {
89
+ $ua = !empty($this->currentRequest) ? $this->currentRequest->UA : '';
90
+ }
91
+ if ($ip === null) {
92
+ $ip = wfUtils::getIP();
93
+ }
94
+ if (!function_exists('hash_equals')) {
95
+ require_once ABSPATH . WPINC . '/compat.php';
96
+ }
97
+ return hash_equals($cookieVal, $this->getVerifiedHumanCookieValue($ua, $ip));
98
+ }
99
+
100
+ /**
101
+ * @param string $ua
102
+ * @param string $ip
103
+ * @return string
104
+ */
105
+ public function getVerifiedHumanCookieValue($ua = null, $ip = null) {
106
+ if ($ua === null) {
107
+ $ua = !empty($this->currentRequest) ? $this->currentRequest->UA : '';
108
+ }
109
+ if ($ip === null) {
110
+ $ip = wfUtils::getIP();
111
+ }
112
+ if (!function_exists('wp_hash')) {
113
+ require_once ABSPATH . WPINC . '/pluggable.php';
114
+ }
115
+ return wp_hash('wordfence_verifiedHuman' . $ua . $ip, 'nonce');
116
+ }
117
+
118
+ /**
119
+ * @return wfRequestModel
120
+ */
121
+ public function getCurrentRequest() {
122
+ return $this->currentRequest;
123
+ }
124
+
125
  public function logPerf($IP, $UA, $URL, $data){
126
  $IP = wfUtils::inet_pton($IP);
127
  $this->getDB()->queryWrite("insert into " . $this->perfTable . " (IP, userID, UA, URL, ctime, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, requestStart, responseStart, responseEnd, domReady, loaded) values (%s, %d, '%s', '%s', unix_timestamp(), %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)",
157
  if ($action == 'loginFailValidUsername' && $userID == 0) {
158
  $action = 'loginFailInvalidUsername';
159
  }
160
+
161
+ $hitID = 0;
162
+ if ($this->currentRequest !== null) {
163
+ $this->currentRequest->userID = $userID;
164
+ $this->currentRequest->action = $action;
165
+ $this->currentRequest->save();
166
+ $hitID = $this->currentRequest->getPrimaryKey();
167
+ }
168
+
169
  //Else userID stays 0 but we do log this even though the user doesn't exist.
170
+ $this->getDB()->queryWrite("insert into " . $this->loginsTable . " (hitID, ctime, fail, action, username, userID, IP, UA) values (%d, %f, %d, '%s', '%s', %s, %s, '%s')",
171
+ $hitID,
172
  sprintf('%.6f', microtime(true)),
173
  $fail,
174
  $action,
410
 
411
  wfActivityReport::logBlockedIP($IP);
412
 
413
+ if ($this->currentRequest !== null) {
414
+ $this->currentRequest->statusCode = 403;
415
+ $this->currentRequest->action = 'blocked:' . ($wfsn ? 'wfsn' : 'wordfence');
416
+ $this->currentRequest->actionDescription = $reason;
417
+ }
418
+
419
  wfCache::updateBlockedIPs('add');
420
  wfConfig::inc('totalIPsBlocked');
421
  return true;
431
 
432
  wfActivityReport::logBlockedIP($IP);
433
 
434
+ if ($this->currentRequest !== null) {
435
+ $this->currentRequest->statusCode = 403;
436
+ $this->currentRequest->action = 'lockedOut';
437
+ $this->currentRequest->actionDescription = $reason;
438
+ }
439
+
440
  wfConfig::inc('totalIPsLocked');
441
  return true;
442
  }
537
  }
538
  return $results;
539
  }
540
+
541
+ /**
542
+ * @return bool|int
543
+ */
544
  public function logHit(){
545
+ if (!wfConfig::liveTrafficEnabled() || !$this->logHitOK()) {
546
+ return false;
547
+ }
548
+ if ($this->currentRequest !== null) {
549
+ if ($this->currentRequest->save()) {
550
+ return $this->currentRequest->getPrimaryKey();
551
  }
552
  }
553
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
554
  }
555
+
556
  public function getPerfStats($afterTime, $limit = 50){
557
  $serverTime = $this->getDB()->querySingle("select unix_timestamp()");
558
  $results = $this->getDB()->querySelect("select * from " . $this->perfTable . " where ctime > %f order by ctime desc limit %d", $afterTime, $limit);
591
  return $results;
592
  }
593
  public function getHits($hitType /* 'hits' or 'logins' */, $type, $afterTime, $limit = 50, $IP = false){
594
+ global $wpdb;
595
  $IPSQL = "";
596
  if($IP){
597
  $IPSQL = " and IP=%s ";
608
  } else if($type == 'gCrawler'){
609
  $typeSQL = " and isGoogle = 1 ";
610
  } else if($type == '404'){
611
+ $typeSQL = " and statusCode = 404 ";
612
  } else if($type == 'human'){
613
  $typeSQL = " and jsRun = 1 ";
614
  } else if($type == 'ruser'){
617
  wordfence::status(1, 'error', "Invalid log type to wfLog: $type");
618
  return false;
619
  }
620
+ array_unshift($sqlArgs, "select h.*, u.display_name from {$this->hitsTable} h
621
+ LEFT JOIN {$wpdb->users} u on h.userID = u.ID
622
+ where ctime > %f $IPSQL $typeSQL order by ctime desc limit %d");
623
  $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs);
624
 
625
  } else if($hitType == 'logins'){
626
+ array_unshift($sqlArgs, "select l.*, u.display_name from {$this->loginsTable} l
627
+ LEFT JOIN {$wpdb->users} u on l.userID = u.ID
628
+ where ctime > %f $IPSQL order by ctime desc limit %d");
629
  $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs );
630
 
631
  } else {
632
  wordfence::status(1, 'error', "getHits got invalid hitType: $hitType");
633
  return false;
634
  }
635
+ $this->processGetHitsResults($type, $results);
636
+ return $results;
637
+ }
638
+
639
+ /**
640
+ * @param string $type
641
+ * @param array $results
642
+ * @throws Exception
643
+ */
644
+ public function processGetHitsResults($type, &$results) {
645
+ $serverTime = $this->getDB()->querySingle("select unix_timestamp()");
646
+
647
  $this->resolveIPs($results);
648
  $ourURL = parse_url(site_url());
649
  $ourHost = strtolower($ourURL['host']);
705
  if( isset( $refURL['query'] ) ) {
706
  parse_str($refURL['query'], $queryVars);
707
  if(isset($queryVars[$q])){
708
+ $res['searchTerms'] = urlencode($queryVars[$q]);
709
  }
710
  }
711
  }
715
  if ( isset( $referringPage ) && stristr( $referringPage['host'], 'google.' ) )
716
  {
717
  parse_str( $referringPage['query'], $queryVars );
718
+ // echo $queryVars['q']; // This is the search term used
719
  }
720
  }
721
  }
733
  }
734
  }
735
 
736
+
737
  if($res['userID']){
738
  $ud = get_userdata($res['userID']);
739
  if($ud){
740
  $res['user'] = array(
741
  'editLink' => wfUtils::editUserLink($res['userID']),
742
+ 'display_name' => $res['display_name'],
743
  'ID' => $res['userID']
744
+ );
745
  $res['user']['avatar'] = get_avatar($res['userID'], 16);
746
  }
747
  } else {
748
  $res['user'] = false;
749
  }
750
  }
 
751
  }
752
+
753
  public function resolveIPs(&$results){
754
  if(sizeof($results) < 1){ return; }
755
  $IPs = array();
770
  }
771
  }
772
  public function logHitOK(){
773
+ if (!$this->canLogHit) {
774
+ return false;
775
+ }
776
  if(is_admin()){ return false; } //Don't log admin pageviews
777
  if(isset($_SERVER['HTTP_USER_AGENT'])){
778
  if(preg_match('/WordPress\/' . $this->wp_version . '/i', $_SERVER['HTTP_USER_AGENT'])){ return false; } //Ignore requests generated by WP UA.
902
  if($doBlock){
903
  $this->getDB()->queryWrite("update " . $this->ipRangesTable . " set totalBlocked = totalBlocked + 1, lastBlocked = unix_timestamp() where id=%d", $blockRec['id']);
904
  wfActivityReport::logBlockedIP($IP);
905
+ $this->currentRequest->actionDescription = 'UA/Referrer/IP Range not allowed';
906
  $this->do503(3600, "Advanced blocking in effect.");
907
  }
908
  }
960
  $this->redirect(wfConfig::get('cbl_redirURL'));
961
  }
962
  } else {
963
+ $this->currentRequest->actionDescription = 'blocked access via country blocking';
964
  $this->do503(3600, "Access from your area has been temporarily limited for security reasons");
965
  wfConfig::inc('totalCountryBlocked');
966
  }
1028
  }
1029
  }
1030
  public function do503($secsToGo, $reason){
1031
+ $this->initLogRequest();
1032
+ $this->currentRequest->statusCode = 403;
1033
+ if (!$this->currentRequest->action) {
1034
+ $this->currentRequest->action = 'blocked:wordfence';
1035
+ }
1036
+ if (!$this->currentRequest->actionDescription) {
1037
+ $this->currentRequest->actionDescription = "blocked: " . $reason;
1038
+ }
1039
+
1040
  wfConfig::inc('total503s');
1041
  wfUtils::doNotCache();
1042
  header('HTTP/1.1 503 Service Temporarily Unavailable');
1348
  }
1349
  }
1350
 
1351
+ /**
1352
+ * The function of this class is to detect admin users created via direct access to the database (in other words, not
1353
+ * through WordPress).
1354
+ */
1355
+ class wfAdminUserMonitor {
1356
+
1357
+ public function isEnabled() {
1358
+ $enabled = wfConfig::get('scansEnabled_suspiciousAdminUsers');
1359
+ if ($enabled && is_multisite()) {
1360
+ if (!function_exists('wp_is_large_network')) {
1361
+ require_once ABSPATH . WPINC . '/ms-functions.php';
1362
+ }
1363
+ $enabled = !wp_is_large_network('sites') && !wp_is_large_network('users');
1364
+ }
1365
+ return $enabled;
1366
+ }
1367
+
1368
+ /**
1369
+ *
1370
+ */
1371
+ public function createInitialList() {
1372
+ $admins = $this->getCurrentAdmins();
1373
+ wfConfig::set_ser('adminUserList', $admins);
1374
+ }
1375
+
1376
+ /**
1377
+ * @param int $userID
1378
+ */
1379
+ public function grantSuperAdmin($userID = null) {
1380
+ if ($userID) {
1381
+ $this->addAdmin($userID);
1382
+ }
1383
+ }
1384
+
1385
+ /**
1386
+ * @param int $userID
1387
+ */
1388
+ public function revokeSuperAdmin($userID = null) {
1389
+ if ($userID) {
1390
+ $this->removeAdmin($userID);
1391
+ }
1392
+ }
1393
+
1394
+ /**
1395
+ * @param int $ID
1396
+ * @param mixed $role
1397
+ * @param mixed $old_roles
1398
+ */
1399
+ public function updateToUserRole($ID = null, $role = null, $old_roles = null) {
1400
+ $admins = $this->getLoggedAdmins();
1401
+ if ($role !== 'administrator' && array_key_exists($ID, $admins)) {
1402
+ $this->removeAdmin($ID);
1403
+ } else if ($role === 'administrator') {
1404
+ $this->addAdmin($ID);
1405
+ }
1406
+ }
1407
+
1408
+ /**
1409
+ * @return array|bool
1410
+ */
1411
+ public function checkNewAdmins() {
1412
+ $loggedAdmins = $this->getLoggedAdmins();
1413
+ $admins = $this->getCurrentAdmins();
1414
+ $suspiciousAdmins = array();
1415
+ foreach ($admins as $adminID => $v) {
1416
+ if (!array_key_exists($adminID, $loggedAdmins)) {
1417
+ $suspiciousAdmins[] = $adminID;
1418
+ }
1419
+ }
1420
+ return $suspiciousAdmins ? $suspiciousAdmins : false;
1421
+ }
1422
+
1423
+ /**
1424
+ * Checks if the supplied user ID is suspicious.
1425
+ *
1426
+ * @param int $userID
1427
+ * @return bool
1428
+ */
1429
+ public function isAdminUserLogged($userID) {
1430
+ $loggedAdmins = $this->getLoggedAdmins();
1431
+ return array_key_exists($userID, $loggedAdmins);
1432
+ }
1433
+
1434
+ /**
1435
+ * @return array
1436
+ */
1437
+ public function getCurrentAdmins() {
1438
+ require_once ABSPATH . WPINC . '/user.php';
1439
+ if (is_multisite()) {
1440
+ $sites = wp_get_sites(array(
1441
+ 'network_id' => null,
1442
+ ));
1443
+ } else {
1444
+ $sites = array(array(
1445
+ 'blog_id' => get_current_blog_id(),
1446
+ ));
1447
+ }
1448
+
1449
+ // not very efficient, but the WordPress API doesn't provide a good way to do this.
1450
+ $admins = array();
1451
+ foreach ($sites as $siteRow) {
1452
+ $user_query = new WP_User_Query(array(
1453
+ 'blog_id' => $siteRow['blog_id'],
1454
+ 'role' => 'administrator',
1455
+ ));
1456
+ $users = $user_query->get_results();
1457
+ if (is_array($users)) {
1458
+ /** @var WP_User $user */
1459
+ foreach ($users as $user) {
1460
+ $admins[$user->ID] = 1;
1461
+ }
1462
+ }
1463
+ }
1464
+
1465
+ // Add any super admins that aren't also admins on a network
1466
+ $superAdmins = get_super_admins();
1467
+ foreach ($superAdmins as $userLogin) {
1468
+ $user = get_user_by('login', $userLogin);
1469
+ if ($user) {
1470
+ $admins[$user->ID] = 1;
1471
+ }
1472
+ }
1473
+ return $admins;
1474
+ }
1475
+
1476
+ public function getLoggedAdmins() {
1477
+ $loggedAdmins = wfConfig::get_ser('adminUserList', false);
1478
+ if (!is_array($loggedAdmins)) {
1479
+ $this->createInitialList();
1480
+ $loggedAdmins = wfConfig::get_ser('adminUserList', false);
1481
+ }
1482
+ if (!is_array($loggedAdmins)) {
1483
+ $loggedAdmins = array();
1484
+ }
1485
+ return $loggedAdmins;
1486
+ }
1487
+
1488
+ /**
1489
+ * @param int $userID
1490
+ */
1491
+ public function addAdmin($userID) {
1492
+ $loggedAdmins = $this->getLoggedAdmins();
1493
+ if (!array_key_exists($userID, $loggedAdmins)) {
1494
+ $loggedAdmins[$userID] = 1;
1495
+ wfConfig::set_ser('adminUserList', $loggedAdmins);
1496
+ }
1497
+ }
1498
+
1499
+ /**
1500
+ * @param int $userID
1501
+ */
1502
+ public function removeAdmin($userID) {
1503
+ $loggedAdmins = $this->getLoggedAdmins();
1504
+ if (array_key_exists($userID, $loggedAdmins) && !array_key_exists($userID, $this->getCurrentAdmins())) {
1505
+ unset($loggedAdmins[$userID]);
1506
+ wfConfig::set_ser('adminUserList', $loggedAdmins);
1507
+ }
1508
+ }
1509
+ }
1510
+
1511
+ /**
1512
+ *
1513
+ */
1514
+ class wfRequestModel extends wfModel {
1515
+
1516
+ private static $actionDataEncodedParams = array(
1517
+ 'paramKey',
1518
+ 'paramValue',
1519
+ 'path',
1520
+ );
1521
+
1522
+ /**
1523
+ * @param $actionData
1524
+ * @return mixed|string|void
1525
+ */
1526
+ public static function serializeActionData($actionData) {
1527
+ if (is_array($actionData)) {
1528
+ foreach (self::$actionDataEncodedParams as $key) {
1529
+ if (array_key_exists($key, $actionData)) {
1530
+ $actionData[$key] = base64_encode($actionData[$key]);
1531
+ }
1532
+ }
1533
+ }
1534
+ return json_encode($actionData);
1535
+ }
1536
+
1537
+ /**
1538
+ * @param $actionDataJSON
1539
+ * @return mixed|string|void
1540
+ */
1541
+ public static function unserializeActionData($actionDataJSON) {
1542
+ $actionData = json_decode($actionDataJSON, true);
1543
+ if (is_array($actionData)) {
1544
+ foreach (self::$actionDataEncodedParams as $key) {
1545
+ if (array_key_exists($key, $actionData)) {
1546
+ $actionData[$key] = base64_decode($actionData[$key]);
1547
+ }
1548
+ }
1549
+ }
1550
+ return $actionData;
1551
+ }
1552
+
1553
+ private $columns = array(
1554
+ 'id',
1555
+ 'attackLogTime',
1556
+ 'ctime',
1557
+ 'IP',
1558
+ 'jsRun',
1559
+ 'statusCode',
1560
+ 'isGoogle',
1561
+ 'userID',
1562
+ 'newVisit',
1563
+ 'URL',
1564
+ 'referer',
1565
+ 'UA',
1566
+ 'action',
1567
+ 'actionDescription',
1568
+ 'actionData',
1569
+ );
1570
+
1571
+ public function getIDColumn() {
1572
+ return 'id';
1573
+ }
1574
+
1575
+ public function getTable() {
1576
+ return $this->getDB()->base_prefix . 'wfHits';
1577
+ }
1578
+
1579
+ public function hasColumn($column) {
1580
+ return in_array($column, $this->columns);
1581
+ }
1582
+ }
1583
+
1584
+
1585
+ class wfLiveTrafficQuery {
1586
+
1587
+ protected $validParams = array(
1588
+ 'id' => 'h.id',
1589
+ 'ctime' => 'h.ctime',
1590
+ 'ip' => 'h.ip',
1591
+ 'jsrun' => 'h.jsrun',
1592
+ 'statuscode' => 'h.statuscode',
1593
+ 'isgoogle' => 'h.isgoogle',
1594
+ 'userid' => 'h.userid',
1595
+ 'newvisit' => 'h.newvisit',
1596
+ 'url' => 'h.url',
1597
+ 'referer' => 'h.referer',
1598
+ 'ua' => 'h.ua',
1599
+ 'action' => 'h.action',
1600
+ 'actiondescription' => 'h.actiondescription',
1601
+ 'actiondata' => 'h.actiondata',
1602
+
1603
+ // wfLogins
1604
+ 'user_login' => 'u.user_login',
1605
+ 'username' => 'l.username',
1606
+ );
1607
+
1608
+ /** @var wfLiveTrafficQueryFilterCollection */
1609
+ private $filters = array();
1610
+
1611
+ /** @var wfLiveTrafficQueryGroupBy */
1612
+ private $groupBy;
1613
+ /**
1614
+ * @var float|null
1615
+ */
1616
+ private $startDate;
1617
+ /**
1618
+ * @var float|null
1619
+ */
1620
+ private $endDate;
1621
+ /**
1622
+ * @var int
1623
+ */
1624
+ private $limit;
1625
+ /**
1626
+ * @var int
1627
+ */
1628
+ private $offset;
1629
+
1630
+ private $tableName;
1631
+
1632
+ /** @var wfLog */
1633
+ private $wfLog;
1634
+
1635
+ /**
1636
+ * wfLiveTrafficQuery constructor.
1637
+ *
1638
+ * @param wfLog $wfLog
1639
+ * @param wfLiveTrafficQueryFilterCollection $filters
1640
+ * @param wfLiveTrafficQueryGroupBy $groupBy
1641
+ * @param float $startDate
1642
+ * @param float $endDate
1643
+ * @param int $limit
1644
+ * @param int $offset
1645
+ */
1646
+ public function __construct($wfLog, $filters = null, $groupBy = null, $startDate = null, $endDate = null, $limit = 20, $offset = 0) {
1647
+ $this->wfLog = $wfLog;
1648
+ $this->filters = $filters;
1649
+ $this->groupBy = $groupBy;
1650
+ $this->startDate = $startDate;
1651
+ $this->endDate = $endDate;
1652
+ $this->limit = $limit;
1653
+ $this->offset = $offset;
1654
+ }
1655
+
1656
+ /**
1657
+ * @return array|null|object
1658
+ */
1659
+ public function execute() {
1660
+ global $wpdb;
1661
+ $sql = $this->buildQuery();
1662
+ $results = $wpdb->get_results($sql, ARRAY_A);
1663
+ $this->getWFLog()->processGetHitsResults('', $results);
1664
+
1665
+ foreach ($results as &$row) {
1666
+ $row['actionData'] = (array) json_decode($row['actionData'], true);
1667
+ }
1668
+ return $results;
1669
+ }
1670
+
1671
+ /**
1672
+ * @return string
1673
+ * @throws wfLiveTrafficQueryException
1674
+ */
1675
+ public function buildQuery() {
1676
+ global $wpdb;
1677
+ $filters = $this->getFilters();
1678
+ $groupBy = $this->getGroupBy();
1679
+ $startDate = $this->getStartDate();
1680
+ $endDate = $this->getEndDate();
1681
+ $limit = absint($this->getLimit());
1682
+ $offset = absint($this->getOffset());
1683
+
1684
+ $wheres = array();
1685
+ if ($startDate) {
1686
+ $wheres[] = $wpdb->prepare('h.ctime > %f', $startDate);
1687
+ }
1688
+ if ($endDate) {
1689
+ $wheres[] = $wpdb->prepare('h.ctime < %f', $endDate);
1690
+ }
1691
+
1692
+ if ($filters instanceof wfLiveTrafficQueryFilterCollection) {
1693
+ $filtersSQL = $filters->toSQL();
1694
+ if ($filtersSQL) {
1695
+ $wheres[] = $filtersSQL;
1696
+ }
1697
+ }
1698
+ $where = join(' AND ', $wheres);
1699
+
1700
+ $orderBy = 'ORDER BY h.ctime DESC';
1701
+ $select = '';
1702
+ $groupBySQL = '';
1703
+ if ($groupBy && $groupBy->validate()) {
1704
+ $groupBySQL = "GROUP BY {$groupBy->getParam()}";
1705
+ $orderBy = 'ORDER BY hitCount DESC';
1706
+ $select .= ', COUNT(h.id) as hitCount';
1707
+ }
1708
+
1709
+ if ($where) {
1710
+ $where = 'WHERE ' . $where;
1711
+ }
1712
+ if (!$limit || $limit > 1000) {
1713
+ $limit = 20;
1714
+ }
1715
+ $limitSQL = $wpdb->prepare('LIMIT %d, %d', $offset, $limit);
1716
+
1717
+ $sql = <<<SQL
1718
+ SELECT h.*, u.display_name, l.username{$select} FROM {$this->getTableName()} h
1719
+ LEFT JOIN {$wpdb->users} u on h.userID = u.ID
1720
+ LEFT JOIN {$wpdb->base_prefix}wfLogins l on h.id = l.hitID
1721
+ $where
1722
+ $groupBySQL
1723
+ $orderBy
1724
+ $limitSQL
1725
+ SQL;
1726
+
1727
+ return $sql;
1728
+ }
1729
+
1730
+ /**
1731
+ * @param $param
1732
+ * @return bool
1733
+ */
1734
+ public function isValidParam($param) {
1735
+ return array_key_exists(strtolower($param), $this->validParams);
1736
+ }
1737
+
1738
+ /**
1739
+ * @param $getParam
1740
+ * @return bool|string
1741
+ */
1742
+ public function getColumnFromParam($getParam) {
1743
+ $getParam = strtolower($getParam);
1744
+ if (array_key_exists($getParam, $this->validParams)) {
1745
+ return $this->validParams[$getParam];
1746
+ }
1747
+ return false;
1748
+ }
1749
+
1750
+ /**
1751
+ * @return wfLiveTrafficQueryFilterCollection
1752
+ */
1753
+ public function getFilters() {
1754
+ return $this->filters;
1755
+ }
1756
+
1757
+ /**
1758
+ * @param wfLiveTrafficQueryFilterCollection $filters
1759
+ */
1760
+ public function setFilters($filters) {
1761
+ $this->filters = $filters;
1762
+ }
1763
+
1764
+ /**
1765
+ * @return float|null
1766
+ */
1767
+ public function getStartDate() {
1768
+ return $this->startDate;
1769
+ }
1770
+
1771
+ /**
1772
+ * @param float|null $startDate
1773
+ */
1774
+ public function setStartDate($startDate) {
1775
+ $this->startDate = $startDate;
1776
+ }
1777
+
1778
+ /**
1779
+ * @return float|null
1780
+ */
1781
+ public function getEndDate() {
1782
+ return $this->endDate;
1783
+ }
1784
+
1785
+ /**
1786
+ * @param float|null $endDate
1787
+ */
1788
+ public function setEndDate($endDate) {
1789
+ $this->endDate = $endDate;
1790
+ }
1791
+
1792
+ /**
1793
+ * @return wfLiveTrafficQueryGroupBy
1794
+ */
1795
+ public function getGroupBy() {
1796
+ return $this->groupBy;
1797
+ }
1798
+
1799
+ /**
1800
+ * @param wfLiveTrafficQueryGroupBy $groupBy
1801
+ */
1802
+ public function setGroupBy($groupBy) {
1803
+ $this->groupBy = $groupBy;
1804
+ }
1805
+
1806
+ /**
1807
+ * @return int
1808
+ */
1809
+ public function getLimit() {
1810
+ return $this->limit;
1811
+ }
1812
+
1813
+ /**
1814
+ * @param int $limit
1815
+ */
1816
+ public function setLimit($limit) {
1817
+ $this->limit = $limit;
1818
+ }
1819
+
1820
+ /**
1821
+ * @return int
1822
+ */
1823
+ public function getOffset() {
1824
+ return $this->offset;
1825
+ }
1826
+
1827
+ /**
1828
+ * @param int $offset
1829
+ */
1830
+ public function setOffset($offset) {
1831
+ $this->offset = $offset;
1832
+ }
1833
+
1834
+ /**
1835
+ * @return string
1836
+ */
1837
+ public function getTableName() {
1838
+ if ($this->tableName === null) {
1839
+ global $wpdb;
1840
+ $this->tableName = $wpdb->base_prefix . 'wfHits';
1841
+ }
1842
+ return $this->tableName;
1843
+ }
1844
+
1845
+ /**
1846
+ * @param string $tableName
1847
+ */
1848
+ public function setTableName($tableName) {
1849
+ $this->tableName = $tableName;
1850
+ }
1851
+
1852
+ /**
1853
+ * @return wfLog
1854
+ */
1855
+ public function getWFLog() {
1856
+ return $this->wfLog;
1857
+ }
1858
+
1859
+ /**
1860
+ * @param wfLog $wfLog
1861
+ */
1862
+ public function setWFLog($wfLog) {
1863
+ $this->wfLog = $wfLog;
1864
+ }
1865
+ }
1866
+
1867
+ class wfLiveTrafficQueryFilterCollection {
1868
+
1869
+ private $filters = array();
1870
+
1871
+ /**
1872
+ * wfLiveTrafficQueryFilterCollection constructor.
1873
+ *
1874
+ * @param array $filters
1875
+ */
1876
+ public function __construct($filters = array()) {
1877
+ $this->filters = $filters;
1878
+ }
1879
+
1880
+ public function toSQL() {
1881
+ $params = array();
1882
+ $sql = '';
1883
+ $filters = $this->getFilters();
1884
+ if ($filters) {
1885
+ /** @var wfLiveTrafficQueryFilter $filter */
1886
+ foreach ($filters as $filter) {
1887
+ $params[$filter->getParam()][] = $filter;
1888
+ }
1889
+ }
1890
+
1891
+ foreach ($params as $param => $filters) {
1892
+ // $sql .= '(';
1893
+ $filtersSQL = '';
1894
+ foreach ($filters as $filter) {
1895
+ $filterSQL = $filter->toSQL();
1896
+ if ($filterSQL) {
1897
+ $filtersSQL .= $filterSQL . ' OR ';
1898
+ }
1899
+ }
1900
+ if ($filtersSQL) {
1901
+ $sql .= '(' . substr($filtersSQL, 0, -4) . ') AND ';
1902
+ }
1903
+ }
1904
+ if ($sql) {
1905
+ $sql = substr($sql, 0, -5);
1906
+ }
1907
+ return $sql;
1908
+ }
1909
+
1910
+ public function addFilter($filter) {
1911
+ $this->filters[] = $filter;
1912
+ }
1913
+
1914
+ /**
1915
+ * @return array
1916
+ */
1917
+ public function getFilters() {
1918
+ return $this->filters;
1919
+ }
1920
+
1921
+ /**
1922
+ * @param array $filters
1923
+ */
1924
+ public function setFilters($filters) {
1925
+ $this->filters = $filters;
1926
+ }
1927
+ }
1928
+
1929
+ class wfLiveTrafficQueryFilter {
1930
+
1931
+ private $param;
1932
+ private $operator;
1933
+ private $value;
1934
+
1935
+ protected $validOperators = array(
1936
+ '=',
1937
+ '!=',
1938
+ 'contains',
1939
+ 'match',
1940
+ );
1941
+
1942
+ /**
1943
+ * @var wfLiveTrafficQuery
1944
+ */
1945
+ private $query;
1946
+
1947
+ /**
1948
+ * wfLiveTrafficQueryFilter constructor.
1949
+ *
1950
+ * @param wfLiveTrafficQuery $query
1951
+ * @param string $param
1952
+ * @param string $operator
1953
+ * @param string $value
1954
+ */
1955
+ public function __construct($query, $param, $operator, $value) {
1956
+ $this->query = $query;
1957
+ $this->param = $param;
1958
+ $this->operator = $operator;
1959
+ $this->value = $value;
1960
+ }
1961
+
1962
+ /**
1963
+ * @return string|void
1964
+ */
1965
+ public function toSQL() {
1966
+ $sql = '';
1967
+ if ($this->validate()) {
1968
+ /** @var wpdb $wpdb */
1969
+ global $wpdb;
1970
+ $operator = $this->getOperator();
1971
+ $param = $this->getQuery()->getColumnFromParam($this->getParam());
1972
+ if (!$param) {
1973
+ return $sql;
1974
+ }
1975
+ $value = $this->getValue();
1976
+ switch ($operator) {
1977
+ case 'contains':
1978
+ $like = addcslashes($value, '_%\\');
1979
+ $sql = $wpdb->prepare("$param LIKE %s", "%$like%");
1980
+ break;
1981
+
1982
+ case 'match':
1983
+ $sql = $wpdb->prepare("$param LIKE %s", $value);
1984
+ break;
1985
+
1986
+ default:
1987
+ $sql = $wpdb->prepare("$param $operator %s", $value);
1988
+ break;
1989
+ }
1990
+ }
1991
+ return $sql;
1992
+ }
1993
+
1994
+ /**
1995
+ * @return bool
1996
+ */
1997
+ public function validate() {
1998
+ $valid = $this->isValidParam($this->getParam()) && $this->isValidOperator($this->getOperator());
1999
+ if (defined('WP_DEBUG') && WP_DEBUG) {
2000
+ if (!$valid) {
2001
+ throw new wfLiveTrafficQueryException("Invalid param/operator [{$this->getParam()}]/[{$this->getOperator()}] passed to " . get_class($this));
2002
+ }
2003
+ return true;
2004
+ }
2005
+ return $valid;
2006
+ }
2007
+
2008
+ /**
2009
+ * @param string $param
2010
+ * @return bool
2011
+ */
2012
+ public function isValidParam($param) {
2013
+ return $this->getQuery() && $this->getQuery()->isValidParam($param);
2014
+ }
2015
+
2016
+ /**
2017
+ * @param string $operator
2018
+ * @return bool
2019
+ */
2020
+ public function isValidOperator($operator) {
2021
+ return in_array($operator, $this->validOperators);
2022
+ }
2023
+
2024
+ /**
2025
+ * @return mixed
2026
+ */
2027
+ public function getParam() {
2028
+ return $this->param;
2029
+ }
2030
+
2031
+ /**
2032
+ * @param mixed $param
2033
+ */
2034
+ public function setParam($param) {
2035
+ $this->param = $param;
2036
+ }
2037
+
2038
+ /**
2039
+ * @return mixed
2040
+ */
2041
+ public function getOperator() {
2042
+ return $this->operator;
2043
+ }
2044
+
2045
+ /**
2046
+ * @param mixed $operator
2047
+ */
2048
+ public function setOperator($operator) {
2049
+ $this->operator = $operator;
2050
+ }
2051
+
2052
+ /**
2053
+ * @return mixed
2054
+ */
2055
+ public function getValue() {
2056
+ return $this->value;
2057
+ }
2058
+
2059
+ /**
2060
+ * @param mixed $value
2061
+ */
2062
+ public function setValue($value) {
2063
+ $this->value = $value;
2064
+ }
2065
+
2066
+ /**
2067
+ * @return wfLiveTrafficQuery
2068
+ */
2069
+ public function getQuery() {
2070
+ return $this->query;
2071
+ }
2072
+
2073
+ /**
2074
+ * @param wfLiveTrafficQuery $query
2075
+ */
2076
+ public function setQuery($query) {
2077
+ $this->query = $query;
2078
+ }
2079
+ }
2080
+
2081
+ class wfLiveTrafficQueryGroupBy {
2082
+
2083
+ private $param;
2084
+
2085
+ /**
2086
+ * @var wfLiveTrafficQuery
2087
+ */
2088
+ private $query;
2089
+
2090
+ /**
2091
+ * wfLiveTrafficQueryGroupBy constructor.
2092
+ *
2093
+ * @param wfLiveTrafficQuery $query
2094
+ * @param string $param
2095
+ */
2096
+ public function __construct($query, $param) {
2097
+ $this->query = $query;
2098
+ $this->param = $param;
2099
+ }
2100
+
2101
+ /**
2102
+ * @return bool
2103
+ * @throws wfLiveTrafficQueryException
2104
+ */
2105
+ public function validate() {
2106
+ $valid = $this->isValidParam($this->getParam());
2107
+ if (defined('WP_DEBUG') && WP_DEBUG) {
2108
+ if (!$valid) {
2109
+ throw new wfLiveTrafficQueryException("Invalid param [{$this->getParam()}] passed to " . get_class($this));
2110
+ }
2111
+ return true;
2112
+ }
2113
+ return $valid;
2114
+ }
2115
+
2116
+ /**
2117
+ * @param string $param
2118
+ * @return bool
2119
+ */
2120
+ public function isValidParam($param) {
2121
+ return $this->getQuery() && $this->getQuery()->isValidParam($param);
2122
+ }
2123
+
2124
+ /**
2125
+ * @return wfLiveTrafficQuery
2126
+ */
2127
+ public function getQuery() {
2128
+ return $this->query;
2129
+ }
2130
+
2131
+ /**
2132
+ * @param wfLiveTrafficQuery $query
2133
+ */
2134
+ public function setQuery($query) {
2135
+ $this->query = $query;
2136
+ }
2137
+
2138
+ /**
2139
+ * @return mixed
2140
+ */
2141
+ public function getParam() {
2142
+ return $this->param;
2143
+ }
2144
+
2145
+ /**
2146
+ * @param mixed $param
2147
+ */
2148
+ public function setParam($param) {
2149
+ $this->param = $param;
2150
+ }
2151
+
2152
+ }
2153
+
2154
+
2155
+ class wfLiveTrafficQueryException extends Exception {
2156
+
2157
+ }
lib/wfScanEngine.php CHANGED
@@ -37,18 +37,41 @@ class wfScanEngine {
37
  private $userPasswdQueue = "";
38
  private $passwdHasIssues = false;
39
 
40
- /**
41
- * @var array
42
- */
43
- private $databaseResults;
44
 
45
  /**
46
  * @var wordfenceDBScanner
47
  */
48
  private $dbScanner;
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  public function __sleep(){ //Same order here as above for properties that are included in serialization
51
- return array('hasher', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'maxExecTime', 'publicScanEnabled', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX', 'userPasswdQueue', 'passwdHasIssues', 'databaseResults', 'dbScanner');
52
  }
53
  public function __construct(){
54
  $this->startTime = time();
@@ -67,7 +90,9 @@ class wfScanEngine {
67
  $this->jobList[] = 'knownFiles_init';
68
  $this->jobList[] = 'knownFiles_main';
69
  $this->jobList[] = 'knownFiles_finish';
70
- foreach (array('knownFiles', 'fileContents', 'database', 'posts', 'comments', 'passwds', 'dns', 'diskSpace', 'oldVersions') as $scanType) {
 
 
71
  if (wfConfig::get('scansEnabled_' . $scanType)) {
72
  if (method_exists($this, 'scan_' . $scanType . '_init')) {
73
  foreach (array('init', 'main', 'finish') as $op) {
@@ -216,6 +241,127 @@ class wfScanEngine {
216
  sleep(2);
217
  }
218
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  private function scan_checkSpamvertized(){
220
  if(wfConfig::get('isPaid')){
221
  if(wfConfig::get('spamvertizeCheck')){
@@ -263,46 +409,13 @@ class wfScanEngine {
263
  }
264
  }
265
 
266
- if(! function_exists( 'get_plugins')){
267
- require_once ABSPATH . '/wp-admin/includes/plugin.php';
268
- }
269
  $this->status(2, 'info', "Getting plugin list from WordPress");
270
- $pluginData = get_plugins();
271
- $knownFilesPlugins = array();
272
- foreach($pluginData as $key => $data){
273
- if(preg_match('/^([^\/]+)\//', $key, $matches)){
274
- $pluginDir = $matches[1];
275
- $pluginFullDir = "wp-content/plugins/" . $pluginDir;
276
- $knownFilesPlugins[$key] = array(
277
- 'Name' => $data['Name'],
278
- 'Version' => $data['Version'],
279
- 'ShortDir' => $pluginDir,
280
- 'FullDir' => $pluginFullDir
281
- );
282
- }
283
- }
284
-
285
  $this->status(2, 'info', "Found " . sizeof($knownFilesPlugins) . " plugins");
286
  $this->i->updateSummaryItem('totalPlugins', sizeof($knownFilesPlugins));
287
 
288
- if (!function_exists('wp_get_themes')) {
289
- require_once ABSPATH . '/wp-includes/theme.php';
290
- }
291
  $this->status(2, 'info', "Getting theme list from WordPress");
292
- $themes = wp_get_themes();
293
- foreach ($themes as $themeName => $themeVal) {
294
- if (preg_match('/\/([^\/]+)$/', $themeVal['Stylesheet Dir'], $matches)) {
295
- $shortDir = $matches[1]; //e.g. evo4cms
296
- $fullDir = substr($themeVal['Stylesheet Dir'], strlen(ABSPATH)); //e.g. wp-content/themes/evo4cms
297
- $knownFilesThemes[$themeName] = array(
298
- 'Name' => $themeVal['Name'],
299
- 'Version' => $themeVal['Version'],
300
- 'ShortDir' => $shortDir,
301
- 'FullDir' => $fullDir
302
- );
303
- }
304
- }
305
-
306
  $this->status(2, 'info', "Found " . sizeof($knownFilesThemes) . " themes");
307
  $this->i->updateSummaryItem('totalThemes', sizeof($knownFilesThemes));
308
 
@@ -351,52 +464,7 @@ class wfScanEngine {
351
  wordfence::statusEnd($this->statusIDX['GSB'], $haveIssuesGSB);
352
  }
353
 
354
- private function scan_database_init() {
355
- $this->statusIDX['db_infect'] = wordfence::statusStart('Scanning database for infections and vulnerabilities');
356
- $this->dbScanner = new wordfenceDBScanner($this->apiKey, $this->wp_version, ABSPATH);
357
- $this->status(2, 'info', "Starting scan of database");
358
- }
359
 
360
- private function scan_database_main() {
361
- if (!$this->dbScanner) {
362
- $this->dbScanner = new wordfenceDBScanner($this->apiKey, $this->wp_version, ABSPATH);
363
- }
364
- $this->databaseResults = $this->dbScanner->scan($this);
365
- }
366
-
367
- private function scan_database_finish() {
368
- $this->status(2, 'info', "Done database scan");
369
- if ($this->dbScanner->errorMsg) {
370
- throw new Exception($this->dbScanner->errorMsg);
371
- }
372
- $this->dbScanner = null;
373
- $haveIssues = false;
374
- foreach ($this->databaseResults as $issue) {
375
- $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
376
- $issue_success = $this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data']);
377
- if ($issue_success) {
378
- $haveIssues = true;
379
- }
380
- }
381
- $this->databaseResults = null;
382
-
383
- $blogsToScan = self::getBlogsToScan('options');
384
- $wfdb = new wfDB();
385
- foreach ($blogsToScan as $blog) {
386
- $charset = $wfdb->querySingle("SELECT option_value FROM " . $blog['table'] . " WHERE option_name='blog_charset'");
387
- if (strtolower($charset) == 'utf-7') {
388
- $this->addIssue('database', 1, $blog['blog_id'] . 'blog_charset', $blog['blog_id'] . 'blog_charset', "An option was found in your site that indicates it may have been hacked.", "The 'blog_charset' option in your database is set to '" . $charset . "' which indicates your site may have been hacked. If hackers can gain access to your database via phpMyAdmin for example, they will change this value in order to inject malicious code into other parts of your site or allow XSS attacks. The 'badi' hack does this.", array(
389
- 'isMultisite' => $blog['isMultisite'],
390
- 'domain' => $blog['domain'],
391
- 'path' => $blog['path'],
392
- 'blog_id' => $blog['blog_id']
393
- ));
394
- $haveIssues = true;
395
- }
396
- }
397
-
398
- wordfence::statusEnd($this->statusIDX['db_infect'], $haveIssues);
399
- }
400
  private function scan_posts_init(){
401
  $this->statusIDX['posts'] = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
402
  $blogsToScan = self::getBlogsToScan('posts');
@@ -950,6 +1018,32 @@ class wfScanEngine {
950
 
951
  wordfence::statusEnd($this->statusIDX['oldVersions'], $haveIssues);
952
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
  public function status($level, $type, $msg){
954
  wordfence::status($level, $type, $msg);
955
  }
@@ -1039,6 +1133,349 @@ class wfScanEngine {
1039
  wordfence::status(4, 'info', "getMaxExecutionTime() returning default of: 15");
1040
  return 15;
1041
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1042
  }
1043
 
1044
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  private $userPasswdQueue = "";
38
  private $passwdHasIssues = false;
39
 
 
 
 
 
40
 
41
  /**
42
  * @var wordfenceDBScanner
43
  */
44
  private $dbScanner;
45
 
46
+ /**
47
+ * @var wfScanKnownFilesLoader
48
+ */
49
+ private $knownFilesLoader;
50
+
51
+ public static function testForFullPathDisclosure($url = null, $filePath = null) {
52
+ if ($url === null && $filePath === null) {
53
+ $url = includes_url('rss-functions.php');
54
+ $filePath = ABSPATH . WPINC . '/rss-functions.php';
55
+ }
56
+
57
+ $response = wp_remote_get($url);
58
+ $html = wp_remote_retrieve_body($response);
59
+ return preg_match("/" . preg_quote(realpath($filePath), "/") . "/i", $html);
60
+ }
61
+
62
+ public static function isDirectoryListingEnabled($url = null) {
63
+ if ($url === null) {
64
+ $uploadPaths = wp_upload_dir();
65
+ $url = $uploadPaths['baseurl'];
66
+ }
67
+
68
+ $response = wp_remote_get($url);
69
+ return !is_wp_error($response) && ($responseBody = wp_remote_retrieve_body($response)) &&
70
+ stripos($responseBody, '<title>Index of') !== false;
71
+ }
72
+
73
  public function __sleep(){ //Same order here as above for properties that are included in serialization
74
+ return array('hasher', 'jobList', 'i', 'wp_version', 'apiKey', 'startTime', 'maxExecTime', 'publicScanEnabled', 'fileContentsResults', 'scanner', 'scanQueue', 'hoover', 'scanData', 'statusIDX', 'userPasswdQueue', 'passwdHasIssues', 'dbScanner');
75
  }
76
  public function __construct(){
77
  $this->startTime = time();
90
  $this->jobList[] = 'knownFiles_init';
91
  $this->jobList[] = 'knownFiles_main';
92
  $this->jobList[] = 'knownFiles_finish';
93
+ foreach (array('knownFiles', 'checkReadableConfig', 'fileContents',
94
+ // 'wpscan_fullPathDisclosure', 'wpscan_directoryListingEnabled',
95
+ 'posts', 'comments', 'passwds', 'dns', 'diskSpace', 'oldVersions', 'suspiciousAdminUsers') as $scanType) {
96
  if (wfConfig::get('scansEnabled_' . $scanType)) {
97
  if (method_exists($this, 'scan_' . $scanType . '_init')) {
98
  foreach (array('init', 'main', 'finish') as $op) {
241
  sleep(2);
242
  }
243
  }
244
+
245
+ private function scan_checkReadableConfig() {
246
+ $haveIssues = false;
247
+ $status = wordfence::statusStart("Check for publicly accessible configuration files, backup files and logs");
248
+
249
+ $backupFileTests = array(
250
+ wfCommonBackupFileTest::createFromRootPath('.user.ini'),
251
+ wfCommonBackupFileTest::createFromRootPath('.htaccess'),
252
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php.bak'),
253
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php.swo'),
254
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php.save'),
255
+ new wfCommonBackupFileTest(home_url('%23wp-config.php%23'), ABSPATH . '#wp-config.php#'),
256
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php~'),
257
+ wfCommonBackupFileTest::createFromRootPath('wp-config.old'),
258
+ wfCommonBackupFileTest::createFromRootPath('.wp-config.php.swp'),
259
+ wfCommonBackupFileTest::createFromRootPath('wp-config.bak'),
260
+ wfCommonBackupFileTest::createFromRootPath('wp-config.save'),
261
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php_bak'),
262
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php.swp'),
263
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php.old'),
264
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php.original'),
265
+ wfCommonBackupFileTest::createFromRootPath('wp-config.php.orig'),
266
+ wfCommonBackupFileTest::createFromRootPath('wp-config.txt'),
267
+ wfCommonBackupFileTest::createFromRootPath('wp-config.original'),
268
+ wfCommonBackupFileTest::createFromRootPath('wp-config.orig'),
269
+ wfCommonBackupFileTest::createFromRootPath('searchreplacedb2.php'),
270
+ new wfCommonBackupFileTest(content_url('/debug.log'), WP_CONTENT_DIR . '/debug.log', array(
271
+ 'headers' => array(
272
+ 'Range' => 'bytes=0-700',
273
+ ),
274
+ )),
275
+ );
276
+ $userIniFilename = ini_get('user_ini.filename');
277
+ if ($userIniFilename && $userIniFilename !== '.user.ini') {
278
+ $backupFileTests[] = wfCommonBackupFileTest::createFromRootPath($userIniFilename);
279
+ }
280
+
281
+
282
+ /** @var wfCommonBackupFileTest $test */
283
+ foreach ($backupFileTests as $test) {
284
+ $pathFromRoot = (strpos($test->getPath(), ABSPATH) === 0) ? substr($test->getPath(), strlen(ABSPATH)) : $test->getPath();
285
+ if ($test->fileExists() && $test->isPubliclyAccessible()) {
286
+ $key = "configReadable" . bin2hex($test->getUrl());
287
+ if ($this->addIssue(
288
+ 'configReadable',
289
+ 2,
290
+ $key,
291
+ $key,
292
+ 'Publicly accessible config, backup, or log file found: ' . esc_html($pathFromRoot),
293
+ '<a href="' . $test->getUrl() . '" target="_blank">' . $test->getUrl() . '</a> is publicly
294
+ accessible and may expose sensitive information about your site. Files such as this one are commonly
295
+ checked for by scanners such as WPScan and should be removed or made inaccessible.',
296
+ array(
297
+ 'url' => $test->getUrl(),
298
+ 'file' => $pathFromRoot,
299
+ 'canDelete' => true,
300
+ )
301
+ )) {
302
+ $haveIssues = true;
303
+ }
304
+ }
305
+ }
306
+
307
+ wordfence::statusEnd($status, $haveIssues);
308
+ }
309
+
310
+ private function scan_wpscan_fullPathDisclosure() {
311
+ $file = realpath(ABSPATH . WPINC . "/rss-functions.php");
312
+ if (!$file) {
313
+ return;
314
+ }
315
+
316
+ $haveIssues = false;
317
+ $status = wordfence::statusStart("Checking if your server discloses the path to the document root");
318
+ $testPage = includes_url() . basename($file);
319
+
320
+ if (self::testForFullPathDisclosure($testPage, $file)) {
321
+ $key = 'wpscan_fullPathDisclosure' . $testPage;
322
+ if ($this->addIssue(
323
+ 'wpscan_fullPathDisclosure',
324
+ 2,
325
+ $key,
326
+ $key,
327
+ 'Web server exposes the document root',
328
+ 'Full Path Disclosure (FPD) vulnerabilities enable the attacker to see the path to the webroot/file. e.g.:
329
+ /home/user/htdocs/file/. Certain vulnerabilities, such as using the load_file() (within a SQL Injection)
330
+ query to view the page source, require the attacker to have the full path to the file they wish to view.',
331
+ array('url' => $testPage)
332
+ )) {
333
+ $haveIssues = true;
334
+ }
335
+ }
336
+
337
+ wordfence::statusEnd($status, $haveIssues);
338
+ }
339
+
340
+ private function scan_wpscan_directoryListingEnabled() {
341
+ $this->statusIDX['wpscan_directoryListingEnabled'] = wordfence::statusStart("Checking to see if directory listing is enabled");
342
+
343
+ $uploadPaths = wp_upload_dir();
344
+ $enabled = self::isDirectoryListingEnabled($uploadPaths['baseurl']);
345
+
346
+ $haveIssues = false;
347
+ if ($enabled) {
348
+ if ($this->addIssue(
349
+ 'wpscan_directoryListingEnabled',
350
+ 2,
351
+ 'wpscan_directoryListingEnabled',
352
+ 'wpscan_directoryListingEnabled',
353
+ "Directory listing is enabled",
354
+ "Directory listing provides an attacker with the complete index of all the resources located inside of the directory. The specific risks and consequences vary depending on which files are listed and accessible, but it is recommended that you disable it unless it is needed.",
355
+ array(
356
+ 'url' => $uploadPaths['baseurl'],
357
+ )
358
+ )) {
359
+ $haveIssues = true;
360
+ }
361
+ }
362
+ wordfence::statusEnd($this->statusIDX['wpscan_directoryListingEnabled'], $haveIssues);
363
+ }
364
+
365
  private function scan_checkSpamvertized(){
366
  if(wfConfig::get('isPaid')){
367
  if(wfConfig::get('spamvertizeCheck')){
409
  }
410
  }
411
 
 
 
 
412
  $this->status(2, 'info', "Getting plugin list from WordPress");
413
+ $knownFilesPlugins = $this->getPlugins();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  $this->status(2, 'info', "Found " . sizeof($knownFilesPlugins) . " plugins");
415
  $this->i->updateSummaryItem('totalPlugins', sizeof($knownFilesPlugins));
416
 
 
 
 
417
  $this->status(2, 'info', "Getting theme list from WordPress");
418
+ $knownFilesThemes = $this->getThemes();
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  $this->status(2, 'info', "Found " . sizeof($knownFilesThemes) . " themes");
420
  $this->i->updateSummaryItem('totalThemes', sizeof($knownFilesThemes));
421
 
464
  wordfence::statusEnd($this->statusIDX['GSB'], $haveIssuesGSB);
465
  }
466
 
 
 
 
 
 
467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  private function scan_posts_init(){
469
  $this->statusIDX['posts'] = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
470
  $blogsToScan = self::getBlogsToScan('posts');
1018
 
1019
  wordfence::statusEnd($this->statusIDX['oldVersions'], $haveIssues);
1020
  }
1021
+
1022
+ public function scan_suspiciousAdminUsers() {
1023
+ $this->statusIDX['suspiciousAdminUsers'] = wordfence::statusStart("Scanning for admin users not created through WordPress");
1024
+ $haveIssues = false;
1025
+
1026
+ $adminUsers = new wfAdminUserMonitor();
1027
+ if ($adminUsers->isEnabled() && $suspiciousAdmins = $adminUsers->checkNewAdmins()) {
1028
+ foreach ($suspiciousAdmins as $userID) {
1029
+ $user = new WP_User($userID);
1030
+ $key = 'suspiciousAdminUsers' . $userID;
1031
+ if ($this->addIssue('suspiciousAdminUsers', 1, $key, $key,
1032
+ "An admin user with the username " . esc_html($user->user_login) . " was created outside of WordPress.",
1033
+ "An admin user with the username " . esc_html($user->user_login) . " was created outside of WordPress. It's
1034
+ possible a plugin could have created the account, but if you do not recognize the user, we suggest you remove
1035
+ it.",
1036
+ array(
1037
+ 'userID' => $userID,
1038
+ ))) {
1039
+ $haveIssues = true;
1040
+ }
1041
+ }
1042
+ }
1043
+
1044
+ wordfence::statusEnd($this->statusIDX['suspiciousAdminUsers'], $haveIssues);
1045
+ }
1046
+
1047
  public function status($level, $type, $msg){
1048
  wordfence::status($level, $type, $msg);
1049
  }
1133
  wordfence::status(4, 'info', "getMaxExecutionTime() returning default of: 15");
1134
  return 15;
1135
  }
1136
+
1137
+ /**
1138
+ * @return wfScanKnownFilesLoader
1139
+ */
1140
+ public function getKnownFilesLoader() {
1141
+ if ($this->knownFilesLoader === null) {
1142
+ $this->knownFilesLoader = new wfScanKnownFilesLoader($this->api, $this->getPlugins(), $this->getThemes());
1143
+ }
1144
+ return $this->knownFilesLoader;
1145
+ }
1146
+
1147
+ /**
1148
+ * @return array
1149
+ */
1150
+ public function getPlugins() {
1151
+ if(! function_exists( 'get_plugins')){
1152
+ require_once ABSPATH . '/wp-admin/includes/plugin.php';
1153
+ }
1154
+ $pluginData = get_plugins();
1155
+ $plugins = array();
1156
+ foreach ($pluginData as $key => $data) {
1157
+ if (preg_match('/^([^\/]+)\//', $key, $matches)) {
1158
+ $pluginDir = $matches[1];
1159
+ $pluginFullDir = "wp-content/plugins/" . $pluginDir;
1160
+ $plugins[$key] = array(
1161
+ 'Name' => $data['Name'],
1162
+ 'Version' => $data['Version'],
1163
+ 'ShortDir' => $pluginDir,
1164
+ 'FullDir' => $pluginFullDir
1165
+ );
1166
+ }
1167
+ }
1168
+ return $plugins;
1169
+ }
1170
+
1171
+ /**
1172
+ * @return array
1173
+ */
1174
+ public function getThemes() {
1175
+ if (!function_exists('wp_get_themes')) {
1176
+ require_once ABSPATH . '/wp-includes/theme.php';
1177
+ }
1178
+ $themeData = wp_get_themes();
1179
+ $themes = array();
1180
+ foreach ($themeData as $themeName => $themeVal) {
1181
+ if (preg_match('/\/([^\/]+)$/', $themeVal['Stylesheet Dir'], $matches)) {
1182
+ $shortDir = $matches[1]; //e.g. evo4cms
1183
+ $fullDir = substr($themeVal['Stylesheet Dir'], strlen(ABSPATH)); //e.g. wp-content/themes/evo4cms
1184
+ $themes[$themeName] = array(
1185
+ 'Name' => $themeVal['Name'],
1186
+ 'Version' => $themeVal['Version'],
1187
+ 'ShortDir' => $shortDir,
1188
+ 'FullDir' => $fullDir
1189
+ );
1190
+ }
1191
+ }
1192
+ return $themes;
1193
+ }
1194
+ }
1195
+
1196
+ class wfScanKnownFilesLoader {
1197
+ /**
1198
+ * @var array
1199
+ */
1200
+ private $plugins;
1201
+
1202
+ /**
1203
+ * @var array
1204
+ */
1205
+ private $themes;
1206
+
1207
+ /**
1208
+ * @var array
1209
+ */
1210
+ private $knownFiles = array();
1211
+
1212
+ /**
1213
+ * @var wfAPI
1214
+ */
1215
+ private $api;
1216
+
1217
+
1218
+ /**
1219
+ * @param wfAPI $api
1220
+ * @param array $plugins
1221
+ * @param array $themes
1222
+ */
1223
+ public function __construct($api, $plugins = null, $themes = null) {
1224
+ $this->api = $api;
1225
+ $this->plugins = $plugins;
1226
+ $this->themes = $themes;
1227
+ }
1228
+
1229
+ /**
1230
+ * @return bool
1231
+ */
1232
+ public function isLoaded() {
1233
+ return is_array($this->knownFiles) && count($this->knownFiles) > 0;
1234
+ }
1235
+
1236
+ /**
1237
+ * @param $file
1238
+ * @return bool
1239
+ * @throws wfScanKnownFilesException
1240
+ */
1241
+ public function isKnownFile($file) {
1242
+ if (!$this->isLoaded()) {
1243
+ $this->fetchKnownFiles();
1244
+ }
1245
+
1246
+ return isset($this->knownFiles['core'][$file]) ||
1247
+ isset($this->knownFiles['plugins'][$file]) ||
1248
+ isset($this->knownFiles['themes'][$file]);
1249
+ }
1250
+
1251
+ /**
1252
+ * @param $file
1253
+ * @return bool
1254
+ * @throws wfScanKnownFilesException
1255
+ */
1256
+ public function isKnownCoreFile($file) {
1257
+ if (!$this->isLoaded()) {
1258
+ $this->fetchKnownFiles();
1259
+ }
1260
+ return isset($this->knownFiles['core'][$file]);
1261
+ }
1262
+
1263
+ /**
1264
+ * @param $file
1265
+ * @return bool
1266
+ * @throws wfScanKnownFilesException
1267
+ */
1268
+ public function isKnownPluginFile($file) {
1269
+ if (!$this->isLoaded()) {
1270
+ $this->fetchKnownFiles();
1271
+ }
1272
+ return isset($this->knownFiles['plugins'][$file]);
1273
+ }
1274
+
1275
+ /**
1276
+ * @param $file
1277
+ * @return bool
1278
+ * @throws wfScanKnownFilesException
1279
+ */
1280
+ public function isKnownThemeFile($file) {
1281
+ if (!$this->isLoaded()) {
1282
+ $this->fetchKnownFiles();
1283
+ }
1284
+ return isset($this->knownFiles['themes'][$file]);
1285
+ }
1286
+
1287
+ /**
1288
+ * @throws wfScanKnownFilesException
1289
+ */
1290
+ public function fetchKnownFiles() {
1291
+ try {
1292
+ $dataArr = $this->api->binCall('get_known_files', json_encode(array(
1293
+ 'plugins' => $this->plugins,
1294
+ 'themes' => $this->themes
1295
+ )));
1296
+
1297
+ if ($dataArr['code'] != 200) {
1298
+ throw new wfScanKnownFilesException("Got error response from Wordfence servers: " . $dataArr['code'], $dataArr['code']);
1299
+ }
1300
+ $this->knownFiles = @json_decode($dataArr['data'], true);
1301
+ if (!is_array($this->knownFiles)) {
1302
+ throw new wfScanKnownFilesException("Invalid response from Wordfence servers.");
1303
+ }
1304
+ } catch (Exception $e) {
1305
+ throw new wfScanKnownFilesException($e->getMessage(), $e->getCode(), $e);
1306
+ }
1307
+ }
1308
+
1309
+ public function getKnownPluginData($file) {
1310
+ if ($this->isKnownPluginFile($file)) {
1311
+ return $this->knownFiles['plugins'][$file];
1312
+ }
1313
+ return null;
1314
+ }
1315
+
1316
+ public function getKnownThemeData($file) {
1317
+ if ($this->isKnownThemeFile($file)) {
1318
+ return $this->knownFiles['themes'][$file];
1319
+ }
1320
+ return null;
1321
+ }
1322
+
1323
+ /**
1324
+ * @return array
1325
+ */
1326
+ public function getPlugins() {
1327
+ return $this->plugins;
1328
+ }
1329
+
1330
+ /**
1331
+ * @param array $plugins
1332
+ */
1333
+ public function setPlugins($plugins) {
1334
+ $this->plugins = $plugins;
1335
+ }
1336
+
1337
+ /**
1338
+ * @return array
1339
+ */
1340
+ public function getThemes() {
1341
+ return $this->themes;
1342
+ }
1343
+
1344
+ /**
1345
+ * @param array $themes
1346
+ */
1347
+ public function setThemes($themes) {
1348
+ $this->themes = $themes;
1349
+ }
1350
+
1351
+ /**
1352
+ * @return array
1353
+ * @throws wfScanKnownFilesException
1354
+ */
1355
+ public function getKnownFiles() {
1356
+ if (!$this->isLoaded()) {
1357
+ $this->fetchKnownFiles();
1358
+ }
1359
+ return $this->knownFiles;
1360
+ }
1361
+
1362
+ /**
1363
+ * @param array $knownFiles
1364
+ */
1365
+ public function setKnownFiles($knownFiles) {
1366
+ $this->knownFiles = $knownFiles;
1367
+ }
1368
+
1369
+ /**
1370
+ * @return wfAPI
1371
+ */
1372
+ public function getAPI() {
1373
+ return $this->api;
1374
+ }
1375
+
1376
+ /**
1377
+ * @param wfAPI $api
1378
+ */
1379
+ public function setAPI($api) {
1380
+ $this->api = $api;
1381
+ }
1382
  }
1383
 
1384
+ class wfScanKnownFilesException extends Exception {
1385
+
1386
+ }
1387
+
1388
+ class wfCommonBackupFileTest {
1389
+
1390
+ /**
1391
+ * @param string $path
1392
+ * @return wfCommonBackupFileTest
1393
+ */
1394
+ public static function createFromRootPath($path) {
1395
+ return new self(home_url($path), ABSPATH . $path);
1396
+ }
1397
+
1398
+ private $url;
1399
+ private $path;
1400
+ /**
1401
+ * @var array
1402
+ */
1403
+ private $requestArgs;
1404
+ private $response;
1405
+
1406
+
1407
+ /**
1408
+ * @param string $url
1409
+ * @param string $path
1410
+ * @param array $requestArgs
1411
+ */
1412
+ public function __construct($url, $path, $requestArgs = array()) {
1413
+ $this->url = $url;
1414
+ $this->path = $path;
1415
+ $this->requestArgs = $requestArgs;
1416
+ }
1417
+
1418
+ /**
1419
+ * @return bool
1420
+ */
1421
+ public function fileExists() {
1422
+ return file_exists($this->path);
1423
+ }
1424
+
1425
+ /**
1426
+ * @return bool
1427
+ */
1428
+ public function isPubliclyAccessible() {
1429
+ $this->response = wp_remote_get($this->url, $this->requestArgs);
1430
+ return wp_remote_retrieve_response_code($this->response) === 200;
1431
+ }
1432
+
1433
+ /**
1434
+ * @return string
1435
+ */
1436
+ public function getUrl() {
1437
+ return $this->url;
1438
+ }
1439
+
1440
+ /**
1441
+ * @param string $url
1442
+ */
1443
+ public function setUrl($url) {
1444
+ $this->url = $url;
1445
+ }
1446
+
1447
+ /**
1448
+ * @return string
1449
+ */
1450
+ public function getPath() {
1451
+ return $this->path;
1452
+ }
1453
+
1454
+ /**
1455
+ * @param string $path
1456
+ */
1457
+ public function setPath($path) {
1458
+ $this->path = $path;
1459
+ }
1460
+
1461
+ /**
1462
+ * @return array
1463
+ */
1464
+ public function getRequestArgs() {
1465
+ return $this->requestArgs;
1466
+ }
1467
+
1468
+ /**
1469
+ * @param array $requestArgs
1470
+ */
1471
+ public function setRequestArgs($requestArgs) {
1472
+ $this->requestArgs = $requestArgs;
1473
+ }
1474
+
1475
+ /**
1476
+ * @return mixed
1477
+ */
1478
+ public function getResponse() {
1479
+ return $this->response;
1480
+ }
1481
+ }
lib/wfSchema.php CHANGED
@@ -45,7 +45,7 @@ class wfSchema {
45
  ctime DOUBLE(17,6) UNSIGNED NOT NULL,
46
  IP int UNSIGNED NOT NULL,
47
  jsRun tinyint default 0,
48
- is404 tinyint NOT NULL,
49
  isGoogle tinyint NOT NULL,
50
  userID int UNSIGNED NOT NULL,
51
  newVisit tinyint UNSIGNED NOT NULL,
@@ -166,7 +166,7 @@ class wfSchema {
166
  blockCount int UNSIGNED NOT NULL DEFAULT 0,
167
  unixday int UNSIGNED NOT NULL,
168
  PRIMARY KEY(IP, unixday)
169
- ) default charset=utf8"
170
  /*
171
  'wfPerfLog' => "(
172
  id int UNSIGNED NOT NULL auto_increment PRIMARY KEY,
45
  ctime DOUBLE(17,6) UNSIGNED NOT NULL,
46
  IP int UNSIGNED NOT NULL,
47
  jsRun tinyint default 0,
48
+ statusCode int NOT NULL default 200,
49
  isGoogle tinyint NOT NULL,
50
  userID int UNSIGNED NOT NULL,
51
  newVisit tinyint UNSIGNED NOT NULL,
166
  blockCount int UNSIGNED NOT NULL DEFAULT 0,
167
  unixday int UNSIGNED NOT NULL,
168
  PRIMARY KEY(IP, unixday)
169
+ ) default charset=utf8",
170
  /*
171
  'wfPerfLog' => "(
172
  id int UNSIGNED NOT NULL auto_increment PRIMARY KEY,
lib/wfUtils.php CHANGED
@@ -155,6 +155,11 @@ class wfUtils {
155
  $first = self::inet_ntop($binary_first);
156
  $last = self::inet_ntop($binary_last);
157
 
 
 
 
 
 
158
  // Split addresses into segments
159
  $first_array = preg_split('/[\.\:]/', $first);
160
  $last_array = preg_split('/[\.\:]/', $last);
@@ -167,11 +172,12 @@ class wfUtils {
167
 
168
  foreach ($first_array as $index => $segment) {
169
  if ($segment === $last_array[$index]) {
170
- $range_segments[] = $segment;
171
  } else if ($segment === '' || $last_array[$index] === '') {
172
  $range_segments[] = '';
173
  } else {
174
- $range_segments[] = "[{$segment}-{$last_array[$index]}]";
 
175
  }
176
  }
177
 
@@ -298,23 +304,27 @@ class wfUtils {
298
 
299
  $hex = bin2hex($ip);
300
  $groups = str_split($hex, 4);
301
- $collapse = false;
302
  $done_collapse = false;
303
  foreach ($groups as $index => $group) {
304
  if ($group == '0000' && !$done_collapse) {
305
- if (!$collapse) {
306
- $groups[$index] = ':';
307
- } else {
308
  $groups[$index] = '';
 
309
  }
310
- $collapse = true;
311
- } else if ($collapse) {
 
 
 
312
  $done_collapse = true;
313
- $collapse = false;
314
  }
315
  $groups[$index] = ltrim($groups[$index], '0');
 
 
 
316
  }
317
- $ip = join(':', array_filter($groups));
318
  $ip = str_replace(':::', '::', $ip);
319
  return $ip == ':' ? '::' : $ip;
320
  }
@@ -346,7 +356,7 @@ class wfUtils {
346
  return false;
347
  }
348
  public static function getBaseURL(){
349
- return plugins_url() . '/wordfence/';
350
  }
351
  public static function getPluginBaseDir(){
352
  if(function_exists('wp_normalize_path')){ //Older WP versions don't have this func and we had many complaints before this check.
@@ -483,6 +493,73 @@ class wfUtils {
483
  return false;
484
  }
485
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  public static function extractHostname($str){
487
  if(preg_match('/https?:\/\/([a-zA-Z0-9\.\-]+)(?:\/|$)/i', $str, $matches)){
488
  return strtolower($matches[1]);
@@ -496,22 +573,41 @@ class wfUtils {
496
  //return self::makeRandomIP();
497
 
498
  // if no REMOTE_ADDR, it's probably running from the command line
499
- $connection_ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
 
 
 
 
 
 
 
 
 
500
 
501
  $howGet = wfConfig::get('howGetIPs', false);
502
  if($howGet){
503
  if($howGet == 'REMOTE_ADDR'){
504
- $IP = self::getCleanIP(array($connection_ip));
505
  } else {
506
- $IP = self::getCleanIP(array($_SERVER[$howGet], $connection_ip));
 
 
 
 
507
  }
508
  } else {
509
- $IPs = array($connection_ip);
510
- if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ $IPs[] = $_SERVER['HTTP_X_FORWARDED_FOR']; }
511
- if(isset($_SERVER['HTTP_X_REAL_IP'])){ $IPs[] = $_SERVER['HTTP_X_REAL_IP']; }
512
- $IP = self::getCleanIP($IPs);
 
 
 
 
 
 
513
  }
514
- return $IP; //Returns a valid IP or false.
515
  }
516
  public static function isValidIP($IP){
517
  return filter_var($IP, FILTER_VALIDATE_IP) !== false;
@@ -1069,6 +1165,17 @@ class wfUtils {
1069
  return $ip;
1070
  }
1071
 
 
 
 
 
 
 
 
 
 
 
 
1072
  /**
1073
  * @param string $readmePath
1074
  * @return bool
@@ -1103,6 +1210,46 @@ class wfUtils {
1103
  }
1104
  return false;
1105
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1106
  }
1107
 
1108
  // GeoIP lib uses these as well
@@ -1118,4 +1265,144 @@ if (!function_exists('inet_pton')) {
1118
  }
1119
 
1120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1121
  ?>
155
  $first = self::inet_ntop($binary_first);
156
  $last = self::inet_ntop($binary_last);
157
 
158
+ if (filter_var($network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
159
+ $first = self::expandIPv6Address($first);
160
+ $last = self::expandIPv6Address($last);
161
+ }
162
+
163
  // Split addresses into segments
164
  $first_array = preg_split('/[\.\:]/', $first);
165
  $last_array = preg_split('/[\.\:]/', $last);
172
 
173
  foreach ($first_array as $index => $segment) {
174
  if ($segment === $last_array[$index]) {
175
+ $range_segments[] = str_pad(ltrim($segment, '0'), 1, '0');
176
  } else if ($segment === '' || $last_array[$index] === '') {
177
  $range_segments[] = '';
178
  } else {
179
+ $range_segments[] = "[". str_pad(ltrim($segment, '0'), 1, '0') . "-" .
180
+ str_pad(ltrim($last_array[$index], '0'), 1, '0') . "]";
181
  }
182
  }
183
 
304
 
305
  $hex = bin2hex($ip);
306
  $groups = str_split($hex, 4);
307
+ $in_collapse = false;
308
  $done_collapse = false;
309
  foreach ($groups as $index => $group) {
310
  if ($group == '0000' && !$done_collapse) {
311
+ if ($in_collapse) {
 
 
312
  $groups[$index] = '';
313
+ continue;
314
  }
315
+ $groups[$index] = ':';
316
+ $in_collapse = true;
317
+ continue;
318
+ }
319
+ if ($in_collapse) {
320
  $done_collapse = true;
 
321
  }
322
  $groups[$index] = ltrim($groups[$index], '0');
323
+ if (strlen($groups[$index]) === 0) {
324
+ $groups[$index] = '0';
325
+ }
326
  }
327
+ $ip = join(':', array_filter($groups, 'strlen'));
328
  $ip = str_replace(':::', '::', $ip);
329
  return $ip == ':' ? '::' : $ip;
330
  }
356
  return false;
357
  }
358
  public static function getBaseURL(){
359
+ return plugins_url('', WORDFENCE_FCPATH) . '/';
360
  }
361
  public static function getPluginBaseDir(){
362
  if(function_exists('wp_normalize_path')){ //Older WP versions don't have this func and we had many complaints before this check.
493
  return false;
494
  }
495
  }
496
+
497
+ /**
498
+ * Expects an array of items. The items are either IP's or IP's separated by comma, space or tab. Or an array of IP's.
499
+ * We then examine all IP's looking for a public IP and storing private IP's in an array. If we find no public IPs we return the first private addr we found.
500
+ *
501
+ * @param array $arr
502
+ * @return bool|mixed
503
+ */
504
+ private static function getCleanIPAndServerVar($arr){
505
+ $privates = array(); //Store private addrs until end as last resort.
506
+ for($i = 0; $i < count($arr); $i++){
507
+ list($item, $var) = $arr[$i];
508
+ if(is_array($item)){
509
+ foreach($item as $j){
510
+ // try verifying the IP is valid before stripping the port off
511
+ if (!self::isValidIP($j)) {
512
+ $j = preg_replace('/:\d+$/', '', $j); //Strip off port
513
+ }
514
+ if (self::isValidIP($j)) {
515
+ if (self::isPrivateAddress($j)) {
516
+ $privates[] = array($j, $var);
517
+ } else {
518
+ return array($j, $var);
519
+ }
520
+ }
521
+ }
522
+ continue; //This was an array so we can skip to the next item
523
+ }
524
+ $skipToNext = false;
525
+ foreach(array(',', ' ', "\t") as $char){
526
+ if(strpos($item, $char) !== false){
527
+ $sp = explode($char, $item);
528
+ foreach($sp as $j){
529
+ if (!self::isValidIP($j)) {
530
+ $j = preg_replace('/:\d+$/', '', $j); //Strip off port
531
+ }
532
+ if(self::isValidIP($j)){
533
+ if(self::isPrivateAddress($j)){
534
+ $privates[] = array($j, $var);
535
+ } else {
536
+ return array($j, $var);
537
+ }
538
+ }
539
+ }
540
+ $skipToNext = true;
541
+ break;
542
+ }
543
+ }
544
+ if($skipToNext){ continue; } //Skip to next item because this one had a comma, space or tab so was delimited and we didn't find anything.
545
+
546
+ if (!self::isValidIP($item)) {
547
+ $item = preg_replace('/:\d+$/', '', $item); //Strip off port
548
+ }
549
+ if(self::isValidIP($item)){
550
+ if(self::isPrivateAddress($item)){
551
+ $privates[] = array($item, $var);
552
+ } else {
553
+ return array($item, $var);
554
+ }
555
+ }
556
+ }
557
+ if(sizeof($privates) > 0){
558
+ return $privates[0]; //Return the first private we found so that we respect the order the IP's were passed to this function.
559
+ } else {
560
+ return false;
561
+ }
562
+ }
563
  public static function extractHostname($str){
564
  if(preg_match('/https?:\/\/([a-zA-Z0-9\.\-]+)(?:\/|$)/i', $str, $matches)){
565
  return strtolower($matches[1]);
573
  //return self::makeRandomIP();
574
 
575
  // if no REMOTE_ADDR, it's probably running from the command line
576
+ $ip = self::getIPAndServerVarible();
577
+ if (is_array($ip)) {
578
+ list($IP, $variable) = $ip;
579
+ return $IP;
580
+ }
581
+ return false;
582
+ }
583
+
584
+ public static function getIPAndServerVarible() {
585
+ $connectionIP = array_key_exists('REMOTE_ADDR', $_SERVER) ? array($_SERVER['REMOTE_ADDR'], 'REMOTE_ADDR') : array('127.0.0.1', 'REMOTE_ADDR');
586
 
587
  $howGet = wfConfig::get('howGetIPs', false);
588
  if($howGet){
589
  if($howGet == 'REMOTE_ADDR'){
590
+ return self::getCleanIPAndServerVar(array($connectionIP));
591
  } else {
592
+ $ipsToCheck = array(
593
+ array($_SERVER[$howGet], $howGet),
594
+ $connectionIP,
595
+ );
596
+ return self::getCleanIPAndServerVar($ipsToCheck);
597
  }
598
  } else {
599
+ $ipsToCheck = array(
600
+ $connectionIP,
601
+ );
602
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
603
+ $ipsToCheck[] = array($_SERVER['HTTP_X_FORWARDED_FOR'], 'HTTP_X_FORWARDED_FOR');
604
+ }
605
+ if (isset($_SERVER['HTTP_X_REAL_IP'])) {
606
+ $ipsToCheck[] = array($_SERVER['HTTP_X_REAL_IP'], 'HTTP_X_REAL_IP');
607
+ }
608
+ return self::getCleanIPAndServerVar($ipsToCheck);
609
  }
610
+ return false; //Returns an array with a valid IP and the server variable, or false.
611
  }
612
  public static function isValidIP($IP){
613
  return filter_var($IP, FILTER_VALIDATE_IP) !== false;
1165
  return $ip;
1166
  }
1167
 
1168
+ public static function set_html_content_type() {
1169
+ return 'text/html';
1170
+ }
1171
+
1172
+ public static function htmlEmail($to, $subject, $body) {
1173
+ add_filter( 'wp_mail_content_type', 'wfUtils::set_html_content_type' );
1174
+ $result = wp_mail($to, $subject, $body);
1175
+ remove_filter( 'wp_mail_content_type', 'wfUtils::set_html_content_type' );
1176
+ return $result;
1177
+ }
1178
+
1179
  /**
1180
  * @param string $readmePath
1181
  * @return bool
1210
  }
1211
  return false;
1212
  }
1213
+
1214
+ public static function htaccessAppend($code)
1215
+ {
1216
+ $htaccess = ABSPATH . '/.htaccess';
1217
+ $content = self::htaccess();
1218
+ if (wfUtils::isNginx() || !is_writable($htaccess)) {
1219
+ return false;
1220
+ }
1221
+
1222
+ if (strpos($content, $code) === false) {
1223
+ // make sure we write this once
1224
+ file_put_contents($htaccess, $content . "\n" . trim($code), LOCK_EX);
1225
+ }
1226
+
1227
+ return true;
1228
+ }
1229
+
1230
+ public static function htaccess() {
1231
+ if (is_readable(ABSPATH . '/.htaccess') && !wfUtils::isNginx()) {
1232
+ return file_get_contents(ABSPATH . '/.htaccess');
1233
+ }
1234
+ return "";
1235
+ }
1236
+
1237
+ /**
1238
+ * @param array $array
1239
+ * @param mixed $oldKey
1240
+ * @param mixed $newKey
1241
+ * @return array
1242
+ * @throws Exception
1243
+ */
1244
+ public static function arrayReplaceKey($array, $oldKey, $newKey) {
1245
+ $keys = array_keys($array);
1246
+ if (($index = array_search($oldKey, $keys)) === false) {
1247
+ throw new Exception(sprintf('Key "%s" does not exist', $oldKey));
1248
+ }
1249
+ $keys[$index] = $newKey;
1250
+ return array_combine($keys, array_values($array));
1251
+ }
1252
+
1253
  }
1254
 
1255
  // GeoIP lib uses these as well
1265
  }
1266
 
1267
 
1268
+ class wfWebServerInfo {
1269
+
1270
+ const APACHE = 1;
1271
+ const NGINX = 2;
1272
+ const LITESPEED = 4;
1273
+ const IIS = 8;
1274
+
1275
+ private $handler;
1276
+ private $software;
1277
+ private $softwareName;
1278
+
1279
+ /**
1280
+ *
1281
+ */
1282
+ public static function createFromEnvironment() {
1283
+ $serverInfo = new self;
1284
+ if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== false) {
1285
+ $serverInfo->setSoftware(self::APACHE);
1286
+ $serverInfo->setSoftwareName('apache');
1287
+ }
1288
+ if (stripos($_SERVER['SERVER_SOFTWARE'], 'litespeed') !== false) {
1289
+ $serverInfo->setSoftware(self::LITESPEED);
1290
+ $serverInfo->setSoftwareName('litespeed');
1291
+ }
1292
+ if (strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false) {
1293
+ $serverInfo->setSoftware(self::NGINX);
1294
+ $serverInfo->setSoftwareName('nginx');
1295
+ }
1296
+ if (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false || strpos($_SERVER['SERVER_SOFTWARE'], 'ExpressionDevServer') !== false) {
1297
+ $serverInfo->setSoftware(self::IIS);
1298
+ $serverInfo->setSoftwareName('iis');
1299
+ }
1300
+
1301
+ $serverInfo->setHandler(php_sapi_name());
1302
+
1303
+ return $serverInfo;
1304
+ }
1305
+
1306
+ /**
1307
+ * @return bool
1308
+ */
1309
+ public function isApache() {
1310
+ return $this->getSoftware() === self::APACHE;
1311
+ }
1312
+
1313
+ /**
1314
+ * @return bool
1315
+ */
1316
+ public function isNGINX() {
1317
+ return $this->getSoftware() === self::NGINX;
1318
+ }
1319
+
1320
+ /**
1321
+ * @return bool
1322
+ */
1323
+ public function isLiteSpeed() {
1324
+ return $this->getSoftware() === self::LITESPEED;
1325
+ }
1326
+
1327
+ /**
1328
+ * @return bool
1329
+ */
1330
+ public function isIIS() {
1331
+ return $this->getSoftware() === self::IIS;
1332
+ }
1333
+
1334
+ /**
1335
+ * @return bool
1336
+ */
1337
+ public function isApacheModPHP() {
1338
+ return $this->isApache() && function_exists('apache_get_modules');
1339
+ }
1340
+
1341
+ /**
1342
+ * Not sure if this can be implemented at the PHP level.
1343
+ * @return bool
1344
+ */
1345
+ public function isApacheSuPHP() {
1346
+ return $this->isApache() && $this->isCGI() &&
1347
+ function_exists('posix_getuid') &&
1348
+ getmyuid() === posix_getuid();
1349
+ }
1350
+
1351
+ /**
1352
+ * @return bool
1353
+ */
1354
+ public function isCGI() {
1355
+ return !$this->isFastCGI() && stripos($this->getHandler(), 'cgi') !== false;
1356
+ }
1357
+
1358
+ /**
1359
+ * @return bool
1360
+ */
1361
+ public function isFastCGI() {
1362
+ return stripos($this->getHandler(), 'fastcgi') !== false || stripos($this->getHandler(), 'fpm-fcgi') !== false;
1363
+ }
1364
+
1365
+ /**
1366
+ * @return mixed
1367
+ */
1368
+ public function getHandler() {
1369
+ return $this->handler;
1370
+ }
1371
+
1372
+ /**
1373
+ * @param mixed $handler
1374
+ */
1375
+ public function setHandler($handler) {
1376
+ $this->handler = $handler;
1377
+ }
1378
+
1379
+ /**
1380
+ * @return mixed
1381
+ */
1382
+ public function getSoftware() {
1383
+ return $this->software;
1384
+ }
1385
+
1386
+ /**
1387
+ * @param mixed $software
1388
+ */
1389
+ public function setSoftware($software) {
1390
+ $this->software = $software;
1391
+ }
1392
+
1393
+ /**
1394
+ * @return mixed
1395
+ */
1396
+ public function getSoftwareName() {
1397
+ return $this->softwareName;
1398
+ }
1399
+
1400
+ /**
1401
+ * @param mixed $softwareName
1402
+ */
1403
+ public function setSoftwareName($softwareName) {
1404
+ $this->softwareName = $softwareName;
1405
+ }
1406
+ }
1407
+
1408
  ?>
lib/wfView.php CHANGED
@@ -36,7 +36,7 @@ class wfView {
36
  * @param array $data
37
  */
38
  public function __construct($view, $data = array()) {
39
- $this->view_path = WP_PLUGIN_DIR . '/wordfence/views';
40
  $this->view = $view;
41
  $this->data = $data;
42
  }
@@ -116,7 +116,7 @@ class wfView {
116
  * Prevent POP
117
  */
118
  public function __wakeup() {
119
- $this->view_path = WP_PLUGIN_DIR . '/wordfence/views';
120
  $this->view = null;
121
  $this->data = array();
122
  $this->view_file_extension = '.php';
36
  * @param array $data
37
  */
38
  public function __construct($view, $data = array()) {
39
+ $this->view_path = WORDFENCE_PATH . 'views';
40
  $this->view = $view;
41
  $this->data = $data;
42
  }
116
  * Prevent POP
117
  */
118
  public function __wakeup() {
119
+ $this->view_path = WORDFENCE_PATH . 'views';
120
  $this->view = null;
121
  $this->data = array();
122
  $this->view_file_extension = '.php';
lib/wordfenceClass.php CHANGED
@@ -19,6 +19,7 @@ require_once 'wfDirectoryIterator.php';
19
  require_once 'wfUpdateCheck.php';
20
  require_once 'wfActivityReport.php';
21
  require_once 'wfHelperBin.php';
 
22
 
23
  class wordfence {
24
  public static $printStatus = false;
@@ -75,13 +76,40 @@ class wordfence {
75
  // Remove cron for email summary
76
  wfActivityReport::clearCronJobs();
77
 
 
 
 
78
  wfConfig::clearDiskCache();
 
 
 
 
 
 
 
 
 
79
  if(wfConfig::get('deleteTablesOnDeact')){
80
  $schema = new wfSchema();
81
  $schema->dropAll();
82
  foreach(array('wordfence_version', 'wordfenceActivated') as $opt){
83
  delete_option($opt);
84
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  }
86
  }
87
  public static function hourlyCron(){
@@ -125,12 +153,13 @@ class wordfence {
125
  $len = strlen($resp['data']);
126
  $reason = "WFSN: Blocked by Wordfence Security Network";
127
  $wfdb->queryWrite("delete from $p"."wfBlocks where wfsn=1 and permanent=0");
 
128
  if($len > 0 && $len % 16 == 0){
129
  for($i = 0; $i < $len; $i += 16){
130
  $ip_bin = substr($resp['data'], $i, 16);
131
  $IPStr = wfUtils::inet_ntop($ip_bin);
132
- if(! self::getLog()->isWhitelisted($IPStr)){
133
- self::getLog()->blockIP($IPStr, $reason, true);
134
  }
135
  }
136
  }
@@ -208,14 +237,7 @@ class wordfence {
208
  // So if we do a once a day truncate to be safe, we'll only potentially lose the hour right before the truncate.
209
  // Worth it to clean out the table completely once a day.
210
 
211
-
212
- $count = $wfdb->querySingle("select count(*) as cnt from $p"."wfHits");
213
- if($count > 20000){
214
- $wfdb->truncate($p . "wfHits"); //So we don't slow down sites that have very large wfHits tables
215
- } else if($count > 2000){
216
- $wfdb->queryWrite("delete from $p"."wfHits order by id asc limit %d", ($count - 100));
217
- }
218
-
219
  /*
220
  $count6 = $wfdb->querySingle("select count(*) as cnt from $p"."wfPerfLog");
221
  if($count6 > 20000){
@@ -414,28 +436,87 @@ class wordfence {
414
  wfUtils::hideReadme();
415
  }
416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  //Must be the final line
418
  }
419
  private static function doEarlyAccessLogging(){
420
  $wfLog = self::getLog();
421
  if($wfLog->logHitOK()){
422
- if( empty($wfFunc) && is_404() ){
 
 
 
 
 
423
  $wfLog->logLeechAndBlock('404');
424
  } else {
425
  $wfLog->logLeechAndBlock('hit');
426
  }
427
- if(wfConfig::liveTrafficEnabled()){
428
- self::$hitID = $wfLog->logHit();
429
- add_action('wp_head', 'wordfence::wfLogHumanHeader');
430
- }
431
- /*
432
- if(wfConfig::get('perfLoggingEnabled', false)){
433
- add_action('wp_head', 'wordfence::wfLogPerfHeader');
434
- }
435
- */
436
  }
437
  }
438
  public static function initProtection(){
 
439
  if(preg_match('/\/wp\-admin\/admin\-ajax\.php/', $_SERVER['REQUEST_URI'])){
440
  if(
441
  (isset($_GET['action']) && $_GET['action'] == 'revslider_show_image' && isset($_GET['img']) && preg_match('/\.php$/i', $_GET['img'])) ||
@@ -447,17 +528,17 @@ class wordfence {
447
  }
448
  }
449
  public static function install_actions(){
450
- self::initProtection();
451
- if(wfUtils::hasLoginCookie()){ //Fast way of checking if user may be logged in. Not secure, but these are only available if you're signed in.
452
- register_activation_hook(WP_PLUGIN_DIR . '/wordfence/wordfence.php', 'wordfence::installPlugin');
453
- register_deactivation_hook(WP_PLUGIN_DIR . '/wordfence/wordfence.php', 'wordfence::uninstallPlugin');
454
- }
455
 
456
  $versionInOptions = get_option('wordfence_version', false);
457
  if( (! $versionInOptions) || version_compare(WORDFENCE_VERSION, $versionInOptions, '>')){
458
  //Either there is no version in options or the version in options is greater and we need to run the upgrade
459
  self::runInstall();
460
  }
 
 
 
461
  //These access wfConfig::get('apiKey') and will fail if runInstall hasn't executed.
462
  wfCache::setupCaching();
463
 
@@ -562,6 +643,34 @@ class wordfence {
562
  }
563
 
564
  add_action('request', 'wordfence::preventAuthorNScans');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  }
566
  /*
567
  public static function cronAddSchedules($schedules){
@@ -613,6 +722,7 @@ class wordfence {
613
  die(json_encode(array('ok' => 1)));
614
  }
615
  public static function ajax_logHuman_callback(){
 
616
  $browscap = new wfBrowscap();
617
  $UA = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
618
  $isCrawler = false;
@@ -630,7 +740,7 @@ class wordfence {
630
  header("Content-Length: 0");
631
  header("X-Robots-Tag: noindex");
632
  if (!$isCrawler) {
633
- setcookie('wordfence_verifiedHuman', wp_create_nonce('wordfence_verifiedHuman' . $UA . wfUtils::getIP()), time() + 86400, '/');
634
  }
635
  }
636
  flush();
@@ -731,6 +841,10 @@ class wordfence {
731
  }
732
  public static function lostPasswordPost(){
733
  $IP = wfUtils::getIP();
 
 
 
 
734
  if(self::getLog()->isWhitelisted($IP)){
735
  return;
736
  }
@@ -775,7 +889,11 @@ class wordfence {
775
  public static function isLockedOut($IP){
776
  return self::getLog()->isIPLockedOut($IP);
777
  }
778
- public static function veryFirstAction(){
 
 
 
 
779
  $wfFunc = isset($_GET['_wfsf']) ? @$_GET['_wfsf'] : false;
780
  if($wfFunc == 'unlockEmail'){
781
  if(! wp_verify_nonce(@$_POST['nonce'], 'wf-form')){
@@ -865,6 +983,70 @@ class wordfence {
865
  }
866
  }
867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  if(wfConfig::get('firewallEnabled')){
869
  $wfLog = self::getLog();
870
  $wfLog->firewallBadIPs();
@@ -905,6 +1087,7 @@ class wordfence {
905
  }
906
  }
907
  }
 
908
  public static function loginAction($username){
909
  if(sizeof($_POST) < 1){ return; } //only execute if login form is posted
910
  if(! $username){ return; }
@@ -1032,13 +1215,15 @@ class wordfence {
1032
  if(wfConfig::get('other_WFNet') && is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'incorrect_password') ){
1033
  if($maxBlockTime = self::wfsnIsBlocked($IP, 'brute')){
1034
  self::getLog()->blockIP($IP, "Blocked by Wordfence Security Network", true, false, $maxBlockTime);
 
 
1035
  }
1036
 
1037
  }
1038
  if($secEnabled){
1039
  if(is_wp_error($authUser) && $authUser->get_error_code() == 'invalid_username'){
1040
  if($blacklist = wfConfig::get('loginSec_userBlacklist')){
1041
- $users = explode(',', $blacklist);
1042
  foreach($users as $user){
1043
  if(strtolower($username) == strtolower($user)){
1044
  self::getLog()->blockIP($IP, "Blocked by login security setting.");
@@ -1121,6 +1306,10 @@ class wordfence {
1121
  if(is_object($userDat)){
1122
  self::getLog()->logLogin('logout', 0, $userDat->user_login);
1123
  }
 
 
 
 
1124
  }
1125
  public static function loginInitAction(){
1126
  if(self::isLockedOut(wfUtils::getIP())){
@@ -1190,6 +1379,23 @@ class wordfence {
1190
  return array('errorMsg' => wp_kses($e->getMessage(), array()));
1191
  }
1192
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
  public static function ajax_sendTestEmail_callback(){
1194
  $result = wp_mail($_POST['email'], "Wordfence Test Email", "This is a test email from " . site_url() . ".\nThe IP address that requested this was: " . wfUtils::getIP());
1195
  $result = $result ? 'True' : 'False';
@@ -1785,6 +1991,12 @@ class wordfence {
1785
  public static function ajax_saveConfig_callback(){
1786
  $reload = '';
1787
  $opts = wfConfig::parseOptions();
 
 
 
 
 
 
1788
  $emails = array();
1789
  foreach(explode(',', preg_replace('/[\r\n\s\t]+/', '', $opts['alertEmails'])) as $email){
1790
  if(strlen($email) > 0){
@@ -1806,6 +2018,13 @@ class wordfence {
1806
  $opts['alertEmails'] = '';
1807
  }
1808
  $opts['scan_exclude'] = wfUtils::cleanupOneEntryPerLine($opts['scan_exclude']);
 
 
 
 
 
 
 
1809
  $whiteIPs = array();
1810
  foreach(explode(',', preg_replace('/[\r\n\s\t]+/', '', $opts['whitelisted'])) as $whiteIP){
1811
  if(strlen($whiteIP) > 0){
@@ -1840,18 +2059,7 @@ class wordfence {
1840
  }
1841
  }
1842
  }
1843
- $userBlacklist = array();
1844
- foreach(explode(',', $opts['loginSec_userBlacklist']) as $user){
1845
- $user = trim($user);
1846
- if(strlen($user) > 0){
1847
- $userBlacklist[] = $user;
1848
- }
1849
- }
1850
- if(sizeof($userBlacklist) > 0){
1851
- $opts['loginSec_userBlacklist'] = implode(',', $userBlacklist);
1852
- } else {
1853
- $opts['loginSec_userBlacklist'] = '';
1854
- }
1855
 
1856
  $opts['apiKey'] = trim($opts['apiKey']);
1857
  if($opts['apiKey'] && (! preg_match('/^[a-fA-F0-9]+$/', $opts['apiKey'])) ){ //User entered something but it's garbage.
@@ -1904,6 +2112,13 @@ class wordfence {
1904
  $regenerateHtaccess = true;
1905
  }
1906
 
 
 
 
 
 
 
 
1907
  foreach($opts as $key => $val){
1908
  if($key != 'apiKey'){ //Don't save API key yet
1909
  wfConfig::set($key, $val);
@@ -1985,6 +2200,59 @@ class wordfence {
1985
  }
1986
  return array('ok' => 1, 'reload' => $reload, 'paidKeyMsg' => $paidKeyMsg );
1987
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1988
  public static function ajax_clearAllBlocked_callback(){
1989
  $op = $_POST['op'];
1990
  $wfLog = self::getLog();
@@ -2008,7 +2276,8 @@ class wordfence {
2008
  }
2009
  public static function ajax_permBlockIP_callback(){
2010
  $IP = $_POST['IP'];
2011
- self::getLog()->blockIP($IP, "Manual permanent block by admin", false, true);
 
2012
  return array('ok' => 1);
2013
  }
2014
  public static function ajax_loadStaticPanel_callback(){
@@ -2089,13 +2358,14 @@ class wordfence {
2089
  public static function ajax_blockIP_callback(){
2090
  $IP = trim($_POST['IP']);
2091
  $perm = (isset($_POST['perm']) && $_POST['perm'] == '1') ? true : false;
 
2092
  if (!wfUtils::isValidIP($IP)) {
2093
  return array('err' => 1, 'errorMsg' => "Please enter a valid IP address to block.");
2094
  }
2095
  if ($IP == wfUtils::getIP()) {
2096
  return array('err' => 1, 'errorMsg' => "You can't block your own IP address.");
2097
  }
2098
- if (self::getLog()->isWhitelisted($IP)) {
2099
  return array('err' => 1, 'errorMsg' => "The IP address " . wp_kses($IP, array()) . " is whitelisted and can't be blocked or it is in a range of internal IP addresses that Wordfence does not block. You can remove this IP from the whitelist on the Wordfence options page.");
2100
  }
2101
  if (wfConfig::get('neverBlockBG') != 'treatAsOtherCrawlers') { //Either neverBlockVerified or neverBlockUA is selected which means the user doesn't want to block google
@@ -2103,7 +2373,7 @@ class wordfence {
2103
  return array('err' => 1, 'errorMsg' => "The IP address you're trying to block belongs to Google. Your options are currently set to not block these crawlers. Change this in Wordfence options if you want to manually block Google.");
2104
  }
2105
  }
2106
- self::getLog()->blockIP($IP, $_POST['reason'], false, $perm);
2107
  return array('ok' => 1);
2108
  }
2109
  public static function ajax_reverseLookup_callback(){
@@ -2170,6 +2440,7 @@ class wordfence {
2170
  $serverTime = $wfdb->querySingle("select unix_timestamp()");
2171
  $jsonData = array(
2172
  'serverTime' => $serverTime,
 
2173
  'msg' => wp_kses_data( (string) $wfdb->querySingle("select msg from $p"."wfStatus where level < 3 order by ctime desc limit 1"))
2174
  );
2175
  $events = array();
@@ -2185,6 +2456,13 @@ class wordfence {
2185
  } else if($alsoGet == 'perfStats'){
2186
  $newestEventTime = $_POST['otherParams'];
2187
  $events = self::getLog()->getPerfStats($newestEventTime);
 
 
 
 
 
 
 
2188
  }
2189
  /*
2190
  $longest = 0;
@@ -2201,10 +2479,11 @@ class wordfence {
2201
  public static function ajax_activityLogUpdate_callback(){
2202
  $issues = new wfIssues();
2203
  return array(
2204
- 'ok' => 1,
2205
- 'items' => self::getLog()->getStatusEvents($_POST['lastctime']),
2206
- 'currentScanID' => $issues->getScanTime()
2207
- );
 
2208
  }
2209
  public static function ajax_updateAlertEmail_callback(){
2210
  $email = trim($_POST['email']);
@@ -2229,7 +2508,7 @@ class wordfence {
2229
  continue;
2230
  }
2231
  $file = $issue['data']['file'];
2232
- $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file);
2233
  $localFile = realpath($localFile);
2234
  if(strpos($localFile, ABSPATH) !== 0){
2235
  $errors[] = "An invalid file was requested: " . wp_kses($file, array());
@@ -2313,7 +2592,7 @@ class wordfence {
2313
  return array('errorMsg' => "Could not delete file because that issue does not appear to be a file related issue.");
2314
  }
2315
  $file = $issue['data']['file'];
2316
- $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file);
2317
  $localFile = realpath($localFile);
2318
  if(strpos($localFile, ABSPATH) !== 0){
2319
  return array('errorMsg' => "An invalid file was requested for deletion.");
@@ -2364,6 +2643,36 @@ class wordfence {
2364
  return array('errorMsg' => "Could not remove the option " . esc_html($issue['data']['option_name']) . ". The error was: " . esc_html($wpdb->last_error));
2365
  }
2366
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2367
  public static function ajax_restoreFile_callback(){
2368
  $issueID = intval($_POST['issueID']);
2369
  $wfIssues = new wfIssues();
@@ -2654,14 +2963,14 @@ class wordfence {
2654
  } else {
2655
  return array('ok' => 1); //fail silently
2656
  }
2657
- }
2658
  public static function ajax_passwdLoadJobs_callback(){
2659
  if(! wfAPI::SSLEnabled()){ return array('ok' => 1); } //If user hits start passwd audit they will get a helpful message. We don't want an error popping up for every ajax call if SSL is not supported.
2660
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
2661
  try {
2662
  $res = $api->call('password_load_jobs', array(), array(), true);
2663
  } catch(Exception $e){
2664
- return array('errorMsg' => "Could not load password audit jobs: " . $e);
2665
  }
2666
  if(is_array($res) && $res['ok']){
2667
  return array(
@@ -2724,7 +3033,7 @@ class wordfence {
2724
  //End logging
2725
 
2726
 
2727
- if(! ($wfFunc == 'diff' || $wfFunc == 'view' || $wfFunc == 'viewOption' || $wfFunc == 'sysinfo' || $wfFunc == 'cronview' || $wfFunc == 'dbview' || $wfFunc == 'conntest' || $wfFunc == 'unknownFiles' || $wfFunc == 'IPTraf' || $wfFunc == 'viewActivityLog' || $wfFunc == 'testmem' || $wfFunc == 'testtime' || $wfFunc == 'download')){
2728
  return;
2729
  }
2730
  if(! wfUtils::isAdmin()){
@@ -2762,6 +3071,8 @@ class wordfence {
2762
  self::wfFunc_testtime();
2763
  } else if($wfFunc == 'download'){
2764
  self::wfFunc_download();
 
 
2765
  }
2766
  exit(0);
2767
  }
@@ -2845,10 +3156,12 @@ wfscr.src = url;
2845
  EOL;
2846
  }
2847
  public static function wfLogHumanHeader(){
2848
- $URL = home_url('/?wordfence_logHuman=1&hid=' . wfUtils::encrypt(self::$hitID));
2849
- $URL = addslashes(preg_replace('/^https?:/i', '', $URL));
2850
- #Load as external script async so we don't slow page down.
2851
- echo <<<HTML
 
 
2852
  <script type="text/javascript">
2853
  (function(url){
2854
  if(/(?:Chrome\/26\.0\.1410\.63 Safari\/537\.31|WordfenceTestMonBot)/.test(navigator.userAgent)){ return; }
@@ -2883,6 +3196,7 @@ EOL;
2883
  })('$URL');
2884
  </script>
2885
  HTML;
 
2886
  }
2887
  public static function shutdownAction(){
2888
  }
@@ -3022,10 +3336,88 @@ HTML;
3022
  exit;
3023
  }
3024
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3025
  public static function initAction(){
3026
  if(wfConfig::liveTrafficEnabled() && (! wfConfig::get('disableCookies', false)) ){
3027
  self::setCookie();
3028
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3029
  }
3030
  private static function setCookie(){
3031
  $cookieName = 'wfvt_' . crc32(site_url());
@@ -3051,6 +3443,9 @@ HTML;
3051
  'welcomeClosed', 'startTourAgain', 'downgradeLicense', 'addTwoFactor', 'twoFacActivate', 'twoFacDel',
3052
  'loadTwoFactor', 'loadAvgSitePerf', 'sendTestEmail', 'addCacheExclusion', 'removeCacheExclusion',
3053
  'loadCacheExclusions', 'email_summary_email_address_debug', 'unblockNetwork', 'permanentlyBlockAllIPs',
 
 
 
3054
  ) as $func){
3055
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
3056
  }
@@ -3078,6 +3473,40 @@ HTML;
3078
  self::setupAdminVars();
3079
  }
3080
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3081
  }
3082
  private static function setupAdminVars(){
3083
  $updateInt = wfConfig::get('actUpdateInterval', 2);
@@ -3157,8 +3586,27 @@ HTML;
3157
  }
3158
  }
3159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3160
  add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
3161
  add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', wfUtils::getBaseURL() . 'images/wordfence-logo-16x16.png');
 
3162
  add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
3163
  /* add_submenu_page('Wordfence', 'Site Performance', 'Site Performance', 'activate_plugins', 'WordfenceSitePerfStats', 'wordfence::menu_sitePerfStats'); */
3164
  add_submenu_page('Wordfence', 'Performance Setup', 'Performance Setup', 'activate_plugins', 'WordfenceSitePerf', 'wordfence::menu_sitePerf');
@@ -3171,6 +3619,7 @@ HTML;
3171
  add_submenu_page("Wordfence", "Whois Lookup", "Whois Lookup", "activate_plugins", "WordfenceWhois", 'wordfence::menu_whois');
3172
  add_submenu_page("Wordfence", "Advanced Blocking", "Advanced Blocking", "activate_plugins", "WordfenceRangeBlocking", 'wordfence::menu_rangeBlocking');
3173
  add_submenu_page("Wordfence", "Options", "Options", "activate_plugins", "WordfenceSecOpt", 'wordfence::menu_options');
 
3174
  }
3175
  public static function menu_options(){
3176
  require 'menu_options.php';
@@ -3203,7 +3652,246 @@ HTML;
3203
  public static function menu_rangeBlocking(){
3204
  require 'menu_rangeBlocking.php';
3205
  }
3206
- public static function liveTrafficW3TCWarning(){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3207
  echo self::cachingWarning("W3 Total Cache");
3208
  }
3209
  public static function liveTrafficSuperCacheWarning(){
@@ -3212,7 +3900,20 @@ HTML;
3212
  public static function cachingWarning($plugin){
3213
  return '<div id="wordfenceConfigWarning" class="error fade"><p><strong>The Wordfence Live Traffic feature has been disabled because you have ' . $plugin . ' active which is not compatible with Wordfence Live Traffic.</strong> If you want to reenable Wordfence Live Traffic, you need to deactivate ' . $plugin . ' and then go to the Wordfence options page and reenable Live Traffic there. Wordfence does work with ' . $plugin . ', however Live Traffic will be disabled and the Wordfence firewall will also count less hits per visitor because of the ' . $plugin . ' caching function. All other functions should work correctly.</p></div>';
3214
  }
3215
- public static function menu_activity(){
 
 
 
 
 
 
 
 
 
 
 
 
 
3216
  require 'menu_activity.php';
3217
  }
3218
  public static function menu_scan(){
@@ -3240,15 +3941,16 @@ HTML;
3240
  }
3241
  }
3242
  }
3243
- public static function replaceVersion($url)
3244
- {
 
 
 
 
3245
  global $wp_version;
3246
- static $version = null;
3247
- if ($version === null) {
3248
- $version = wp_hash($wp_version . WORDFENCE_VERSION);
3249
- }
3250
- return preg_replace("/([&;\?]ver)=[0-9\.]+/", "$1={$version}", $url);
3251
  }
 
3252
  public static function genFilter($gen, $type){
3253
  if(wfConfig::get('other_hideWPVersion')){
3254
  return '';
@@ -3555,9 +4257,10 @@ HTML;
3555
  AND blockedTime + %d > UNIX_TIMESTAMP()', $blockedTime));
3556
  break;
3557
  }
 
3558
  if ($IPs && is_array($IPs)) {
3559
  foreach ($IPs as $IP) {
3560
- self::getLog()->blockIP(wfUtils::inet_ntop($IP), $reason, false, true);
3561
  }
3562
  }
3563
  switch ($type) {
@@ -3573,27 +4276,136 @@ HTML;
3573
  return array('ok' => 1);
3574
  }
3575
 
3576
-
3577
  /**
3578
- * Modify the query to prevent username enumeration.
3579
- *
3580
- * @param array $query_vars
3581
  * @return array
3582
  */
3583
- public static function preventAuthorNScans($query_vars) {
3584
- if (wfConfig::get('loginSec_disableAuthorScan') && !is_admin() &&
3585
- !empty($query_vars['author']) && is_numeric(preg_replace('/[^0-9]/', '', $query_vars['author'])) &&
3586
- (
3587
- (isset($_GET['author']) && is_numeric(preg_replace('/[^0-9]/', '', $_GET['author']))) ||
3588
- (isset($_POST['author']) && is_numeric(preg_replace('/[^0-9]/', '', $_POST['author'])))
3589
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3590
  ) {
3591
  $query_vars['author'] = -1;
3592
  }
3593
  return $query_vars;
3594
  }
3595
 
3596
-
3597
  /**
3598
  * @param WP_Upgrader $updater
3599
  * @param array $hook_extra
@@ -3603,5 +4415,909 @@ HTML;
3603
  wfUtils::hideReadme();
3604
  }
3605
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3606
  }
 
 
 
 
3607
  ?>
19
  require_once 'wfUpdateCheck.php';
20
  require_once 'wfActivityReport.php';
21
  require_once 'wfHelperBin.php';
22
+ require_once 'wfDiagnostic.php';
23
 
24
  class wordfence {
25
  public static $printStatus = false;
76
  // Remove cron for email summary
77
  wfActivityReport::clearCronJobs();
78
 
79
+ // Remove the admin user list so it can be regenerated if Wordfence is reactivated.
80
+ wfConfig::set_ser('adminUserList', false);
81
+
82
  wfConfig::clearDiskCache();
83
+
84
+ if (!WFWAF_SUBDIRECTORY_INSTALL) {
85
+ try {
86
+ wfWAF::getInstance()->getStorageEngine()->setConfig('wafDisabled', true);
87
+ } catch (wfWAFStorageFileException $e) {
88
+ error_log($e->getMessage());
89
+ }
90
+ }
91
+
92
  if(wfConfig::get('deleteTablesOnDeact')){
93
  $schema = new wfSchema();
94
  $schema->dropAll();
95
  foreach(array('wordfence_version', 'wordfenceActivated') as $opt){
96
  delete_option($opt);
97
  }
98
+
99
+ if (!WFWAF_SUBDIRECTORY_INSTALL) {
100
+ try {
101
+ if (WFWAF_AUTO_PREPEND) {
102
+ $helper = new wfWAFAutoPrependHelper();
103
+ if ($helper->uninstall()) {
104
+ wfWAF::getInstance()->uninstall();
105
+ }
106
+ } else {
107
+ wfWAF::getInstance()->uninstall();
108
+ }
109
+ } catch (wfWAFStorageFileException $e) {
110
+ error_log($e->getMessage());
111
+ }
112
+ }
113
  }
114
  }
115
  public static function hourlyCron(){
153
  $len = strlen($resp['data']);
154
  $reason = "WFSN: Blocked by Wordfence Security Network";
155
  $wfdb->queryWrite("delete from $p"."wfBlocks where wfsn=1 and permanent=0");
156
+ $log = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
157
  if($len > 0 && $len % 16 == 0){
158
  for($i = 0; $i < $len; $i += 16){
159
  $ip_bin = substr($resp['data'], $i, 16);
160
  $IPStr = wfUtils::inet_ntop($ip_bin);
161
+ if(! $log->isWhitelisted($IPStr)){
162
+ $log->blockIP($IPStr, $reason, true);
163
  }
164
  }
165
  }
237
  // So if we do a once a day truncate to be safe, we'll only potentially lose the hour right before the truncate.
238
  // Worth it to clean out the table completely once a day.
239
 
240
+ self::trimWfHits();
 
 
 
 
 
 
 
241
  /*
242
  $count6 = $wfdb->querySingle("select count(*) as cnt from $p"."wfPerfLog");
243
  if($count6 > 20000){
436
  wfUtils::hideReadme();
437
  }
438
 
439
+ $colsFor610 = array(
440
+ 'attackLogTime' => '`attackLogTime` double(17,6) unsigned NOT NULL AFTER `id`',
441
+ 'statusCode' => '`statusCode` int(11) NOT NULL DEFAULT 0 AFTER `jsRun`',
442
+ 'action' => "`action` varchar(64) NOT NULL DEFAULT '' AFTER `UA`",
443
+ 'actionDescription' => '`actionDescription` text AFTER `action`',
444
+ 'actionData' => '`actionData` text AFTER `actionDescription`',
445
+ );
446
+
447
+ $hitTable = $wpdb->base_prefix . 'wfHits';
448
+ foreach ($colsFor610 as $col => $colDefintion) {
449
+ $count = $wpdb->get_col($wpdb->prepare(<<<SQL
450
+ SELECT * FROM information_schema.COLUMNS
451
+ WHERE TABLE_SCHEMA=DATABASE()
452
+ AND COLUMN_NAME=%s
453
+ AND TABLE_NAME=%s
454
+ SQL
455
+ , $col, $hitTable));
456
+ if (!$count) {
457
+ $wpdb->query("ALTER TABLE $hitTable ADD COLUMN $colDefintion");
458
+ }
459
+ }
460
+
461
+ $has404 = $wpdb->get_col($wpdb->prepare(<<<SQL
462
+ SELECT * FROM information_schema.COLUMNS
463
+ WHERE TABLE_SCHEMA=DATABASE()
464
+ AND COLUMN_NAME='is404'
465
+ AND TABLE_NAME=%s
466
+ SQL
467
+ , $hitTable));
468
+ if ($has404) {
469
+ $wpdb->query(<<<SQL
470
+ UPDATE $hitTable
471
+ SET statusCode= CASE
472
+ WHEN is404=1 THEN 404
473
+ ELSE 200
474
+ END
475
+ SQL
476
+ );
477
+
478
+ $wpdb->query("ALTER TABLE $hitTable DROP COLUMN `is404`");
479
+ }
480
+
481
+ $loginsTable = "{$wpdb->base_prefix}wfLogins";
482
+ $hasHitID = $wpdb->get_col($wpdb->prepare(<<<SQL
483
+ SELECT * FROM information_schema.COLUMNS
484
+ WHERE TABLE_SCHEMA=DATABASE()
485
+ AND COLUMN_NAME='hitID'
486
+ AND TABLE_NAME=%s
487
+ SQL
488
+ , $loginsTable));
489
+ if (!$hasHitID) {
490
+ $wpdb->query("ALTER TABLE $loginsTable ADD COLUMN hitID int(11) DEFAULT NULL AFTER `id`, ADD INDEX(hitID)");
491
+ }
492
+
493
+ if (!WFWAF_SUBDIRECTORY_INSTALL) {
494
+ try {
495
+ wfWAF::getInstance()->getStorageEngine()->setConfig('wafDisabled', false);
496
+ } catch (wfWAFStorageFileException $e) {
497
+ error_log($e);
498
+ }
499
+ }
500
+
501
  //Must be the final line
502
  }
503
  private static function doEarlyAccessLogging(){
504
  $wfLog = self::getLog();
505
  if($wfLog->logHitOK()){
506
+ $request = $wfLog->getCurrentRequest();
507
+
508
+ if(is_404()){
509
+ if ($request) {
510
+ $request->statusCode = 404;
511
+ }
512
  $wfLog->logLeechAndBlock('404');
513
  } else {
514
  $wfLog->logLeechAndBlock('hit');
515
  }
 
 
 
 
 
 
 
 
 
516
  }
517
  }
518
  public static function initProtection(){
519
+ self::getLog()->initLogRequest();
520
  if(preg_match('/\/wp\-admin\/admin\-ajax\.php/', $_SERVER['REQUEST_URI'])){
521
  if(
522
  (isset($_GET['action']) && $_GET['action'] == 'revslider_show_image' && isset($_GET['img']) && preg_match('/\.php$/i', $_GET['img'])) ||
528
  }
529
  }
530
  public static function install_actions(){
531
+ register_activation_hook(WORDFENCE_FCPATH, 'wordfence::installPlugin');
532
+ register_deactivation_hook(WORDFENCE_FCPATH, 'wordfence::uninstallPlugin');
 
 
 
533
 
534
  $versionInOptions = get_option('wordfence_version', false);
535
  if( (! $versionInOptions) || version_compare(WORDFENCE_VERSION, $versionInOptions, '>')){
536
  //Either there is no version in options or the version in options is greater and we need to run the upgrade
537
  self::runInstall();
538
  }
539
+
540
+ self::initProtection();
541
+
542
  //These access wfConfig::get('apiKey') and will fail if runInstall hasn't executed.
543
  wfCache::setupCaching();
544
 
643
  }
644
 
645
  add_action('request', 'wordfence::preventAuthorNScans');
646
+ add_action('password_reset', 'wordfence::actionPasswordReset');
647
+
648
+ $adminUsers = new wfAdminUserMonitor();
649
+ if ($adminUsers->isEnabled()) {
650
+ add_action('set_user_role', array($adminUsers, 'updateToUserRole'), 10, 3);
651
+ add_action('grant_super_admin', array($adminUsers, 'grantSuperAdmin'), 10, 1);
652
+ add_action('revoke_super_admin', array($adminUsers, 'revokeSuperAdmin'), 10, 1);
653
+ } else if (wfConfig::get_ser('adminUserList', false)) {
654
+ // reset this in the event it's disabled or the network is too large
655
+ wfConfig::set_ser('adminUserList', false);
656
+ }
657
+
658
+ if (!self::getLog()->getCurrentRequest()->jsRun && wfConfig::liveTrafficEnabled()) {
659
+ add_action('wp_head', 'wordfence::wfLogHumanHeader');
660
+ add_action('login_head', 'wordfence::wfLogHumanHeader');
661
+ }
662
+
663
+ add_action('wordfence_processAttackData', 'wordfence::processAttackData');
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')) {
671
+ add_filter('update_feedback', 'wordfence::restoreReadmeForUpgrade');
672
+ }
673
+
674
  }
675
  /*
676
  public static function cronAddSchedules($schedules){
722
  die(json_encode(array('ok' => 1)));
723
  }
724
  public static function ajax_logHuman_callback(){
725
+ self::getLog()->canLogHit = false;
726
  $browscap = new wfBrowscap();
727
  $UA = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
728
  $isCrawler = false;
740
  header("Content-Length: 0");
741
  header("X-Robots-Tag: noindex");
742
  if (!$isCrawler) {
743
+ setcookie('wordfence_verifiedHuman', self::getLog()->getVerifiedHumanCookieValue($UA, wfUtils::getIP()), time() + 86400, '/');
744
  }
745
  }
746
  flush();
841
  }
842
  public static function lostPasswordPost(){
843
  $IP = wfUtils::getIP();
844
+ if ($request = self::getLog()->getCurrentRequest()) {
845
+ $request->action = 'lostPassword';
846
+ $request->save();
847
+ }
848
  if(self::getLog()->isWhitelisted($IP)){
849
  return;
850
  }
889
  public static function isLockedOut($IP){
890
  return self::getLog()->isIPLockedOut($IP);
891
  }
892
+
893
+ public static function veryFirstAction() {
894
+ /** @var wpdb $wpdb ; */
895
+ global $wpdb;
896
+
897
  $wfFunc = isset($_GET['_wfsf']) ? @$_GET['_wfsf'] : false;
898
  if($wfFunc == 'unlockEmail'){
899
  if(! wp_verify_nonce(@$_POST['nonce'], 'wf-form')){
983
  }
984
  }
985
 
986
+ // Sync the WAF data with the database.
987
+ if (!WFWAF_SUBDIRECTORY_INSTALL && $waf = wfWAF::getInstance()) {
988
+ try {
989
+ $configDefaults = array(
990
+ 'apiKey' => wfConfig::get('apiKey'),
991
+ 'isPaid' => wfConfig::get('isPaid'),
992
+ 'siteURL' => site_url(),
993
+ 'homeURL' => home_url(),
994
+ 'whitelistedIPs' => (string) wfConfig::get('whitelisted'),
995
+ 'howGetIPs' => (string) wfConfig::get('howGetIPs'),
996
+ );
997
+ foreach ($configDefaults as $key => $value) {
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
+
1012
+ if ($waf instanceof wfWAFWordPress && ($learningModeAttackException = $waf->getLearningModeAttackException())) {
1013
+ $log = self::getLog();
1014
+ $log->initLogRequest();
1015
+ $request = $log->getCurrentRequest();
1016
+ $request->action = 'learned:waf';
1017
+ $request->attackLogTime = microtime(true);
1018
+
1019
+ $ruleIDs = array();
1020
+ /** @var wfWAFRule $failedRule */
1021
+ foreach ($learningModeAttackException->getFailedRules() as $failedRule) {
1022
+ $ruleIDs[] = $failedRule->getRuleID();
1023
+ }
1024
+
1025
+ $actionData = array(
1026
+ 'learningMode' => 1,
1027
+ 'failedRules' => $ruleIDs,
1028
+ 'paramKey' => $learningModeAttackException->getParamKey(),
1029
+ 'paramValue' => $learningModeAttackException->getParamValue(),
1030
+ );
1031
+ if ($ruleIDs && $ruleIDs[0]) {
1032
+ $rule = $waf->getRule($ruleIDs[0]);
1033
+ if ($rule) {
1034
+ $request->actionDescription = $rule->getDescription();
1035
+ $actionData['category'] = $rule->getCategory();
1036
+ $actionData['ssl'] = $waf->getRequest()->getProtocol() === 'https';
1037
+ $actionData['fullRequest'] = base64_encode($waf->getRequest());
1038
+ }
1039
+ }
1040
+ $request->actionData = wfRequestModel::serializeActionData($actionData);
1041
+ register_shutdown_function(array($request, 'save'));
1042
+
1043
+ self::scheduleSendAttackData();
1044
+ }
1045
+ } catch (wfWAFStorageFileException $e) {
1046
+ // We don't have anywhere to write files in this scenario.
1047
+ }
1048
+ }
1049
+
1050
  if(wfConfig::get('firewallEnabled')){
1051
  $wfLog = self::getLog();
1052
  $wfLog->firewallBadIPs();
1087
  }
1088
  }
1089
  }
1090
+
1091
  public static function loginAction($username){
1092
  if(sizeof($_POST) < 1){ return; } //only execute if login form is posted
1093
  if(! $username){ return; }
1215
  if(wfConfig::get('other_WFNet') && is_wp_error($authUser) && ($authUser->get_error_code() == 'invalid_username' || $authUser->get_error_code() == 'incorrect_password') ){
1216
  if($maxBlockTime = self::wfsnIsBlocked($IP, 'brute')){
1217
  self::getLog()->blockIP($IP, "Blocked by Wordfence Security Network", true, false, $maxBlockTime);
1218
+ $secsToGo = wfConfig::get('blockedTime');
1219
+ self::getLog()->do503($secsToGo, "Blocked by Wordfence Security Network");
1220
  }
1221
 
1222
  }
1223
  if($secEnabled){
1224
  if(is_wp_error($authUser) && $authUser->get_error_code() == 'invalid_username'){
1225
  if($blacklist = wfConfig::get('loginSec_userBlacklist')){
1226
+ $users = explode("\n", wfUtils::cleanupOneEntryPerLine($blacklist));
1227
  foreach($users as $user){
1228
  if(strtolower($username) == strtolower($user)){
1229
  self::getLog()->blockIP($IP, "Blocked by login security setting.");
1306
  if(is_object($userDat)){
1307
  self::getLog()->logLogin('logout', 0, $userDat->user_login);
1308
  }
1309
+ // Unset the roadblock cookie
1310
+ if (!WFWAF_SUBDIRECTORY_INSTALL) {
1311
+ wfUtils::setcookie(wfWAF::getInstance()->getAuthCookieName(), ' ', time() - (86400 * 365), '/', null, null, true);
1312
+ }
1313
  }
1314
  public static function loginInitAction(){
1315
  if(self::isLockedOut(wfUtils::getIP())){
1379
  return array('errorMsg' => wp_kses($e->getMessage(), array()));
1380
  }
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();
1388
+ $findReplace = array(
1389
+ '<th ' => '<th style="text-align:left;background-color:#222;color:#fff;"',
1390
+ '<th>' => '<th style="text-align:left;background-color:#222;color:#fff;">',
1391
+ '<td class="success"' => '<td style="font-weight:bold;color:#008c10;" class="success"',
1392
+ '<td class="error"' => '<td style="font-weight:bold;color:#d0514c;" class="error"',
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(){
1400
  $result = wp_mail($_POST['email'], "Wordfence Test Email", "This is a test email from " . site_url() . ".\nThe IP address that requested this was: " . wfUtils::getIP());
1401
  $result = $result ? 'True' : 'False';
1991
  public static function ajax_saveConfig_callback(){
1992
  $reload = '';
1993
  $opts = wfConfig::parseOptions();
1994
+
1995
+ // These are now on the Diagnostics page, so they aren't sent across.
1996
+ foreach (self::$diagnosticParams as $param) {
1997
+ $opts[$param] = wfConfig::get($param);
1998
+ }
1999
+
2000
  $emails = array();
2001
  foreach(explode(',', preg_replace('/[\r\n\s\t]+/', '', $opts['alertEmails'])) as $email){
2002
  if(strlen($email) > 0){
2018
  $opts['alertEmails'] = '';
2019
  }
2020
  $opts['scan_exclude'] = wfUtils::cleanupOneEntryPerLine($opts['scan_exclude']);
2021
+
2022
+ foreach (explode("\n", $opts['scan_include_extra']) as $regex) {
2023
+ if (@preg_match("/$regex/", "") === FALSE) {
2024
+ return array('errorMsg' => "\"" . esc_html($regex). "\" is not a valid regular expression");
2025
+ }
2026
+ }
2027
+
2028
  $whiteIPs = array();
2029
  foreach(explode(',', preg_replace('/[\r\n\s\t]+/', '', $opts['whitelisted'])) as $whiteIP){
2030
  if(strlen($whiteIP) > 0){
2059
  }
2060
  }
2061
  }
2062
+ $opts['loginSec_userBlacklist'] = wfUtils::cleanupOneEntryPerLine($opts['loginSec_userBlacklist']);
 
 
 
 
 
 
 
 
 
 
 
2063
 
2064
  $opts['apiKey'] = trim($opts['apiKey']);
2065
  if($opts['apiKey'] && (! preg_match('/^[a-fA-F0-9]+$/', $opts['apiKey'])) ){ //User entered something but it's garbage.
2112
  $regenerateHtaccess = true;
2113
  }
2114
 
2115
+ if (!is_numeric($opts['liveTraf_maxRows'])) {
2116
+ return array(
2117
+ 'errorMsg' => 'Please enter a number for the amount of Live Traffic data to store.',
2118
+ );
2119
+ }
2120
+
2121
+
2122
  foreach($opts as $key => $val){
2123
  if($key != 'apiKey'){ //Don't save API key yet
2124
  wfConfig::set($key, $val);
2200
  }
2201
  return array('ok' => 1, 'reload' => $reload, 'paidKeyMsg' => $paidKeyMsg );
2202
  }
2203
+
2204
+ public static $diagnosticParams = array(
2205
+ 'addCacheComment',
2206
+ 'debugOn',
2207
+ 'startScansRemotely',
2208
+ 'ssl_verify',
2209
+ 'disableConfigCaching',
2210
+ 'betaThreatDefenseFeed',
2211
+ );
2212
+
2213
+ public static function ajax_saveDebuggingConfig_callback() {
2214
+ foreach (self::$diagnosticParams as $param) {
2215
+ wfConfig::set($param, array_key_exists($param, $_POST) ? '1' : '0');
2216
+ }
2217
+ try {
2218
+ wfWAF::getInstance()->getStorageEngine()
2219
+ ->setConfig('betaThreatDefenseFeed', wfConfig::get('betaThreatDefenseFeed'));
2220
+ } catch (wfWAFStorageFileException $e) {
2221
+ error_log($e->getMessage());
2222
+ }
2223
+ return array('ok' => 1, 'reload' => false, 'paidKeyMsg' => '');
2224
+ }
2225
+
2226
+
2227
+ public static function ajax_hideFileHtaccess_callback(){
2228
+ $issues = new wfIssues();
2229
+ $issue = $issues->getIssueByID($_POST['issueID']);
2230
+ if (!$issue) {
2231
+ return array('cerrorMsg' => "We could not find that issue in our database.");
2232
+ }
2233
+
2234
+ $file = $issue['data']['file'];
2235
+ $localFile = ABSPATH . '/' . $file;
2236
+ $localFile = realpath($localFile);
2237
+ if (strpos($localFile, ABSPATH) !== 0) {
2238
+ return array('cerrorMsg' => "An invalid file was requested for deletion.");
2239
+ }
2240
+ $localFile = substr($localFile, strlen(ABSPATH));
2241
+
2242
+ if (!wfUtils::htaccessAppend("<Files \"{$localFile}\">
2243
+ <IfModule mod_authz_core.c>
2244
+ Require all denied
2245
+ </IfModule>
2246
+ <IfModule !mod_authz_core.c>
2247
+ Order deny,allow
2248
+ Deny from all
2249
+ </IfModule>
2250
+ </Files>")) {
2251
+ return array('cerrorMsg' => "You don't have permission to repair .htaccess. You need to either fix the file manually using FTP or change the file permissions and ownership so that your web server has write access to repair the file.");
2252
+ }
2253
+ $issues->updateIssue($_POST['issueID'], 'delete');
2254
+ return array('ok' => 1);
2255
+ }
2256
  public static function ajax_clearAllBlocked_callback(){
2257
  $op = $_POST['op'];
2258
  $wfLog = self::getLog();
2276
  }
2277
  public static function ajax_permBlockIP_callback(){
2278
  $IP = $_POST['IP'];
2279
+ $log = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
2280
+ $log->blockIP($IP, "Manual permanent block by admin", false, true);
2281
  return array('ok' => 1);
2282
  }
2283
  public static function ajax_loadStaticPanel_callback(){
2358
  public static function ajax_blockIP_callback(){
2359
  $IP = trim($_POST['IP']);
2360
  $perm = (isset($_POST['perm']) && $_POST['perm'] == '1') ? true : false;
2361
+ $log = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
2362
  if (!wfUtils::isValidIP($IP)) {
2363
  return array('err' => 1, 'errorMsg' => "Please enter a valid IP address to block.");
2364
  }
2365
  if ($IP == wfUtils::getIP()) {
2366
  return array('err' => 1, 'errorMsg' => "You can't block your own IP address.");
2367
  }
2368
+ if ($log->isWhitelisted($IP)) {
2369
  return array('err' => 1, 'errorMsg' => "The IP address " . wp_kses($IP, array()) . " is whitelisted and can't be blocked or it is in a range of internal IP addresses that Wordfence does not block. You can remove this IP from the whitelist on the Wordfence options page.");
2370
  }
2371
  if (wfConfig::get('neverBlockBG') != 'treatAsOtherCrawlers') { //Either neverBlockVerified or neverBlockUA is selected which means the user doesn't want to block google
2373
  return array('err' => 1, 'errorMsg' => "The IP address you're trying to block belongs to Google. Your options are currently set to not block these crawlers. Change this in Wordfence options if you want to manually block Google.");
2374
  }
2375
  }
2376
+ $log->blockIP($IP, $_POST['reason'], false, $perm);
2377
  return array('ok' => 1);
2378
  }
2379
  public static function ajax_reverseLookup_callback(){
2440
  $serverTime = $wfdb->querySingle("select unix_timestamp()");
2441
  $jsonData = array(
2442
  'serverTime' => $serverTime,
2443
+ 'serverMicrotime' => microtime(true),
2444
  'msg' => wp_kses_data( (string) $wfdb->querySingle("select msg from $p"."wfStatus where level < 3 order by ctime desc limit 1"))
2445
  );
2446
  $events = array();
2456
  } else if($alsoGet == 'perfStats'){
2457
  $newestEventTime = $_POST['otherParams'];
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'])) {
2464
+ $jsonData['sql'] = $results['sql'];
2465
+ }
2466
  }
2467
  /*
2468
  $longest = 0;
2479
  public static function ajax_activityLogUpdate_callback(){
2480
  $issues = new wfIssues();
2481
  return array(
2482
+ 'ok' => 1,
2483
+ 'items' => self::getLog()->getStatusEvents($_POST['lastctime']),
2484
+ 'currentScanID' => $issues->getScanTime(),
2485
+ 'signatureUpdateTime' => wfConfig::get('signatureUpdateTime'),
2486
+ );
2487
  }
2488
  public static function ajax_updateAlertEmail_callback(){
2489
  $email = trim($_POST['email']);
2508
  continue;
2509
  }
2510
  $file = $issue['data']['file'];
2511
+ $localFile = ABSPATH . '/' . $file;
2512
  $localFile = realpath($localFile);
2513
  if(strpos($localFile, ABSPATH) !== 0){
2514
  $errors[] = "An invalid file was requested: " . wp_kses($file, array());
2592
  return array('errorMsg' => "Could not delete file because that issue does not appear to be a file related issue.");
2593
  }
2594
  $file = $issue['data']['file'];
2595
+ $localFile = ABSPATH . '/' . $file;
2596
  $localFile = realpath($localFile);
2597
  if(strpos($localFile, ABSPATH) !== 0){
2598
  return array('errorMsg' => "An invalid file was requested for deletion.");
2643
  return array('errorMsg' => "Could not remove the option " . esc_html($issue['data']['option_name']) . ". The error was: " . esc_html($wpdb->last_error));
2644
  }
2645
  }
2646
+ public static function ajax_fixFPD_callback(){
2647
+ $issues = new wfIssues();
2648
+ $issue = $issues->getIssueByID($_POST['issueID']);
2649
+ if (!$issue) {
2650
+ return array('cerrorMsg' => "We could not find that issue in our database.");
2651
+ }
2652
+
2653
+ $htaccess = ABSPATH . '/.htaccess';
2654
+ $change = "<IfModule mod_php5.c>\n\tphp_value display_errors 0\n</IfModule>";
2655
+ $content = "";
2656
+ if (file_exists($htaccess)) {
2657
+ $content = file_get_contents($htaccess);
2658
+ }
2659
+
2660
+ if (@file_put_contents($htaccess, trim($content . "\n" . $change), LOCK_EX) === false) {
2661
+ return array('cerrorMsg' => "You don't have permission to repair .htaccess. You need to either fix the file
2662
+ manually using FTP or change the file permissions and ownership so that your web server has write access to repair the file.");
2663
+ }
2664
+ if (wfScanEngine::testForFullPathDisclosure()) {
2665
+ // Didn't fix it, so revert the changes and return an error
2666
+ file_put_contents($htaccess, $content, LOCK_EX);
2667
+ return array(
2668
+ 'cerrorMsg' => "Modifying the .htaccess file did not resolve the issue, so the original .htaccess file
2669
+ was restored. You can fix this manually by setting <code>display_errors</code> to <code>Off</code> in
2670
+ your php.ini if your site is on a VPS or dedicated server that you control.",
2671
+ );
2672
+ }
2673
+ $issues->updateIssue($_POST['issueID'], 'delete');
2674
+ return array('ok' => 1);
2675
+ }
2676
  public static function ajax_restoreFile_callback(){
2677
  $issueID = intval($_POST['issueID']);
2678
  $wfIssues = new wfIssues();
2963
  } else {
2964
  return array('ok' => 1); //fail silently
2965
  }
2966
+ }
2967
  public static function ajax_passwdLoadJobs_callback(){
2968
  if(! wfAPI::SSLEnabled()){ return array('ok' => 1); } //If user hits start passwd audit they will get a helpful message. We don't want an error popping up for every ajax call if SSL is not supported.
2969
  $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
2970
  try {
2971
  $res = $api->call('password_load_jobs', array(), array(), true);
2972
  } catch(Exception $e){
2973
+ return array('errorMsg' => "Could not load password audit jobs: " . $e);
2974
  }
2975
  if(is_array($res) && $res['ok']){
2976
  return array(
3033
  //End logging
3034
 
3035
 
3036
+ if(! ($wfFunc == 'diff' || $wfFunc == 'view' || $wfFunc == 'viewOption' || $wfFunc == 'sysinfo' || $wfFunc == 'cronview' || $wfFunc == 'dbview' || $wfFunc == 'conntest' || $wfFunc == 'unknownFiles' || $wfFunc == 'IPTraf' || $wfFunc == 'viewActivityLog' || $wfFunc == 'testmem' || $wfFunc == 'testtime' || $wfFunc == 'download' || ($wfFunc == 'debugWAF' && WFWAF_DEBUG))){
3037
  return;
3038
  }
3039
  if(! wfUtils::isAdmin()){
3071
  self::wfFunc_testtime();
3072
  } else if($wfFunc == 'download'){
3073
  self::wfFunc_download();
3074
+ } else if($wfFunc == 'debugWAF' && WFWAF_DEBUG){
3075
+ self::wfFunc_debugWAF();
3076
  }
3077
  exit(0);
3078
  }
3156
  EOL;
3157
  }
3158
  public static function wfLogHumanHeader(){
3159
+ self::$hitID = self::getLog()->logHit();
3160
+ if (self::$hitID) {
3161
+ $URL = home_url('/?wordfence_logHuman=1&hid=' . wfUtils::encrypt(self::$hitID));
3162
+ $URL = addslashes(preg_replace('/^https?:/i', '', $URL));
3163
+ #Load as external script async so we don't slow page down.
3164
+ echo <<<HTML
3165
  <script type="text/javascript">
3166
  (function(url){
3167
  if(/(?:Chrome\/26\.0\.1410\.63 Safari\/537\.31|WordfenceTestMonBot)/.test(navigator.userAgent)){ return; }
3196
  })('$URL');
3197
  </script>
3198
  HTML;
3199
+ }
3200
  }
3201
  public static function shutdownAction(){
3202
  }
3336
  exit;
3337
  }
3338
 
3339
+ /**
3340
+ *
3341
+ */
3342
+ public static function wfFunc_debugWAF() {
3343
+ $data = array();
3344
+ if (!empty($_GET['hitid'])) {
3345
+ $data['hit'] = new wfRequestModel($_GET['hitid']);
3346
+ if ($data['hit']->actionData) {
3347
+ $data['hitData'] = (object) wfRequestModel::unserializeActionData($data['hit']->actionData);
3348
+ }
3349
+ echo wfView::create('waf/debug', $data);
3350
+ }
3351
+ }
3352
+
3353
  public static function initAction(){
3354
  if(wfConfig::liveTrafficEnabled() && (! wfConfig::get('disableCookies', false)) ){
3355
  self::setCookie();
3356
  }
3357
+ // This is more of a hurdle, but might stop an automated process.
3358
+ if (current_user_can('administrator')) {
3359
+ $adminUsers = new wfAdminUserMonitor();
3360
+ if ($adminUsers->isEnabled() && !$adminUsers->isAdminUserLogged(get_current_user_id())) {
3361
+ define('DISALLOW_FILE_MODS', true);
3362
+ }
3363
+ }
3364
+
3365
+ $currentUserID = get_current_user_id();
3366
+ $role = wordfence::getCurrentUserRole();
3367
+ if (!WFWAF_SUBDIRECTORY_INSTALL) {
3368
+ try {
3369
+ $authCookie = wfWAF::getInstance()->parseAuthCookie();
3370
+ if (is_user_logged_in() &&
3371
+ (
3372
+ !$authCookie ||
3373
+ (int) $currentUserID !== (int) $authCookie['userID'] ||
3374
+ $role !== $authCookie['role']
3375
+ )
3376
+ ) {
3377
+ $secureLoggedInCookie = is_ssl() && parse_url(get_option('home'), PHP_URL_SCHEME) === 'https';
3378
+
3379
+ wfUtils::setcookie(wfWAF::getInstance()->getAuthCookieName(),
3380
+ $currentUserID . '|' . $role . '|' .
3381
+ wfWAF::getInstance()->getAuthCookieValue($currentUserID, $role),
3382
+ time() + 43200, COOKIEPATH, COOKIE_DOMAIN, $secureLoggedInCookie, true);
3383
+ }
3384
+ } catch (wfWAFStorageFileException $e) {
3385
+ error_log($e->getMessage());
3386
+ }
3387
+ }
3388
+
3389
+ if (wfConfig::get('other_hideWPVersion')) {
3390
+
3391
+ global $wp_version;
3392
+ global $wp_styles;
3393
+
3394
+ if (!($wp_styles instanceof WP_Styles)) {
3395
+ $wp_styles = new WP_Styles();
3396
+ }
3397
+ if ($wp_styles->default_version === $wp_version) {
3398
+ $wp_styles->default_version = wp_hash($wp_styles->default_version);
3399
+ }
3400
+
3401
+ foreach ($wp_styles->registered as $key => $val) {
3402
+ if ($wp_styles->registered[$key]->ver === $wp_version) {
3403
+ $wp_styles->registered[$key]->ver = wp_hash($wp_styles->registered[$key]->ver);
3404
+ }
3405
+ }
3406
+
3407
+ global $wp_scripts;
3408
+ if (!($wp_scripts instanceof WP_Scripts)) {
3409
+ $wp_scripts = new WP_Scripts();
3410
+ }
3411
+ if ($wp_scripts->default_version === $wp_version) {
3412
+ $wp_scripts->default_version = wp_hash($wp_scripts->default_version);
3413
+ }
3414
+
3415
+ foreach ($wp_scripts->registered as $key => $val) {
3416
+ if ($wp_scripts->registered[$key]->ver === $wp_version) {
3417
+ $wp_scripts->registered[$key]->ver = wp_hash($wp_scripts->registered[$key]->ver);
3418
+ }
3419
+ }
3420
+ }
3421
  }
3422
  private static function setCookie(){
3423
  $cookieName = 'wfvt_' . crc32(site_url());
3443
  'welcomeClosed', 'startTourAgain', 'downgradeLicense', 'addTwoFactor', 'twoFacActivate', 'twoFacDel',
3444
  'loadTwoFactor', 'loadAvgSitePerf', 'sendTestEmail', 'addCacheExclusion', 'removeCacheExclusion',
3445
  'loadCacheExclusions', 'email_summary_email_address_debug', 'unblockNetwork', 'permanentlyBlockAllIPs',
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
  }
3473
  self::setupAdminVars();
3474
  }
3475
 
3476
+ if (!WFWAF_AUTO_PREPEND || WFWAF_SUBDIRECTORY_INSTALL) {
3477
+ if (empty($_GET['wafAction'])) {
3478
+ if (is_multisite()) {
3479
+ add_action('network_admin_notices', 'wordfence::wafAutoPrependNotice');
3480
+ } else {
3481
+ add_action('admin_notices', 'wordfence::wafAutoPrependNotice');
3482
+ }
3483
+ }
3484
+
3485
+ if (!empty($_GET['wafAction'])) {
3486
+ switch ($_GET['wafAction']) {
3487
+ case 'configureAutoPrepend':
3488
+ if (isset($_REQUEST['serverConfiguration'])) {
3489
+ check_admin_referer('wfWAFAutoPrepend', 'wfnonce');
3490
+ $helper = new wfWAFAutoPrependHelper($_REQUEST['serverConfiguration']);
3491
+ if (!empty($_REQUEST['downloadBackup'])) {
3492
+ $helper->downloadBackups(!empty($_REQUEST['backupIndex']) ? absint($_REQUEST['backupIndex']) : 0);
3493
+ }
3494
+
3495
+ // $adminURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend');
3496
+ // request_filesystem_credentials($adminURL);
3497
+ }
3498
+ break;
3499
+ }
3500
+ }
3501
+ }
3502
+
3503
+ if (!empty($_REQUEST['wafVerify']) && wp_verify_nonce($_REQUEST['wafVerify'], 'wfWAFAutoPrepend')) {
3504
+ if (is_multisite()) {
3505
+ add_action('network_admin_notices', 'wordfence::wafAutoPrependVerify');
3506
+ } else {
3507
+ add_action('admin_notices', 'wordfence::wafAutoPrependVerify');
3508
+ }
3509
+ }
3510
  }
3511
  private static function setupAdminVars(){
3512
  $updateInt = wfConfig::get('actUpdateInterval', 2);
3586
  }
3587
  }
3588
 
3589
+ if (!empty($_GET['page']) && $_GET['page'] === 'WordfenceWAF' && !empty($_GET['wafconfigrebuild']) && !WFWAF_SUBDIRECTORY_INSTALL) {
3590
+ check_admin_referer('wafconfigrebuild', 'waf-nonce');
3591
+
3592
+ $storage = wfWAF::getInstance()->getStorageEngine();
3593
+ if ($storage instanceof wfWAFStorageFile) {
3594
+ $configFile = $storage->getConfigFile();
3595
+ if (@unlink($configFile)) {
3596
+ if (function_exists('network_admin_url') && is_multisite()) {
3597
+ $wafMenuURL = network_admin_url('admin.php?page=WordfenceWAF');
3598
+ } else {
3599
+ $wafMenuURL = admin_url('admin.php?page=WordfenceWAF');
3600
+ }
3601
+ wp_redirect($wafMenuURL);
3602
+ exit;
3603
+ }
3604
+ }
3605
+ }
3606
+
3607
  add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
3608
  add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', wfUtils::getBaseURL() . 'images/wordfence-logo-16x16.png');
3609
+ add_submenu_page("Wordfence", "Firewall", "Firewall", "activate_plugins", "WordfenceWAF", 'wordfence::menu_waf');
3610
  add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
3611
  /* add_submenu_page('Wordfence', 'Site Performance', 'Site Performance', 'activate_plugins', 'WordfenceSitePerfStats', 'wordfence::menu_sitePerfStats'); */
3612
  add_submenu_page('Wordfence', 'Performance Setup', 'Performance Setup', 'activate_plugins', 'WordfenceSitePerf', 'wordfence::menu_sitePerf');
3619
  add_submenu_page("Wordfence", "Whois Lookup", "Whois Lookup", "activate_plugins", "WordfenceWhois", 'wordfence::menu_whois');
3620
  add_submenu_page("Wordfence", "Advanced Blocking", "Advanced Blocking", "activate_plugins", "WordfenceRangeBlocking", 'wordfence::menu_rangeBlocking');
3621
  add_submenu_page("Wordfence", "Options", "Options", "activate_plugins", "WordfenceSecOpt", 'wordfence::menu_options');
3622
+ add_submenu_page("Wordfence", "Diagnostics", "Diagnostics", "activate_plugins", "WordfenceDiagnostic", 'wordfence::menu_diagnostic');
3623
  }
3624
  public static function menu_options(){
3625
  require 'menu_options.php';
3652
  public static function menu_rangeBlocking(){
3653
  require 'menu_rangeBlocking.php';
3654
  }
3655
+
3656
+ public static function menu_waf() {
3657
+ global $wp_filesystem;
3658
+
3659
+ wp_enqueue_style('wordfence-jquery-ui-css', wfUtils::getBaseURL() . 'css/jquery-ui.min.css', array(), WORDFENCE_VERSION);
3660
+ wp_enqueue_style('wordfence-jquery-ui-structure-css', wfUtils::getBaseURL() . 'css/jquery-ui.structure.min.css', array(), WORDFENCE_VERSION);
3661
+ wp_enqueue_style('wordfence-jquery-ui-theme-css', wfUtils::getBaseURL() . 'css/jquery-ui.theme.min.css', array(), WORDFENCE_VERSION);
3662
+ wp_enqueue_style('wordfence-jquery-ui-timepicker-css', wfUtils::getBaseURL() . 'css/jquery-ui-timepicker-addon.css', array(), WORDFENCE_VERSION);
3663
+ wp_enqueue_style('wordfence-select2-css', wfUtils::getBaseURL() . 'css/select2.min.css', array(), WORDFENCE_VERSION);
3664
+
3665
+ wp_enqueue_script('wordfence-timepicker-js', wfUtils::getBaseURL() . 'js/jquery-ui-timepicker-addon.js', array('jquery', 'jquery-ui-datepicker', 'jquery-ui-slider'), WORDFENCE_VERSION);
3666
+ wp_enqueue_script('wordfence-select2-js', wfUtils::getBaseURL() . 'js/select2.min.js', array('jquery'), WORDFENCE_VERSION);
3667
+
3668
+ try {
3669
+ $wafData = self::_getWAFData();
3670
+ } catch (wfWAFStorageFileConfigException $e) {
3671
+ // We don't have anywhere to write files in this scenario. Let's notify the user to update the permissions.
3672
+ $wafData = array();
3673
+ $logPath = str_replace(ABSPATH, '~/', WFWAF_LOG_PATH);
3674
+ if (function_exists('network_admin_url') && is_multisite()) {
3675
+ $wafMenuURL = network_admin_url('admin.php?page=WordfenceWAF&wafconfigrebuild=1');
3676
+ } else {
3677
+ $wafMenuURL = admin_url('admin.php?page=WordfenceWAF&wafconfigrebuild=1');
3678
+ }
3679
+ $wafMenuURL = add_query_arg(array(
3680
+ 'waf-nonce' => wp_create_nonce('wafconfigrebuild'),
3681
+ ), $wafMenuURL);
3682
+ $storageExceptionMessage = $e->getMessage() . ' <a href="' . esc_url($wafMenuURL) . '">Click here</a> to rebuild the configuration file.';
3683
+ } catch (wfWAFStorageFileException $e) {
3684
+ // We don't have anywhere to write files in this scenario. Let's notify the user to update the permissions.
3685
+ $wafData = array();
3686
+ $logPath = str_replace(ABSPATH, '~/', WFWAF_LOG_PATH);
3687
+ $storageExceptionMessage = 'We were unable to write to ' . $logPath . ' which the WAF uses for storage. Please
3688
+ update permissions on the parent directory so the web server can write to it.';
3689
+ }
3690
+
3691
+ if (!empty($_GET['wafAction'])) {
3692
+ switch ($_GET['wafAction']) {
3693
+ case 'configureAutoPrepend':
3694
+ if (WFWAF_AUTO_PREPEND && !WFWAF_SUBDIRECTORY_INSTALL) {
3695
+ break;
3696
+ }
3697
+ $wfnonce = wp_create_nonce('wfWAFAutoPrepend');
3698
+
3699
+ $currentAutoPrependFile = ini_get('auto_prepend_file');
3700
+ $currentAutoPrepend = !empty($_REQUEST['currentAutoPrepend']) ? $_REQUEST['currentAutoPrepend'] : null;
3701
+ $adminURL = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend&currentAutoPrepend=' . $currentAutoPrepend);
3702
+ if ($currentAutoPrependFile &&
3703
+ is_file($currentAutoPrependFile) &&
3704
+ empty($currentAutoPrepend) &&
3705
+ !WFWAF_SUBDIRECTORY_INSTALL
3706
+ ) {
3707
+ $wafActionContent = sprintf("<p>The Wordfence Web Application Firewall is designed
3708
+ to run via a PHP ini setting called <code>auto_prepend_file</code> in order to ensure it runs before any potentially
3709
+ vulnerable code runs. This PHP setting is currently in use, and is including this file:</p>
3710
+
3711
+ <pre class='wf-pre'>%s</pre>
3712
+
3713
+ <p>If you don't recognize this file, please <a href='https://wordpress.org/support/plugin/wordfence'>contact us on the
3714
+ WordPress support forums</a> before proceeding.</p>
3715
+
3716
+ <p>You can proceed with the installation and we will include this from within our <code>wordfence-waf.php</code> file
3717
+ which should maintain compatibility with your site, or you can opt to override the existing PHP setting.</p>
3718
+
3719
+ <p>
3720
+ <a class='button button-primary' href='%s'>Include this file (Recommended)</a>
3721
+ <a class='button' href='%s'>Override this value</a>
3722
+ </p>
3723
+ ",
3724
+ esc_html($currentAutoPrependFile),
3725
+ esc_url(network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend&currentAutoPrepend=include')),
3726
+ esc_url(network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend&currentAutoPrepend=override'))
3727
+ );
3728
+ break;
3729
+ } else if (isset($_REQUEST['serverConfiguration'])) {
3730
+ check_admin_referer('wfWAFAutoPrepend', 'wfnonce');
3731
+ $allow_relaxed_file_ownership = true;
3732
+ $helper = new wfWAFAutoPrependHelper($_REQUEST['serverConfiguration'],
3733
+ $currentAutoPrepend === 'override' ? null : $currentAutoPrependFile);
3734
+ if (($backups = $helper->getFilesNeededForBackup()) && empty($_REQUEST['confirmedBackup'])) {
3735
+ $wafActionContent = '<p>Please download a backup copy of the following files before we make the necessary changes:</p>';
3736
+ $wafActionContent .= '<ul>';
3737
+ foreach ($backups as $index => $backup) {
3738
+ $wafActionContent .= '<li><a class="button" onclick="wfWAFConfirmBackup(' . $index . ');" href="' .
3739
+ esc_url(add_query_arg(array(
3740
+ 'downloadBackup' => 1,
3741
+ 'backupIndex' => $index,
3742
+ 'serverConfiguration' => $helper->getServerConfig(),
3743
+ 'wfnonce' => $wfnonce,
3744
+ ), $adminURL)) . '">Download ' . esc_html(basename($backup)) . '</a></li>';
3745
+ }
3746
+ $serverConfig = esc_attr($helper->getServerConfig());
3747
+ $jsonBackups = json_encode(array_map('basename', $backups));
3748
+ $adminURL = esc_url($adminURL);
3749
+ $wafActionContent .= "</ul>
3750
+ <form action='$adminURL' method='post'>
3751
+ <input type='hidden' name='wfnonce' value='$wfnonce'>
3752
+ <input type='hidden' value='$serverConfig' name='serverConfiguration'>
3753
+ <input type='hidden' value='1' name='confirmedBackup'>
3754
+ <button id='confirmed-backups' disabled class='button button-primary' type='submit'>Continue</button>
3755
+ </form>
3756
+ <script>
3757
+ var wfWAFBackups = $jsonBackups;
3758
+ var wfWAFConfirmedBackups = [];
3759
+ function wfWAFConfirmBackup(index) {
3760
+ wfWAFBackups[index] = false;
3761
+ var confirmed = true;
3762
+ for (var i = 0; i < wfWAFBackups.length; i++) {
3763
+ if (wfWAFBackups[i] !== false) {
3764
+ confirmed = false;
3765
+ }
3766
+ }
3767
+ if (confirmed) {
3768
+ document.getElementById('confirmed-backups').disabled = false;
3769
+ }
3770
+ }
3771
+ </script>";
3772
+ break;
3773
+ }
3774
+
3775
+ ob_start();
3776
+ if (false === ($credentials = request_filesystem_credentials($adminURL, '', false, ABSPATH,
3777
+ array('version', 'locale'), $allow_relaxed_file_ownership))
3778
+ ) {
3779
+ $wafActionContent = ob_get_clean();
3780
+ break;
3781
+ }
3782
+
3783
+ if (!WP_Filesystem($credentials, ABSPATH, $allow_relaxed_file_ownership)) {
3784
+ // Failed to connect, Error and request again
3785
+ request_filesystem_credentials($adminURL, '', true, ABSPATH, array('version', 'locale'),
3786
+ $allow_relaxed_file_ownership);
3787
+ $wafActionContent = ob_get_clean();
3788
+ break;
3789
+ }
3790
+
3791
+ if ($wp_filesystem->errors->get_error_code()) {
3792
+ foreach ($wp_filesystem->errors->get_error_messages() as $message)
3793
+ show_message($message);
3794
+ $wafActionContent = ob_get_clean();
3795
+ break;
3796
+ }
3797
+ ob_end_clean();
3798
+
3799
+ try {
3800
+ $helper->performInstallation($wp_filesystem);
3801
+
3802
+ $adminURL = json_encode(esc_url_raw(network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend&wafVerify='
3803
+ . $wfnonce . '&currentAutoPrepend=' . $currentAutoPrepend)));
3804
+ $wafActionContent = "<script>
3805
+ document.location.href=$adminURL;
3806
+ </script>";
3807
+ break;
3808
+ } catch (wfWAFAutoPrependHelperException $e) {
3809
+ $wafActionContent = "<p>" . $e->getMessage() . "</p>";
3810
+ break;
3811
+ }
3812
+ }
3813
+
3814
+
3815
+ $bootstrap = self::getWAFBootstrapPath();
3816
+
3817
+ // Auto populate drop down with server configuration
3818
+ // If no preconfiguration routine exists, output instructions for manual configuration
3819
+ $serverInfo = wfWebServerInfo::createFromEnvironment();
3820
+
3821
+ $dropdown = array(
3822
+ array("apache-mod_php", 'Apache + mod_php', $serverInfo->isApacheModPHP()),
3823
+ array("apache-suphp", 'Apache + suPHP', $serverInfo->isApacheSuPHP()),
3824
+ array("cgi", 'Apache + CGI/FastCGI', $serverInfo->isApache() &&
3825
+ !$serverInfo->isApacheSuPHP() &&
3826
+ ($serverInfo->isCGI() || $serverInfo->isFastCGI())),
3827
+ array("litespeed", 'LiteSpeed', $serverInfo->isLiteSpeed()),
3828
+ array("nginx", 'NGINX', $serverInfo->isNGINX()),
3829
+ // array("iis", 'Windows (IIS)', $serverInfo->isIIS()),
3830
+ );
3831
+ $wafActionContent = '<p>To be as secure as possible, the Wordfence Web Application Firewall is designed
3832
+ to run via a PHP ini setting called <code>auto_prepend_file</code> in order to ensure it runs before any potentially
3833
+ vulnerable code runs.</p>
3834
+
3835
+ <div class="wf-notice"><strong>NOTE:</strong> If you have separate WordPress installations with Wordfence installed within a subdirectory of
3836
+ this site, it is recommended that you perform the Firewall installation procedure on those sites before this one.</div>
3837
+ ';
3838
+ $hasRecommendedOption = false;
3839
+ $wafPrependOptions = '';
3840
+ foreach ($dropdown as $option) {
3841
+ list($optionValue, $optionText, $selected) = $option;
3842
+ $wafPrependOptions .= "<option value=\"$optionValue\"" . ($selected ? ' selected' : '')
3843
+ . ">$optionText" . ($selected ? ' (recommended based on our tests)' : '') . "</option>\n";
3844
+ if ($selected) {
3845
+ $hasRecommendedOption = true;
3846
+ }
3847
+ }
3848
+
3849
+ if (!$hasRecommendedOption) {
3850
+ $wafActionContent .= "<p>If you know your web server's configuration, please select it from the
3851
+ list below:</p>";
3852
+ } else {
3853
+ $wafActionContent .= "<p>We've preselected your server configuration based on our tests, but if
3854
+ you know your web server's configuration, please select it now.</p>";
3855
+ }
3856
+
3857
+ $wafActionContent .= "
3858
+ <form action='$adminURL' method='post'>
3859
+ <input type='hidden' name='wfnonce' value='$wfnonce'>
3860
+ <select name='serverConfiguration'>
3861
+ $wafPrependOptions
3862
+ </select>
3863
+ <button class='button button-primary' type='submit'>Continue</button>
3864
+ </form>
3865
+ ";
3866
+
3867
+ $wafActionContent .= "
3868
+ <h3>Alternate method:</h3>
3869
+ <p>We've also included instructions to manually perform the change if you are using a web server other than what is listed in the drop-down, or if file permissions prevent this change.</p>";
3870
+
3871
+ $additionally = 'You';
3872
+ if (!self::checkAndCreateBootstrap()) {
3873
+ $wafActionContent .= "<p>You will need create the following file in your WordPress root:</p>
3874
+ <pre class='wf-pre'>" . esc_html(self::getWAFBootstrapPath()) . "</pre>
3875
+ <p>You can create the file and set the permissions to allow WordPress to write to it, or you can add the code yourself:</p>
3876
+ <pre class='wf-pre'>" . esc_textarea(self::getWAFBootstrapContent()) . "</pre>";
3877
+
3878
+ $additionally = 'Additionally, you';
3879
+ }
3880
+ $wafActionContent .= "<p>{$additionally} will need to append the following code to your <code>php.ini</code>:</p>
3881
+ <pre class='wf-pre'>auto_prepend_file = '" . esc_textarea($bootstrap) . "'</pre>";
3882
+
3883
+
3884
+ $wafActionContent = sprintf('<div style="margin: 20px 0;">%s</div>', $wafActionContent);
3885
+ break;
3886
+
3887
+ case '':
3888
+ break;
3889
+ }
3890
+ }
3891
+ require 'menu_waf.php';
3892
+ }
3893
+
3894
+ public static function liveTrafficW3TCWarning() {
3895
  echo self::cachingWarning("W3 Total Cache");
3896
  }
3897
  public static function liveTrafficSuperCacheWarning(){
3900
  public static function cachingWarning($plugin){
3901
  return '<div id="wordfenceConfigWarning" class="error fade"><p><strong>The Wordfence Live Traffic feature has been disabled because you have ' . $plugin . ' active which is not compatible with Wordfence Live Traffic.</strong> If you want to reenable Wordfence Live Traffic, you need to deactivate ' . $plugin . ' and then go to the Wordfence options page and reenable Live Traffic there. Wordfence does work with ' . $plugin . ', however Live Traffic will be disabled and the Wordfence firewall will also count less hits per visitor because of the ' . $plugin . ' caching function. All other functions should work correctly.</p></div>';
3902
  }
3903
+ public static function menu_diagnostic(){
3904
+ $emailForm = true;
3905
+ require 'menu_diagnostic.php';
3906
+ }
3907
+ public static function menu_activity() {
3908
+ wp_enqueue_style('wordfence-jquery-ui-css', wfUtils::getBaseURL() . 'css/jquery-ui.min.css', array(), WORDFENCE_VERSION);
3909
+ wp_enqueue_style('wordfence-jquery-ui-structure-css', wfUtils::getBaseURL() . 'css/jquery-ui.structure.min.css', array(), WORDFENCE_VERSION);
3910
+ wp_enqueue_style('wordfence-jquery-ui-theme-css', wfUtils::getBaseURL() . 'css/jquery-ui.theme.min.css', array(), WORDFENCE_VERSION);
3911
+ wp_enqueue_style('wordfence-jquery-ui-timepicker-css', wfUtils::getBaseURL() . 'css/jquery-ui-timepicker-addon.css', array(), WORDFENCE_VERSION);
3912
+
3913
+ wp_enqueue_script('wordfence-timepicker-js', wfUtils::getBaseURL() . 'js/jquery-ui-timepicker-addon.js', array('jquery', 'jquery-ui-datepicker', 'jquery-ui-slider'), WORDFENCE_VERSION);
3914
+ wp_enqueue_script('wordfence-knockout-js', wfUtils::getBaseURL() . 'js/knockout-3.3.0.js', array(), WORDFENCE_VERSION);
3915
+ wp_enqueue_script('wordfence-live-traffic-js', wfUtils::getBaseURL() . 'js/admin.liveTraffic.js', array('jquery'), WORDFENCE_VERSION);
3916
+
3917
  require 'menu_activity.php';
3918
  }
3919
  public static function menu_scan(){
3941
  }
3942
  }
3943
  }
3944
+
3945
+ public static function replaceVersion($url) {
3946
+ return preg_replace_callback("/([&;\?]ver)=(.+?)(?:&|$)/", "wordfence::replaceVersionCallback", $url);
3947
+ }
3948
+
3949
+ public static function replaceVersionCallback($matches) {
3950
  global $wp_version;
3951
+ return $matches[1] . '=' . ($wp_version === $matches[2] ? wp_hash($matches[2]) : $matches[2]);
 
 
 
 
3952
  }
3953
+
3954
  public static function genFilter($gen, $type){
3955
  if(wfConfig::get('other_hideWPVersion')){
3956
  return '';
4257
  AND blockedTime + %d > UNIX_TIMESTAMP()', $blockedTime));
4258
  break;
4259
  }
4260
+ $log = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
4261
  if ($IPs && is_array($IPs)) {
4262
  foreach ($IPs as $IP) {
4263
+ $log->blockIP(wfUtils::inet_ntop($IP), $reason, false, true);
4264
  }
4265
  }
4266
  switch ($type) {
4276
  return array('ok' => 1);
4277
  }
4278
 
 
4279
  /**
 
 
 
4280
  * @return array
4281
  */
4282
+ public static function ajax_deleteAdminUser_callback() {
4283
+ /** @var wpdb $wpdb */
4284
+ global $wpdb;
4285
+ $issueID = absint(!empty($_POST['issueID']) ? $_POST['issueID'] : 0);
4286
+ $wfIssues = new wfIssues();
4287
+ $issue = $wfIssues->getIssueByID($issueID);
4288
+ if (!$issue) {
4289
+ return array('errorMsg' => "We could not find that issue in our database.");
4290
+ }
4291
+ $data = $issue['data'];
4292
+ if (empty($data['userID'])) {
4293
+ return array('errorMsg' => "We could not find that user in the database.");
4294
+ }
4295
+ $user = new WP_User($data['userID']);
4296
+ if (!$user->exists()) {
4297
+ return array('errorMsg' => "We could not find that user in the database.");
4298
+ }
4299
+ $userLogin = $user->user_login;
4300
+ if (is_multisite() && strcasecmp($user->user_email, get_site_option('admin_email')) === 0) {
4301
+ return array('errorMsg' => "This user's email is the network admin email. It will need to be changed before deleting this user.");
4302
+ }
4303
+ if (is_multisite()) {
4304
+ revoke_super_admin($data['userID']);
4305
+ }
4306
+ wp_delete_user($data['userID']);
4307
+ if (is_multisite()) {
4308
+ $wpdb->delete($wpdb->users, array('ID' => $data['userID']));
4309
+ }
4310
+ $wfIssues->deleteIssue($issueID);
4311
+
4312
+ return array(
4313
+ 'ok' => 1,
4314
+ 'user_login' => $userLogin,
4315
+ );
4316
+ }
4317
+
4318
+ public static function ajax_revokeAdminUser_callback() {
4319
+ $issueID = absint(!empty($_POST['issueID']) ? $_POST['issueID'] : 0);
4320
+ $wfIssues = new wfIssues();
4321
+ $issue = $wfIssues->getIssueByID($issueID);
4322
+ if (!$issue) {
4323
+ return array('errorMsg' => "We could not find that issue in our database.");
4324
+ }
4325
+ $data = $issue['data'];
4326
+ if (empty($data['userID'])) {
4327
+ return array('errorMsg' => "We could not find that user in the database.");
4328
+ }
4329
+ $user = new WP_User($data['userID']);
4330
+ $userLogin = $user->user_login;
4331
+ wp_revoke_user($data['userID']);
4332
+ if (is_multisite()) {
4333
+ revoke_super_admin($data['userID']);
4334
+ }
4335
+
4336
+ $wfIssues->deleteIssue($issueID);
4337
+
4338
+ return array(
4339
+ 'ok' => 1,
4340
+ 'user_login' => $userLogin,
4341
+ );
4342
+ }
4343
+
4344
+ /**
4345
+ *
4346
+ */
4347
+ public static function ajax_disableDirectoryListing_callback() {
4348
+ $issueID = absint($_POST['issueID']);
4349
+ $wfIssues = new wfIssues();
4350
+ $issue = $wfIssues->getIssueByID($issueID);
4351
+ if (!$issue) {
4352
+ return array(
4353
+ 'err' => 1,
4354
+ 'errorMsg' => "We could not find that issue in our database.",
4355
+ );
4356
+ }
4357
+ $wfIssues->deleteIssue($issueID);
4358
+
4359
+ $htaccessPath = wfCache::getHtaccessPath();
4360
+ if (!$htaccessPath) {
4361
+ return array(
4362
+ 'err' => 1,
4363
+ 'errorMsg' => "Wordfence could not find your .htaccess file.",
4364
+ );
4365
+ }
4366
+
4367
+ $fileContents = file_get_contents($htaccessPath);
4368
+ if (file_put_contents($htaccessPath, "# Added by Wordfence " . date('r') . "\nOptions -Indexes\n\n" . $fileContents, LOCK_EX)) {
4369
+ $uploadPaths = wp_upload_dir();
4370
+ if (!wfScanEngine::isDirectoryListingEnabled($uploadPaths['baseurl'])) {
4371
+ return array(
4372
+ 'ok' => 1,
4373
+ );
4374
+ } else {
4375
+ // Revert any changes done to .htaccess
4376
+ file_put_contents($htaccessPath, $fileContents, LOCK_EX);
4377
+ return array(
4378
+ 'err' => 1,
4379
+ 'errorMsg' => "Updating the .htaccess did not fix the issue. You may need to add <code>Options -Indexes</code>
4380
+ to your httpd.conf if using Apache, or find documentation on how to disable directory listing for your web server.",
4381
+ );
4382
+ }
4383
+ }
4384
+ return array(
4385
+ 'err' => 1,
4386
+ 'errorMsg' => "There was an error writing to your .htaccess file.",
4387
+ );
4388
+ }
4389
+
4390
+ /**
4391
+ * Modify the query to prevent username enumeration.
4392
+ *
4393
+ * @param array $query_vars
4394
+ * @return array
4395
+ */
4396
+ public static function preventAuthorNScans($query_vars) {
4397
+ if (wfConfig::get('loginSec_disableAuthorScan') && !is_admin() &&
4398
+ !empty($query_vars['author']) && is_numeric(preg_replace('/[^0-9]/', '', $query_vars['author'])) &&
4399
+ (
4400
+ (isset($_GET['author']) && is_numeric(preg_replace('/[^0-9]/', '', $_GET['author']))) ||
4401
+ (isset($_POST['author']) && is_numeric(preg_replace('/[^0-9]/', '', $_POST['author'])))
4402
+ )
4403
  ) {
4404
  $query_vars['author'] = -1;
4405
  }
4406
  return $query_vars;
4407
  }
4408
 
 
4409
  /**
4410
  * @param WP_Upgrader $updater
4411
  * @param array $hook_extra
4415
  wfUtils::hideReadme();
4416
  }
4417
  }
4418
+
4419
+ public static function ajax_saveWAFConfig_callback() {
4420
+ if (isset($_POST['wafConfigAction'])) {
4421
+ switch ($_POST['wafConfigAction']) {
4422
+ case 'config':
4423
+ if (!empty($_POST['wafStatus'])) {
4424
+ if ($_POST['wafStatus'] == 'learning-mode' && !empty($_POST['learningModeGracePeriodEnabled'])) {
4425
+ $gracePeriodEnd = strtotime(isset($_POST['learningModeGracePeriod']) ? $_POST['learningModeGracePeriod'] : '');
4426
+ if ($gracePeriodEnd > time()) {
4427
+ wfWAF::getInstance()->getStorageEngine()->setConfig('learningModeGracePeriodEnabled', 1);
4428
+ wfWAF::getInstance()->getStorageEngine()->setConfig('learningModeGracePeriod', $gracePeriodEnd);
4429
+ } else {
4430
+ return array(
4431
+ 'err' => 1,
4432
+ 'errorMsg' => "The grace period end time must be in the future.",
4433
+ );
4434
+ }
4435
+ } else {
4436
+ wfWAF::getInstance()->getStorageEngine()->setConfig('learningModeGracePeriodEnabled', 0);
4437
+ wfWAF::getInstance()->getStorageEngine()->unsetConfig('learningModeGracePeriod');
4438
+ }
4439
+ wfWAF::getInstance()->getStorageEngine()->setConfig('wafStatus', $_POST['wafStatus']);
4440
+ }
4441
+
4442
+ break;
4443
+
4444
+ case 'addWhitelist':
4445
+ if (isset($_POST['whitelistedPath']) && isset($_POST['whitelistedParam'])) {
4446
+ $path = stripslashes($_POST['whitelistedPath']);
4447
+ $paramKey = stripslashes($_POST['whitelistedParam']);
4448
+ if (!$path || !$paramKey) {
4449
+ break;
4450
+ }
4451
+ $data = array(
4452
+ 'timestamp' => time(),
4453
+ 'description' => 'Whitelisted via Firewall Options page',
4454
+ 'ip' => wfUtils::getIP(),
4455
+ 'disabled' => empty($_POST['whitelistedEnabled']),
4456
+ );
4457
+ if (function_exists('get_current_user_id')) {
4458
+ $data['userID'] = get_current_user_id();
4459
+ }
4460
+ wfWAF::getInstance()->whitelistRuleForParam($path, $paramKey, 'all', $data);
4461
+ }
4462
+ break;
4463
+
4464
+ case 'replaceWhitelist':
4465
+ if (
4466
+ !empty($_POST['oldWhitelistedPath']) && !empty($_POST['oldWhitelistedParam']) &&
4467
+ !empty($_POST['newWhitelistedPath']) && !empty($_POST['newWhitelistedParam'])
4468
+ ) {
4469
+ $oldWhitelistedPath = stripslashes($_POST['oldWhitelistedPath']);
4470
+ $oldWhitelistedParam = stripslashes($_POST['oldWhitelistedParam']);
4471
+
4472
+ $newWhitelistedPath = stripslashes($_POST['newWhitelistedPath']);
4473
+ $newWhitelistedParam = stripslashes($_POST['newWhitelistedParam']);
4474
+
4475
+ $savedWhitelistedURLParams = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLParams');
4476
+ // These are already base64'd
4477
+ $oldKey = $oldWhitelistedPath . '|' . $oldWhitelistedParam;
4478
+ $newKey = base64_encode($newWhitelistedPath) . '|' . base64_encode($newWhitelistedParam);
4479
+ try {
4480
+ $savedWhitelistedURLParams = wfUtils::arrayReplaceKey($savedWhitelistedURLParams, $oldKey, $newKey);
4481
+ } catch (Exception $e) {
4482
+ error_log("Caught exception from 'wfUtils::arrayReplaceKey' with message: " . $e->getMessage());
4483
+ }
4484
+ wfWAF::getInstance()->getStorageEngine()->setConfig('whitelistedURLParams', $savedWhitelistedURLParams);
4485
+ }
4486
+ break;
4487
+
4488
+ case 'deleteWhitelist':
4489
+ if (
4490
+ isset($_POST['deletedWhitelistedPath']) && is_string($_POST['deletedWhitelistedPath']) &&
4491
+ isset($_POST['deletedWhitelistedParam']) && is_string($_POST['deletedWhitelistedParam'])
4492
+ ) {
4493
+ $deletedWhitelistedPath = stripslashes($_POST['deletedWhitelistedPath']);
4494
+ $deletedWhitelistedParam = stripslashes($_POST['deletedWhitelistedParam']);
4495
+ $savedWhitelistedURLParams = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLParams');
4496
+ $key = base64_encode($deletedWhitelistedPath) . '|' . base64_encode($deletedWhitelistedParam);
4497
+ unset($savedWhitelistedURLParams[$key]);
4498
+ wfWAF::getInstance()->getStorageEngine()->setConfig('whitelistedURLParams', $savedWhitelistedURLParams);
4499
+ }
4500
+ break;
4501
+
4502
+ case 'enableWhitelist':
4503
+ if (isset($_POST['whitelistedPath']) && isset($_POST['whitelistedParam'])) {
4504
+ $path = stripslashes($_POST['whitelistedPath']);
4505
+ $paramKey = stripslashes($_POST['whitelistedParam']);
4506
+ if (!$path || !$paramKey) {
4507
+ break;
4508
+ }
4509
+ $enabled = !empty($_POST['whitelistedEnabled']);
4510
+
4511
+ $savedWhitelistedURLParams = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLParams');
4512
+ $key = $path . '|' . $paramKey;
4513
+ if (array_key_exists($key, $savedWhitelistedURLParams) && is_array($savedWhitelistedURLParams[$key])) {
4514
+ foreach ($savedWhitelistedURLParams[$key] as $ruleID => $data) {
4515
+ $savedWhitelistedURLParams[$key][$ruleID]['disabled'] = !$enabled;
4516
+ }
4517
+ }
4518
+ wfWAF::getInstance()->getStorageEngine()->setConfig('whitelistedURLParams', $savedWhitelistedURLParams);
4519
+ }
4520
+ break;
4521
+
4522
+ case 'enableRule':
4523
+ $ruleEnabled = !empty($_POST['ruleEnabled']);
4524
+ $ruleID = !empty($_POST['ruleID']) ? (int) $_POST['ruleID'] : false;
4525
+ if ($ruleID) {
4526
+ $disabledRules = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('disabledRules');
4527
+ if ($ruleEnabled) {
4528
+ unset($disabledRules[$ruleID]);
4529
+ } else {
4530
+ $disabledRules[$ruleID] = true;
4531
+ }
4532
+ wfWAF::getInstance()->getStorageEngine()->setConfig('disabledRules', $disabledRules);
4533
+ }
4534
+ break;
4535
+ }
4536
+ }
4537
+
4538
+ return array(
4539
+ 'success' => true,
4540
+ 'data' => self::_getWAFData(),
4541
+ );
4542
+ }
4543
+
4544
+ public static function ajax_updateWAFRules_callback() {
4545
+ $event = new wfWAFCronFetchRulesEvent(time() - 2);
4546
+ $event->setWaf(wfWAF::getInstance());
4547
+ $event->fire();
4548
+
4549
+ return self::_getWAFData();
4550
+ }
4551
+
4552
+ public static function ajax_loadLiveTraffic_callback() {
4553
+ $return = array();
4554
+
4555
+ $filters = new wfLiveTrafficQueryFilterCollection();
4556
+ $query = new wfLiveTrafficQuery(self::getLog());
4557
+ $query->setFilters($filters);
4558
+ if (array_key_exists('groupby', $_REQUEST)) {
4559
+ $param = $_REQUEST['groupby'];
4560
+ if ($param === 'type') {
4561
+ $param = 'jsRun';
4562
+ }
4563
+ $query->setGroupBy(new wfLiveTrafficQueryGroupBy($query, $param));
4564
+ }
4565
+ $query->setLimit(isset($_REQUEST['limit']) ? absint($_REQUEST['limit']) : 20);
4566
+ $query->setOffset(isset($_REQUEST['offset']) ? absint($_REQUEST['offset']) : 0);
4567
+
4568
+ if (!empty($_REQUEST['since'])) {
4569
+ $query->setStartDate($_REQUEST['since']);
4570
+ } else if (!empty($_REQUEST['startDate'])) {
4571
+ $query->setStartDate(is_numeric($_REQUEST['startDate']) ? $_REQUEST['startDate'] : strtotime($_REQUEST['startDate']));
4572
+ }
4573
+
4574
+ if (!empty($_REQUEST['endDate'])) {
4575
+ $query->setEndDate(is_numeric($_REQUEST['endDate']) ? $_REQUEST['endDate'] : strtotime($_REQUEST['endDate']));
4576
+ }
4577
+
4578
+ if (
4579
+ array_key_exists('param', $_REQUEST) && is_array($_REQUEST['param']) &&
4580
+ array_key_exists('operator', $_REQUEST) && is_array($_REQUEST['operator']) &&
4581
+ array_key_exists('value', $_REQUEST) && is_array($_REQUEST['value'])
4582
+ ) {
4583
+ for ($i = 0; $i < count($_REQUEST['param']); $i++) {
4584
+ if (
4585
+ array_key_exists($i, $_REQUEST['param']) &&
4586
+ array_key_exists($i, $_REQUEST['operator']) &&
4587
+ array_key_exists($i, $_REQUEST['value'])
4588
+ ) {
4589
+ $param = $_REQUEST['param'][$i];
4590
+ $operator = $_REQUEST['operator'][$i];
4591
+ $value = $_REQUEST['value'][$i];
4592
+
4593
+ switch (strtolower($param)) {
4594
+ case 'type':
4595
+ $param = 'jsRun';
4596
+ $value = strtolower($value) === 'human' ? 1 : 0;
4597
+ break;
4598
+ case 'ip':
4599
+ $value = wfUtils::inet_pton($value);
4600
+ break;
4601
+ case 'userid':
4602
+ $value = absint($value);
4603
+ break;
4604
+ }
4605
+ if ($operator === 'match' && $param !== 'ip') {
4606
+ $value = str_replace('*', '%', $value);
4607
+ }
4608
+ $filters->addFilter(new wfLiveTrafficQueryFilter($query, $param, $operator, $value));
4609
+ }
4610
+ }
4611
+ }
4612
+
4613
+ try {
4614
+ $return['data'] = $query->execute();
4615
+ if (defined('WP_DEBUG') && WP_DEBUG) {
4616
+ $return['sql'] = $query->buildQuery();
4617
+ }
4618
+ } catch (wfLiveTrafficQueryException $e) {
4619
+ $return['data'] = array();
4620
+ $return['sql'] = $e->getMessage();
4621
+ }
4622
+
4623
+ $return['success'] = true;
4624
+
4625
+ return $return;
4626
+ }
4627
+
4628
+ public static function ajax_whitelistWAFParamKey_callback() {
4629
+ if (class_exists('wfWAF') && $waf = wfWAF::getInstance()) {
4630
+ if (isset($_POST['path']) && isset($_POST['paramKey']) && isset($_POST['failedRules'])) {
4631
+ $data = array(
4632
+ 'timestamp' => time(),
4633
+ 'description' => 'Whitelisted via Live Traffic',
4634
+ 'ip' => wfUtils::getIP(),
4635
+ );
4636
+ if (function_exists('get_current_user_id')) {
4637
+ $data['userID'] = get_current_user_id();
4638
+ }
4639
+ $waf->whitelistRuleForParam(base64_decode($_POST['path']), base64_decode($_POST['paramKey']),
4640
+ $_POST['failedRules'], $data);
4641
+
4642
+ return array(
4643
+ 'success' => true,
4644
+ );
4645
+ }
4646
+ }
4647
+ return false;
4648
+ }
4649
+
4650
+ private static function _getWAFData() {
4651
+ $data['learningMode'] = wfWAF::getInstance()->isInLearningMode();
4652
+ $data['rules'] = wfWAF::getInstance()->getRules();
4653
+ /** @var wfWAFRule $rule */
4654
+ foreach ($data['rules'] as $ruleID => $rule) {
4655
+ $data['rules'][$ruleID] = $rule->toArray();
4656
+ }
4657
+
4658
+ $whitelistedURLParams = wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLParams', array());
4659
+ $data['whitelistedURLParams'] = array();
4660
+ foreach ($whitelistedURLParams as $urlParamKey => $rules) {
4661
+ list($path, $paramKey) = explode('|', $urlParamKey);
4662
+ $whitelistData = null;
4663
+ foreach ($rules as $ruleID => $whitelistedData) {
4664
+ if ($whitelistData === null) {
4665
+ $whitelistData = $whitelistedData;
4666
+ continue;
4667
+ }
4668
+ if ($ruleID === 'all') {
4669
+ $whitelistData = $whitelistedData;
4670
+ break;
4671
+ }
4672
+ }
4673
+
4674
+ if (is_array($whitelistData) && array_key_exists('userID', $whitelistData) && function_exists('get_user_by')) {
4675
+ $user = get_user_by('id', $whitelistData['userID']);
4676
+ if ($user) {
4677
+ $whitelistData['username'] = $user->user_login;
4678
+ }
4679
+ }
4680
+
4681
+ $data['whitelistedURLParams'][] = array(
4682
+ 'path' => $path,
4683
+ 'paramKey' => $paramKey,
4684
+ 'ruleID' => array_keys($rules),
4685
+ 'data' => $whitelistData,
4686
+ );
4687
+ }
4688
+
4689
+ $data['disabledRules'] = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('disabledRules');
4690
+ if ($lastUpdated = wfWAF::getInstance()->getStorageEngine()->getConfig('rulesLastUpdated')) {
4691
+ $data['rulesLastUpdated'] = $lastUpdated;
4692
+ }
4693
+ $data['isPaid'] = (bool) wfConfig::get('isPaid', 0);
4694
+ return $data;
4695
+ }
4696
+
4697
+ public static function actionUserRegistration($user_id) {
4698
+ if (user_can($user_id, 'manage_options') && ($request = self::getLog()->getCurrentRequest())) {
4699
+ //self::getLog()->canLogHit = true;
4700
+ $request->action = 'user:adminCreate';
4701
+ $request->save();
4702
+ }
4703
+ }
4704
+
4705
+ public static function actionPasswordReset($user = null, $new_pass = null) {
4706
+ if ($request = self::getLog()->getCurrentRequest()) {
4707
+ //self::getLog()->canLogHit = true;
4708
+ $request->action = 'user:passwordReset';
4709
+ $request->save();
4710
+ }
4711
+ }
4712
+
4713
+ public static function trimWfHits() {
4714
+ global $wpdb;
4715
+ $p = $wpdb->base_prefix;
4716
+ $wfdb = new wfDB();
4717
+ $count = $wfdb->querySingle("select count(*) as cnt from $p"."wfHits");
4718
+ $liveTrafficMaxRows = absint(wfConfig::get('liveTraf_maxRows', 2000));
4719
+ if ($count > $liveTrafficMaxRows * 10) {
4720
+ $wfdb->truncate($p . "wfHits"); //So we don't slow down sites that have very large wfHits tables
4721
+ } else if ($count > $liveTrafficMaxRows) {
4722
+ $wfdb->queryWrite("delete from $p" . "wfHits order by id asc limit %d", ($count - $liveTrafficMaxRows) + ($liveTrafficMaxRows * .2));
4723
+ }
4724
+ }
4725
+
4726
+ private static function scheduleSendAttackData($timeToSend = null) {
4727
+ if ($timeToSend === null) {
4728
+ $timeToSend = time() + (60 * 5);
4729
+ }
4730
+ $notMainSite = is_multisite() && !is_main_site();
4731
+ if ($notMainSite) {
4732
+ global $current_site;
4733
+ switch_to_blog($current_site->blog_id);
4734
+ }
4735
+ if (!wp_next_scheduled('wordfence_processAttackData')) {
4736
+ wp_schedule_single_event($timeToSend, 'wordfence_processAttackData');
4737
+ }
4738
+ if ($notMainSite) {
4739
+ restore_current_blog();
4740
+ }
4741
+ }
4742
+
4743
+ /**
4744
+ *
4745
+ */
4746
+ public static function processAttackData() {
4747
+ global $wpdb;
4748
+ $waf = wfWAF::getInstance();
4749
+ if ($waf->getStorageEngine()->getConfig('attackDataKey', false) === false) {
4750
+ $waf->getStorageEngine()->setConfig('attackDataKey', mt_rand(0, 0xfff));
4751
+ }
4752
+
4753
+ $limit = 500;
4754
+ $lastSendTime = wfConfig::get('lastAttackDataSendTime');
4755
+ $attackData = $wpdb->get_results($wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM {$wpdb->base_prefix}wfHits
4756
+ WHERE action in ('blocked:waf', 'learned:waf')
4757
+ AND attackLogTime > %.6f
4758
+ LIMIT %d", $lastSendTime, $limit));
4759
+ $totalRows = $wpdb->get_var('SELECT FOUND_ROWS()');
4760
+
4761
+ if ($attackData) {
4762
+ $response = wp_remote_get(sprintf(WFWAF_API_URL_SEC . "waf-rules/%d.txt", $waf->getStorageEngine()->getConfig('attackDataKey')));
4763
+
4764
+ if (!is_wp_error($response)) {
4765
+ $okToSendBody = wp_remote_retrieve_body($response);
4766
+ if ($okToSendBody === 'ok') {
4767
+ // Build JSON to send
4768
+ $dataToSend = array();
4769
+ $attackDataToUpdate = array();
4770
+ foreach ($attackData as $attackDataRow) {
4771
+ $actionData = (array) wfRequestModel::unserializeActionData($attackDataRow->actionData);
4772
+ $dataToSend[] = array(
4773
+ $attackDataRow->attackLogTime,
4774
+ $attackDataRow->ctime,
4775
+ wfUtils::inet_ntop($attackDataRow->IP),
4776
+ (array_key_exists('learningMode', $actionData) ? $actionData['learningMode'] : 0),
4777
+ (array_key_exists('paramKey', $actionData) ? base64_encode($actionData['paramKey']) : false),
4778
+ (array_key_exists('paramValue', $actionData) ? base64_encode($actionData['paramValue']) : false),
4779
+ (array_key_exists('failedRules', $actionData) ? $actionData['failedRules'] : ''),
4780
+ strpos($attackDataRow->URL, 'https') === 0 ? 1 : 0,
4781
+ (array_key_exists('fullRequest', $actionData) ? $actionData['fullRequest'] : ''),
4782
+ );
4783
+ if (array_key_exists('fullRequest', $actionData)) {
4784
+ unset($actionData['fullRequest']);
4785
+ $attackDataToUpdate[$attackDataRow->id] = array(
4786
+ 'actionData' => wfRequestModel::serializeActionData($actionData),
4787
+ );
4788
+ }
4789
+ if ($attackDataRow->attackLogTime > $lastSendTime) {
4790
+ $lastSendTime = $attackDataRow->attackLogTime;
4791
+ }
4792
+ }
4793
+
4794
+ $response = wp_remote_post(WFWAF_API_URL_SEC . "?" . http_build_query(array(
4795
+ 'action' => 'send_waf_attack_data',
4796
+ 'k' => $waf->getStorageEngine()->getConfig('apiKey'),
4797
+ 's' => $waf->getStorageEngine()->getConfig('siteURL') ? $waf->getStorageEngine()->getConfig('siteURL') :
4798
+ sprintf('%s://%s/', $waf->getRequest()->getProtocol(), rawurlencode($waf->getRequest()->getHost())),
4799
+ )),
4800
+ array(
4801
+ 'body' => json_encode($dataToSend),
4802
+ 'headers' => array(
4803
+ 'Content-Type' => 'application/json',
4804
+ ),
4805
+ 'timeout' => 30,
4806
+ ));
4807
+
4808
+ if (!is_wp_error($response) && ($body = wp_remote_retrieve_body($response))) {
4809
+ $jsonData = json_decode($body, true);
4810
+ if (is_array($jsonData) && array_key_exists('success', $jsonData)) {
4811
+ // Successfully sent data, remove the full request from the table to reduce storage size
4812
+ foreach ($attackDataToUpdate as $hitID => $dataToUpdate) {
4813
+ $wpdb->update($wpdb->base_prefix . 'wfHits', $dataToUpdate, array(
4814
+ 'id' => $hitID,
4815
+ ));
4816
+ }
4817
+ wfConfig::set('lastAttackDataSendTime', $lastSendTime);
4818
+ if ($totalRows > $limit) {
4819
+ self::scheduleSendAttackData();
4820
+ }
4821
+ }
4822
+ }
4823
+ } else if (is_string($okToSendBody) && preg_match('/next check in: ([0-9]+)/', $okToSendBody, $matches)) {
4824
+ self::scheduleSendAttackData(time() + $matches[1]);
4825
+ }
4826
+
4827
+ // Could be that the server is down, so hold off on sending data for a little while.
4828
+ } else {
4829
+ self::scheduleSendAttackData(time() + 7200);
4830
+ }
4831
+ }
4832
+
4833
+ self::trimWfHits();
4834
+ }
4835
+
4836
+ public static function syncAttackData() {
4837
+ global $wpdb;
4838
+ $waf = wfWAF::getInstance();
4839
+ $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$wpdb->base_prefix}wfHits");
4840
+ if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
4841
+ $attackData = $waf->getStorageEngine()->getNewestAttackDataArray($lastAttackMicroseconds);
4842
+ if ($attackData) {
4843
+ foreach ($attackData as $request) {
4844
+ if (count($request) !== 9) {
4845
+ continue;
4846
+ }
4847
+
4848
+ list($logTimeMicroseconds, $requestTime, $ip, $learningMode, $paramKey, $paramValue, $failedRules, $ssl, $requestString) = $request;
4849
+
4850
+ // Skip old entries and hits in learning mode, since they'll get picked up anyways.
4851
+ if ($logTimeMicroseconds <= $lastAttackMicroseconds || $learningMode) {
4852
+ continue;
4853
+ }
4854
+
4855
+ $hit = new wfRequestModel();
4856
+ $hit->attackLogTime = $logTimeMicroseconds;
4857
+ $hit->statusCode = 403;
4858
+ $hit->ctime = $requestTime;
4859
+ $hit->IP = wfUtils::inet_pton($ip);
4860
+
4861
+ if (preg_match('/user\-agent:(.*?)\n/i', $requestString, $matches)) {
4862
+ $hit->UA = trim($matches[1]);
4863
+ $hit->isGoogle = wfCrawl::isGoogleCrawler($hit->UA);
4864
+ }
4865
+
4866
+ if (preg_match('/Referer:(.*?)\n/i', $requestString, $matches)) {
4867
+ $hit->referer = trim($matches[1]);
4868
+ }
4869
+
4870
+ if (preg_match('/^[a-z]+\s+(.*?)\s+/i', $requestString, $uriMatches) && preg_match('/Host:(.*?)\n/i', $requestString, $hostMatches)) {
4871
+ $hit->URL = 'http' . ($ssl ? 's' : '') . '://' . trim($hostMatches[1]) . trim($uriMatches[1]);
4872
+ }
4873
+
4874
+ if (preg_match('/cookie:(.*?)\n/i', $requestString, $matches)) {
4875
+ $hit->newVisit = strpos($matches[1], 'wfvt_' . crc32(site_url())) !== false ? 1 : 0;
4876
+ $hasVerifiedHumanCookie = strpos($matches[1], 'wordfence_verifiedHuman') !== false;
4877
+ if ($hasVerifiedHumanCookie && preg_match('/wordfence_verifiedHuman=(.*?);/', $matches[1], $cookieMatches)) {
4878
+ $hit->jsRun = (int) wp_verify_nonce($cookieMatches[1], 'wordfence_verifiedHuman' . $hit->UA . $ip);
4879
+ }
4880
+
4881
+ $hasLoginCookie = strpos($matches[1], $ssl ? SECURE_AUTH_COOKIE : AUTH_COOKIE) !== false;
4882
+ if ($hasLoginCookie && preg_match('/' . ($ssl ? SECURE_AUTH_COOKIE : AUTH_COOKIE) . '=(.*?);/', $matches[1], $cookieMatches)) {
4883
+ $authCookie = rawurldecode($cookieMatches[1]);
4884
+ $authID = $ssl ? wp_validate_auth_cookie($authCookie, 'secure_auth') : wp_validate_auth_cookie($authCookie, 'auth');
4885
+ if ($authID) {
4886
+ $hit->userID = $authID;
4887
+ }
4888
+ }
4889
+ }
4890
+
4891
+ $path = '/';
4892
+ if (preg_match('/^[A-Z]+ (.*?) HTTP\\/1\\.1/', $requestString, $matches)) {
4893
+ if (($pos = strpos($matches[1], '?')) !== false) {
4894
+ $path = substr($matches[1], 0, $pos);
4895
+ } else {
4896
+ $path = $matches[1];
4897
+ }
4898
+ }
4899
+
4900
+ $hit->action = 'blocked:waf';
4901
+
4902
+ /** @var wfWAFRule $rule */
4903
+ $ruleIDs = explode('|', $failedRules);
4904
+ $actionData = array(
4905
+ 'learningMode' => $learningMode,
4906
+ 'failedRules' => $failedRules,
4907
+ 'paramKey' => $paramKey,
4908
+ 'paramValue' => $paramValue,
4909
+ 'path' => $path,
4910
+ );
4911
+ if ($ruleIDs && $ruleIDs[0]) {
4912
+ $rule = $waf->getRule($ruleIDs[0]);
4913
+ if ($rule) {
4914
+ $hit->actionDescription = $rule->getDescription();
4915
+ $actionData['category'] = $rule->getCategory();
4916
+ $actionData['ssl'] = $ssl;
4917
+ $actionData['fullRequest'] = base64_encode($requestString);
4918
+ }
4919
+ }
4920
+
4921
+ $hit->actionData = wfRequestModel::serializeActionData($actionData);
4922
+ $hit->save();
4923
+
4924
+ self::scheduleSendAttackData();
4925
+ }
4926
+ }
4927
+ $waf->getStorageEngine()->truncateAttackData();
4928
+ }
4929
+ update_site_option('wordfence_syncingAttackData', 0);
4930
+ exit;
4931
+ }
4932
+
4933
+ /**
4934
+ * This is the only hook I see to tie into WP's core update process.
4935
+ * Since we hide the readme.html to prevent the WordPress version from being discovered, it breaks the upgrade
4936
+ * process because it cannot copy the previous readme.html.
4937
+ *
4938
+ * @param string $string
4939
+ * @return string
4940
+ */
4941
+ public static function restoreReadmeForUpgrade($string) {
4942
+ static $didRun;
4943
+ if (!isset($didRun)) {
4944
+ $didRun = true;
4945
+ wfUtils::showReadme();
4946
+ register_shutdown_function('wfUtils::hideReadme');
4947
+ }
4948
+
4949
+ return $string;
4950
+ }
4951
+
4952
+ public static function wafAutoPrependNotice() {
4953
+ $url = network_admin_url('admin.php?page=WordfenceWAF&wafAction=configureAutoPrepend');
4954
+ echo '<div class="update-nag">To make your site as secure as possible, take a moment to setup the Wordfence Web
4955
+ Application Firewall: &nbsp;<a class="button button-small" href="' . esc_url($url) . '">Click here to configure.</a><br>
4956
+ <em style="font-size: 85%;">If you cannot complete the setup process,
4957
+ <a target="_blank" href="https://docs.wordfence.com/en/Web_Application_Firewall_Setup">click here for help</a>.</em>
4958
+ </div>';
4959
+ }
4960
+
4961
+ public static function wafAutoPrependVerify() {
4962
+ if (WFWAF_AUTO_PREPEND && !WFWAF_SUBDIRECTORY_INSTALL) {
4963
+ echo '<div class="updated is-dismissible"><p>The installation was successful! Your site is protected to the fullest extent!</p></div>';
4964
+ } else {
4965
+ echo '<div class="notice notice-error"><p>The changes have not yet taken effect. If you are using LiteSpeed
4966
+ as your web server or CGI/FastCGI interface, you may need to wait a few minutes for the changes to take effect since the
4967
+ configuration files are sometimes cached. You also may need to select a different server configuration in order to
4968
+ complete this step, but wait for a few minutes before trying. You can try refreshing this page. </p></div>';
4969
+ }
4970
+ }
4971
+
4972
+ public static function getWAFBootstrapPath() {
4973
+ return ABSPATH . 'wordfence-waf.php';
4974
+ }
4975
+
4976
+ public static function getWAFBootstrapContent($currentAutoPrependedFile = null) {
4977
+ $currentAutoPrepend = '';
4978
+ if ($currentAutoPrependedFile && is_file($currentAutoPrependedFile) && !WFWAF_SUBDIRECTORY_INSTALL) {
4979
+ $currentAutoPrepend = sprintf('
4980
+ // This file was the current value of auto_prepend_file during the Wordfence WAF installation (%2$s)
4981
+ if (file_exists(%1$s)) {
4982
+ include_once %1$s;
4983
+ }', var_export($currentAutoPrependedFile, true), date('r'));
4984
+ }
4985
+ return sprintf('<?php
4986
+ // Before removing this file, please verify the PHP ini setting `auto_prepend_file` does not point to this.
4987
+ %3$s
4988
+ if (file_exists(%1$s)) {
4989
+ define("WFWAF_LOG_PATH", %2$s);
4990
+ include_once %1$s;
4991
+ }
4992
+ ?>',
4993
+ var_export(WORDFENCE_PATH . 'waf/bootstrap.php', true),
4994
+ var_export(WFWAF_SUBDIRECTORY_INSTALL ? WP_CONTENT_DIR . '/wflogs/' : WFWAF_LOG_PATH, true),
4995
+ $currentAutoPrepend);
4996
+ }
4997
+
4998
+ public static function checkAndCreateBootstrap() {
4999
+ $bootstrapPath = self::getWAFBootstrapPath();
5000
+ if (!file_exists($bootstrapPath) || !filesize($bootstrapPath)) {
5001
+ @file_put_contents($bootstrapPath, self::getWAFBootstrapContent(), LOCK_EX);
5002
+ clearstatcache();
5003
+ }
5004
+ return file_exists($bootstrapPath) && filesize($bootstrapPath);
5005
+ }
5006
+
5007
+ /**
5008
+ * @return bool|string
5009
+ */
5010
+ private static function getCurrentUserRole() {
5011
+ if (current_user_can('administrator') || is_super_admin()) {
5012
+ return 'administrator';
5013
+ }
5014
+ $roles = array('editor', 'author', 'contributor', 'subscriber');
5015
+ foreach ($roles as $role) {
5016
+ if (current_user_can($role)) {
5017
+ return $role;
5018
+ }
5019
+ }
5020
+ return false;
5021
+ }
5022
+ }
5023
+
5024
+ class wfWAFAutoPrependHelper {
5025
+
5026
+ private $serverConfig;
5027
+ /**
5028
+ * @var string
5029
+ */
5030
+ private $currentAutoPrependedFile;
5031
+
5032
+ /**
5033
+ * @param string|null $serverConfig
5034
+ * @param string|null $currentAutoPrependedFile
5035
+ */
5036
+ public function __construct($serverConfig = null, $currentAutoPrependedFile = null) {
5037
+ $this->serverConfig = $serverConfig;
5038
+ $this->currentAutoPrependedFile = $currentAutoPrependedFile;
5039
+ }
5040
+
5041
+ public function getFilesNeededForBackup() {
5042
+ $backups = array();
5043
+ $htaccess = $this->getHtaccessPath();
5044
+ switch ($this->getServerConfig()) {
5045
+ case 'apache-mod_php':
5046
+ case 'apache-suphp':
5047
+ case 'litespeed':
5048
+ case 'cgi':
5049
+ if (file_exists($htaccess)) {
5050
+ $backups[] = $htaccess;
5051
+ }
5052
+ break;
5053
+ }
5054
+ if ($userIni = ini_get('user_ini.filename')) {
5055
+ $userIniPath = $this->getUserIniPath();
5056
+ switch ($this->getServerConfig()) {
5057
+ case 'cgi':
5058
+ case 'apache-suphp':
5059
+ case 'nginx':
5060
+ case 'litespeed':
5061
+ if (file_exists($userIniPath)) {
5062
+ $backups[] = $userIniPath;
5063
+ }
5064
+ break;
5065
+ }
5066
+ }
5067
+ return $backups;
5068
+ }
5069
+
5070
+ public function downloadBackups($index = 0) {
5071
+ $backups = $this->getFilesNeededForBackup();
5072
+ if ($backups && array_key_exists($index, $backups)) {
5073
+ $url = site_url();
5074
+ $url = preg_replace('/^https?:\/\//i', '', $url);
5075
+ $url = preg_replace('/[^a-zA-Z0-9\.]+/', '_', $url);
5076
+ $url = preg_replace('/^_+/', '', $url);
5077
+ $url = preg_replace('/_+$/', '', $url);
5078
+ header('Content-Type: application/octet-stream');
5079
+ $backupFileName = ltrim(basename($backups[$index]), '.');
5080
+ header('Content-Disposition: attachment; filename="' . $backupFileName . '_Backup_for_' . $url . '.txt"');
5081
+ readfile($backups[$index]);
5082
+ die();
5083
+ }
5084
+ }
5085
+
5086
+ /**
5087
+ * @return mixed
5088
+ */
5089
+ public function getServerConfig() {
5090
+ return $this->serverConfig;
5091
+ }
5092
+
5093
+ /**
5094
+ * @param mixed $serverConfig
5095
+ */
5096
+ public function setServerConfig($serverConfig) {
5097
+ $this->serverConfig = $serverConfig;
5098
+ }
5099
+
5100
+ /**
5101
+ * @param WP_Filesystem_Base $wp_filesystem
5102
+ * @throws wfWAFAutoPrependHelperException
5103
+ */
5104
+ public function performInstallation($wp_filesystem) {
5105
+ $bootstrapPath = wordfence::getWAFBootstrapPath();
5106
+ if (!$wp_filesystem->put_contents($bootstrapPath, wordfence::getWAFBootstrapContent($this->currentAutoPrependedFile))) {
5107
+ throw new wfWAFAutoPrependHelperException('We were unable to create the <code>wordfence-waf.php</code> file
5108
+ in the root of the WordPress installation. It\'s possible WordPress cannot write to the <code>wordfence-waf.php</code>
5109
+ file because of file permissions. Please verify the permissions are correct and retry the installation.');
5110
+ }
5111
+
5112
+ $serverConfig = $this->getServerConfig();
5113
+
5114
+ $htaccessPath = $this->getHtaccessPath();
5115
+ $homePath = dirname($htaccessPath);
5116
+
5117
+ $userIniPath = $this->getUserIniPath();
5118
+ $userIni = ini_get('user_ini.filename');
5119
+
5120
+ $userIniHtaccessDirectives = '';
5121
+ if ($userIni) {
5122
+ $userIniHtaccessDirectives = sprintf('<Files "%s">
5123
+ <IfModule mod_authz_core.c>
5124
+ Require all denied
5125
+ </IfModule>
5126
+ <IfModule !mod_authz_core.c>
5127
+ Order deny,allow
5128
+ Deny from all
5129
+ </IfModule>
5130
+ </Files>
5131
+ ', addcslashes($userIni, '"'));
5132
+ }
5133
+
5134
+
5135
+ // .htaccess configuration
5136
+ switch ($serverConfig) {
5137
+ case 'apache-mod_php':
5138
+ $autoPrependDirective = sprintf("# Wordfence WAF
5139
+ <IfModule mod_php%d.c>
5140
+ php_value auto_prepend_file '%s'
5141
+ </IfModule>
5142
+ $userIniHtaccessDirectives
5143
+ # END Wordfence WAF
5144
+ ", PHP_MAJOR_VERSION, addcslashes($bootstrapPath, "'"));
5145
+ break;
5146
+
5147
+ case 'litespeed':
5148
+ $autoPrependDirective = sprintf("# Wordfence WAF
5149
+ <IfModule LiteSpeed>
5150
+ php_value auto_prepend_file '%s'
5151
+ </IfModule>
5152
+ $userIniHtaccessDirectives
5153
+ # END Wordfence WAF
5154
+ ", addcslashes($bootstrapPath, "'"));
5155
+ break;
5156
+
5157
+ case 'apache-suphp':
5158
+ $autoPrependDirective = sprintf("# Wordfence WAF
5159
+ <IfModule mod_suphp.c>
5160
+ suPHP_ConfigPath '%s'
5161
+ </IfModule>
5162
+ $userIniHtaccessDirectives
5163
+ # END Wordfence WAF
5164
+ ", addcslashes($homePath, "'"));
5165
+ break;
5166
+
5167
+ case 'cgi':
5168
+ if ($userIniHtaccessDirectives) {
5169
+ $autoPrependDirective = sprintf("# Wordfence WAF
5170
+ $userIniHtaccessDirectives
5171
+ # END Wordfence WAF
5172
+ ", addcslashes($homePath, "'"));
5173
+ }
5174
+ break;
5175
+
5176
+ }
5177
+
5178
+ if (!empty($autoPrependDirective)) {
5179
+ // Modify .htaccess
5180
+ $htaccessContent = $wp_filesystem->get_contents($htaccessPath);
5181
+
5182
+ if ($htaccessContent) {
5183
+ $regex = '/# Wordfence WAF.*?# END Wordfence WAF/is';
5184
+ if (preg_match($regex, $htaccessContent, $matches)) {
5185
+ $htaccessContent = preg_replace($regex, $autoPrependDirective, $htaccessContent);
5186
+ } else {
5187
+ $htaccessContent .= "\n\n" . $autoPrependDirective;
5188
+ }
5189
+ } else {
5190
+ $htaccessContent = $autoPrependDirective;
5191
+ }
5192
+
5193
+ if (!$wp_filesystem->put_contents($htaccessPath, $htaccessContent)) {
5194
+ throw new wfWAFAutoPrependHelperException('We were unable to make changes to the .htaccess file. It\'s
5195
+ possible WordPress cannot write to the .htaccess file because of file permissions, which may have been
5196
+ set by another security plugin, or you may have set them manually. Please verify the permissions allow
5197
+ the web server to write to the file, and retry the installation.');
5198
+ }
5199
+ if ($serverConfig == 'litespeed') {
5200
+ // sleep(2);
5201
+ $wp_filesystem->touch($htaccessPath);
5202
+ }
5203
+
5204
+ }
5205
+ if ($userIni) {
5206
+ // .user.ini configuration
5207
+ switch ($serverConfig) {
5208
+ case 'cgi':
5209
+ case 'nginx':
5210
+ case 'apache-suphp':
5211
+ case 'litespeed':
5212
+ $autoPrependIni = sprintf("; Wordfence WAF
5213
+ auto_prepend_file = '%s'
5214
+ ; END Wordfence WAF
5215
+ ", addcslashes($bootstrapPath, "'"));
5216
+
5217
+ break;
5218
+ }
5219
+
5220
+ if (!empty($autoPrependIni)) {
5221
+
5222
+ // Modify .user.ini
5223
+ $userIniContent = $wp_filesystem->get_contents($userIniPath);
5224
+ if (is_string($userIniContent)) {
5225
+ $userIniContent = str_replace('auto_prepend_file', ';auto_prepend_file', $userIniContent);
5226
+ $regex = '/; Wordfence WAF.*?; END Wordfence WAF/is';
5227
+ if (preg_match($regex, $userIniContent, $matches)) {
5228
+ $userIniContent = preg_replace($regex, $autoPrependIni, $userIniContent);
5229
+ } else {
5230
+ $userIniContent .= "\n\n" . $autoPrependIni;
5231
+ }
5232
+ } else {
5233
+ $userIniContent = $autoPrependIni;
5234
+ }
5235
+
5236
+ if (!$wp_filesystem->put_contents($userIniPath, $userIniContent)) {
5237
+ throw new wfWAFAutoPrependHelperException(sprintf('We were unable to make changes to the %1$s file.
5238
+ It\'s possible WordPress cannot write to the %1$s file because of file permissions.
5239
+ Please verify the permissions are correct and retry the installation.', basename($userIniPath)));
5240
+ }
5241
+ }
5242
+ }
5243
+ }
5244
+
5245
+ public function getHtaccessPath() {
5246
+ return get_home_path() . '.htaccess';
5247
+ }
5248
+
5249
+ public function getUserIniPath() {
5250
+ $userIni = ini_get('user_ini.filename');
5251
+ if ($userIni) {
5252
+ return get_home_path() . $userIni;
5253
+ }
5254
+ return false;
5255
+ }
5256
+
5257
+ public function uninstall() {
5258
+ /** @var WP_Filesystem_Base $wp_filesystem */
5259
+ global $wp_filesystem;
5260
+
5261
+ $htaccessPath = $this->getHtaccessPath();
5262
+ $userIniPath = $this->getUserIniPath();
5263
+
5264
+ $adminURL = admin_url('/');
5265
+ $allow_relaxed_file_ownership = true;
5266
+ $homePath = dirname($htaccessPath);
5267
+
5268
+ ob_start();
5269
+ if (false === ($credentials = request_filesystem_credentials($adminURL, '', false, $homePath,
5270
+ array('version', 'locale'), $allow_relaxed_file_ownership))
5271
+ ) {
5272
+ ob_end_clean();
5273
+ return false;
5274
+ }
5275
+
5276
+ if (!WP_Filesystem($credentials, $homePath, $allow_relaxed_file_ownership)) {
5277
+ // Failed to connect, Error and request again
5278
+ request_filesystem_credentials($adminURL, '', true, ABSPATH, array('version', 'locale'),
5279
+ $allow_relaxed_file_ownership);
5280
+ ob_end_clean();
5281
+ return false;
5282
+ }
5283
+
5284
+ if ($wp_filesystem->errors->get_error_code()) {
5285
+ ob_end_clean();
5286
+ return false;
5287
+ }
5288
+ ob_end_clean();
5289
+
5290
+ if ($wp_filesystem->is_file($htaccessPath)) {
5291
+ $htaccessContent = $wp_filesystem->get_contents($htaccessPath);
5292
+ $regex = '/# Wordfence WAF.*?# END Wordfence WAF/is';
5293
+ if (preg_match($regex, $htaccessContent, $matches)) {
5294
+ $htaccessContent = preg_replace($regex, '', $htaccessContent);
5295
+ if (!$wp_filesystem->put_contents($htaccessPath, $htaccessContent)) {
5296
+ return false;
5297
+ }
5298
+ }
5299
+ }
5300
+
5301
+ if ($wp_filesystem->is_file($userIniPath)) {
5302
+ $userIniContent = $wp_filesystem->get_contents($userIniPath);
5303
+ $regex = '/; Wordfence WAF.*?; END Wordfence WAF/is';
5304
+ if (preg_match($regex, $userIniContent, $matches)) {
5305
+ $userIniContent = preg_replace($regex, '', $userIniContent);
5306
+ if (!$wp_filesystem->put_contents($userIniPath, $userIniContent)) {
5307
+ return false;
5308
+ }
5309
+ }
5310
+ }
5311
+
5312
+ $bootstrapPath = wordfence::getWAFBootstrapPath();
5313
+ if ($wp_filesystem->is_file($bootstrapPath)) {
5314
+ $wp_filesystem->delete($bootstrapPath);
5315
+ }
5316
+ return true;
5317
+ }
5318
  }
5319
+
5320
+ class wfWAFAutoPrependHelperException extends Exception {
5321
+ }
5322
+
5323
  ?>
lib/wordfenceConstants.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
- define('WORDFENCE_API_VERSION', '2.20');
3
  define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
 
 
5
  define('WORDFENCE_HACKATTEMPT_URL', 'http://noc3.wordfence.com:9050/');
6
  define('WORDFENCE_MAX_SCAN_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
7
  define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
1
  <?php
2
+ define('WORDFENCE_API_VERSION', '2.22');
3
  define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
+ define('WORDFENCE_API_URL_BASE_SEC', WORDFENCE_API_URL_SEC . '/v' . WORDFENCE_API_VERSION . '/');
6
+ define('WORDFENCE_API_URL_BASE_NONSEC', WORDFENCE_API_URL_NONSEC . '/v' . WORDFENCE_API_VERSION . '/');
7
  define('WORDFENCE_HACKATTEMPT_URL', 'http://noc3.wordfence.com:9050/');
8
  define('WORDFENCE_MAX_SCAN_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
9
  define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
lib/wordfenceHash.php CHANGED
@@ -39,7 +39,8 @@ class wordfenceHash {
39
  $this->striplen = $striplen;
40
  $this->path = $path;
41
  $this->only = $only;
42
-
 
43
  $this->startTime = microtime(true);
44
 
45
  if(wfConfig::get('scansEnabled_core')){
@@ -58,20 +59,14 @@ class wordfenceHash {
58
 
59
  //Doing a delete for now. Later we can optimize this to only scan modified files.
60
  //$this->db->queryWrite("update " . $this->db->prefix() . "wfFileMods set oldMD5 = newMD5");
61
- $this->db->queryWrite("delete from " . $this->db->prefix() . "wfFileMods");
62
- $fetchCoreHashesStatus = wordfence::statusStart("Fetching core, theme and plugin file signatures from Wordfence");
63
- $dataArr = $engine->api->binCall('get_known_files', json_encode(array(
64
- 'plugins' => $plugins,
65
- 'themes' => $themes
66
- )) );
67
- if($dataArr['code'] != 200){
68
- wordfence::statusEndErr();
69
- throw new Exception("Got error response from Wordfence servers: " . $dataArr['code']);
70
- }
71
- $this->knownFiles = @json_decode($dataArr['data'], true);
72
- if(! is_array($this->knownFiles)){
73
  wordfence::statusEndErr();
74
- throw new Exception("Invalid response from Wordfence servers.");
75
  }
76
  wordfence::statusEnd($fetchCoreHashesStatus, false, true);
77
  if($this->malwareEnabled){
39
  $this->striplen = $striplen;
40
  $this->path = $path;
41
  $this->only = $only;
42
+ $this->engine = $engine;
43
+
44
  $this->startTime = microtime(true);
45
 
46
  if(wfConfig::get('scansEnabled_core')){
59
 
60
  //Doing a delete for now. Later we can optimize this to only scan modified files.
61
  //$this->db->queryWrite("update " . $this->db->prefix() . "wfFileMods set oldMD5 = newMD5");
62
+ $this->db->truncate($this->db->prefix() . "wfFileMods");
63
+ $fetchCoreHashesStatus = wordfence::statusStart("Fetching core, theme and plugin file signatures from Wordfence");
64
+ try {
65
+ $this->knownFiles = $this->engine->getKnownFilesLoader()
66
+ ->getKnownFiles();
67
+ } catch (wfScanKnownFilesException $e) {
 
 
 
 
 
 
68
  wordfence::statusEndErr();
69
+ throw $e;
70
  }
71
  wordfence::statusEnd($fetchCoreHashesStatus, false, true);
72
  if($this->malwareEnabled){
lib/wordfenceScanner.php CHANGED
@@ -15,8 +15,12 @@ class wordfenceScanner {
15
  protected $patterns = "";
16
  protected $api = false;
17
  protected static $excludePattern = NULL;
 
 
 
18
  public function __sleep(){
19
- return array('path', 'results', 'errorMsg', 'apiKey', 'wordpressVersion', 'urlHoover', 'totalFilesScanned', 'startTime', 'lastStatusTime', 'patterns');
 
20
  }
21
  public function __wakeup(){
22
  }
@@ -38,18 +42,48 @@ class wordfenceScanner {
38
  }
39
 
40
  /**
 
41
  * @todo add caching to this.
42
  * @throws Exception
43
  */
44
  protected function setupSigs() {
45
  $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
46
  $sigData = $this->api->call('get_patterns', array(), array());
47
- //For testing, comment out above two, include server sig file and get local sigs
48
- //$sigData = wfSigs::getSigData();
49
- if(! (is_array($sigData) && isset($sigData['sigPattern'])) ){
50
  throw new Exception("Wordfence could not get the attack signature patterns from the scanning server.");
51
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  $this->patterns = $sigData;
 
 
 
53
  }
54
 
55
  /**
@@ -78,7 +112,13 @@ class wordfenceScanner {
78
  return self::$excludePattern;
79
  }
80
 
 
 
 
 
81
  public function scan($forkObj){
 
 
82
  if(! $this->startTime){
83
  $this->startTime = microtime(true);
84
  }
@@ -154,6 +194,9 @@ class wordfenceScanner {
154
  continue;
155
  }
156
  $totalRead = 0;
 
 
 
157
  while(! feof($fh)){
158
  $data = fread($fh, 1 * 1024 * 1024); //read 1 megs max per chunk
159
  $totalRead += strlen($data);
@@ -170,52 +213,34 @@ class wordfenceScanner {
170
  'ignoreC' => $fileSum,
171
  'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
172
  'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it.",
173
- 'data' => array(
174
- 'file' => $file,
175
- 'canDiff' => false,
176
- 'canFix' => false,
177
- 'canDelete' => true
178
- )
179
- ));
180
- break;
181
- }
182
- } else if(strpos($file, 'lib/wordfenceScanner.php') === false && preg_match($this->patterns['sigPattern'], $data, $matches)){
183
- if(! $this->isSafeFile($this->path . $file)){
184
- $this->addResult(array(
185
- 'type' => 'file',
186
- 'severity' => 1,
187
- 'ignoreP' => $this->path . $file,
188
- 'ignoreC' => $fileSum,
189
- 'shortMsg' => "File appears to be malicious: " . esc_html($file),
190
- 'longMsg' => "This file appears to be installed by a hacker to perform malicious activity. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\">\"" . esc_html($matches[1]) . "\"</strong>.",
191
- 'data' => array(
192
- 'file' => $file,
193
- 'canDiff' => false,
194
- 'canFix' => false,
195
- 'canDelete' => true
196
- )));
197
- break;
198
- }
199
-
200
- }
201
- if(preg_match($this->patterns['pat2'], $data)){
202
- if(! $this->isSafeFile($this->path . $file)){
203
- $this->addResult(array(
204
- 'type' => 'file',
205
- 'severity' => 1,
206
- 'ignoreP' => $this->path . $file,
207
- 'ignoreC' => $fileSum,
208
- 'shortMsg' => "This file may contain malicious executable code: " . esc_html($this->path . $file),
209
- 'longMsg' => "This file is a PHP executable file and contains an " . esc_html($this->patterns['word1']) . " function and " . esc_html($this->patterns['word2']) . " decoding function on the same line. This is a common technique used by hackers to hide and execute code. If you know about this file you can choose to ignore it to exclude it from future scans.",
210
- 'data' => array(
211
  'file' => $file,
212
- 'canDiff' => false,
213
- 'canFix' => false,
214
- 'canDelete' => true
215
- )
216
  ));
217
  break;
218
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  }
220
  if(wfConfig::get('scansEnabled_highSense')){
221
  $badStringFound = false;
@@ -236,13 +261,10 @@ class wordfenceScanner {
236
  'ignoreC' => $fileSum,
237
  'shortMsg' => "This file may contain malicious executable code: " . esc_html($this->path . $file),
238
  'longMsg' => "This file is a PHP executable file and contains the word 'eval' (without quotes) and the word '" . esc_html($badStringFound) . "' (without quotes). The eval() function along with an encoding function like the one mentioned are commonly used by hackers to hide their code. If you know about this file you can choose to ignore it to exclude it from future scans.",
239
- 'data' => array(
240
  'file' => $file,
241
- 'canDiff' => false,
242
- 'canFix' => false,
243
- 'canDelete' => true
244
- )
245
- ));
246
  break;
247
  }
248
  }
@@ -278,6 +300,8 @@ class wordfenceScanner {
278
  }
279
  $this->urlHoover->cleanup();
280
  foreach($hooverResults as $file => $hresults){
 
 
281
  foreach($hresults as $result){
282
  if(preg_match('/wfBrowscapCache\.php$/', $file)){
283
  continue;
@@ -291,14 +315,11 @@ class wordfenceScanner {
291
  'ignoreC' => md5_file($this->path . $file),
292
  'shortMsg' => "File contains suspected malware URL: " . esc_html($this->path . $file),
293
  'longMsg' => "This file contains a suspected malware URL listed on Google's list of malware sites. Wordfence decodes " . esc_html($this->patterns['word3']) . " when scanning files so the URL may not be visible if you view this file. The URL is: " . esc_html($result['URL']) . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.",
294
- 'data' => array(
295
  'file' => $file,
296
  'badURL' => $result['URL'],
297
- 'canDiff' => false,
298
- 'canFix' => false,
299
- 'canDelete' => true,
300
  'gsb' => 'goog-malware-shavar'
301
- )
302
  ));
303
  }
304
  } else if($result['badList'] == 'googpub-phish-shavar'){
@@ -310,14 +331,11 @@ class wordfenceScanner {
310
  'ignoreC' => md5_file($this->path . $file),
311
  'shortMsg' => "File contains suspected phishing URL: " . esc_html($this->path . $file),
312
  'longMsg' => "This file contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . esc_html($result['URL']),
313
- 'data' => array(
314
  'file' => $file,
315
  'badURL' => $result['URL'],
316
- 'canDiff' => false,
317
- 'canFix' => false,
318
- 'canDelete' => true,
319
  'gsb' => 'googpub-phish-shavar'
320
- )
321
  ));
322
  }
323
  }
@@ -356,48 +374,45 @@ class wordfenceScanner {
356
  }
357
  return false;
358
  }
359
- }
360
 
361
- class wordfenceDBScanner extends wordfenceScanner {
362
-
363
- // protected $patterns = '/QGV4dHJhY3QoJF9SRVFVRVNUKTs=/i';
364
-
365
- public function scan($forkObj) {
366
- /** @var wpdb */
367
- global $wpdb;
368
- if (!$this->startTime) {
369
- $this->startTime = microtime(true);
370
- }
371
- if (!$this->lastStatusTime) {
372
- $this->lastStatusTime = microtime(true);
373
- }
374
- $db = new wfDB();
375
 
376
- $blogsToScan = wfScanEngine::getBlogsToScan('options');
377
- foreach ($blogsToScan as $blog) {
378
- // Check the options table for known shells
379
- $results = $db->querySelect("SELECT * FROM {$blog['table']} WHERE option_value REGEXP %s", trim(rtrim($this->patterns['dbSigPattern'], 'imsxeADSUXJu'), '/'));
 
 
 
 
380
 
381
- foreach ($results as $row) {
382
- preg_match($this->patterns['dbSigPattern'], $row['option_value'], $matches);
383
- $this->addResult(array(
384
- 'type' => 'database',
385
- 'severity' => 1,
386
- 'ignoreP' => "{$db->prefix()}option.{$row['option_name']}",
387
- 'ignoreC' => md5($row['option_value']),
388
- 'shortMsg' => "This option may contain malicious executable code: " . esc_html($row['option_name']),
389
- 'longMsg' => "This option appears to be inserted by a hacker to perform malicious activity. If you know about this option you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\">\"" . esc_html($matches[1]). "\"</strong>.",
390
- 'data' => array(
391
- 'option_name' => $row['option_name'],
392
- 'site_id' => $blog['blog_id'],
393
- 'canDelete' => true,
394
- ),
395
  ));
396
  }
397
  }
398
 
399
- return $this->results;
 
 
 
 
400
  }
401
  }
402
 
 
403
  ?>
15
  protected $patterns = "";
16
  protected $api = false;
17
  protected static $excludePattern = NULL;
18
+ /** @var wfScanEngine */
19
+ protected $scanEngine;
20
+
21
  public function __sleep(){
22
+ return array('path', 'results', 'errorMsg', 'apiKey', 'wordpressVersion', 'urlHoover', 'totalFilesScanned',
23
+ 'startTime', 'lastStatusTime', 'patterns', 'scanEngine');
24
  }
25
  public function __wakeup(){
26
  }
42
  }
43
 
44
  /**
45
+ * Get scan regexes from noc1 and add any user defined regexes, including descriptions, ID's and time added.
46
  * @todo add caching to this.
47
  * @throws Exception
48
  */
49
  protected function setupSigs() {
50
  $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
51
  $sigData = $this->api->call('get_patterns', array(), array());
52
+ if(! (is_array($sigData) && isset($sigData['rules'])) ){
 
 
53
  throw new Exception("Wordfence could not get the attack signature patterns from the scanning server.");
54
  }
55
+
56
+ if (is_array($sigData['rules'])) {
57
+ $finalPattern = '/';
58
+ foreach ($sigData['rules'] as $signatureRow) {
59
+ list($id, $timeAdded, $pattern, $description) = $signatureRow;
60
+ $finalPattern .= ($pattern . '|');
61
+ }
62
+ $finalPattern = substr($finalPattern, 0, -1) . '/i';
63
+ if(@preg_match($finalPattern, null) === false){
64
+ throw new Exception("The regex Wordfence received from it's servers is invalid. The pattern is: " . $finalPattern);
65
+ }
66
+ }
67
+
68
+ $extra = wfConfig::get('scan_include_extra');
69
+ if (!empty($extra)) {
70
+ $regexs = explode("\n", $extra);
71
+ $id = 1000001;
72
+ foreach($regexs as $r){
73
+ $r = rtrim($r, "\r");
74
+ try {
75
+ preg_match('/' . $r . '/i', "");
76
+ } catch(Exception $e){
77
+ throw new Exception("The following user defined scan pattern has an error: $r");
78
+ }
79
+ $sigData['rules'][] = array($id++, time(), $r, "User defined scan pattern");
80
+ }
81
+ }
82
+
83
  $this->patterns = $sigData;
84
+ if (isset($this->patterns['signatureUpdateTime'])) {
85
+ wfConfig::set('signatureUpdateTime', $this->patterns['signatureUpdateTime']);
86
+ }
87
  }
88
 
89
  /**
112
  return self::$excludePattern;
113
  }
114
 
115
+ /**
116
+ * @param wfScanEngine $forkObj
117
+ * @return array
118
+ */
119
  public function scan($forkObj){
120
+ $this->scanEngine = $forkObj;
121
+ $loader = $this->scanEngine->getKnownFilesLoader();
122
  if(! $this->startTime){
123
  $this->startTime = microtime(true);
124
  }
194
  continue;
195
  }
196
  $totalRead = 0;
197
+
198
+ $dataForFile = $this->dataForFile($file);
199
+
200
  while(! feof($fh)){
201
  $data = fread($fh, 1 * 1024 * 1024); //read 1 megs max per chunk
202
  $totalRead += strlen($data);
213
  'ignoreC' => $fileSum,
214
  'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
215
  'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it.",
216
+ 'data' => array_merge(array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  'file' => $file,
218
+ ), $dataForFile),
 
 
 
219
  ));
220
  break;
221
  }
222
+ } else if(strpos($file, 'lib/wordfenceScanner.php') === false){ // && preg_match($this->patterns['sigPattern'], $data, $matches)){
223
+ $regexMatched = false;
224
+ foreach($this->patterns['rules'] as $rule){
225
+ if(preg_match('/(' . $rule[2] . ')/i', $data, $matches)){
226
+ if(! $this->isSafeFile($this->path . $file)){
227
+ $this->addResult(array(
228
+ 'type' => 'file',
229
+ 'severity' => 1,
230
+ 'ignoreP' => $this->path . $file,
231
+ 'ignoreC' => $fileSum,
232
+ 'shortMsg' => "File appears to be malicious: " . esc_html($file),
233
+ 'longMsg' => "This file appears to be installed by a hacker to perform malicious activity. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\">\"" . esc_html((strlen($matches[1]) > 200 ? substr($matches[1], 0, 200) . '...' : $matches[1])) . "\"</strong>. The infection type is: <strong>" . esc_html($rule[3]) . '</strong>',
234
+ 'data' => array_merge(array(
235
+ 'file' => $file,
236
+ ), $dataForFile),
237
+ ));
238
+ $regexMatched = true;
239
+ break;
240
+ }
241
+ }
242
+ }
243
+ if($regexMatched){ break; }
244
  }
245
  if(wfConfig::get('scansEnabled_highSense')){
246
  $badStringFound = false;
261
  'ignoreC' => $fileSum,
262
  'shortMsg' => "This file may contain malicious executable code: " . esc_html($this->path . $file),
263
  'longMsg' => "This file is a PHP executable file and contains the word 'eval' (without quotes) and the word '" . esc_html($badStringFound) . "' (without quotes). The eval() function along with an encoding function like the one mentioned are commonly used by hackers to hide their code. If you know about this file you can choose to ignore it to exclude it from future scans.",
264
+ 'data' => array_merge(array(
265
  'file' => $file,
266
+ ), $dataForFile),
267
+ ));
 
 
 
268
  break;
269
  }
270
  }
300
  }
301
  $this->urlHoover->cleanup();
302
  foreach($hooverResults as $file => $hresults){
303
+ $dataForFile = $this->dataForFile($file);
304
+
305
  foreach($hresults as $result){
306
  if(preg_match('/wfBrowscapCache\.php$/', $file)){
307
  continue;
315
  'ignoreC' => md5_file($this->path . $file),
316
  'shortMsg' => "File contains suspected malware URL: " . esc_html($this->path . $file),
317
  'longMsg' => "This file contains a suspected malware URL listed on Google's list of malware sites. Wordfence decodes " . esc_html($this->patterns['word3']) . " when scanning files so the URL may not be visible if you view this file. The URL is: " . esc_html($result['URL']) . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.",
318
+ 'data' => array_merge(array(
319
  'file' => $file,
320
  'badURL' => $result['URL'],
 
 
 
321
  'gsb' => 'goog-malware-shavar'
322
+ ), $dataForFile),
323
  ));
324
  }
325
  } else if($result['badList'] == 'googpub-phish-shavar'){
331
  'ignoreC' => md5_file($this->path . $file),
332
  'shortMsg' => "File contains suspected phishing URL: " . esc_html($this->path . $file),
333
  'longMsg' => "This file contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . esc_html($result['URL']),
334
+ 'data' => array_merge(array(
335
  'file' => $file,
336
  'badURL' => $result['URL'],
 
 
 
337
  'gsb' => 'googpub-phish-shavar'
338
+ ), $dataForFile),
339
  ));
340
  }
341
  }
374
  }
375
  return false;
376
  }
 
377
 
378
+ /**
379
+ * @param string $file
380
+ * @return array
381
+ */
382
+ private function dataForFile($file) {
383
+ $loader = $this->scanEngine->getKnownFilesLoader();
384
+ $data = array();
385
+ if ($isKnownFile = $loader->isKnownFile($file)) {
386
+ if ($loader->isKnownCoreFile($file)) {
387
+ $data['cType'] = 'core';
 
 
 
 
388
 
389
+ } else if ($loader->isKnownPluginFile($file)) {
390
+ $data['cType'] = 'plugin';
391
+ list($itemName, $itemVersion, $cKey) = $loader->getKnownPluginData($file);
392
+ $data = array_merge($data, array(
393
+ 'cName' => $itemName,
394
+ 'cVersion' => $itemVersion,
395
+ 'cKey' => $cKey
396
+ ));
397
 
398
+ } else if ($loader->isKnownThemeFile($file)) {
399
+ $data['cType'] = 'theme';
400
+ list($itemName, $itemVersion, $cKey) = $loader->getKnownThemeData($file);
401
+ $data = array_merge($data, array(
402
+ 'cName' => $itemName,
403
+ 'cVersion' => $itemVersion,
404
+ 'cKey' => $cKey
 
 
 
 
 
 
 
405
  ));
406
  }
407
  }
408
 
409
+ $data['canDiff'] = $isKnownFile;
410
+ $data['canFix'] = $isKnownFile;
411
+ $data['canDelete'] = !$isKnownFile;
412
+
413
+ return $data;
414
  }
415
  }
416
 
417
+
418
  ?>
readme.txt CHANGED
@@ -1,15 +1,15 @@
1
  === Wordfence Security ===
2
  Contributors: mmaunder
3
- Tags: wordpress, security, 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, 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.4.1
6
- Stable tag: 6.0.25
7
 
8
  The Wordfence WordPress security plugin provides free enterprise-class WordPress security, protecting your website from hacks and malware.
9
  == Description ==
10
  = THE MOST DOWNLOADED WORDPRESS SECURITY PLUGIN =
11
 
12
- Wordfence starts by checking if your site is already infected. We do a deep server-side scan of your source code comparing it to the Official WordPress repository for core, themes and plugins. Then Wordfence secures your site and makes it up to 50 times faster.
13
 
14
  Wordfence Security is 100% free and open source. We also offer a Premium API key that gives you Premium Support, Country Blocking, Scheduled Scans, Password Auditing and we even check if your website IP address is being used to Spamvertize. [Click here to sign-up for Wordfence Premium now](http://www.wordfence.com/?utm_source=repo&utm_medium=web&utm_campaign=pluginDescCTA) or simply install Wordfence free and start protecting your website.
15
 
@@ -23,6 +23,11 @@ Wordfence Security is now Multi-Site compatible and includes Cellphone Sign-in w
23
 
24
  = WORDFENCE WORDPRESS SECURITY FEATURES =
25
 
 
 
 
 
 
26
  = Blocking Features =
27
  * Real-time blocking of known attackers. If another site using Wordfence is attacked and blocks the attacker, your site is automatically protected.
28
  * Block entire malicious networks. Includes advanced IP and Domain WHOIS to report malicious IP's or networks and block entire networks using the firewall. Report security threats to network owner.
@@ -190,6 +195,14 @@ Designed for every skill level, [The WordPress Security Learning Center](https:/
190
 
191
  == Changelog ==
192
 
 
 
 
 
 
 
 
 
193
  = 6.0.25 =
194
  * Improvement: Added help callout for compromised sites.
195
  * Improvement: Updated local GeoIP database.
1
  === Wordfence Security ===
2
  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.1
7
 
8
  The Wordfence WordPress security plugin provides free enterprise-class WordPress security, protecting your website from hacks and malware.
9
  == Description ==
10
  = THE MOST DOWNLOADED WORDPRESS SECURITY PLUGIN =
11
 
12
+ Wordfence provides the best protection available for your website. Powered by the constantly updated Threat Defense Feed, our Web Application Firewall stops you from getting hacked. Wordfence Scan leverages the same proprietary feed, alerting you quickly in the event your site is compromised. Our Live Traffic view gives you real-time visibility into traffic and hack attempts on your website. A deep set of addtional tools round out the most complete WordPress security solution available.
13
 
14
  Wordfence Security is 100% free and open source. We also offer a Premium API key that gives you Premium Support, Country Blocking, Scheduled Scans, Password Auditing and we even check if your website IP address is being used to Spamvertize. [Click here to sign-up for Wordfence Premium now](http://www.wordfence.com/?utm_source=repo&utm_medium=web&utm_campaign=pluginDescCTA) or simply install Wordfence free and start protecting your website.
15
 
23
 
24
  = WORDFENCE WORDPRESS SECURITY FEATURES =
25
 
26
+ = WordPress Firewall =
27
+ * Web Application Firewall stops you from getting hacked by identifying malicious traffic, blocking attackers before they can access your website.
28
+ * Threat Defense Feed automatically updates firewall rules that protect you from the latest threats. Premium members receive the real-time version.
29
+ * Block common security threats like fake Googlebots, malicious scans from hackers and botnets.
30
+
31
  = Blocking Features =
32
  * Real-time blocking of known attackers. If another site using Wordfence is attacked and blocks the attacker, your site is automatically protected.
33
  * Block entire malicious networks. Includes advanced IP and Domain WHOIS to report malicious IP's or networks and block entire networks using the firewall. Report security threats to network owner.
195
 
196
  == Changelog ==
197
 
198
+ = 6.1.1 =
199
+ * Enhancement: Added Web Application Firewall
200
+ * Enhancement: Added Diagnostics page
201
+ * Enhancement: Added new scans:
202
+ * Admins created outside of WordPress
203
+ * Publicly accessible common (database or wp-config.php) backup files
204
+ * Improvement: Updated Live Traffic with filters and to include blocked requests in the feed.
205
+
206
  = 6.0.25 =
207
  * Improvement: Added help callout for compromised sites.
208
  * Improvement: Updated local GeoIP database.
vendor/autoload.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer' . '/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInit8dd399f3311032288f54211d3b2a0e01::getLoader();
vendor/composer/ClassLoader.php ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see http://www.php-fig.org/psr/psr-0/
41
+ * @see http://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ // PSR-4
46
+ private $prefixLengthsPsr4 = array();
47
+ private $prefixDirsPsr4 = array();
48
+ private $fallbackDirsPsr4 = array();
49
+
50
+ // PSR-0
51
+ private $prefixesPsr0 = array();
52
+ private $fallbackDirsPsr0 = array();
53
+
54
+ private $useIncludePath = false;
55
+ private $classMap = array();
56
+
57
+ private $classMapAuthoritative = false;
58
+
59
+ public function getPrefixes()
60
+ {
61
+ if (!empty($this->prefixesPsr0)) {
62
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
63
+ }
64
+
65
+ return array();
66
+ }
67
+
68
+ public function getPrefixesPsr4()
69
+ {
70
+ return $this->prefixDirsPsr4;
71
+ }
72
+
73
+ public function getFallbackDirs()
74
+ {
75
+ return $this->fallbackDirsPsr0;
76
+ }
77
+
78
+ public function getFallbackDirsPsr4()
79
+ {
80
+ return $this->fallbackDirsPsr4;
81
+ }
82
+
83
+ public function getClassMap()
84
+ {
85
+ return $this->classMap;
86
+ }
87
+
88
+ /**
89
+ * @param array $classMap Class to filename map
90
+ */
91
+ public function addClassMap(array $classMap)
92
+ {
93
+ if ($this->classMap) {
94
+ $this->classMap = array_merge($this->classMap, $classMap);
95
+ } else {
96
+ $this->classMap = $classMap;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Registers a set of PSR-0 directories for a given prefix, either
102
+ * appending or prepending to the ones previously set for this prefix.
103
+ *
104
+ * @param string $prefix The prefix
105
+ * @param array|string $paths The PSR-0 root directories
106
+ * @param bool $prepend Whether to prepend the directories
107
+ */
108
+ public function add($prefix, $paths, $prepend = false)
109
+ {
110
+ if (!$prefix) {
111
+ if ($prepend) {
112
+ $this->fallbackDirsPsr0 = array_merge(
113
+ (array) $paths,
114
+ $this->fallbackDirsPsr0
115
+ );
116
+ } else {
117
+ $this->fallbackDirsPsr0 = array_merge(
118
+ $this->fallbackDirsPsr0,
119
+ (array) $paths
120
+ );
121
+ }
122
+
123
+ return;
124
+ }
125
+
126
+ $first = $prefix[0];
127
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
128
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
129
+
130
+ return;
131
+ }
132
+ if ($prepend) {
133
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
134
+ (array) $paths,
135
+ $this->prefixesPsr0[$first][$prefix]
136
+ );
137
+ } else {
138
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
139
+ $this->prefixesPsr0[$first][$prefix],
140
+ (array) $paths
141
+ );
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Registers a set of PSR-4 directories for a given namespace, either
147
+ * appending or prepending to the ones previously set for this namespace.
148
+ *
149
+ * @param string $prefix The prefix/namespace, with trailing '\\'
150
+ * @param array|string $paths The PSR-0 base directories
151
+ * @param bool $prepend Whether to prepend the directories
152
+ *
153
+ * @throws \InvalidArgumentException
154
+ */
155
+ public function addPsr4($prefix, $paths, $prepend = false)
156
+ {
157
+ if (!$prefix) {
158
+ // Register directories for the root namespace.
159
+ if ($prepend) {
160
+ $this->fallbackDirsPsr4 = array_merge(
161
+ (array) $paths,
162
+ $this->fallbackDirsPsr4
163
+ );
164
+ } else {
165
+ $this->fallbackDirsPsr4 = array_merge(
166
+ $this->fallbackDirsPsr4,
167
+ (array) $paths
168
+ );
169
+ }
170
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
171
+ // Register directories for a new namespace.
172
+ $length = strlen($prefix);
173
+ if ('\\' !== $prefix[$length - 1]) {
174
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
175
+ }
176
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
177
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
178
+ } elseif ($prepend) {
179
+ // Prepend directories for an already registered namespace.
180
+ $this->prefixDirsPsr4[$prefix] = array_merge(
181
+ (array) $paths,
182
+ $this->prefixDirsPsr4[$prefix]
183
+ );
184
+ } else {
185
+ // Append directories for an already registered namespace.
186
+ $this->prefixDirsPsr4[$prefix] = array_merge(
187
+ $this->prefixDirsPsr4[$prefix],
188
+ (array) $paths
189
+ );
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Registers a set of PSR-0 directories for a given prefix,
195
+ * replacing any others previously set for this prefix.
196
+ *
197
+ * @param string $prefix The prefix
198
+ * @param array|string $paths The PSR-0 base directories
199
+ */
200
+ public function set($prefix, $paths)
201
+ {
202
+ if (!$prefix) {
203
+ $this->fallbackDirsPsr0 = (array) $paths;
204
+ } else {
205
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Registers a set of PSR-4 directories for a given namespace,
211
+ * replacing any others previously set for this namespace.
212
+ *
213
+ * @param string $prefix The prefix/namespace, with trailing '\\'
214
+ * @param array|string $paths The PSR-4 base directories
215
+ *
216
+ * @throws \InvalidArgumentException
217
+ */
218
+ public function setPsr4($prefix, $paths)
219
+ {
220
+ if (!$prefix) {
221
+ $this->fallbackDirsPsr4 = (array) $paths;
222
+ } else {
223
+ $length = strlen($prefix);
224
+ if ('\\' !== $prefix[$length - 1]) {
225
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
226
+ }
227
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
228
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Turns on searching the include path for class files.
234
+ *
235
+ * @param bool $useIncludePath
236
+ */
237
+ public function setUseIncludePath($useIncludePath)
238
+ {
239
+ $this->useIncludePath = $useIncludePath;
240
+ }
241
+
242
+ /**
243
+ * Can be used to check if the autoloader uses the include path to check
244
+ * for classes.
245
+ *
246
+ * @return bool
247
+ */
248
+ public function getUseIncludePath()
249
+ {
250
+ return $this->useIncludePath;
251
+ }
252
+
253
+ /**
254
+ * Turns off searching the prefix and fallback directories for classes
255
+ * that have not been registered with the class map.
256
+ *
257
+ * @param bool $classMapAuthoritative
258
+ */
259
+ public function setClassMapAuthoritative($classMapAuthoritative)
260
+ {
261
+ $this->classMapAuthoritative = $classMapAuthoritative;
262
+ }
263
+
264
+ /**
265
+ * Should class lookup fail if not found in the current class map?
266
+ *
267
+ * @return bool
268
+ */
269
+ public function isClassMapAuthoritative()
270
+ {
271
+ return $this->classMapAuthoritative;
272
+ }
273
+
274
+ /**
275
+ * Registers this instance as an autoloader.
276
+ *
277
+ * @param bool $prepend Whether to prepend the autoloader or not
278
+ */
279
+ public function register($prepend = false)
280
+ {
281
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
282
+ }
283
+
284
+ /**
285
+ * Unregisters this instance as an autoloader.
286
+ */
287
+ public function unregister()
288
+ {
289
+ spl_autoload_unregister(array($this, 'loadClass'));
290
+ }
291
+
292
+ /**
293
+ * Loads the given class or interface.
294
+ *
295
+ * @param string $class The name of the class
296
+ * @return bool|null True if loaded, null otherwise
297
+ */
298
+ public function loadClass($class)
299
+ {
300
+ if ($file = $this->findFile($class)) {
301
+ includeFile($file);
302
+
303
+ return true;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Finds the path to the file where the class is defined.
309
+ *
310
+ * @param string $class The name of the class
311
+ *
312
+ * @return string|false The path if found, false otherwise
313
+ */
314
+ public function findFile($class)
315
+ {
316
+ // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
317
+ if ('\\' == $class[0]) {
318
+ $class = substr($class, 1);
319
+ }
320
+
321
+ // class map lookup
322
+ if (isset($this->classMap[$class])) {
323
+ return $this->classMap[$class];
324
+ }
325
+ if ($this->classMapAuthoritative) {
326
+ return false;
327
+ }
328
+
329
+ $file = $this->findFileWithExtension($class, '.php');
330
+
331
+ // Search for Hack files if we are running on HHVM
332
+ if ($file === null && defined('HHVM_VERSION')) {
333
+ $file = $this->findFileWithExtension($class, '.hh');
334
+ }
335
+
336
+ if ($file === null) {
337
+ // Remember that this class does not exist.
338
+ return $this->classMap[$class] = false;
339
+ }
340
+
341
+ return $file;
342
+ }
343
+
344
+ private function findFileWithExtension($class, $ext)
345
+ {
346
+ // PSR-4 lookup
347
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
348
+
349
+ $first = $class[0];
350
+ if (isset($this->prefixLengthsPsr4[$first])) {
351
+ foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
352
+ if (0 === strpos($class, $prefix)) {
353
+ foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
354
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
355
+ return $file;
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ // PSR-4 fallback dirs
363
+ foreach ($this->fallbackDirsPsr4 as $dir) {
364
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
365
+ return $file;
366
+ }
367
+ }
368
+
369
+ // PSR-0 lookup
370
+ if (false !== $pos = strrpos($class, '\\')) {
371
+ // namespaced class name
372
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
373
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
374
+ } else {
375
+ // PEAR-like class name
376
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
377
+ }
378
+
379
+ if (isset($this->prefixesPsr0[$first])) {
380
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
381
+ if (0 === strpos($class, $prefix)) {
382
+ foreach ($dirs as $dir) {
383
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
384
+ return $file;
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+ // PSR-0 fallback dirs
392
+ foreach ($this->fallbackDirsPsr0 as $dir) {
393
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
394
+ return $file;
395
+ }
396
+ }
397
+
398
+ // PSR-0 include paths.
399
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
400
+ return $file;
401
+ }
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Scope isolated include.
407
+ *
408
+ * Prevents access to $this/self from included files.
409
+ */
410
+ function includeFile($file)
411
+ {
412
+ include $file;
413
+ }
vendor/composer/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Copyright (c) 2015 Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_classmap.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_psr4.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_real.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInit8dd399f3311032288f54211d3b2a0e01
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ public static function getLoader()
17
+ {
18
+ if (null !== self::$loader) {
19
+ return self::$loader;
20
+ }
21
+
22
+ spl_autoload_register(array('ComposerAutoloaderInit8dd399f3311032288f54211d3b2a0e01', 'loadClassLoader'), true, true);
23
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit8dd399f3311032288f54211d3b2a0e01', 'loadClassLoader'));
25
+
26
+ $map = require __DIR__ . '/autoload_namespaces.php';
27
+ foreach ($map as $namespace => $path) {
28
+ $loader->set($namespace, $path);
29
+ }
30
+
31
+ $map = require __DIR__ . '/autoload_psr4.php';
32
+ foreach ($map as $namespace => $path) {
33
+ $loader->setPsr4($namespace, $path);
34
+ }
35
+
36
+ $classMap = require __DIR__ . '/autoload_classmap.php';
37
+ if ($classMap) {
38
+ $loader->addClassMap($classMap);
39
+ }
40
+
41
+ $loader->register(true);
42
+
43
+ return $loader;
44
+ }
45
+ }
vendor/composer/installed.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "wordfence/wf-waf",
4
+ "version": "1.0.0",
5
+ "version_normalized": "1.0.0.0",
6
+ "source": {
7
+ "type": "git",
8
+ "url": "https://github.com/wordfence/wf-waf.git",
9
+ "reference": "origin/master"
10
+ },
11
+ "dist": {
12
+ "type": "zip",
13
+ "url": "https://github.com/wordfence/wf-waf/zipball/master",
14
+ "reference": null,
15
+ "shasum": null
16
+ },
17
+ "type": "library",
18
+ "installation-source": "source"
19
+ }
20
+ ]
vendor/wordfence/wf-waf/src/baseRules.rules ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Thresholds which, if exceeded will cause a rule group to block
2
+ scores.sqli = 100
3
+ scores.xss = 100
4
+ scores.rce = 100
5
+
6
+ # Blacklisted URL/params to not whitelist
7
+ blacklistParam(url='/\/wp\-admin[\/]+admin\-ajax\.php/i', param=request.queryString.action)
8
+ blacklistParam(url='/\/wp\-admin[\/]+admin\-ajax\.php/i', param=request.queryString.img)
9
+ blacklistParam(url='/\/wp\-admin[\/]+admin\-ajax\.php/i', param=request.body.action)
10
+ blacklistParam(url='/\/wp\-admin[\/]+admin\-ajax\.php/i', param=request.body.img)
11
+
12
+ # Netsparker
13
+ blacklistParam(url='/.*/', param=request.body.nsextt)
14
+
15
+ # File uploads
16
+ blacklistParam(url='/\/uploadify\.php$/i', param=request.fileNames.Filedata)
17
+ blacklistParam(url='/.*/', param=request.fileNames.yiw_contact)
18
+ blacklistParam(url='/\/license\.php$/i', param=request.fileNames.filename)
19
+ blacklistParam(url='/\/wp\-admin[\/]+admin\-ajax\.php$/i', param=request.fileNames.update_file)
20
+ blacklistParam(url='/tiny_mce[\/]+plugins[\/]+tinybrowser[\/]+upload_file\.php$/i', param=request.fileNames.Filedata)
21
+ blacklistParam(url='/elfinder[\/]+php[\/]+connector\.minimal\.php$/i', param=request.fileNames.upload)
22
+
23
+
24
+ # Whitelisted URL/params to not run through the rules
25
+ # Trackbacks
26
+ whitelistParam(url='/.*/', param=request.body.excerpt)
27
+
28
+ # Comments
29
+ whitelistParam(url='/wp-comments-post\.php$/i', param=request.body.comment, rules=[3, 12])
30
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.content)
31
+
32
+ # WordPress SEO / Auto-saving
33
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.data)
34
+
35
+ # WordPress Plugins/Posts Search
36
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?(?:plugin(?:s|-install)|edit)\.php$/i', param=request.queryString.s)
37
+
38
+ # WAF config page
39
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.whitelistedPath)
40
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.whitelistedParam)
41
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.oldWhitelistedPath)
42
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.oldWhitelistedParam)
43
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.newWhitelistedPath)
44
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.newWhitelistedParam)
45
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.bannedURLs)
46
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.scan_include_extra)
47
+
48
+ # WordPress misc
49
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?(?:plugin|theme)-editor\.php$/i', param=request.body.newcontent)
50
+ whitelistParam(url='/.{0,1}/', param=request.queryString._wp_http_referer)
51
+
52
+ # Plugins page
53
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?plugins\.php$/i', param=request.queryString.plugin)
54
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?plugins\.php$/i', param=request.queryString.action)
55
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?plugins\.php$/i', param=request.queryString.checked)
56
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?plugins\.php$/i', param=request.body.action)
57
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?plugins\.php$/i', param=request.body.checked)
58
+ whitelistParam(url='/\/wp-admin\/(?:network\/)?plugins\.php$/i', param=request.body.submit)
59
+
60
+ # Options page
61
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.blogname)
62
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.blogdescription)
63
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.siteurl)
64
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.home)
65
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.admin_email)
66
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.moderation_keys)
67
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.blacklist_keys)
68
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.permalink_structure)
69
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.category_base)
70
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.tag_base)
71
+
72
+ whitelistParam(url='/\/wp-admin\/edit-comments\.php$/i', param=request.queryString.s)
73
+
74
+ # WordPress login page
75
+ whitelistParam(url='/\/wp-login\.php$/i', param=request.body.log)
76
+ whitelistParam(url='/\/wp-login\.php$/i', param=request.body.pwd)
77
+ whitelistParam(url='/\/wp-login\.php$/i', param=request.body.redirect_to)
78
+
79
+ # Multisite Admin Pages
80
+ whitelistParam(url='/\/wp-admin\/network\/(?:user|site)s\.php$/i', param=request.queryString.s)
81
+ whitelistParam(url='/\/wp-admin\/network\/site-new\.php$/i', param=request.body.blog)
82
+
83
+ # Deleting WAF whitelist items
84
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.deletedWhitelistedPath)
85
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.deletedWhitelistedParam)
86
+
87
+ # iThemes Security
88
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.itsec_global.log_location)
89
+ whitelistParam(url='/\/wp-admin\/options\.php$/i', param=request.body.itsec_backup.location)
90
+ whitelistParam(url='/\/wp-admin\/admin-ajax\.php$/i', param=request.body.dir)
91
+
92
+ # PHPMyAdmin
93
+ whitelistParam(url='/(?:lint|import)\.php$/i', param=request.body.sql_query)
94
+
95
+ # SQLi detection
96
+ sqliRegex = '/(?:[^\w<]|\/\*\![0-9]*|^)(?:
97
+ @@HOSTNAME|
98
+ ALTER|ANALYZE|ASENSITIVE|
99
+ BEFORE|BENCHMARK|BETWEEN|BIGINT|BINARY|BLOB|
100
+ CALL|CASE|CHANGE|CHAR|CHARACTER|CHAR_LENGTH|COLLATE|COLUMN|CONCAT|CONDITION|CONSTRAINT|CONTINUE|CONVERT|CREATE|CROSS|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|
101
+ DATABASE|DATABASES|DAY_HOUR|DAY_MICROSECOND|DAY_MINUTE|DAY_SECOND|DECIMAL|DECLARE|DEFAULT|DELAYED|DELETE|DESCRIBE|DETERMINISTIC|DISTINCT|DISTINCTROW|DOUBLE|DROP|DUAL|DUMPFILE|
102
+ EACH|ELSE|ELSEIF|ELT|ENCLOSED|ESCAPED|EXISTS|EXIT|EXPLAIN|EXTRACTVALUE|
103
+ FETCH|FLOAT|FLOAT4|FLOAT8|FORCE|FOREIGN|FROM|FULLTEXT|
104
+ GRANT|GROUP|HAVING|HEX|HIGH_PRIORITY|HOUR_MICROSECOND|HOUR_MINUTE|HOUR_SECOND|
105
+ IFNULL|IGNORE|INDEX|INFILE|INNER|INOUT|INSENSITIVE|INSERT|INTERVAL|ISNULL|ITERATE|
106
+ JOIN|KILL|LEADING|LEAVE|LIMIT|LINEAR|LINES|LOAD|LOAD_FILE|LOCALTIME|LOCALTIMESTAMP|LOCK|LONG|LONGBLOB|LONGTEXT|LOOP|LOW_PRIORITY|
107
+ MASTER_SSL_VERIFY_SERVER_CERT|MATCH|MAXVALUE|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MID|MIDDLEINT|MINUTE_MICROSECOND|MINUTE_SECOND|MODIFIES|
108
+ NATURAL|NO_WRITE_TO_BINLOG|NULL|NUMERIC|OPTION|ORD|ORDER|OUTER|OUTFILE|
109
+ PRECISION|PRIMARY|PRIVILEGES|PROCEDURE|PROCESSLIST|PURGE|
110
+ RANGE|READ_WRITE|REGEXP|RELEASE|REPEAT|REQUIRE|RESIGNAL|RESTRICT|RETURN|REVOKE|RLIKE|ROLLBACK|
111
+ SCHEMA|SCHEMAS|SECOND_MICROSECOND|SELECT|SENSITIVE|SEPARATOR|SHOW|SIGNAL|SLEEP|SMALLINT|SPATIAL|SPECIFIC|SQLEXCEPTION|SQLSTATE|SQLWARNING|SQL_BIG_RESULT|SQL_CALC_FOUND_ROWS|SQL_SMALL_RESULT|STARTING|STRAIGHT_JOIN|SUBSTR|
112
+ TABLE|TERMINATED|TINYBLOB|TINYINT|TINYTEXT|TRAILING|TRANSACTION|TRIGGER|
113
+ UNDO|UNHEX|UNION|UNLOCK|UNSIGNED|UPDATE|UPDATEXML|USAGE|USING|UTC_DATE|UTC_TIME|UTC_TIMESTAMP|
114
+ VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|WHEN|WHERE|WHILE|WRITE|YEAR_MONTH|ZEROFILL)(?=[^\w]|$)/ix'
115
+
116
+
117
+ xssRegex = '/(?:
118
+ #tags
119
+ (?:\<|\+ADw\-|\xC2\xBC)(script|iframe|svg|object|embed|applet|link|style|meta|\/\/|\?xml\-stylesheet)(?:[^\w]|\xC2\xBE)|
120
+ #protocols
121
+ (?:^|[^\w])(?:(?:\s*(?:&\#(?:x0*6a|0*106)|j)\s*(?:&\#(?:x0*61|0*97)|a)\s*(?:&\#(?:x0*76|0*118)|v)\s*(?:&\#(?:x0*61|0*97)|a)|\s*(?:&\#(?:x0*76|0*118)|v)\s*(?:&\#(?:x0*62|0*98)|b)|\s*(?:&\#(?:x0*65|0*101)|e)\s*(?:&\#(?:x0*63|0*99)|c)\s*(?:&\#(?:x0*6d|0*109)|m)\s*(?:&\#(?:x0*61|0*97)|a)|\s*(?:&\#(?:x0*6c|0*108)|l)\s*(?:&\#(?:x0*69|0*105)|i)\s*(?:&\#(?:x0*76|0*118)|v)\s*(?:&\#(?:x0*65|0*101)|e))\s*(?:&\#(?:x0*73|0*115)|s)\s*(?:&\#(?:x0*63|0*99)|c)\s*(?:&\#(?:x0*72|0*114)|r)\s*(?:&\#(?:x0*69|0*105)|i)\s*(?:&\#(?:x0*70|0*112)|p)\s*(?:&\#(?:x0*74|0*116)|t)|\s*(?:&\#(?:x0*6d|0*109)|m)\s*(?:&\#(?:x0*68|0*104)|h)\s*(?:&\#(?:x0*74|0*116)|t)\s*(?:&\#(?:x0*6d|0*109)|m)\s*(?:&\#(?:x0*6c|0*108)|l)|\s*(?:&\#(?:x0*6d|0*109)|m)\s*(?:&\#(?:x0*6f|0*111)|o)\s*(?:&\#(?:x0*63|0*99)|c)\s*(?:&\#(?:x0*68|0*104)|h)\s*(?:&\#(?:x0*61|0*97)|a)|\s*(?:&\#(?:x0*64|0*100)|d)\s*(?:&\#(?:x0*61|0*97)|a)\s*(?:&\#(?:x0*74|0*116)|t)\s*(?:&\#(?:x0*61|0*97)|a))\s*(?:&\#(?:x0*3a|0*58)|\:)|
122
+ #css expression
123
+ (?:^|[^\w])(?:(?:\\0*65|\\0*45|e)(?:\/\*.*?\*\/)*(?:\\0*78|\\0*58|x)(?:\/\*.*?\*\/)*(?:\\0*70|\\0*50|p)(?:\/\*.*?\*\/)*(?:\\0*72|\\0*52|r)(?:\/\*.*?\*\/)*(?:\\0*65|\\0*45|e)(?:\/\*.*?\*\/)*(?:\\0*73|\\0*53|s)(?:\/\*.*?\*\/)*(?:\\0*73|\\0*53|s)(?:\/\*.*?\*\/)*(?:\\0*69|\\0*49|i)(?:\/\*.*?\*\/)*(?:\\0*6f|\\0*4f|o)(?:\/\*.*?\*\/)*(?:\\0*6e|\\0*4e|n))[^\w]*?(?:\\0*28|\()|
124
+ #css properties
125
+ (?:^|[^\w])(?:(?:(?:\\0*62|\\0*42|b)(?:\/\*.*?\*\/)*(?:\\0*65|\\0*45|e)(?:\/\*.*?\*\/)*(?:\\0*68|\\0*48|h)(?:\/\*.*?\*\/)*(?:\\0*61|\\0*41|a)(?:\/\*.*?\*\/)*(?:\\0*76|\\0*56|v)(?:\/\*.*?\*\/)*(?:\\0*69|\\0*49|i)(?:\/\*.*?\*\/)*(?:\\0*6f|\\0*4f|o)(?:\/\*.*?\*\/)*(?:\\0*72|\\0*52|r)(?:\/\*.*?\*\/)*)|(?:(?:\\0*2d|\\0*2d|-)(?:\/\*.*?\*\/)*(?:\\0*6d|\\0*4d|m)(?:\/\*.*?\*\/)*(?:\\0*6f|\\0*4f|o)(?:\/\*.*?\*\/)*(?:\\0*7a|\\0*5a|z)(?:\/\*.*?\*\/)*(?:\\0*2d|\\0*2d|-)(?:\/\*.*?\*\/)*(?:\\0*62|\\0*42|b)(?:\/\*.*?\*\/)*(?:\\0*69|\\0*49|i)(?:\/\*.*?\*\/)*(?:\\0*6e|\\0*4e|n)(?:\/\*.*?\*\/)*(?:\\0*64|\\0*44|d)(?:\/\*.*?\*\/)*(?:\\0*69|\\0*49|i)(?:\/\*.*?\*\/)*(?:\\0*6e|\\0*4e|n)(?:\/\*.*?\*\/)*(?:\\0*67|\\0*47|g)(?:\/\*.*?\*\/)*))[^\w]*(?:\\0*3a|\\0*3a|:)[^\w]*(?:\\0*75|\\0*55|u)(?:\\0*72|\\0*52|r)(?:\\0*6c|\\0*4c|l)|
126
+ #properties
127
+ (?:^|[^\w])(?:on(?:abort|activate|afterprint|afterupdate|autocomplete|autocompleteerror|beforeactivate|beforecopy|beforecut|beforedeactivate|beforeeditfocus|beforepaste|beforeprint|beforeunload|beforeupdate|blur|bounce|cancel|canplay|canplaythrough|cellchange|change|click|close|contextmenu|controlselect|copy|cuechange|cut|dataavailable|datasetchanged|datasetcomplete|dblclick|deactivate|drag|dragend|dragenter|dragleave|dragover|dragstart|drop|durationchange|emptied|encrypted|ended|error|errorupdate|filterchange|finish|focus|focusin|focusout|formaction|formchange|forminput|hashchange|help|input|invalid|keydown|keypress|keyup|languagechange|layoutcomplete|load|loadeddata|loadedmetadata|loadstart|losecapture|message|mousedown|mouseenter|mouseleave|mousemove|mouseout|mouseover|mouseup|mousewheel|move|moveend|movestart|mozfullscreenchange|mozfullscreenerror|mozpointerlockchange|mozpointerlockerror|offline|online|page|pagehide|pageshow|paste|pause|play|playing|popstate|progress|propertychange|ratechange|readystatechange|reset|resize|resizeend|resizestart|rowenter|rowexit|rowsdelete|rowsinserted|scroll|search|seeked|seeking|select|selectstart|show|stalled|start|storage|submit|suspend|timer|timeupdate|toggle|unload|volumechange|waiting|webkitfullscreenchange|webkitfullscreenerror|wheel)|data\-bind|ev:event)[^\w]
128
+ )/ix'
129
+
130
+ # User Roles Manager Priviledge Escalation <= 4.24
131
+ if (notEquals('', request.body.ure_other_roles) and
132
+ match('#/wp\-admin/(network/)?(profile|user-new)\.php#i', request.path) and
133
+ currentUserIsNot('administrator', server.empty)):
134
+ block(id=18, category='priv-esc', description='User Roles Manager Priviledge Escalation <= 4.24')
135
+
136
+ # Whitelisted WordPress URLs
137
+ if ((match('#/wp\-admin/(network/)?(post|profile|user-new|settings)\.php$#i', server.script_filename)) or
138
+ (match('#/wp\-admin/admin\-ajax\.php$#i', server.script_filename) and (
139
+ equals('wordfence_loadLiveTraffic', request.body.action) or
140
+ equals('wordfence_ticker', request.body.action)
141
+ ))):
142
+ allow(id=1, category='whitelist', description='Whitelisted URL')
143
+
144
+ # Slider revolution
145
+ if (match('/\/wp\-admin[\/]+admin\-ajax\.php/', request.path) and (
146
+ (equals('revslider_show_image', request.queryString.action) and match('/\.php$/i', request.queryString.img)) or
147
+ (equals('revslider_show_image', request.body.action) and match('/\.php$/i', request.body.img))
148
+ )):
149
+ block(id=2, category='lfi', description='Slider Revolution: Local File Inclusion')
150
+
151
+ # dzs-videogallery 8.80
152
+ if (match('/dzs\-videogallery[\/]+admin[\/]+(?:playlist|tag)seditor[\/]+popup\.php/', request.path) and contains("'", request.queryString.initer)):
153
+ blockXSS(id=15, category='xss', description='dzs-videogallery 8.80 XSS HTML injection in inline JavaScript')
154
+
155
+ # Simple Ads Manager <= 2.9.4.116 - SQL Injection: https://wpvulndb.com/vulnerabilities/8357
156
+ if (match('/simple-ads-manager[\/]+sam-ajax-loader\.php/', request.path) and
157
+ match(sqliRegex, base64decode(request.body.wc))):
158
+ block(id=16, category='sqli', description='Simple Ads Manager <= 2.9.4.116 - SQL Injection')
159
+
160
+ # Gwolle Guestbook <= 1.5.3 - Remote File Inclusion (RFI): https://wpvulndb.com/vulnerabilities/8218
161
+ if (match('/gwolle\-gb[\/]+frontend[\/]+captcha[\/]+ajaxresponse\.php/', request.path) and match('/.*/', request.queryString.abspath)):
162
+ block(id=17, category='rfi', description='Gwolle Guestbook <= 1.5.3 - Remote File Inclusion')
163
+
164
+ # SQLi detection
165
+ if (matchCount(sqliRegex, request.body, request.queryString)):
166
+ failSQLi(id=3, category='sqli', score=40, description='SQL Injection')
167
+
168
+ # blacklisted tags, protocols and attributes
169
+ if (matchCount(xssRegex, request.body, request.queryString)):
170
+ failXSS(id=9, category='xss', score=100, description='XSS: Cross Site Scripting')
171
+
172
+ # Shell file uploads
173
+ if (match('/\.(p(h(p|tml)[0-9]?|l|y)|(j|a)sp|aspx|sh|shtml|html?|cgi|htaccess)($|\.)/i', request.fileNames)):
174
+ block(id=11, category='file_upload', description='Malicous File Upload')
175
+
176
+ # Directory traversal
177
+ if (match('/(^|\/|\\)\.\.(\\|\/)/', request.body, request.queryString)):
178
+ block(id=12, category='lfi', description='Directory Traversal')
179
+
180
+ # LFI absolute paths
181
+ if (match('/^\/(?:\.\/)*(?:var|home|usr|mnt|media|etc|tmp|dev|proc)\//i', request.body, request.queryString)):
182
+ block(id=13, category='lfi', description='LFI: Local File Inclusion')
183
+
184
+ # XXE
185
+ if (match('/<\!(?:DOCTYPE|ENTITY)\s+(?:%\s*)?\w+\s+SYSTEM/i', request.body, request.queryString)):
186
+ block(id=14, category='xxe', description='XXE: External Entity Expansion')
187
+
vendor/wordfence/wf-waf/src/bootstrap-sample.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ php_value auto_prepend_file ~/wp-content/plugins/wordfence/waf/bootstrap.php
5
+ */
6
+
7
+ require_once dirname(__FILE__) . '/init.php';
8
+ if (!defined('WFWAF_LOG_PATH')) {
9
+ define('WFWAF_LOG_PATH', WFWAF_PATH . 'logs/');
10
+ }
11
+
12
+ wfWAF::setInstance(new wfWAF(
13
+ wfWAFRequest::createFromGlobals(),
14
+ new wfWAFStorageFile(
15
+ WFWAF_LOG_PATH . 'attack-data.php',
16
+ WFWAF_LOG_PATH . 'ips.php',
17
+ WFWAF_LOG_PATH . 'config.php',
18
+ WFWAF_LOG_PATH . 'wafRules.rules'
19
+ )
20
+ ));
21
+ wfWAF::getInstance()->getEventBus()->attach(new wfWAFBaseObserver);
22
+
23
+ $rulesFiles = array(
24
+ WFWAF_PATH . 'rules.php',
25
+ WFWAF_LOG_PATH . 'rules.php',
26
+ );
27
+ foreach ($rulesFiles as $rulesFile) {
28
+ if (!file_exists($rulesFile)) {
29
+ @touch($rulesFile);
30
+ }
31
+ if (is_writable($rulesFile)) {
32
+ wfWAF::getInstance()->setCompiledRulesFile($rulesFile);
33
+ break;
34
+ }
35
+ }
36
+
37
+ try {
38
+ if (!file_exists(wfWAF::getInstance()->getCompiledRulesFile()) || !filesize(wfWAF::getInstance()->getCompiledRulesFile())) {
39
+ try {
40
+ wfWAF::getInstance()->updateRuleSet(file_get_contents(WFWAF_PATH . 'baseRules.rules'));
41
+ } catch (wfWAFBuildRulesException $e) {
42
+ error_log($e->getMessage());
43
+ } catch (Exception $e) {
44
+ error_log($e->getMessage());
45
+ }
46
+ }
47
+
48
+ try {
49
+ wfWAF::getInstance()->run();
50
+ } catch (wfWAFBuildRulesException $e) {
51
+ error_log($e->getMessage());
52
+ } catch (Exception $e) {
53
+ error_log($e->getMessage());
54
+ }
55
+ } catch (wfWAFStorageFileException $e) {
56
+ // Choose another storage engine here.
57
+ }
vendor/wordfence/wf-waf/src/init.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ define('WFWAF_VERSION', '1.0.0');
4
+ define('WFWAF_PATH', dirname(__FILE__) . '/');
5
+ define('WFWAF_LIB_PATH', WFWAF_PATH . 'lib/');
6
+ define('WFWAF_VIEW_PATH', WFWAF_PATH . 'views/');
7
+ define('WFWAF_API_URL_SEC', 'https://noc4.wordfence.com/v1.1/');
8
+ if (!defined('WFWAF_DEBUG')) {
9
+ define('WFWAF_DEBUG', false);
10
+ }
11
+ if (!defined('WFWAF_ENABLED')) {
12
+ define('WFWAF_ENABLED', true);
13
+ }
14
+
15
+ require_once WFWAF_LIB_PATH . 'waf.php';
16
+ require_once WFWAF_LIB_PATH . 'utils.php';
17
+
18
+ require_once WFWAF_LIB_PATH . 'storage.php';
19
+ require_once WFWAF_LIB_PATH . 'storage/file.php';
20
+
21
+ require_once WFWAF_LIB_PATH . 'rules.php';
22
+ require_once WFWAF_LIB_PATH . 'parser/lexer.php';
23
+ require_once WFWAF_LIB_PATH . 'parser/parser.php';
24
+ require_once WFWAF_LIB_PATH . 'parser/sqli.php';
25
+
26
+ require_once WFWAF_LIB_PATH . 'request.php';
27
+ require_once WFWAF_LIB_PATH . 'http.php';
28
+ require_once WFWAF_LIB_PATH . 'view.php';
vendor/wordfence/wf-waf/src/lib/config.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+
vendor/wordfence/wf-waf/src/lib/http.php ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wfWAFHTTP {
4
+
5
+ private $url;
6
+ private $auth;
7
+ private $body;
8
+ private $cookies;
9
+ // private $fileNames;
10
+ // private $files;
11
+ private $headers;
12
+ private $method;
13
+ private $queryString;
14
+
15
+ /**
16
+ * @var wfWAFHTTPTransport
17
+ */
18
+ private $transport;
19
+
20
+ /**
21
+ * @param string $url
22
+ * @param wfWAFHTTP $request
23
+ * @return wfWAFHTTPResponse|bool
24
+ * @throws wfWAFHTTPTransportException
25
+ */
26
+ public static function get($url, $request = null) {
27
+ if (!$request) {
28
+ $request = new self();
29
+ }
30
+ $request->setUrl($url);
31
+ $request->setMethod('GET');
32
+ $request->setTransport(wfWAFHTTPTransport::getInstance());
33
+ // $request->setCookies("XDEBUG_SESSION=netbeans-xdebug");
34
+ return $request->send();
35
+ }
36
+
37
+ /**
38
+ * @param string $url
39
+ * @param array $post
40
+ * @param wfWAFHTTP $request
41
+ * @return wfWAFHTTPResponse|bool
42
+ * @throws wfWAFHTTPTransportException
43
+ */
44
+ public static function post($url, $post = array(), $request = null) {
45
+ if (!$request) {
46
+ $request = new self();
47
+ }
48
+ $request->setUrl($url);
49
+ $request->setMethod('POST');
50
+ $request->setBody($post);
51
+ $request->setTransport(wfWAFHTTPTransport::getInstance());
52
+ return $request->send();
53
+ }
54
+
55
+ /**
56
+ * @return wfWAFHTTPResponse|bool
57
+ * @throws wfWAFHTTPTransportException
58
+ */
59
+ public function send() {
60
+ if (!$this->getTransport()) {
61
+ throw new wfWAFHTTPTransportException('Need to provide a valid HTTP transport before calling ' . __METHOD__);
62
+ }
63
+ return $this->getTransport()->send($this);
64
+ }
65
+
66
+ /**
67
+ * @return mixed
68
+ */
69
+ public function getUrl() {
70
+ return $this->url;
71
+ }
72
+
73
+ /**
74
+ * @param mixed $url
75
+ */
76
+ public function setUrl($url) {
77
+ $this->url = $url;
78
+ }
79
+
80
+ /**
81
+ * @return mixed
82
+ */
83
+ public function getAuth() {
84
+ return $this->auth;
85
+ }
86
+
87
+ /**
88
+ * @param mixed $auth
89
+ */
90
+ public function setAuth($auth) {
91
+ $this->auth = $auth;
92
+ }
93
+
94
+ /**
95
+ * @return mixed
96
+ */
97
+ public function getBody() {
98
+ return $this->body;
99
+ }
100
+
101
+ /**
102
+ * @param mixed $body
103
+ */
104
+ public function setBody($body) {
105
+ $this->body = $body;
106
+ }
107
+
108
+ /**
109
+ * @return mixed
110
+ */
111
+ public function getCookies() {
112
+ return $this->cookies;
113
+ }
114
+
115
+ /**
116
+ * @param mixed $cookies
117
+ */
118
+ public function setCookies($cookies) {
119
+ $this->cookies = $cookies;
120
+ }
121
+
122
+ /**
123
+ * @return mixed
124
+ */
125
+ public function getHeaders() {
126
+ return $this->headers;
127
+ }
128
+
129
+ /**
130
+ * @param mixed $headers
131
+ */
132
+ public function setHeaders($headers) {
133
+ $this->headers = $headers;
134
+ }
135
+
136
+ /**
137
+ * @return mixed
138
+ */
139
+ public function getMethod() {
140
+ return $this->method;
141
+ }
142
+
143
+ /**
144
+ * @param mixed $method
145
+ */
146
+ public function setMethod($method) {
147
+ $this->method = $method;
148
+ }
149
+
150
+ /**
151
+ * @return mixed
152
+ */
153
+ public function getQueryString() {
154
+ return $this->queryString;
155
+ }
156
+
157
+ /**
158
+ * @param mixed $queryString
159
+ */
160
+ public function setQueryString($queryString) {
161
+ $this->queryString = $queryString;
162
+ }
163
+
164
+ /**
165
+ * @return wfWAFHTTPTransport
166
+ */
167
+ public function getTransport() {
168
+ return $this->transport;
169
+ }
170
+
171
+ /**
172
+ * @param wfWAFHTTPTransport $transport
173
+ */
174
+ public function setTransport($transport) {
175
+ $this->transport = $transport;
176
+ }
177
+ }
178
+
179
+ class wfWAFHTTPResponse {
180
+
181
+ private $body;
182
+ private $headers;
183
+ private $statusCode;
184
+
185
+ /**
186
+ * @return mixed
187
+ */
188
+ public function getBody() {
189
+ return $this->body;
190
+ }
191
+
192
+ /**
193
+ * @param mixed $body
194
+ */
195
+ public function setBody($body) {
196
+ $this->body = $body;
197
+ }
198
+
199
+ /**
200
+ * @return mixed
201
+ */
202
+ public function getHeaders() {
203
+ return $this->headers;
204
+ }
205
+
206
+ /**
207
+ * @param mixed $headers
208
+ */
209
+ public function setHeaders($headers) {
210
+ $this->headers = $headers;
211
+ }
212
+
213
+ /**
214
+ * @return mixed
215
+ */
216
+ public function getStatusCode() {
217
+ return $this->statusCode;
218
+ }
219
+
220
+ /**
221
+ * @param mixed $statusCode
222
+ */
223
+ public function setStatusCode($statusCode) {
224
+ $this->statusCode = $statusCode;
225
+ }
226
+ }
227
+
228
+ abstract class wfWAFHTTPTransport {
229
+
230
+ private static $instance;
231
+
232
+ /**
233
+ * @return mixed
234
+ */
235
+ public static function getInstance() {
236
+ if (!self::$instance) {
237
+ self::$instance = self::getFirstTransport();
238
+ }
239
+ return self::$instance;
240
+ }
241
+
242
+ /**
243
+ * @param mixed $instance
244
+ */
245
+ public static function setInstance($instance) {
246
+ self::$instance = $instance;
247
+ }
248
+
249
+ /**
250
+ * @return wfWAFHTTPTransport
251
+ * @throws wfWAFHTTPTransportException
252
+ */
253
+ public static function getFirstTransport() {
254
+ if (function_exists('curl_init')) {
255
+ return new wfWAFHTTPTransportCurl();
256
+ } else if (function_exists('file_get_contents')) {
257
+ return new wfWAFHTTPTransportStreams();
258
+ }
259
+ throw new wfWAFHTTPTransportException('No valid HTTP transport found.');
260
+ }
261
+
262
+ /**
263
+ * @param array $cookieArray
264
+ * @return string
265
+ */
266
+ public static function buildCookieString($cookieArray) {
267
+ $cookies = '';
268
+ foreach ($cookieArray as $cookieName => $value) {
269
+ $cookies .= "$cookieName=" . urlencode($value) . '; ';
270
+ }
271
+ $cookies = rtrim($cookies);
272
+ return $cookies;
273
+ }
274
+
275
+ /**
276
+ * @param wfWAFHTTP $request
277
+ * @return wfWAFHTTPResponse|bool
278
+ */
279
+ abstract public function send($request);
280
+ }
281
+
282
+ class wfWAFHTTPTransportCurl extends wfWAFHTTPTransport {
283
+
284
+ /**
285
+ * @todo Proxy settings
286
+ * @param wfWAFHTTP $request
287
+ * @return wfWAFHTTPResponse|bool
288
+ */
289
+ public function send($request) {
290
+ $url = $request->getUrl();
291
+ if ($queryString = $request->getQueryString()) {
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;
303
+ }
304
+ if ($body = $request->getBody()) {
305
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
306
+ }
307
+ if ($auth = $request->getAuth()) {
308
+ curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
309
+ }
310
+ if ($cookies = $request->getCookies()) {
311
+ if (is_array($cookies)) {
312
+ $cookies = self::buildCookieString($cookies);
313
+ }
314
+ curl_setopt($ch, CURLOPT_COOKIE, $cookies);
315
+ }
316
+ if ($headers = $request->getHeaders()) {
317
+ if (is_array($headers)) {
318
+ $_headers = array();
319
+ foreach ($headers as $header => $value) {
320
+ $_headers[] = $header . ': ' . $value;
321
+ }
322
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
323
+ }
324
+ }
325
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
326
+ curl_setopt($ch, CURLOPT_TIMEOUT, 5);
327
+ curl_setopt($ch, CURLOPT_HEADER, 1);
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);
336
+ $response->setHeaders($header);
337
+ return $response;
338
+ }
339
+ return false;
340
+ }
341
+ }
342
+
343
+ class wfWAFHTTPTransportStreams extends wfWAFHTTPTransport {
344
+
345
+ /**
346
+ * @todo Implement wfWAFHTTPTransportStreams::send.
347
+ * @param wfWAFHTTP $request
348
+ * @return mixed
349
+ * @throws wfWAFHTTPTransportException
350
+ */
351
+ public function send($request) {
352
+ $timeout = 5;
353
+
354
+ $url = $request->getUrl();
355
+ if ($queryString = $request->getQueryString()) {
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());
363
+
364
+ $headers = "Host: $urlParsed[host]\r\n";
365
+ if ($auth = $request->getAuth()) {
366
+ $headers .= 'Authorization: Basic ' . base64_encode($auth['user'] . ':' . $auth['password']) . "\r\n";
367
+ }
368
+ if ($cookies = $request->getCookies()) {
369
+ if (is_array($cookies)) {
370
+ $cookies = self::buildCookieString($cookies);
371
+ }
372
+ $headers .= "Cookie: $cookies\r\n";
373
+ }
374
+ $hasUA = false;
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";
382
+ }
383
+ }
384
+ }
385
+ if (!$hasUA) {
386
+ $headers .= "User-Agent: Wordfence Streams UA\r\n";
387
+ }
388
+
389
+ $httpOptions = array(
390
+ 'method' => $request->getMethod(),
391
+ 'ignore_errors' => true,
392
+ 'timeout' => $timeout,
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);
407
+ $stream = fopen($request->getUrl(), 'r', false, $context);
408
+ if (!is_resource($stream)) {
409
+ return false;
410
+ }
411
+
412
+ $metaData = stream_get_meta_data($stream);
413
+
414
+ // Get the HTTP response code
415
+ $httpResponse = array_shift($metaData['wrapper_data']);
416
+
417
+ if (preg_match_all('/(\w+\/\d\.\d) (\d{3})/', $httpResponse, $matches) !== false) {
418
+ // $protocol = $matches[1][0];
419
+ $status = (int) $matches[2][0];
420
+ } else {
421
+ // $protocol = null;
422
+ $status = null;
423
+ }
424
+
425
+ $responseObj = new wfWAFHTTPResponse();
426
+ $responseObj->setHeaders(join("\r\n", $metaData['wrapper_data']));
427
+ $responseObj->setBody(stream_get_contents($stream));
428
+ $responseObj->setStatusCode($status);
429
+
430
+ // Close the stream after use
431
+ fclose($stream);
432
+
433
+ return $responseObj;
434
+ }
435
+ }
436
+
437
+ class wfWAFHTTPTransportException extends wfWAFException {
438
+ }
vendor/wordfence/wf-waf/src/lib/parser/lexer.php ADDED
@@ -0,0 +1,667 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ interface wfWAFLexerInterface {
4
+
5
+ public function nextToken();
6
+
7
+ }
8
+
9
+ class wfWAFRuleLexer implements wfWAFLexerInterface {
10
+
11
+ const MATCH_IDENTIFIER = '/[a-zA-Z_][\\w_]*/';
12
+ const MATCH_SINGLE_STRING_LITERAL = '/\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
13
+ const MATCH_DOUBLE_STRING_LITERAL = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"/As';
14
+ const MATCH_NUMBER_LITERAL = '/-?\d+(\.\d+)?/';
15
+ const MATCH_DOT = '/\./';
16
+ const MATCH_COMPARISON_OPERATOR = '/\|\||&&/';
17
+ const MATCH_OPEN_PARENTHESIS = '/\(/';
18
+ const MATCH_CLOSE_PARENTHESIS = '/\)/';
19
+ const MATCH_COMMA = '/,/';
20
+ const MATCH_RULE_COMPARISON_END = '/:/';
21
+ const MATCH_ASSIGNMENT = '/=/';
22
+ const MATCH_SINGLE_LINE_COMMENT = '/(?:#|\/\/)[^\n]*/';
23
+ const MATCH_MULTIPLE_LINE_COMMENT = '/\/\*.*?\*\//s';
24
+ const MATCH_OPEN_BRACKET = '/\[/';
25
+ const MATCH_CLOSE_BRACKET = '/\]/';
26
+
27
+ const T_RULE_START = 'T_RULE_START';
28
+ const T_IDENTIFIER = 'T_IDENTIFIER';
29
+ const T_SINGLE_STRING_LITERAL = 'T_SINGLE_STRING_LITERAL';
30
+ const T_DOUBLE_STRING_LITERAL = 'T_DOUBLE_STRING_LITERAL';
31
+ const T_NUMBER_LITERAL = 'T_NUMBER_LITERAL';
32
+ const T_DOT = 'T_DOT';
33
+ const T_COMPARISON_OPERATOR = 'T_COMPARISON_OPERATOR';
34
+ const T_OPEN_PARENTHESIS = 'T_OPEN_PARENTHESIS';
35
+ const T_CLOSE_PARENTHESIS = 'T_CLOSE_PARENTHESIS';
36
+ const T_COMMA = 'T_COMMA';
37
+ const T_RULE_COMPARISON_END = 'T_RULE_COMPARISON_END';
38
+ const T_ASSIGNMENT = 'T_ASSIGNMENT';
39
+ const T_SINGLE_LINE_COMMENT = 'T_SINGLE_LINE_COMMENT';
40
+ const T_MULTIPLE_LINE_COMMENT = 'T_MULTIPLE_LINE_COMMENT';
41
+ const T_OPEN_BRACKET = 'T_OPEN_BRACKET';
42
+ const T_CLOSE_BRACKET = 'T_CLOSE_BRACKET';
43
+
44
+
45
+ /**
46
+ * @var string
47
+ */
48
+ private $rules;
49
+
50
+ /**
51
+ * @var wfWAFStringScanner
52
+ */
53
+ private $scanner;
54
+
55
+ /**
56
+ * wfWAFRuleLexer constructor.
57
+ * @param $rules
58
+ */
59
+ public function __construct($rules) {
60
+ $this->setRules($rules);
61
+ $this->scanner = new wfWAFStringScanner($rules);
62
+ }
63
+
64
+ /**
65
+ * @return array
66
+ * @throws wfWAFParserSyntaxError
67
+ */
68
+ public function tokenize() {
69
+ $tokens = array();
70
+ while ($token = $this->nextToken()) {
71
+ $tokens[] = $token;
72
+ }
73
+ return $tokens;
74
+ }
75
+
76
+ /**
77
+ * @return bool|wfWAFLexerToken
78
+ * @throws wfWAFParserSyntaxError
79
+ */
80
+ public function nextToken() {
81
+ if (!$this->scanner->eos()) {
82
+ $this->scanner->skip('/\s+/s');
83
+ if ($this->scanner->eos()) {
84
+ return false;
85
+ }
86
+ if (($match = $this->scanner->scan(self::MATCH_IDENTIFIER)) !== null)
87
+ switch (strtolower($match)) {
88
+ case 'if':
89
+ return $this->createToken(self::T_RULE_START, $match);
90
+ case 'and':
91
+ case 'or':
92
+ case 'xor':
93
+ return $this->createToken(self::T_COMPARISON_OPERATOR, $match);
94
+ default:
95
+ return $this->createToken(self::T_IDENTIFIER, $match);
96
+ }
97
+ else if (($match = $this->scanner->scan(self::MATCH_SINGLE_STRING_LITERAL)) !== null) return $this->createToken(self::T_SINGLE_STRING_LITERAL, $match);
98
+ else if (($match = $this->scanner->scan(self::MATCH_DOUBLE_STRING_LITERAL)) !== null) return $this->createToken(self::T_DOUBLE_STRING_LITERAL, $match);
99
+ else if (($match = $this->scanner->scan(self::MATCH_NUMBER_LITERAL)) !== null) return $this->createToken(self::T_NUMBER_LITERAL, $match);
100
+ else if (($match = $this->scanner->scan(self::MATCH_DOT)) !== null) return $this->createToken(self::T_DOT, $match);
101
+ else if (($match = $this->scanner->scan(self::MATCH_COMPARISON_OPERATOR)) !== null) return $this->createToken(self::T_COMPARISON_OPERATOR, $match);
102
+ else if (($match = $this->scanner->scan(self::MATCH_OPEN_PARENTHESIS)) !== null) return $this->createToken(self::T_OPEN_PARENTHESIS, $match);
103
+ else if (($match = $this->scanner->scan(self::MATCH_CLOSE_PARENTHESIS)) !== null) return $this->createToken(self::T_CLOSE_PARENTHESIS, $match);
104
+ else if (($match = $this->scanner->scan(self::MATCH_COMMA)) !== null) return $this->createToken(self::T_COMMA, $match);
105
+ else if (($match = $this->scanner->scan(self::MATCH_RULE_COMPARISON_END)) !== null) return $this->createToken(self::T_RULE_COMPARISON_END, $match);
106
+ else if (($match = $this->scanner->scan(self::MATCH_ASSIGNMENT)) !== null) return $this->createToken(self::T_ASSIGNMENT, $match);
107
+ else if (($match = $this->scanner->scan(self::MATCH_OPEN_BRACKET)) !== null) return $this->createToken(self::T_OPEN_BRACKET, $match);
108
+ else if (($match = $this->scanner->scan(self::MATCH_CLOSE_BRACKET)) !== null) return $this->createToken(self::T_CLOSE_BRACKET, $match);
109
+ else if (($match = $this->scanner->scan(self::MATCH_SINGLE_LINE_COMMENT)) !== null) return $this->createToken(self::T_SINGLE_LINE_COMMENT, $match);
110
+ else if (($match = $this->scanner->scan(self::MATCH_MULTIPLE_LINE_COMMENT)) !== null) return $this->createToken(self::T_MULTIPLE_LINE_COMMENT, $match);
111
+ else {
112
+ $e = new wfWAFParserSyntaxError(sprintf('Invalid character "%s" found on line %d, column %d',
113
+ $this->scanner->scanChar(), $this->scanner->getLine(), $this->scanner->getColumn()));
114
+ $e->setParseLine($this->scanner->getLine());
115
+ $e->setParseColumn($this->scanner->getColumn());
116
+ throw $e;
117
+ }
118
+ }
119
+ return false;
120
+ }
121
+
122
+ /**
123
+ * @param $type
124
+ * @param $value
125
+ * @return wfWAFLexerToken
126
+ */
127
+ protected function createToken($type, $value) {
128
+ return new wfWAFLexerToken($type, $value, $this->scanner->getLine(), $this->scanner->getColumn());
129
+ }
130
+
131
+ /**
132
+ * @return string
133
+ */
134
+ public function getRules() {
135
+ return $this->rules;
136
+ }
137
+
138
+ /**
139
+ * @param string $rules
140
+ */
141
+ public function setRules($rules) {
142
+ $this->rules = rtrim($rules);
143
+ }
144
+ }
145
+
146
+ /**
147
+ *
148
+ */
149
+ class wfWAFLexerToken {
150
+
151
+ private $type;
152
+ private $value;
153
+ private $line;
154
+ private $column;
155
+
156
+ /**
157
+ * wfWAFRuleToken constructor.
158
+ *
159
+ * @param $type
160
+ * @param $value
161
+ * @param $line
162
+ * @param $column
163
+ */
164
+ public function __construct($type, $value, $line, $column) {
165
+ $this->setType($type);
166
+ $this->setValue($value);
167
+ $this->setLine($line);
168
+ $this->setColumn($column);
169
+ }
170
+
171
+ /**
172
+ * @return string
173
+ */
174
+ public function getLowerCaseValue() {
175
+ return strtolower($this->getValue());
176
+ }
177
+
178
+ /**
179
+ * @return string
180
+ */
181
+ public function getUpperCaseValue() {
182
+ return strtoupper($this->getValue());
183
+ }
184
+
185
+ /**
186
+ * @return mixed
187
+ */
188
+ public function getType() {
189
+ return $this->type;
190
+ }
191
+
192
+ /**
193
+ * @param mixed $type
194
+ */
195
+ public function setType($type) {
196
+ $this->type = $type;
197
+ }
198
+
199
+ /**
200
+ * @return mixed
201
+ */
202
+ public function getValue() {
203
+ return $this->value;
204
+ }
205
+
206
+ /**
207
+ * @param mixed $value
208
+ */
209
+ public function setValue($value) {
210
+ $this->value = $value;
211
+ }
212
+
213
+ /**
214
+ * @return mixed
215
+ */
216
+ public function getLine() {
217
+ return $this->line;
218
+ }
219
+
220
+ /**
221
+ * @param mixed $line
222
+ */
223
+ public function setLine($line) {
224
+ $this->line = $line;
225
+ }
226
+
227
+ /**
228
+ * @return mixed
229
+ */
230
+ public function getColumn() {
231
+ return $this->column;
232
+ }
233
+
234
+ /**
235
+ * @param mixed $column
236
+ */
237
+ public function setColumn($column) {
238
+ $this->column = $column;
239
+ }
240
+ }
241
+
242
+
243
+ class wfWAFParserSyntaxError extends wfWAFException {
244
+
245
+ private $parseLine;
246
+ private $parseColumn;
247
+ private $token;
248
+
249
+ /**
250
+ * @return mixed
251
+ */
252
+ public function getToken() {
253
+ return $this->token;
254
+ }
255
+
256
+ /**
257
+ * @param mixed $token
258
+ */
259
+ public function setToken($token) {
260
+ $this->token = $token;
261
+ }
262
+
263
+ /**
264
+ * @return mixed
265
+ */
266
+ public function getParseLine() {
267
+ return $this->parseLine;
268
+ }
269
+
270
+ /**
271
+ * @param mixed $parseLine
272
+ */
273
+ public function setParseLine($parseLine) {
274
+ $this->parseLine = $parseLine;
275
+ }
276
+
277
+ /**
278
+ * @return mixed
279
+ */
280
+ public function getParseColumn() {
281
+ return $this->parseColumn;
282
+ }
283
+
284
+ /**
285
+ * @param mixed $parseColumn
286
+ */
287
+ public function setParseColumn($parseColumn) {
288
+ $this->parseColumn = $parseColumn;
289
+ }
290
+
291
+ }
292
+
293
+ class wfWAFBaseParser {
294
+
295
+ protected $tokens;
296
+ protected $index;
297
+ /** @var wfWAFLexerInterface */
298
+ protected $lexer;
299
+
300
+ public function __construct($lexer) {
301
+ $this->lexer = $lexer;
302
+ }
303
+
304
+ /**
305
+ * @param wfWAFLexerToken $token
306
+ * @param int $type
307
+ * @param string $message
308
+ * @throws wfWAFParserSyntaxError
309
+ */
310
+ protected function expectTokenTypeEquals($token, $type, $message = 'Wordfence WAF Syntax Error: Unexpected %s found on line %d, column %d. Expected %s.') {
311
+ if ($token->getType() !== $type) {
312
+ $this->triggerSyntaxError($token, sprintf($message, $token->getType(),
313
+ $token->getLine(), $token->getColumn(), $type));
314
+ }
315
+ }
316
+
317
+ /**
318
+ * @param wfWAFLexerToken $token
319
+ * @param array $types
320
+ * @param string $message
321
+ * @throws wfWAFParserSyntaxError
322
+ */
323
+ protected function expectTokenTypeInArray($token, $types, $message = 'Wordfence WAF Syntax Error: Unexpected %s found on line %d, column %d') {
324
+ if (!in_array($token->getType(), $types)) {
325
+ $this->triggerSyntaxError($token, sprintf($message, $token->getType(),
326
+ $token->getLine(), $token->getColumn()));
327
+ }
328
+ }
329
+
330
+ /**
331
+ * @param wfWAFLexerToken $token
332
+ * @param string $message
333
+ * @throws wfWAFParserSyntaxError
334
+ */
335
+ protected function triggerSyntaxError($token, $message = 'Wordfence WAF Syntax Error: Unexpected %s %s found on line %d, column %d') {
336
+ $e = new wfWAFParserSyntaxError(sprintf($message, $token->getType(), $token->getValue(),
337
+ $token->getLine(), $token->getColumn()));
338
+ $e->setToken($token);
339
+ $e->setParseLine($token->getLine());
340
+ $e->setParseColumn($token->getColumn());
341
+ throw $e;
342
+ }
343
+
344
+ /**
345
+ * @return wfWAFLexerToken
346
+ */
347
+ protected function currentToken() {
348
+ return $this->getToken($this->index);
349
+ }
350
+
351
+ /**
352
+ * @return bool|wfWAFLexerToken
353
+ */
354
+ protected function nextToken() {
355
+ $this->index++;
356
+ return $this->getToken($this->index);
357
+ }
358
+
359
+ /**
360
+ * @param string $message
361
+ * @return wfWAFLexerToken
362
+ * @throws wfWAFParserSyntaxError
363
+ */
364
+ protected function expectNextToken($message = 'Expected statement') {
365
+ $this->index++;
366
+ if ($token = $this->getToken($this->index)) {
367
+ return $token;
368
+ }
369
+ throw new wfWAFParserSyntaxError($message);
370
+ }
371
+
372
+ /**
373
+ * @param int $index
374
+ * @return mixed
375
+ */
376
+ protected function getToken($index) {
377
+ if (is_array($this->tokens) && array_key_exists($index, $this->tokens)) {
378
+ return $this->tokens[$index];
379
+ }
380
+ if ($token = $this->getLexer()->nextToken()) {
381
+ $this->tokens[$index] = $token;
382
+ return $this->tokens[$index];
383
+ }
384
+ return false;
385
+ }
386
+
387
+ /**
388
+ * @return wfWAFLexerInterface
389
+ */
390
+ public function getLexer() {
391
+ return $this->lexer;
392
+ }
393
+
394
+ /**
395
+ * @param wfWAFLexerInterface $lexer
396
+ */
397
+ public function setLexer($lexer) {
398
+ $this->lexer = $lexer;
399
+ }
400
+
401
+ /**
402
+ * @return mixed
403
+ */
404
+ public function getTokens() {
405
+ return $this->tokens;
406
+ }
407
+
408
+ /**
409
+ * @param mixed $tokens
410
+ */
411
+ public function setTokens($tokens) {
412
+ $this->tokens = $tokens;
413
+ }
414
+ }
415
+
416
+ /**
417
+ *
418
+ */
419
+ class wfWAFStringScanner {
420
+
421
+ private $string;
422
+ private $length;
423
+ private $pointer;
424
+ private $prevPointer;
425
+ private $match;
426
+ private $captures;
427
+
428
+ /**
429
+ * wfWAFStringScanner constructor.
430
+ * @param $string
431
+ */
432
+ public function __construct($string = null) {
433
+ if (is_string($string)) {
434
+ $this->setString($string);
435
+ }
436
+ }
437
+
438
+ /**
439
+ * @param $regex
440
+ * @return mixed
441
+ */
442
+ public function scan($regex) {
443
+ $remaining = $this->getRemainingString();
444
+ if ($this->regexMatch($regex, $remaining, $matches)) {
445
+ $matchLen = strlen($matches[0]);
446
+ if ($matchLen > 0 && strpos($remaining, $matches[0]) === 0) {
447
+ return $this->setState($matches, $this->getPointer() + $matchLen, $this->getPointer());
448
+ }
449
+ }
450
+ return $this->setState();
451
+ }
452
+
453
+ /**
454
+ * @param $regex
455
+ * @return int|null
456
+ */
457
+ public function skip($regex) {
458
+ return $this->scan($regex) ? strlen($this->getMatch()) : null;
459
+ }
460
+
461
+ /**
462
+ * @return mixed
463
+ */
464
+ public function scanChar() {
465
+ return $this->scan('/./s');
466
+ }
467
+
468
+ /**
469
+ * @param string $regex
470
+ * @return mixed
471
+ */
472
+ public function check($regex) {
473
+ $remaining = $this->getRemainingString();
474
+ if ($this->regexMatch($regex, $remaining, $matches)) {
475
+ $matchLen = strlen($matches[0]);
476
+ if ($matchLen > 0 && strpos($remaining, $matches[0]) === 0) {
477
+ return $this->setState($matches);
478
+ }
479
+ }
480
+ return $this->setState();
481
+ }
482
+
483
+ /**
484
+ * @param string $regex
485
+ * @param string $remaining
486
+ * @param $matches
487
+ * @return int
488
+ */
489
+ public function regexMatch($regex, $remaining, &$matches) {
490
+ // $startTime = microtime(true);
491
+ $result = preg_match($regex, $remaining, $matches);
492
+ // printf("%s took %f seconds\n", $regex, microtime(true) - $startTime);
493
+ return $result;
494
+ }
495
+
496
+ /**
497
+ * @return bool
498
+ */
499
+ public function eos() {
500
+ return $this->getPointer() === $this->getLength();
501
+ }
502
+
503
+ /**
504
+ * @return string
505
+ */
506
+ public function getRemainingString() {
507
+ return substr($this->getString(), $this->getPointer());
508
+ }
509
+
510
+ /**
511
+ * @return $this
512
+ */
513
+ public function reset() {
514
+ $this->setState(array(), 0, 0);
515
+ return $this;
516
+ }
517
+
518
+ /**
519
+ * The current line of the scanned string.
520
+ *
521
+ * @return int
522
+ */
523
+ public function getLine() {
524
+ if ($this->getPointer() + 1 > $this->getLength()) {
525
+ return substr_count($this->getString(), "\n") + 1;
526
+ }
527
+ return substr_count($this->getString(), "\n", 0, $this->getPointer() + 1) + 1;
528
+ }
529
+
530
+ /**
531
+ * The current column of the line of the scanned string.
532
+ *
533
+ * @return int
534
+ */
535
+ public function getColumn() {
536
+ return $this->getPointer() - ((int) strrpos(substr($this->getString(), 0, $this->getPointer() + 1), "\n")) + 1;
537
+ }
538
+
539
+ /**
540
+ * @param array $matches
541
+ * @param int|null $pointer
542
+ * @param int|null $prevPointer
543
+ * @return mixed
544
+ */
545
+ protected function setState($matches = array(), $pointer = null, $prevPointer = null) {
546
+ if ($pointer !== null) {
547
+ $this->setPointer($pointer);
548
+ }
549
+ if ($prevPointer !== null) {
550
+ $this->setPrevPointer($prevPointer);
551
+ }
552
+ if (is_array($matches)) {
553
+ $this->setCaptures(array_slice($matches, 1));
554
+ if (count($matches) > 0) {
555
+ $this->setMatch($matches[0]);
556
+ } else {
557
+ $this->setMatch(null);
558
+ }
559
+ } else {
560
+ $this->setMatch(null);
561
+ }
562
+ return $this->getMatch();
563
+ }
564
+
565
+ /**
566
+ * @return string
567
+ */
568
+ public function getString() {
569
+ return $this->string;
570
+ }
571
+
572
+ /**
573
+ * @param string $string
574
+ * @throws InvalidArgumentException
575
+ */
576
+ public function setString($string) {
577
+ if (!is_string($string)) {
578
+ throw new InvalidArgumentException(sprintf('String expected, got [%s]', gettype($string)));
579
+ }
580
+ $this->setLength(strlen($string));
581
+ $this->string = $string;
582
+ $this->reset();
583
+ }
584
+
585
+ /**
586
+ * @return int
587
+ */
588
+ public function getLength() {
589
+ return $this->length;
590
+ }
591
+
592
+ /**
593
+ * @param int $length
594
+ */
595
+ protected function setLength($length) {
596
+ $this->length = $length;
597
+ }
598
+
599
+ /**
600
+ * @param int $length
601
+ */
602
+ public function advancePointer($length) {
603
+ $this->setPointer($this->getPointer() + $length);
604
+ }
605
+
606
+ /**
607
+ * @return int
608
+ */
609
+ public function getPointer() {
610
+ return $this->pointer;
611
+ }
612
+
613
+ /**
614
+ * @param int $pointer
615
+ */
616
+ protected function setPointer($pointer) {
617
+ $this->pointer = $pointer;
618
+ }
619
+
620
+ /**
621
+ * @return int
622
+ */
623
+ public function getPrevPointer() {
624
+ return $this->prevPointer;
625
+ }
626
+
627
+ /**
628
+ * @param int $prevPointer
629
+ */
630
+ protected function setPrevPointer($prevPointer) {
631
+ $this->prevPointer = $prevPointer;
632
+ }
633
+
634
+ /**
635
+ * @return mixed
636
+ */
637
+ public function getMatch() {
638
+ return $this->match;
639
+ }
640
+
641
+ /**
642
+ * @param mixed $match
643
+ */
644
+ protected function setMatch($match) {
645
+ $this->match = $match;
646
+ }
647
+
648
+ /**
649
+ * @param null $index
650
+ * @return mixed
651
+ */
652
+ public function getCaptures($index = null) {
653
+ if (is_numeric($index)) {
654
+ return isset($this->captures[$index]) ? $this->captures[$index] : null;
655
+ }
656
+ return $this->captures;
657
+ }
658
+
659
+ /**
660
+ * @param mixed $captures
661
+ */
662
+ protected function setCaptures($captures) {
663
+ $this->captures = $captures;
664
+ }
665
+ }
666
+
667
+
vendor/wordfence/wf-waf/src/lib/parser/parser.php ADDED
@@ -0,0 +1,754 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once dirname(__FILE__) . '/lexer.php';
4
+
5
+ class wfWAFRuleParser extends wfWAFBaseParser {
6
+
7
+ /**
8
+ * @var wfWAF
9
+ */
10
+ private $waf;
11
+ private $parenCount = 0;
12
+
13
+ /**
14
+ * wfWAFRuleParser constructor.
15
+ * @param $lexer
16
+ * @param wfWAF $waf
17
+ */
18
+ public function __construct($lexer, $waf) {
19
+ parent::__construct($lexer);
20
+ $this->setWAF($waf);
21
+ }
22
+
23
+ /**
24
+ * @return array
25
+ * @throws wfWAFParserSyntaxError
26
+ * @throws wfWAFRuleParserSyntaxError
27
+ */
28
+ public function parse() {
29
+ $rules = array();
30
+ $scores = array();
31
+ $blacklistedParams = array();
32
+ $whitelistedParams = array();
33
+ $variables = array();
34
+ $this->index = -1;
35
+ while ($token = $this->nextToken()) {
36
+
37
+ // Rule parsing
38
+ if ($token->getType() == wfWAFRuleLexer::T_RULE_START) {
39
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
40
+
41
+ $comparisonGroup = $this->parseConditional();
42
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
43
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_RULE_COMPARISON_END);
44
+ $action = $this->parseAction();
45
+
46
+ $rules[] = new wfWAFRule(
47
+ $this->getWAF(),
48
+ $action->getRuleID(),
49
+ $action->getType(),
50
+ $action->getCategory(),
51
+ $action->getScore(),
52
+ $action->getDescription(),
53
+ $action->getAction(),
54
+ $comparisonGroup
55
+ );
56
+ }
57
+
58
+ // Score/config parsing
59
+ if ($token->getType() == wfWAFRuleLexer::T_IDENTIFIER) {
60
+ switch ($token->getValue()) {
61
+ case 'scores':
62
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_DOT);
63
+ $scoreCategoryToken = $this->expectNextToken();
64
+ $this->expectTokenTypeEquals($scoreCategoryToken, wfWAFRuleLexer::T_IDENTIFIER);
65
+
66
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
67
+
68
+ $scoreToken = $this->expectNextToken();
69
+ $this->expectTokenTypeEquals($scoreToken, wfWAFRuleLexer::T_NUMBER_LITERAL);
70
+ $scores[$scoreCategoryToken->getValue()] = $scoreToken->getValue();
71
+ break;
72
+
73
+ case 'blacklistParam':
74
+ $blacklistedParams[] = $this->parseURLParams();
75
+ break;
76
+
77
+ case 'whitelistParam':
78
+ $whitelistedParams[] = $this->parseURLParams();
79
+ break;
80
+
81
+ default:
82
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
83
+ $valueToken = $this->expectNextToken();
84
+ $this->expectTokenTypeInArray($valueToken, array(
85
+ wfWAFRuleLexer::T_SINGLE_STRING_LITERAL,
86
+ wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL,
87
+ wfWAFRuleLexer::T_NUMBER_LITERAL,
88
+ ));
89
+ if ($valueToken->getType() === wfWAFRuleLexer::T_SINGLE_STRING_LITERAL) {
90
+ $value = substr($valueToken->getValue(), 1, -1);
91
+ $value = str_replace("\\'", "'", $value);
92
+ } else if ($valueToken->getType() === wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL) {
93
+ $value = substr($valueToken->getValue(), 1, -1);
94
+ $value = str_replace('\\"', '"', $value);
95
+ } else {
96
+ $value = $valueToken->getValue();
97
+ }
98
+ $variables[$token->getValue()] = new wfWAFRuleVariable($this->getWAF(), $token->getValue(), $value);
99
+ break;
100
+ }
101
+ }
102
+ }
103
+
104
+ return array(
105
+ 'scores' => $scores,
106
+ 'blacklistedParams' => $blacklistedParams,
107
+ 'whitelistedParams' => $whitelistedParams,
108
+ 'variables' => $variables,
109
+ 'rules' => $rules,
110
+ );
111
+ }
112
+
113
+ /**
114
+ * @param array $vars
115
+ * @return string
116
+ */
117
+ public function renderRules($vars) {
118
+ $rules = '';
119
+ if (array_key_exists('scores', $vars)) {
120
+ foreach ($vars['scores'] as $category => $score) {
121
+ // scores.sqli = 100
122
+ $rules .= sprintf("scores.%s = %d\n", $category, $score);
123
+ }
124
+ $rules .= "\n";
125
+ }
126
+
127
+ $params = array(
128
+ 'blacklistParam' => 'blacklistedParams',
129
+ 'whitelistParam' => 'whitelistedParams',
130
+ );
131
+ foreach ($params as $action => $key) {
132
+ if (array_key_exists($key, $vars)) {
133
+ /** @var wfWAFRuleParserURLParam $urlParam */
134
+ foreach ($vars[$key] as $urlParam) {
135
+ $rules .= $urlParam->renderRule($action) . "\n";
136
+ }
137
+ $rules .= "\n";
138
+ }
139
+ }
140
+
141
+ if (array_key_exists('variables', $vars)) {
142
+ /** @var wfWAFRuleVariable $variable */
143
+ foreach ($vars['variables'] as $variableName => $variable) {
144
+ $rules .= sprintf("%s = %s\n", $variable->renderRule(), $variable->renderValue());
145
+ }
146
+ $rules .= "\n";
147
+ }
148
+
149
+ if (array_key_exists('rules', $vars)) {
150
+ /** @var wfWAFRule $rule */
151
+ foreach ($vars['rules'] as $rule) {
152
+ $rules .= $rule->renderRule() . "\n";
153
+ }
154
+ $rules .= "\n";
155
+ }
156
+ return $rules;
157
+ }
158
+
159
+ /**
160
+ * @param int $index
161
+ * @return mixed
162
+ */
163
+ public function getToken($index) {
164
+ if (is_array($this->tokens) && array_key_exists($index, $this->tokens)) {
165
+ return $this->tokens[$index];
166
+ }
167
+ if ($token = $this->getLexer()->nextToken()) {
168
+ $this->tokens[$index] = $token;
169
+ return $this->tokens[$index];
170
+ }
171
+ return false;
172
+ }
173
+
174
+ /**
175
+ * @return wfWAFRuleComparisonGroup
176
+ */
177
+ private function parseConditional() {
178
+ $comparisonGroup = new wfWAFRuleComparisonGroup();
179
+ while ($token = $this->nextToken()) {
180
+ switch ($token->getType()) {
181
+ case wfWAFRuleLexer::T_IDENTIFIER:
182
+ $comparisonGroup->add($this->parseComparison());
183
+ break;
184
+
185
+ case wfWAFRuleLexer::T_COMPARISON_OPERATOR:
186
+ $comparisonGroup->add(new wfWAFRuleLogicalOperator($token->getValue()));
187
+ break;
188
+
189
+ case wfWAFRuleLexer::T_OPEN_PARENTHESIS:
190
+ $this->parenCount++;
191
+ $comparisonGroup->add($this->parseConditional());
192
+ break;
193
+
194
+ case wfWAFRuleLexer::T_CLOSE_PARENTHESIS:
195
+ if ($this->parenCount === 0) {
196
+ $this->index--;
197
+ return $comparisonGroup;
198
+ }
199
+ $this->parenCount--;
200
+ return $comparisonGroup;
201
+ }
202
+
203
+ }
204
+ return $comparisonGroup;
205
+ }
206
+
207
+ private function parseComparison() {
208
+ /**
209
+ * @var wfWAFLexerToken $actionToken
210
+ * @var wfWAFLexerToken $expectedToken
211
+ */
212
+ $actionToken = $this->currentToken();
213
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
214
+ $value = $this->expectLiteral();
215
+
216
+ $subjects = array();
217
+ while (true) {
218
+ $commaToken = $this->nextToken();
219
+ if (!($commaToken && $commaToken->getType() === wfWAFRuleLexer::T_COMMA)) {
220
+ $this->index--;
221
+ break;
222
+ }
223
+ list($filters, $subject) = $this->parseFilters();
224
+ $subjects[] = new wfWAFRuleComparisonSubject($this->getWAF(), $subject, $filters);
225
+ }
226
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
227
+
228
+ $comparison = new wfWAFRuleComparison($this->getWAF(), $actionToken->getValue(), $value, $subjects);
229
+ return $comparison;
230
+ }
231
+
232
+ /**
233
+ * @return wfWAFRuleParserAction
234
+ */
235
+ private function parseAction() {
236
+ $action = new wfWAFRuleParserAction();
237
+
238
+ $actionToken = $this->expectNextToken();
239
+ $this->expectTokenTypeEquals($actionToken, wfWAFRuleLexer::T_IDENTIFIER);
240
+ $action->setAction($actionToken->getValue());
241
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
242
+
243
+ while (true) {
244
+ $token = $this->expectNextToken();
245
+ switch ($token->getType()) {
246
+ case wfWAFRuleLexer::T_IDENTIFIER:
247
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
248
+ $valueToken = $this->expectNextToken();
249
+ $this->expectTokenTypeInArray($valueToken, array(
250
+ wfWAFRuleLexer::T_SINGLE_STRING_LITERAL,
251
+ wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL,
252
+ wfWAFRuleLexer::T_NUMBER_LITERAL,
253
+ ));
254
+ $action->set($token->getValue(), $valueToken->getValue());
255
+ break;
256
+
257
+ case wfWAFRuleLexer::T_COMMA:
258
+ break;
259
+
260
+ case wfWAFRuleLexer::T_CLOSE_PARENTHESIS:
261
+ break 2;
262
+
263
+ default:
264
+ $this->triggerSyntaxError($token, sprintf('Wordfence WAF Rules Syntax Error: Unexpected %s found on line %d, column %d',
265
+ $token->getType(), $token->getLine(), $token->getColumn()));
266
+ }
267
+ }
268
+ return $action;
269
+ }
270
+
271
+ private function parseFilters() {
272
+ $filters = array();
273
+ $subject = null;
274
+ do {
275
+ $globalToken = $this->expectNextToken();
276
+ $this->expectTokenTypeEquals($globalToken, wfWAFRuleLexer::T_IDENTIFIER);
277
+ $parenToken = $this->expectNextToken();
278
+ switch ($parenToken->getType()) {
279
+ case wfWAFRuleLexer::T_DOT:
280
+ $this->index -= 2;
281
+ $subject = $this->parseSubject();
282
+ break 2;
283
+
284
+ case wfWAFRuleLexer::T_OPEN_PARENTHESIS:
285
+ $filters[] = $globalToken->getValue();
286
+ break;
287
+
288
+ default:
289
+ $this->triggerSyntaxError($parenToken,
290
+ sprintf('Wordfence WAF Rules Syntax Error: Unexpected %s found on line %d, column %d.',
291
+ $parenToken->getType(), $parenToken->getLine(), $parenToken->getColumn()));
292
+ }
293
+ } while (true);
294
+ if ($subject === null) {
295
+ throw new wfWAFParserSyntaxError('No subject supplied to filter');
296
+ }
297
+ for ($i = 0; $i < count($filters); $i++) {
298
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
299
+ }
300
+ return array($filters, $subject);
301
+ }
302
+
303
+ /**
304
+ * @throws wfWAFParserSyntaxError
305
+ */
306
+ private function parseSubject() {
307
+ $globalToken = $this->expectNextToken();
308
+ $this->expectTokenTypeEquals($globalToken, wfWAFRuleLexer::T_IDENTIFIER);
309
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_DOT);
310
+ $globalToken2 = $this->expectNextToken();
311
+ $this->expectTokenTypeEquals($globalToken2, wfWAFRuleLexer::T_IDENTIFIER);
312
+ $subject = array(
313
+ $globalToken->getValue() . '.' . $globalToken2->getValue(),
314
+ );
315
+ while (true) {
316
+ $dotToken = $this->expectNextToken();
317
+ if ($dotToken->getType() !== wfWAFRuleLexer::T_DOT) {
318
+ $this->index--;
319
+ break;
320
+ }
321
+ $paramToken = $this->expectNextToken();
322
+ $this->expectTokenTypeEquals($paramToken, wfWAFRuleLexer::T_IDENTIFIER);
323
+ $subject[] = $paramToken->getValue();
324
+ }
325
+ if (count($subject) === 1) {
326
+ list($subject) = $subject;
327
+ }
328
+ return $subject;
329
+ }
330
+
331
+ /**
332
+ * @return wfWAFRuleParserURLParam
333
+ * @throws wfWAFParserSyntaxError
334
+ */
335
+ private function parseURLParams() {
336
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
337
+
338
+ $urlParam = new wfWAFRuleParserURLParam();
339
+ while (true) {
340
+ $token = $this->expectNextToken();
341
+ switch ($token->getType()) {
342
+ case wfWAFRuleLexer::T_IDENTIFIER:
343
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
344
+ if ($token->getValue() === 'url') {
345
+ $url = $this->expectLiteral();
346
+ $urlParam->setUrl($url);
347
+ } else if ($token->getValue() === 'param') {
348
+ $subject = $this->parseSubject();
349
+ $urlParam->setParam(wfWAFRuleComparison::getSubjectKey($subject));
350
+ } else if ($token->getValue() === 'rules') {
351
+ $rules = $this->expectLiteral();
352
+ $urlParam->setRules($rules);
353
+ }
354
+
355
+ break;
356
+
357
+ case wfWAFRuleLexer::T_COMMA:
358
+ break;
359
+
360
+ case wfWAFRuleLexer::T_CLOSE_PARENTHESIS:
361
+ break 2;
362
+
363
+ default:
364
+ $this->triggerSyntaxError($token, sprintf('Wordfence WAF Rules Syntax Error: Unexpected %s found on line %d, column %d',
365
+ $token->getType(), $token->getLine(), $token->getColumn()));
366
+ }
367
+ }
368
+ return $urlParam;
369
+ }
370
+
371
+ /**
372
+ * @return mixed|string
373
+ * @throws wfWAFRuleParserSyntaxError
374
+ */
375
+ private function expectLiteral() {
376
+ $expectedToken = $this->expectNextToken();
377
+ $this->expectTokenTypeInArray($expectedToken, array(
378
+ wfWAFRuleLexer::T_SINGLE_STRING_LITERAL,
379
+ wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL,
380
+ wfWAFRuleLexer::T_IDENTIFIER,
381
+ wfWAFRuleLexer::T_NUMBER_LITERAL,
382
+ wfWAFRuleLexer::T_OPEN_BRACKET,
383
+ ));
384
+ if ($expectedToken->getType() === wfWAFRuleLexer::T_SINGLE_STRING_LITERAL) {
385
+ // Remove quotes, strip slashes
386
+ $value = substr($expectedToken->getValue(), 1, -1);
387
+ $value = str_replace("\\'", "'", $value);
388
+ } else if ($expectedToken->getType() === wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL) {
389
+ // Remove quotes, strip slashes
390
+ $value = substr($expectedToken->getValue(), 1, -1);
391
+ $value = str_replace('\\"', '"', $value);
392
+ } else if ($expectedToken->getType() === wfWAFRuleLexer::T_IDENTIFIER) {
393
+ // Remove quotes, strip slashes
394
+ $value = new wfWAFRuleVariable($this->getWAF(), $expectedToken->getValue());
395
+ } else if ($expectedToken->getType() === wfWAFRuleLexer::T_OPEN_BRACKET) {
396
+ $value = array();
397
+ while (true) {
398
+ $nextToken = $this->expectNextToken();
399
+ if ($nextToken->getType() === wfWAFRuleLexer::T_CLOSE_BRACKET) {
400
+ break;
401
+ }
402
+ if ($nextToken->getType() === wfWAFRuleLexer::T_COMMA) {
403
+ continue;
404
+ }
405
+ $this->index--;
406
+ $value[] = $this->expectLiteral();
407
+ }
408
+ } else {
409
+ $value = $expectedToken->getValue();
410
+ }
411
+ return $value;
412
+ }
413
+
414
+ /**
415
+ * @param wfWAFLexerToken $token
416
+ * @return bool
417
+ */
418
+ protected function isCommentToken($token) {
419
+ return $token->getType() === wfWAFRuleLexer::T_MULTIPLE_LINE_COMMENT || $token->getType() === wfWAFRuleLexer::T_SINGLE_LINE_COMMENT;
420
+ }
421
+
422
+ /**
423
+ * @return wfWAF
424
+ */
425
+ public function getWAF() {
426
+ return $this->waf;
427
+ }
428
+
429
+ /**
430
+ * @param wfWAF $waf
431
+ */
432
+ public function setWAF($waf) {
433
+ $this->waf = $waf;
434
+ }
435
+ }
436
+
437
+ class wfWAFRuleParserAction {
438
+
439
+ private $ruleID;
440
+ private $type;
441
+ private $category;
442
+ private $score;
443
+ private $description;
444
+ private $action;
445
+
446
+ /**
447
+ * @param string $param
448
+ * @param mixed $value
449
+ */
450
+ public function set($param, $value) {
451
+ $propLinkTable = array(
452
+ 'id' => 'ruleID',
453
+ );
454
+ if (array_key_exists($param, $propLinkTable)) {
455
+ $param = $propLinkTable[$param];
456
+ }
457
+ if (property_exists($this, $param)) {
458
+ $this->$param = trim($value, '\'"');
459
+ }
460
+ }
461
+
462
+ /**
463
+ * @return mixed
464
+ */
465
+ public function getRuleID() {
466
+ return $this->ruleID;
467
+ }
468
+
469
+ /**
470
+ * @param mixed $ruleID
471
+ */
472
+ public function setRuleID($ruleID) {
473
+ $this->ruleID = $ruleID;
474
+ }
475
+
476
+ /**
477
+ * @return mixed
478
+ */
479
+ public function getType() {
480
+ return $this->type;
481
+ }
482
+
483
+ /**
484
+ * @param mixed $type
485
+ */
486
+ public function setType($type) {
487
+ $this->type = $type;
488
+ }
489
+
490
+ /**
491
+ * @return mixed
492
+ */
493
+ public function getCategory() {
494
+ return $this->category;
495
+ }
496
+
497
+ /**
498
+ * @param mixed $category
499
+ */
500
+ public function setCategory($category) {
501
+ $this->category = $category;
502
+ }
503
+
504
+ /**
505
+ * @return mixed
506
+ */
507
+ public function getScore() {
508
+ return $this->score;
509
+ }
510
+
511
+ /**
512
+ * @param mixed $score
513
+ */
514
+ public function setScore($score) {
515
+ $this->score = $score;
516
+ }
517
+
518
+ /**
519
+ * @return mixed
520
+ */
521
+ public function getDescription() {
522
+ return $this->description;
523
+ }
524
+
525
+ /**
526
+ * @param mixed $description
527
+ */
528
+ public function setDescription($description) {
529
+ $this->description = $description;
530
+ }
531
+
532
+ /**
533
+ * @return mixed
534
+ */
535
+ public function getAction() {
536
+ return $this->action;
537
+ }
538
+
539
+ /**
540
+ * @param mixed $action
541
+ */
542
+ public function setAction($action) {
543
+ $this->action = $action;
544
+ }
545
+ }
546
+
547
+
548
+ class wfWAFRuleParserURLParam {
549
+ /**
550
+ * @var string
551
+ */
552
+ private $url;
553
+ /**
554
+ * @var string
555
+ */
556
+ private $param;
557
+ /**
558
+ * @var null
559
+ */
560
+ private $rules;
561
+
562
+ /**
563
+ * @param string $param
564
+ * @param mixed $value
565
+ */
566
+ public function set($param, $value) {
567
+ if (property_exists($this, $param)) {
568
+ $this->$param = trim($value, '\'"');
569
+ }
570
+ }
571
+
572
+ /**
573
+ * @param string $url
574
+ * @param string $param
575
+ * @param null $rules
576
+ */
577
+ public function __construct($url = null, $param = null, $rules = null) {
578
+ $this->url = $url;
579
+ $this->param = $param;
580
+ $this->rules = $rules;
581
+ }
582
+
583
+ /**
584
+ * Return format:
585
+ * blacklistParam(url='/\/uploadify\.php$/i', param=request.fileNames.Filedata, rules=[3, 14])
586
+ *
587
+ * @param string $action
588
+ * @return string
589
+ */
590
+ public function renderRule($action) {
591
+ return sprintf('%s(url=%s, param=%s%s)', $action, wfWAFRule::exportString($this->getUrl()), $this->renderParam($this->getParam()),
592
+ $this->getRules() ? ', rules=[' . join(', ', array_map('intval', $this->getRules())) . ']' : '');
593
+ }
594
+
595
+ /**
596
+ * @param string $param
597
+ * @return mixed
598
+ */
599
+ private function renderParam($param) {
600
+ return str_replace(array('[', ']'), array('.', ''), $param);
601
+ }
602
+
603
+ /**
604
+ * @return string
605
+ */
606
+ public function getUrl() {
607
+ return $this->url;
608
+ }
609
+
610
+ /**
611
+ * @param string $url
612
+ */
613
+ public function setUrl($url) {
614
+ $this->url = $url;
615
+ }
616
+
617
+ /**
618
+ * @return string
619
+ */
620
+ public function getParam() {
621
+ return $this->param;
622
+ }
623
+
624
+ /**
625
+ * @param string $param
626
+ */
627
+ public function setParam($param) {
628
+ $this->param = $param;
629
+ }
630
+
631
+ /**
632
+ * @return null
633
+ */
634
+ public function getRules() {
635
+ return $this->rules;
636
+ }
637
+
638
+ /**
639
+ * @param null $rules
640
+ */
641
+ public function setRules($rules) {
642
+ $this->rules = $rules;
643
+ }
644
+ }
645
+
646
+ class wfWAFRuleParserSyntaxError extends wfWAFParserSyntaxError {
647
+
648
+ private $token;
649
+
650
+ /**
651
+ * @return mixed
652
+ */
653
+ public function getToken() {
654
+ return $this->token;
655
+ }
656
+
657
+ /**
658
+ * @param mixed $token
659
+ */
660
+ public function setToken($token) {
661
+ $this->token = $token;
662
+ }
663
+ }
664
+
665
+ class wfWAFRuleVariable {
666
+ /**
667
+ * @var string
668
+ */
669
+ private $name;
670
+ /**
671
+ * @var mixed|null
672
+ */
673
+ private $value;
674
+ /**
675
+ * @var wfWAF
676
+ */
677
+ private $waf;
678
+
679
+
680
+ /**
681
+ * wfWAFRuleVariable constructor.
682
+ * @param wfWAF $waf
683
+ * @param string $name
684
+ * @param mixed $value
685
+ */
686
+ public function __construct($waf, $name, $value = null) {
687
+ $this->waf = $waf;
688
+ $this->name = $name;
689
+ $this->value = $value;
690
+ }
691
+
692
+ public function render() {
693
+ return sprintf('new %s($this, %s, %s)', get_class($this),
694
+ var_export($this->getName(), true), var_export($this->getValue(), true));
695
+ }
696
+
697
+ public function renderRule() {
698
+ return sprintf('%s', $this->getName());
699
+ }
700
+
701
+ public function renderValue() {
702
+ return wfWAFRule::exportString($this);
703
+ }
704
+
705
+ public function __toString() {
706
+ $value = $this->getValue();
707
+ if (is_string($value)) {
708
+ return $value;
709
+ }
710
+ return (string) $this->getWAF()->getVariable($this->getName());
711
+ }
712
+
713
+ /**
714
+ * @return string
715
+ */
716
+ public function getName() {
717
+ return $this->name;
718
+ }
719
+
720
+ /**
721
+ * @param string $name
722
+ */
723
+ public function setName($name) {
724
+ $this->name = $name;
725
+ }
726
+
727
+ /**
728
+ * @return mixed|null
729
+ */
730
+ public function getValue() {
731
+ return $this->value;
732
+ }
733
+
734
+ /**
735
+ * @param mixed|null $value
736
+ */
737
+ public function setValue($value) {
738
+ $this->value = $value;
739
+ }
740
+
741
+ /**
742
+ * @return wfWAF
743
+ */
744
+ public function getWAF() {
745
+ return $this->waf;
746
+ }
747
+
748
+ /**
749
+ * @param wfWAF $waf
750
+ */
751
+ public function setWAF($waf) {
752
+ $this->waf = $waf;
753
+ }
754
+ }
vendor/wordfence/wf-waf/src/lib/parser/sqli.php ADDED
@@ -0,0 +1,2971 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wfWAFSQLiParser extends wfWAFBaseParser {
4
+
5
+ const FLAG_PARSE_MYSQL_PORTABLE_COMMENTS = wfWAFSQLiLexer::FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS;
6
+
7
+ /**
8
+ * @param string $param
9
+ * @return bool
10
+ */
11
+ public static function testForSQLi($param) {
12
+ static $instance;
13
+ static $tests;
14
+ if (!$instance) {
15
+ $instance = new self(new wfWAFSQLiLexer());
16
+ }
17
+ if (!$tests) {
18
+ // SQL statement and token count for lexer
19
+ $tests = array(
20
+ array('%s', 1),
21
+ array('SELECT * FROM t WHERE i = %s ', 8),
22
+ array("SELECT * FROM t WHERE i = '%s' ", 8),
23
+ array('SELECT * FROM t WHERE i = "%s" ', 8),
24
+ array('SELECT * FROM t WHERE i = (%s) ', 10),
25
+ array("SELECT * FROM t WHERE i = ('%s') ", 10),
26
+ array('SELECT * FROM t WHERE i = ("%s") ', 10),
27
+ array('SELECT * FROM t WHERE i = ((%s)) ', 12),
28
+ array("SELECT * FROM t WHERE i = (('%s')) ", 12),
29
+ array('SELECT * FROM t WHERE i = (("%s")) ', 12),
30
+ array('SELECT * FROM t WHERE i = (((%s))) ', 14),
31
+ array("SELECT * FROM t WHERE i = ((('%s'))) ", 14),
32
+ array('SELECT * FROM t WHERE i = ((("%s"))) ', 14),
33
+
34
+ array('SELECT * FROM t WHERE i = %s and j = (1
35
+ ) ', 14),
36
+ array("SELECT * FROM t WHERE i = '%s' and j = (1
37
+ ) ", 14),
38
+ array('SELECT * FROM t WHERE i = "%s" and j = (1
39
+ ) ', 14),
40
+
41
+ array('SELECT MATCH(t) AGAINST (%s) from t ', 11),
42
+ array("SELECT MATCH(t) AGAINST ('%s') from t ", 11),
43
+ array('SELECT MATCH(t) AGAINST ("%s") from t ', 11),
44
+
45
+ // array('SELECT CASE WHEN %s THEN 1 ELSE 0 END from t ', 11),
46
+ // array("SELECT CASE WHEN '%s' THEN 1 ELSE 0 END from t ", 11),
47
+ // array('SELECT CASE WHEN "%s" THEN 1 ELSE 0 END from t ', 11),
48
+ //
49
+ // array('SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END) from t ', 15),
50
+ // array("SELECT (CASE WHEN ('%s') THEN 1 ELSE 0 END) from t ", 15),
51
+ // array('SELECT (CASE WHEN ("%s") THEN 1 ELSE 0 END) from t ', 15),
52
+
53
+ array('SELECT * FROM (select %s) ', 7),
54
+ array("SELECT * FROM (select '%s') ", 7),
55
+ array('SELECT * FROM (select "%s") ', 7),
56
+ array('SELECT * FROM (select (%s)) ', 9),
57
+ array("SELECT * FROM (select ('%s')) ", 9),
58
+ array('SELECT * FROM (select ("%s")) ', 9),
59
+ array('SELECT * FROM (select ((%s))) ', 11),
60
+ array("SELECT * FROM (select (('%s'))) ", 11),
61
+ array('SELECT * FROM (select (("%s"))) ', 11),
62
+ //
63
+ // array('SELECT * FROM t JOIN t2 on i = %s ', 10),
64
+ // array("SELECT * FROM t JOIN t2 on i = '%s' ", 10),
65
+ // array('SELECT * FROM t JOIN t2 on i = "%s" ', 10),
66
+ // array('SELECT * FROM t JOIN t2 on i = (%s) ', 12),
67
+ // array("SELECT * FROM t JOIN t2 on i = ('%s') ", 12),
68
+ // array('SELECT * FROM t JOIN t2 on i = ("%s") ', 12),
69
+ // array('SELECT * FROM t JOIN t2 on i = ((%s)) ', 14),
70
+ // array("SELECT * FROM t JOIN t2 on i = (('%s')) ", 14),
71
+ // array('SELECT * FROM t JOIN t2 on i = (("%s")) ', 14),
72
+ // array('SELECT * FROM t JOIN t2 on i = (((%s))) ', 16),
73
+ // array("SELECT * FROM t JOIN t2 on i = ((('%s'))) ", 16),
74
+ // array('SELECT * FROM t JOIN t2 on i = ((("%s"))) ', 16),
75
+
76
+ array('SELECT * FROM %s ', 4),
77
+ array('INSERT INTO t (col) VALUES (%s) ', 10),
78
+ array("INSERT INTO t (col) VALUES ('%s') ", 10),
79
+ array('INSERT INTO t (col) VALUES ("%s") ', 10),
80
+ array('UPDATE t1 SET col1 = %s ', 6),
81
+ array('UPDATE t1 SET col1 = \'%s\' ', 6),
82
+ );
83
+ }
84
+ $lexerFlags = array(0, wfWAFSQLiLexer::FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS);
85
+ foreach ($lexerFlags as $flags) {
86
+ foreach ($tests as $test) {
87
+ // $startTime = microtime(true);
88
+ list($sql, $expectedTokenCount) = $test;
89
+ try {
90
+ $instance->setFlags($flags);
91
+ $instance->setSubject(sprintf($sql, $param));
92
+ if (($instance->hasMoreThanNumTokens($expectedTokenCount) && $instance->evaluate())
93
+ || $instance->hasMultiplePortableCommentVersions()) {
94
+ // printf("%s took %f seconds\n", $sql, microtime(true) - $startTime);
95
+ return true;
96
+ }
97
+ // printf("%s took %f seconds\n", $sql, microtime(true) - $startTime);
98
+ } catch (wfWAFParserSyntaxError $e) {
99
+
100
+ }
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+
106
+ private $subject;
107
+
108
+ /**
109
+ * @var int
110
+ */
111
+ private $flags;
112
+ /** @var wfWAFSQLiLexer */
113
+ protected $lexer;
114
+ private $portableCommentVersions = array();
115
+
116
+ private $intervalUnits = array(
117
+ 'SECOND',
118
+ 'MINUTE',
119
+ 'HOUR',
120
+ 'DAY_SYM',
121
+ 'WEEK',
122
+ 'MONTH',
123
+ 'QUARTER',
124
+ 'YEAR',
125
+ 'SECOND_MICROSECOND',
126
+ 'MINUTE_MICROSECOND',
127
+ 'MINUTE_SECOND',
128
+ 'HOUR_MICROSECOND',
129
+ 'HOUR_SECOND',
130
+ 'HOUR_MINUTE',
131
+ 'DAY_MICROSECOND',
132
+ 'DAY_SECOND',
133
+ 'DAY_MINUTE',
134
+ 'DAY_HOUR',
135
+ 'YEAR_MONTH',
136
+ );
137
+
138
+ private $keywords = array(
139
+ 'ID',
140
+ 'TIME',
141
+ 'DATE',
142
+ 'SQLTIME',
143
+ 'ACCESSIBLE',
144
+ 'ADD',
145
+ 'ALL',
146
+ 'ALTER',
147
+ 'ANALYZE',
148
+ 'AND',
149
+ 'AS',
150
+ 'ASC',
151
+ 'ASENSITIVE',
152
+ 'BEFORE',
153
+ 'BETWEEN',
154
+ 'BIGINT',
155
+ 'BINARY',
156
+ 'BLOB',
157
+ 'BOTH',
158
+ 'BY',
159
+ 'CALL',
160
+ 'CASCADE',
161
+ 'CASE',
162
+ 'CHANGE',
163
+ 'CHAR',
164
+ 'CHARACTER',
165
+ 'CHECK',
166
+ 'COLLATE',
167
+ 'COLUMN',
168
+ 'CONDITION',
169
+ 'CONSTRAINT',
170
+ 'CONTINUE',
171
+ 'CONVERT',
172
+ 'CREATE',
173
+ 'CROSS',
174
+ 'CURRENT_DATE',
175
+ 'CURRENT_TIME',
176
+ 'CURRENT_TIMESTAMP',
177
+ 'CURRENT_USER',
178
+ 'CURSOR',
179
+ 'DATABASE',
180
+ 'DATABASES',
181
+ 'DAY_HOUR',
182
+ 'DAY_MICROSECOND',
183
+ 'DAY_MINUTE',
184
+ 'DAY_SECOND',
185
+ 'DEC',
186
+ 'DECIMAL',
187
+ 'DECLARE',
188
+ 'DEFAULT',
189
+ 'DELAYED',
190
+ 'DELETE',
191
+ 'DESC',
192
+ 'DESCRIBE',
193
+ 'DETERMINISTIC',
194
+ 'DISTINCT',
195
+ 'DISTINCTROW',
196
+ 'DIV',
197
+ 'DOUBLE',
198
+ 'DROP',
199
+ 'DUAL',
200
+ 'EACH',
201
+ 'ELSE',
202
+ 'ELSEIF',
203
+ 'ENCLOSED',
204
+ 'ESCAPED',
205
+ 'EXISTS',
206
+ 'EXIT',
207
+ 'EXPLAIN',
208
+ 'FALSE',
209
+ 'FETCH',
210
+ 'FLOAT',
211
+ 'FLOAT4',
212
+ 'FLOAT8',
213
+ 'FOR',
214
+ 'FORCE',
215
+ 'FOREIGN',
216
+ 'FROM',
217
+ 'FULLTEXT',
218
+ 'GRANT',
219
+ 'GROUP',
220
+ 'HAVING',
221
+ 'HIGH_PRIORITY',
222
+ 'HOUR_MICROSECOND',
223
+ 'HOUR_MINUTE',
224
+ 'HOUR_SECOND',
225
+ 'IF',
226
+ 'IGNORE',
227
+ 'IN',
228
+ 'INDEX',
229
+ 'INFILE',
230
+ 'INNER',
231
+ 'INOUT',
232
+ 'INSENSITIVE',
233
+ 'INSERT',
234
+ 'INT',
235
+ 'INT1',
236
+ 'INT2',
237
+ 'INT3',
238
+ 'INT4',
239
+ 'INT8',
240
+ 'INTEGER',
241
+ 'INTERVAL',
242
+ 'INTO',
243
+ 'IS',
244
+ 'ITERATE',
245
+ 'JOIN',
246
+ 'KEY',
247
+ 'KEYS',
248
+ 'KILL',
249
+ 'LEADING',
250
+ 'LEAVE',
251
+ 'LEFT',
252
+ 'LIKE',
253
+ 'LIMIT',
254
+ 'LINEAR',
255
+ 'LINES',
256
+ 'LOAD',
257
+ 'LOCALTIME',
258
+ 'LOCALTIMESTAMP',
259
+ 'LOCK',
260
+ 'LONG',
261
+ 'LONGBLOB',
262
+ 'LONGTEXT',
263
+ 'LOOP',
264
+ 'LOW_PRIORITY',
265
+ 'MASTER_SSL_VERIFY_SERVER_CERT',
266
+ 'MATCH',
267
+ 'MEDIUMBLOB',
268
+ 'MEDIUMINT',
269
+ 'MEDIUMTEXT',
270
+ 'MIDDLEINT',
271
+ 'MINUTE_MICROSECOND',
272
+ 'MINUTE_SECOND',
273
+ 'MOD',
274
+ 'MODIFIES',
275
+ 'NATURAL',
276
+ 'NOT',
277
+ 'NO_WRITE_TO_BINLOG',
278
+ 'NULL',
279
+ 'NUMERIC',
280
+ 'ON',
281
+ 'OPTIMIZE',
282
+ 'OPTION',
283
+ 'OPTIONALLY',
284
+ 'OR',
285
+ 'ORDER',
286
+ 'OUT',
287
+ 'OUTER',
288
+ 'OUTFILE',
289
+ 'PRECISION',
290
+ 'PRIMARY',
291
+ 'PROCEDURE',
292
+ 'PURGE',
293
+ 'RANGE',
294
+ 'READ',
295
+ 'READS',
296
+ 'READ_WRITE',
297
+ 'REAL',
298
+ 'REFERENCES',
299
+ 'REGEXP',
300
+ 'RELEASE',
301
+ 'RENAME',
302
+ 'REPEAT',
303
+ 'REPLACE',
304
+ 'REQUIRE',
305
+ 'RESTRICT',
306
+ 'RETURN',
307
+ 'REVOKE',
308
+ 'RIGHT',
309
+ 'RLIKE',
310
+ 'SCHEMA',
311
+ 'SCHEMAS',
312
+ 'SECOND_MICROSECOND',
313
+ 'SELECT',
314
+ 'SENSITIVE',
315
+ 'SEPARATOR',
316
+ 'SET',
317
+ 'SHOW',
318
+ 'SMALLINT',
319
+ 'SPATIAL',
320
+ 'SPECIFIC',
321
+ 'SQL',
322
+ 'SQLEXCEPTION',
323
+ 'SQLSTATE',
324
+ 'SQLWARNING',
325
+ 'SQL_BIG_RESULT',
326
+ 'SQL_CALC_FOUND_ROWS',
327
+ 'SQL_SMALL_RESULT',
328
+ 'SSL',
329
+ 'STARTING',
330
+ 'STRAIGHT_JOIN',
331
+ 'TABLE',
332
+ 'TERMINATED',
333
+ 'THEN',
334
+ 'TINYBLOB',
335
+ 'TINYINT',
336
+ 'TINYTEXT',
337
+ 'TO',
338
+ 'TRAILING',
339
+ 'TRIGGER',
340
+ 'TRUE',
341
+ 'UNDO',
342
+ 'UNION',
343
+ 'UNIQUE',
344
+ 'UNLOCK',
345
+ 'UNSIGNED',
346
+ 'UPDATE',
347
+ 'USAGE',
348
+ 'USE',
349
+ 'USING',
350
+ 'UTC_DATE',
351
+ 'UTC_TIME',
352
+ 'UTC_TIMESTAMP',
353
+ 'VALUES',
354
+ 'VARBINARY',
355
+ 'VARCHAR',
356
+ 'VARCHARACTER',
357
+ 'VARYING',
358
+ 'WHEN',
359
+ 'WHERE',
360
+ 'WHILE',
361
+ 'WITH',
362
+ 'WRITE',
363
+ 'XOR',
364
+ 'YEAR_MONTH',
365
+ 'ZEROFILL',
366
+ 'ACCESSIBLE',
367
+ 'LINEAR',
368
+ 'MASTER_SSL_VERIFY_SERVER_CERT',
369
+ 'RANGE',
370
+ 'READ_ONLY',
371
+ 'READ_WRITE',
372
+ );
373
+
374
+ private $numberFunctions = array(
375
+ 'ABS',
376
+ 'ACOS',
377
+ 'ASIN',
378
+ 'ATAN2',
379
+ 'ATAN',
380
+ 'CEIL',
381
+ 'CEILING',
382
+ 'CONV',
383
+ 'COS',
384
+ 'COT',
385
+ 'CRC32',
386
+ 'DEGREES',
387
+ 'EXP',
388
+ 'FLOOR',
389
+ 'LN',
390
+ 'LOG10',
391
+ 'LOG2',
392
+ 'LOG',
393
+ 'MOD',
394
+ 'PI',
395
+ 'POW',
396
+ 'POWER',
397
+ 'RADIANS',
398
+ 'RAND',
399
+ 'ROUND',
400
+ 'SIGN',
401
+ 'SIN',
402
+ 'SQRT',
403
+ 'TAN',
404
+ 'TRUNCATE',
405
+ );
406
+
407
+ private $charFunctions = array(
408
+ 'ASCII_SYM',
409
+ 'BIN',
410
+ 'BIT_LENGTH',
411
+ 'CHAR_LENGTH',
412
+ 'CHAR',
413
+ 'CONCAT_WS',
414
+ 'CONCAT',
415
+ 'ELT',
416
+ 'EXPORT_SET',
417
+ 'FIELD',
418
+ 'FIND_IN_SET',
419
+ 'FORMAT',
420
+ 'FROM_BASE64',
421
+ 'HEX',
422
+ 'INSERT',
423
+ 'INSTR',
424
+ 'LEFT',
425
+ 'LENGTH',
426
+ 'LOAD_FILE',
427
+ 'LOCATE',
428
+ 'LOWER',
429
+ 'LPAD',
430
+ 'LTRIM',
431
+ 'MAKE_SET',
432
+ 'MID',
433
+ 'OCT',
434
+ 'ORD',
435
+ 'QUOTE',
436
+ 'REPEAT',
437
+ 'REPLACE',
438
+ 'REVERSE',
439
+ 'RIGHT',
440
+ 'RPAD',
441
+ 'RTRIM',
442
+ 'SOUNDEX',
443
+ 'SPACE',
444
+ 'STRCMP',
445
+ 'SUBSTRING_INDEX',
446
+ 'SUBSTRING',
447
+ 'TO_BASE64',
448
+ 'TRIM',
449
+ 'UNHEX',
450
+ 'UPPER',
451
+ 'WEIGHT_STRING',
452
+ );
453
+
454
+ private $timeFunctions = array(
455
+ 'ADDDATE',
456
+ 'ADDTIME',
457
+ 'CONVERT_TZ',
458
+ 'CURDATE',
459
+ 'CURTIME',
460
+ 'DATE_ADD',
461
+ 'DATE_FORMAT',
462
+ 'DATE_SUB',
463
+ 'DATE_SYM',
464
+ 'DATEDIFF',
465
+ 'DAYNAME',
466
+ 'DAYOFMONTH',
467
+ 'DAYOFWEEK',
468
+ 'DAYOFYEAR',
469
+ 'EXTRACT',
470
+ 'FROM_DAYS',
471
+ 'FROM_UNIXTIME',
472
+ 'GET_FORMAT',
473
+ 'HOUR',
474
+ 'LAST_DAY ',
475
+ 'MAKEDATE',
476
+ 'MAKETIME ',
477
+ 'MICROSECOND',
478
+ 'MINUTE',
479
+ 'MONTH',
480
+ 'MONTHNAME',
481
+ 'NOW',
482
+ 'PERIOD_ADD',
483
+ 'PERIOD_DIFF',
484
+ 'QUARTER',
485
+ 'SEC_TO_TIME',
486
+ 'SECOND',
487
+ 'STR_TO_DATE',
488
+ 'SUBTIME',
489
+ 'SYSDATE',
490
+ 'TIME_FORMAT',
491
+ 'TIME_TO_SEC',
492
+ 'TIME_SYM',
493
+ 'TIMEDIFF',
494
+ 'TIMESTAMP',
495
+ 'TIMESTAMPADD',
496
+ 'TIMESTAMPDIFF',
497
+ 'TO_DAYS',
498
+ 'TO_SECONDS',
499
+ 'UNIX_TIMESTAMP',
500
+ 'UTC_DATE',
501
+ 'UTC_TIME',
502
+ 'UTC_TIMESTAMP',
503
+ 'WEEK',
504
+ 'WEEKDAY',
505
+ 'WEEKOFYEAR',
506
+ 'YEAR',
507
+ 'YEARWEEK',
508
+ );
509
+
510
+ private $otherFunctions = array(
511
+ 'MAKE_SET', 'LOAD_FILE',
512
+ 'IF', 'IFNULL',
513
+ 'AES_ENCRYPT', 'AES_DECRYPT',
514
+ 'DECODE', 'ENCODE',
515
+ 'DES_DECRYPT', 'DES_ENCRYPT',
516
+ 'ENCRYPT', 'MD5',
517
+ 'OLD_PASSWORD', 'PASSWORD',
518
+ 'BENCHMARK', 'CHARSET', 'COERCIBILITY', 'COLLATION', 'CONNECTION_ID',
519
+ 'CURRENT_USER', 'DATABASE', 'SCHEMA', 'USER', 'SESSION_USER', 'SYSTEM_USER',
520
+ 'VERSION_SYM',
521
+ 'FOUND_ROWS', 'LAST_INSERT_ID', 'DEFAULT',
522
+ 'GET_LOCK', 'RELEASE_LOCK', 'IS_FREE_LOCK', 'IS_USED_LOCK', 'MASTER_POS_WAIT',
523
+ 'INET_ATON', 'INET_NTOA',
524
+ 'NAME_CONST',
525
+ 'SLEEP',
526
+ 'UUID',
527
+ 'VALUES',
528
+ );
529
+
530
+ private $groupFunctions = array(
531
+ 'AVG', 'COUNT', 'MAX_SYM', 'MIN_SYM', 'SUM',
532
+ 'BIT_AND', 'BIT_OR', 'BIT_XOR',
533
+ 'GROUP_CONCAT',
534
+ 'STD', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP',
535
+ 'VAR_POP', 'VAR_SAMP', 'VARIANCE',
536
+ );
537
+
538
+
539
+ /**
540
+ * @param wfWAFSQLiLexer $lexer
541
+ * @param string $subject
542
+ * @param int $flags
543
+ */
544
+ public function __construct($lexer, $subject = null, $flags = 0) {
545
+ parent::__construct($lexer);
546
+ $this->setSubject($subject);
547
+ $this->setFlags($flags);
548
+ }
549
+
550
+ protected function _init() {
551
+ $this->portableCommentVersions = array();
552
+ $this->index = -1;
553
+ }
554
+
555
+ /**
556
+ * @param int $num
557
+ * @return bool
558
+ */
559
+ public function hasMoreThanNumTokens($num) {
560
+ $this->_init();
561
+
562
+ $savePoint = $this->index;
563
+ for ($i = 0; $i <= $num; $i++) {
564
+ if (!$this->nextToken()) {
565
+ $this->index = $savePoint;
566
+ return false;
567
+ }
568
+ }
569
+ $this->index = $savePoint;
570
+ return true;
571
+ }
572
+
573
+ /**
574
+ * @return bool
575
+ */
576
+ public function evaluate() {
577
+ try {
578
+ $this->parse();
579
+ return true;
580
+ } catch (wfWAFParserSyntaxError $e) {
581
+ return false;
582
+ }
583
+ }
584
+
585
+ public function parse() {
586
+ $this->_init();
587
+ if (
588
+ $this->parseSelectStatement()
589
+ || $this->parseInsertStatement()
590
+ || $this->parseUpdateStatement()
591
+ // || $this->parseDeleteStatement()
592
+ // || $this->parseReplaceStatement()
593
+ ) {
594
+ $token = $this->nextToken();
595
+ if ($token && !$this->isTokenOfType($token, wfWAFSQLiLexer::SEMICOLON)) {
596
+ $this->triggerSyntaxError($this->currentToken());
597
+ }
598
+ } else {
599
+ $this->triggerSyntaxError($this->expectNextToken());
600
+ }
601
+ }
602
+
603
+ /**
604
+ * @param int $index
605
+ * @return bool
606
+ */
607
+ protected function getToken($index) {
608
+ if (array_key_exists($index, $this->tokens)) {
609
+ return $this->tokens[$index];
610
+ }
611
+ while ($token = $this->getLexer()->nextToken()) {
612
+ if (!$this->isCommentToken($token)) {
613
+ $this->tokens[$index] = $token;
614
+ return $this->tokens[$index];
615
+ }
616
+ }
617
+ return false;
618
+ }
619
+
620
+
621
+ /**
622
+ * @param wfWAFLexerToken $token
623
+ * @return bool
624
+ */
625
+ public function isCommentToken($token) {
626
+ if ($this->isTokenOfType($token, wfWAFSQLiLexer::MYSQL_PORTABLE_COMMENT_START)) {
627
+ $this->portableCommentVersions[(int) preg_replace('/[^\d]/', '', $token->getValue())] = 1;
628
+ }
629
+
630
+ return $this->isTokenOfType($token, array(
631
+ wfWAFSQLiLexer::SINGLE_LINE_COMMENT,
632
+ wfWAFSQLiLexer::MULTI_LINE_COMMENT,
633
+ wfWAFSQLiLexer::MYSQL_PORTABLE_COMMENT_START,
634
+ wfWAFSQLiLexer::MYSQL_PORTABLE_COMMENT_END,
635
+ ));
636
+ }
637
+
638
+ public function hasMultiplePortableCommentVersions() {
639
+ return count($this->portableCommentVersions) > 1;
640
+ }
641
+
642
+ /**
643
+ * Expects the next token to be an identifier with the supplied case-insensitive value
644
+ *
645
+ * @param $keyword
646
+ * @return wfWAFLexerToken
647
+ * @throws wfWAFParserSyntaxError
648
+ */
649
+ protected function expectNextIdentifierEquals($keyword) {
650
+ $nextToken = $this->expectNextToken();
651
+ $this->expectTokenTypeEquals($nextToken, wfWAFSQLiLexer::UNQUOTED_IDENTIFIER);
652
+ if ($nextToken->getLowerCaseValue() !== strtolower($keyword)) {
653
+ $this->triggerSyntaxError($nextToken);
654
+ }
655
+ return $nextToken;
656
+ }
657
+
658
+ private function parseSelectStatement() {
659
+ $startIndex = $this->index;
660
+ $hasSelect = false;
661
+ while ($this->parseSelectExpression()) {
662
+ $hasSelect = true;
663
+ $savePoint = $this->index;
664
+ if ($this->isIdentifierWithValue($this->nextToken(), 'union')) {
665
+ $hasSelect = false;
666
+ if (!$this->isIdentifierWithValue($this->nextToken(), 'all')) {
667
+ $this->index--;
668
+ }
669
+ continue;
670
+ }
671
+ $this->index = $savePoint;
672
+ break;
673
+ }
674
+ if ($hasSelect) {
675
+ return true;
676
+ }
677
+ $this->index = $startIndex;
678
+ return false;
679
+ }
680
+
681
+ private function parseSelectExpression() {
682
+ $savePoint = $this->index;
683
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) &&
684
+ $this->parseSelectExpression() &&
685
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
686
+ ) {
687
+ return true;
688
+ }
689
+ $this->index = $savePoint;
690
+
691
+ if ($this->parseSelect()) {
692
+ if ($this->parseFrom()) {
693
+ $this->parseWhere();
694
+ $this->parseProcedure();
695
+ $this->parseGroupBy();
696
+ $this->parseHaving();
697
+ }
698
+ $this->parseOrderBy();
699
+ $this->parseLimit();
700
+
701
+ return true;
702
+ }
703
+ return false;
704
+ }
705
+
706
+ /**
707
+ * @throws wfWAFParserSyntaxError
708
+ */
709
+ private function parseSelect() {
710
+ $startPoint = $this->index;
711
+ if ($this->isIdentifierWithValue($this->nextToken(), 'select')) {
712
+ $optionalSelectParamsRegex = '/ALL|DISTINCT(?:ROW)?|HIGH_PRIORITY|MAX_STATEMENT_TIME|STRAIGHT_JOIN|SQL_SMALL_RESULT|SQL_BIG_RESULT|SQL_BUFFER_RESULT|SQL_CACHE|SQL_NO_CACHE|SQL_CALC_FOUND_ROWS/i';
713
+ while (true) {
714
+ $savePoint = $this->index;
715
+ $token = $this->nextToken();
716
+ if ($token) {
717
+ $value = $token->getLowerCaseValue();
718
+ if (preg_match($optionalSelectParamsRegex, $value)) {
719
+ if ($value == 'max_statement_time') {
720
+ $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFSQLiLexer::EQUALS_SYMBOL);
721
+ $this->expectTokenTypeInArray($this->expectNextToken(), array(
722
+ wfWAFSQLiLexer::INTEGER_LITERAL,
723
+ wfWAFSQLiLexer::BINARY_NUMBER_LITERAL,
724
+ wfWAFSQLiLexer::HEX_NUMBER_LITERAL,
725
+ wfWAFSQLiLexer::BINARY_NUMBER_LITERAL,
726
+ ));
727
+ }
728
+ continue;
729
+ }
730
+ }
731
+ $this->index = $savePoint;
732
+ break;
733
+ }
734
+ return $this->parseSelectList();
735
+ }
736
+ $this->index = $startPoint;
737
+ return false;
738
+ }
739
+
740
+ /**
741
+ * @throws wfWAFParserSyntaxError
742
+ */
743
+ private function parseSelectList() {
744
+ $startPoint = $this->index;
745
+ $hasSelects = false;
746
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::ASTERISK)) {
747
+ $hasSelects = true;
748
+ if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
749
+ // Just SELECT * [FROM ...]
750
+ $this->index--;
751
+ return true;
752
+ }
753
+ } else {
754
+ $this->index = $startPoint;
755
+ }
756
+ while ($this->parseDisplayedColumn()) {
757
+ $hasSelects = true;
758
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
759
+ continue;
760
+ }
761
+ $this->index--;
762
+ break;
763
+ }
764
+ if ($hasSelects) {
765
+ return true;
766
+ }
767
+ $this->index = $startPoint;
768
+ return false;
769
+ }
770
+
771
+ private function parseDisplayedColumn() {
772
+ /*
773
+ ( table_spec DOT ASTERISK )
774
+ |
775
+ ( column_spec (alias)? )
776
+ |
777
+ ( bit_expr (alias)? )
778
+ */
779
+ $savePoint = $this->index;
780
+ if ($this->parseTableSpec() &&
781
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::DOT) &&
782
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::ASTERISK)
783
+ ) {
784
+ return true;
785
+ }
786
+ $this->index = $savePoint;
787
+
788
+ $savePoint = $this->index;
789
+ if ($this->parseExpression()) {
790
+ $this->parseAlias();
791
+ return true;
792
+ }
793
+ $this->index = $savePoint;
794
+
795
+ $savePoint = $this->index;
796
+ if ($this->parseColumnSpec()) {
797
+ $this->parseAlias();
798
+ return true;
799
+ }
800
+ $this->index = $savePoint;
801
+
802
+ return false;
803
+ }
804
+
805
+ /**
806
+ * @return bool
807
+ */
808
+ private function parseExpressionList() {
809
+ $startIndex = $this->index;
810
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) {
811
+ $hasExpressions = false;
812
+ while ($this->parseExpression()) {
813
+ $hasExpressions = true;
814
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
815
+ continue;
816
+ }
817
+ $this->index--;
818
+ break;
819
+ }
820
+ if ($hasExpressions && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
821
+ return true;
822
+ }
823
+ }
824
+ $this->index = $startIndex;
825
+ return false;
826
+ }
827
+
828
+ private function parseExpression() {
829
+ // Combines these:
830
+ // exp_factor3 ( AND_SYM exp_factor3 )* ;
831
+ // expression: exp_factor1 ( OR_SYM exp_factor1 )* ;
832
+ // exp_factor1: exp_factor2 ( XOR exp_factor2 )* ;
833
+ // exp_factor2: exp_factor3 ( AND_SYM exp_factor3 )* ;
834
+
835
+ $savePoint = $this->index;
836
+ $hasExpression = false;
837
+ while ($this->parseExpressionFactor3()) {
838
+ $hasExpression = true;
839
+ $savePoint2 = $this->index;
840
+ $token = $this->nextToken();
841
+ if ($this->isOrToken($token) || $this->isAndToken($token) || $this->isIdentifierWithValue($token, 'xor')) {
842
+ continue;
843
+ }
844
+ $this->index = $savePoint2;
845
+ break;
846
+ }
847
+ if ($hasExpression) {
848
+ return true;
849
+ }
850
+ $this->index = $savePoint;
851
+ return false;
852
+ }
853
+
854
+ private function parseExpressionFactor3() {
855
+ // (NOT_SYM)? exp_factor4 ;
856
+ $savePoint = $this->index;
857
+ if (!$this->isNotSymbolToken($this->nextToken())) {
858
+ $this->index--;
859
+ }
860
+ if ($this->parseExpressionFactor4()) {
861
+ return true;
862
+ }
863
+ $this->index = $savePoint;
864
+ return false;
865
+ }
866
+
867
+ private function parseExpressionFactor4() {
868
+ // bool_primary ( IS_SYM (NOT_SYM)? (boolean_literal|NULL_SYM) )? ;
869
+ $savePoint = $this->index;
870
+ if ($this->parseBoolPrimary()) {
871
+ $savePoint = $this->index;
872
+ if ($this->isIdentifierWithValue($this->nextToken(), 'is')) {
873
+ if (!$this->isNotSymbolToken($this->nextToken())) {
874
+ $this->index--;
875
+ }
876
+ if ($this->isIdentifierWithValue($this->nextToken(), array(
877
+ 'true', 'false', 'null',
878
+ ))
879
+ ) {
880
+ return true;
881
+ }
882
+ }
883
+ $this->index = $savePoint;
884
+ return true;
885
+ }
886
+ $this->index = $savePoint;
887
+ return false;
888
+ }
889
+
890
+ /**
891
+ * @return bool
892
+ */
893
+ private function parseBoolPrimary() {
894
+ $startIndex = $this->index;
895
+ $token = $this->nextToken();
896
+ if ($token) {
897
+ $hasNot = false;
898
+ if ($this->isNotSymbolToken($token)) {
899
+ $hasNot = true;
900
+ $token = $this->nextToken();
901
+ }
902
+ $val = $token->getLowerCaseValue();
903
+ if ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) {
904
+ if ($val === 'exists' && $this->parseSubquery()) {
905
+ return true;
906
+ } else if ($hasNot) {
907
+ $this->index = $startIndex;
908
+ return false;
909
+ }
910
+ }
911
+ if (!$hasNot) {
912
+ $this->index = $startIndex;
913
+ }
914
+ }
915
+
916
+ if ($this->parsePredicate()) {
917
+ $savePoint = $this->index;
918
+ $opToken = $this->nextToken();
919
+ if ($opToken) {
920
+ switch ($opToken->getType()) {
921
+ case wfWAFSQLiLexer::EQUALS_SYMBOL:
922
+ case wfWAFSQLiLexer::LESS_THAN:
923
+ case wfWAFSQLiLexer::GREATER_THAN:
924
+ case wfWAFSQLiLexer::LESS_THAN_EQUAL_TO:
925
+ case wfWAFSQLiLexer::GREATER_THAN_EQUAL_TO:
926
+ case wfWAFSQLiLexer::NOT_EQUALS:
927
+ case wfWAFSQLiLexer::SET_VAR:
928
+ $savePoint2 = $this->index;
929
+ if ($this->isIdentifierWithValue($this->nextToken(), array(
930
+ 'any', 'all'
931
+ )) &&
932
+ $this->parseSubquery()
933
+ ) {
934
+ return true;
935
+ }
936
+ $this->index = $savePoint2;
937
+
938
+ $savePoint2 = $this->index;
939
+ if ($this->testForSubquery() && $this->parseSubquery()) {
940
+ return true;
941
+ }
942
+ $this->index = $savePoint2;
943
+
944
+ if ($this->parsePredicate()) {
945
+ return true;
946
+ }
947
+ $this->index = $startIndex;
948
+ return false;
949
+ }
950
+ }
951
+ $this->index = $savePoint;
952
+ return true;
953
+ }
954
+ $this->index = $startIndex;
955
+ return false;
956
+ }
957
+
958
+ private function parsePredicate() {
959
+ $startIndex = $this->index;
960
+ if ($this->parseBitExpression()) {
961
+ $savePoint = $this->index;
962
+ $token = $this->nextToken();
963
+ if ($token) {
964
+ if ($hasNot = $this->isNotSymbolToken($token)) {
965
+ $token = $this->nextToken();
966
+ if (!$token) {
967
+ $this->index = $startIndex;
968
+ return false;
969
+ }
970
+ }
971
+ $val = $token->getLowerCaseValue();
972
+
973
+ if ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) {
974
+ switch ($val) {
975
+ case 'in':
976
+ if ($this->parseSubquery() || $this->parseExpressionList()) {
977
+ return true;
978
+ }
979
+ break;
980
+
981
+ case 'between':
982
+ if ($this->parseBitExpression() && $this->isIdentifierWithValue($this->nextToken(), 'and') &&
983
+ $this->parsePredicate()
984
+ ) {
985
+ return true;
986
+ }
987
+ break;
988
+
989
+ case 'sounds':
990
+ if ($this->isIdentifierWithValue($this->nextToken(), 'like') &&
991
+ $this->parseBitExpression()
992
+ ) {
993
+ return true;
994
+ }
995
+ break;
996
+
997
+ case 'like':
998
+ case 'rlike':
999
+ if ($this->parseSimpleExpression()) {
1000
+ // We've got a LIKE statement at this point
1001
+ $savePoint = $this->index;
1002
+ if ($this->isIdentifierWithValue($this->nextToken(), 'escape') &&
1003
+ $this->parseSimpleExpression()
1004
+ ) {
1005
+ return true;
1006
+ }
1007
+ $this->index = $savePoint;
1008
+ return true;
1009
+ }
1010
+ break;
1011
+
1012
+ case 'regexp':
1013
+ if ($this->parseBitExpression()) {
1014
+ return true;
1015
+ }
1016
+ break;
1017
+
1018
+ default:
1019
+ if ($hasNot) {
1020
+ $this->index = $startIndex;
1021
+ return false;
1022
+ }
1023
+ break;
1024
+ }
1025
+ }
1026
+ }
1027
+ $this->index = $savePoint;
1028
+ return true;
1029
+ }
1030
+ $this->index = $startIndex;
1031
+ return false;
1032
+ }
1033
+
1034
+ /**
1035
+ * @return bool
1036
+ */
1037
+ private function parseBitExpression() {
1038
+ // factor1 ( VERTBAR factor1 )? ;
1039
+ $savePoint = $this->index;
1040
+ if ($this->parseBitExprFactor5()) {
1041
+ $savePoint = $this->index;
1042
+ $token = $this->nextToken();
1043
+ if (($this->isTokenOfType($token, array(
1044
+ wfWAFSQLiLexer::BIT_OR,
1045
+ wfWAFSQLiLexer::BIT_AND,
1046
+ wfWAFSQLiLexer::BIT_XOR,
1047
+ wfWAFSQLiLexer::BIT_LEFT_SHIFT,
1048
+ wfWAFSQLiLexer::BIT_RIGHT_SHIFT,
1049
+ wfWAFSQLiLexer::BIT_INVERSION,
1050
+ wfWAFSQLiLexer::PLUS,
1051
+ wfWAFSQLiLexer::MINUS,
1052
+ wfWAFSQLiLexer::ASTERISK,
1053
+ wfWAFSQLiLexer::DIVISION,
1054
+ wfWAFSQLiLexer::MOD,
1055
+ ))
1056
+ ||
1057
+ $this->isIdentifierWithValue($token, array(
1058
+ 'div', 'mod'
1059
+ ))) &&
1060
+ $this->parseBitExpression()
1061
+ ) {
1062
+ return true;
1063
+ }
1064
+ $this->index = $savePoint;
1065
+ return true;
1066
+ }
1067
+ $this->index = $savePoint;
1068
+ return false;
1069
+ }
1070
+
1071
+
1072
+ private function parseBitExprFactor5() {
1073
+ // factor6 ( (PLUS|MINUS) interval_expr )? ;
1074
+ $savePoint = $this->index;
1075
+ if ($this->parseBitExprFactor6()) {
1076
+ $savePoint = $this->index;
1077
+ if ($this->isTokenOfType($this->nextToken(), array(
1078
+ wfWAFSQLiLexer::PLUS,
1079
+ wfWAFSQLiLexer::MINUS,
1080
+ )) && $this->parseIntervalExpression()
1081
+ ) {
1082
+ return true;
1083
+ }
1084
+ $this->index = $savePoint;
1085
+ return true;
1086
+ }
1087
+ $this->index = $savePoint;
1088
+ return false;
1089
+ }
1090
+
1091
+ private function parseBitExprFactor6() {
1092
+ // (PLUS | MINUS | NEGATION | BINARY) simple_expr
1093
+ // | simple_expr ;
1094
+
1095
+ $startPoint = $this->index;
1096
+ $savePoint = $this->index;
1097
+ while (
1098
+ ($token = $this->nextToken()) &&
1099
+ (
1100
+ $this->isTokenOfType($token, array(
1101
+ wfWAFSQLiLexer::PLUS,
1102
+ wfWAFSQLiLexer::MINUS,
1103
+ )) ||
1104
+ ($this->isTokenOfType($token, wfWAFSQLiLexer::BIT_INVERSION)) ||
1105
+ ($this->isIdentifierWithValue($token, 'BINARY'))
1106
+ )
1107
+ ) {
1108
+ $savePoint = $this->index;
1109
+ }
1110
+ $this->index = $savePoint;
1111
+
1112
+ if ($this->parseSimpleExpression()) {
1113
+ return true;
1114
+ }
1115
+ $this->index = $startPoint;
1116
+ return false;
1117
+
1118
+ }
1119
+
1120
+ /**
1121
+ * literal_value
1122
+ * | column_spec
1123
+ * | function_call
1124
+ * | USER_VAR
1125
+ * | expression_list
1126
+ * | (ROW_SYM expression_list)
1127
+ * | subquery
1128
+ * | EXISTS subquery
1129
+ * | match_against_statement
1130
+ * | case_when_statement
1131
+ * | interval_expr
1132
+ *
1133
+ * @return bool
1134
+ */
1135
+ private function parseSimpleExpression() {
1136
+ $startPoint = $this->index;
1137
+ $simple = ($parseLiteral = $this->parseLiteral()) ||
1138
+ ($parseMatchAgainst = $this->parseMatchAgainst()) ||
1139
+ ($parseFunctionCall = $this->parseFunctionCall()) ||
1140
+ ($parseVariable = $this->parseVariable()) ||
1141
+ ($parseExpressionList = $this->parseExpressionList()) ||
1142
+ ($parseSubquery = $this->parseSubquery()) ||
1143
+ ($parseExistsSubquery = $this->parseExistsSubquery()) ||
1144
+ ($parseCaseWhen = $this->parseCaseWhen()) ||
1145
+ ($parseIntervalExpression = $this->parseIntervalExpression()) ||
1146
+ ($parseColumnSpec = $this->parseColumnSpec());
1147
+
1148
+ if ($simple) {
1149
+ $token = $this->nextToken();
1150
+ if ($token && $token->getLowerCaseValue() == 'collate') {
1151
+ $savePoint = $this->index;
1152
+ if ($this->parseCollationName()) {
1153
+ return true;
1154
+ }
1155
+ $this->index = $savePoint;
1156
+ } else {
1157
+ $this->index--;
1158
+ }
1159
+ return true;
1160
+ }
1161
+ $this->index = $startPoint;
1162
+ return false;
1163
+ }
1164
+
1165
+ /**
1166
+ * @return bool
1167
+ */
1168
+ private function parseLiteral() {
1169
+ $startIndex = $this->index;
1170
+ $savePoint = $this->index;
1171
+ while ($this->isTokenOfType($this->nextToken(), array(
1172
+ wfWAFSQLiLexer::PLUS,
1173
+ wfWAFSQLiLexer::MINUS,
1174
+ ))) {
1175
+ $savePoint = $this->index;
1176
+ }
1177
+ $this->index = $savePoint;
1178
+
1179
+ $nextToken = $this->nextToken();
1180
+ if ($nextToken) {
1181
+ switch ($nextToken->getType()) {
1182
+ case wfWAFSQLiLexer::INTEGER_LITERAL:
1183
+ case wfWAFSQLiLexer::BINARY_NUMBER_LITERAL:
1184
+ case wfWAFSQLiLexer::HEX_NUMBER_LITERAL:
1185
+ case wfWAFSQLiLexer::REAL_NUMBER_LITERAL:
1186
+ return true;
1187
+ // Allow concatenation: 'test' 'test' is valid
1188
+ case wfWAFSQLiLexer::DOUBLE_STRING_LITERAL:
1189
+ case wfWAFSQLiLexer::SINGLE_STRING_LITERAL:
1190
+ $savePoint = $this->index;
1191
+ while ($this->isTokenOfType($this->nextToken(), array(
1192
+ wfWAFSQLiLexer::DOUBLE_STRING_LITERAL,
1193
+ wfWAFSQLiLexer::SINGLE_STRING_LITERAL
1194
+ ))) {
1195
+ $savePoint = $this->index;
1196
+ }
1197
+ $this->index = $savePoint;
1198
+ return true;
1199
+
1200
+ case wfWAFSQLiLexer::UNQUOTED_IDENTIFIER:
1201
+ if ($nextToken->getLowerCaseValue() === 'null') {
1202
+ return true;
1203
+ }
1204
+ break;
1205
+ }
1206
+ }
1207
+ $this->index = $startIndex;
1208
+ return false;
1209
+ }
1210
+
1211
+ /**
1212
+ * @return bool
1213
+ */
1214
+ private function parseColumnSpec() {
1215
+ $savePoint = $this->index;
1216
+ if ($this->parseTableSpec()) {
1217
+ $savePoint = $this->index;
1218
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::DOT)) {
1219
+ $nextToken = $this->nextToken();
1220
+ if ($nextToken && ($nextToken->getType() == wfWAFSQLiLexer::UNQUOTED_IDENTIFIER ||
1221
+ $nextToken->getType() == wfWAFSQLiLexer::QUOTED_IDENTIFIER)
1222
+ ) {
1223
+ return true;
1224
+ }
1225
+ $this->index = $savePoint;
1226
+ return false;
1227
+ }
1228
+
1229
+ $this->index = $savePoint;
1230
+ return true;
1231
+ }
1232
+ $this->index = $savePoint;
1233
+ return false;
1234
+ }
1235
+
1236
+ /**
1237
+ * CAST_SYM LPAREN expression AS_SYM cast_data_type RPAREN )
1238
+ * | ( CONVERT_SYM LPAREN expression COMMA cast_data_type RPAREN )
1239
+ * | ( CONVERT_SYM LPAREN expression USING_SYM transcoding_name RPAREN )
1240
+ * | ( group_functions LPAREN ( ASTERISK | ALL | DISTINCT )? bit_expr RPAREN )
1241
+ *
1242
+ * @return bool
1243
+ */
1244
+ private function parseFunctionCall() {
1245
+ $startPoint = $this->index;
1246
+ $functionToken = $this->nextToken();
1247
+ if ($functionToken && $functionToken->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) {
1248
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) {
1249
+ switch ($functionToken->getLowerCaseValue()) {
1250
+ case 'cast':
1251
+ if ($this->parseExpression() &&
1252
+ $this->isIdentifierWithValue($this->nextToken(), 'as') &&
1253
+ $this->parseCastDataType() &&
1254
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1255
+ ) {
1256
+ return true;
1257
+ }
1258
+ break;
1259
+
1260
+ case 'convert':
1261
+ if ($this->parseExpression()) {
1262
+ $savePoint = $this->index;
1263
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA) &&
1264
+ $this->parseCastDataType() &&
1265
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1266
+ ) {
1267
+ return true;
1268
+ }
1269
+ $this->index = $savePoint;
1270
+ $savePoint = $this->index;
1271
+ if ($this->isIdentifierWithValue($this->nextToken(), 'using') &&
1272
+ $this->parseTranscodingName() &&
1273
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1274
+ ) {
1275
+ return true;
1276
+ }
1277
+ $this->index = $savePoint;
1278
+ }
1279
+ break;
1280
+
1281
+ default:
1282
+ $savePoint = $this->index;
1283
+ if (in_array($functionToken->getUpperCaseValue(), $this->groupFunctions)) {
1284
+ $token = $this->nextToken();
1285
+ if (!$this->isIdentifierWithValue($token, array(
1286
+ 'all', 'distinct',
1287
+ )) && !$this->isTokenOfType($token, wfWAFSQLiLexer::ASTERISK)
1288
+ ) {
1289
+ $this->index--;
1290
+ }
1291
+ $this->parseBitExpression();
1292
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
1293
+ return true;
1294
+ }
1295
+ }
1296
+ $this->index = $savePoint;
1297
+
1298
+ while ($this->parseExpression()) {
1299
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
1300
+ continue;
1301
+ }
1302
+ $this->index--;
1303
+ break;
1304
+ }
1305
+
1306
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
1307
+ return true;
1308
+ }
1309
+ break;
1310
+ }
1311
+ }
1312
+ }
1313
+ $this->index = $startPoint;
1314
+ return false;
1315
+ }
1316
+
1317
+ /**
1318
+ * BINARY (INTEGER_NUM)?
1319
+ * | CHAR (INTEGER_NUM)?
1320
+ * | DATE_SYM
1321
+ * | DATETIME
1322
+ * | DECIMAL_SYM ( INTEGER_NUM (COMMA INTEGER_NUM)? )?
1323
+ * | SIGNED_SYM (INTEGER_SYM)?
1324
+ * | TIME_SYM
1325
+ * | UNSIGNED_SYM (INTEGER_SYM)?
1326
+ *
1327
+ * @return bool
1328
+ */
1329
+ private function parseCastDataType() {
1330
+ $startPoint = $this->index;
1331
+ $token = $this->nextToken();
1332
+ if ($this->isKeywordToken($token)) {
1333
+ switch ($token->getLowerCaseValue()) {
1334
+ case 'binary':
1335
+ case 'char':
1336
+ $savePoint = $this->index;
1337
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL)) {
1338
+ return true;
1339
+ }
1340
+ $this->index = $savePoint;
1341
+ return true;
1342
+
1343
+ case 'date':
1344
+ case 'datetime':
1345
+ case 'time':
1346
+ return true;
1347
+
1348
+ case 'signed':
1349
+ case 'unsigned':
1350
+ if (!$this->isIdentifierWithValue($this->nextToken(), 'integer')) {
1351
+ $this->index--;
1352
+ }
1353
+ return true;
1354
+
1355
+ case 'decimal':
1356
+ $savePoint = $this->index;
1357
+ while ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL)) {
1358
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
1359
+ continue;
1360
+ }
1361
+ $this->index--;
1362
+ return true;
1363
+ }
1364
+ $this->index = $savePoint;
1365
+ return true;
1366
+ }
1367
+ }
1368
+ $this->index = $startPoint;
1369
+ return false;
1370
+ }
1371
+
1372
+ private function parseTranscodingName() {
1373
+ $savePoint = $this->index;
1374
+ $token = $this->nextToken();
1375
+ if ($token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) {
1376
+ return false;
1377
+ }
1378
+ $this->index = $savePoint;
1379
+ return false;
1380
+ }
1381
+
1382
+ private function parseVariable() {
1383
+ $nextToken = $this->nextToken();
1384
+ if ($nextToken && $nextToken->getType() === wfWAFSQLiLexer::VARIABLE) {
1385
+ return true;
1386
+ }
1387
+ $this->index--;
1388
+ return false;
1389
+ }
1390
+
1391
+ /**
1392
+ * @return bool
1393
+ */
1394
+ private function parseSubquery() {
1395
+ $startIndex = $this->index;
1396
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) &&
1397
+ $this->parseSelectStatement() &&
1398
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1399
+ ) {
1400
+ return true;
1401
+ }
1402
+ $this->index = $startIndex;
1403
+ return false;
1404
+ }
1405
+
1406
+ private function testForSubquery() {
1407
+ $startIndex = $this->index;
1408
+ $nextToken = $this->nextToken();
1409
+ if ($nextToken && $nextToken->getType() === wfWAFSQLiLexer::OPEN_PARENTHESIS) {
1410
+ $selectToken = $this->nextToken();
1411
+ if ($this->isIdentifierWithValue($selectToken, 'select')) {
1412
+ $this->index = $startIndex;
1413
+ return true;
1414
+ }
1415
+ }
1416
+ $this->index = $startIndex;
1417
+ return false;
1418
+ }
1419
+
1420
+ /**
1421
+ *
1422
+ *
1423
+ * @return bool
1424
+ */
1425
+ private function parseExistsSubquery() {
1426
+ $startIndex = $this->index;
1427
+ $existsToken = $this->nextToken();
1428
+ if ($this->isIdentifierWithValue($existsToken, 'exists')) {
1429
+ if ($this->parseSubquery()) {
1430
+ return true;
1431
+ }
1432
+ }
1433
+ $this->index = $startIndex;
1434
+ return false;
1435
+ }
1436
+
1437
+ /**
1438
+ * MATCH (column_spec (COMMA column_spec)* ) AGAINST (expression (search_modifier)? )
1439
+ *
1440
+ * @return bool
1441
+ */
1442
+ private function parseMatchAgainst() {
1443
+ $startIndex = $this->index;
1444
+ if ($this->isIdentifierWithValue($this->nextToken(), 'match')) {
1445
+ $savePoint = $this->index;
1446
+ if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) {
1447
+ $this->index = $savePoint;
1448
+ }
1449
+ $hasColumns = false;
1450
+ while ($this->parseColumnSpec()) {
1451
+ $hasColumns = true;
1452
+ $savePoint = $this->index;
1453
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
1454
+ continue;
1455
+ }
1456
+ $this->index = $savePoint;
1457
+ break;
1458
+ }
1459
+ $savePoint = $this->index;
1460
+ if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
1461
+ $this->index = $savePoint;
1462
+ }
1463
+ if ($hasColumns && $this->isIdentifierWithValue($this->nextToken(), 'against') &&
1464
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) &&
1465
+ $this->parseExpression() &&
1466
+ ($this->parseSearchModifier() || true) &&
1467
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1468
+ ) {
1469
+ return true;
1470
+ }
1471
+ }
1472
+ $this->index = $startIndex;
1473
+ return false;
1474
+ }
1475
+
1476
+ /**
1477
+ * Used in match/against
1478
+ *
1479
+ * @link https://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html
1480
+ * @return bool
1481
+ */
1482
+ private function parseSearchModifier() {
1483
+ $startIndex = $this->index;
1484
+
1485
+ $startToken = $this->nextToken();
1486
+ if ($this->isIdentifierWithValue($startToken, 'in')) {
1487
+ $next = $this->nextToken();
1488
+ if ($this->isIdentifierWithValue($next, 'natural') &&
1489
+ $this->isIdentifierWithValue($this->nextToken(), 'language') &&
1490
+ $this->isIdentifierWithValue($this->nextToken(), 'mode')
1491
+ ) {
1492
+ $saveIndex = $this->index;
1493
+ if ($this->isIdentifierWithValue($this->nextToken(), 'with') &&
1494
+ $this->isIdentifierWithValue($this->nextToken(), 'query') &&
1495
+ $this->isIdentifierWithValue($this->nextToken(), 'expansion')
1496
+ ) {
1497
+ return true;
1498
+ }
1499
+ $this->index = $saveIndex;
1500
+ return true;
1501
+
1502
+ } else if ($this->isIdentifierWithValue($next, 'boolean') &&
1503
+ $this->isIdentifierWithValue($this->nextToken(), 'mode')
1504
+ ) {
1505
+ return true;
1506
+ }
1507
+ } else if ($this->isIdentifierWithValue($startToken, 'with')) {
1508
+ if ($this->isIdentifierWithValue($this->nextToken(), 'query') &&
1509
+ $this->isIdentifierWithValue($this->nextToken(), 'expansion')
1510
+ ) {
1511
+ return true;
1512
+ }
1513
+ }
1514
+
1515
+ $this->index = $startIndex;
1516
+ return false;
1517
+ }
1518
+
1519
+ /**
1520
+ * @return bool
1521
+ */
1522
+ private function parseCaseWhen() {
1523
+ $startIndex = $this->index;
1524
+ if ($this->isIdentifierWithValue($this->nextToken(), 'case')) {
1525
+ $hasWhen = false;
1526
+ while (true) {
1527
+ if (!$this->isIdentifierWithValue($this->nextToken(), 'when')) {
1528
+ $this->index--;
1529
+ break;
1530
+ }
1531
+ if ($this->parseExpression()) {
1532
+ if ($this->isIdentifierWithValue($this->nextToken(), 'then') && $this->parseBitExpression()) {
1533
+ $hasWhen = true;
1534
+ continue;
1535
+ }
1536
+ $this->index--;
1537
+ }
1538
+ $this->index--;
1539
+ break;
1540
+ }
1541
+ if ($hasWhen) {
1542
+ $endToken = $this->nextToken();
1543
+ if ($this->isIdentifierWithValue($endToken, 'else')) {
1544
+ if (!$this->parseBitExpression()) {
1545
+ $this->index = $startIndex;
1546
+ return false;
1547
+ }
1548
+ $endToken = $this->nextToken();
1549
+ }
1550
+ if ($this->isIdentifierWithValue($endToken, 'end')) {
1551
+ return true;
1552
+ }
1553
+ }
1554
+ }
1555
+ $this->index = $startIndex;
1556
+ return false;
1557
+ }
1558
+
1559
+ /**
1560
+ * @return bool
1561
+ */
1562
+ private function parseIntervalExpression() {
1563
+ $startIndex = $this->index;
1564
+ if ($this->isIdentifierWithValue($this->nextToken(), 'interval') && $this->parseExpression()) {
1565
+ $intervalUnitToken = $this->nextToken();
1566
+ if ($intervalUnitToken && in_array($intervalUnitToken->getType(), $this->intervalUnits)) {
1567
+ return true;
1568
+ }
1569
+ }
1570
+ $this->index = $startIndex;
1571
+ return false;
1572
+ }
1573
+
1574
+ /**
1575
+ * @return bool
1576
+ */
1577
+ public function parseCollationName() {
1578
+ $startIndex = $this->index;
1579
+ $token = $this->nextToken();
1580
+ if ($token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) {
1581
+ return true;
1582
+ }
1583
+ $this->index = $startIndex;
1584
+ return false;
1585
+ }
1586
+
1587
+ /**
1588
+ * @return bool
1589
+ */
1590
+ private function parseFrom() {
1591
+ $startIndex = $this->index;
1592
+ $token = $this->nextToken();
1593
+ if ($this->isIdentifierWithValue($token, 'from')) {
1594
+ return $this->parseTableReferences();
1595
+ }
1596
+ $this->index = $startIndex;
1597
+ return false;
1598
+ }
1599
+
1600
+ /**
1601
+ * @link http://dev.mysql.com/doc/refman/5.6/en/join.html
1602
+ * @return bool
1603
+ */
1604
+ private function parseTableReferences() {
1605
+ $startPoint = $this->index;
1606
+ $hasReferences = false;
1607
+ while ($this->parseEscapedTableReference()) {
1608
+ $hasReferences = true;
1609
+ $savePoint = $this->index;
1610
+ $token = $this->nextToken();
1611
+ if ($this->isTokenOfType($token, wfWAFSQLiLexer::COMMA)) {
1612
+ continue;
1613
+ }
1614
+ $this->index = $savePoint;
1615
+ break;
1616
+ }
1617
+ if ($hasReferences) {
1618
+ return true;
1619
+ }
1620
+ $this->index = $startPoint;
1621
+ return false;
1622
+ }
1623
+
1624
+ /**
1625
+ * @return bool
1626
+ */
1627
+ private function parseEscapedTableReference() {
1628
+ $startPoint = $this->index;
1629
+ if ($this->parseTableReference() ||
1630
+ (
1631
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_BRACKET) &&
1632
+ $this->isIdentifierWithValue($this->nextToken(), 'oj') &&
1633
+ $this->parseTableReference() &&
1634
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_BRACKET)
1635
+ )
1636
+ ) {
1637
+ return true;
1638
+ }
1639
+
1640
+ $this->index = $startPoint;
1641
+ return false;
1642
+ }
1643
+
1644
+ /**
1645
+ * @return bool
1646
+ */
1647
+ private function parseTableReference() {
1648
+ $savePoint = $this->index;
1649
+ $hasTables = false;
1650
+ if ($this->parseTableFactor()) {
1651
+ $hasTables = true;
1652
+ while ($this->parseJoinTable()) {
1653
+
1654
+ }
1655
+ }
1656
+ if ($hasTables) {
1657
+ return true;
1658
+ }
1659
+ $this->index = $savePoint;
1660
+ return false;
1661
+ }
1662
+
1663
+ /**
1664
+ * table_factor:
1665
+ * tbl_name [PARTITION (partition_names)] [[AS] alias] [index_hint_list]
1666
+ * | table_subquery [AS] alias
1667
+ * | ( table_references )
1668
+ */
1669
+ private function parseTableFactor() {
1670
+ $savePoint = $this->index;
1671
+ if ($this->parseTableSpec()) {
1672
+ $savePoint2 = $this->index;
1673
+ if (!$this->parsePartitionClause()) {
1674
+ $this->index = $savePoint2;
1675
+ }
1676
+
1677
+ $this->parseAlias();
1678
+ $this->parseIndexHintList();
1679
+
1680
+ return true;
1681
+ }
1682
+ $this->index = $savePoint;
1683
+
1684
+ $savePoint = $this->index;
1685
+ if ($this->parseSubquery() && $this->parseAlias()) {
1686
+ return true;
1687
+ }
1688
+ $this->index = $savePoint;
1689
+
1690
+ $savePoint = $this->index;
1691
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) &&
1692
+ $this->parseTableReferences() &&
1693
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1694
+ ) {
1695
+ return true;
1696
+ }
1697
+ $this->index = $savePoint;
1698
+ return false;
1699
+ }
1700
+
1701
+ /**
1702
+ * PARTITION (partition_names)
1703
+ *
1704
+ * @return bool
1705
+ */
1706
+ private function parsePartitionClause() {
1707
+ $startIndex = $this->index;
1708
+ if ($this->isIdentifierWithValue($this->nextToken(), 'partition') &&
1709
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) &&
1710
+ $this->parsePartitionNames() &&
1711
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1712
+ ) {
1713
+ return true;
1714
+ }
1715
+ $this->index = $startIndex;
1716
+ return false;
1717
+ }
1718
+
1719
+ /**
1720
+ * @return bool
1721
+ */
1722
+ private function parsePartitionNames() {
1723
+ $startPoint = $this->index;
1724
+ $hasPartition = false;
1725
+ while ($this->parsePartitionName()) {
1726
+ $hasPartition = true;
1727
+ $savePoint = $this->index;
1728
+ if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
1729
+ $this->index = $savePoint;
1730
+ break;
1731
+ }
1732
+ }
1733
+ if ($hasPartition) {
1734
+ return true;
1735
+ }
1736
+ $this->index = $startPoint;
1737
+ return false;
1738
+ }
1739
+
1740
+ /**
1741
+ * @return bool
1742
+ */
1743
+ private function parsePartitionName() {
1744
+ $startPoint = $this->index;
1745
+ $token = $this->nextToken();
1746
+ if ($this->isTokenOfType($token, wfWAFSQLiLexer::QUOTED_IDENTIFIER) ||
1747
+ $this->isValidNonKeywordIdentifier($token)
1748
+ ) {
1749
+ return true;
1750
+ }
1751
+ $this->index = $startPoint;
1752
+ return false;
1753
+ }
1754
+
1755
+ /**
1756
+ * join_table:
1757
+ * table_reference [INNER | CROSS] JOIN table_factor [join_condition]
1758
+ * | table_reference STRAIGHT_JOIN table_factor
1759
+ * | table_reference STRAIGHT_JOIN table_factor ON conditional_expr
1760
+ * | table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_condition
1761
+ * | table_reference NATURAL [{LEFT|RIGHT} [OUTER]] JOIN table_factor
1762
+ *
1763
+ * @return bool
1764
+ */
1765
+ private function parseJoinTable() {
1766
+ $savePoint = $this->index;
1767
+ if (!$this->isIdentifierWithValue($this->nextToken(), array(
1768
+ 'inner', 'cross',
1769
+ ))
1770
+ ) {
1771
+ $this->index = $savePoint;
1772
+ }
1773
+ if ($this->isIdentifierWithValue($this->nextToken(), 'join') && $this->parseTableFactor()) {
1774
+ $this->parseJoinCondition();
1775
+ return true;
1776
+ }
1777
+ $this->index = $savePoint;
1778
+
1779
+ $savePoint = $this->index;
1780
+ if ($this->isIdentifierWithValue($this->nextToken(), 'straight_join') &&
1781
+ $this->parseTableFactor()
1782
+ ) {
1783
+ $savePoint = $this->index;
1784
+ if (!($this->isIdentifierWithValue($this->nextToken(), 'on') && $this->parseExpression())) {
1785
+ $this->index = $savePoint;
1786
+ }
1787
+ return true;
1788
+ }
1789
+ $this->index = $savePoint;
1790
+
1791
+ $savePoint = $this->index;
1792
+ if ($this->isIdentifierWithValue($this->nextToken(), array(
1793
+ 'left', 'right',
1794
+ ))
1795
+ ) {
1796
+ $savePoint2 = $this->index;
1797
+ if (!$this->isIdentifierWithValue($this->nextToken(), array(
1798
+ 'outer',
1799
+ ))
1800
+ ) {
1801
+ $this->index = $savePoint2;
1802
+ }
1803
+ } else {
1804
+ $this->index = $savePoint;
1805
+ }
1806
+ if ($this->isIdentifierWithValue($this->nextToken(), 'join') &&
1807
+ $this->parseTableReference() &&
1808
+ $this->parseJoinCondition()
1809
+ ) {
1810
+ return true;
1811
+ }
1812
+ $this->index = $savePoint;
1813
+
1814
+ $savePoint = $this->index;
1815
+ if ($this->isIdentifierWithValue($this->nextToken(), array(
1816
+ 'natural',
1817
+ ))
1818
+ ) {
1819
+ if ($this->isIdentifierWithValue($this->nextToken(), array(
1820
+ 'left', 'right',
1821
+ ))
1822
+ ) {
1823
+ $savePoint2 = $this->index;
1824
+ if (!$this->isIdentifierWithValue($this->nextToken(), array(
1825
+ 'outer',
1826
+ ))
1827
+ ) {
1828
+ $this->index = $savePoint2;
1829
+ }
1830
+ } else {
1831
+ $this->index = $savePoint;
1832
+ }
1833
+ if ($this->isIdentifierWithValue($this->nextToken(), 'join') &&
1834
+ $this->parseTableFactor()
1835
+ ) {
1836
+ return true;
1837
+ }
1838
+
1839
+ }
1840
+ $this->index = $savePoint;
1841
+ return false;
1842
+ }
1843
+
1844
+ /**
1845
+ * (ON expression) | (USING_SYM column_list)
1846
+ *
1847
+ * @return bool
1848
+ */
1849
+ private function parseJoinCondition() {
1850
+ $savePoint = $this->index;
1851
+ if ($this->isIdentifierWithValue($this->nextToken(), 'on') && $this->parseExpression()) {
1852
+ return true;
1853
+ }
1854
+ $this->index = $savePoint;
1855
+
1856
+ $savePoint = $this->index;
1857
+ if ($this->isIdentifierWithValue($this->nextToken(), 'using') && $this->parseColumnList()) {
1858
+ return true;
1859
+ }
1860
+ $this->index = $savePoint;
1861
+ return false;
1862
+ }
1863
+
1864
+ /**
1865
+ * @return bool
1866
+ */
1867
+ private function parseTableSpec() {
1868
+ $savePoint = $this->index;
1869
+ if ($this->isTokenOfType($this->nextToken(), array(
1870
+ wfWAFSQLiLexer::UNQUOTED_IDENTIFIER,
1871
+ wfWAFSQLiLexer::QUOTED_IDENTIFIER,
1872
+ ))
1873
+ ) {
1874
+ $savePoint = $this->index;
1875
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::DOT) &&
1876
+ $this->isTokenOfType($this->nextToken(), array(
1877
+ wfWAFSQLiLexer::UNQUOTED_IDENTIFIER,
1878
+ wfWAFSQLiLexer::QUOTED_IDENTIFIER,
1879
+ ))
1880
+ ) {
1881
+ return true;
1882
+ }
1883
+ $this->index = $savePoint;
1884
+ return true;
1885
+ }
1886
+ $this->index = $savePoint;
1887
+ return false;
1888
+ }
1889
+
1890
+ /**
1891
+ * @return bool
1892
+ */
1893
+ private function parseAlias() {
1894
+ $savePoint = $this->index;
1895
+ $token = $this->nextToken();
1896
+ if ($this->isIdentifierWithValue($token, 'as')) {
1897
+ $token = $this->nextToken();
1898
+ }
1899
+ if ($this->isValidNonKeywordIdentifier($token)) {
1900
+ return true;
1901
+ }
1902
+ $this->index = $savePoint;
1903
+ return false;
1904
+ }
1905
+
1906
+ /**
1907
+ * @return bool
1908
+ */
1909
+ private function parseIndexHintList() {
1910
+ $startPoint = $this->index;
1911
+ $hasHints = false;
1912
+ while ($this->parseIndexHint()) {
1913
+ $hasHints = true;
1914
+ $savePoint = $this->index;
1915
+ if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
1916
+ $this->index = $savePoint;
1917
+ break;
1918
+ }
1919
+ }
1920
+ if ($hasHints) {
1921
+ return true;
1922
+ }
1923
+ $this->index = $startPoint;
1924
+ return false;
1925
+ }
1926
+
1927
+ /**
1928
+ * @return bool
1929
+ */
1930
+ private function parseIndexHint() {
1931
+ // USE_SYM index_options LPAREN (index_list)? RPAREN
1932
+ $savePoint = $this->index;
1933
+ if ($this->isIdentifierWithValue($this->nextToken(), 'use') &&
1934
+ $this->parseIndexOptions() &&
1935
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)
1936
+ ) {
1937
+ $this->parseIndexList();
1938
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
1939
+ return true;
1940
+ }
1941
+ }
1942
+ $this->index = $savePoint;
1943
+
1944
+ // IGNORE_SYM index_options LPAREN index_list RPAREN
1945
+ $savePoint = $this->index;
1946
+ if ($this->isIdentifierWithValue($this->nextToken(), 'ignore') &&
1947
+ $this->parseIndexOptions() &&
1948
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) &&
1949
+ $this->parseIndexList() &&
1950
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1951
+ ) {
1952
+ return true;
1953
+ }
1954
+ $this->index = $savePoint;
1955
+
1956
+ // FORCE_SYM index_options LPAREN index_list RPAREN
1957
+ $savePoint = $this->index;
1958
+ if ($this->isIdentifierWithValue($this->nextToken(), 'force') &&
1959
+ $this->parseIndexOptions() &&
1960
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) &&
1961
+ $this->parseIndexList() &&
1962
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
1963
+ ) {
1964
+ return true;
1965
+ }
1966
+ $this->index = $savePoint;
1967
+
1968
+ return false;
1969
+ }
1970
+
1971
+ /**
1972
+ * (INDEX_SYM | KEY_SYM) ( FOR_SYM ((JOIN_SYM) | (ORDER_SYM BY_SYM) | (GROUP_SYM BY_SYM)) )?
1973
+ *
1974
+ * @return bool
1975
+ */
1976
+ private function parseIndexOptions() {
1977
+ $savePoint = $this->index;
1978
+ $token = $this->nextToken();
1979
+ if ($this->isIdentifierWithValue($token, 'index') ||
1980
+ $this->isIdentifierWithValue($token, 'key')
1981
+ ) {
1982
+ $savePoint = $this->index;
1983
+ if ($this->isIdentifierWithValue($this->nextToken(), 'for')) {
1984
+
1985
+ $savePoint = $this->index;
1986
+ if ($this->isIdentifierWithValue($this->nextToken(), 'join')) {
1987
+ return true;
1988
+ }
1989
+ $this->index = $savePoint;
1990
+
1991
+ $savePoint = $this->index;
1992
+ if ($this->isIdentifierWithValue($this->nextToken(), 'order') &&
1993
+ $this->isIdentifierWithValue($this->nextToken(), 'by')
1994
+ ) {
1995
+ return true;
1996
+ }
1997
+ $this->index = $savePoint;
1998
+
1999
+ $savePoint = $this->index;
2000
+ if ($this->isIdentifierWithValue($this->nextToken(), 'group') &&
2001
+ $this->isIdentifierWithValue($this->nextToken(), 'by')
2002
+ ) {
2003
+ return true;
2004
+ }
2005
+ $this->index = $savePoint;
2006
+
2007
+ return true;
2008
+ }
2009
+ $this->index = $savePoint;
2010
+ return true;
2011
+ }
2012
+ $this->index = $savePoint;
2013
+ return false;
2014
+ }
2015
+
2016
+ private function parseIndexList() {
2017
+ $startPoint = $this->index;
2018
+ $hasIndex = false;
2019
+ while ($this->parseIndexName()) {
2020
+ $hasIndex = true;
2021
+ $savePoint = $this->index;
2022
+ if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2023
+ $this->index = $savePoint;
2024
+ break;
2025
+ }
2026
+ }
2027
+ if ($hasIndex) {
2028
+ return true;
2029
+ }
2030
+ $this->index = $startPoint;
2031
+ return false;
2032
+ }
2033
+
2034
+ private function parseIndexName() {
2035
+ $startPoint = $this->index;
2036
+ $token = $this->nextToken();
2037
+ if ($this->isValidNonKeywordIdentifier($token)) {
2038
+ return true;
2039
+ }
2040
+ $this->index = $startPoint;
2041
+ return false;
2042
+ }
2043
+
2044
+ /**
2045
+ * LPAREN column_spec (COMMA column_spec)* RPAREN
2046
+ *
2047
+ * @return bool
2048
+ */
2049
+ private function parseColumnList() {
2050
+ $startPoint = $this->index;
2051
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) {
2052
+ $hasColumn = false;
2053
+ while ($this->parseColumnSpec()) {
2054
+ $hasColumn = true;
2055
+ $savePoint = $this->index;
2056
+ if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2057
+ $this->index = $savePoint;
2058
+ break;
2059
+ }
2060
+ }
2061
+ if ($hasColumn && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
2062
+ return true;
2063
+ }
2064
+ }
2065
+ $this->index = $startPoint;
2066
+ return false;
2067
+ }
2068
+
2069
+ private function parseWhere() {
2070
+ $startIndex = $this->index;
2071
+ $token = $this->nextToken();
2072
+ if ($this->isIdentifierWithValue($token, 'where')) {
2073
+ if ($this->parseExpression()) {
2074
+ return true;
2075
+ }
2076
+ }
2077
+ $this->index = $startIndex;
2078
+ return false;
2079
+ }
2080
+
2081
+ private function parseProcedure() {
2082
+ $startIndex = $this->index;
2083
+ if ($this->isIdentifierWithValue($this->nextToken(), 'PROCEDURE') &&
2084
+ $this->isIdentifierWithValue($this->nextToken(), 'ANALYSE') &&
2085
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)
2086
+ ) {
2087
+ $savePoint = $this->index;
2088
+ if ($this->parseExpression()) {
2089
+ $savePoint = $this->index;
2090
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA) &&
2091
+ $this->parseExpression() &&
2092
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)
2093
+ ) {
2094
+ return true;
2095
+ }
2096
+ $this->index = $savePoint;
2097
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
2098
+ return true;
2099
+ }
2100
+ }
2101
+ $this->index = $savePoint;
2102
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
2103
+ return true;
2104
+ }
2105
+ }
2106
+ $this->index = $startIndex;
2107
+ return false;
2108
+ }
2109
+
2110
+ /**
2111
+ * GROUP_SYM BY_SYM groupby_item (COMMA groupby_item)* (WITH ROLLUP_SYM)?
2112
+ *
2113
+ * @return bool
2114
+ */
2115
+ private function parseGroupBy() {
2116
+ $startIndex = $this->index;
2117
+ if ($this->isIdentifierWithValue($this->nextToken(), 'group') &&
2118
+ $this->isIdentifierWithValue($this->nextToken(), 'by')
2119
+ ) {
2120
+ $hasItems = false;
2121
+ while ($this->parseGroupByItem()) {
2122
+ $hasItems = true;
2123
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2124
+ continue;
2125
+ }
2126
+ $this->index--;
2127
+ break;
2128
+ }
2129
+ if ($hasItems) {
2130
+ $savePoint = $this->index;
2131
+ if (!($this->isIdentifierWithValue($this->nextToken(), 'with') &&
2132
+ $this->isIdentifierWithValue($this->nextToken(), 'rollup'))
2133
+ ) {
2134
+ $this->index = $savePoint;
2135
+ }
2136
+ return true;
2137
+ }
2138
+ }
2139
+ $this->index = $startIndex;
2140
+ return false;
2141
+ }
2142
+
2143
+ /**
2144
+ * column_spec | INTEGER_NUM | bit_expr ;
2145
+ *
2146
+ * @return bool
2147
+ */
2148
+ private function parseGroupByItem() {
2149
+ $startIndex = $this->index;
2150
+ if ($this->parseBitExpression()) {
2151
+ return true;
2152
+ }
2153
+ $this->index = $startIndex;
2154
+ return false;
2155
+ }
2156
+
2157
+ /**
2158
+ * HAVING expression
2159
+ *
2160
+ * @return bool
2161
+ */
2162
+ private function parseHaving() {
2163
+ $startIndex = $this->index;
2164
+ if ($this->isIdentifierWithValue($this->nextToken(), 'having')
2165
+ && $this->parseExpression()
2166
+ ) {
2167
+ return true;
2168
+ }
2169
+ $this->index = $startIndex;
2170
+ return false;
2171
+ }
2172
+
2173
+ /**
2174
+ * ORDER_SYM BY_SYM orderby_item (COMMA orderby_item)*
2175
+ *
2176
+ * @return bool
2177
+ */
2178
+ private function parseOrderBy() {
2179
+ $startIndex = $this->index;
2180
+ if ($this->isIdentifierWithValue($this->nextToken(), 'order') &&
2181
+ $this->isIdentifierWithValue($this->nextToken(), 'by')
2182
+ ) {
2183
+ $hasItems = false;
2184
+ while ($this->parseOrderByItem()) {
2185
+ $hasItems = true;
2186
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2187
+ continue;
2188
+ }
2189
+ $this->index--;
2190
+ break;
2191
+ }
2192
+ if ($hasItems) {
2193
+ return true;
2194
+ }
2195
+ }
2196
+ $this->index = $startIndex;
2197
+ return false;
2198
+ }
2199
+
2200
+ /**
2201
+ * groupby_item (ASC | DESC)? ;
2202
+ *
2203
+ * @return bool
2204
+ */
2205
+ private function parseOrderByItem() {
2206
+ $startIndex = $this->index;
2207
+ if ($this->parseGroupByItem()) {
2208
+ $savePoint = $this->index;
2209
+ if (!$this->isIdentifierWithValue($this->nextToken(), array(
2210
+ 'asc', 'desc',
2211
+ ))
2212
+ ) {
2213
+ $this->index = $savePoint;
2214
+ }
2215
+ return true;
2216
+ }
2217
+ $this->index = $startIndex;
2218
+ return false;
2219
+ }
2220
+
2221
+ private function parseLimit() {
2222
+ // LIMIT ((offset COMMA)? row_count) | (row_count OFFSET_SYM offset)
2223
+ $startIndex = $this->index;
2224
+ if ($this->isIdentifierWithValue($this->nextToken(), 'limit') &&
2225
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL)
2226
+ ) {
2227
+ $savePoint = $this->index;
2228
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA) &&
2229
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL)
2230
+ ) {
2231
+ return true;
2232
+ }
2233
+ $this->index = $savePoint;
2234
+
2235
+ $savePoint = $this->index;
2236
+ if ($this->isIdentifierWithValue($this->nextToken(), 'offset') &&
2237
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL)
2238
+ ) {
2239
+ return true;
2240
+ }
2241
+ $this->index = $savePoint;
2242
+ return true;
2243
+ }
2244
+ $this->index = $startIndex;
2245
+ return false;
2246
+ }
2247
+
2248
+ /**
2249
+ * @link http://dev.mysql.com/doc/refman/5.6/en/insert.html
2250
+ * @return bool
2251
+ */
2252
+ private function parseInsertStatement() {
2253
+ $startIndex = $this->index;
2254
+ if ($this->parseInsertStatement1() ||
2255
+ $this->parseInsertStatement2() ||
2256
+ $this->parseInsertStatement3()
2257
+ ) {
2258
+ return true;
2259
+ }
2260
+ $this->index = $startIndex;
2261
+ return false;
2262
+ }
2263
+
2264
+ /**
2265
+ * insert_header
2266
+ * (column_list)?
2267
+ * value_list_clause
2268
+ * ( insert_subfix )?
2269
+ *
2270
+ * @return bool
2271
+ */
2272
+ private function parseInsertStatement1() {
2273
+ $startIndex = $this->index;
2274
+ if ($this->parseInsertHeader()) {
2275
+ $this->parseColumnList();
2276
+ if ($this->parseValueListClause()) {
2277
+ $this->parseInsertSubfix();
2278
+ return true;
2279
+ }
2280
+ }
2281
+ $this->index = $startIndex;
2282
+ return false;
2283
+ }
2284
+
2285
+ /**
2286
+ * insert_header
2287
+ * set_columns_cluase
2288
+ * ( insert_subfix )?
2289
+ *
2290
+ * @return bool
2291
+ */
2292
+ private function parseInsertStatement2() {
2293
+ $startIndex = $this->index;
2294
+ if ($this->parseInsertHeader() && $this->parseSetColumnsClause()) {
2295
+ $this->parseInsertSubfix();
2296
+ return true;
2297
+ }
2298
+ $this->index = $startIndex;
2299
+ return false;
2300
+ }
2301
+
2302
+ /**
2303
+ * insert_header
2304
+ * (column_list)?
2305
+ * select_expression
2306
+ * ( insert_subfix )?
2307
+ *
2308
+ * @return bool
2309
+ */
2310
+ private function parseInsertStatement3() {
2311
+ $startIndex = $this->index;
2312
+ if ($this->parseInsertHeader()) {
2313
+ $this->parseColumnList();
2314
+ if ($this->parseSelectStatement()) {
2315
+ $this->parseInsertSubfix();
2316
+ return true;
2317
+ }
2318
+ }
2319
+ $this->index = $startIndex;
2320
+ return false;
2321
+ }
2322
+
2323
+ /**
2324
+ * INSERT (LOW_PRIORITY | HIGH_PRIORITY)? (IGNORE_SYM)?
2325
+ * (INTO)? table_spec
2326
+ * (partition_clause)?
2327
+ *
2328
+ * @return bool
2329
+ */
2330
+ private function parseInsertHeader() {
2331
+ $startIndex = $this->index;
2332
+ if ($this->isIdentifierWithValue($this->nextToken(), 'insert')) {
2333
+ $savePoint = $this->index;
2334
+ // (LOW_PRIORITY | HIGH_PRIORITY)?
2335
+ if (!$this->isIdentifierWithValue($this->nextToken(), array(
2336
+ 'LOW_PRIORITY', 'HIGH_PRIORITY'
2337
+ ))
2338
+ ) {
2339
+ $this->index = $savePoint;
2340
+ }
2341
+
2342
+ // (IGNORE_SYM)?
2343
+ $savePoint = $this->index;
2344
+ if (!$this->isIdentifierWithValue($this->nextToken(), array(
2345
+ 'IGNORE'
2346
+ ))
2347
+ ) {
2348
+ $this->index = $savePoint;
2349
+ }
2350
+
2351
+ // (INTO)?
2352
+ $savePoint = $this->index;
2353
+ if (!$this->isIdentifierWithValue($this->nextToken(), array(
2354
+ 'into'
2355
+ ))
2356
+ ) {
2357
+ $this->index = $savePoint;
2358
+ }
2359
+
2360
+ // table_spec
2361
+ if ($this->parseTableSpec()) {
2362
+ $savePoint = $this->index;
2363
+ // (partition_clause)?
2364
+ if (!$this->parsePartitionClause()) {
2365
+ $this->index = $savePoint;
2366
+ }
2367
+ return true;
2368
+ }
2369
+ }
2370
+ $this->index = $startIndex;
2371
+ return false;
2372
+ }
2373
+
2374
+ /**
2375
+ * (VALUES | VALUE_SYM) column_value_list (COMMA column_value_list)*;
2376
+ *
2377
+ * @return bool
2378
+ */
2379
+ private function parseValueListClause() {
2380
+ $startIndex = $this->index;
2381
+ if ($this->isIdentifierWithValue($this->nextToken(), array(
2382
+ 'value', 'values',
2383
+ ))
2384
+ ) {
2385
+ $hasValues = false;
2386
+ while ($this->parseColumnValueList()) {
2387
+ $hasValues = true;
2388
+ $savePoint = $this->index;
2389
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2390
+ $hasValues = false;
2391
+ continue;
2392
+ }
2393
+ $this->index = $savePoint;
2394
+ break;
2395
+ }
2396
+ if ($hasValues) {
2397
+ return true;
2398
+ }
2399
+ }
2400
+ $this->index = $startIndex;
2401
+ return false;
2402
+ }
2403
+
2404
+ /**
2405
+ * LPAREN (bit_expr|DEFAULT) (COMMA (bit_expr|DEFAULT) )* RPAREN ;
2406
+ *
2407
+ * @return bool
2408
+ */
2409
+ private function parseColumnValueList() {
2410
+ $startIndex = $this->index;
2411
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) {
2412
+ $hasValues = false;
2413
+ while (true) {
2414
+ $savePoint = $this->index;
2415
+ if (!$this->parseBitExpression() && !$this->isIdentifierWithValue($this->nextToken(), 'DEFAULT')) {
2416
+ $this->index = $savePoint;
2417
+ break;
2418
+ }
2419
+ $hasValues = true;
2420
+ $savePoint = $this->index;
2421
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2422
+ $hasValues = false;
2423
+ continue;
2424
+ }
2425
+ $this->index = $savePoint;
2426
+ break;
2427
+ }
2428
+ if ($hasValues && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) {
2429
+ return true;
2430
+ }
2431
+ }
2432
+ $this->index = $startIndex;
2433
+ return false;
2434
+ }
2435
+
2436
+ private function parseInsertSubfix() {
2437
+ // ON DUPLICATE_SYM KEY_SYM UPDATE column_spec EQ_SYM expression (COMMA column_spec EQ_SYM expression)*
2438
+ $startIndex = $this->index;
2439
+ if (
2440
+ $this->isIdentifierWithValue($this->nextToken(), 'on') &&
2441
+ $this->isIdentifierWithValue($this->nextToken(), 'duplicate') &&
2442
+ $this->isIdentifierWithValue($this->nextToken(), 'key') &&
2443
+ $this->isIdentifierWithValue($this->nextToken(), 'update')
2444
+ ) {
2445
+ $hasValues = false;
2446
+ while (true) {
2447
+ $savePoint = $this->index;
2448
+ if ($this->parseColumnSpec() &&
2449
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::EQUALS_SYMBOL) &&
2450
+ $this->parseExpression()
2451
+ ) {
2452
+ $hasValues = true;
2453
+ $savePoint = $this->index;
2454
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2455
+ $hasValues = false;
2456
+ continue;
2457
+ }
2458
+ $this->index = $savePoint;
2459
+ break;
2460
+ }
2461
+ $this->index = $savePoint;
2462
+ break;
2463
+ }
2464
+ if ($hasValues) {
2465
+ return true;
2466
+ }
2467
+ }
2468
+ $this->index = $startIndex;
2469
+ return false;
2470
+ }
2471
+
2472
+ /**
2473
+ * SET_SYM set_column_cluase ( COMMA set_column_cluase )*;
2474
+ *
2475
+ * @return bool
2476
+ */
2477
+ private function parseSetColumnsClause() {
2478
+ $startIndex = $this->index;
2479
+ if ($this->isIdentifierWithValue($this->nextToken(), 'set')) {
2480
+ $hasValues = true;
2481
+ while ($this->parseSetColumnClause()) {
2482
+ $hasValues = true;
2483
+ $savePoint = $this->index;
2484
+ if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) {
2485
+ $hasValues = false;
2486
+ continue;
2487
+ }
2488
+ $this->index = $savePoint;
2489
+ break;
2490
+ }
2491
+ if ($hasValues) {
2492
+ return true;
2493
+ }
2494
+ }
2495
+ $this->index = $startIndex;
2496
+ return false;
2497
+ }
2498
+
2499
+ /**
2500
+ * column_spec EQ_SYM (expression|DEFAULT) ;
2501
+ *
2502
+ * @return bool
2503
+ */
2504
+ private function parseSetColumnClause() {
2505
+ $startIndex = $this->index;
2506
+ if ($this->parseColumnSpec() &&
2507
+ $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::EQUALS_SYMBOL) &&
2508
+ ($this->parseExpression() || $this->isIdentifierWithValue($this->nextToken(), 'default'))
2509
+ ) {
2510
+ return true;
2511
+ }
2512
+ $this->index = $startIndex;
2513
+ return false;
2514
+ }
2515
+
2516
+ /**
2517
+ * UPDATE (LOW_PRIORITY)? (IGNORE_SYM)? table_reference
2518
+ * set_columns_cluase
2519
+ * (where_clause)?
2520
+ * (orderby_clause)?
2521
+ * (limit_clause)?
2522
+ * |
2523
+ * UPDATE (LOW_PRIORITY)? (IGNORE_SYM)? table_references
2524
+ * set_columns_cluase
2525
+ * (where_clause)?
2526
+ *
2527
+ * @return bool
2528
+ */
2529
+ private function parseUpdateStatement() {
2530
+ $startIndex = $this->index;
2531
+ if ($this->isIdentifierWithValue($this->nextToken(), 'update')) {
2532
+ $savePoint = $this->index;
2533
+ if (!$this->isIdentifierWithValue($this->nextToken(), 'LOW_PRIORITY')) {
2534
+ $this->index = $savePoint;
2535
+ }
2536
+
2537
+ $savePoint = $this->index;
2538
+ if (!$this->isIdentifierWithValue($this->nextToken(), 'ignore')) {
2539
+ $this->index = $savePoint;
2540
+ }
2541
+
2542
+ if ($this->parseTableReferences() && $this->parseSetColumnsClause()) {
2543
+ $this->parseWhere();
2544
+ $this->parseOrderBy();
2545
+ $this->parseLimit();
2546
+ return true;
2547
+ }
2548
+ }
2549
+ $this->index = $startIndex;
2550
+ return false;
2551
+
2552
+ }
2553
+
2554
+ /**
2555
+ * @param wfWAFLexerToken $token
2556
+ * @param string|array $value
2557
+ * @return bool
2558
+ */
2559
+ private function isIdentifierWithValue($token, $value) {
2560
+ return $token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER &&
2561
+ (is_array($value) ? in_array($token->getLowerCaseValue(), array_map('strtolower', $value)) :
2562
+ $token->getLowerCaseValue() === strtolower($value));
2563
+ }
2564
+
2565
+ /**
2566
+ * @param wfWAFLexerToken $token
2567
+ * @param mixed $type
2568
+ * @return bool
2569
+ */
2570
+ private function isTokenOfType($token, $type) {
2571
+ if (is_array($type)) {
2572
+ return $token && in_array($token->getType(), $type);
2573
+ }
2574
+ return $token && $token->getType() === $type;
2575
+ }
2576
+
2577
+ /**
2578
+ * @param wfWAFLexerToken $token
2579
+ * @return bool
2580
+ */
2581
+ private function isNotSymbolToken($token) {
2582
+ return $token &&
2583
+ (
2584
+ ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && $token->getLowerCaseValue() === 'not') ||
2585
+ ($token->getType() === wfWAFSQLiLexer::EXPR_NOT)
2586
+ );
2587
+ }
2588
+
2589
+ /**
2590
+ * @param wfWAFLexerToken $token
2591
+ * @return bool
2592
+ */
2593
+ private function isKeywordToken($token) {
2594
+ return $token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER &&
2595
+ in_array($token->getUpperCaseValue(), $this->keywords);
2596
+ }
2597
+
2598
+ /**
2599
+ * @param wfWAFLexerToken $token
2600
+ * @return bool
2601
+ */
2602
+ private function isValidNonKeywordIdentifier($token) {
2603
+ return $token && (
2604
+ $token->getType() === wfWAFSQLiLexer::QUOTED_IDENTIFIER ||
2605
+ ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && !$this->isKeywordToken($token))
2606
+ );
2607
+ }
2608
+
2609
+ /**
2610
+ * @param wfWAFLexerToken $token
2611
+ * @return bool
2612
+ */
2613
+ private function isOrToken($token) {
2614
+ return $token && ($this->isIdentifierWithValue($token, 'or') || $this->isTokenOfType($token, wfWAFSQLiLexer::EXPR_OR));
2615
+ }
2616
+
2617
+ /**
2618
+ * @param wfWAFLexerToken $token
2619
+ * @return bool
2620
+ */
2621
+ private function isAndToken($token) {
2622
+ return $token && ($this->isIdentifierWithValue($token, 'and') || $this->isTokenOfType($token, wfWAFSQLiLexer::EXPR_AND));
2623
+ }
2624
+
2625
+ /**
2626
+ * @return string
2627
+ */
2628
+ public function getSubject() {
2629
+ return $this->subject;
2630
+ }
2631
+
2632
+ /**
2633
+ * @param string $subject
2634
+ */
2635
+ public function setSubject($subject) {
2636
+ $this->subject = $subject;
2637
+ $this->setTokens(array());
2638
+ $this->lexer->setSQL($this->subject);
2639
+ }
2640
+
2641
+ /**
2642
+ * @return int
2643
+ */
2644
+ public function getFlags() {
2645
+ return $this->flags;
2646
+ }
2647
+
2648
+ /**
2649
+ * @param int $flags
2650
+ */
2651
+ public function setFlags($flags) {
2652
+ $this->flags = $flags;
2653
+ $this->lexer->setFlags($this->flags);
2654
+ }
2655
+ }
2656
+
2657
+
2658
+ class wfWAFSQLiLexer implements wfWAFLexerInterface {
2659
+
2660
+ const FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS = 0x1;
2661
+
2662
+ const UNQUOTED_IDENTIFIER = 'UNQUOTED_IDENTIFIER';
2663
+ const VARIABLE = 'VARIABLE';
2664
+ const QUOTED_IDENTIFIER = 'QUOTED_IDENTIFIER';
2665
+ const DOUBLE_STRING_LITERAL = 'DOUBLE_STRING_LITERAL';
2666
+ const SINGLE_STRING_LITERAL = 'SINGLE_STRING_LITERAL';
2667
+ const INTEGER_LITERAL = 'INTEGER_LITERAL';
2668
+ const REAL_NUMBER_LITERAL = 'REAL_NUMBER_LITERAL';
2669
+ const BINARY_NUMBER_LITERAL = 'BINARY_NUMBER_LITERAL';
2670
+ const HEX_NUMBER_LITERAL = 'HEX_NUMBER_LITERAL';
2671
+ const DOT = 'DOT';
2672
+ const OPEN_PARENTHESIS = 'OPEN_PARENTHESIS';
2673
+ const CLOSE_PARENTHESIS = 'CLOSE_PARENTHESIS';
2674
+ const OPEN_BRACKET = 'OPEN_BRACKET';
2675
+ const CLOSE_BRACKET = 'CLOSE_BRACKET';
2676
+ const COMMA = 'COMMA';
2677
+ const EXPR_OR = 'EXPR_OR';
2678
+ const EXPR_AND = 'EXPR_AND';
2679
+ const EXPR_NOT = 'EXPR_NOT';
2680
+ const BIT_AND = 'BIT_AND';
2681
+ const BIT_LEFT_SHIFT = 'BIT_LEFT_SHIFT';
2682
+ const BIT_RIGHT_SHIFT = 'BIT_RIGHT_SHIFT';
2683
+ const BIT_XOR = 'BIT_XOR';
2684
+ const BIT_INVERSION = 'BIT_INVERSION';
2685
+ const BIT_OR = 'BIT_OR';
2686
+ const PLUS = 'PLUS';
2687
+ const MINUS = 'MINUS';
2688
+ const ASTERISK = 'ASTERISK';
2689
+ const DIVISION = 'DIVISION';
2690
+ const MOD = 'MOD';
2691
+ const ARROW = 'ARROW';
2692
+ const EQUALS_SYMBOL = 'EQUALS_SYMBOL';
2693
+ const NOT_EQUALS = 'NOT_EQUALS';
2694
+ const LESS_THAN = 'LESS_THAN';
2695
+ const GREATER_THAN = 'GREATER_THAN';
2696
+ const LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO';
2697
+ const GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO';
2698
+ const SET_VAR = 'SET_VAR';
2699
+ const RIGHT_BRACKET = 'RIGHT_BRACKET';
2700
+ const LEFT_BRACKET = 'LEFT_BRACKET';
2701
+ const SEMICOLON = 'SEMICOLON';
2702
+ const COLON = 'COLON';
2703
+ const MYSQL_PORTABLE_COMMENT_START = 'MYSQL_PORTABLE_COMMENT_START';
2704
+ const MYSQL_PORTABLE_COMMENT_END = 'MYSQL_PORTABLE_COMMENT_END';
2705
+ const SINGLE_LINE_COMMENT = 'SINGLE_LINE_COMMENT';
2706
+ const MULTI_LINE_COMMENT = 'MULTI_LINE_COMMENT';
2707
+ /**
2708
+ * @var int
2709
+ */
2710
+ private $flags;
2711
+ private $tokenMatchers;
2712
+ private $hasPortableCommentStart = false;
2713
+
2714
+ public static function getLexerTokenMatchers() {
2715
+ static $tokenMatchers;
2716
+ if ($tokenMatchers === null) {
2717
+ $tokenMatchers = array(
2718
+ new wfWAFLexerTokenMatcher(self::REAL_NUMBER_LITERAL, '/^(?:[0-9]+\\.[0-9]+|[0-9]+\\.|\\.[0-9]+|[Ee][\\+\\-][0-9]+)/'),
2719
+ new wfWAFLexerTokenMatcher(self::BINARY_NUMBER_LITERAL, '/^(?:0b[01]+|[bB]\'[01]+\')/', true),
2720
+ new wfWAFLexerTokenMatcher(self::HEX_NUMBER_LITERAL, '/^(?:0x[0-9a-fA-F]+|[xX]\'[0-9a-fA-F]+\')/', true),
2721
+ new wfWAFLexerTokenMatcher(self::INTEGER_LITERAL, '/^[0-9]+/', true),
2722
+ new wfWAFLexerTokenMatcher(self::VARIABLE, '/^(?:@(?:`([^`\\\\]{0,256}(?:\\\\.[^`\\\\]{0,256}){0,256})`|
2723
+ "(?:[^#"\\\\]{0,256}(?:\\\\.[^#"\\\\]{0,256}){0,256})"|
2724
+ \'(?:[^\'\\\\]{0,256}(?:\\\\.[^\'\\\\]{0,256}){0,256})\'|
2725
+ [a-zA-Z_\\$\\.]+|
2726
+ @[a-zA-Z_\\$][a-zA-Z_\\$0-9]{0,256}){0,1})
2727
+ /Asx'),
2728
+ new wfWAFLexerTokenMatcher(self::QUOTED_IDENTIFIER, '/^`(?:[^`\\\\]{0,256}(?:\\\\.[^`\\\\]{0,256}){0,256})`/As'),
2729
+ new wfWAFLexerTokenMatcher(self::DOUBLE_STRING_LITERAL, '/^(?:[nN]|_[0-9a-zA-Z\\$_]{0,256})?"(?:[^#"\\\\]{0,256}(?:\\\\.[^#"\\\\]{0,256}){0,256})"/As'),
2730
+ new wfWAFLexerTokenMatcher(self::SINGLE_STRING_LITERAL, '/^(?:[nN]|_[0-9a-zA-Z\\$_]{0,256})?\'(?:[^\'\\\\]{0,256}(?:\\\\.[^\'\\\\]{0,256}){0,256})\'/As'),
2731
+ // U+0080 .. U+FFFF
2732
+ new wfWAFLexerTokenMatcher(self::UNQUOTED_IDENTIFIER, '/^[0-9a-zA-Z\\$_\\x{0080}-\\x{FFFF}]{1,256}/u'),
2733
+ new wfWAFLexerTokenMatcher(self::MYSQL_PORTABLE_COMMENT_START, '/^\\/\\*\\![0-9]{0,5}/s'),
2734
+ new wfWAFLexerTokenMatcher(self::MYSQL_PORTABLE_COMMENT_END, '/^\\*\\//s'),
2735
+ new wfWAFLexerTokenMatcher(self::SINGLE_LINE_COMMENT, '/^(?:#[^\n]*|--(?:[ \t\r][^\n]*|[\n]))/'),
2736
+ new wfWAFLexerTokenMatcher(self::MULTI_LINE_COMMENT, '/^\\/\\*.*?\\*\\//s'),
2737
+ new wfWAFLexerTokenMatcher(self::DOT, '/^\\./'),
2738
+ new wfWAFLexerTokenMatcher(self::OPEN_PARENTHESIS, '/^\\(/'),
2739
+ new wfWAFLexerTokenMatcher(self::CLOSE_PARENTHESIS, '/^\\)/'),
2740
+ new wfWAFLexerTokenMatcher(self::COMMA, '/^,/'),
2741
+ new wfWAFLexerTokenMatcher(self::EXPR_OR, '/^\\|\\|/'),
2742
+ new wfWAFLexerTokenMatcher(self::EXPR_AND, '/^&&/'),
2743
+ new wfWAFLexerTokenMatcher(self::BIT_LEFT_SHIFT, '/^\\<\\</'),
2744
+ new wfWAFLexerTokenMatcher(self::BIT_RIGHT_SHIFT, '/^\\>\\>/'),
2745
+ new wfWAFLexerTokenMatcher(self::EQUALS_SYMBOL, '/^(?:\\=|\\<\\=\\>)/'),
2746
+ new wfWAFLexerTokenMatcher(self::ARROW, '/^\\=\\>/'),
2747
+ new wfWAFLexerTokenMatcher(self::LESS_THAN_EQUAL_TO, '/^\\<\\=/'),
2748
+ new wfWAFLexerTokenMatcher(self::GREATER_THAN_EQUAL_TO, '/^\\>\\=/'),
2749
+ new wfWAFLexerTokenMatcher(self::NOT_EQUALS, '/^(?:\\<\\>|(?:\\!|\\~|\\^)\\=)/'),
2750
+ new wfWAFLexerTokenMatcher(self::LESS_THAN, '/^\\</'),
2751
+ new wfWAFLexerTokenMatcher(self::GREATER_THAN, '/^\\>/'),
2752
+ new wfWAFLexerTokenMatcher(self::SET_VAR, '/^:\\=/'),
2753
+ new wfWAFLexerTokenMatcher(self::BIT_XOR, '/^\\^/'),
2754
+ new wfWAFLexerTokenMatcher(self::BIT_INVERSION, '/^\\~/'),
2755
+ new wfWAFLexerTokenMatcher(self::BIT_OR, '/^\\|/'),
2756
+ new wfWAFLexerTokenMatcher(self::PLUS, '/^\\+/'),
2757
+ new wfWAFLexerTokenMatcher(self::MINUS, '/^\\-/'),
2758
+ new wfWAFLexerTokenMatcher(self::ASTERISK, '/^\\*/'),
2759
+ new wfWAFLexerTokenMatcher(self::DIVISION, '/^\\//'),
2760
+ new wfWAFLexerTokenMatcher(self::MOD, '/^%/'),
2761
+ new wfWAFLexerTokenMatcher(self::EXPR_NOT, '/^\\!/'),
2762
+ new wfWAFLexerTokenMatcher(self::BIT_AND, '/^&/'),
2763
+ new wfWAFLexerTokenMatcher(self::RIGHT_BRACKET, '/^\\]/'),
2764
+ new wfWAFLexerTokenMatcher(self::LEFT_BRACKET, '/^\\[/'),
2765
+ new wfWAFLexerTokenMatcher(self::SEMICOLON, '/^;/'),
2766
+ new wfWAFLexerTokenMatcher(self::COLON, '/^:/'),
2767
+ );
2768
+ }
2769
+ return $tokenMatchers;
2770
+ }
2771
+
2772
+ /**
2773
+ * @var string
2774
+ */
2775
+ private $sql;
2776
+
2777
+ /**
2778
+ * @var wfWAFStringScanner
2779
+ */
2780
+ private $scanner;
2781
+
2782
+ /**
2783
+ * wfWAFRuleLexer constructor.
2784
+ * @param $sql
2785
+ * @param int $flags
2786
+ */
2787
+ public function __construct($sql = null, $flags = 0) {
2788
+ $this->scanner = new wfWAFStringScanner();
2789
+ $this->tokenMatchers = self::getLexerTokenMatchers();
2790
+ $this->setSQL($sql);
2791
+ $this->setFlags($flags);
2792
+ }
2793
+
2794
+ /**
2795
+ * @return array
2796
+ * @throws wfWAFParserSyntaxError
2797
+ */
2798
+ public function tokenize() {
2799
+ $tokens = array();
2800
+ while ($token = $this->nextToken()) {
2801
+ $tokens[] = $token;
2802
+ }
2803
+ return $tokens;
2804
+ }
2805
+
2806
+ /**
2807
+ * @return bool|wfWAFLexerToken
2808
+ * @throws wfWAFParserSyntaxError
2809
+ */
2810
+ public function nextToken() {
2811
+ if (!$this->scanner->eos()) {
2812
+ /** @var wfWAFLexerTokenMatcher $tokenMatcher */
2813
+ foreach ($this->tokenMatchers as $tokenMatcher) {
2814
+ $this->scanner->skip('/^\s+/s');
2815
+ if ($this->scanner->eos()) {
2816
+ return false;
2817
+ }
2818
+
2819
+ if (($this->flags & self::FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS) === 0 &&
2820
+ ($tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_START ||
2821
+ $tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_END)
2822
+ ) {
2823
+ continue;
2824
+ }
2825
+ if (!$this->hasPortableCommentStart && $tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_END) {
2826
+ continue;
2827
+ }
2828
+
2829
+ if ($tokenMatcher->useMaximalMunch() && ($match = $this->scanner->check($tokenMatcher->getMatch())) !== null) {
2830
+ $biggestToken = $this->createToken($tokenMatcher->getTokenID(), $match);
2831
+ /** @var wfWAFLexerTokenMatcher $tokenMatcher2 */
2832
+ foreach ($this->tokenMatchers as $tokenMatcher2) {
2833
+ if ($tokenMatcher === $tokenMatcher2) {
2834
+ continue;
2835
+ }
2836
+ if (($match2 = $this->scanner->check($tokenMatcher2->getMatch())) !== null) {
2837
+ $biggestToken2 = $this->createToken($tokenMatcher2->getTokenID(), $match2);
2838
+ if (strlen($biggestToken2->getValue()) > strlen($biggestToken->getValue())) {
2839
+ $biggestToken = $biggestToken2;
2840
+ }
2841
+ }
2842
+ }
2843
+ $this->scanner->advancePointer(strlen($biggestToken->getValue()));
2844
+ return $biggestToken;
2845
+
2846
+ } else if (($match = $this->scanner->scan($tokenMatcher->getMatch())) !== null) {
2847
+ $token = $this->createToken($tokenMatcher->getTokenID(), $match);
2848
+ if ($tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_START) {
2849
+ $this->hasPortableCommentStart = true;
2850
+ } else if ($tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_END) {
2851
+ $this->hasPortableCommentStart = false;
2852
+ }
2853
+ return $token;
2854
+ }
2855
+ }
2856
+ $char = $this->scanner->scanChar();
2857
+ $e = new wfWAFParserSyntaxError(sprintf('Invalid character "%s" (\x%02x) found on line %d, column %d',
2858
+ $char, ord($char), $this->scanner->getLine(), $this->scanner->getColumn()));
2859
+ $e->setParseLine($this->scanner->getLine());
2860
+ $e->setParseColumn($this->scanner->getColumn());
2861
+ throw $e;
2862
+ }
2863
+ return false;
2864
+ }
2865
+
2866
+ public function hasMoreTokens() {
2867
+
2868
+ }
2869
+
2870
+ /**
2871
+ * @param $type
2872
+ * @param $value
2873
+ * @return wfWAFLexerToken
2874
+ */
2875
+ protected function createToken($type, $value) {
2876
+ return new wfWAFLexerToken($type, $value, $this->scanner->getLine(), $this->scanner->getColumn());
2877
+ }
2878
+
2879
+ /**
2880
+ * @return string
2881
+ */
2882
+ public function getSQL() {
2883
+ return $this->sql;
2884
+ }
2885
+
2886
+ /**
2887
+ * @param string $sql
2888
+ */
2889
+ public function setSQL($sql) {
2890
+ if (is_string($sql)) {
2891
+ $this->scanner->setString($sql);
2892
+ }
2893
+ $this->sql = $sql;
2894
+ }
2895
+
2896
+ /**
2897
+ * @return int
2898
+ */
2899
+ public function getFlags() {
2900
+ return $this->flags;
2901
+ }
2902
+
2903
+ /**
2904
+ * @param int $flags
2905
+ */
2906
+ public function setFlags($flags) {
2907
+ $this->flags = $flags;
2908
+ }
2909
+ }
2910
+
2911
+ class wfWAFLexerTokenMatcher {
2912
+ /**
2913
+ * @var mixed
2914
+ */
2915
+ private $tokenID;
2916
+ /**
2917
+ * @var string
2918
+ */
2919
+ private $match;
2920
+ /**
2921
+ * @var bool
2922
+ */
2923
+ private $useMaximalMunch;
2924
+
2925
+
2926
+ /**
2927
+ * @param mixed $tokenID
2928
+ * @param string $match
2929
+ * @param bool $useMaximalMunch
2930
+ */
2931
+ public function __construct($tokenID, $match, $useMaximalMunch = false) {
2932
+ $this->tokenID = $tokenID;
2933
+ $this->match = $match;
2934
+ $this->useMaximalMunch = $useMaximalMunch;
2935
+ }
2936
+
2937
+ /**
2938
+ * @return bool
2939
+ */
2940
+ public function useMaximalMunch() {
2941
+ return $this->useMaximalMunch;
2942
+ }
2943
+
2944
+ /**
2945
+ * @return mixed
2946
+ */
2947
+ public function getTokenID() {
2948
+ return $this->tokenID;
2949
+ }
2950
+
2951
+ /**
2952
+ * @param mixed $tokenID
2953
+ */
2954
+ public function setTokenID($tokenID) {
2955
+ $this->tokenID = $tokenID;
2956
+ }
2957
+
2958
+ /**
2959
+ * @return string
2960
+ */
2961
+ public function getMatch() {
2962
+ return $this->match;
2963
+ }
2964
+
2965
+ /**
2966
+ * @param string $match
2967
+ */
2968
+ public function setMatch($match) {
2969
+ $this->match = $match;
2970
+ }
2971
+ }
vendor/wordfence/wf-waf/src/lib/request.php ADDED
@@ -0,0 +1,816 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ interface wfWAFRequestInterface {
4
+
5
+ public function getBody();
6
+
7
+ public function getQueryString();
8
+
9
+ public function getHeaders();
10
+
11
+ public function getCookies();
12
+
13
+ public function getFiles();
14
+
15
+ public function getFileNames();
16
+
17
+ public function getHost();
18
+
19
+ public function getURI();
20
+
21
+ public function getPath();
22
+
23
+ public function getIP();
24
+
25
+ public function getMethod();
26
+
27
+ public function getProtocol();
28
+
29
+ public function getAuth();
30
+
31
+ public function getTimestamp();
32
+
33
+ public function __toString();
34
+
35
+ }
36
+
37
+
38
+ class wfWAFRequest implements wfWAFRequestInterface {
39
+
40
+ /**
41
+ * @param string $requestString
42
+ * @return wfWAFRequest
43
+ */
44
+ public static function parseString($requestString) {
45
+ if (!is_string($requestString)) {
46
+ throw new InvalidArgumentException(__METHOD__ . ' expects a string for first parameter, recieved ' . gettype($requestString));
47
+ }
48
+
49
+ if (version_compare(phpversion(), '5.3.0') > 0) {
50
+ $class = get_called_class();
51
+ $request = new $class();
52
+ } else {
53
+ $request = new self();
54
+ }
55
+
56
+ $request->setAuth(array());
57
+ $request->setBody(array());
58
+ $request->setCookies(array());
59
+ $request->setFileNames(array());
60
+ $request->setFiles(array());
61
+ $request->setHeaders(array());
62
+ $request->setHost('');
63
+ $request->setIP('');
64
+ $request->setMethod('');
65
+ $request->setPath('');
66
+ $request->setProtocol('');
67
+ $request->setQueryString(array());
68
+ $request->setTimestamp('');
69
+ $request->setURI('');
70
+
71
+ list($headersString, $bodyString) = explode("\n\n", $requestString, 2);
72
+ $headersString = trim($headersString);
73
+ $bodyString = trim($bodyString);
74
+ $headers = explode("\n", $headersString);
75
+ // Assume first is method
76
+ if (preg_match('/^([a-z]+) (.*?) HTTP\/1.[0-9]/i', $headers[0], $matches)) {
77
+ $request->setMethod($matches[1]);
78
+ $uri = $matches[2];
79
+ $request->setUri($uri);
80
+ if (($pos = strpos($uri, '?')) !== false) {
81
+ $queryString = substr($uri, $pos + 1);
82
+ parse_str($queryString, $queryStringArray);
83
+ $request->setQueryString($queryStringArray);
84
+
85
+ $path = substr($uri, 0, $pos);
86
+ $request->setPath($path);
87
+ } else {
88
+ $request->setPath($uri);
89
+ }
90
+ }
91
+ $kvHeaders = array();
92
+ for ($i = 1; $i < count($headers); $i++) {
93
+ $headerString = $headers[$i];
94
+ list($header, $headerValue) = explode(':', $headerString, 2);
95
+ $header = trim($header);
96
+ $headerValue = trim($headerValue);
97
+ $kvHeaders[$header] = $headerValue;
98
+
99
+ switch (strtolower($header)) {
100
+ case 'authorization':
101
+ if (preg_match('/basic ([A-Za-z0-9\+\/=]+)/i', $headerValue, $matches)) {
102
+ list($authUser, $authPass) = explode(':', base64_decode($matches[1]), 2);
103
+ $auth['user'] = $authUser;
104
+ $auth['password'] = $authPass;
105
+ $request->setAuth($auth);
106
+ }
107
+ break;
108
+
109
+ case 'host':
110
+ $request->setHost($headerValue);
111
+ break;
112
+
113
+ case 'cookie':
114
+ $cookieArray = array();
115
+ $cookies = explode(';', $headerValue);
116
+ foreach ($cookies as $cookie) {
117
+ if (strpos($cookie, '=') !== false) {
118
+ list($cookieName, $cookieValue) = explode('=', $cookie, 2);
119
+ $cookieArray[trim($cookieName)] = urldecode(trim($cookieValue));
120
+ }
121
+ }
122
+ $request->setCookies($cookieArray);
123
+ break;
124
+ }
125
+
126
+ }
127
+ $request->setHeaders($kvHeaders);
128
+
129
+ if (strlen($bodyString) > 0) {
130
+ if (preg_match('/^multipart\/form\-data; boundary=(.*?)$/i', $request->getHeaders('Content-Type'), $boundaryMatches)) {
131
+ $body = '';
132
+ $files = array();
133
+ $fileNames = array();
134
+
135
+ $boundary = $boundaryMatches[1];
136
+ $bodyChunks = explode("--$boundary", $bodyString);
137
+ foreach ($bodyChunks as $chunk) {
138
+ if (!$chunk || $chunk == '--') {
139
+ continue;
140
+ }
141
+
142
+ list($chunkHeaders, $chunkData) = explode("\n\n", $chunk, 2);
143
+ $chunkHeaders = explode("\n", $chunkHeaders);
144
+ $param = array(
145
+ 'value' => substr($chunkData, 0, -1),
146
+ );
147
+ foreach ($chunkHeaders as $chunkHeader) {
148
+ if (strpos($chunkHeader, ':') !== false) {
149
+ list($chunkHeaderKey, $chunkHeaderValue) = explode(':', $chunkHeader, 2);
150
+ $chunkHeaderKey = trim($chunkHeaderKey);
151
+ $chunkHeaderValue = trim($chunkHeaderValue);
152
+ switch ($chunkHeaderKey) {
153
+ case 'Content-Disposition':
154
+ $dataAttributes = explode(';', $chunkHeaderValue);
155
+ foreach ($dataAttributes as $attr) {
156
+ $attr = trim($attr);
157
+ if (preg_match('/^name="(.*?)"$/i', $attr, $attrMatch)) {
158
+ $param['name'] = $attrMatch[1];
159
+ continue;
160
+ }
161
+ if (preg_match('/^filename="(.*?)"$/i', $attr, $attrMatch)) {
162
+ $param['filename'] = $attrMatch[1];
163
+ continue;
164
+ }
165
+ }
166
+ break;
167
+ case 'Content-Type':
168
+ $param['type'] = $chunkHeaderValue;
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ if (array_key_exists('name', $param)) {
174
+ if (array_key_exists('filename', $param)) {
175
+ $files[$param['name']] = array(
176
+ 'name' => $param['filename'],
177
+ 'type' => $param['type'],
178
+ 'size' => strlen($param['value']),
179
+ 'content' => $param['value'],
180
+ );
181
+ $fileNames[$param['name']] = $param['filename'];
182
+ } else {
183
+ $body .= urlencode($param['name']) . '=' . urlencode($param['value']) . '&';
184
+ }
185
+ }
186
+ }
187
+
188
+ if ($body) {
189
+ parse_str($body, $postBody);
190
+ if (is_array($postBody)) {
191
+ $request->setBody($postBody);
192
+ } else {
193
+ $request->setBody($body);
194
+ }
195
+ }
196
+ if ($files) {
197
+ $request->setFiles($files);
198
+ }
199
+ if ($fileNames) {
200
+ $request->setFileNames($fileNames);
201
+ }
202
+
203
+ } else {
204
+ parse_str($bodyString, $postBody);
205
+ if (is_array($postBody)) {
206
+ $request->setBody($postBody);
207
+ } else {
208
+ $request->setBody($bodyString);
209
+ }
210
+ }
211
+ }
212
+
213
+ return $request;
214
+ }
215
+
216
+ /**
217
+ * @param wfWAFRequest|null $request
218
+ * @return wfWAFRequest
219
+ */
220
+ public static function createFromGlobals($request = null) {
221
+ if ($request === null) {
222
+ if (version_compare(phpversion(), '5.3.0') > 0) {
223
+ $class = get_called_class();
224
+ $request = new $class();
225
+ } else {
226
+ $request = new self();
227
+ }
228
+ }
229
+
230
+ $request->setAuth(array());
231
+ $request->setCookies(array());
232
+ $request->setFileNames(array());
233
+ $request->setFiles(array());
234
+ $request->setHeaders(array());
235
+ $request->setHost('');
236
+ $request->setIP('');
237
+ $request->setMethod('');
238
+ $request->setPath('');
239
+ $request->setProtocol('');
240
+ $request->setTimestamp('');
241
+ $request->setURI('');
242
+
243
+ $request->setBody(wfWAFUtils::stripMagicQuotes($_POST));
244
+ $request->setQueryString(wfWAFUtils::stripMagicQuotes($_GET));
245
+ $request->setCookies(wfWAFUtils::stripMagicQuotes($_COOKIE));
246
+ $request->setFiles(wfWAFUtils::stripMagicQuotes($_FILES));
247
+
248
+ if (!empty($_FILES)) {
249
+ $fileNames = array();
250
+ foreach ($_FILES as $input => $file) {
251
+ $fileNames[$input] = wfWAFUtils::stripMagicQuotes($file['name']);
252
+ }
253
+ $request->setFileNames($fileNames);
254
+ }
255
+ $auth = array();
256
+ if (array_key_exists('PHP_AUTH_USER', $_SERVER)) {
257
+ $auth['user'] = wfWAFUtils::stripMagicQuotes($_SERVER['PHP_AUTH_USER']);
258
+ }
259
+ if (array_key_exists('PHP_AUTH_PW', $_SERVER)) {
260
+ $auth['password'] = wfWAFUtils::stripMagicQuotes($_SERVER['PHP_AUTH_PW']);
261
+ }
262
+ $request->setAuth($auth);
263
+
264
+ if (array_key_exists('REQUEST_TIME_FLOAT', $_SERVER)) {
265
+ $timestamp = $_SERVER['REQUEST_TIME_FLOAT'];
266
+ } else if (array_key_exists('REQUEST_TIME', $_SERVER)) {
267
+ $timestamp = $_SERVER['REQUEST_TIME'];
268
+ } else {
269
+ $timestamp = time();
270
+ }
271
+ $request->setTimestamp($timestamp);
272
+
273
+ $headers = array();
274
+ if (!empty($_SERVER)) {
275
+ foreach ($_SERVER as $key => $value) {
276
+ if (strpos($key, 'HTTP_') === 0) {
277
+ $header = substr($key, 5);
278
+ $header = str_replace(array(' ', '_'), array('', ' '), $header);
279
+ $header = ucwords(strtolower($header));
280
+ $header = str_replace(' ', '-', $header);
281
+ $headers[$header] = wfWAFUtils::stripMagicQuotes($value);
282
+ }
283
+ }
284
+ if (array_key_exists('CONTENT_TYPE', $_SERVER)) {
285
+ $headers['Content-Type'] = wfWAFUtils::stripMagicQuotes($_SERVER['CONTENT_TYPE']);
286
+ }
287
+ if (array_key_exists('CONTENT_LENGTH', $_SERVER)) {
288
+ $headers['Content-Length'] = wfWAFUtils::stripMagicQuotes($_SERVER['CONTENT_LENGTH']);
289
+ }
290
+ }
291
+ $request->setHeaders($headers);
292
+
293
+ $host = '';
294
+ if (array_key_exists('Host', $headers)) {
295
+ $host = $headers['Host'];
296
+ } else if (array_key_exists('SERVER_NAME', $_SERVER)) {
297
+ $host = wfWAFUtils::stripMagicQuotes($_SERVER['SERVER_NAME']);
298
+ }
299
+ $request->setHost($host);
300
+
301
+ $request->setMethod(array_key_exists('REQUEST_METHOD', $_SERVER) ? wfWAFUtils::stripMagicQuotes($_SERVER['REQUEST_METHOD']) : 'GET');
302
+ $request->setProtocol((array_key_exists('HTTPS', $_SERVER) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http');
303
+ $request->setUri(array_key_exists('REQUEST_URI', $_SERVER) ? wfWAFUtils::stripMagicQuotes($_SERVER['REQUEST_URI']) : '');
304
+
305
+ $uri = parse_url($request->getURI());
306
+ if (is_array($uri) && array_key_exists('path', $uri)) {
307
+ $path = $uri['path'];
308
+ } else {
309
+ $path = $request->getURI();
310
+ }
311
+ $request->setPath($path);
312
+
313
+ return $request;
314
+ }
315
+
316
+ private $auth;
317
+ private $body;
318
+ private $cookies;
319
+ private $fileNames;
320
+ private $files;
321
+ private $headers;
322
+ private $host;
323
+ private $ip;
324
+ private $method;
325
+ private $path;
326
+ private $protocol;
327
+ private $queryString;
328
+ private $timestamp;
329
+ private $uri;
330
+
331
+ private $highlightParamFormat;
332
+ private $highlightMatchFormat;
333
+ private $highlightMatches;
334
+ private $highlightMatchFilter = 'urlencode';
335
+
336
+
337
+ protected function _arrayValueByKeys($global, $key) {
338
+ if (is_array($global)) {
339
+ if (is_array($key)) {
340
+ $_key = array_shift($key);
341
+ if (array_key_exists($_key, $global)) {
342
+ if (count($key) > 0) {
343
+ return $this->_arrayValueByKeys($global[$_key], $key);
344
+ } else {
345
+ return $global[$_key];
346
+ }
347
+ }
348
+ } else {
349
+ return array_key_exists($key, $global) ? $global[$key] : null;
350
+ }
351
+ }
352
+ return null;
353
+ }
354
+
355
+ public function getBody() {
356
+ if (func_num_args() > 0) {
357
+ $args = func_get_args();
358
+ return $this->_arrayValueByKeys($this->body, $args);
359
+ }
360
+ return $this->body;
361
+ }
362
+
363
+ public function getQueryString() {
364
+ if (func_num_args() > 0) {
365
+ $args = func_get_args();
366
+ return $this->_arrayValueByKeys($this->queryString, $args);
367
+ }
368
+ return $this->queryString;
369
+ }
370
+
371
+ public function getHeaders() {
372
+ if (func_num_args() > 0) {
373
+ $args = func_get_args();
374
+ return $this->_arrayValueByKeys($this->headers, $args);
375
+ }
376
+ return $this->headers;
377
+ }
378
+
379
+ public function getCookies() {
380
+ if (func_num_args() > 0) {
381
+ $args = func_get_args();
382
+ return $this->_arrayValueByKeys($this->cookies, $args);
383
+ }
384
+ return $this->cookies;
385
+ }
386
+
387
+ public function getFiles() {
388
+ if (func_num_args() > 0) {
389
+ $args = func_get_args();
390
+ return $this->_arrayValueByKeys($this->files, $args);
391
+ }
392
+ return $this->files;
393
+ }
394
+
395
+ public function getFileNames() {
396
+ if (func_num_args() > 0) {
397
+ $args = func_get_args();
398
+ return $this->_arrayValueByKeys($this->fileNames, $args);
399
+ }
400
+ return $this->fileNames;
401
+ }
402
+
403
+ public function getHost() {
404
+ return $this->host;
405
+ }
406
+
407
+ public function getURI() {
408
+ return $this->uri;
409
+ }
410
+
411
+ public function getPath() {
412
+ return $this->path;
413
+ }
414
+
415
+ public function getIP() {
416
+ return $this->ip;
417
+ }
418
+
419
+ public function getMethod() {
420
+ return $this->method;
421
+ }
422
+
423
+ public function getProtocol() {
424
+ return $this->protocol;
425
+ }
426
+
427
+ public function getAuth($arg1 = null) {
428
+ if ($arg1) {
429
+ if (is_array($this->auth) && array_key_exists($arg1, $this->auth)) {
430
+ return $this->auth[$arg1];
431
+ }
432
+ return null;
433
+ }
434
+ return $this->auth;
435
+ }
436
+
437
+ public function getTimestamp() {
438
+ return $this->timestamp;
439
+ }
440
+
441
+ public function __toString() {
442
+ return $this->highlightFailedParams();
443
+ }
444
+
445
+ /**
446
+ * @param array $failedParams
447
+ * @param string $highlightParamFormat
448
+ * @param string $highlightMatchFormat
449
+ * @return string
450
+ */
451
+ public function highlightFailedParams($failedParams = array(), $highlightParamFormat = '[param]%s[/param]',
452
+ $highlightMatchFormat = '[match]%s[/match]') {
453
+ $highlights = array();
454
+
455
+ // Cap at 50kb
456
+ $maxRequestLen = 1024 * 50;
457
+
458
+ $this->highlightParamFormat = $highlightParamFormat;
459
+ $this->highlightMatchFormat = $highlightMatchFormat;
460
+
461
+ if (is_array($failedParams)) {
462
+ foreach ($failedParams as $paramKey => $categories) {
463
+ foreach ($categories as $categoryKey => $failedRules) {
464
+ foreach ($failedRules as $failedRule) {
465
+ $rule = $failedRule['rule'];
466
+ /** @var wfWAFRuleComparisonFailure $failedComparison */
467
+ $failedComparison = $failedRule['failedComparison'];
468
+ $action = $failedRule['action'];
469
+
470
+ $paramKey = $failedComparison->getParamKey();
471
+ if (preg_match('/request\.([a-z0-9]+)(?:\[(.*?)\](.*?))?$/i', $paramKey, $matches)) {
472
+ $global = $matches[1];
473
+ if (method_exists('wfWAFRequestInterface', "get" . ucfirst($global))) {
474
+ $highlight = array(
475
+ 'match' => $failedComparison->getMatches(),
476
+ );
477
+ if (isset($matches[2])) {
478
+ $highlight['param'] = "$matches[2]$matches[3]";
479
+ }
480
+ $highlights[$global][] = $highlight;
481
+ }
482
+ }
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ $uri = $this->getURI();
489
+ $queryStringPos = strpos($uri, '?');
490
+ if ($queryStringPos !== false) {
491
+ $uri = substr($uri, 0, $queryStringPos);
492
+ }
493
+ $queryString = $this->getQueryString();
494
+ if ($queryString) {
495
+ $uri .= '?' . http_build_query($queryString);
496
+ }
497
+ if (!empty($highlights['queryString'])) {
498
+ foreach ($highlights['queryString'] as $matches) {
499
+ if (!empty($matches['param'])) {
500
+ $this->highlightMatches = $matches['match'];
501
+ $uri = preg_replace_callback('/(&|\?|^)(' . preg_quote(urlencode($matches['param']), '/') . ')=(.*?)(&|$)/', array(
502
+ $this, 'highlightParam',
503
+ ), $uri);
504
+ }
505
+ }
506
+ }
507
+
508
+ if (!empty($highlights['uri'])) {
509
+ foreach ($highlights['uri'] as $matches) {
510
+ if ($matches) {
511
+
512
+ }
513
+ }
514
+ $uri = sprintf($highlightParamFormat, $uri);
515
+ }
516
+
517
+ $request = "{$this->getMethod()} $uri HTTP/1.1\n";
518
+ $hasAuth = false;
519
+ $auth = $this->getAuth();
520
+
521
+ if (is_array($this->getHeaders())) {
522
+ foreach ($this->getHeaders() as $header => $value) {
523
+ switch (strtolower($header)) {
524
+ case 'cookie':
525
+ // TODO: Hook up highlights to cookies
526
+ $cookies = '';
527
+ foreach ($this->getCookies() as $cookieName => $cookieValue) {
528
+ $cookies .= $cookieName . '=' . urlencode($cookieValue) . '; ';
529
+ }
530
+ $request .= 'Cookie: ' . trim($cookies) . "\n";
531
+ break;
532
+
533
+ case 'host':
534
+ $request .= 'Host: ' . $this->getHost() . "\n";
535
+ break;
536
+
537
+ case 'authorization':
538
+ $hasAuth = true;
539
+ if ($auth) {
540
+ $request .= 'Authorization: Basic ' . base64_encode($auth['user'] . ':' . $auth['password']) . "\n";
541
+ }
542
+ break;
543
+
544
+ default:
545
+ $request .= $header . ': ' . $value . "\n";
546
+ break;
547
+ }
548
+ }
549
+ }
550
+
551
+ if (!$hasAuth && $auth) {
552
+ $request .= 'Authorization: Basic ' . base64_encode($auth['user'] . ':' . $auth['password']) . "\n";
553
+ }
554
+
555
+ $body = $this->getBody();
556
+ $contentType = $this->getHeaders('Content-Type');
557
+ if (is_array($body)) {
558
+ if (stripos($contentType, 'application/x-www-form-urlencoded') === 0) {
559
+ $body = http_build_query($body);
560
+ if (!empty($highlights['body'])) {
561
+ foreach ($highlights['body'] as $matches) {
562
+ if (!empty($matches['param'])) {
563
+ $this->highlightMatches = $matches['match'];
564
+ $body = preg_replace_callback('/(&|^)(' . preg_quote(urlencode($matches['param']), '/') . ')=(.*?)(&|$)/', array(
565
+ $this, 'highlightParam',
566
+ ), $body);
567
+ }
568
+ }
569
+ }
570
+ } else if (preg_match('/^multipart\/form\-data; boundary=(.*?)$/i', $contentType, $boundaryMatches)) {
571
+ $boundary = $boundaryMatches[1];
572
+ $bodyArray = array();
573
+ foreach ($body as $key => $value) {
574
+ $bodyArray = array_merge($bodyArray, $this->reduceBodyParameter($key, $value));
575
+ }
576
+ $body = '';
577
+ foreach ($bodyArray as $param => $value) {
578
+ if (!empty($highlights['body'])) {
579
+ foreach ($highlights['body'] as $matches) {
580
+ if (!empty($matches['param']) && $matches['param'] === $param) {
581
+ $value = sprintf($this->highlightParamFormat, $value);
582
+ if (is_array($matches['match'][0])) {
583
+ $replace = array();
584
+ foreach ($matches['match'][0] as $key => $match) {
585
+ $replace[$match] = sprintf($this->highlightMatchFormat, $match);
586
+ }
587
+ if ($replace) {
588
+ $value = str_replace(array_keys($replace), $replace, $value);
589
+ }
590
+ } else { // preg_match
591
+ $value = str_replace($matches['match'][0], sprintf($this->highlightMatchFormat, $matches['match'][0]), $value);
592
+ }
593
+ break;
594
+ }
595
+ }
596
+ }
597
+
598
+ $body .= <<<FORM
599
+ --$boundary
600
+ Content-Disposition: form-data; name="$param"
601
+
602
+ $value
603
+
604
+ FORM;
605
+ }
606
+
607
+ foreach ($this->getFiles() as $param => $file) {
608
+ $name = array_key_exists('name', $file) ? $file['name'] : '';
609
+ if (is_array($name)) {
610
+ continue; // TODO: implement files as arrays
611
+ }
612
+ $mime = array_key_exists('type', $file) ? $file['type'] : '';
613
+ $value = '';
614
+ $lenToRead = $maxRequestLen - (strlen($request) + strlen($body) + 1);
615
+ if (array_key_exists('content', $file)) {
616
+ $value = $file['content'];
617
+ } else if ($lenToRead > 0 && file_exists($file['tmp_name'])) {
618
+ $handle = fopen($file['tmp_name'], 'r');
619
+ $value = fread($handle, $lenToRead);
620
+ fclose($handle);
621
+ }
622
+
623
+ if (!empty($highlights['fileNames'])) {
624
+ foreach ($highlights['fileNames'] as $matches) {
625
+ if (!empty($matches['param']) && $matches['param'] === $param) {
626
+ $name = sprintf($this->highlightParamFormat, $name);
627
+ $name = str_replace($matches['match'][0], sprintf($this->highlightMatchFormat, $matches['match'][0]), $name);
628
+ break;
629
+ }
630
+ }
631
+ }
632
+
633
+ $body .= <<<FORM
634
+ --$boundary
635
+ Content-Disposition: form-data; name="$param"; filename="$name"
636
+ Content-Type: $mime
637
+ Expires: 0
638
+
639
+ $value
640
+
641
+ FORM;
642
+ }
643
+
644
+ if ($body) {
645
+ $body .= "--$boundary--\n";
646
+ }
647
+ }
648
+ }
649
+ if (!is_string($body)) {
650
+ $body = '';
651
+ }
652
+
653
+ $request .= "\n" . $body;
654
+
655
+ if (strlen($request) > $maxRequestLen) {
656
+ $request = substr($request, 0, $maxRequestLen);
657
+ }
658
+ return $request;
659
+ }
660
+
661
+ /**
662
+ * @param array $matches
663
+ * @return string
664
+ */
665
+ private function highlightParam($matches) {
666
+ $value = '';
667
+ if (is_array($this->highlightMatches)) {
668
+ // preg_match_all
669
+ if (is_array($this->highlightMatches[0])) {
670
+ $value = $matches[3];
671
+ $replace = array();
672
+ foreach ($this->highlightMatches[0] as $key => $match) {
673
+ $this->highlightMatches[0][$key] = $this->callHighlightMatchFilter($match);
674
+ $replace[] = sprintf($this->highlightMatchFormat, $this->callHighlightMatchFilter($match));
675
+ }
676
+ if ($replace) {
677
+ $value = str_replace($this->highlightMatches[0], $replace, $value);
678
+ }
679
+
680
+ } else { // preg_match
681
+ $param = $this->callHighlightMatchFilter($this->highlightMatches[0]);
682
+ $value = str_replace($param, sprintf($this->highlightMatchFormat, $param), $matches[3]);
683
+ }
684
+ }
685
+ if (strlen($value) === 0) {
686
+ $value = sprintf($this->highlightMatchFormat, $value);
687
+ }
688
+
689
+ return $matches[1] . sprintf($this->highlightParamFormat, $matches[2] . '=' . $value) . $matches[4];
690
+ }
691
+
692
+ /**
693
+ * @param $match
694
+ * @return mixed
695
+ */
696
+ private function callHighlightMatchFilter($match) {
697
+ return is_callable($this->highlightMatchFilter) ? call_user_func($this->highlightMatchFilter, $match) : $match;
698
+ }
699
+
700
+ /**
701
+ * @param string $key
702
+ * @param string|array $value
703
+ * @return array
704
+ */
705
+ private function reduceBodyParameter($key, $value) {
706
+ if (is_array($value)) {
707
+ $param = array();
708
+ foreach ($value as $index => $val) {
709
+ $param = array_merge($param, $this->reduceBodyParameter("$key[$index]", $val));
710
+ }
711
+ return $param;
712
+ }
713
+ return array(
714
+ $key => $value,
715
+ );
716
+ }
717
+
718
+ /**
719
+ * @param mixed $auth
720
+ */
721
+ public function setAuth($auth) {
722
+ $this->auth = $auth;
723
+ }
724
+
725
+ /**
726
+ * @param mixed $body
727
+ */
728
+ public function setBody($body) {
729
+ $this->body = $body;
730
+ }
731
+
732
+ /**
733
+ * @param mixed $cookies
734
+ */
735
+ public function setCookies($cookies) {
736
+ $this->cookies = $cookies;
737
+ }
738
+
739
+ /**
740
+ * @param mixed $fileNames
741
+ */
742
+ public function setFileNames($fileNames) {
743
+ $this->fileNames = $fileNames;
744
+ }
745
+
746
+ /**
747
+ * @param mixed $files
748
+ */
749
+ public function setFiles($files) {
750
+ $this->files = $files;
751
+ }
752
+
753
+ /**
754
+ * @param mixed $headers
755
+ */
756
+ public function setHeaders($headers) {
757
+ $this->headers = $headers;
758
+ }
759
+
760
+ /**
761
+ * @param mixed $host
762
+ */
763
+ public function setHost($host) {
764
+ $this->host = $host;
765
+ }
766
+
767
+ /**
768
+ * @param mixed $ip
769
+ */
770
+ public function setIP($ip) {
771
+ $this->ip = $ip;
772
+ }
773
+
774
+ /**
775
+ * @param mixed $method
776
+ */
777
+ public function setMethod($method) {
778
+ $this->method = $method;
779
+ }
780
+
781
+ /**
782
+ * @param mixed $path
783
+ */
784
+ public function setPath($path) {
785
+ $this->path = $path;
786
+ }
787
+
788
+ /**
789
+ * @param mixed $protocol
790
+ */
791
+ public function setProtocol($protocol) {
792
+ $this->protocol = $protocol;
793
+ }
794
+
795
+ /**
796
+ * @param mixed $queryString
797
+ */
798
+ public function setQueryString($queryString) {
799
+ $this->queryString = $queryString;
800
+ }
801
+
802
+ /**
803
+ * @param mixed $timestamp
804
+ */
805
+ public function setTimestamp($timestamp) {
806
+ $this->timestamp = $timestamp;
807
+ }
808
+
809
+ /**
810
+ * @param mixed $uri
811
+ */
812
+ public function setUri($uri) {
813
+ $this->uri = $uri;
814
+ }
815
+ }
816
+
vendor/wordfence/wf-waf/src/lib/rules.php ADDED
@@ -0,0 +1,1229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ interface wfWAFRuleInterface {
4
+
5
+ /**
6
+ * @return string
7
+ */
8
+ public function render();
9
+
10
+ public function renderRule();
11
+
12
+ public function evaluate();
13
+ }
14
+
15
+ class wfWAFRuleException extends wfWAFException {
16
+ }
17
+
18
+ class wfWAFRuleLogicalOperatorException extends wfWAFException {
19
+ }
20
+
21
+ class wfWAFRule implements wfWAFRuleInterface {
22
+
23
+ private $ruleID;
24
+ private $type;
25
+ private $category;
26
+ private $score;
27
+ private $description;
28
+ private $action;
29
+ private $comparisonGroup;
30
+ /**
31
+ * @var wfWAF
32
+ */
33
+ private $waf;
34
+
35
+ /**
36
+ * @param wfWAF $waf
37
+ * @param int $ruleID
38
+ * @param string $type
39
+ * @param string $category
40
+ * @param int $score
41
+ * @param string $description
42
+ * @param string $action
43
+ * @param wfWAFRuleComparisonGroup $comparisonGroup
44
+ * @return wfWAFRule
45
+ */
46
+ public static function create($waf, $ruleID, $type, $category, $score, $description, $action, $comparisonGroup) {
47
+ return new self($waf, $ruleID, $type, $category, $score, $description, $action, $comparisonGroup);
48
+ }
49
+
50
+ /**
51
+ * @param string $value
52
+ * @return string
53
+ */
54
+ public static function exportString($value) {
55
+ return sprintf("'%s'", str_replace("'", "\\'", $value));
56
+ }
57
+
58
+ /**
59
+ * @param wfWAF $waf
60
+ * @param int $ruleID
61
+ * @param string $type
62
+ * @param string $category
63
+ * @param int $score
64
+ * @param string $description
65
+ * @param string $action
66
+ * @param wfWAFRuleComparisonGroup $comparisonGroup
67
+ */
68
+ public function __construct($waf, $ruleID, $type, $category, $score, $description, $action, $comparisonGroup) {
69
+ $this->setWAF($waf);
70
+ $this->setRuleID($ruleID);
71
+ $this->setType($type);
72
+ $this->setCategory($category);
73
+ $this->setScore($score);
74
+ $this->setDescription($description);
75
+ $this->setAction($action);
76
+ $this->setComparisonGroup($comparisonGroup);
77
+ }
78
+
79
+ /**
80
+ * @return string
81
+ */
82
+ public function render() {
83
+ return sprintf('%s::create($this, %d, %s, %s, %s, %s, %s, %s)', get_class($this),
84
+ $this->getRuleID(),
85
+ var_export($this->getType(), true),
86
+ var_export($this->getCategory(), true),
87
+ var_export($this->getScore(), true),
88
+ var_export($this->getDescription(), true),
89
+ var_export($this->getAction(), true),
90
+ $this->getComparisonGroup()->render()
91
+ );
92
+ }
93
+
94
+ /**
95
+ * @return string
96
+ */
97
+ public function renderRule() {
98
+ return sprintf(<<<RULE
99
+ if %s:
100
+ %s(%s)
101
+ RULE
102
+ ,
103
+ $this->getComparisonGroup()->renderRule(),
104
+ $this->getAction(),
105
+ join(', ', array_filter(array(
106
+ $this->getRuleID() ? 'id=' . (int) $this->getRuleID() : '',
107
+ $this->getCategory() ? 'category=' . self::exportString($this->getCategory()) : '',
108
+ $this->getScore() > 0 ? 'score=' . (int) $this->getScore() : '',
109
+ $this->getCategory() ? 'description=' . self::exportString($this->getDescription()) : '',
110
+ )))
111
+ );
112
+ }
113
+
114
+ public function evaluate() {
115
+ $comparisons = $this->getComparisonGroup();
116
+ $waf = $this->getWAF();
117
+ if ($comparisons instanceof wfWAFRuleComparisonGroup && $waf instanceof wfWAF) {
118
+ $comparisons->setRule($this);
119
+ if ($comparisons->evaluate()) {
120
+ $waf->tripRule($this);
121
+ return true;
122
+ }
123
+ }
124
+ return false;
125
+ }
126
+
127
+ public function debug() {
128
+ return $this->getComparisonGroup()->debug();
129
+ }
130
+
131
+ /**
132
+ * For JSON.
133
+ *
134
+ * @return array
135
+ */
136
+ public function toArray() {
137
+ return array(
138
+ 'ruleID' => $this->getRuleID(),
139
+ 'type' => $this->getType(),
140
+ 'category' => $this->getCategory(),
141
+ 'score' => $this->getScore(),
142
+ 'description' => $this->getDescription(),
143
+ 'action' => $this->getAction(),
144
+ );
145
+ }
146
+
147
+ /**
148
+ * @return int
149
+ */
150
+ public function getRuleID() {
151
+ return $this->ruleID;
152
+ }
153
+
154
+ /**
155
+ * @param int $ruleID
156
+ */
157
+ public function setRuleID($ruleID) {
158
+ $this->ruleID = $ruleID;
159
+ }
160
+
161
+ /**
162
+ * @return string
163
+ */
164
+ public function getType() {
165
+ return $this->type;
166
+ }
167
+
168
+ /**
169
+ * @param string $type
170
+ */
171
+ public function setType($type) {
172
+ $this->type = $type;
173
+ }
174
+
175
+ /**
176
+ * @return string
177
+ */
178
+ public function getCategory() {
179
+ return $this->category;
180
+ }
181
+
182
+ /**
183
+ * @param string $category
184
+ */
185
+ public function setCategory($category) {
186
+ $this->category = $category;
187
+ }
188
+
189
+ /**
190
+ * @return int
191
+ */
192
+ public function getScore() {
193
+ return $this->score;
194
+ }
195
+
196
+ /**
197
+ * @param int $score
198
+ */
199
+ public function setScore($score) {
200
+ $this->score = $score;
201
+ }
202
+
203
+ /**
204
+ * @return string
205
+ */
206
+ public function getDescription() {
207
+ return $this->description;
208
+ }
209
+
210
+ /**
211
+ * @param string $description
212
+ */
213
+ public function setDescription($description) {
214
+ $this->description = $description;
215
+ }
216
+
217
+ /**
218
+ * @return string
219
+ */
220
+ public function getAction() {
221
+ return $this->action;
222
+ }
223
+
224
+ /**
225
+ * @param string $action
226
+ */
227
+ public function setAction($action) {
228
+ $this->action = $action;
229
+ }
230
+
231
+ /**
232
+ * @return wfWAFRuleComparisonGroup
233
+ */
234
+ public function getComparisonGroup() {
235
+ return $this->comparisonGroup;
236
+ }
237
+
238
+ /**
239
+ * @param wfWAFRuleComparisonGroup $comparisonGroup
240
+ */
241
+ public function setComparisonGroup($comparisonGroup) {
242
+ $this->comparisonGroup = $comparisonGroup;
243
+ }
244
+
245
+ /**
246
+ * @return wfWAF
247
+ */
248
+ public function getWAF() {
249
+ return $this->waf;
250
+ }
251
+
252
+ /**
253
+ * @param wfWAF $waf
254
+ */
255
+ public function setWAF($waf) {
256
+ $this->waf = $waf;
257
+ }
258
+ }
259
+
260
+ class wfWAFRuleLogicalOperator implements wfWAFRuleInterface {
261
+
262
+ /**
263
+ * @var string
264
+ */
265
+ private $operator;
266
+
267
+ /**
268
+ * @var array
269
+ */
270
+ protected $validOperators = array(
271
+ '||',
272
+ '&&',
273
+ 'and',
274
+ 'or',
275
+ 'xor',
276
+ );
277
+ /**
278
+ * @var bool
279
+ */
280
+ private $currentValue = false;
281
+ /**
282
+ * @var wfWAFRuleInterface
283
+ */
284
+ private $comparison;
285
+
286
+ /**
287
+ * @param string $operator
288
+ * @param bool $currentValue
289
+ * @param wfWAFRuleInterface $comparison
290
+ */
291
+ public function __construct($operator, $currentValue = false, $comparison = null) {
292
+ $this->setOperator($operator);
293
+ $this->setCurrentValue($currentValue);
294
+ $this->setComparison($comparison);
295
+ }
296
+
297
+ /**
298
+ * @return string
299
+ * @throws wfWAFRuleLogicalOperatorException
300
+ */
301
+ public function render() {
302
+ if (!$this->isValid()) {
303
+ throw new wfWAFRuleLogicalOperatorException(sprintf('Invalid logical operator "%s", must be one of %s', $this->getOperator(), join(", ", $this->validOperators)));
304
+ }
305
+ return sprintf("new %s(%s)", get_class($this), var_export(trim(strtoupper($this->getOperator())), true));
306
+ }
307
+
308
+ /**
309
+ * @return string
310
+ * @throws wfWAFRuleLogicalOperatorException
311
+ */
312
+ public function renderRule() {
313
+ if (!$this->isValid()) {
314
+ throw new wfWAFRuleLogicalOperatorException(sprintf('Invalid logical operator "%s", must be one of %s', $this->getOperator(), join(", ", $this->validOperators)));
315
+ }
316
+ return trim(strtolower($this->getOperator()));
317
+ }
318
+
319
+ public function evaluate() {
320
+ $currentValue = $this->getCurrentValue();
321
+ $comparison = $this->getComparison();
322
+ if (is_bool($currentValue) && $comparison instanceof wfWAFRuleInterface) {
323
+ switch (strtolower($this->getOperator())) {
324
+ case '&&':
325
+ case 'and':
326
+ return $currentValue && $comparison->evaluate();
327
+
328
+ case '||':
329
+ case 'or':
330
+ return $currentValue || $comparison->evaluate();
331
+
332
+ case 'xor':
333
+ return $currentValue xor $comparison->evaluate();
334
+ }
335
+ }
336
+ return false;
337
+ }
338
+
339
+ /**
340
+ * @return bool
341
+ */
342
+ public function isValid() {
343
+ return in_array(strtolower($this->getOperator()), $this->validOperators);
344
+ }
345
+
346
+ /**
347
+ * @return string
348
+ */
349
+ public function getOperator() {
350
+ return $this->operator;
351
+ }
352
+
353
+ /**
354
+ * @param string $operator
355
+ */
356
+ public function setOperator($operator) {
357
+ $this->operator = $operator;
358
+ }
359
+
360
+ /**
361
+ * @return boolean
362
+ */
363
+ public function getCurrentValue() {
364
+ return $this->currentValue;
365
+ }
366
+
367
+ /**
368
+ * @param boolean $currentValue
369
+ */
370
+ public function setCurrentValue($currentValue) {
371
+ $this->currentValue = $currentValue;
372
+ }
373
+
374
+ /**
375
+ * @return wfWAFRuleInterface
376
+ */
377
+ public function getComparison() {
378
+ return $this->comparison;
379
+ }
380
+
381
+ /**
382
+ * @param wfWAFRuleInterface $comparison
383
+ */
384
+ public function setComparison($comparison) {
385
+ $this->comparison = $comparison;
386
+ }
387
+ }
388
+
389
+ class wfWAFRuleComparison implements wfWAFRuleInterface {
390
+
391
+ private $matches;
392
+ private $failedSubjects;
393
+ private $result;
394
+ /**
395
+ * @var wfWAFRule
396
+ */
397
+ private $rule;
398
+
399
+ protected static $allowedActions = array(
400
+ 'contains',
401
+ 'notcontains',
402
+ 'match',
403
+ 'notmatch',
404
+ 'matchcount',
405
+ 'containscount',
406
+ 'equals',
407
+ 'notequals',
408
+ 'identical',
409
+ 'notidentical',
410
+ 'greaterthan',
411
+ 'greaterthanequalto',
412
+ 'lessthan',
413
+ 'lessthanequalto',
414
+ 'lengthgreaterthan',
415
+ 'lengthlessthan',
416
+ 'currentuseris',
417
+ 'currentuserisnot',
418
+ );
419
+
420
+ /**
421
+ * @var mixed
422
+ */
423
+ private $expected;
424
+ /**
425
+ * @var mixed
426
+ */
427
+ private $subjects;
428
+ /**
429
+ * @var string
430
+ */
431
+ private $action;
432
+ private $multiplier;
433
+ /**
434
+ * @var wfWAF
435
+ */
436
+ private $waf;
437
+
438
+ /**
439
+ * @param wfWAF $waf
440
+ * @param string $action
441
+ * @param mixed $expected
442
+ * @param mixed $subjects
443
+ */
444
+ public function __construct($waf, $action, $expected, $subjects = null) {
445
+ $this->setWAF($waf);
446
+ $this->setAction($action);
447
+ $this->setExpected($expected);
448
+ $this->setSubjects($subjects);
449
+ }
450
+
451
+ /**
452
+ * @param string|array $subject
453
+ * @return string
454
+ */
455
+ public static function getSubjectKey($subject) {
456
+ if (!is_array($subject)) {
457
+ return (string) $subject;
458
+ }
459
+ $return = '';
460
+ $global = array_shift($subject);
461
+ foreach ($subject as $key) {
462
+ $return .= '[' . $key . ']';
463
+ }
464
+ return $global . $return;
465
+ }
466
+
467
+ /**
468
+ * @return string
469
+ * @throws wfWAFRuleException
470
+ */
471
+ public function render() {
472
+ if (!$this->isActionValid()) {
473
+ throw new wfWAFRuleException('Invalid action passed to ' . get_class($this) . ', action: ' . var_export($this->getAction(), true));
474
+ }
475
+ $subjectExport = '';
476
+ /** @var wfWAFRuleComparisonSubject $subject */
477
+ foreach ($this->getSubjects() as $subject) {
478
+ $subjectExport .= $subject->render() . ",\n";
479
+ }
480
+ $subjectExport = 'array(' . substr($subjectExport, 0, -2) . ')';
481
+
482
+ $expected = $this->getExpected();
483
+ return sprintf('new %s($this, %s, %s, %s)', get_class($this), var_export((string) $this->getAction(), true),
484
+ ($expected instanceof wfWAFRuleVariable ? $expected->render() : var_export($expected, true)), $subjectExport);
485
+ }
486
+
487
+ /**
488
+ * @return string
489
+ * @throws wfWAFRuleException
490
+ */
491
+ public function renderRule() {
492
+ if (!$this->isActionValid()) {
493
+ throw new wfWAFRuleException('Invalid action passed to ' . get_class($this) . ', action: ' . var_export($this->getAction(), true));
494
+ }
495
+ $subjectExport = '';
496
+ /** @var wfWAFRuleComparisonSubject $subject */
497
+ foreach ($this->getSubjects() as $subject) {
498
+ $subjectExport .= $subject->renderRule() . ", ";
499
+ }
500
+ $subjectExport = substr($subjectExport, 0, -2);
501
+
502
+ $expected = $this->getExpected();
503
+ return sprintf('%s(%s, %s)', $this->getAction(),
504
+ ($expected instanceof wfWAFRuleVariable ? $expected->renderRule() : wfWAFRule::exportString($expected)),
505
+ $subjectExport);
506
+ }
507
+
508
+ public function isActionValid() {
509
+ return in_array(strtolower($this->getAction()), self::$allowedActions);
510
+ }
511
+
512
+ public function evaluate() {
513
+ if (!$this->isActionValid()) {
514
+ return false;
515
+ }
516
+ $subjects = $this->getSubjects();
517
+ if (!is_array($subjects)) {
518
+ return false;
519
+ }
520
+
521
+ $this->result = false;
522
+ /** @var wfWAFRuleComparisonSubject $subject */
523
+ foreach ($subjects as $subject) {
524
+ $global = $subject->getValue();
525
+ $subjectKey = $subject->getKey();
526
+
527
+ if ($this->_evaluate(array($this, $this->getAction()), $global, $subjectKey)) {
528
+ $this->result = true;
529
+ }
530
+ }
531
+ return $this->result;
532
+ }
533
+
534
+ /**
535
+ * @param callback $callback
536
+ * @param mixed $global
537
+ * @param string $subjectKey
538
+ * @return bool
539
+ */
540
+ private function _evaluate($callback, $global, $subjectKey) {
541
+ $result = false;
542
+
543
+ if ($this->getWAF() && $this->getRule() &&
544
+ $this->getWAF()->isRuleParamWhitelisted($this->getRule()->getRuleID(), $this->getWAF()->getRequest()->getPath(), $subjectKey)
545
+ ) {
546
+ return $result;
547
+ }
548
+
549
+ if (is_array($global)) {
550
+ foreach ($global as $key => $value) {
551
+ if ($this->_evaluate($callback, $value, $subjectKey . '[' . $key . ']')) {
552
+ $result = true;
553
+ }
554
+ }
555
+ } else if (call_user_func($callback, $global)) {
556
+ $result = true;
557
+ $this->failedSubjects[] = array(
558
+ 'subject' => $subjectKey,
559
+ 'value' => $global,
560
+ 'multiplier' => $this->getMultiplier(),
561
+ 'matches' => $this->getMatches(),
562
+ );
563
+ }
564
+ return $result;
565
+ }
566
+
567
+ public function contains($subject) {
568
+ if (is_array($this->getExpected())) {
569
+ return in_array($this->getExpected(), $subject);
570
+ }
571
+ return strpos((string) $subject, (string) $this->getExpected()) !== false;
572
+ }
573
+
574
+ public function notContains($subject) {
575
+ return !$this->contains($subject);
576
+ }
577
+
578
+ public function match($subject) {
579
+ return preg_match((string) $this->getExpected(), (string) $subject, $this->matches) > 0;
580
+ }
581
+
582
+ public function notMatch($subject) {
583
+ return !$this->match($subject);
584
+ }
585
+
586
+ public function matchCount($subject) {
587
+ $this->multiplier = preg_match_all((string) $this->getExpected(), (string) $subject, $this->matches);
588
+ return $this->multiplier > 0;
589
+ }
590
+
591
+ public function containsCount($subject) {
592
+ if (is_array($this->getExpected())) {
593
+ $this->multiplier = 0;
594
+ foreach ($this->getExpected() as $val) {
595
+ if ($val == $subject) {
596
+ $this->multiplier++;
597
+ }
598
+ }
599
+ return $this->multiplier > 0;
600
+ }
601
+ $this->multiplier = substr_count($subject, $this->getExpected());
602
+ return $this->multiplier > 0;
603
+ }
604
+
605
+ public function equals($subject) {
606
+ return $this->getExpected() == $subject;
607
+ }
608
+
609
+ public function notEquals($subject) {
610
+ return $this->getExpected() != $subject;
611
+ }
612
+
613
+ public function identical($subject) {
614
+ return $this->getExpected() === $subject;
615
+ }
616
+
617
+ public function notIdentical($subject) {
618
+ return $this->getExpected() !== $subject;
619
+ }
620
+
621
+ public function greaterThan($subject) {
622
+ return $subject > $this->getExpected();
623
+ }
624
+
625
+ public function greaterThanEqualTo($subject) {
626
+ return $subject >= $this->getExpected();
627
+ }
628
+
629
+ public function lessThan($subject) {
630
+ return $subject < $this->getExpected();
631
+ }
632
+
633
+ public function lessThanEqualTo($subject) {
634
+ return $subject <= $this->getExpected();
635
+ }
636
+
637
+ public function lengthGreaterThan($subject) {
638
+ return strlen(is_array($subject) ? join('', $subject) : (string) $subject) > $this->getExpected();
639
+ }
640
+
641
+ public function lengthLessThan($subject) {
642
+ return strlen(is_array($subject) ? join('', $subject) : (string) $subject) < $this->getExpected();
643
+ }
644
+
645
+ public function currentUserIs($subject) {
646
+ if ($authCookie = $this->getWAF()->parseAuthCookie()) {
647
+ return $authCookie['role'] === $this->getExpected();
648
+ }
649
+ return false;
650
+ }
651
+
652
+ public function currentUserIsNot($subject) {
653
+ return !$this->currentUserIs($subject);
654
+ }
655
+
656
+ /**
657
+ * @return mixed
658
+ */
659
+ public function getAction() {
660
+ return $this->action;
661
+ }
662
+
663
+ /**
664
+ * @param mixed $action
665
+ */
666
+ public function setAction($action) {
667
+ $this->action = $action;
668
+ }
669
+
670
+ /**
671
+ * @return mixed
672
+ */
673
+ public function getExpected() {
674
+ return $this->expected;
675
+ }
676
+
677
+ /**
678
+ * @param mixed $expected
679
+ */
680
+ public function setExpected($expected) {
681
+ $this->expected = $expected;
682
+ }
683
+
684
+ /**
685
+ * @return mixed
686
+ */
687
+ public function getSubjects() {
688
+ return $this->subjects;
689
+ }
690
+
691
+ /**
692
+ * @param mixed $subjects
693
+ * @return $this
694
+ */
695
+ public function setSubjects($subjects) {
696
+ $this->subjects = $subjects;
697
+ return $this;
698
+ }
699
+
700
+ /**
701
+ * @return mixed
702
+ */
703
+ public function getMatches() {
704
+ return $this->matches;
705
+ }
706
+
707
+ /**
708
+ * @return mixed
709
+ */
710
+ public function getFailedSubjects() {
711
+ return $this->failedSubjects;
712
+ }
713
+
714
+ /**
715
+ * @return mixed
716
+ */
717
+ public function getResult() {
718
+ return $this->result;
719
+ }
720
+
721
+ /**
722
+ * @return mixed
723
+ */
724
+ public function getMultiplier() {
725
+ return $this->multiplier;
726
+ }
727
+
728
+ /**
729
+ * @return wfWAF
730
+ */
731
+ public function getWAF() {
732
+ return $this->waf;
733
+ }
734
+
735
+ /**
736
+ * @param wfWAF $waf
737
+ */
738
+ public function setWAF($waf) {
739
+ $this->waf = $waf;
740
+ }
741
+
742
+ /**
743
+ * @return wfWAFRule
744
+ */
745
+ public function getRule() {
746
+ return $this->rule;
747
+ }
748
+
749
+ /**
750
+ * @param wfWAFRule $rule
751
+ */
752
+ public function setRule($rule) {
753
+ $this->rule = $rule;
754
+ }
755
+ }
756
+
757
+ class wfWAFRuleComparisonGroup implements wfWAFRuleInterface {
758
+
759
+ private $items = array();
760
+ private $failedComparisons = array();
761
+ private $result = false;
762
+ /**
763
+ * @var wfWAFRule
764
+ */
765
+ private $rule;
766
+
767
+ public function __construct() {
768
+ $args = func_get_args();
769
+ foreach ($args as $arg) {
770
+ $this->add($arg);
771
+ }
772
+ }
773
+
774
+ public function add($item) {
775
+ $this->items[] = $item;
776
+ }
777
+
778
+ public function remove($item) {
779
+ $key = array_search($item, $this->items);
780
+ if ($key !== false) {
781
+ unset($this->items[$key]);
782
+ }
783
+ }
784
+
785
+ /**
786
+ *
787
+ * @throws wfWAFRuleException
788
+ */
789
+ public function evaluate() {
790
+ if (count($this->items) % 2 != 1) {
791
+ throw new wfWAFRuleException('Invalid number of rules and logical operators. Should be odd number of rules and logical operators.');
792
+ }
793
+
794
+ $this->result = false;
795
+ $operator = null;
796
+ /** @var wfWAFRuleComparison|wfWAFRuleLogicalOperator|wfWAFRuleComparisonGroup $comparison */
797
+ for ($i = 0; $i < count($this->items); $i++) {
798
+ $comparison = $this->items[$i];
799
+ if ($i % 2 == 1 && !($comparison instanceof wfWAFRuleLogicalOperator)) {
800
+ throw new wfWAFRuleException('Invalid WAF rule format, expected wfWAFRuleLogicalOperator, got ' . get_class($comparison));
801
+ }
802
+ if ($i % 2 == 0 && !($comparison instanceof wfWAFRuleComparison || $comparison instanceof wfWAFRuleComparisonGroup)) {
803
+ throw new wfWAFRuleException('Invalid WAF rule format, expected wfWAFRuleComparison or wfWAFRuleComparisonGroup, got ' . get_class($comparison));
804
+ }
805
+
806
+ if ($comparison instanceof wfWAFRuleLogicalOperator) {
807
+ $operator = $comparison;
808
+ continue;
809
+ }
810
+ if ($comparison instanceof wfWAFRuleComparison || $comparison instanceof wfWAFRuleComparisonGroup) {
811
+ $comparison->setRule($this->getRule());
812
+ if ($operator instanceof wfWAFRuleLogicalOperator) {
813
+ $operator->setCurrentValue($this->result);
814
+ $operator->setComparison($comparison);
815
+ $this->result = $operator->evaluate();
816
+ } else {
817
+ $this->result = $comparison->evaluate();
818
+ }
819
+ }
820
+ if ($comparison instanceof wfWAFRuleComparison && $comparison->getResult()) {
821
+ foreach ($comparison->getFailedSubjects() as $failedSubject) {
822
+ $this->failedComparisons[] = new wfWAFRuleComparisonFailure(
823
+ $failedSubject['subject'], $failedSubject['value'], $comparison->getExpected(),
824
+ $comparison->getAction(), $failedSubject['multiplier'], $failedSubject['matches']
825
+ );
826
+ }
827
+ }
828
+ if ($comparison instanceof wfWAFRuleComparisonGroup && $comparison->getResult()) {
829
+ foreach ($comparison->getFailedComparisons() as $comparisonFail) {
830
+ $this->failedComparisons[] = $comparisonFail;
831
+ }
832
+ }
833
+ }
834
+ return $this->result;
835
+ }
836
+
837
+ /**
838
+ * @return string
839
+ * @throws wfWAFRuleException
840
+ */
841
+ public function render() {
842
+ if (count($this->items) % 2 != 1) {
843
+ throw new wfWAFRuleException('Invalid number of rules and logical operators. Should be odd number of rules and logical operators.');
844
+ }
845
+
846
+ $return = array();
847
+ /**
848
+ * @var wfWAFRuleInterface $item
849
+ */
850
+ for ($i = 0; $i < count($this->items); $i++) {
851
+ $item = $this->items[$i];
852
+ if ($i % 2 == 1 && !($item instanceof wfWAFRuleLogicalOperator)) {
853
+ throw new wfWAFRuleException('Invalid WAF rule format, expected wfWAFRuleLogicalOperator, got ' . get_class($item));
854
+ }
855
+ if ($i % 2 == 0 && !($item instanceof wfWAFRuleComparison || $item instanceof wfWAFRuleComparisonGroup)) {
856
+ throw new wfWAFRuleException('Invalid WAF rule format, expected wfWAFRule or wfWAFRuleComparisonGroup, got ' . get_class($item));
857
+ }
858
+ $return[] = $item->render();
859
+ }
860
+ return sprintf('new %s(%s)', get_class($this), join(', ', $return));
861
+ }
862
+
863
+ /**
864
+ * @return string
865
+ * @throws wfWAFRuleException
866
+ */
867
+ public function renderRule() {
868
+ if (count($this->items) % 2 != 1) {
869
+ throw new wfWAFRuleException('Invalid number of rules and logical operators. Should be odd number of rules and logical operators.');
870
+ }
871
+
872
+ $return = array();
873
+ /**
874
+ * @var wfWAFRuleInterface $item
875
+ */
876
+ for ($i = 0; $i < count($this->items); $i++) {
877
+ $item = $this->items[$i];
878
+ if ($i % 2 == 1 && !($item instanceof wfWAFRuleLogicalOperator)) {
879
+ throw new wfWAFRuleException('Invalid WAF rule format, expected wfWAFRuleLogicalOperator, got ' . get_class($item));
880
+ }
881
+ if ($i % 2 == 0 && !($item instanceof wfWAFRuleComparison || $item instanceof wfWAFRuleComparisonGroup)) {
882
+ throw new wfWAFRuleException('Invalid WAF rule format, expected wfWAFRule or wfWAFRuleComparisonGroup, got ' . get_class($item));
883
+ }
884
+ $return[] = $item->renderRule();
885
+ }
886
+ return sprintf('(%s)', join(' ', $return));
887
+ }
888
+
889
+ public function debug() {
890
+ $debug = '';
891
+ /** @var wfWAFRuleComparisonFailure $failedComparison */
892
+ foreach ($this->getFailedComparisons() as $failedComparison) {
893
+ $debug .= $failedComparison->getParamKey() . ' ' . $failedComparison->getAction() . ' ' . $failedComparison->getExpected() . "\n";
894
+ }
895
+ return $debug;
896
+ }
897
+
898
+ /**
899
+ * @return array
900
+ */
901
+ public function getItems() {
902
+ return $this->items;
903
+ }
904
+
905
+ /**
906
+ * @param array $items
907
+ */
908
+ public function setItems($items) {
909
+ $this->items = $items;
910
+ }
911
+
912
+ /**
913
+ * @return mixed
914
+ */
915
+ public function getFailedComparisons() {
916
+ return $this->failedComparisons;
917
+ }
918
+
919
+ /**
920
+ * @return boolean
921
+ */
922
+ public function getResult() {
923
+ return $this->result;
924
+ }
925
+
926
+ /**
927
+ * @return wfWAFRule
928
+ */
929
+ public function getRule() {
930
+ return $this->rule;
931
+ }
932
+
933
+ /**
934
+ * @param wfWAFRule $rule
935
+ */
936
+ public function setRule($rule) {
937
+ $this->rule = $rule;
938
+ }
939
+ }
940
+
941
+ class wfWAFRuleComparisonFailure {
942
+
943
+ private $paramKey;
944
+ private $expected;
945
+ private $action;
946
+ /**
947
+ * @var null|int
948
+ */
949
+ private $multiplier;
950
+ /**
951
+ * @var string
952
+ */
953
+ private $paramValue;
954
+ /**
955
+ * @var mixed
956
+ */
957
+ private $matches;
958
+
959
+ /**
960
+ * @param string $paramKey
961
+ * @param string $paramValue
962
+ * @param string $expected
963
+ * @param string $action
964
+ * @param mixed $multiplier
965
+ * @param mixed $matches
966
+ */
967
+ public function __construct($paramKey, $paramValue, $expected, $action, $multiplier = null, $matches = null) {
968
+ $this->setParamKey($paramKey);
969
+ $this->setExpected($expected);
970
+ $this->setAction($action);
971
+ $this->setMultiplier($multiplier);
972
+ $this->setParamValue($paramValue);
973
+ $this->setMatches($matches);
974
+ }
975
+
976
+ /**
977
+ * @return mixed
978
+ */
979
+ public function getParamKey() {
980
+ return $this->paramKey;
981
+ }
982
+
983
+ /**
984
+ * @param mixed $paramKey
985
+ */
986
+ public function setParamKey($paramKey) {
987
+ $this->paramKey = $paramKey;
988
+ }
989
+
990
+ /**
991
+ * @return mixed
992
+ */
993
+ public function getExpected() {
994
+ return $this->expected;
995
+ }
996
+
997
+ /**
998
+ * @param mixed $expected
999
+ */
1000
+ public function setExpected($expected) {
1001
+ $this->expected = $expected;
1002
+ }
1003
+
1004
+ /**
1005
+ * @return mixed
1006
+ */
1007
+ public function getAction() {
1008
+ return $this->action;
1009
+ }
1010
+
1011
+ /**
1012
+ * @param mixed $action
1013
+ */
1014
+ public function setAction($action) {
1015
+ $this->action = $action;
1016
+ }
1017
+
1018
+ /**
1019
+ * @return int|null
1020
+ */
1021
+ public function getMultiplier() {
1022
+ return $this->multiplier;
1023
+ }
1024
+
1025
+ /**
1026
+ * @param int|null $multiplier
1027
+ */
1028
+ public function setMultiplier($multiplier) {
1029
+ $this->multiplier = $multiplier;
1030
+ }
1031
+
1032
+ /**
1033
+ * @return bool
1034
+ */
1035
+ public function hasMultiplier() {
1036
+ return $this->getMultiplier() > 1;
1037
+ }
1038
+
1039
+ /**
1040
+ * @return string
1041
+ */
1042
+ public function getParamValue() {
1043
+ return $this->paramValue;
1044
+ }
1045
+
1046
+ /**
1047
+ * @param string $paramValue
1048
+ */
1049
+ public function setParamValue($paramValue) {
1050
+ $this->paramValue = $paramValue;
1051
+ }
1052
+
1053
+ /**
1054
+ * @return mixed
1055
+ */
1056
+ public function getMatches() {
1057
+ return $this->matches;
1058
+ }
1059
+
1060
+ /**
1061
+ * @param mixed $matches
1062
+ */
1063
+ public function setMatches($matches) {
1064
+ $this->matches = $matches;
1065
+ }
1066
+ }
1067
+
1068
+ class wfWAFRuleComparisonSubject {
1069
+
1070
+ /**
1071
+ * @var array
1072
+ */
1073
+ private $subject;
1074
+ /**
1075
+ * @var array
1076
+ */
1077
+ private $filters;
1078
+
1079
+ /** @var wfWAF */
1080
+ private $waf;
1081
+
1082
+ public static function create($waf, $subject, $filters) {
1083
+ return new self($waf, $subject, $filters);
1084
+ }
1085
+
1086
+ /**
1087
+ * wfWAFRuleComparisonSubject constructor.
1088
+ * @param wfWAF $waf
1089
+ * @param array $subject
1090
+ * @param array $filters
1091
+ */
1092
+ public function __construct($waf, $subject, $filters) {
1093
+ $this->waf = $waf;
1094
+ $this->subject = $subject;
1095
+ $this->filters = $filters;
1096
+ }
1097
+
1098
+ /**
1099
+ * @return mixed|null
1100
+ */
1101
+ public function getValue() {
1102
+ $subject = $this->getSubject();
1103
+ if (!is_array($subject)) {
1104
+ return $this->runFilters($this->getWAF()->getGlobal($subject));
1105
+ }
1106
+ if (is_array($subject) && count($subject) > 0) {
1107
+ $globalKey = array_shift($subject);
1108
+ return $this->runFilters($this->_getValue($subject, $this->getWAF()->getGlobal($globalKey)));
1109
+ }
1110
+ return null;
1111
+ }
1112
+
1113
+ /**
1114
+ * @param array $subjectKey
1115
+ * @param array $global
1116
+ * @return null
1117
+ */
1118
+ private function _getValue($subjectKey, $global) {
1119
+ if (!is_array($global) || !is_array($subjectKey)) {
1120
+ return null;
1121
+ }
1122
+
1123
+ $key = array_shift($subjectKey);
1124
+ if (array_key_exists($key, $global)) {
1125
+ if (count($subjectKey) > 0) {
1126
+ return $this->_getValue($subjectKey, $global[$key]);
1127
+ } else {
1128
+ return $global[$key];
1129
+ }
1130
+ }
1131
+ return null;
1132
+ }
1133
+
1134
+
1135
+ /**
1136
+ * @return string
1137
+ */
1138
+ public function getKey() {
1139
+ return wfWAFRuleComparison::getSubjectKey($this->getSubject());
1140
+ }
1141
+
1142
+ /**
1143
+ * @param mixed $value
1144
+ * @return mixed
1145
+ */
1146
+ private function runFilters($value) {
1147
+ $filters = $this->getFilters();
1148
+ if (is_array($filters)) {
1149
+ foreach ($filters as $filter) {
1150
+ if (method_exists($this, 'filter' . $filter)) {
1151
+ $value = call_user_func(array($this, 'filter' . $filter), $value);
1152
+ }
1153
+ }
1154
+ }
1155
+ return $value;
1156
+ }
1157
+
1158
+ /**
1159
+ * @param mixed $value
1160
+ * @return string
1161
+ */
1162
+ public function filterBase64decode($value) {
1163
+ if (is_string($value)) {
1164
+ return base64_decode($value);
1165
+ }
1166
+ return $value;
1167
+ }
1168
+
1169
+ /**
1170
+ * @return string
1171
+ */
1172
+ public function render() {
1173
+ return sprintf('%s::create($this, %s, %s)', get_class($this), var_export($this->getSubject(), true),
1174
+ var_export($this->getFilters(), true));
1175
+ }
1176
+
1177
+ /**
1178
+ * @return string
1179
+ */
1180
+ public function renderRule() {
1181
+ $rule = is_array($this->getSubject()) ? join('.', $this->getSubject()) : $this->getSubject();
1182
+ foreach ($this->getFilters() as $filter) {
1183
+ $rule = $filter . '(' . $rule . ')';
1184
+ }
1185
+ return $rule;
1186
+ }
1187
+
1188
+ /**
1189
+ * @return array
1190
+ */
1191
+ public function getSubject() {
1192
+ return $this->subject;
1193
+ }
1194
+
1195
+ /**
1196
+ * @param array $subject
1197
+ */
1198
+ public function setSubject($subject) {
1199
+ $this->subject = $subject;
1200
+ }
1201
+
1202
+ /**
1203
+ * @return array
1204
+ */
1205
+ public function getFilters() {
1206
+ return $this->filters;
1207
+ }
1208
+
1209
+ /**
1210
+ * @param array $filters
1211
+ */
1212
+ public function setFilters($filters) {
1213
+ $this->filters = $filters;
1214
+ }
1215
+
1216
+ /**
1217
+ * @return wfWAF
1218
+ */
1219
+ public function getWAF() {
1220
+ return $this->waf;
1221
+ }
1222
+
1223
+ /**
1224
+ * @param wfWAF $waf
1225
+ */
1226
+ public function setWAF($waf) {
1227
+ $this->waf = $waf;
1228
+ }
1229
+ }
vendor/wordfence/wf-waf/src/lib/storage.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ interface wfWAFStorageInterface {
4
+
5
+ public function hasPreviousAttackData($olderThan);
6
+
7
+ public function hasNewerAttackData($newerThan);
8
+
9
+ public function getAttackData();
10
+
11
+ public function getAttackDataArray();
12
+
13
+ public function getNewestAttackDataArray($newerThan);
14
+
15
+ public function truncateAttackData();
16
+
17
+ /**
18
+ * @param array $failedRules
19
+ * @param string $failedParamKey
20
+ * @param string $failedParamValue
21
+ * @param wfWAFRequestInterface $request
22
+ * @param mixed $_
23
+ * @return mixed
24
+ */
25
+ public function logAttack($failedRules, $failedParamKey, $failedParamValue, $request, $_ = null);
26
+
27
+ /**
28
+ * @param int $timestamp
29
+ * @param string $ip
30
+ * @param bool $ssl
31
+ * @param array $failedRuleIDs
32
+ * @param wfWAFRequestInterface|string $request
33
+ * @param mixed $_
34
+ * @return mixed
35
+ */
36
+ // public function logAttack($timestamp, $ip, $ssl, $failedRuleIDs, $request, $_ = null);
37
+
38
+ /**
39
+ * @param float $timestamp
40
+ * @param string $ip
41
+ * @return mixed
42
+ */
43
+ public function blockIP($timestamp, $ip);
44
+
45
+ public function isIPBlocked($ip);
46
+
47
+ public function getConfig($key, $default = null);
48
+
49
+ public function setConfig($key, $value);
50
+
51
+ public function unsetConfig($key);
52
+
53
+ public function uninstall();
54
+
55
+ public function isInLearningMode();
56
+
57
+ public function isDisabled();
58
+
59
+ public function getRulesDSLCacheFile();
60
+
61
+ public function isAttackDataFull();
62
+ }
vendor/wordfence/wf-waf/src/lib/storage/file.php ADDED
@@ -0,0 +1,1320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wfWAFStorageFile implements wfWAFStorageInterface {
4
+
5
+ const LOG_FILE_HEADER = "<?php exit('Access denied'); __halt_compiler(); ?>\n";
6
+
7
+ public static function atomicFilePutContents($file, $content, $prefix = 'config') {
8
+ $tmpFile = @tempnam(dirname($file), $prefix . '.tmp.');
9
+ if (!$tmpFile) {
10
+ $tmpFile = @tempnam(sys_get_temp_dir(), $prefix . '.tmp.');
11
+ }
12
+ if (!$tmpFile) {
13
+ throw new wfWAFStorageFileException('Unable to save temporary file for atomic writing.');
14
+ }
15
+ $tmpHandle = @fopen($tmpFile, 'w');
16
+ if (!$tmpHandle) {
17
+ throw new wfWAFStorageFileException('Unable to save temporary file ' . $tmpFile . ' for atomic writing.');
18
+ }
19
+
20
+ self::lock($tmpHandle, LOCK_EX);
21
+ fwrite($tmpHandle, $content);
22
+ fflush($tmpHandle);
23
+ self::lock($tmpHandle, LOCK_UN);
24
+ fclose($tmpHandle);
25
+
26
+ // Attempt to verify file has finished writing (sometimes the disk will lie for better benchmarks)
27
+ $tmpContents = file_get_contents($tmpFile);
28
+ if ($tmpContents !== $content) {
29
+ throw new wfWAFStorageFileException('Unable to verify temporary file contents for atomic writing.');
30
+ }
31
+
32
+ if (!@rename($tmpFile, $file)) {
33
+ $backFile = @tempnam(dirname($file), $prefix . '.bak.');
34
+ if (!$backFile) {
35
+ $backFile = @tempnam(sys_get_temp_dir(), $prefix . '.bak.');
36
+ }
37
+ if (!$backFile) {
38
+ throw new wfWAFStorageFileException('Unable to save temporary file for atomic writing.');
39
+ }
40
+ if (WFWAF_DEBUG) {
41
+ rename($file, $backFile);
42
+ rename($tmpFile, $file);
43
+ unlink($backFile);
44
+ unlink($tmpFile);
45
+ } else {
46
+ @rename($file, $backFile);
47
+ @rename($tmpFile, $file);
48
+ @unlink($backFile);
49
+ @unlink($tmpFile);
50
+ }
51
+ }
52
+ }
53
+
54
+ public static function lock($handle, $lock, $wouldLock = 1) {
55
+ $locked = flock($handle, $lock, $wouldLock);
56
+ if (!$locked) {
57
+ error_log('Lock not acquired ' . $locked);
58
+ }
59
+ return $locked;
60
+ }
61
+
62
+ /**
63
+ * @var resource
64
+ */
65
+ private $ipCacheFileHandle;
66
+
67
+ /**
68
+ * @var string|null
69
+ */
70
+ private $attackDataFile;
71
+ /**
72
+ * @var wfWAFAttackDataStorageFileEngine
73
+ */
74
+ private $attackDataEngine;
75
+
76
+ /**
77
+ * @var string|null
78
+ */
79
+ private $ipCacheFile;
80
+ private $configFile;
81
+ private $rulesDSLCacheFile;
82
+ private $dataChanged = false;
83
+ private $data = false;
84
+ /**
85
+ * @var resource
86
+ */
87
+ private $configFileHandle;
88
+ private $uninstalled;
89
+ private $attackDataRows;
90
+ private $attackDataNewerThan;
91
+
92
+
93
+ /**
94
+ * @param string|null $attackDataFile
95
+ * @param string|null $ipCacheFile
96
+ * @param string|null $configFile
97
+ * @param null $rulesDSLCacheFile
98
+ */
99
+ public function __construct($attackDataFile = null, $ipCacheFile = null, $configFile = null, $rulesDSLCacheFile = null) {
100
+ $this->setAttackDataFile($attackDataFile);
101
+ $this->setIPCacheFile($ipCacheFile);
102
+ $this->setConfigFile($configFile);
103
+ $this->setRulesDSLCacheFile($rulesDSLCacheFile);
104
+ }
105
+
106
+ /**
107
+ * @param float $olderThan
108
+ * @return bool
109
+ * @throws wfWAFStorageFileException
110
+ */
111
+ public function hasPreviousAttackData($olderThan) {
112
+ $this->open();
113
+ $timestamp = $this->getAttackDataEngine()->getOldestTimestamp();
114
+ return $timestamp && $timestamp < $olderThan;
115
+ }
116
+
117
+ /**
118
+ * @param float $newerThan
119
+ * @return bool
120
+ * @throws wfWAFStorageFileException
121
+ */
122
+ public function hasNewerAttackData($newerThan) {
123
+ $this->open();
124
+ $timestamp = $this->getAttackDataEngine()->getNewestTimestamp();
125
+ return $timestamp && $timestamp > $newerThan;
126
+ }
127
+
128
+
129
+ /**
130
+ * @return mixed|string|void
131
+ * @throws wfWAFStorageFileException
132
+ */
133
+ public function getAttackData() {
134
+ $this->open();
135
+ $this->attackDataRows = array();
136
+ $this->getAttackDataEngine()->scanRows(array($this, '_getAttackDataRowsSerialized'));
137
+ return json_encode($this->attackDataRows);
138
+ }
139
+
140
+ /**
141
+ * @return array
142
+ * @throws wfWAFStorageFileException
143
+ */
144
+ public function getAttackDataArray() {
145
+ $this->open();
146
+ $this->attackDataRows = array();
147
+ $this->getAttackDataEngine()->scanRows(array($this, '_getAttackDataRows'));
148
+ return $this->attackDataRows;
149
+ }
150
+
151
+ /**
152
+ * @param resource $fileHandle
153
+ * @param int $offset
154
+ * @param int $length
155
+ */
156
+ public function _getAttackDataRowsSerialized($fileHandle, $offset, $length) {
157
+ fseek($fileHandle, $offset);
158
+ self::lock($fileHandle, LOCK_SH);
159
+ $binary = fread($fileHandle, $length);
160
+ self::lock($fileHandle, LOCK_UN);
161
+ $row = wfWAFAttackDataStorageFileEngineRow::unpack($binary);
162
+ $data = json_decode($row->getData(), true);
163
+ if (is_array($data)) {
164
+ array_unshift($data, $row->getTimestamp());
165
+ $this->attackDataRows[] = $data;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * @param resource $fileHandle
171
+ * @param int $offset
172
+ * @param int $length
173
+ */
174
+ public function _getAttackDataRows($fileHandle, $offset, $length) {
175
+ fseek($fileHandle, $offset);
176
+ self::lock($fileHandle, LOCK_SH);
177
+ $binary = fread($fileHandle, $length);
178
+ self::lock($fileHandle, LOCK_UN);
179
+ $row = wfWAFAttackDataStorageFileEngineRow::unpack($binary);
180
+ $data = $this->unserializeRow($row->getData());
181
+ array_unshift($data, $row->getTimestamp());
182
+ $this->attackDataRows[] = $data;
183
+ }
184
+
185
+ /**
186
+ * @param $newerThan
187
+ * @return array
188
+ * @throws wfWAFStorageFileException
189
+ */
190
+ public function getNewestAttackDataArray($newerThan) {
191
+ $this->open();
192
+ $this->attackDataRows = array();
193
+ $this->attackDataNewerThan = $newerThan;
194
+ $this->getAttackDataEngine()->scanRowsReverse(array($this, '_getAttackDataRowsNewerThan'));
195
+ return $this->attackDataRows;
196
+ }
197
+
198
+ /**
199
+ * @param resource $fileHandle
200
+ * @param int $offset
201
+ * @param int $length
202
+ * @return bool
203
+ */
204
+ public function _getAttackDataRowsNewerThan($fileHandle, $offset, $length) {
205
+ fseek($fileHandle, $offset);
206
+ self::lock($fileHandle, LOCK_SH);
207
+ $binaryTimestamp = fread($fileHandle, 8);
208
+ self::lock($fileHandle, LOCK_UN);
209
+ $timestamp = wfWAFAttackDataStorageFileEngine::unpackMicrotime($binaryTimestamp);
210
+ if ($timestamp > $this->attackDataNewerThan) {
211
+ $binary = $binaryTimestamp . fread($fileHandle, $length - 8);
212
+ $row = wfWAFAttackDataStorageFileEngineRow::unpack($binary);
213
+ $data = $this->unserializeRow($row->getData());
214
+ if (is_array($data)) {
215
+ array_unshift($data, $row->getTimestamp());
216
+ $this->attackDataRows[] = $data;
217
+ }
218
+ return true;
219
+ }
220
+ return false;
221
+ }
222
+
223
+ /**
224
+ * @return bool
225
+ * @throws wfWAFStorageFileException
226
+ */
227
+ public function truncateAttackData() {
228
+ $this->open();
229
+ $this->getAttackDataEngine()->truncate();
230
+ return $this->getAttackDataEngine()->getRowCount() === 0;
231
+ }
232
+
233
+ /**
234
+ * @return bool
235
+ * @throws wfWAFStorageFileException
236
+ */
237
+ public function isAttackDataFull() {
238
+ $this->open();
239
+ return $this->getAttackDataEngine()->getRowCount() === wfWAFAttackDataStorageFileEngine::MAX_ROWS;
240
+ }
241
+
242
+ /**
243
+ * @param array $failedRules
244
+ * @param string $failedParamKey
245
+ * @param string $failedParamValue
246
+ * @param wfWAFRequestInterface $request
247
+ * @param mixed $_
248
+ * @return mixed
249
+ */
250
+ public function logAttack($failedRules, $failedParamKey, $failedParamValue, $request, $_ = null) {
251
+ $this->open();
252
+ $row = array(
253
+ $request->getTimestamp(),
254
+ $request->getIP(),
255
+ (int) $this->isInLearningMode(),
256
+ $failedParamKey,
257
+ $failedParamValue,
258
+ );
259
+
260
+ $failedRulesString = '';
261
+ if (is_array($failedRules)) {
262
+ /**
263
+ * @var int $index
264
+ * @var wfWAFRule|int $rule
265
+ */
266
+ foreach ($failedRules as $index => $rule) {
267
+ if ($rule instanceof wfWAFRule) {
268
+ $failedRulesString .= $rule->getRuleID() . '|';
269
+ } else {
270
+ $failedRulesString .= $rule . '|';
271
+ }
272
+ }
273
+ $failedRulesString = substr($failedRulesString, 0, -1);
274
+ }
275
+ $row[] = $failedRulesString;
276
+ $row[] = $request->getProtocol() === 'https' ? 1 : 0;
277
+ $row[] = (string) $request;
278
+ $args = func_get_args();
279
+ $row = array_merge($row, array_slice($args, 4));
280
+
281
+ if (($rowString = $this->serializeRow($row)) !== false) {
282
+ $attackRow = new wfWAFAttackDataStorageFileEngineRow(microtime(false), $rowString);
283
+ $this->getAttackDataEngine()->addRow($attackRow);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * @param int $timestamp
289
+ * @param string $ip
290
+ * @return mixed|void
291
+ * @throws wfWAFStorageFileException
292
+ */
293
+ public function blockIP($timestamp, $ip) {
294
+ $this->open();
295
+ if (!$this->isIPBlocked($ip)) {
296
+ self::lock($this->ipCacheFileHandle, LOCK_EX);
297
+ fseek($this->ipCacheFileHandle, 0, SEEK_END);
298
+ fwrite($this->ipCacheFileHandle, wfWAFUtils::inet_pton($ip) . pack('V', $timestamp));
299
+ fflush($this->ipCacheFileHandle);
300
+ self::lock($this->ipCacheFileHandle, LOCK_UN);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * @param string $ip
306
+ * @return bool
307
+ */
308
+ public function isIPBlocked($ip) {
309
+ $this->open();
310
+ $ipBin = wfWAFUtils::inet_pton($ip);
311
+ fseek($this->ipCacheFileHandle, strlen(self::LOG_FILE_HEADER), SEEK_SET);
312
+ self::lock($this->ipCacheFileHandle, LOCK_SH);
313
+ while (!feof($this->ipCacheFileHandle)) {
314
+ $ipStr = fread($this->ipCacheFileHandle, 20);
315
+ $ip2 = substr($ipStr, 0, 16);
316
+ if ($ipBin === $ip2 && unpack('V', substr($ipStr, 16, 4)) >= time()) {
317
+ self::lock($this->ipCacheFileHandle, LOCK_UN);
318
+ return true;
319
+ }
320
+ }
321
+ self::lock($this->ipCacheFileHandle, LOCK_UN);
322
+ return false;
323
+ }
324
+
325
+ /**
326
+ * @return bool
327
+ */
328
+ public function isOpened() {
329
+ return is_resource($this->configFileHandle);
330
+ }
331
+
332
+ /**
333
+ * @throws wfWAFStorageFileException
334
+ */
335
+ public function open() {
336
+ if ($this->isOpened()) {
337
+ return;
338
+ }
339
+ if ($this->uninstalled) {
340
+ throw new wfWAFStorageFileException('Unable to open WAF file storage, WAF has been uninstalled.');
341
+ }
342
+
343
+ $files = array(
344
+ array($this->getIPCacheFile(), 'ipCacheFileHandle', self::LOG_FILE_HEADER),
345
+ array($this->getConfigFile(), 'configFileHandle', self::LOG_FILE_HEADER . serialize($this->getDefaultConfiguration())),
346
+ );
347
+ foreach ($files as $file) {
348
+ list($filePath, $fileHandle, $defaultContents) = $file;
349
+ if (!file_exists($filePath)) {
350
+ @file_put_contents($filePath, $defaultContents, LOCK_EX);
351
+ }
352
+ $this->$fileHandle = @fopen($filePath, 'r+');
353
+ if (!$this->$fileHandle) {
354
+ throw new wfWAFStorageFileException('Unable to open ' . $filePath . ' for reading and writing.');
355
+ }
356
+ }
357
+
358
+ $this->setAttackDataEngine(new wfWAFAttackDataStorageFileEngine($this->getAttackDataFile()));
359
+ $this->getAttackDataEngine()->open();
360
+ }
361
+
362
+ /**
363
+ *
364
+ */
365
+ public function close() {
366
+ if (!$this->isOpened()) {
367
+ return;
368
+ }
369
+ fclose($this->ipCacheFileHandle);
370
+ fclose($this->configFileHandle);
371
+ $this->ipCacheFileHandle = null;
372
+ $this->configFileHandle = null;
373
+ $this->getAttackDataEngine()->close();
374
+ }
375
+
376
+ /**
377
+ * Clean up old expired IP blocks.
378
+ */
379
+ public function vacuum() {
380
+ $this->open();
381
+ $readPointer = strlen(self::LOG_FILE_HEADER);
382
+ $writePointer = strlen(self::LOG_FILE_HEADER);
383
+ fseek($this->ipCacheFileHandle, $readPointer, SEEK_SET);
384
+ self::lock($this->ipCacheFileHandle, LOCK_EX);
385
+ while (!feof($this->ipCacheFileHandle)) {
386
+ $ipCacheRow = fread($this->ipCacheFileHandle, 20);
387
+ $expires = unpack('V', substr($ipCacheRow, 16, 4));
388
+ if ($expires >= time()) {
389
+ fseek($this->ipCacheFileHandle, $writePointer, SEEK_SET);
390
+ fwrite($this->ipCacheFileHandle, $ipCacheRow);
391
+ $writePointer += 20;
392
+ fseek($this->ipCacheFileHandle, $readPointer, SEEK_SET);
393
+ }
394
+ $readPointer += 20;
395
+ fseek($this->ipCacheFileHandle, $readPointer, SEEK_SET);
396
+ }
397
+ ftruncate($this->ipCacheFileHandle, $writePointer);
398
+ self::lock($this->ipCacheFileHandle, LOCK_UN);
399
+ }
400
+
401
+ /**
402
+ * @param string $key
403
+ * @param mixed $default
404
+ * @return mixed
405
+ */
406
+ public function getConfig($key, $default = null) {
407
+ if (!$this->data) {
408
+ $this->fetchConfigData();
409
+ }
410
+ return array_key_exists($key, $this->data) ? $this->data[$key] : $default;
411
+ }
412
+
413
+ /**
414
+ * @param string $key
415
+ * @param mixed $value
416
+ */
417
+ public function setConfig($key, $value) {
418
+ if (!$this->data) {
419
+ $this->fetchConfigData();
420
+ }
421
+ if (!$this->dataChanged && (
422
+ (array_key_exists($key, $this->data) && $this->data[$key] !== $value) ||
423
+ !array_key_exists($key, $this->data)
424
+ )
425
+ ) {
426
+ $this->dataChanged = array($key, true);
427
+ register_shutdown_function(array($this, 'saveConfig'));
428
+ }
429
+ $this->data[$key] = $value;
430
+ }
431
+
432
+ /**
433
+ * @param string $key
434
+ */
435
+ public function unsetConfig($key) {
436
+ if (!$this->data) {
437
+ $this->fetchConfigData();
438
+ }
439
+ if (!$this->dataChanged && array_key_exists($key, $this->data)) {
440
+ $this->dataChanged = array($key, true);
441
+ register_shutdown_function(array($this, 'saveConfig'));
442
+ }
443
+ unset($this->data[$key]);
444
+ }
445
+
446
+ /**
447
+ * @throws wfWAFStorageFileException
448
+ */
449
+ public function fetchConfigData() {
450
+ $this->configFileHandle = null;
451
+ $this->open();
452
+ self::lock($this->configFileHandle, LOCK_SH);
453
+ $i = 0;
454
+ // Attempt to read contents of the config file. This could be in the middle of a write, so we account for it and
455
+ // wait for the operation to complete.
456
+ fseek($this->configFileHandle, strlen(self::LOG_FILE_HEADER), SEEK_SET);
457
+ $serializedData = '';
458
+ while (!feof($this->configFileHandle)) {
459
+ $serializedData .= fread($this->configFileHandle, 1024);
460
+ }
461
+ $this->data = @unserialize($serializedData);
462
+
463
+ if ($this->data === false) {
464
+ throw new wfWAFStorageFileConfigException('Error reading config data, configuration file could be corrupted.');
465
+ }
466
+
467
+ self::lock($this->configFileHandle, LOCK_UN);
468
+ }
469
+
470
+ /**
471
+ * @throws wfWAFStorageFileException
472
+ */
473
+ public function saveConfig() {
474
+ if (WFWAF_DEBUG) {
475
+ error_log('Saving WAF config for change in key ' . $this->dataChanged[0] . ', value: ' .
476
+ ((is_object($this->data[$this->dataChanged[0]]) || $this->dataChanged[0] === 'cron') ?
477
+ gettype($this->data[$this->dataChanged[0]]) :
478
+ var_export($this->data[$this->dataChanged[0]], true)));
479
+ }
480
+
481
+ if ($this->uninstalled) {
482
+ return;
483
+ }
484
+
485
+ wfWAFStorageFile::atomicFilePutContents($this->getConfigFile(), self::LOG_FILE_HEADER . serialize($this->data));
486
+ }
487
+
488
+ /**
489
+ *
490
+ */
491
+ public function uninstall() {
492
+ $this->uninstalled = true;
493
+ $this->close();
494
+ @unlink($this->getConfigFile());
495
+ @unlink($this->getAttackDataFile());
496
+ @unlink($this->getIPCacheFile());
497
+ @unlink($this->getRulesDSLCacheFile());
498
+ }
499
+
500
+ /**
501
+ * @return bool
502
+ */
503
+ public function isInLearningMode() {
504
+ if ($this->getConfig('wafStatus') == 'learning-mode') {
505
+ if ($this->getConfig('learningModeGracePeriodEnabled')) {
506
+ if ($this->getConfig('learningModeGracePeriod') > time()) {
507
+ return true;
508
+ } else {
509
+ // Reached the end of the grace period, activate the WAF.
510
+ $this->setConfig('wafStatus', 'enabled');
511
+ $this->setConfig('learningModeGracePeriodEnabled', 0);
512
+ $this->unsetConfig('learningModeGracePeriod');
513
+ }
514
+ } else {
515
+ return true;
516
+ }
517
+ }
518
+ return false;
519
+ }
520
+
521
+ public function isDisabled() {
522
+ return $this->getConfig('wafStatus') === 'disabled' || $this->getConfig('wafDisabled');
523
+ }
524
+
525
+ /**
526
+ * @return array
527
+ */
528
+ public function getDefaultConfiguration() {
529
+ return array(
530
+ 'wafStatus' => 'learning-mode',
531
+ 'learningModeGracePeriodEnabled' => 1,
532
+ 'learningModeGracePeriod' => time() + (86400 * 7),
533
+ 'authKey' => wfWAFUtils::getRandomString(64),
534
+ );
535
+ }
536
+
537
+ /**
538
+ * @return mixed
539
+ */
540
+ public function getConfigFile() {
541
+ return $this->configFile;
542
+ }
543
+
544
+ /**
545
+ * @param mixed $configFile
546
+ */
547
+ public function setConfigFile($configFile) {
548
+ $this->configFile = $configFile;
549
+ }
550
+
551
+ /**
552
+ * @return string|null
553
+ */
554
+ public function getAttackDataFile() {
555
+ return $this->attackDataFile;
556
+ }
557
+
558
+ /**
559
+ * @param string|null $attackDataFile
560
+ */
561
+ public function setAttackDataFile($attackDataFile) {
562
+ $this->attackDataFile = $attackDataFile;
563
+ }
564
+
565
+ /**
566
+ * @return string|null
567
+ */
568
+ public function getIPCacheFile() {
569
+ return $this->ipCacheFile;
570
+ }
571
+
572
+ /**
573
+ * @param string|null $ipCacheFile
574
+ */
575
+ public function setIPCacheFile($ipCacheFile) {
576
+ $this->ipCacheFile = $ipCacheFile;
577
+ }
578
+
579
+ /**
580
+ * @return mixed
581
+ */
582
+ public function getRulesDSLCacheFile() {
583
+ return $this->rulesDSLCacheFile;
584
+ }
585
+
586
+ /**
587
+ * @param mixed $rulesDSLCacheFile
588
+ */
589
+ public function setRulesDSLCacheFile($rulesDSLCacheFile) {
590
+ $this->rulesDSLCacheFile = $rulesDSLCacheFile;
591
+ }
592
+
593
+ /**
594
+ * param key, param value, request string
595
+ *
596
+ * @var array
597
+ */
598
+ private $rowsToB64 = array(3, 4, 7);
599
+
600
+ /**
601
+ * @param $row
602
+ * @return bool|string
603
+ */
604
+ private function serializeRow($row) {
605
+ foreach ($this->rowsToB64 as $index) {
606
+ if (array_key_exists($index, $row)) {
607
+ $row[$index] = base64_encode($row[$index]);
608
+ }
609
+ }
610
+ $row = json_encode($row);
611
+ if (is_string($row) && strlen($row) > 0) {
612
+ return $row;
613
+ }
614
+ return false;
615
+ }
616
+
617
+ /**
618
+ * @param $row
619
+ * @return array|bool|mixed|object
620
+ */
621
+ private function unserializeRow($row) {
622
+ if ($row) {
623
+ $json = json_decode($row, true);
624
+ if (is_array($json)) {
625
+ foreach ($this->rowsToB64 as $index) {
626
+ if (array_key_exists($index, $json)) {
627
+ $json[$index] = base64_decode($json[$index]);
628
+ }
629
+ }
630
+ return $json;
631
+ }
632
+ }
633
+ return false;
634
+ }
635
+
636
+ /**
637
+ * @return wfWAFAttackDataStorageFileEngine
638
+ */
639
+ public function getAttackDataEngine() {
640
+ return $this->attackDataEngine;
641
+ }
642
+
643
+ /**
644
+ * @param wfWAFAttackDataStorageFileEngine $attackDataEngine
645
+ */
646
+ public function setAttackDataEngine($attackDataEngine) {
647
+ $this->attackDataEngine = $attackDataEngine;
648
+ }
649
+ }
650
+
651
+ class wfWAFAttackDataStorageFileEngine {
652
+
653
+ const MAX_ROWS = 10000;
654
+ const MAX_READ_LENGTH = 51200;
655
+ const FILE_SIGNATURE = "wfWAF\x00\x00\x00";
656
+
657
+ /**
658
+ * @param string|float|null $microtime
659
+ * @return string
660
+ */
661
+ public static function packMicrotime($microtime = null) {
662
+ if ($microtime === null) {
663
+ $microtime = microtime();
664
+ }
665
+ if (is_string($microtime)) {
666
+ list($msec, $sec) = explode(' ', $microtime, 2);
667
+ } else if (is_float($microtime)) {
668
+ list($sec, $msec) = explode('.', (string) $microtime, 2);
669
+ $msec = '0.' . $msec;
670
+ } else {
671
+ throw new InvalidArgumentException(__METHOD__ . ' $microtime expected to be string or float, received '
672
+ . gettype($microtime));
673
+ }
674
+ $msec = $msec * 1000000;
675
+ return pack('V*', $sec, $msec);
676
+ }
677
+
678
+ /**
679
+ * @param string $binary
680
+ * @return string
681
+ */
682
+ public static function unpackMicrotime($binary) {
683
+ if (!is_string($binary) || strlen($binary) !== 8) {
684
+ throw new InvalidArgumentException(__METHOD__ . ' $binary expected to be string with length of 8, received '
685
+ . gettype($binary) . (is_string($binary) ? ' of length ' . strlen($binary) : ''));
686
+ }
687
+ list(, $attackLogSeconds, $attackLogMicroseconds) = unpack('V*', $binary);
688
+ return sprintf('%d.%s', $attackLogSeconds, str_pad($attackLogMicroseconds, 6, '0', STR_PAD_LEFT));
689
+ }
690
+
691
+ public static function getCompressionAlgos() {
692
+ static $compressionFunctions;
693
+ if ($compressionFunctions === null) {
694
+ $compressionFunctions = array(
695
+ new wfWAFStorageFileCompressionGZDeflate(),
696
+ new wfWAFStorageFileCompressionGZCompress(),
697
+ new wfWAFStorageFileCompressionGZEncode(),
698
+ );
699
+ }
700
+ return $compressionFunctions;
701
+ }
702
+
703
+ /**
704
+ * @param string $decompressed
705
+ * @return mixed
706
+ */
707
+ public static function compress($decompressed) {
708
+ if (empty($decompressed))
709
+ return $decompressed;
710
+
711
+ $compressionAlgos = self::getCompressionAlgos();
712
+ /** @var wfWAFStorageFileCompressionAlgo $algo */
713
+ foreach ($compressionAlgos as $algo) {
714
+ if ($algo->isUsable() && ($compressed = $algo->testCompression($decompressed)) !== false) {
715
+ return $compressed;
716
+ }
717
+ }
718
+ return $decompressed;
719
+ }
720
+
721
+ /**
722
+ * @param string $compressed
723
+ * @return mixed
724
+ */
725
+ public static function decompress($compressed) {
726
+ if (empty($compressed))
727
+ return $compressed;
728
+
729
+ $compressionAlgos = self::getCompressionAlgos();
730
+ /** @var wfWAFStorageFileCompressionAlgo $algo */
731
+ foreach ($compressionAlgos as $algo) {
732
+ if ($algo->isUsable() && ($decompressed = $algo->decompress($compressed)) !== false) {
733
+ return $decompressed;
734
+ }
735
+ }
736
+ return $compressed;
737
+ }
738
+
739
+
740
+ private $file;
741
+ private $fileHandle;
742
+
743
+ private $header = array();
744
+ private $offsetTable = array();
745
+
746
+ /**
747
+ * wfWAFStorageFileEngine constructor.
748
+ * @param string $file
749
+ */
750
+ public function __construct($file) {
751
+ $this->file = $file;
752
+ }
753
+
754
+ /**
755
+ * @throws wfWAFStorageFileException
756
+ */
757
+ public function open() {
758
+ if (is_resource($this->fileHandle)) {
759
+ return;
760
+ }
761
+ if (!file_exists($this->file)) {
762
+ @file_put_contents($this->file, $this->getDefaultHeader(), LOCK_EX);
763
+ }
764
+ $this->fileHandle = @fopen($this->file, 'r+');
765
+ if (!$this->fileHandle) {
766
+ throw new wfWAFStorageFileException('Unable to open ' . $this->file . ' for reading and writing.');
767
+ }
768
+ }
769
+
770
+ /**
771
+ *
772
+ */
773
+ public function close() {
774
+ if (is_resource($this->fileHandle)) {
775
+ fclose($this->fileHandle);
776
+ }
777
+ $this->fileHandle = null;
778
+ $this->header = array();
779
+ $this->offsetTable = array();
780
+ }
781
+
782
+ /**
783
+ * @param int $offset
784
+ * @return int
785
+ */
786
+ private function seek($offset) {
787
+ return fseek($this->fileHandle, $offset, SEEK_SET);
788
+ }
789
+
790
+ /**
791
+ * @return int
792
+ */
793
+ private function seekToData() {
794
+ return $this->seek(strlen($this->getHeaderLength()));
795
+ }
796
+
797
+ /**
798
+ * @param int $length
799
+ * @return string
800
+ */
801
+ private function read($length) {
802
+ if ($length > self::MAX_READ_LENGTH) {
803
+ $length = self::MAX_READ_LENGTH;
804
+ }
805
+ return fread($this->fileHandle, $length);
806
+ }
807
+
808
+ /**
809
+ * @param string $data
810
+ * @return int
811
+ */
812
+ private function write($data) {
813
+ return fwrite($this->fileHandle, $data);
814
+ }
815
+
816
+ /**
817
+ * @return bool
818
+ */
819
+ private function lockRead() {
820
+ return wfWAFStorageFile::lock($this->fileHandle, LOCK_SH);
821
+ }
822
+
823
+ /**
824
+ * @return bool
825
+ */
826
+ private function lockWrite() {
827
+ return wfWAFStorageFile::lock($this->fileHandle, LOCK_EX);
828
+ }
829
+
830
+ /**
831
+ * @return bool
832
+ */
833
+ private function unlock() {
834
+ return wfWAFStorageFile::lock($this->fileHandle, LOCK_UN);
835
+ }
836
+
837
+ /**
838
+ * @return int
839
+ */
840
+ public function getHeaderLength() {
841
+ return strlen($this->getDefaultHeader());
842
+ }
843
+
844
+ /**
845
+ * @return string
846
+ */
847
+ public function getDefaultHeader() {
848
+ /**
849
+ * 51 PHP die() header
850
+ * 8 Signature
851
+ * 8 oldest 64bit timestamp
852
+ * 8 newest 64bit timestamp
853
+ * 4 row count
854
+ * 1600 offset table
855
+ * 1 last length
856
+ */
857
+ $headerLength = strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE) + 8 + 8 + 4 + (self::MAX_ROWS * 4);
858
+ return wfWAFStorageFile::LOG_FILE_HEADER
859
+ . self::FILE_SIGNATURE
860
+ . str_repeat("\x00", 8 + 8 + 4)
861
+ . pack('V', $headerLength)
862
+ . str_repeat("\x00", self::MAX_ROWS * 4);
863
+ }
864
+
865
+ /**
866
+ * @throws wfWAFStorageFileException
867
+ */
868
+ public function unpackHeader() {
869
+ if ($this->header) {
870
+ return $this->header;
871
+ }
872
+
873
+ $this->open();
874
+ $this->header = array();
875
+ $this->seek(0);
876
+ $this->lockRead();
877
+ $this->header['phpHeader'] = $this->read(strlen(wfWAFStorageFile::LOG_FILE_HEADER));
878
+ $this->header['signature'] = $this->read(strlen(self::FILE_SIGNATURE));
879
+ if ($this->header['phpHeader'] !== wfWAFStorageFile::LOG_FILE_HEADER || $this->header['signature'] !== self::FILE_SIGNATURE) {
880
+ $this->unlock();
881
+ $this->truncate();
882
+ $this->lockRead();
883
+ $this->seek(0);
884
+ $this->lockRead();
885
+ $this->header['phpHeader'] = $this->read(strlen(wfWAFStorageFile::LOG_FILE_HEADER));
886
+ $this->header['signature'] = $this->read(strlen(self::FILE_SIGNATURE));
887
+ }
888
+ $this->header['oldestTimestamp'] = self::unpackMicrotime($this->read(8));
889
+ $this->header['newestTimestamp'] = self::unpackMicrotime($this->read(8));
890
+ list(, $this->header['rowCount']) = unpack('V', $this->read(4));
891
+ $this->header['offsetTable'] = $this->unpackOffsetTable();
892
+ $this->unlock();
893
+ return $this->header;
894
+ }
895
+
896
+ /**
897
+ * @return array
898
+ */
899
+ private function unpackOffsetTable() {
900
+ if ($this->offsetTable) {
901
+ return $this->offsetTable;
902
+ }
903
+ $rowCount = min($this->header['rowCount'], self::MAX_ROWS);
904
+ $this->seek(strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE) + 8 + 8 + 4);
905
+ $offsetTableBinary = $this->read(($rowCount + 1) * 4);
906
+ $this->offsetTable = array_values(unpack('V*', $offsetTableBinary));
907
+ return $this->offsetTable;
908
+ }
909
+
910
+ /**
911
+ * @param callable $callback
912
+ */
913
+ public function scanRows($callback) {
914
+ if (!is_callable($callback)) {
915
+ throw new InvalidArgumentException(__METHOD__ . ' $callback expected to be callable, received ' . gettype($callback));
916
+ }
917
+ $this->open();
918
+ $header = $this->unpackHeader();
919
+ $this->seekToData();
920
+ for ($index = 0; $index < $header['rowCount'] && $index < self::MAX_ROWS; $index++) {
921
+ $offset = $header['offsetTable'][$index];
922
+ $length = $header['offsetTable'][$index + 1] - $offset;
923
+ if ($length > self::MAX_READ_LENGTH) {
924
+ $length = self::MAX_READ_LENGTH;
925
+ }
926
+ $result = call_user_func($callback, $this->fileHandle, $offset, $length);
927
+ if ($result === false) {
928
+ break;
929
+ }
930
+ }
931
+ }
932
+
933
+ /**
934
+ * @param callable $callback
935
+ */
936
+ public function scanRowsReverse($callback) {
937
+ if (!is_callable($callback)) {
938
+ throw new InvalidArgumentException(__METHOD__ . ' $callback expected to be callable, received ' . gettype($callback));
939
+ }
940
+ $this->open();
941
+ $header = $this->unpackHeader();
942
+ // $this->seekToData();
943
+ for ($index = min($header['rowCount'], self::MAX_ROWS) - 1; $index >= 0; $index--) {
944
+ $offset = $header['offsetTable'][$index];
945
+ $length = $header['offsetTable'][$index + 1] - $offset;
946
+ if ($length > self::MAX_READ_LENGTH) {
947
+ $length = self::MAX_READ_LENGTH;
948
+ }
949
+ $result = call_user_func($callback, $this->fileHandle, $offset, $length);
950
+ if ($result === false) {
951
+ break;
952
+ }
953
+ }
954
+ }
955
+
956
+ /**
957
+ * @param $index
958
+ * @return wfWAFAttackDataStorageFileEngineRow
959
+ * @throws wfWAFStorageFileException
960
+ */
961
+ public function getRow($index) {
962
+ $this->open();
963
+ $this->header = array();
964
+ $this->offsetTable = array();
965
+ $header = $this->unpackHeader();
966
+ $this->seekToData();
967
+ if ($index < $header['rowCount'] && $index >= 0) {
968
+ $offset = $header['offsetTable'][$index];
969
+ $length = $header['offsetTable'][$index + 1] - $offset;
970
+ } else {
971
+ return false;
972
+ }
973
+ $this->seek($offset);
974
+ $this->lockRead();
975
+ $binary = $this->read($length);
976
+ $this->unlock();
977
+ return wfWAFAttackDataStorageFileEngineRow::unpack($binary);
978
+ }
979
+
980
+ /**
981
+ * @return mixed
982
+ * @throws wfWAFStorageFileException
983
+ */
984
+ public function getRowCount() {
985
+ $this->open();
986
+ $header = $this->unpackHeader();
987
+ return $header['rowCount'];
988
+ }
989
+
990
+ /**
991
+ * @param wfWAFAttackDataStorageFileEngineRow $row
992
+ * @return bool
993
+ * @throws wfWAFStorageFileException
994
+ */
995
+ public function addRow($row) {
996
+ $this->open();
997
+
998
+ $this->seek(strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE) + 8 + 8);
999
+ $this->lockRead();
1000
+ list(, $rowCount) = unpack('V', $this->read(4));
1001
+ if ($rowCount >= self::MAX_ROWS) {
1002
+ $this->unlock();
1003
+ return false;
1004
+ }
1005
+
1006
+ $this->header = array();
1007
+ $this->offsetTable = array();
1008
+
1009
+ $this->seek(strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE) + 8 + 8 + 4 + ($rowCount * 4));
1010
+ list(, $nextRowOffset) = unpack('V', $this->read(4));
1011
+
1012
+ $rowString = $row->pack();
1013
+
1014
+ $this->lockWrite();
1015
+
1016
+ // Update offset table
1017
+ $this->seek(strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE) + 8 + 8 + 4 + (($rowCount + 1) * 4));
1018
+ $this->write(pack('V', $nextRowOffset + strlen($rowString)));
1019
+
1020
+ // Update rowCount
1021
+ $this->seek(strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE) + 8 + 8);
1022
+ $this->write(pack('V', $rowCount + 1));
1023
+
1024
+ // Write data
1025
+ $this->seek($nextRowOffset);
1026
+ $packedTimestamp = substr($rowString, 0, 8);
1027
+ $this->write($rowString);
1028
+
1029
+ // Update oldest timestamp
1030
+ if ($rowCount === 0) {
1031
+ $this->seek(strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE));
1032
+ $this->write($packedTimestamp);
1033
+ }
1034
+
1035
+ // Update newest timestamp
1036
+ $this->seek(strlen(wfWAFStorageFile::LOG_FILE_HEADER) + strlen(self::FILE_SIGNATURE) + 8);
1037
+ $this->write($packedTimestamp);
1038
+
1039
+ $this->unlock();
1040
+
1041
+ $this->header = array();
1042
+ $this->offsetTable = array();
1043
+
1044
+ return true;
1045
+ }
1046
+
1047
+ /**
1048
+ *
1049
+ */
1050
+ public function truncate() {
1051
+ $defaultHeader = $this->getDefaultHeader();
1052
+ $this->close();
1053
+ wfWAFStorageFile::atomicFilePutContents($this->getFile(), $defaultHeader, 'attack');
1054
+ $this->header = array();
1055
+ $this->offsetTable = array();
1056
+ $this->open();
1057
+ }
1058
+
1059
+ /**
1060
+ * @return mixed
1061
+ * @throws wfWAFStorageFileException
1062
+ */
1063
+ public function getOldestTimestamp() {
1064
+ $this->open();
1065
+ if ($this->getRowCount() === 0) {
1066
+ return false;
1067
+ }
1068
+ $header = $this->unpackHeader();
1069
+ return $header['oldestTimestamp'];
1070
+ }
1071
+
1072
+ /**
1073
+ * @return mixed
1074
+ * @throws wfWAFStorageFileException
1075
+ */
1076
+ public function getNewestTimestamp() {
1077
+ $this->open();
1078
+ if ($this->getRowCount() === 0) {
1079
+ return false;
1080
+ }
1081
+ $header = $this->unpackHeader();
1082
+ return $header['newestTimestamp'];
1083
+ }
1084
+
1085
+ /**
1086
+ * @return string
1087
+ */
1088
+ public function getFile() {
1089
+ return $this->file;
1090
+ }
1091
+
1092
+ /**
1093
+ * @param string $file
1094
+ */
1095
+ public function setFile($file) {
1096
+ $this->file = $file;
1097
+ }
1098
+ }
1099
+
1100
+ interface wfWAFAttackDataStorageFileEngineScanRowCallback {
1101
+
1102
+ public function scanRow($handle, $offset, $length);
1103
+ }
1104
+
1105
+ class wfWAFAttackDataStorageFileEngineResultSet implements wfWAFAttackDataStorageFileEngineScanRowCallback {
1106
+
1107
+ private $rows = array();
1108
+
1109
+ public function scanRow($handle, $offset, $length) {
1110
+ fseek($handle, $offset);
1111
+ $binary = fread($handle, $length);
1112
+ $this->rows = wfWAFAttackDataStorageFileEngineRow::unpack($binary);
1113
+ }
1114
+
1115
+ /**
1116
+ * @return array
1117
+ */
1118
+ public function getRows() {
1119
+ return $this->rows;
1120
+ }
1121
+ }
1122
+
1123
+ class wfWAFAttackDataStorageFileEngineScanRowAttackDataNewer implements wfWAFAttackDataStorageFileEngineScanRowCallback {
1124
+
1125
+ /**
1126
+ * @var int
1127
+ */
1128
+ private $newerThan;
1129
+
1130
+ /**
1131
+ * wfWAFStorageFileEngineScanRowAttackDataNewer constructor.
1132
+ * @param int $newerThan
1133
+ */
1134
+ public function __construct($newerThan) {
1135
+ $this->newerThan = $newerThan;
1136
+ }
1137
+
1138
+ /**
1139
+ * @param resource $handle
1140
+ * @param int $offset
1141
+ * @param int $length
1142
+ * @return bool
1143
+ */
1144
+ public function scanRow($handle, $offset, $length) {
1145
+ $attackLogTimeBin = fread($handle, 8);
1146
+ list(, $attackLogSeconds, $attackLogMicroseconds) = unpack('VV', $attackLogTimeBin);
1147
+ $attackLogTime = $attackLogSeconds . '.' . $attackLogMicroseconds;
1148
+ return $this->newerThan < $attackLogTime;
1149
+ }
1150
+ }
1151
+
1152
+ class wfWAFAttackDataStorageFileEngineScanRowAttackDataOlder implements wfWAFAttackDataStorageFileEngineScanRowCallback {
1153
+
1154
+ /**
1155
+ * @var int
1156
+ */
1157
+ private $olderThan;
1158
+
1159
+ /**
1160
+ * wfWAFStorageFileEngineScanRowAttackDataNewer constructor.
1161
+ * @param int $olderThan
1162
+ */
1163
+ public function __construct($olderThan) {
1164
+ $this->olderThan = $olderThan;
1165
+ }
1166
+
1167
+ /**
1168
+ * @param resource $handle
1169
+ * @param int $offset
1170
+ * @param int $length
1171
+ * @return bool
1172
+ */
1173
+ public function scanRow($handle, $offset, $length) {
1174
+ $attackLogTimeBin = fread($handle, 8);
1175
+ list(, $attackLogSeconds, $attackLogMicroseconds) = unpack('VV', $attackLogTimeBin);
1176
+ $attackLogTime = $attackLogSeconds . '.' . $attackLogMicroseconds;
1177
+ return $this->olderThan > $attackLogTime;
1178
+ }
1179
+ }
1180
+
1181
+ class wfWAFAttackDataStorageFileEngineRow {
1182
+
1183
+ /**
1184
+ * @param string $binary
1185
+ * @return wfWAFAttackDataStorageFileEngineRow
1186
+ */
1187
+ public static function unpack($binary) {
1188
+ $attackLogTime = wfWAFAttackDataStorageFileEngine::unpackMicrotime(substr($binary, 0, 8));
1189
+ $data = wfWAFAttackDataStorageFileEngine::decompress(substr($binary, 8));
1190
+ return new self($attackLogTime, $data);
1191
+ }
1192
+
1193
+ /**
1194
+ * @var float|string
1195
+ */
1196
+ private $timestamp;
1197
+ /**
1198
+ * @var string
1199
+ */
1200
+ private $data;
1201
+
1202
+ /**
1203
+ * @param float $timestamp
1204
+ * @param string $data
1205
+ */
1206
+ public function __construct($timestamp, $data) {
1207
+ $this->timestamp = $timestamp;
1208
+ $this->data = $data;
1209
+ }
1210
+
1211
+ /**
1212
+ * @return string
1213
+ */
1214
+ public function pack() {
1215
+ return wfWAFAttackDataStorageFileEngine::packMicrotime($this->getTimestamp()) . wfWAFAttackDataStorageFileEngine::compress($this->getData());
1216
+ }
1217
+
1218
+ /**
1219
+ * @return float|string
1220
+ */
1221
+ public function getTimestamp() {
1222
+ return $this->timestamp;
1223
+ }
1224
+
1225
+ /**
1226
+ * @param float|string $timestamp
1227
+ */
1228
+ public function setTimestamp($timestamp) {
1229
+ $this->timestamp = $timestamp;
1230
+ }
1231
+
1232
+ /**
1233
+ * @return string
1234
+ */
1235
+ public function getData() {
1236
+ return $this->data;
1237
+ }
1238
+
1239
+ /**
1240
+ * @param string $data
1241
+ */
1242
+ public function setData($data) {
1243
+ $this->data = $data;
1244
+ }
1245
+ }
1246
+
1247
+ abstract class wfWAFStorageFileCompressionAlgo {
1248
+
1249
+ abstract public function isUsable();
1250
+
1251
+ abstract public function compress($string);
1252
+
1253
+ abstract public function decompress($binary);
1254
+
1255
+ /**
1256
+ * @param string $string
1257
+ * @return bool
1258
+ */
1259
+ public function testCompression($string) {
1260
+ $compressed = $this->compress($string);
1261
+ if ($string === $this->decompress($compressed)) {
1262
+ return $compressed;
1263
+ }
1264
+ return false;
1265
+ }
1266
+ }
1267
+
1268
+ class wfWAFStorageFileCompressionGZDeflate extends wfWAFStorageFileCompressionAlgo {
1269
+
1270
+ public function isUsable() {
1271
+ return function_exists('gzinflate') && function_exists('gzdeflate');
1272
+ }
1273
+
1274
+ public function compress($string) {
1275
+ return @gzdeflate($string);
1276
+ }
1277
+
1278
+ public function decompress($binary) {
1279
+ return @gzinflate($binary);
1280
+ }
1281
+ }
1282
+
1283
+ class wfWAFStorageFileCompressionGZCompress extends wfWAFStorageFileCompressionAlgo {
1284
+
1285
+ public function isUsable() {
1286
+ return function_exists('gzuncompress') && function_exists('gzcompress');
1287
+ }
1288
+
1289
+ public function compress($string) {
1290
+ return @gzcompress($string);
1291
+ }
1292
+
1293
+ public function decompress($binary) {
1294
+ return @gzuncompress($binary);
1295
+ }
1296
+ }
1297
+
1298
+ class wfWAFStorageFileCompressionGZEncode extends wfWAFStorageFileCompressionAlgo {
1299
+
1300
+ public function isUsable() {
1301
+ return function_exists('gzencode') && function_exists('gzdecode');
1302
+ }
1303
+
1304
+ public function compress($string) {
1305
+ return @gzencode($string);
1306
+ }
1307
+
1308
+ public function decompress($binary) {
1309
+ return @gzdecode($binary);
1310
+ }
1311
+ }
1312
+
1313
+ class wfWAFStorageFileException extends wfWAFException {
1314
+
1315
+ }
1316
+
1317
+ class wfWAFStorageFileConfigException extends wfWAFStorageFileException {
1318
+
1319
+ }
1320
+
vendor/wordfence/wf-waf/src/lib/utils.php ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wfWAFUtils {
4
+
5
+ /**
6
+ * Return dot or colon notation of IPv4 or IPv6 address.
7
+ *
8
+ * @param string $ip
9
+ * @return string|bool
10
+ */
11
+ public static function inet_ntop($ip) {
12
+ // trim this to the IPv4 equiv if it's in the mapped range
13
+ if (strlen($ip) == 16 && substr($ip, 0, 12) == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff") {
14
+ $ip = substr($ip, 12, 4);
15
+ }
16
+ return self::hasIPv6Support() ? inet_ntop($ip) : self::_inet_ntop($ip);
17
+ }
18
+
19
+ /**
20
+ * Return the packed binary string of an IPv4 or IPv6 address.
21
+ *
22
+ * @param string $ip
23
+ * @return string
24
+ */
25
+ public static function inet_pton($ip) {
26
+ // convert the 4 char IPv4 to IPv6 mapped version.
27
+ $pton = str_pad(self::hasIPv6Support() ? inet_pton($ip) : self::_inet_pton($ip), 16,
28
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00", STR_PAD_LEFT);
29
+ return $pton;
30
+ }
31
+
32
+ /**
33
+ * Added compatibility for hosts that do not have inet_pton.
34
+ *
35
+ * @param $ip
36
+ * @return bool|string
37
+ */
38
+ public static function _inet_pton($ip) {
39
+ // IPv4
40
+ if (preg_match('/^(?:\d{1,3}(?:\.|$)){4}/', $ip)) {
41
+ $octets = explode('.', $ip);
42
+ $bin = chr($octets[0]) . chr($octets[1]) . chr($octets[2]) . chr($octets[3]);
43
+ return $bin;
44
+ }
45
+
46
+ // IPv6
47
+ if (preg_match('/^((?:[\da-f]{1,4}(?::|)){0,8})(::)?((?:[\da-f]{1,4}(?::|)){0,8})$/i', $ip)) {
48
+ if ($ip === '::') {
49
+ return "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
50
+ }
51
+ $colon_count = substr_count($ip, ':');
52
+ $dbl_colon_pos = strpos($ip, '::');
53
+ if ($dbl_colon_pos !== false) {
54
+ $ip = str_replace('::', str_repeat(':0000',
55
+ (($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip) - 2) ? 9 : 8) - $colon_count) . ':', $ip);
56
+ $ip = trim($ip, ':');
57
+ }
58
+
59
+ $ip_groups = explode(':', $ip);
60
+ $ipv6_bin = '';
61
+ foreach ($ip_groups as $ip_group) {
62
+ $ipv6_bin .= pack('H*', str_pad($ip_group, 4, '0', STR_PAD_LEFT));
63
+ }
64
+
65
+ return strlen($ipv6_bin) === 16 ? $ipv6_bin : false;
66
+ }
67
+
68
+ // IPv4 mapped IPv6
69
+ if (preg_match('/^((?:0{1,4}(?::|)){0,5})(::)?ffff:((?:\d{1,3}(?:\.|$)){4})$/i', $ip, $matches)) {
70
+ $octets = explode('.', $matches[3]);
71
+ return "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" . chr($octets[0]) . chr($octets[1]) . chr($octets[2]) . chr($octets[3]);
72
+ }
73
+
74
+ return false;
75
+ }
76
+
77
+ /**
78
+ * Added compatibility for hosts that do not have inet_ntop.
79
+ *
80
+ * @param $ip
81
+ * @return bool|string
82
+ */
83
+ public static function _inet_ntop($ip) {
84
+ // IPv4
85
+ if (strlen($ip) === 4) {
86
+ return ord($ip[0]) . '.' . ord($ip[1]) . '.' . ord($ip[2]) . '.' . ord($ip[3]);
87
+ }
88
+
89
+ // IPv6
90
+ if (strlen($ip) === 16) {
91
+
92
+ // IPv4 mapped IPv6
93
+ if (substr($ip, 0, 12) == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff") {
94
+ return "::ffff:" . ord($ip[12]) . '.' . ord($ip[13]) . '.' . ord($ip[14]) . '.' . ord($ip[15]);
95
+ }
96
+
97
+ $hex = bin2hex($ip);
98
+ $groups = str_split($hex, 4);
99
+ $collapse = false;
100
+ $done_collapse = false;
101
+ foreach ($groups as $index => $group) {
102
+ if ($group == '0000' && !$done_collapse) {
103
+ if (!$collapse) {
104
+ $groups[$index] = ':';
105
+ } else {
106
+ $groups[$index] = '';
107
+ }
108
+ $collapse = true;
109
+ } else if ($collapse) {
110
+ $done_collapse = true;
111
+ $collapse = false;
112
+ }
113
+ $groups[$index] = ltrim($groups[$index], '0');
114
+ }
115
+ $ip = join(':', array_filter($groups));
116
+ $ip = str_replace(':::', '::', $ip);
117
+ return $ip == ':' ? '::' : $ip;
118
+ }
119
+
120
+ return false;
121
+ }
122
+
123
+ /**
124
+ * Verify PHP was compiled with IPv6 support.
125
+ *
126
+ * Some hosts appear to not have inet_ntop, and others appear to have inet_ntop but are unable to process IPv6 addresses.
127
+ *
128
+ * @return bool
129
+ */
130
+ public static function hasIPv6Support() {
131
+ return defined('AF_INET6');
132
+ }
133
+
134
+ /**
135
+ * Compare two strings in constant time. It can leak the length of a string.
136
+ *
137
+ * @param string $a Expected string.
138
+ * @param string $b Actual string.
139
+ * @return bool Whether strings are equal.
140
+ */
141
+ public static function hash_equals($a, $b) {
142
+ $a_length = strlen($a);
143
+ if ($a_length !== strlen($b)) {
144
+ return false;
145
+ }
146
+ $result = 0;
147
+
148
+ // Do not attempt to "optimize" this.
149
+ for ($i = 0; $i < $a_length; $i++) {
150
+ $result |= ord($a[$i]) ^ ord($b[$i]);
151
+ }
152
+
153
+ return $result === 0;
154
+ }
155
+
156
+ /**
157
+ * @param $algo
158
+ * @param $data
159
+ * @param $key
160
+ * @param bool|false $raw_output
161
+ * @return bool|string
162
+ */
163
+ public static function hash_hmac($algo, $data, $key, $raw_output = false) {
164
+ if (function_exists('hash_hmac')) {
165
+ return hash_hmac($algo, $data, $key, $raw_output);
166
+ }
167
+ return self::_hash_hmac($algo, $data, $key, $raw_output);
168
+ }
169
+
170
+ /**
171
+ * @param $algo
172
+ * @param $data
173
+ * @param $key
174
+ * @param bool|false $raw_output
175
+ * @return bool|string
176
+ */
177
+ private static function _hash_hmac($algo, $data, $key, $raw_output = false) {
178
+ $packs = array('md5' => 'H32', 'sha1' => 'H40');
179
+
180
+ if (!isset($packs[$algo]))
181
+ return false;
182
+
183
+ $pack = $packs[$algo];
184
+
185
+ if (strlen($key) > 64)
186
+ $key = pack($pack, $algo($key));
187
+
188
+ $key = str_pad($key, 64, chr(0));
189
+
190
+ $ipad = (substr($key, 0, 64) ^ str_repeat(chr(0x36), 64));
191
+ $opad = (substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64));
192
+
193
+ $hmac = $algo($opad . pack($pack, $algo($ipad . $data)));
194
+
195
+ if ($raw_output)
196
+ return pack($pack, $hmac);
197
+ return $hmac;
198
+ }
199
+
200
+ /**
201
+ * @param int $length
202
+ * @param string $chars
203
+ * @return string
204
+ */
205
+ public static function getRandomString($length = 16, $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|') {
206
+ // This is faster than calling self::random_int for $length
207
+ $bytes = self::random_bytes($length);
208
+ $return = '';
209
+ $maxIndex = strlen($chars) - 1;
210
+ for ($i = 0; $i < $length; $i++) {
211
+ $fp = (float) ord($bytes[$i]) / 255.0; // convert to [0,1]
212
+ $index = (int) (round($fp * $maxIndex));
213
+ $return .= $chars[$index];
214
+ }
215
+ return $return;
216
+ }
217
+
218
+ /**
219
+ * Polyfill for random_bytes.
220
+ *
221
+ * @param int $bytes
222
+ * @return string
223
+ */
224
+ public static function random_bytes($bytes) {
225
+ $bytes = (int) $bytes;
226
+ if (function_exists('random_bytes')) {
227
+ try {
228
+ $rand = random_bytes($bytes);
229
+ if (is_string($rand) && strlen($rand) === $bytes) {
230
+ return $rand;
231
+ }
232
+ } catch (Exception $e) {
233
+ // Fall through
234
+ } catch (TypeError $e) {
235
+ // Fall through
236
+ } catch (Error $e) {
237
+ // Fall through
238
+ }
239
+ }
240
+ if (function_exists('mcrypt_create_iv')) {
241
+ $rand = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
242
+ if (is_string($rand) && strlen($rand) === $bytes) {
243
+ return $rand;
244
+ }
245
+ }
246
+ if (function_exists('openssl_random_pseudo_bytes')) {
247
+ $rand = @openssl_random_pseudo_bytes($bytes, $strong);
248
+ if (is_string($rand) && strlen($rand) === $bytes) {
249
+ return $rand;
250
+ }
251
+ }
252
+ // Last resort is insecure
253
+ $return = '';
254
+ for ($i = 0; $i < $bytes; $i++) {
255
+ $return .= chr(mt_rand(0, 255));
256
+ }
257
+ return $return;
258
+ }
259
+
260
+ /**
261
+ * Polyfill for random_int.
262
+ *
263
+ * @param int $min
264
+ * @param int $max
265
+ * @return int
266
+ */
267
+ public static function random_int($min = 0, $max = 0x7FFFFFFF) {
268
+ if (function_exists('random_int')) {
269
+ try {
270
+ return random_int($min, $max);
271
+ } catch (Exception $e) {
272
+ // Fall through
273
+ } catch (TypeError $e) {
274
+ // Fall through
275
+ } catch (Error $e) {
276
+ // Fall through
277
+ }
278
+ }
279
+ $diff = $max - $min;
280
+ $bytes = self::random_bytes(4);
281
+ if ($bytes === false || strlen($bytes) != 4) {
282
+ throw new RuntimeException("Unable to get 4 bytes");
283
+ }
284
+ $val = unpack("Nint", $bytes);
285
+ $val = $val['int'] & 0x7FFFFFFF;
286
+ $fp = (float) $val / 2147483647.0; // convert to [0,1]
287
+ return (int) (round($fp * $diff) + $min);
288
+ }
289
+
290
+ /**
291
+ * @param mixed $subject
292
+ * @return array|string
293
+ */
294
+ public static function stripMagicQuotes($subject) {
295
+ $sybase = ini_get('magic_quotes_sybase');
296
+ $sybaseEnabled = ((is_numeric($sybase) && $sybase) ||
297
+ (is_string($sybase) && $sybase && !in_array(strtolower($sybase), array(
298
+ 'off',
299
+ 'false'
300
+ ))));
301
+ if ((function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()) || $sybaseEnabled) {
302
+ return self::stripslashes_deep($subject);
303
+ }
304
+ return $subject;
305
+ }
306
+
307
+ /**
308
+ * @param mixed $subject
309
+ * @return array|string
310
+ */
311
+ public static function stripslashes_deep($subject) {
312
+ if (is_array($subject)) {
313
+ return array_map(array(
314
+ 'self', 'stripslashes_deep',
315
+ ), $subject);
316
+ } else if (is_string($subject)) {
317
+ return stripslashes($subject);
318
+ }
319
+ return $subject;
320
+ }
321
+ }
vendor/wordfence/wf-waf/src/lib/view.php ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wfWAFView {
4
+
5
+ /**
6
+ * @var string
7
+ */
8
+ protected $viewPath;
9
+
10
+ /**
11
+ * @var string
12
+ */
13
+ protected $viewFileExtension = '.php';
14
+
15
+ /**
16
+ * @var string
17
+ */
18
+ protected $view;
19
+
20
+ /**
21
+ * @var array
22
+ */
23
+ protected $data;
24
+
25
+ /**
26
+ * @param string $view
27
+ * @param array $data
28
+ * @return wfWAFView
29
+ */
30
+ public static function create($view, $data = array()) {
31
+ return new self($view, $data);
32
+ }
33
+
34
+ /**
35
+ * @param string $view
36
+ * @param array $data
37
+ */
38
+ public function __construct($view, $data = array()) {
39
+ $this->viewPath = WFWAF_VIEW_PATH;
40
+ $this->view = $view;
41
+ $this->data = $data;
42
+ }
43
+
44
+ /**
45
+ * @return string
46
+ * @throws wfWAFViewNotFoundException
47
+ */
48
+ public function render() {
49
+ $view = preg_replace('/\.{2,}/', '.', $this->view);
50
+ $viewPath = $this->viewPath . '/' . $view . $this->viewFileExtension;
51
+ if (!file_exists($viewPath)) {
52
+ throw new wfWAFViewNotFoundException('The view ' . $viewPath . ' does not exist or is not readable.');
53
+ }
54
+
55
+ extract($this->data, EXTR_SKIP);
56
+
57
+ ob_start();
58
+ /** @noinspection PhpIncludeInspection */
59
+ include $viewPath;
60
+ return ob_get_clean();
61
+ }
62
+
63
+ /**
64
+ * @return string
65
+ */
66
+ public function __toString() {
67
+ try {
68
+ return $this->render();
69
+ } catch (wfWAFViewNotFoundException $e) {
70
+ return defined('WFWAF_DEBUG') && WFWAF_DEBUG ? $e->getMessage() : 'The view could not be loaded.';
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @param $data
76
+ * @return $this
77
+ */
78
+ public function addData($data) {
79
+ $this->data = array_merge($data, $this->data);
80
+ return $this;
81
+ }
82
+
83
+ /**
84
+ * @return array
85
+ */
86
+ public function getData() {
87
+ return $this->data;
88
+ }
89
+
90
+ /**
91
+ * @param array $data
92
+ * @return $this
93
+ */
94
+ public function setData($data) {
95
+ $this->data = $data;
96
+ return $this;
97
+ }
98
+
99
+ /**
100
+ * @return string
101
+ */
102
+ public function getView() {
103
+ return $this->view;
104
+ }
105
+
106
+ /**
107
+ * @param string $view
108
+ * @return $this
109
+ */
110
+ public function setView($view) {
111
+ $this->view = $view;
112
+ return $this;
113
+ }
114
+
115
+ /**
116
+ * Prevent POP
117
+ */
118
+ public function __wakeup() {
119
+ $this->viewPath = WFWAF_VIEW_PATH;
120
+ $this->view = null;
121
+ $this->data = array();
122
+ $this->viewFileExtension = '.php';
123
+ }
124
+ }
125
+
126
+ class wfWAFViewNotFoundException extends Exception {
127
+ }
vendor/wordfence/wf-waf/src/lib/waf.php ADDED
@@ -0,0 +1,1545 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class wfWAF {
4
+
5
+ const AUTH_COOKIE = 'wfwaf-authcookie';
6
+
7
+ /**
8
+ * @var wfWAF
9
+ */
10
+ private static $instance;
11
+ private $blacklistedParams;
12
+ private $whitelistedParams;
13
+ private $variables = array();
14
+
15
+ /**
16
+ * @return wfWAF
17
+ */
18
+ public static function getInstance() {
19
+ return self::$instance;
20
+ }
21
+
22
+ /**
23
+ * @param wfWAF $instance
24
+ */
25
+ public static function setInstance($instance) {
26
+ self::$instance = $instance;
27
+ }
28
+
29
+ protected $rulesFile;
30
+ protected $trippedRules = array();
31
+ protected $failedRules = array();
32
+ protected $scores = array();
33
+ protected $scoresXSS = array();
34
+ protected $failScores = array();
35
+ protected $rules = array();
36
+ /**
37
+ * @var wfWAFRequestInterface
38
+ */
39
+ private $request;
40
+ /**
41
+ * @var wfWAFStorageInterface
42
+ */
43
+ private $storageEngine;
44
+ /**
45
+ * @var wfWAFEventBus
46
+ */
47
+ private $eventBus;
48
+ private $debug = array();
49
+ private $disabledRules;
50
+ private $publicKey = '-----BEGIN PUBLIC KEY-----
51
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzovUDp/qu7r6LT5d8dLL
52
+ H/87aRrCjUd6XtnG+afAPVfMKNp4u4L+UuYfw1RfpfquP/zLMGdfmJCUp/oJywkW
53
+ Rkqo+y7pDuqIFQ59dHvizmYQRvaZgvincBDpey5Ek9AFfB9fqYYnH9+eQw8eLdQi
54
+ h6Zsh8RsuxFM2BW6JD9Km7L5Lyxw9jU+lye7I3ICYtUOVxc3n3bJT2SiIwHK57pW
55
+ g/asJEUDiYQzsaa90YPOLdf1Ysz2rkgnCduQaEGz/RPhgUrmZfKwq8puEmkh7Yee
56
+ auEa+7b+FGTKs7dUo2BNGR7OVifK4GZ8w/ajS0TelhrSRi3BBQCGXLzUO/UURUAh
57
+ 1QIDAQAB
58
+ -----END PUBLIC KEY-----';
59
+
60
+
61
+ /**
62
+ * @param wfWAFRequestInterface $request
63
+ * @param wfWAFStorageInterface $storageEngine
64
+ * @param wfWAFEventBus $eventBus
65
+ * @param string|null $rulesFile
66
+ */
67
+ public function __construct($request, $storageEngine, $eventBus = null, $rulesFile = null) {
68
+ $this->setRequest($request);
69
+ $this->setStorageEngine($storageEngine);
70
+ $this->setEventBus($eventBus ? $eventBus : new wfWAFEventBus);
71
+ $this->setCompiledRulesFile($rulesFile === null ? WFWAF_PATH . 'rules.php' : $rulesFile);
72
+ }
73
+
74
+ public function getGlobal($global) {
75
+ if (strpos($global, '.') === false) {
76
+ return null;
77
+ }
78
+ list($prefix, $_global) = explode('.', $global);
79
+ switch ($prefix) {
80
+ case 'request':
81
+ $method = "get" . ucfirst($_global);
82
+ if (method_exists('wfWAFRequestInterface', $method)) {
83
+ return call_user_func(array(
84
+ $this->getRequest(),
85
+ $method,
86
+ ));
87
+ }
88
+ break;
89
+ case 'server':
90
+ $key = strtoupper($_global);
91
+ if (isset($_SERVER) && array_key_exists($key, $_SERVER)) {
92
+ return $_SERVER[$key];
93
+ }
94
+ break;
95
+ }
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ *
101
+ */
102
+ public function runCron() {
103
+ if ((
104
+ $this->getStorageEngine()->getConfig('attackDataNextInterval', null) === null ||
105
+ $this->getStorageEngine()->getConfig('attackDataNextInterval', time() + 0xffff) <= time()
106
+ ) &&
107
+ $this->getStorageEngine()->hasPreviousAttackData(microtime(true) - (60 * 5))
108
+ ) {
109
+ $this->sendAttackData();
110
+ }
111
+ $cron = $this->getStorageEngine()->getConfig('cron');
112
+ if (is_array($cron)) {
113
+ /** @var wfWAFCronEvent $event */
114
+ foreach ($cron as $index => $event) {
115
+ $event->setWaf($this);
116
+ if ($event->isInPast()) {
117
+ $event->fire();
118
+ $newEvent = $event->reschedule();
119
+ if ($newEvent instanceof wfWAFCronEvent && $newEvent !== $event) {
120
+ $cron[$index] = $newEvent;
121
+ } else {
122
+ unset($cron[$index]);
123
+ }
124
+ }
125
+ }
126
+ }
127
+ $this->getStorageEngine()->setConfig('cron', $cron);
128
+ }
129
+
130
+ /**
131
+ *
132
+ */
133
+ public function run() {
134
+ $this->loadRules();
135
+ if ($this->isDisabled()) {
136
+ $this->eventBus->wafDisabled();
137
+ return;
138
+ }
139
+ $this->runMigrations();
140
+ $request = $this->getRequest();
141
+ if ($request->getBody('wfwaf-false-positive-verified') && $this->currentUserCanWhitelist() &&
142
+ wfWAFUtils::hash_equals($request->getBody('wfwaf-false-positive-nonce'), $this->getAuthCookieValue('nonce', ''))
143
+ ) {
144
+ $urlParams = json_decode($request->getBody('wfwaf-false-positive-params'), true);
145
+ if (is_array($urlParams) && $urlParams) {
146
+ $whitelistCount = 0;
147
+ foreach ($urlParams as $urlParam) {
148
+ $path = isset($urlParam['path']) ? $urlParam['path'] : false;
149
+ $paramKey = isset($urlParam['paramKey']) ? $urlParam['paramKey'] : false;
150
+ $ruleID = isset($urlParam['ruleID']) ? $urlParam['ruleID'] : false;
151
+ if ($path && $paramKey && $ruleID) {
152
+ $this->whitelistRuleForParam($path, $paramKey, $ruleID, array(
153
+ 'timestamp' => time(),
154
+ 'description' => 'Whitelisted by via false positive dialog',
155
+ 'ip' => $request->getIP(),
156
+ ));
157
+ $whitelistCount++;
158
+ }
159
+ }
160
+ exit("Successfully whitelisted $whitelistCount params.");
161
+ }
162
+ }
163
+
164
+ $ip = $this->getRequest()->getIP();
165
+ if ($this->isIPBlocked($ip)) {
166
+ $this->eventBus->prevBlocked($ip);
167
+ $e = new wfWAFBlockException();
168
+ $this->blockAction($e);
169
+ }
170
+
171
+ try {
172
+ $this->eventBus->beforeRunRules();
173
+ $this->runRules();
174
+ $this->eventBus->afterRunRules();
175
+
176
+ } catch (wfWAFAllowException $e) {
177
+ // Do nothing
178
+ $this->eventBus->allow($ip, $e);
179
+
180
+ } catch (wfWAFBlockException $e) {
181
+ $this->eventBus->block($ip, $e);
182
+ $this->blockAction($e);
183
+
184
+ } catch (wfWAFBlockXSSException $e) {
185
+ $this->eventBus->blockXSS($ip, $e);
186
+ $this->blockXSSAction($e);
187
+
188
+ } catch (wfWAFBlockSQLiException $e) {
189
+ $this->eventBus->blockSQLi($ip, $e);
190
+ $this->blockAction($e);
191
+ }
192
+
193
+ $this->runCron();
194
+
195
+ // Check if this is signed request and update ruleset.
196
+
197
+ $ping = $this->getRequest()->getBody('ping');
198
+ $pingResponse = $this->getRequest()->getBody('ping_response');
199
+ $wfIP = $this->isWordfenceIP($this->getRequest()->getIP());
200
+ $pingIsApiKey = wfWAFUtils::hash_equals($ping, sha1($this->getStorageEngine()->getConfig('apiKey')));
201
+
202
+ if ($ping && $pingResponse && $pingIsApiKey &&
203
+ $this->verifySignedRequest($this->getRequest()->getBody('signature'), $this->getStorageEngine()->getConfig('apiKey'))
204
+ ) {
205
+ // $this->updateRuleSet(base64_decode($this->getRequest()->body('ping')));
206
+ $event = new wfWAFCronFetchRulesEvent(time() - 2);
207
+ $event->setWaf($this);
208
+ $event->fire();
209
+
210
+ header('Content-type: text/plain');
211
+ $pingResponse = preg_replace('/[a-zA-Z0-9]/', '', $this->getRequest()->getBody('ping_response'));
212
+ exit('Success: ' . sha1($this->getStorageEngine()->getConfig('apiKey') . $pingResponse));
213
+ }
214
+ }
215
+
216
+ /**
217
+ *
218
+ */
219
+ public function loadRules() {
220
+ if (file_exists($this->getCompiledRulesFile())) {
221
+ // Acquire lock on this file so we're not including it while it's being written in another process.
222
+ $handle = fopen($this->getCompiledRulesFile(), 'r');
223
+ flock($handle, LOCK_SH);
224
+ /** @noinspection PhpIncludeInspection */
225
+ include $this->getCompiledRulesFile();
226
+ flock($handle, LOCK_UN);
227
+ fclose($handle);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * @throws wfWAFAllowException|wfWAFBlockException|wfWAFBlockXSSException
233
+ */
234
+ public function runRules() {
235
+ /**
236
+ * @var int $ruleID
237
+ * @var wfWAFRule $rule
238
+ */
239
+ foreach ($this->getRules() as $ruleID => $rule) {
240
+ if (!$this->isRuleDisabled($ruleID)) {
241
+ $rule->evaluate();
242
+ }
243
+ }
244
+
245
+ $blockActions = array();
246
+ foreach ($this->failedRules as $paramKey => $categories) {
247
+ foreach ($categories as $category => $failedRules) {
248
+ foreach ($failedRules as $failedRule) {
249
+ /**
250
+ * @var wfWAFRule $rule
251
+ * @var wfWAFRuleComparisonFailure $failedComparison
252
+ */
253
+ $rule = $failedRule['rule'];
254
+ $failedComparison = $failedRule['failedComparison'];
255
+ $action = $failedRule['action'];
256
+
257
+ $score = $rule->getScore();
258
+ if ($failedComparison->hasMultiplier()) {
259
+ $score *= $failedComparison->getMultiplier();
260
+ }
261
+ if (!isset($this->failScores[$category])) {
262
+ $this->failScores[$category] = 100;
263
+ }
264
+ if (!isset($this->scores[$paramKey][$category])) {
265
+ $this->scores[$paramKey][$category] = 0;
266
+ }
267
+ $this->scores[$paramKey][$category] += $score;
268
+ if ($this->scores[$paramKey][$category] >= $this->failScores[$category]) {
269
+ $blockActions[$category] = array(
270
+ 'paramKey' => $paramKey,
271
+ 'score' => $this->scores[$paramKey][$category],
272
+ 'action' => $action,
273
+ 'rule' => $rule,
274
+ 'failedComparison' => $failedComparison,
275
+ );
276
+ }
277
+ $this->debug[] = sprintf("%s tripped %s for %s->%s('%s'). Score %d/%d", $paramKey, $action,
278
+ $category, $failedComparison->getAction(), $failedComparison->getExpected(),
279
+ $this->scores[$paramKey][$category], $this->failScores[$category]);
280
+ }
281
+ }
282
+ }
283
+
284
+ uasort($blockActions, array($this, 'sortBlockActions'));
285
+ foreach ($blockActions as $blockAction) {
286
+ call_user_func(array($this, $blockAction['action']), $blockAction['rule'], $blockAction['failedComparison'], false);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * @param array $a
292
+ * @param array $b
293
+ * @return int
294
+ */
295
+ private function sortBlockActions($a, $b) {
296
+ if ($a['score'] == $b['score']) {
297
+ return 0;
298
+ }
299
+ return ($a['score'] > $b['score']) ? -1 : 1;
300
+ }
301
+
302
+ protected function runMigrations() {
303
+ $currentVersion = $this->getStorageEngine()->getConfig('version');
304
+ if (!$currentVersion || version_compare($currentVersion, WFWAF_VERSION) === -1) {
305
+ if (!$currentVersion) {
306
+ $cron = array(
307
+ new wfWAFCronFetchRulesEvent(time() +
308
+ (86400 * ($this->getStorageEngine()->getConfig('isPaid') ? .5 : 7))),
309
+ );
310
+ $this->getStorageEngine()->setConfig('cron', $cron);
311
+ }
312
+
313
+ // Any migrations to newer versions go here.
314
+ $this->getStorageEngine()->setConfig('version', WFWAF_VERSION);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * @param wfWAFRule $rule
320
+ */
321
+ public function tripRule($rule) {
322
+ $this->trippedRules[] = $rule;
323
+ $action = $rule->getAction();
324
+ $scores = $rule->getScore();
325
+ $categories = $rule->getCategory();
326
+ if (is_array($categories)) {
327
+ for ($i = 0; $i < count($categories); $i++) {
328
+ if (is_array($action) && !empty($action[$i])) {
329
+ $a = $action[$i];
330
+ } else {
331
+ $a = $action;
332
+ }
333
+ if ($this->isAllowedAction($a)) {
334
+ $r = clone $rule;
335
+ $r->setScore($scores[$i]);
336
+ $r->setCategory($categories[$i]);
337
+ /** @var wfWAFRuleComparisonFailure $failed */
338
+ foreach ($r->getComparisonGroup()->getFailedComparisons() as $failed) {
339
+ call_user_func(array($this, $a), $r, $failed);
340
+ }
341
+ }
342
+ }
343
+ } else {
344
+ if ($this->isAllowedAction($action)) {
345
+ /** @var wfWAFRuleComparisonFailure $failed */
346
+ foreach ($rule->getComparisonGroup()->getFailedComparisons() as $failed) {
347
+ call_user_func(array($this, $action), $rule, $failed);
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ /**
354
+ * @return bool
355
+ */
356
+ public function isInLearningMode() {
357
+ return $this->getStorageEngine()->isInLearningMode();
358
+ }
359
+
360
+ /**
361
+ * @return bool
362
+ */
363
+ public function isDisabled() {
364
+ return $this->getStorageEngine()->isDisabled() || !WFWAF_ENABLED;
365
+ }
366
+
367
+ public function hasOpenSSL() {
368
+ return function_exists('openssl_verify');
369
+ }
370
+
371
+ /**
372
+ * @param string $signature
373
+ * @param string $data
374
+ * @return bool
375
+ */
376
+ public function verifySignedRequest($signature, $data) {
377
+ if (!$this->hasOpenSSL()) {
378
+ return false;
379
+ }
380
+ $valid = openssl_verify($data, $signature, $this->getPublicKey(), OPENSSL_ALGO_SHA1);
381
+ return $valid === 1;
382
+ }
383
+
384
+ /**
385
+ * @param string $hash
386
+ * @param string $data
387
+ * @return bool
388
+ */
389
+ public function verifyHashedRequest($hash, $data) {
390
+ if ($this->hasOpenSSL()) {
391
+ return false;
392
+ }
393
+ return wfWAFUtils::hash_equals($hash,
394
+ wfWAFUtils::hash_hmac('sha1', $data, $this->getStorageEngine()->getConfig('apiKey')));
395
+ }
396
+
397
+ /**
398
+ * @param string $ip
399
+ * @return bool
400
+ */
401
+ public function isWordfenceIP($ip) {
402
+ if (preg_match('/69.46.36.(\d+)/', $ip, $matches)) {
403
+ return $matches[1] >= 1 && $matches[1] <= 32;
404
+ }
405
+ return false;
406
+ }
407
+
408
+ /**
409
+ * @param $rules
410
+ * @param bool|int $updateLastUpdatedTimestamp
411
+ * @throws wfWAFBuildRulesException
412
+ */
413
+ public function updateRuleSet($rules, $updateLastUpdatedTimestamp = true) {
414
+ try {
415
+ if (is_string($rules)) {
416
+ $ruleString = $rules;
417
+ $parser = new wfWAFRuleParser(new wfWAFRuleLexer($rules), $this);
418
+ $rules = $parser->parse();
419
+ }
420
+
421
+ if (!is_writable($this->getCompiledRulesFile())) {
422
+ throw new wfWAFBuildRulesException('Rules file not writable.');
423
+ }
424
+
425
+ file_put_contents($this->getCompiledRulesFile(), sprintf(<<<PHP
426
+ <?php
427
+ if (!defined('WFWAF_VERSION')) {
428
+ exit('Access denied');
429
+ }
430
+ /*
431
+ This file is generated automatically. Any changes made will be lost.
432
+ */
433
+
434
+ %s?>
435
+ PHP
436
+ , $this->buildRuleSet($rules)), LOCK_EX);
437
+ if (!empty($ruleString) && !WFWAF_DEBUG) {
438
+ file_put_contents($this->getStorageEngine()->getRulesDSLCacheFile(), $ruleString, LOCK_EX);
439
+ }
440
+
441
+ if ($updateLastUpdatedTimestamp) {
442
+ $this->getStorageEngine()->setConfig('rulesLastUpdated',
443
+ is_int($updateLastUpdatedTimestamp) ? $updateLastUpdatedTimestamp : time());
444
+ }
445
+
446
+ } catch (wfWAFBuildRulesException $e) {
447
+ // Do something.
448
+ throw $e;
449
+ }
450
+ }
451
+
452
+ /**
453
+ * @param string $rules
454
+ * @return string
455
+ * @throws wfWAFException
456
+ */
457
+ public function buildRuleSet($rules) {
458
+ if (is_string($rules)) {
459
+ $parser = new wfWAFRuleParser(new wfWAFRuleLexer($rules), $this);
460
+ $rules = $parser->parse();
461
+ }
462
+
463
+ if (!array_key_exists('rules', $rules) || !is_array($rules['rules'])) {
464
+ throw new wfWAFBuildRulesException('Invalid rule format passed to buildRuleSet.');
465
+ }
466
+ $exportedCode = '';
467
+
468
+ if (isset($rules['scores']) && is_array($rules['scores'])) {
469
+ foreach ($rules['scores'] as $category => $score) {
470
+ $exportedCode .= sprintf("\$this->failScores[%s] = %d;\n", var_export($category, true), $score);
471
+ }
472
+ $exportedCode .= "\n";
473
+ }
474
+
475
+ if (isset($rules['variables']) && is_array($rules['variables'])) {
476
+ foreach ($rules['variables'] as $var => $value) {
477
+ $exportedCode .= sprintf("\$this->variables[%s] = %s;\n", var_export($var, true),
478
+ ($value instanceof wfWAFRuleVariable) ? $value->render() : var_export($value, true));
479
+ }
480
+ $exportedCode .= "\n";
481
+ }
482
+
483
+ foreach (array('blacklistedParams', 'whitelistedParams') as $key) {
484
+ if (isset($rules[$key]) && is_array($rules[$key])) {
485
+ /** @var wfWAFRuleParserURLParam $urlParam */
486
+ foreach ($rules[$key] as $urlParam) {
487
+ if ($urlParam->getRules()) {
488
+ $url = array(
489
+ 'url' => $urlParam->getUrl(),
490
+ 'rules' => $urlParam->getRules(),
491
+ );
492
+ } else {
493
+ $url = $urlParam->getUrl();
494
+ }
495
+
496
+ $exportedCode .= sprintf("\$this->{$key}[%s][] = %s;\n", var_export($urlParam->getParam(), true),
497
+ var_export($url, true));
498
+ }
499
+ $exportedCode .= "\n";
500
+ }
501
+ }
502
+
503
+ /** @var wfWAFRule $rule */
504
+ foreach ($rules['rules'] as $rule) {
505
+ $rule->setWAF($this);
506
+ $exportedCode .= sprintf(<<<HTML
507
+ \$this->rules[%d] = %s;
508
+
509
+ HTML
510
+ ,
511
+ $rule->getRuleID(),
512
+ $rule->render()
513
+ );
514
+ }
515
+
516
+ return $exportedCode;
517
+ }
518
+
519
+ /**
520
+ * @param $rules
521
+ * @return wfWAFRuleComparisonGroup
522
+ * @throws wfWAFBuildRulesException
523
+ */
524
+ protected function _buildRuleSet($rules) {
525
+ $ruleGroup = new wfWAFRuleComparisonGroup();
526
+ foreach ($rules as $rule) {
527
+ if (!array_key_exists('type', $rule)) {
528
+ throw new wfWAFBuildRulesException('Invalid rule: type not set.');
529
+ }
530
+ switch ($rule['type']) {
531
+ case 'comparison_group':
532
+ if (!array_key_exists('comparisons', $rule) || !is_array($rule['comparisons'])) {
533
+ throw new wfWAFBuildRulesException('Invalid rule format passed to _buildRuleSet.');
534
+ }
535
+ $ruleGroup->add($this->_buildRuleSet($rule['comparisons']));
536
+ break;
537
+
538
+ case 'comparison':
539
+ if (array_key_exists('parameter', $rule)) {
540
+ $rule['parameters'] = array($rule['parameter']);
541
+ }
542
+
543
+ foreach (array('action', 'expected', 'parameters') as $ruleRequirement) {
544
+ if (!array_key_exists($ruleRequirement, $rule)) {
545
+ throw new wfWAFBuildRulesException("Invalid rule: $ruleRequirement not set.");
546
+ }
547
+ }
548
+
549
+ $ruleGroup->add(new wfWAFRuleComparison($this, $rule['action'], $rule['expected'], $rule['parameters']));
550
+ break;
551
+
552
+ case 'operator':
553
+ if (!array_key_exists('operator', $rule)) {
554
+ throw new wfWAFBuildRulesException('Invalid rule format passed to _buildRuleSet. operator not passed.');
555
+ }
556
+ $ruleGroup->add(new wfWAFRuleLogicalOperator($rule['operator']));
557
+ break;
558
+
559
+ default:
560
+ throw new wfWAFBuildRulesException("Invalid rule type [{$rule['type']}] passed to _buildRuleSet.");
561
+ }
562
+ }
563
+ return $ruleGroup;
564
+ }
565
+
566
+ public function isRuleDisabled($ruleID) {
567
+ if ($this->disabledRules === null) {
568
+ $this->disabledRules = $this->getStorageEngine()->getConfig('disabledRules');
569
+ if (!is_array($this->disabledRules)) {
570
+ $this->disabledRules = array();
571
+ }
572
+ }
573
+ return !empty($this->disabledRules[$ruleID]);
574
+ }
575
+
576
+ /**
577
+ * @param wfWAFRule $rule
578
+ * @param wfWAFRuleComparisonFailure $failedComparison
579
+ * @throws wfWAFBlockException
580
+ */
581
+ public function fail($rule, $failedComparison) {
582
+ $category = $rule->getCategory();
583
+ $paramKey = $failedComparison->getParamKey();
584
+ $this->failedRules[$paramKey][$category][] = array(
585
+ 'rule' => $rule,
586
+ 'failedComparison' => $failedComparison,
587
+ 'action' => 'block',
588
+ );
589
+ }
590
+
591
+ /**
592
+ * @param wfWAFRule $rule
593
+ * @param wfWAFRuleComparisonFailure $failedComparison
594
+ * @throws wfWAFBlockException
595
+ */
596
+ public function failXSS($rule, $failedComparison) {
597
+ $category = $rule->getCategory();
598
+ $paramKey = $failedComparison->getParamKey();
599
+ $this->failedRules[$paramKey][$category][] = array(
600
+ 'rule' => $rule,
601
+ 'failedComparison' => $failedComparison,
602
+ 'action' => 'blockXSS',
603
+ );
604
+ }
605
+
606
+ /**
607
+ * @param wfWAFRule $rule
608
+ * @param wfWAFRuleComparisonFailure $failedComparison
609
+ * @throws wfWAFBlockException
610
+ */
611
+ public function failSQLi($rule, $failedComparison) {
612
+ $category = $rule->getCategory();
613
+ $paramKey = $failedComparison->getParamKey();
614
+ $this->failedRules[$paramKey][$category][] = array(
615
+ 'rule' => $rule,
616
+ 'failedComparison' => $failedComparison,
617
+ 'action' => 'blockSQLi',
618
+ );
619
+ }
620
+
621
+ /**
622
+ * @param wfWAFRule $rule
623
+ * @param wfWAFRuleComparisonFailure $failedComparison
624
+ * @throws wfWAFAllowException
625
+ */
626
+ public function allow($rule, $failedComparison) {
627
+ // Exclude this request from further blocking
628
+ $e = new wfWAFAllowException();
629
+ $e->setFailedRules(array($rule));
630
+ $e->setParamKey($failedComparison->getParamKey());
631
+ $e->setParamValue($failedComparison->getParamValue());
632
+ $e->setRequest($this->getRequest());
633
+ throw $e;
634
+ }
635
+
636
+ /**
637
+ * @param wfWAFRule $rule
638
+ * @param wfWAFRuleComparisonFailure $failedComparison
639
+ * @param bool $updateFailedRules
640
+ * @throws wfWAFBlockException
641
+ */
642
+ public function block($rule, $failedComparison, $updateFailedRules = true) {
643
+ $paramKey = $failedComparison->getParamKey();
644
+ $category = $rule->getCategory();
645
+
646
+ if ($updateFailedRules) {
647
+ $this->failedRules[$paramKey][$category][] = array(
648
+ 'rule' => $rule,
649
+ 'failedComparison' => $failedComparison,
650
+ 'action' => 'block',
651
+ );
652
+ }
653
+
654
+ $e = new wfWAFBlockException();
655
+ $e->setFailedRules(array($rule));
656
+ $e->setParamKey($failedComparison->getParamKey());
657
+ $e->setParamValue($failedComparison->getParamValue());
658
+ $e->setRequest($this->getRequest());
659
+ throw $e;
660
+ }
661
+
662
+ /**
663
+ * @param wfWAFRule $rule
664
+ * @param wfWAFRuleComparisonFailure $failedComparison
665
+ * @param bool $updateFailedRules
666
+ * @throws wfWAFBlockXSSException
667
+ */
668
+ public function blockXSS($rule, $failedComparison, $updateFailedRules = true) {
669
+ $paramKey = $failedComparison->getParamKey();
670
+ $category = $rule->getCategory();
671
+
672
+ if ($updateFailedRules) {
673
+ $this->failedRules[$paramKey][$category][] = array(
674
+ 'rule' => $rule,
675
+ 'failedComparison' => $failedComparison,
676
+ 'action' => 'blockXSS',
677
+ );
678
+ }
679
+ $e = new wfWAFBlockXSSException();
680
+ $e->setFailedRules(array($rule));
681
+ $e->setParamKey($failedComparison->getParamKey());
682
+ $e->setParamValue($failedComparison->getParamValue());
683
+ $e->setRequest($this->getRequest());
684
+ throw $e;
685
+ }
686
+
687
+ /**
688
+ * @param wfWAFRule $rule
689
+ * @param wfWAFRuleComparisonFailure $failedComparison
690
+ * @param bool $updateFailedRules
691
+ * @throws wfWAFBlockSQLiException
692
+ */
693
+ public function blockSQLi($rule, $failedComparison, $updateFailedRules = true) {
694
+ // Verify the param looks like SQLi to help reduce false positives.
695
+ if (!wfWAFSQLiParser::testForSQLi($failedComparison->getParamValue())) {
696
+ return;
697
+ }
698
+
699
+ $paramKey = $failedComparison->getParamKey();
700
+ $category = $rule->getCategory();
701
+
702
+ if ($updateFailedRules) {
703
+ $this->failedRules[$paramKey][$category][] = array(
704
+ 'rule' => $rule,
705
+ 'failedComparison' => $failedComparison,
706
+ 'action' => 'blockXSS',
707
+ );
708
+ }
709
+ $e = new wfWAFBlockSQLiException();
710
+ $e->setFailedRules(array($rule));
711
+ $e->setParamKey($failedComparison->getParamKey());
712
+ $e->setParamValue($failedComparison->getParamValue());
713
+ $e->setRequest($this->getRequest());
714
+ throw $e;
715
+ }
716
+
717
+ /**
718
+ * @todo Hook up $httpCode
719
+ * @param wfWAFBlockException $e
720
+ * @param int $httpCode
721
+ */
722
+ public function blockAction($e, $httpCode = 403) {
723
+ $this->getStorageEngine()->logAttack($e->getFailedRules(), $e->getParamKey(), $e->getParamValue(), $e->getRequest());
724
+ $this->getStorageEngine()->blockIP($this->getRequest()->getTimestamp(), $this->getRequest()->getIP());
725
+ header('HTTP/1.0 403 Forbidden');
726
+ exit($this->getBlockedMessage());
727
+ }
728
+
729
+ /**
730
+ * @todo Hook up $httpCode
731
+ * @param wfWAFBlockXSSException $e
732
+ * @param int $httpCode
733
+ */
734
+ public function blockXSSAction($e, $httpCode = 403) {
735
+ $this->getStorageEngine()->logAttack($e->getFailedRules(), $e->getParamKey(), $e->getParamValue(), $e->getRequest());
736
+ header('HTTP/1.0 403 Forbidden');
737
+ exit($this->getBlockedMessage());
738
+ }
739
+
740
+ /**
741
+ * @return string
742
+ */
743
+ public function getBlockedMessage() {
744
+ if ($this->currentUserCanWhitelist()) {
745
+ return wfWAFView::create('403-roadblock', array(
746
+ 'waf' => $this,
747
+ ))->render();
748
+ }
749
+ return wfWAFView::create('403', array(
750
+ 'waf' => $this,
751
+ ))->render();
752
+ }
753
+
754
+ /**
755
+ *
756
+ */
757
+ public function whitelistFailedRules() {
758
+ foreach ($this->failedRules as $paramKey => $categories) {
759
+ foreach ($categories as $category => $failedRules) {
760
+ foreach ($failedRules as $failedRule) {
761
+ /**
762
+ * @var wfWAFRule $rule
763
+ * @var wfWAFRuleComparisonFailure $failedComparison
764
+ */
765
+ $rule = $failedRule['rule'];
766
+ $failedComparison = $failedRule['failedComparison'];
767
+
768
+ $data = array(
769
+ 'timestamp' => time(),
770
+ 'description' => 'Whitelisted while in Learning Mode.',
771
+ 'ip' => $this->getRequest()->getIP(),
772
+ );
773
+ if (function_exists('get_current_user_id')) {
774
+ $data['userID'] = get_current_user_id();
775
+ }
776
+ $this->whitelistRuleForParam($this->getRequest()->getPath(), $failedComparison->getParamKey(),
777
+ $rule->getRuleID(), $data);
778
+ }
779
+ }
780
+ }
781
+ }
782
+
783
+ /**
784
+ * @param string $path
785
+ * @param string $paramKey
786
+ * @param int $ruleID
787
+ * @param array $data
788
+ */
789
+ public function whitelistRuleForParam($path, $paramKey, $ruleID, $data = array()) {
790
+ if ($this->isParamKeyURLBlacklisted($ruleID, $paramKey, $path)) {
791
+ return;
792
+ }
793
+
794
+ $whitelist = $this->getStorageEngine()->getConfig('whitelistedURLParams');
795
+ if (!is_array($whitelist)) {
796
+ $whitelist = array();
797
+ }
798
+ if (is_array($ruleID)) {
799
+ foreach ($ruleID as $id) {
800
+ $whitelist[base64_encode($path) . "|" . base64_encode($paramKey)][$id] = $data;
801
+ }
802
+ } else {
803
+ $whitelist[base64_encode($path) . "|" . base64_encode($paramKey)][$ruleID] = $data;
804
+ }
805
+
806
+ $this->getStorageEngine()->setConfig('whitelistedURLParams', $whitelist);
807
+ }
808
+
809
+ /**
810
+ * @param int $ruleID
811
+ * @param string $urlPath
812
+ * @param string $paramKey
813
+ * @return bool
814
+ */
815
+ public function isRuleParamWhitelisted($ruleID, $urlPath, $paramKey) {
816
+ if ($this->isParamKeyURLBlacklisted($ruleID, $paramKey, $urlPath)) {
817
+ return false;
818
+ }
819
+
820
+ if (is_array($this->whitelistedParams) && array_key_exists($paramKey, $this->whitelistedParams)
821
+ && is_array($this->whitelistedParams[$paramKey])
822
+ ) {
823
+ foreach ($this->whitelistedParams[$paramKey] as $urlRegex) {
824
+ if (is_array($urlRegex)) {
825
+ if (!in_array($ruleID, $urlRegex['rules'])) {
826
+ continue;
827
+ }
828
+ $urlRegex = $urlRegex['url'];
829
+ }
830
+ if (preg_match($urlRegex, $urlPath)) {
831
+ return true;
832
+ }
833
+ }
834
+ }
835
+
836
+ $whitelistKey = base64_encode($urlPath) . "|" . base64_encode($paramKey);
837
+ $whitelist = $this->getStorageEngine()->getConfig('whitelistedURLParams', array());
838
+ if (!is_array($whitelist)) {
839
+ $whitelist = array();
840
+ }
841
+
842
+ if (array_key_exists($whitelistKey, $whitelist)) {
843
+ foreach (array('all', $ruleID) as $key) {
844
+ if (array_key_exists($key, $whitelist[$whitelistKey])) {
845
+ $ruleData = $whitelist[$whitelistKey][$key];
846
+ if (is_array($ruleData) && array_key_exists('disabled', $ruleData)) {
847
+ return !$ruleData['disabled'];
848
+ } else if ($ruleData) {
849
+ return true;
850
+ }
851
+ }
852
+ }
853
+ }
854
+ return false;
855
+ }
856
+
857
+ /**
858
+ *
859
+ */
860
+ public function sendAttackData() {
861
+ if ($this->getStorageEngine()->getConfig('attackDataKey', false) === false) {
862
+ $this->getStorageEngine()->setConfig('attackDataKey', mt_rand(0, 0xfff));
863
+ }
864
+
865
+ $request = new wfWAFHTTP();
866
+ try {
867
+ $response = wfWAFHTTP::get(
868
+ sprintf(WFWAF_API_URL_SEC . "waf-rules/%d.txt", $this->getStorageEngine()->getConfig('attackDataKey')),
869
+ $request);
870
+
871
+ if ($response instanceof wfWAFHTTPResponse) {
872
+ if ($response->getBody() === 'ok') {
873
+ $request = new wfWAFHTTP();
874
+ $request->setHeaders(array(
875
+ 'Content-Type' => 'application/json',
876
+ ));
877
+ $response = wfWAFHTTP::post(WFWAF_API_URL_SEC . "?" . http_build_query(array(
878
+ 'action' => 'send_waf_attack_data',
879
+ 'k' => $this->getStorageEngine()->getConfig('apiKey'),
880
+ 's' => $this->getStorageEngine()->getConfig('siteURL') ? $this->getStorageEngine()->getConfig('siteURL') :
881
+ sprintf('%s://%s/', $this->getRequest()->getProtocol(), rawurlencode($this->getRequest()->getHost())),
882
+ )), $this->getStorageEngine()->getAttackData(), $request);
883
+
884
+ if ($response instanceof wfWAFHTTPResponse && $response->getBody()) {
885
+ $jsonData = json_decode($response->getBody(), true);
886
+ if (is_array($jsonData) && array_key_exists('success', $jsonData)) {
887
+ $this->getStorageEngine()->truncateAttackData();
888
+ $this->getStorageEngine()->unsetConfig('attackDataNextInterval');
889
+ }
890
+ }
891
+ } else if (is_string($response->getBody()) && preg_match('/next check in: ([0-9]+)/', $response->getBody(), $matches)) {
892
+ $this->getStorageEngine()->setConfig('attackDataNextInterval', time() + $matches[1]);
893
+ if ($this->getStorageEngine()->isAttackDataFull()) {
894
+ $this->getStorageEngine()->truncateAttackData();
895
+ }
896
+ }
897
+
898
+ // Could be that the server is down, so hold off on sending data for a little while.
899
+ } else {
900
+ $this->getStorageEngine()->setConfig('attackDataNextInterval', time() + 7200);
901
+ }
902
+
903
+ } catch (wfWAFHTTPTransportException $e) {
904
+ error_log($e->getMessage());
905
+ }
906
+ }
907
+
908
+ /**
909
+ * @param string $action
910
+ * @return array
911
+ */
912
+ public function isAllowedAction($action) {
913
+ static $actions;
914
+ if (!isset($actions)) {
915
+ $actions = array_flip($this->getAllowedActions());
916
+ }
917
+ return array_key_exists($action, $actions);
918
+ }
919
+
920
+ /**
921
+ * @return array
922
+ */
923
+ public function getAllowedActions() {
924
+ return array('fail', 'allow', 'block', 'failXSS', 'blockXSS', 'failSQLi', 'blockSQLi');
925
+ }
926
+
927
+ /**
928
+ *
929
+ */
930
+ public function uninstall() {
931
+ @unlink($this->getCompiledRulesFile());
932
+ $this->getStorageEngine()->uninstall();
933
+ }
934
+
935
+ /**
936
+ * @param int $ruleID
937
+ * @param string $paramKey
938
+ * @param string $urlPath
939
+ * @return bool
940
+ */
941
+ public function isParamKeyURLBlacklisted($ruleID, $paramKey, $urlPath) {
942
+ if (is_array($this->blacklistedParams) && array_key_exists($paramKey, $this->blacklistedParams)
943
+ && is_array($this->blacklistedParams[$paramKey])
944
+ ) {
945
+ foreach ($this->blacklistedParams[$paramKey] as $urlRegex) {
946
+ if (is_array($urlRegex)) {
947
+ if (!in_array($ruleID, $urlRegex['rules'])) {
948
+ continue;
949
+ }
950
+ $urlRegex = $urlRegex['url'];
951
+ }
952
+ if (preg_match($urlRegex, $urlPath)) {
953
+ return true;
954
+ }
955
+ }
956
+ }
957
+ return false;
958
+ }
959
+
960
+ /**
961
+ * @return bool
962
+ */
963
+ public function currentUserCanWhitelist() {
964
+ if ($authCookie = $this->parseAuthCookie()) {
965
+ return $authCookie['role'] === 'administrator';
966
+ }
967
+ return false;
968
+ }
969
+
970
+ /**
971
+ * @param string|null $cookieVal
972
+ * @return bool
973
+ */
974
+ public function parseAuthCookie($cookieVal = null) {
975
+ if ($cookieVal === null) {
976
+ $cookieName = $this->getAuthCookieName();
977
+ $cookieVal = !empty($_COOKIE[$cookieName]) && is_string($_COOKIE[$cookieName]) ? $_COOKIE[$cookieName] : '';
978
+ }
979
+ $pieces = explode('|', $cookieVal);
980
+ if (count($pieces) !== 3) {
981
+ return false;
982
+ }
983
+ list($userID, $role, $signature) = $pieces;
984
+ if (wfWAFUtils::hash_equals($signature, $this->getAuthCookieValue($userID, $role))) {
985
+ return array(
986
+ 'userID' => $userID,
987
+ 'role' => $role,
988
+ );
989
+ }
990
+ return false;
991
+ }
992
+
993
+ /**
994
+ * @param int|string $userID
995
+ * @param string $role
996
+ * @return bool|string
997
+ */
998
+ public function getAuthCookieValue($userID, $role) {
999
+ $algo = function_exists('hash') ? 'sha256' : 'sha1';
1000
+ return wfWAFUtils::hash_hmac($algo, $userID . $role . floor(time() / 43200), $this->getStorageEngine()->getConfig('authKey'));
1001
+ }
1002
+
1003
+ /**
1004
+ * @param string|null $host
1005
+ * @return string
1006
+ */
1007
+ public function getAuthCookieName($host = null) {
1008
+ if ($host === null) {
1009
+ $host = $this->getRequest()->getHost();
1010
+ }
1011
+ return self::AUTH_COOKIE . '-' . md5($host);
1012
+ }
1013
+
1014
+ /**
1015
+ * @return string
1016
+ */
1017
+ public function getCompiledRulesFile() {
1018
+ return $this->rulesFile;
1019
+ }
1020
+
1021
+ /**
1022
+ * @param string $rulesFile
1023
+ */
1024
+ public function setCompiledRulesFile($rulesFile) {
1025
+ $this->rulesFile = $rulesFile;
1026
+ }
1027
+
1028
+ /**
1029
+ * @param $ip
1030
+ * @return mixed
1031
+ */
1032
+ public function isIPBlocked($ip) {
1033
+ return $this->getStorageEngine()->isIPBlocked($ip);
1034
+ }
1035
+
1036
+ /**
1037
+ * @return array
1038
+ */
1039
+ public function getTrippedRules() {
1040
+ return $this->trippedRules;
1041
+ }
1042
+
1043
+ /**
1044
+ * @return array
1045
+ */
1046
+ public function getTrippedRuleIDs() {
1047
+ $ret = array();
1048
+ /** @var wfWAFRule $rule */
1049
+ foreach ($this->getTrippedRules() as $rule) {
1050
+ $ret[] = $rule->getRuleID();
1051
+ }
1052
+ return $ret;
1053
+ }
1054
+
1055
+ public function showBench() {
1056
+ return sprintf("Bench: %f seconds\n\n", microtime(true) - $this->getRequest()->getTimestamp());
1057
+ }
1058
+
1059
+ public function debug() {
1060
+ return join("\n", $this->debug) . "\n\n" . $this->showBench();
1061
+ // $debug = '';
1062
+ // /** @var wfWAFRule $rule */
1063
+ // foreach ($this->trippedRules as $rule) {
1064
+ // $debug .= $rule->debug();
1065
+ // }
1066
+ // return $debug;
1067
+ }
1068
+
1069
+ /**
1070
+ * @return array
1071
+ */
1072
+ public function getScores() {
1073
+ return $this->scores;
1074
+ }
1075
+
1076
+ /**
1077
+ * @param string $var
1078
+ * @return null
1079
+ */
1080
+ public function getVariable($var) {
1081
+ if (array_key_exists($var, $this->variables)) {
1082
+ return $this->variables[$var];
1083
+ }
1084
+ return null;
1085
+ }
1086
+
1087
+ /**
1088
+ * @return wfWAFRequestInterface
1089
+ */
1090
+ public function getRequest() {
1091
+ return $this->request;
1092
+ }
1093
+
1094
+ /**
1095
+ * @param wfWAFRequestInterface $request
1096
+ */
1097
+ public function setRequest($request) {
1098
+ $this->request = $request;
1099
+ }
1100
+
1101
+ /**
1102
+ * @return wfWAFStorageInterface
1103
+ */
1104
+ public function getStorageEngine() {
1105
+ return $this->storageEngine;
1106
+ }
1107
+
1108
+ /**
1109
+ * @param wfWAFStorageInterface $storageEngine
1110
+ */
1111
+ public function setStorageEngine($storageEngine) {
1112
+ $this->storageEngine = $storageEngine;
1113
+ }
1114
+
1115
+ /**
1116
+ * @return wfWAFEventBus
1117
+ */
1118
+ public function getEventBus() {
1119
+ return $this->eventBus;
1120
+ }
1121
+
1122
+ /**
1123
+ * @param wfWAFEventBus $eventBus
1124
+ */
1125
+ public function setEventBus($eventBus) {
1126
+ $this->eventBus = $eventBus;
1127
+ }
1128
+
1129
+ /**
1130
+ * @return array
1131
+ */
1132
+ public function getRules() {
1133
+ return $this->rules;
1134
+ }
1135
+
1136
+ /**
1137
+ * @param array $rules
1138
+ */
1139
+ public function setRules($rules) {
1140
+ $this->rules = $rules;
1141
+ }
1142
+
1143
+ /**
1144
+ * @param int $ruleID
1145
+ * @return null|wfWAFRule
1146
+ */
1147
+ public function getRule($ruleID) {
1148
+ $rules = $this->getRules();
1149
+ if (is_array($rules) && array_key_exists($ruleID, $rules)) {
1150
+ return $rules[$ruleID];
1151
+ }
1152
+ return null;
1153
+ }
1154
+
1155
+ /**
1156
+ * @return string
1157
+ */
1158
+ public function getPublicKey() {
1159
+ return $this->publicKey;
1160
+ }
1161
+
1162
+ /**
1163
+ * @param string $publicKey
1164
+ */
1165
+ public function setPublicKey($publicKey) {
1166
+ $this->publicKey = $publicKey;
1167
+ }
1168
+
1169
+ /**
1170
+ * @return array
1171
+ */
1172
+ public function getFailedRules() {
1173
+ return $this->failedRules;
1174
+ }
1175
+ }
1176
+
1177
+ /**
1178
+ * Serialized for use with the WAF cron.
1179
+ */
1180
+ abstract class wfWAFCronEvent {
1181
+
1182
+ abstract public function fire();
1183
+
1184
+ abstract public function reschedule();
1185
+
1186
+ protected $fireTime;
1187
+ private $waf;
1188
+
1189
+ /**
1190
+ * @param int $fireTime
1191
+ */
1192
+ public function __construct($fireTime) {
1193
+ $this->setFireTime($fireTime);
1194
+ }
1195
+
1196
+ /**
1197
+ * @param int|null $time
1198
+ * @return bool
1199
+ */
1200
+ public function isInPast($time = null) {
1201
+ if ($time === null) {
1202
+ $time = time();
1203
+ }
1204
+ return $this->getFireTime() <= $time;
1205
+ }
1206
+
1207
+ public function __sleep() {
1208
+ return array('fireTime');
1209
+ }
1210
+
1211
+ /**
1212
+ * @return mixed
1213
+ */
1214
+ public function getFireTime() {
1215
+ return $this->fireTime;
1216
+ }
1217
+
1218
+ /**
1219
+ * @param mixed $fireTime
1220
+ */
1221
+ public function setFireTime($fireTime) {
1222
+ $this->fireTime = $fireTime;
1223
+ }
1224
+
1225
+ /**
1226
+ * @return wfWAF
1227
+ */
1228
+ public function getWaf() {
1229
+ return $this->waf;
1230
+ }
1231
+
1232
+ /**
1233
+ * @param wfWAF $waf
1234
+ */
1235
+ public function setWaf($waf) {
1236
+ $this->waf = $waf;
1237
+ }
1238
+ }
1239
+
1240
+ class wfWAFCronFetchRulesEvent extends wfWAFCronEvent {
1241
+
1242
+ /**
1243
+ * @var wfWAFHTTPResponse
1244
+ */
1245
+ private $response;
1246
+
1247
+ public function fire() {
1248
+ $waf = $this->getWaf();
1249
+ if (!$waf) {
1250
+ return;
1251
+ }
1252
+ $guessSiteURL = sprintf('%s://%s/', $waf->getRequest()->getProtocol(), $waf->getRequest()->getHost());
1253
+ try {
1254
+ $this->response = wfWAFHTTP::get(WFWAF_API_URL_SEC . "?" . http_build_query(array(
1255
+ 'action' => 'get_waf_rules',
1256
+ 'k' => $waf->getStorageEngine()->getConfig('apiKey'),
1257
+ 's' => $waf->getStorageEngine()->getConfig('siteURL') ? $waf->getStorageEngine()->getConfig('siteURL') : $guessSiteURL,
1258
+ 'h' => $waf->getStorageEngine()->getConfig('homeURL') ? $waf->getStorageEngine()->getConfig('homeURL') : $guessSiteURL,
1259
+ 'openssl' => $waf->hasOpenSSL() ? 1 : 0,
1260
+ 'betaFeed' => (int) $waf->getStorageEngine()->getConfig('betaThreatDefenseFeed'),
1261
+ )));
1262
+ if ($this->response) {
1263
+ $jsonData = json_decode($this->response->getBody(), true);
1264
+ if (is_array($jsonData)) {
1265
+
1266
+ if ($waf->hasOpenSSL() &&
1267
+ isset($jsonData['data']['signature']) &&
1268
+ isset($jsonData['data']['rules']) &&
1269
+ $waf->verifySignedRequest(base64_decode($jsonData['data']['signature']), $jsonData['data']['rules'])
1270
+ ) {
1271
+ $waf->updateRuleSet(base64_decode($jsonData['data']['rules']),
1272
+ isset($jsonData['data']['timestamp']) ? $jsonData['data']['timestamp'] : true);
1273
+ if (array_key_exists('premiumCount', $jsonData['data'])) {
1274
+ $waf->getStorageEngine()->setConfig('premiumCount', $jsonData['data']['premiumCount']);
1275
+ }
1276
+
1277
+ } else if (!$waf->hasOpenSSL() &&
1278
+ isset($jsonData['data']['hash']) &&
1279
+ isset($jsonData['data']['rules']) &&
1280
+ $waf->verifyHashedRequest($jsonData['data']['hash'], $jsonData['data']['rules'])
1281
+ ) {
1282
+ $waf->updateRuleSet(base64_decode($jsonData['data']['rules']),
1283
+ isset($jsonData['data']['timestamp']) ? $jsonData['data']['timestamp'] : true);
1284
+ if (array_key_exists('premiumCount', $jsonData['data'])) {
1285
+ $waf->getStorageEngine()->setConfig('premiumCount', $jsonData['data']['premiumCount']);
1286
+ }
1287
+ }
1288
+
1289
+ }
1290
+ }
1291
+ } catch (wfWAFHTTPTransportException $e) {
1292
+ error_log($e->getMessage());
1293
+ } catch (wfWAFBuildRulesException $e) {
1294
+ error_log($e->getMessage());
1295
+ }
1296
+ }
1297
+
1298
+ /**
1299
+ * @return wfWAFCronEvent|bool
1300
+ */
1301
+ public function reschedule() {
1302
+ $waf = $this->getWaf();
1303
+ if (!$waf) {
1304
+ return false;
1305
+ }
1306
+ $newEvent = new self(time() + (86400 * ($waf->getStorageEngine()->getConfig('isPaid') ? .5 : 7)));
1307
+ if ($this->response) {
1308
+ $headers = $this->response->getHeaders();
1309
+ if (isset($headers['Expires'])) {
1310
+ $timestamp = strtotime($headers['Expires']);
1311
+ // Make sure it's at least 2 hours ahead.
1312
+ if ($timestamp && $timestamp > (time() + 7200)) {
1313
+ $newEvent->setFireTime($timestamp);
1314
+ }
1315
+ }
1316
+ }
1317
+ return $newEvent;
1318
+ }
1319
+ }
1320
+
1321
+ class wfWAFEventBus implements wfWAFObserver {
1322
+
1323
+ private $observers = array();
1324
+
1325
+ /**
1326
+ * @param wfWAFObserver $observer
1327
+ * @throws wfWAFEventBusException
1328
+ */
1329
+ public function attach($observer) {
1330
+ if (!($observer instanceof wfWAFObserver)) {
1331
+ throw new wfWAFEventBusException('Observer supplied to wfWAFEventBus::attach must implement wfWAFObserver');
1332
+ }
1333
+ $this->observers[] = $observer;
1334
+ }
1335
+
1336
+ /**
1337
+ * @param wfWAFObserver $observer
1338
+ */
1339
+ public function detach($observer) {
1340
+ $key = array_search($observer, $this->observers, true);
1341
+ if ($key !== false) {
1342
+ unset($this->observers[$key]);
1343
+ }
1344
+ }
1345
+
1346
+ public function prevBlocked($ip) {
1347
+ /** @var wfWAFObserver $observer */
1348
+ foreach ($this->observers as $observer) {
1349
+ $observer->prevBlocked($ip);
1350
+ }
1351
+ }
1352
+
1353
+ public function block($ip, $exception) {
1354
+ /** @var wfWAFObserver $observer */
1355
+ foreach ($this->observers as $observer) {
1356
+ $observer->block($ip, $exception);
1357
+ }
1358
+ }
1359
+
1360
+ public function allow($ip, $exception) {
1361
+ /** @var wfWAFObserver $observer */
1362
+ foreach ($this->observers as $observer) {
1363
+ $observer->allow($ip, $exception);
1364
+ }
1365
+ }
1366
+
1367
+ public function blockXSS($ip, $exception) {
1368
+ /** @var wfWAFObserver $observer */
1369
+ foreach ($this->observers as $observer) {
1370
+ $observer->blockXSS($ip, $exception);
1371
+ }
1372
+ }
1373
+
1374
+ public function blockSQLi($ip, $exception) {
1375
+ /** @var wfWAFObserver $observer */
1376
+ foreach ($this->observers as $observer) {
1377
+ $observer->blockSQLi($ip, $exception);
1378
+ }
1379
+ }
1380
+
1381
+
1382
+ public function wafDisabled() {
1383
+ /** @var wfWAFObserver $observer */
1384
+ foreach ($this->observers as $observer) {
1385
+ $observer->wafDisabled();
1386
+ }
1387
+ }
1388
+
1389
+ public function beforeRunRules() {
1390
+ /** @var wfWAFObserver $observer */
1391
+ foreach ($this->observers as $observer) {
1392
+ $observer->beforeRunRules();
1393
+ }
1394
+ }
1395
+
1396
+ public function afterRunRules() {
1397
+ /** @var wfWAFObserver $observer */
1398
+ foreach ($this->observers as $observer) {
1399
+ $observer->afterRunRules();
1400
+ }
1401
+ }
1402
+ }
1403
+
1404
+ interface wfWAFObserver {
1405
+
1406
+ public function prevBlocked($ip);
1407
+
1408
+ public function block($ip, $exception);
1409
+
1410
+ public function allow($ip, $exception);
1411
+
1412
+ public function blockXSS($ip, $exception);
1413
+
1414
+ public function blockSQLi($ip, $exception);
1415
+
1416
+ public function wafDisabled();
1417
+
1418
+ public function beforeRunRules();
1419
+
1420
+ public function afterRunRules();
1421
+ }
1422
+
1423
+ class wfWAFBaseObserver implements wfWAFObserver {
1424
+
1425
+ public function prevBlocked($ip) {
1426
+
1427
+ }
1428
+
1429
+ public function block($ip, $exception) {
1430
+
1431
+ }
1432
+
1433
+ public function allow($ip, $exception) {
1434
+
1435
+ }
1436
+
1437
+ public function blockXSS($ip, $exception) {
1438
+
1439
+ }
1440
+
1441
+ public function blockSQLi($ip, $exception) {
1442
+
1443
+ }
1444
+
1445
+ public function wafDisabled() {
1446
+
1447
+ }
1448
+
1449
+ public function beforeRunRules() {
1450
+
1451
+ }
1452
+
1453
+ public function afterRunRules() {
1454
+
1455
+ }
1456
+ }
1457
+
1458
+ class wfWAFException extends Exception {
1459
+ }
1460
+
1461
+ class wfWAFRunException extends Exception {
1462
+
1463
+ /** @var array */
1464
+ private $failedRules;
1465
+ /** @var string */
1466
+ private $paramKey;
1467
+ /** @var string */
1468
+ private $paramValue;
1469
+ /** @var wfWAFRequestInterface */
1470
+ private $request;
1471
+
1472
+ /**
1473
+ * @return array
1474
+ */
1475
+ public function getFailedRules() {
1476
+ return $this->failedRules;
1477
+ }
1478
+
1479
+ /**
1480
+ * @param array $failedRules
1481
+ */
1482
+ public function setFailedRules($failedRules) {
1483
+ $this->failedRules = $failedRules;
1484
+ }
1485
+
1486
+ /**
1487
+ * @return string
1488
+ */
1489
+ public function getParamKey() {
1490
+ return $this->paramKey;
1491
+ }
1492
+
1493
+ /**
1494
+ * @param string $paramKey
1495
+ */
1496
+ public function setParamKey($paramKey) {
1497
+ $this->paramKey = $paramKey;
1498
+ }
1499
+
1500
+ /**
1501
+ * @return string
1502
+ */
1503
+ public function getParamValue() {
1504
+ return $this->paramValue;
1505
+ }
1506
+
1507
+ /**
1508
+ * @param string $paramValue
1509
+ */
1510
+ public function setParamValue($paramValue) {
1511
+ $this->paramValue = $paramValue;
1512
+ }
1513
+
1514
+ /**
1515
+ * @return wfWAFRequestInterface
1516
+ */
1517
+ public function getRequest() {
1518
+ return $this->request;
1519
+ }
1520
+
1521
+ /**
1522
+ * @param wfWAFRequestInterface $request
1523
+ */
1524
+ public function setRequest($request) {
1525
+ $this->request = $request;
1526
+ }
1527
+ }
1528
+
1529
+ class wfWAFAllowException extends wfWAFRunException {
1530
+ }
1531
+
1532
+ class wfWAFBlockException extends wfWAFRunException {
1533
+ }
1534
+
1535
+ class wfWAFBlockXSSException extends wfWAFRunException {
1536
+ }
1537
+
1538
+ class wfWAFBlockSQLiException extends wfWAFRunException {
1539
+ }
1540
+
1541
+ class wfWAFBuildRulesException extends wfWAFException {
1542
+ }
1543
+
1544
+ class wfWAFEventBusException extends wfWAFException {
1545
+ }
vendor/wordfence/wf-waf/src/logs/.htaccess ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <IfModule mod_authz_core.c>
2
+ Require all denied
3
+ </IfModule>
4
+ <IfModule !mod_authz_core.c>
5
+ Order deny,allow
6
+ Deny from all
7
+ </IfModule>
vendor/wordfence/wf-waf/src/logs/attack-data.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php exit('Access denied'); __halt_compiler(); ?>
vendor/wordfence/wf-waf/src/logs/config.php ADDED
Binary file
vendor/wordfence/wf-waf/src/logs/ips.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php exit('Access denied'); __halt_compiler(); ?>
vendor/wordfence/wf-waf/src/rules.key ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzovUDp/qu7r6LT5d8dLL
3
+ H/87aRrCjUd6XtnG+afAPVfMKNp4u4L+UuYfw1RfpfquP/zLMGdfmJCUp/oJywkW
4
+ Rkqo+y7pDuqIFQ59dHvizmYQRvaZgvincBDpey5Ek9AFfB9fqYYnH9+eQw8eLdQi
5
+ h6Zsh8RsuxFM2BW6JD9Km7L5Lyxw9jU+lye7I3ICYtUOVxc3n3bJT2SiIwHK57pW
6
+ g/asJEUDiYQzsaa90YPOLdf1Ysz2rkgnCduQaEGz/RPhgUrmZfKwq8puEmkh7Yee
7
+ auEa+7b+FGTKs7dUo2BNGR7OVifK4GZ8w/ajS0TelhrSRi3BBQCGXLzUO/UURUAh
8
+ 1QIDAQAB
9
+ -----END PUBLIC KEY-----
vendor/wordfence/wf-waf/src/rules.php ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('WFWAF_VERSION')) {
3
+ exit('Access denied');
4
+ }
5
+ /*
6
+ This file is generated automatically. Any changes made will be lost.
7
+ */
8
+
9
+ $this->failScores['sqli'] = 100;
10
+ $this->failScores['xss'] = 100;
11
+ $this->failScores['rce'] = 100;
12
+
13
+ $this->variables['sqliRegex'] = new wfWAFRuleVariable($this, 'sqliRegex', '/(?:[^\\w<]|\\/\\*\\![0-9]*|^)(?:
14
+ @@HOSTNAME|
15
+ ALTER|ANALYZE|ASENSITIVE|
16
+ BEFORE|BENCHMARK|BETWEEN|BIGINT|BINARY|BLOB|
17
+ CALL|CASE|CHANGE|CHAR|CHARACTER|CHAR_LENGTH|COLLATE|COLUMN|CONCAT|CONDITION|CONSTRAINT|CONTINUE|CONVERT|CREATE|CROSS|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|
18
+ DATABASE|DATABASES|DAY_HOUR|DAY_MICROSECOND|DAY_MINUTE|DAY_SECOND|DECIMAL|DECLARE|DEFAULT|DELAYED|DELETE|DESCRIBE|DETERMINISTIC|DISTINCT|DISTINCTROW|DOUBLE|DROP|DUAL|DUMPFILE|
19
+ EACH|ELSE|ELSEIF|ELT|ENCLOSED|ESCAPED|EXISTS|EXIT|EXPLAIN|EXTRACTVALUE|
20
+ FETCH|FLOAT|FLOAT4|FLOAT8|FORCE|FOREIGN|FROM|FULLTEXT|
21
+ GRANT|GROUP|HAVING|HEX|HIGH_PRIORITY|HOUR_MICROSECOND|HOUR_MINUTE|HOUR_SECOND|
22
+ IFNULL|IGNORE|INDEX|INFILE|INNER|INOUT|INSENSITIVE|INSERT|INTERVAL|ISNULL|ITERATE|
23
+ JOIN|KILL|LEADING|LEAVE|LIMIT|LINEAR|LINES|LOAD|LOAD_FILE|LOCALTIME|LOCALTIMESTAMP|LOCK|LONG|LONGBLOB|LONGTEXT|LOOP|LOW_PRIORITY|
24
+ MASTER_SSL_VERIFY_SERVER_CERT|MATCH|MAXVALUE|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MID|MIDDLEINT|MINUTE_MICROSECOND|MINUTE_SECOND|MODIFIES|
25
+ NATURAL|NO_WRITE_TO_BINLOG|NULL|NUMERIC|OPTION|ORD|ORDER|OUTER|OUTFILE|
26
+ PRECISION|PRIMARY|PRIVILEGES|PROCEDURE|PROCESSLIST|PURGE|
27
+ RANGE|READ_WRITE|REGEXP|RELEASE|REPEAT|REQUIRE|RESIGNAL|RESTRICT|RETURN|REVOKE|RLIKE|ROLLBACK|
28
+ SCHEMA|SCHEMAS|SECOND_MICROSECOND|SELECT|SENSITIVE|SEPARATOR|SHOW|SIGNAL|SLEEP|SMALLINT|SPATIAL|SPECIFIC|SQLEXCEPTION|SQLSTATE|SQLWARNING|SQL_BIG_RESULT|SQL_CALC_FOUND_ROWS|SQL_SMALL_RESULT|STARTING|STRAIGHT_JOIN|SUBSTR|
29
+ TABLE|TERMINATED|TINYBLOB|TINYINT|TINYTEXT|TRAILING|TRANSACTION|TRIGGER|
30
+ UNDO|UNHEX|UNION|UNLOCK|UNSIGNED|UPDATE|UPDATEXML|USAGE|USING|UTC_DATE|UTC_TIME|UTC_TIMESTAMP|
31
+ VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|WHEN|WHERE|WHILE|WRITE|YEAR_MONTH|ZEROFILL)(?=[^\\w]|$)/ix');
32
+ $this->variables['xssRegex'] = new wfWAFRuleVariable($this, 'xssRegex', '/(?:
33
+ #tags
34
+ (?:\\<|\\+ADw\\-|\\xC2\\xBC)(script|iframe|svg|object|embed|applet|link|style|meta|\\/\\/|\\?xml\\-stylesheet)(?:[^\\w]|\\xC2\\xBE)|
35
+ #protocols
36
+ (?:^|[^\\w])(?:(?:\\s*(?:&\\#(?:x0*6a|0*106)|j)\\s*(?:&\\#(?:x0*61|0*97)|a)\\s*(?:&\\#(?:x0*76|0*118)|v)\\s*(?:&\\#(?:x0*61|0*97)|a)|\\s*(?:&\\#(?:x0*76|0*118)|v)\\s*(?:&\\#(?:x0*62|0*98)|b)|\\s*(?:&\\#(?:x0*65|0*101)|e)\\s*(?:&\\#(?:x0*63|0*99)|c)\\s*(?:&\\#(?:x0*6d|0*109)|m)\\s*(?:&\\#(?:x0*61|0*97)|a)|\\s*(?:&\\#(?:x0*6c|0*108)|l)\\s*(?:&\\#(?:x0*69|0*105)|i)\\s*(?:&\\#(?:x0*76|0*118)|v)\\s*(?:&\\#(?:x0*65|0*101)|e))\\s*(?:&\\#(?:x0*73|0*115)|s)\\s*(?:&\\#(?:x0*63|0*99)|c)\\s*(?:&\\#(?:x0*72|0*114)|r)\\s*(?:&\\#(?:x0*69|0*105)|i)\\s*(?:&\\#(?:x0*70|0*112)|p)\\s*(?:&\\#(?:x0*74|0*116)|t)|\\s*(?:&\\#(?:x0*6d|0*109)|m)\\s*(?:&\\#(?:x0*68|0*104)|h)\\s*(?:&\\#(?:x0*74|0*116)|t)\\s*(?:&\\#(?:x0*6d|0*109)|m)\\s*(?:&\\#(?:x0*6c|0*108)|l)|\\s*(?:&\\#(?:x0*6d|0*109)|m)\\s*(?:&\\#(?:x0*6f|0*111)|o)\\s*(?:&\\#(?:x0*63|0*99)|c)\\s*(?:&\\#(?:x0*68|0*104)|h)\\s*(?:&\\#(?:x0*61|0*97)|a)|\\s*(?:&\\#(?:x0*64|0*100)|d)\\s*(?:&\\#(?:x0*61|0*97)|a)\\s*(?:&\\#(?:x0*74|0*116)|t)\\s*(?:&\\#(?:x0*61|0*97)|a))\\s*(?:&\\#(?:x0*3a|0*58)|\\:)|
37
+ #css expression
38
+ (?:^|[^\\w])(?:(?:\\\\0*65|\\\\0*45|e)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*78|\\\\0*58|x)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*70|\\\\0*50|p)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*72|\\\\0*52|r)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*65|\\\\0*45|e)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*73|\\\\0*53|s)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*73|\\\\0*53|s)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*69|\\\\0*49|i)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*6f|\\\\0*4f|o)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*6e|\\\\0*4e|n))[^\\w]*?(?:\\\\0*28|\\()|
39
+ #css properties
40
+ (?:^|[^\\w])(?:(?:(?:\\\\0*62|\\\\0*42|b)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*65|\\\\0*45|e)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*68|\\\\0*48|h)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*61|\\\\0*41|a)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*76|\\\\0*56|v)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*69|\\\\0*49|i)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*6f|\\\\0*4f|o)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*72|\\\\0*52|r)(?:\\/\\*.*?\\*\\/)*)|(?:(?:\\\\0*2d|\\\\0*2d|-)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*6d|\\\\0*4d|m)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*6f|\\\\0*4f|o)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*7a|\\\\0*5a|z)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*2d|\\\\0*2d|-)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*62|\\\\0*42|b)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*69|\\\\0*49|i)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*6e|\\\\0*4e|n)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*64|\\\\0*44|d)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*69|\\\\0*49|i)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*6e|\\\\0*4e|n)(?:\\/\\*.*?\\*\\/)*(?:\\\\0*67|\\\\0*47|g)(?:\\/\\*.*?\\*\\/)*))[^\\w]*(?:\\\\0*3a|\\\\0*3a|:)[^\\w]*(?:\\\\0*75|\\\\0*55|u)(?:\\\\0*72|\\\\0*52|r)(?:\\\\0*6c|\\\\0*4c|l)|
41
+ #properties
42
+ (?:^|[^\\w])(?:on(?:abort|activate|afterprint|afterupdate|autocomplete|autocompleteerror|beforeactivate|beforecopy|beforecut|beforedeactivate|beforeeditfocus|beforepaste|beforeprint|beforeunload|beforeupdate|blur|bounce|cancel|canplay|canplaythrough|cellchange|change|click|close|contextmenu|controlselect|copy|cuechange|cut|dataavailable|datasetchanged|datasetcomplete|dblclick|deactivate|drag|dragend|dragenter|dragleave|dragover|dragstart|drop|durationchange|emptied|encrypted|ended|error|errorupdate|filterchange|finish|focus|focusin|focusout|formaction|formchange|forminput|hashchange|help|input|invalid|keydown|keypress|keyup|languagechange|layoutcomplete|load|loadeddata|loadedmetadata|loadstart|losecapture|message|mousedown|mouseenter|mouseleave|mousemove|mouseout|mouseover|mouseup|mousewheel|move|moveend|movestart|mozfullscreenchange|mozfullscreenerror|mozpointerlockchange|mozpointerlockerror|offline|online|page|pagehide|pageshow|paste|pause|play|playing|popstate|progress|propertychange|ratechange|readystatechange|reset|resize|resizeend|resizestart|rowenter|rowexit|rowsdelete|rowsinserted|scroll|search|seeked|seeking|select|selectstart|show|stalled|start|storage|submit|suspend|timer|timeupdate|toggle|unload|volumechange|waiting|webkitfullscreenchange|webkitfullscreenerror|wheel)|data\\-bind|ev:event)[^\\w]
43
+ )/ix');
44
+
45
+ $this->blacklistedParams['request.queryString[action]'][] = '/\\/wp\\-admin[\\/]+admin\\-ajax\\.php/i';
46
+ $this->blacklistedParams['request.queryString[img]'][] = '/\\/wp\\-admin[\\/]+admin\\-ajax\\.php/i';
47
+ $this->blacklistedParams['request.body[action]'][] = '/\\/wp\\-admin[\\/]+admin\\-ajax\\.php/i';
48
+ $this->blacklistedParams['request.body[img]'][] = '/\\/wp\\-admin[\\/]+admin\\-ajax\\.php/i';
49
+ $this->blacklistedParams['request.body[nsextt]'][] = '/.*/';
50
+ $this->blacklistedParams['request.fileNames[Filedata]'][] = '/\\/uploadify\\.php$/i';
51
+ $this->blacklistedParams['request.fileNames[yiw_contact]'][] = '/.*/';
52
+ $this->blacklistedParams['request.fileNames[filename]'][] = '/\\/license\\.php$/i';
53
+ $this->blacklistedParams['request.fileNames[update_file]'][] = '/\\/wp\\-admin[\\/]+admin\\-ajax\\.php$/i';
54
+ $this->blacklistedParams['request.fileNames[Filedata]'][] = '/tiny_mce[\\/]+plugins[\\/]+tinybrowser[\\/]+upload_file\\.php$/i';
55
+ $this->blacklistedParams['request.fileNames[upload]'][] = '/elfinder[\\/]+php[\\/]+connector\\.minimal\\.php$/i';
56
+
57
+ $this->whitelistedParams['request.body[excerpt]'][] = '/.*/';
58
+ $this->whitelistedParams['request.body[comment]'][] = array (
59
+ 'url' => '/wp-comments-post\\.php$/i',
60
+ 'rules' =>
61
+ array (
62
+ 0 => '3',
63
+ 1 => '12',
64
+ ),
65
+ );
66
+ $this->whitelistedParams['request.body[content]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
67
+ $this->whitelistedParams['request.body[data]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
68
+ $this->whitelistedParams['request.queryString[s]'][] = '/\\/wp-admin\\/(?:network\\/)?(?:plugin(?:s|-install)|edit)\\.php$/i';
69
+ $this->whitelistedParams['request.body[whitelistedPath]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
70
+ $this->whitelistedParams['request.body[whitelistedParam]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
71
+ $this->whitelistedParams['request.body[oldWhitelistedPath]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
72
+ $this->whitelistedParams['request.body[oldWhitelistedParam]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
73
+ $this->whitelistedParams['request.body[newWhitelistedPath]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
74
+ $this->whitelistedParams['request.body[newWhitelistedParam]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
75
+ $this->whitelistedParams['request.body[bannedURLs]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
76
+ $this->whitelistedParams['request.body[scan_include_extra]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
77
+ $this->whitelistedParams['request.body[newcontent]'][] = '/\\/wp-admin\\/(?:network\\/)?(?:plugin|theme)-editor\\.php$/i';
78
+ $this->whitelistedParams['request.queryString[_wp_http_referer]'][] = '/.{0,1}/';
79
+ $this->whitelistedParams['request.queryString[plugin]'][] = '/\\/wp-admin\\/(?:network\\/)?plugins\\.php$/i';
80
+ $this->whitelistedParams['request.queryString[action]'][] = '/\\/wp-admin\\/(?:network\\/)?plugins\\.php$/i';
81
+ $this->whitelistedParams['request.queryString[checked]'][] = '/\\/wp-admin\\/(?:network\\/)?plugins\\.php$/i';
82
+ $this->whitelistedParams['request.body[action]'][] = '/\\/wp-admin\\/(?:network\\/)?plugins\\.php$/i';
83
+ $this->whitelistedParams['request.body[checked]'][] = '/\\/wp-admin\\/(?:network\\/)?plugins\\.php$/i';
84
+ $this->whitelistedParams['request.body[submit]'][] = '/\\/wp-admin\\/(?:network\\/)?plugins\\.php$/i';
85
+ $this->whitelistedParams['request.body[blogname]'][] = '/\\/wp-admin\\/options\\.php$/i';
86
+ $this->whitelistedParams['request.body[blogdescription]'][] = '/\\/wp-admin\\/options\\.php$/i';
87
+ $this->whitelistedParams['request.body[siteurl]'][] = '/\\/wp-admin\\/options\\.php$/i';
88
+ $this->whitelistedParams['request.body[home]'][] = '/\\/wp-admin\\/options\\.php$/i';
89
+ $this->whitelistedParams['request.body[admin_email]'][] = '/\\/wp-admin\\/options\\.php$/i';
90
+ $this->whitelistedParams['request.body[moderation_keys]'][] = '/\\/wp-admin\\/options\\.php$/i';
91
+ $this->whitelistedParams['request.body[blacklist_keys]'][] = '/\\/wp-admin\\/options\\.php$/i';
92
+ $this->whitelistedParams['request.body[permalink_structure]'][] = '/\\/wp-admin\\/options\\.php$/i';
93
+ $this->whitelistedParams['request.body[category_base]'][] = '/\\/wp-admin\\/options\\.php$/i';
94
+ $this->whitelistedParams['request.body[tag_base]'][] = '/\\/wp-admin\\/options\\.php$/i';
95
+ $this->whitelistedParams['request.queryString[s]'][] = '/\\/wp-admin\\/edit-comments\\.php$/i';
96
+ $this->whitelistedParams['request.body[log]'][] = '/\\/wp-login\\.php$/i';
97
+ $this->whitelistedParams['request.body[pwd]'][] = '/\\/wp-login\\.php$/i';
98
+ $this->whitelistedParams['request.body[redirect_to]'][] = '/\\/wp-login\\.php$/i';
99
+ $this->whitelistedParams['request.queryString[s]'][] = '/\\/wp-admin\\/network\\/(?:user|site)s\\.php$/i';
100
+ $this->whitelistedParams['request.body[blog]'][] = '/\\/wp-admin\\/network\\/site-new\\.php$/i';
101
+ $this->whitelistedParams['request.body[deletedWhitelistedPath]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
102
+ $this->whitelistedParams['request.body[deletedWhitelistedParam]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
103
+ $this->whitelistedParams['request.body[itsec_global][log_location]'][] = '/\\/wp-admin\\/options\\.php$/i';
104
+ $this->whitelistedParams['request.body[itsec_backup][location]'][] = '/\\/wp-admin\\/options\\.php$/i';
105
+ $this->whitelistedParams['request.body[dir]'][] = '/\\/wp-admin\\/admin-ajax\\.php$/i';
106
+ $this->whitelistedParams['request.body[sql_query]'][] = '/(?:lint|import)\\.php$/i';
107
+
108
+ $this->rules[18] = wfWAFRule::create($this, 18, NULL, 'priv-esc', NULL, 'User Roles Manager Priviledge Escalation <= 4.24', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'notEquals', '', array(wfWAFRuleComparisonSubject::create($this, array (
109
+ 0 => 'request.body',
110
+ 1 => 'ure_other_roles',
111
+ ), array (
112
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparison($this, 'match', '#/wp\\-admin/(network/)?(profile|user-new)\\.php#i', array(wfWAFRuleComparisonSubject::create($this, 'request.path', array (
113
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparison($this, 'currentUserIsNot', 'administrator', array(wfWAFRuleComparisonSubject::create($this, 'server.empty', array (
114
+ ))))));
115
+ $this->rules[1] = wfWAFRule::create($this, 1, NULL, 'whitelist', NULL, 'Whitelisted URL', 'allow', new wfWAFRuleComparisonGroup(new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '#/wp\\-admin/(network/)?(post|profile|user-new|settings)\\.php$#i', array(wfWAFRuleComparisonSubject::create($this, 'server.script_filename', array (
116
+ ))))), new wfWAFRuleLogicalOperator('OR'), new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '#/wp\\-admin/admin\\-ajax\\.php$#i', array(wfWAFRuleComparisonSubject::create($this, 'server.script_filename', array (
117
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'equals', 'wordfence_loadLiveTraffic', array(wfWAFRuleComparisonSubject::create($this, array (
118
+ 0 => 'request.body',
119
+ 1 => 'action',
120
+ ), array (
121
+ )))), new wfWAFRuleLogicalOperator('OR'), new wfWAFRuleComparison($this, 'equals', 'wordfence_ticker', array(wfWAFRuleComparisonSubject::create($this, array (
122
+ 0 => 'request.body',
123
+ 1 => 'action',
124
+ ), array (
125
+ ))))))));
126
+ $this->rules[2] = wfWAFRule::create($this, 2, NULL, 'lfi', NULL, 'Slider Revolution: Local File Inclusion', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/\\/wp\\-admin[\\/]+admin\\-ajax\\.php/', array(wfWAFRuleComparisonSubject::create($this, 'request.path', array (
127
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparisonGroup(new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'equals', 'revslider_show_image', array(wfWAFRuleComparisonSubject::create($this, array (
128
+ 0 => 'request.queryString',
129
+ 1 => 'action',
130
+ ), array (
131
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparison($this, 'match', '/\\.php$/i', array(wfWAFRuleComparisonSubject::create($this, array (
132
+ 0 => 'request.queryString',
133
+ 1 => 'img',
134
+ ), array (
135
+ ))))), new wfWAFRuleLogicalOperator('OR'), new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'equals', 'revslider_show_image', array(wfWAFRuleComparisonSubject::create($this, array (
136
+ 0 => 'request.body',
137
+ 1 => 'action',
138
+ ), array (
139
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparison($this, 'match', '/\\.php$/i', array(wfWAFRuleComparisonSubject::create($this, array (
140
+ 0 => 'request.body',
141
+ 1 => 'img',
142
+ ), array (
143
+ ))))))));
144
+ $this->rules[15] = wfWAFRule::create($this, 15, NULL, 'xss', NULL, 'dzs-videogallery 8.80 XSS HTML injection in inline JavaScript', 'blockXSS', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/dzs\\-videogallery[\\/]+admin[\\/]+(?:playlist|tag)seditor[\\/]+popup\\.php/', array(wfWAFRuleComparisonSubject::create($this, 'request.path', array (
145
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparison($this, 'contains', '\'', array(wfWAFRuleComparisonSubject::create($this, array (
146
+ 0 => 'request.queryString',
147
+ 1 => 'initer',
148
+ ), array (
149
+ ))))));
150
+ $this->rules[16] = wfWAFRule::create($this, 16, NULL, 'sqli', NULL, 'Simple Ads Manager <= 2.9.4.116 - SQL Injection', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/simple-ads-manager[\\/]+sam-ajax-loader\\.php/', array(wfWAFRuleComparisonSubject::create($this, 'request.path', array (
151
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparison($this, 'match', new wfWAFRuleVariable($this, 'sqliRegex', NULL), array(wfWAFRuleComparisonSubject::create($this, array (
152
+ 0 => 'request.body',
153
+ 1 => 'wc',
154
+ ), array (
155
+ 0 => 'base64decode',
156
+ ))))));
157
+ $this->rules[17] = wfWAFRule::create($this, 17, NULL, 'rfi', NULL, 'Gwolle Guestbook <= 1.5.3 - Remote File Inclusion', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/gwolle\\-gb[\\/]+frontend[\\/]+captcha[\\/]+ajaxresponse\\.php/', array(wfWAFRuleComparisonSubject::create($this, 'request.path', array (
158
+ )))), new wfWAFRuleLogicalOperator('AND'), new wfWAFRuleComparison($this, 'match', '/.*/', array(wfWAFRuleComparisonSubject::create($this, array (
159
+ 0 => 'request.queryString',
160
+ 1 => 'abspath',
161
+ ), array (
162
+ ))))));
163
+ $this->rules[3] = wfWAFRule::create($this, 3, NULL, 'sqli', '40', 'SQL Injection', 'failSQLi', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'matchCount', new wfWAFRuleVariable($this, 'sqliRegex', NULL), array(wfWAFRuleComparisonSubject::create($this, 'request.body', array (
164
+ )),
165
+ wfWAFRuleComparisonSubject::create($this, 'request.queryString', array (
166
+ ))))));
167
+ $this->rules[9] = wfWAFRule::create($this, 9, NULL, 'xss', '100', 'XSS: Cross Site Scripting', 'failXSS', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'matchCount', new wfWAFRuleVariable($this, 'xssRegex', NULL), array(wfWAFRuleComparisonSubject::create($this, 'request.body', array (
168
+ )),
169
+ wfWAFRuleComparisonSubject::create($this, 'request.queryString', array (
170
+ ))))));
171
+ $this->rules[11] = wfWAFRule::create($this, 11, NULL, 'file_upload', NULL, 'Malicous File Upload', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/\\.(p(h(p|tml)[0-9]?|l|y)|(j|a)sp|aspx|sh|shtml|html?|cgi|htaccess)($|\\.)/i', array(wfWAFRuleComparisonSubject::create($this, 'request.fileNames', array (
172
+ ))))));
173
+ $this->rules[12] = wfWAFRule::create($this, 12, NULL, 'lfi', NULL, 'Directory Traversal', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/(^|\\/|\\\\)\\.\\.(\\\\|\\/)/', array(wfWAFRuleComparisonSubject::create($this, 'request.body', array (
174
+ )),
175
+ wfWAFRuleComparisonSubject::create($this, 'request.queryString', array (
176
+ ))))));
177
+ $this->rules[13] = wfWAFRule::create($this, 13, NULL, 'lfi', NULL, 'LFI: Local File Inclusion', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/^\\/(?:\\.\\/)*(?:var|home|usr|mnt|media|etc|tmp|dev|proc)\\//i', array(wfWAFRuleComparisonSubject::create($this, 'request.body', array (
178
+ )),
179
+ wfWAFRuleComparisonSubject::create($this, 'request.queryString', array (
180
+ ))))));
181
+ $this->rules[14] = wfWAFRule::create($this, 14, NULL, 'xxe', NULL, 'XXE: External Entity Expansion', 'block', new wfWAFRuleComparisonGroup(new wfWAFRuleComparison($this, 'match', '/<\\!(?:DOCTYPE|ENTITY)\\s+(?:%\\s*)?\\w+\\s+SYSTEM/i', array(wfWAFRuleComparisonSubject::create($this, 'request.body', array (
182
+ )),
183
+ wfWAFRuleComparisonSubject::create($this, 'request.queryString', array (
184
+ ))))));
185
+ ?>
vendor/wordfence/wf-waf/src/views/403-roadblock.php ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @var wfWAF $waf */
4
+ /** @var wfWAFView $this */
5
+
6
+ $method = strtolower($waf->getRequest()->getMethod());
7
+ $urlParamsToWhitelist = array();
8
+ foreach ($waf->getFailedRules() as $paramKey => $categories) {
9
+ foreach ($categories as $category => $failedRules) {
10
+ foreach ($failedRules as $failedRule) {
11
+ /**
12
+ * @var wfWAFRule $rule
13
+ * @var wfWAFRuleComparisonFailure $failedComparison
14
+ */
15
+ $rule = $failedRule['rule'];
16
+ $failedComparison = $failedRule['failedComparison'];
17
+
18
+ $urlParamsToWhitelist[] = array(
19
+ 'path' => $waf->getRequest()->getPath(),
20
+ 'paramKey' => $failedComparison->getParamKey(),
21
+ 'ruleID' => $rule->getRuleID(),
22
+ );
23
+ }
24
+ }
25
+ }
26
+
27
+
28
+ ?>
29
+ <!DOCTYPE html>
30
+ <html>
31
+ <head>
32
+ <meta charset="UTF-8">
33
+ <title>403 Forbidden</title>
34
+ </head>
35
+ <body>
36
+
37
+ <h1>403 Forbidden</h1>
38
+
39
+ <p>A potentially unsafe operation has been detected in your request to this site, and has been blocked by Wordfence.</p>
40
+
41
+ <?php if ($urlParamsToWhitelist): ?>
42
+ <p>If you are an administrator and you are certain this is a false positive, you can automatically whitelist this
43
+ request and repeat the same action.</p>
44
+
45
+ <form id="whitelist-form" action="<?php echo htmlentities($waf->getRequest()->getPath(), ENT_QUOTES, 'utf-8') ?>"
46
+ method="post">
47
+ <input type="hidden" name="wfwaf-false-positive-params"
48
+ value="<?php echo htmlentities(json_encode($urlParamsToWhitelist), ENT_QUOTES, 'utf-8') ?>">
49
+ <input type="hidden" name="wfwaf-false-positive-nonce"
50
+ value="<?php echo htmlentities($waf->getAuthCookieValue('nonce', ''), ENT_QUOTES, 'utf-8') ?>">
51
+
52
+ <div id="whitelist-actions">
53
+ <p>
54
+ <label>
55
+ <input id="verified-false-positive-checkbox" type="checkbox" name="wfwaf-false-positive-verified"
56
+ value="1">
57
+ <em>I am certain this is a false positive.</em>
58
+ </label>
59
+ </p>
60
+
61
+ <p>
62
+ <button id="whitelist-button" type="submit">Whitelist This Action</button>
63
+ </p>
64
+ </div>
65
+
66
+ <p id="success" style="color: #35b13a; font-weight: bold; display: none"><em>All set! You can refresh the page
67
+ to try this action again.</em></p>
68
+
69
+ <p id="error" style="color: #dd422c; font-weight: bold; display: none"><em>Something went wrong whitelisting
70
+ this request. You can try setting the Firewall Status to Learning Mode under Web App Firewall in the
71
+ Wordfence menu, and retry this same action.</em></p>
72
+ </form>
73
+ <script>
74
+ var whitelistButton = document.getElementById('whitelist-button');
75
+ var verified = document.getElementById('verified-false-positive-checkbox');
76
+ verified.checked = false;
77
+ verified.onclick = function() {
78
+ whitelistButton.disabled = !this.checked;
79
+ };
80
+ verified.onclick();
81
+
82
+ document.getElementById('whitelist-form').onsubmit = function(evt) {
83
+ evt.preventDefault();
84
+ var request = new XMLHttpRequest();
85
+ request.addEventListener("load", function() {
86
+ if (this.status === 200 && this.responseText.indexOf('Successfully whitelisted') > -1) {
87
+ document.getElementById('whitelist-actions').style.display = 'none';
88
+ document.getElementById('success').style.display = 'block';
89
+ } else {
90
+ document.getElementById('error').style.display = 'block';
91
+ }
92
+ });
93
+ request.open("POST", this.action, true);
94
+ request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
95
+ var inputs = this.querySelectorAll('input[name]');
96
+ var data = '';
97
+ for (var i = 0; i < inputs.length; i++) {
98
+ data += encodeURIComponent(inputs[i].name) + '=' + encodeURIComponent(inputs[i].value) + '&';
99
+ }
100
+ request.send(data);
101
+ return false;
102
+ };
103
+
104
+
105
+ </script>
106
+
107
+ <?php endif ?>
108
+
109
+ </body>
110
+ </html>
vendor/wordfence/wf-waf/src/views/403.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>403 Forbidden</title>
6
+ </head>
7
+ <body>
8
+
9
+ <h1>403 Forbidden</h1>
10
+
11
+ <p>A potentially unsafe operation has been detected in your request to this site.</p>
12
+
13
+ </body>
14
+ </html>
views/waf/debug.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @var wfRequestModel $hit */
4
+ /** @var stdClass $hitData */
5
+
6
+ $title = sprintf('Debugging #%d as False Positive', $hit->id);
7
+
8
+ $fields = array(
9
+ 'URL' => $hit->URL,
10
+ 'Timestamp' => date('r', $hit->ctime),
11
+ 'IP' => wfUtils::inet_ntop($hit->IP),
12
+ 'Status Code' => $hit->statusCode,
13
+ 'User Agent' => $hit->UA,
14
+ 'Referer' => $hit->referer,
15
+ );
16
+
17
+ if (isset($hitData->fullRequest)) {
18
+ $requestString = base64_decode($hitData->fullRequest);
19
+ $request = wfWAFRequest::parseString($requestString);
20
+ } else {
21
+ $request = new wfWAFRequest();
22
+ $request->setAuth(array());
23
+ $request->setBody(array());
24
+ $request->setCookies(array());
25
+ $request->setFileNames(array());
26
+ $request->setFiles(array());
27
+ $request->setHeaders(array());
28
+ $request->setHost('');
29
+ $request->setIp('');
30
+ $request->setMethod('GET');
31
+ $request->setPath('');
32
+ $request->setProtocol('http');
33
+ $request->setQueryString(array());
34
+ $request->setTimestamp('');
35
+ $request->setUri('');
36
+
37
+ $headers = array();
38
+ $urlPieces = parse_url($hit->URL);
39
+ if ($urlPieces) {
40
+ if (array_key_exists('scheme', $urlPieces)) {
41
+ $request->setProtocol($urlPieces['scheme']);
42
+ }
43
+ if (array_key_exists('host', $urlPieces)) {
44
+ $request->setHost($urlPieces['host']);
45
+ $headers['Host'] = $urlPieces['host'];
46
+ }
47
+ $uri = '/';
48
+ if (array_key_exists('path', $urlPieces)) {
49
+ $request->setPath($urlPieces['path']);
50
+ $uri = $urlPieces['path'];
51
+ }
52
+ if (array_key_exists('query', $urlPieces)) {
53
+ $uri .= '?' . $urlPieces['query'];
54
+ parse_str($urlPieces['query'], $query);
55
+ $request->setQueryString($query);
56
+ }
57
+ $request->setUri($uri);
58
+ }
59
+ $headers['User-Agent'] = $hit->UA;
60
+ $headers['Referer'] = $hit->referer;
61
+ $request->setHeaders($headers);
62
+
63
+ preg_match('/request\.([a-z]+)(?:\[(.*?)\](.*?))?/i', $hitData->paramKey, $matches);
64
+ if ($matches) {
65
+ switch ($matches[1]) {
66
+ case 'body':
67
+ $request->setMethod('POST');
68
+ parse_str("$matches[2]$matches[3]", $body);
69
+ $request->setBody($body);
70
+ break;
71
+ }
72
+ }
73
+ }
74
+
75
+ $request->setIP(wfUtils::inet_ntop($hit->IP));
76
+ $request->setTimestamp($hit->ctime);
77
+
78
+
79
+ $waf = wfWAF::getInstance();
80
+ $waf->setRequest($request);
81
+
82
+ $result = '<strong class="ok">Passed</strong>';
83
+ $failedRules = array();
84
+ try {
85
+ $waf->runRules();
86
+ } catch (wfWAFAllowException $e) {
87
+ $result = '<strong class="ok">Whitelisted</strong>';
88
+ } catch (wfWAFBlockException $e) {
89
+ $result = '<strong class="error">Blocked</strong>';
90
+ $failedRules = $waf->getFailedRules();
91
+ } catch (wfWAFBlockSQLiException $e) {
92
+ $result = '<strong class="error">Blocked For SQLi</strong>';
93
+ $failedRules = $waf->getFailedRules();
94
+ } catch (wfWAFBlockXSSException $e) {
95
+ $result = '<strong class="error">Blocked For XSS</strong>';
96
+ $failedRules = $waf->getFailedRules();
97
+ }
98
+
99
+ ?>
100
+ <!doctype html>
101
+ <html lang="en">
102
+ <head>
103
+ <meta charset="UTF-8">
104
+ <title><?php echo esc_html($title) ?></title>
105
+ <link rel="stylesheet" href="<?php echo wfUtils::getBaseURL() . 'css/main.css' ?>">
106
+ <style>
107
+ html {
108
+ font-family: "Open Sans", Helvetica, Arial, sans-serif;
109
+ }
110
+ h1, h2, h3, h4, h5 {
111
+ margin: 20px 0px 8px;
112
+ }
113
+ pre, p {
114
+ 8px 0px 20px;
115
+ }
116
+ pre.request-debug {
117
+ padding: 12px;
118
+ background: #fafafa;
119
+ border: 1px solid #999999;
120
+ overflow: auto;
121
+ }
122
+ pre.request-debug em {
123
+ font-style: normal;
124
+ padding: 1px;
125
+ border: 1px solid #ffb463;
126
+ background-color: #ffffe0;
127
+ border-radius: 2px;
128
+ }
129
+ pre.request-debug strong {
130
+ border: 1px solid #ff4a35;
131
+ background-color: #ffefe7;
132
+ margin: 1px;
133
+ }
134
+ .ok {
135
+ color: #00c000;
136
+ }
137
+ .error {
138
+ color: #ff4a35;
139
+ }
140
+ #wrapper {
141
+ max-width: 1060px;
142
+ margin: 0px auto;
143
+ }
144
+ </style>
145
+ </head>
146
+ <body>
147
+ <div id="wrapper">
148
+ <h1><?php echo esc_html($title) ?></h1>
149
+
150
+ <table class="wf-table">
151
+ <thead>
152
+ <tr>
153
+ <th colspan="2">Request Details</th>
154
+ </tr>
155
+ </thead>
156
+ <?php foreach ($fields as $label => $value): ?>
157
+ <tr>
158
+ <td><?php echo esc_html($label) ?>:</td>
159
+ <td><?php echo esc_html($value) ?></td>
160
+ </tr>
161
+ <?php endforeach ?>
162
+ </table>
163
+
164
+ <h4>HTTP Request: <?php echo $result ?></h4>
165
+ <?php if (!isset($hitData->fullRequest)): ?>
166
+ <em style="font-size: 14px;">This is a reconstruction of the request using what was flagged by the WAF.
167
+ Full requests are only stored when <code>WFWAF_DEBUG</code> is enabled.</em>
168
+ <?php endif ?>
169
+ <pre class="request-debug"><?php
170
+ $paramKey = wp_hash(uniqid('param', true));
171
+ $matchKey = wp_hash(uniqid('match', true));
172
+
173
+ $template = array(
174
+ "[$paramKey]" => '<em>',
175
+ "[/$paramKey]" => '</em>',
176
+ "[$matchKey]" => '<strong>',
177
+ "[/$matchKey]" => '</strong>',
178
+ );
179
+ $highlightParamFormat = "[$paramKey]%s[/$paramKey]";
180
+ $highlightMatchFormat = "[$matchKey]%s[/$matchKey]";
181
+ $requestOut = esc_html($request->highlightFailedParams($failedRules, $highlightParamFormat, $highlightMatchFormat));
182
+
183
+ echo str_replace(array_keys($template), $template, $requestOut) ?></pre>
184
+
185
+ <?php if ($failedRules): ?>
186
+ <h4>Failed Rules</h4>
187
+ <table class="wf-table">
188
+ <thead>
189
+ <tr>
190
+ <th>ID</th>
191
+ <th>Category</th>
192
+ </tr>
193
+ </thead>
194
+ <tbody>
195
+ <?php
196
+ foreach ($failedRules as $paramKey => $categories) {
197
+ foreach ($categories as $categoryKey => $failed) {
198
+ foreach ($failed as $failedRule) {
199
+ /** @var wfWAFRule $rule */
200
+ $rule = $failedRule['rule'];
201
+ printf("<tr><td>%d</td><td>%s</td></tr>", $rule->getRuleID(), $rule->getDescription());
202
+ }
203
+ }
204
+ }
205
+ ?>
206
+ </tbody>
207
+ </table>
208
+
209
+ <?php endif ?>
210
+
211
+ <p>
212
+ <button type="button" id="run-waf-rules">Run Through WAF Rules</button>
213
+ </p>
214
+
215
+ <script>
216
+ document.getElementById('run-waf-rules').onclick = function() {
217
+ document.location.href = document.location.href;
218
+ }
219
+ </script>
220
+
221
+
222
+ </div>
223
+
224
+ </body>
225
+ </html>
waf/bootstrap.php ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ php_value auto_prepend_file ~/wp-content/plugins/wordfence/waf/bootstrap.php
5
+ */
6
+
7
+ if (!defined('WFWAF_AUTO_PREPEND')) {
8
+ define('WFWAF_AUTO_PREPEND', true);
9
+ }
10
+
11
+ require_once dirname(__FILE__) . '/../vendor/wordfence/wf-waf/src/init.php';
12
+
13
+ class wfWAFWordPressRequest extends wfWAFRequest {
14
+
15
+ /**
16
+ * @param wfWAFRequest|null $request
17
+ * @return wfWAFRequest
18
+ */
19
+ public static function createFromGlobals($request = null) {
20
+ if (version_compare(phpversion(), '5.3.0') >= 0) {
21
+ $class = get_called_class();
22
+ $request = new $class();
23
+ } else {
24
+ $request = new self();
25
+ }
26
+ return parent::createFromGlobals($request);
27
+ }
28
+
29
+ public function getIP() {
30
+ $howGet = wfWAF::getInstance()->getStorageEngine()->getConfig('howGetIPs');
31
+ if (is_string($howGet) && array_key_exists($howGet, $_SERVER)) {
32
+ $ips[] = $_SERVER[$howGet];
33
+ }
34
+ $ips[] = $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
35
+ foreach ($ips as $ip) {
36
+ if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
37
+ return $ip;
38
+ }
39
+ }
40
+ return $ip;
41
+ }
42
+ }
43
+
44
+ class wfWAFWordPressObserver extends wfWAFBaseObserver {
45
+
46
+ public function beforeRunRules() {
47
+ // Whitelisted URLs (in WAF config)
48
+ $whitelistedURLs = wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLs');
49
+ if ($whitelistedURLs) {
50
+ $whitelistPattern = "";
51
+ foreach ($whitelistedURLs as $whitelistedURL) {
52
+ $whitelistPattern .= preg_replace('/\\\\\*/', '.*?', preg_quote($whitelistedURL, '/')) . '|';
53
+ }
54
+ $whitelistPattern = '/^(?:' . substr($whitelistPattern, 0, -1) . ')$/i';
55
+
56
+ wfWAFRule::create(wfWAF::getInstance(), 0x8000000, 'rule', 'whitelist', 0, 'User Supplied Whitelisted URL', 'allow',
57
+ new wfWAFRuleComparisonGroup(
58
+ new wfWAFRuleComparison(wfWAF::getInstance(), 'match', $whitelistPattern, array(
59
+ 'request.uri',
60
+ ))
61
+ )
62
+ )->evaluate();
63
+ }
64
+
65
+ // Whitelisted IPs (Wordfence config)
66
+ $whitelistedIPs = wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedIPs');
67
+ if ($whitelistedIPs) {
68
+ require_once dirname(__FILE__) . '/wfWAFUserIPRange.php';
69
+ if (!is_array($whitelistedIPs)) {
70
+ $whitelistedIPs = explode(',', $whitelistedIPs);
71
+ }
72
+ foreach ($whitelistedIPs as $whitelistedIP) {
73
+ $ipRange = new wfWAFUserIPRange($whitelistedIP);
74
+ if ($ipRange->isIPInRange(wfWAF::getInstance()->getRequest()->getIP())) {
75
+ throw new wfWAFAllowException('Wordfence whitelisted IP.');
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ *
84
+ */
85
+ class wfWAFWordPress extends wfWAF {
86
+
87
+ /** @var wfWAFRunException */
88
+ private $learningModeAttackException;
89
+
90
+ /**
91
+ * @param wfWAFBlockException $e
92
+ * @param int $httpCode
93
+ */
94
+ public function blockAction($e, $httpCode = 403) {
95
+ if ($this->isInLearningMode()) {
96
+ register_shutdown_function(array(
97
+ $this, 'whitelistFailedRules',
98
+ ));
99
+ $this->getStorageEngine()->logAttack($e->getFailedRules(), $e->getParamKey(), $e->getParamValue(), $e->getRequest());
100
+ $this->setLearningModeAttackException($e);
101
+ } else {
102
+ parent::blockAction($e, $httpCode);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * @param wfWAFBlockXSSException $e
108
+ * @param int $httpCode
109
+ */
110
+ public function blockXSSAction($e, $httpCode = 403) {
111
+ if ($this->isInLearningMode()) {
112
+ register_shutdown_function(array(
113
+ $this, 'whitelistFailedRules',
114
+ ));
115
+ $this->getStorageEngine()->logAttack($e->getFailedRules(), $e->getParamKey(), $e->getParamValue(), $e->getRequest());
116
+ $this->setLearningModeAttackException($e);
117
+ } else {
118
+ parent::blockXSSAction($e, $httpCode);
119
+ }
120
+ }
121
+
122
+ /**
123
+ *
124
+ */
125
+ public function runCron() {
126
+ /**
127
+ * Removed sending attack data. Attack data is sent in @see wordfence::veryFirstAction
128
+ */
129
+ $cron = $this->getStorageEngine()->getConfig('cron');
130
+ if (is_array($cron)) {
131
+ /** @var wfWAFCronEvent $event */
132
+ foreach ($cron as $index => $event) {
133
+ $event->setWaf($this);
134
+ if ($event->isInPast()) {
135
+ $event->fire();
136
+ $newEvent = $event->reschedule();
137
+ if ($newEvent instanceof wfWAFCronEvent && $newEvent !== $event) {
138
+ $cron[$index] = $newEvent;
139
+ } else {
140
+ unset($cron[$index]);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ $this->getStorageEngine()->setConfig('cron', $cron);
146
+ }
147
+
148
+
149
+ /**
150
+ * @param $ip
151
+ * @return mixed
152
+ */
153
+ public function isIPBlocked($ip) {
154
+ return false;
155
+ }
156
+
157
+ /**
158
+ * @return wfWAFRunException
159
+ */
160
+ public function getLearningModeAttackException() {
161
+ return $this->learningModeAttackException;
162
+ }
163
+
164
+ /**
165
+ * @param wfWAFRunException $learningModeAttackException
166
+ */
167
+ public function setLearningModeAttackException($learningModeAttackException) {
168
+ $this->learningModeAttackException = $learningModeAttackException;
169
+ }
170
+ }
171
+
172
+ if (!defined('WFWAF_LOG_PATH')) {
173
+ define('WFWAF_LOG_PATH', WP_CONTENT_DIR . '/wflogs/');
174
+ }
175
+ if (!is_dir(WFWAF_LOG_PATH)) {
176
+ @mkdir(WFWAF_LOG_PATH, 0755);
177
+ @file_put_contents(rtrim(WFWAF_LOG_PATH . '/') . '/.htaccess', <<<APACHE
178
+ <IfModule mod_authz_core.c>
179
+ Require all denied
180
+ </IfModule>
181
+ <IfModule !mod_authz_core.c>
182
+ Order deny,allow
183
+ Deny from all
184
+ </IfModule>
185
+ APACHE
186
+ );
187
+ }
188
+
189
+
190
+ wfWAF::setInstance(new wfWAFWordPress(
191
+ wfWAFWordPressRequest::createFromGlobals(),
192
+ new wfWAFStorageFile(WFWAF_LOG_PATH . 'attack-data.php', WFWAF_LOG_PATH . 'ips.php', WFWAF_LOG_PATH . 'config.php', WFWAF_LOG_PATH . 'wafRules.rules')
193
+ ));
194
+ wfWAF::getInstance()->getEventBus()->attach(new wfWAFWordPressObserver);
195
+
196
+ try {
197
+ $rulesFiles = array(
198
+ WFWAF_LOG_PATH . 'rules.php',
199
+ // WFWAF_PATH . 'rules.php',
200
+ );
201
+ foreach ($rulesFiles as $rulesFile) {
202
+ if (!file_exists($rulesFile)) {
203
+ @touch($rulesFile);
204
+ }
205
+ if (is_writable($rulesFile)) {
206
+ wfWAF::getInstance()->setCompiledRulesFile($rulesFile);
207
+ break;
208
+ }
209
+ }
210
+
211
+ if (!file_exists(wfWAF::getInstance()->getCompiledRulesFile()) || !filesize(wfWAF::getInstance()->getCompiledRulesFile())) {
212
+ try {
213
+ wfWAF::getInstance()->updateRuleSet(file_get_contents(WFWAF_PATH . 'baseRules.rules'), false);
214
+ } catch (wfWAFBuildRulesException $e) {
215
+ // Log this somewhere
216
+ error_log($e->getMessage());
217
+ } catch (Exception $e) {
218
+ // Suppress this
219
+ error_log($e->getMessage());
220
+ }
221
+ }
222
+
223
+ if (WFWAF_DEBUG && file_exists(wfWAF::getInstance()->getStorageEngine()->getRulesDSLCacheFile())) {
224
+ try {
225
+ wfWAF::getInstance()->updateRuleSet(file_get_contents(wfWAF::getInstance()->getStorageEngine()->getRulesDSLCacheFile()), false);
226
+ } catch (wfWAFBuildRulesException $e) {
227
+ $GLOBALS['wfWAFDebugBuildException'] = $e;
228
+ } catch (Exception $e) {
229
+ $GLOBALS['wfWAFDebugBuildException'] = $e;
230
+ }
231
+ }
232
+
233
+ try {
234
+ wfWAF::getInstance()->run();
235
+ } catch (wfWAFBuildRulesException $e) {
236
+ // Log this
237
+ error_log($e->getMessage());
238
+ } catch (Exception $e) {
239
+ // Suppress this
240
+ error_log($e->getMessage());
241
+ }
242
+
243
+ } catch (wfWAFStorageFileConfigException $e) {
244
+ // Let this request through for now
245
+ error_log($e->getMessage());
246
+
247
+ } catch (wfWAFStorageFileException $e) {
248
+ // We need to choose another storage engine here.
249
+ }
waf/wfWAFUserIPRange.php ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ *
5
+ */
6
+ class wfWAFUserIPRange {
7
+
8
+ /**
9
+ * @var string|null
10
+ */
11
+ private $ip_string;
12
+
13
+ /**
14
+ * @param string|null $ip_string
15
+ */
16
+ public function __construct($ip_string = null) {
17
+ $this->setIPString($ip_string);
18
+ }
19
+
20
+ /**
21
+ * Check if the supplied IP address is within the user supplied range.
22
+ *
23
+ * @param string $ip
24
+ * @return bool
25
+ */
26
+ public function isIPInRange($ip) {
27
+ $ip_string = $this->getIPString();
28
+
29
+ // IPv4 range
30
+ if (strpos($ip_string, '.') !== false && strpos($ip, '.') !== false) {
31
+ if (preg_match('/\[\d+\-\d+\]/', $ip_string)) {
32
+ $IPparts = explode('.', $ip);
33
+ $whiteParts = explode('.', $ip_string);
34
+ $mismatch = false;
35
+ for ($i = 0; $i <= 3; $i++) {
36
+ if (preg_match('/^\[(\d+)\-(\d+)\]$/', $whiteParts[$i], $m)) {
37
+ if ($IPparts[$i] < $m[1] || $IPparts[$i] > $m[2]) {
38
+ $mismatch = true;
39
+ }
40
+ } else if ($whiteParts[$i] != $IPparts[$i]) {
41
+ $mismatch = true;
42
+ }
43
+ }
44
+ if ($mismatch === false) {
45
+ return true; // Is whitelisted because we did not get a mismatch
46
+ }
47
+ } else if ($ip_string == $ip) {
48
+ return true;
49
+ }
50
+
51
+ // IPv6 range
52
+ } else if (strpos($ip_string, ':') !== false && strpos($ip, ':') !== false) {
53
+ if (preg_match('/\[[a-f0-9]+\-[a-f0-9]+\]/', $ip_string)) {
54
+ $IPparts = explode(':', strtolower(wfUtils::expandIPv6Address($ip)));
55
+ $whiteParts = explode(':', strtolower(self::expandIPv6Range($ip_string)));
56
+ $mismatch = false;
57
+ for ($i = 0; $i <= 7; $i++) {
58
+ if (preg_match('/^\[([a-f0-9]+)\-([a-f0-9]+)\]$/i', $whiteParts[$i], $m)) {
59
+ $ip_group = hexdec($IPparts[$i]);
60
+ $range_group_from = hexdec($m[1]);
61
+ $range_group_to = hexdec($m[2]);
62
+ if ($ip_group < $range_group_from || $ip_group > $range_group_to) {
63
+ $mismatch = true;
64
+ break;
65
+ }
66
+ } else if ($whiteParts[$i] != $IPparts[$i]) {
67
+ $mismatch = true;
68
+ break;
69
+ }
70
+ }
71
+ if ($mismatch === false) {
72
+ return true; // Is whitelisted because we did not get a mismatch
73
+ }
74
+ } else if ($ip_string == $ip) {
75
+ return true;
76
+ }
77
+ }
78
+
79
+ return false;
80
+ }
81
+
82
+ /**
83
+ * Return a set of where clauses to use in MySQL.
84
+ *
85
+ * @param string $column
86
+ * @return false|null|string
87
+ */
88
+ public function toSQL($column = 'ip') {
89
+ /** @var wpdb $wpdb */
90
+ global $wpdb;
91
+ $ip_string = $this->getIPString();
92
+
93
+ if (strpos($ip_string, '.') !== false && preg_match('/\[\d+\-\d+\]/', $ip_string)) {
94
+ $whiteParts = explode('.', $ip_string);
95
+ $sql = "(SUBSTR($column, 1, 12) = LPAD(CHAR(0xff, 0xff), 12, CHAR(0)) AND ";
96
+
97
+ for ($i = 0, $j = 24; $i <= 3; $i++, $j -= 8) {
98
+ // MySQL can only perform bitwise operations on integers
99
+ $conv = sprintf('CAST(CONV(HEX(SUBSTR(%s, 13, 8)), 16, 10) as UNSIGNED INTEGER)', $column);
100
+ if (preg_match('/^\[(\d+)\-(\d+)\]$/', $whiteParts[$i], $m)) {
101
+ $sql .= $wpdb->prepare("$conv >> $j & 0xFF BETWEEN %d AND %d", $m[1], $m[2]);
102
+ } else {
103
+ $sql .= $wpdb->prepare("$conv >> $j & 0xFF = %d", $whiteParts[$i]);
104
+ }
105
+ $sql .= ' AND ';
106
+ }
107
+ $sql = substr($sql, 0, -5) . ')';
108
+ return $sql;
109
+
110
+ } else if (strpos($ip_string, ':') !== false && preg_match('/\[[a-f0-9]+\-[a-f0-9]+\]/', $ip_string)) {
111
+ $whiteParts = explode(':', strtolower(self::expandIPv6Range($ip_string)));
112
+ $sql = '(';
113
+
114
+ for ($i = 0; $i <= 7; $i++) {
115
+ // MySQL can only perform bitwise operations on integers
116
+ $conv = sprintf('CAST(CONV(HEX(SUBSTR(%s, %d, 8)), 16, 10) as UNSIGNED INTEGER)', $column, $i < 4 ? 1 : 9);
117
+ $j = 16 * (3 - ($i % 4));
118
+ if (preg_match('/^\[([a-f0-9]+)\-([a-f0-9]+)\]$/', $whiteParts[$i], $m)) {
119
+ $sql .= $wpdb->prepare("$conv >> $j & 0xFFFF BETWEEN 0x%x AND 0x%x", hexdec($m[1]), hexdec($m[2]));
120
+ } else {
121
+ $sql .= $wpdb->prepare("$conv >> $j & 0xFFFF = 0x%x", hexdec($whiteParts[$i]));
122
+ }
123
+ $sql .= ' AND ';
124
+ }
125
+ $sql = substr($sql, 0, -5) . ')';
126
+ return $sql;
127
+ }
128
+ return $wpdb->prepare("($column = %s)", wfUtils::inet_pton($ip_string));
129
+ }
130
+
131
+ /**
132
+ * Expand a compressed printable range representation of an IPv6 address.
133
+ *
134
+ * @todo Hook up exceptions for better error handling.
135
+ * @todo Allow IPv4 mapped IPv6 addresses (::ffff:192.168.1.1).
136
+ * @param string $ip_range
137
+ * @return string
138
+ */
139
+ public static function expandIPv6Range($ip_range) {
140
+ $colon_count = substr_count($ip_range, ':');
141
+ $dbl_colon_count = substr_count($ip_range, '::');
142
+ if ($dbl_colon_count > 1) {
143
+ return false;
144
+ }
145
+ $dbl_colon_pos = strpos($ip_range, '::');
146
+ if ($dbl_colon_pos !== false) {
147
+ $ip_range = str_replace('::', str_repeat(':0000',
148
+ (($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip_range) - 2) ? 9 : 8) - $colon_count) . ':', $ip_range);
149
+ $ip_range = trim($ip_range, ':');
150
+ }
151
+ $colon_count = substr_count($ip_range, ':');
152
+ if ($colon_count != 7) {
153
+ return false;
154
+ }
155
+
156
+ $groups = explode(':', $ip_range);
157
+ $expanded = '';
158
+ foreach ($groups as $group) {
159
+ if (preg_match('/\[([a-f0-9]{1,4})\-([a-f0-9]{1,4})\]/i', $group, $matches)) {
160
+ $expanded .= sprintf('[%s-%s]', str_pad(strtolower($matches[1]), 4, '0', STR_PAD_LEFT), str_pad(strtolower($matches[2]), 4, '0', STR_PAD_LEFT)) . ':';
161
+ } else if (preg_match('/[a-f0-9]{1,4}/i', $group)) {
162
+ $expanded .= str_pad(strtolower($group), 4, '0', STR_PAD_LEFT) . ':';
163
+ } else {
164
+ return false;
165
+ }
166
+ }
167
+ return trim($expanded, ':');
168
+ }
169
+
170
+ /**
171
+ * @return bool
172
+ */
173
+ public function isValidRange() {
174
+ return $this->isValidIPv4Range() || $this->isValidIPv6Range();
175
+ }
176
+
177
+ /**
178
+ * @return bool
179
+ */
180
+ public function isValidIPv4Range() {
181
+ $ip_string = $this->getIPString();
182
+ if (preg_match_all('/(\d+)/', $ip_string, $matches) > 0) {
183
+ foreach ($matches[1] as $match) {
184
+ $group = (int) $match;
185
+ if ($group > 255 || $group < 0) {
186
+ return false;
187
+ }
188
+ }
189
+ }
190
+
191
+ $group_regex = '([0-9]{1,3}|\[[0-9]{1,3}\-[0-9]{1,3}\])';
192
+ return preg_match('/^' . str_repeat("$group_regex.", 3) . $group_regex . '$/i', $ip_string) > 0;
193
+ }
194
+
195
+ /**
196
+ * @return bool
197
+ */
198
+ public function isValidIPv6Range() {
199
+ $ip_string = $this->getIPString();
200
+ if (strpos($ip_string, '::') !== false) {
201
+ $ip_string = self::expandIPv6Range($ip_string);
202
+ }
203
+ if (!$ip_string) {
204
+ return false;
205
+ }
206
+ $group_regex = '([a-f0-9]{1,4}|\[[a-f0-9]{1,4}\-[a-f0-9]{1,4}\])';
207
+ return preg_match('/^' . str_repeat("$group_regex:", 7) . $group_regex . '$/i', $ip_string) > 0;
208
+ }
209
+
210
+
211
+ /**
212
+ * @return string|null
213
+ */
214
+ public function getIPString() {
215
+ return $this->ip_string;
216
+ }
217
+
218
+ /**
219
+ * @param string|null $ip_string
220
+ */
221
+ public function setIPString($ip_string) {
222
+ $this->ip_string = $ip_string;
223
+ }
224
+ }
wordfence.php CHANGED
@@ -4,13 +4,33 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and High Speed Cache
6
  Author: Wordfence
7
- Version: 6.0.25
8
  Author URI: http://www.wordfence.com/
 
9
  */
10
  if(defined('WP_INSTALLING') && WP_INSTALLING){
11
  return;
12
  }
13
- define('WORDFENCE_VERSION', '6.0.25');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  if(get_option('wordfenceActivated') != 1){
15
  add_action('activated_plugin','wordfence_save_activation_error'); function wordfence_save_activation_error(){ update_option('wf_plugin_act_error', ob_get_contents()); }
16
  }
@@ -20,6 +40,21 @@ if(! defined('WORDFENCE_VERSIONONLY_MODE')){ //Used to get version from file.
20
  @ini_set('memory_limit', '128M'); //Some hosts have ini set at as little as 32 megs. 64 is the min sane amount of memory.
21
  }
22
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  require_once('lib/wordfenceConstants.php');
24
  require_once('lib/wordfenceClass.php');
25
  wordfence::install_actions();
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus, Firewall and High Speed Cache
6
  Author: Wordfence
7
+ Version: 6.1.1
8
  Author URI: http://www.wordfence.com/
9
+ Network: true
10
  */
11
  if(defined('WP_INSTALLING') && WP_INSTALLING){
12
  return;
13
  }
14
+ define('WORDFENCE_VERSION', '6.1.1');
15
+ define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
16
+ basename(dirname(__FILE__)) . '/' . basename(__FILE__));
17
+
18
+ global $wp_plugin_paths;
19
+ foreach ($wp_plugin_paths as $dir => $realdir) {
20
+ if (strpos(__FILE__, $realdir) === 0) {
21
+ define('WORDFENCE_FCPATH', $dir . '/' . basename(__FILE__));
22
+ define('WORDFENCE_PATH', trailingslashit($dir));
23
+ break;
24
+ }
25
+ }
26
+ if (!defined('WORDFENCE_FCPATH')) {
27
+ /** @noinspection PhpConstantReassignmentInspection */
28
+ define('WORDFENCE_FCPATH', __FILE__);
29
+ /** @noinspection PhpConstantReassignmentInspection */
30
+ define('WORDFENCE_PATH', trailingslashit(dirname(WORDFENCE_FCPATH)));
31
+ }
32
+
33
+
34
  if(get_option('wordfenceActivated') != 1){
35
  add_action('activated_plugin','wordfence_save_activation_error'); function wordfence_save_activation_error(){ update_option('wf_plugin_act_error', ob_get_contents()); }
36
  }
40
  @ini_set('memory_limit', '128M'); //Some hosts have ini set at as little as 32 megs. 64 is the min sane amount of memory.
41
  }
42
  }
43
+
44
+ /**
45
+ * Constant to determine if Wordfence is installed on another WordPress site one or more directories up in
46
+ * auto_prepend_file mode.
47
+ */
48
+ define('WFWAF_SUBDIRECTORY_INSTALL', class_exists('wfWAF') &&
49
+ !in_array(realpath(dirname(__FILE__) . '/vendor/wordfence/wf-waf/src/init.php'), get_included_files()));
50
+ if (!WFWAF_SUBDIRECTORY_INSTALL) {
51
+ require_once 'vendor/wordfence/wf-waf/src/init.php';
52
+ if (!wfWAF::getInstance()) {
53
+ define('WFWAF_AUTO_PREPEND', false);
54
+ require_once 'waf/bootstrap.php';
55
+ }
56
+ }
57
+
58
  require_once('lib/wordfenceConstants.php');
59
  require_once('lib/wordfenceClass.php');
60
  wordfence::install_actions();