PageRenderTime 24ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/SecurityInfo/PhpSecInfo/PhpSecInfo.php

https://github.com/quarkness/piwik
PHP | 591 lines | 218 code | 120 blank | 253 comment | 30 complexity | c36e49ef4380519fdf72bde6eaf29120 MD5 | raw file
  1. <?php
  2. /**
  3. * Main class file
  4. *
  5. * @package PhpSecInfo
  6. * @author Ed Finkler <coj@funkatron.com>
  7. */
  8. /**
  9. * The default language setting if none is set/retrievable
  10. *
  11. */
  12. define ('PHPSECINFO_LANG_DEFAULT', 'en');
  13. /**
  14. * a general version string to differentiate releases
  15. *
  16. */
  17. define ('PHPSECINFO_VERSION', '0.2.2');
  18. /**
  19. * a YYYYMMDD date string to indicate "build" date
  20. *
  21. */
  22. define ('PHPSECINFO_BUILD', '20080723');
  23. /**
  24. * Homepage for phpsecinfo project
  25. *
  26. */
  27. define ('PHPSECINFO_URL', 'http://phpsecinfo.com');
  28. /**
  29. * The base folder where views are stored. Include trailing slash
  30. *
  31. */
  32. define('PHPSECINFO_VIEW_DIR_DEFAULT', 'View/');
  33. /**
  34. * The default format, used to load the proper view.
  35. */
  36. define('PHPSECINFO_FORMAT_DEFAULT', 'Html');
  37. /**
  38. * The base directory, used to resolve requires and includes
  39. */
  40. define('PHPSECINFO_BASE_DIR', dirname(__FILE__));
  41. /**
  42. * This is the main class for the phpsecinfo system. It's responsible for
  43. * dynamically loading tests, running those tests, and generating the results
  44. * output
  45. *
  46. * Example:
  47. * <code>
  48. * <?php require_once(PHPSECINFO_BASE_DIR.'/PhpSecInfo.php'); ?>
  49. * <?php phpsecinfo(); ?>
  50. * </code>
  51. *
  52. * If you want to capture the output, or just grab the test results and display them
  53. * in your own way, you'll need to do slightly more work.
  54. *
  55. * Example:
  56. * <code>
  57. * require_once(PHPSECINFO_BASE_DIR.'/PhpSecInfo.php');
  58. * // instantiate the class
  59. * $psi = new PhpSecInfo();
  60. *
  61. * // load and run all tests
  62. * $psi->loadAndRun();
  63. *
  64. * // grab the results as a multidimensional array
  65. * $results = $psi->getResultsAsArray();
  66. * echo "<pre>"; echo print_r($results, true); echo "</pre>";
  67. *
  68. * // grab the standard results output as a string
  69. * $html = $psi->getOutput();
  70. *
  71. * // send it to the browser
  72. * echo $html;
  73. * </code>
  74. *
  75. *
  76. * The procedural function "phpsecinfo" is defined below this class.
  77. * @see phpsecinfo()
  78. *
  79. * @author Ed Finkler <coj@funkatron.com>
  80. *
  81. * see CHANGELOG for changes
  82. *
  83. */
  84. class PhpSecInfo
  85. {
  86. /**
  87. * An array of tests to run
  88. *
  89. * @var array PhpSecInfo_Test
  90. */
  91. var $tests_to_run = array();
  92. /**
  93. * An array of results. Each result is an associative array:
  94. * <code>
  95. * $result['result'] = PHPSECINFO_TEST_RESULT_NOTICE;
  96. * $result['message'] = "a string describing the test results and what they mean";
  97. * </code>
  98. *
  99. * @var array
  100. */
  101. var $test_results = array();
  102. /**
  103. * An array of tests that were not run
  104. *
  105. * <code>
  106. * $result['result'] = PHPSECINFO_TEST_RESULT_NOTRUN;
  107. * $result['message'] = "a string explaining why the test was not run";
  108. * </code>
  109. *
  110. * @var array
  111. */
  112. var $tests_not_run = array();
  113. /**
  114. * The language code used. Defaults to PHPSECINFO_LANG_DEFAULT, which
  115. * is 'en'
  116. *
  117. * @var string
  118. * @see PHPSECINFO_LANG_DEFAULT
  119. */
  120. var $language = PHPSECINFO_LANG_DEFAULT;
  121. /**
  122. * An array of integers recording the number of test results in each category. Categories can include
  123. * some or all of the PHPSECINFO_TEST_* constants. Constants are the keys, # of results are the values.
  124. *
  125. * @var array
  126. */
  127. var $result_counts = array();
  128. /**
  129. * The number of tests that have been run
  130. *
  131. * @var integer
  132. */
  133. var $num_tests_run = 0;
  134. /**
  135. * The base directory for phpsecinfo. Set within the constructor. Paths are resolved from this.
  136. * @var string
  137. */
  138. var $_base_dir;
  139. /**
  140. * The directory PHPSecInfo will look for views. It defaults to the value
  141. * in PHPSECINFO_VIEW_DIR_DEFAULT, but can be changed with the setViewDirectory()
  142. * method.
  143. *
  144. * @var string
  145. */
  146. var $_view_directory;
  147. /**
  148. * The output format, used to load the proper view
  149. *
  150. * @var string
  151. **/
  152. var $_format;
  153. /**
  154. * Constructor
  155. *
  156. * @return PhpSecInfo
  157. */
  158. function PhpSecInfo($opts = null) {
  159. $this->_base_dir = dirname(__FILE__);
  160. if ($opts) {
  161. if (isset($opts['view_directory'])) {
  162. $this->setViewDirectory($opts['view_directory']);
  163. } else {
  164. $this->setViewDirectory(dirname(__FILE__).DIRECTORY_SEPARATOR . PHPSECINFO_VIEW_DIR_DEFAULT);
  165. }
  166. if (isset($opts['format'])) {
  167. $this->setFormat($opts['format']);
  168. } else {
  169. if (!strcasecmp(PHP_SAPI, 'cli')) {
  170. $this->setFormat('Cli');
  171. } else {
  172. $this->setFormat(PHPSECINFO_FORMAT_DEFAULT);
  173. }
  174. }
  175. } else { /* Use defaults */
  176. $this->setViewDirectory(dirname(__FILE__).DIRECTORY_SEPARATOR . PHPSECINFO_VIEW_DIR_DEFAULT);
  177. if (!strcasecmp(PHP_SAPI, 'cli')) {
  178. $this->setFormat('Cli');
  179. } else {
  180. $this->setFormat(PHPSECINFO_FORMAT_DEFAULT);
  181. }
  182. }
  183. }
  184. /**
  185. * recurses through the Test subdir and includes classes in each test group subdir,
  186. * then builds an array of classnames for the tests that will be run
  187. *
  188. */
  189. function loadTests() {
  190. $test_root = dir(dirname(__FILE__).DIRECTORY_SEPARATOR.'Test');
  191. //echo "<pre>"; echo print_r($test_root, true); echo "</pre>";
  192. while (false !== ($entry = $test_root->read())) {
  193. if ( is_dir($test_root->path.DIRECTORY_SEPARATOR.$entry) && !preg_match('~^(\.|_vti)(.*)$~', $entry) ) {
  194. $test_dirs[] = $entry;
  195. }
  196. }
  197. //echo "<pre>"; echo print_r($test_dirs, true); echo "</pre>";
  198. // include_once all files in each test dir
  199. foreach ($test_dirs as $test_dir) {
  200. $this_dir = dir($test_root->path.DIRECTORY_SEPARATOR.$test_dir);
  201. while (false !== ($entry = $this_dir->read())) {
  202. if (!is_dir($this_dir->path.DIRECTORY_SEPARATOR.$entry)) {
  203. include_once $this_dir->path.DIRECTORY_SEPARATOR.$entry;
  204. $classNames[] = "PhpSecInfo_Test_".$test_dir."_".basename($entry, '.php');
  205. }
  206. }
  207. }
  208. // modded this to not throw a PHP5 STRICT notice, although I don't like passing by value here
  209. $this->tests_to_run = $classNames;
  210. }
  211. /**
  212. * This runs the tests in the tests_to_run array and
  213. * places returned data in the following arrays/scalars:
  214. * - $this->test_results
  215. * - $this->result_counts
  216. * - $this->num_tests_run
  217. * - $this->tests_not_run;
  218. *
  219. */
  220. function runTests() {
  221. // initialize a bunch of arrays
  222. $this->test_results = array();
  223. $this->result_counts = array();
  224. $this->result_counts[PHPSECINFO_TEST_RESULT_NOTRUN] = 0;
  225. $this->num_tests_run = 0;
  226. foreach ($this->tests_to_run as $testClass) {
  227. /**
  228. * @var $test PhpSecInfo_Test
  229. */
  230. $test = new $testClass();
  231. if ($test->isTestable()) {
  232. $test->test();
  233. $rs = array( 'result' => $test->getResult(),
  234. 'message' => $test->getMessage(),
  235. 'value_current' => $test->getCurrentTestValue(),
  236. 'value_recommended' => $test->getRecommendedTestValue(),
  237. 'moreinfo_url' => $test->getMoreInfoURL(),
  238. );
  239. $this->test_results[$test->getTestGroup()][$test->getTestName()] = $rs;
  240. // initialize if not yet set
  241. if (!isset ($this->result_counts[$rs['result']]) ) {
  242. $this->result_counts[$rs['result']] = 0;
  243. }
  244. $this->result_counts[$rs['result']]++;
  245. $this->num_tests_run++;
  246. } else {
  247. $rs = array( 'result' => $test->getResult(),
  248. 'message' => $test->getMessage(),
  249. 'value_current' => NULL,
  250. 'value_recommended' => NULL,
  251. 'moreinfo_url' => $test->getMoreInfoURL(),
  252. );
  253. $this->result_counts[PHPSECINFO_TEST_RESULT_NOTRUN]++;
  254. $this->tests_not_run[$test->getTestGroup()."::".$test->getTestName()] = $rs;
  255. }
  256. }
  257. }
  258. /**
  259. * This is the main output method. The look and feel mimics phpinfo()
  260. *
  261. */
  262. function renderOutput($page_title="Security Information About PHP") {
  263. /**
  264. * We need to use PhpSecInfo_Test::getBooleanIniValue() below
  265. * @see PhpSecInfo_Test::getBooleanIniValue()
  266. */
  267. if (!class_exists('PhpSecInfo_Test')) {
  268. include( dirname(__FILE__).DIRECTORY_SEPARATOR.'Test'.DIRECTORY_SEPARATOR.'Test.php');
  269. }
  270. $this->loadView($this->_format);
  271. }
  272. /**
  273. * This is a helper method that makes it easy to output tables of test results
  274. * for a given test group
  275. *
  276. * @param string $group_name
  277. * @param array $group_results
  278. */
  279. function _outputRenderTable($group_name, $group_results) {
  280. // exit out if $group_results was empty or not an array. This sorta seems a little hacky...
  281. if (!is_array($group_results) || sizeof($group_results) < 1) {
  282. return false;
  283. }
  284. ksort($group_results);
  285. $this->loadView($this->_format.'/Result', array('group_name'=>$group_name, 'group_results'=>$group_results));
  286. return true;
  287. }
  288. /**
  289. * This outputs a table containing a summary of the test results (counts and % in each result type)
  290. *
  291. * @see PHPSecInfo::_outputRenderTable()
  292. * @see PHPSecInfo::_outputGetResultTypeFromCode()
  293. */
  294. function _outputRenderStatsTable() {
  295. foreach($this->result_counts as $code=>$val) {
  296. if ($code != PHPSECINFO_TEST_RESULT_NOTRUN) {
  297. $percentage = round($val/$this->num_tests_run * 100,2);
  298. $result_type = $this->_outputGetResultTypeFromCode($code);
  299. $stats[$result_type] = array( 'count' => $val,
  300. 'result' => $code,
  301. 'message' => "$val out of {$this->num_tests_run} ($percentage%)");
  302. }
  303. }
  304. $this->_outputRenderTable('Test Results Summary', $stats);
  305. }
  306. /**
  307. * This outputs a table containing a summary or test that were not executed, and the reasons why they were skipped
  308. *
  309. * @see PHPSecInfo::_outputRenderTable()
  310. */
  311. function _outputRenderNotRunTable() {
  312. $this->_outputRenderTable('Tests Not Run', $this->tests_not_run);
  313. }
  314. /**
  315. * This is a helper function that returns a CSS class corresponding to
  316. * the result code the test returned. This allows us to color-code
  317. * results
  318. *
  319. * @param integer $code
  320. * @return string
  321. */
  322. function _outputGetCssClassFromResult($code) {
  323. switch ($code) {
  324. case PHPSECINFO_TEST_RESULT_OK:
  325. return 'value-ok';
  326. break;
  327. case PHPSECINFO_TEST_RESULT_NOTICE:
  328. return 'value-notice';
  329. break;
  330. case PHPSECINFO_TEST_RESULT_WARN:
  331. return 'value-warn';
  332. break;
  333. case PHPSECINFO_TEST_RESULT_NOTRUN:
  334. return 'value-notrun';
  335. break;
  336. case PHPSECINFO_TEST_RESULT_ERROR:
  337. return 'value-error';
  338. break;
  339. default:
  340. return 'value-notrun';
  341. break;
  342. }
  343. }
  344. /**
  345. * This is a helper function that returns a label string corresponding to
  346. * the result code the test returned. This is mainly used for the Test
  347. * Results Summary table.
  348. *
  349. * @see PHPSecInfo::_outputRenderStatsTable()
  350. * @param integer $code
  351. * @return string
  352. */
  353. function _outputGetResultTypeFromCode($code) {
  354. switch ($code) {
  355. case PHPSECINFO_TEST_RESULT_OK:
  356. return 'Pass';
  357. break;
  358. case PHPSECINFO_TEST_RESULT_NOTICE:
  359. return 'Notice';
  360. break;
  361. case PHPSECINFO_TEST_RESULT_WARN:
  362. return 'Warning';
  363. break;
  364. case PHPSECINFO_TEST_RESULT_NOTRUN:
  365. return 'Not Run';
  366. break;
  367. case PHPSECINFO_TEST_RESULT_ERROR:
  368. return 'Error';
  369. break;
  370. default:
  371. return 'Invalid Result Code';
  372. break;
  373. }
  374. }
  375. /**
  376. * Loads and runs all the tests
  377. *
  378. * As loading, then running, is a pretty common process, this saves a extra method call
  379. *
  380. * @since 0.1.1
  381. *
  382. */
  383. function loadAndRun() {
  384. $this->loadTests();
  385. $this->runTests();
  386. }
  387. /**
  388. * returns an associative array of test data. Four keys are set:
  389. * - test_results (array)
  390. * - tests_not_run (array)
  391. * - result_counts (array)
  392. * - num_tests_run (integer)
  393. *
  394. * note that this must be called after tests are loaded and run
  395. *
  396. * @since 0.1.1
  397. * @return array
  398. */
  399. function getResultsAsArray() {
  400. $results = array();
  401. $results['test_results'] = $this->test_results;
  402. $results['tests_not_run'] = $this->tests_not_run;
  403. $results['result_counts'] = $this->result_counts;
  404. $results['num_tests_run'] = $this->num_tests_run;
  405. return $results;
  406. }
  407. /**
  408. * returns the standard output as a string instead of echoing it to the browser
  409. *
  410. * note that this must be called after tests are loaded and run
  411. *
  412. * @since 0.1.1
  413. *
  414. * @return string
  415. */
  416. function getOutput() {
  417. ob_start();
  418. $this->renderOutput();
  419. $output = ob_get_clean();
  420. return $output;
  421. }
  422. /**
  423. * A very, very simple "view" system
  424. *
  425. */
  426. function loadView($view_name, $data=null) {
  427. if ($data != null) {
  428. extract($data);
  429. }
  430. $view_file = $this->getViewDirectory().$view_name.".php";
  431. if ( file_exists($view_file) && is_readable($view_file) ) {
  432. ob_start();
  433. include $view_file;
  434. echo ob_get_clean();
  435. } else {
  436. user_error("The view '{$view_file}' either does not exist or is not readable", E_USER_WARNING);
  437. }
  438. }
  439. /**
  440. * Returns the current view directory
  441. *
  442. * @return string
  443. */
  444. function getViewDirectory() {
  445. return $this->_view_directory;
  446. }
  447. /**
  448. * Sets the directory that PHPSecInfo will look in for views
  449. *
  450. * @param string $newdir
  451. */
  452. function setViewDirectory($newdir) {
  453. $this->_view_directory = $newdir;
  454. }
  455. function getFormat() {
  456. return $this->_format;
  457. }
  458. function setFormat($format) {
  459. $this->_format = $format;
  460. }
  461. }
  462. /**
  463. * A globally-available function that runs the tests and creates the result page
  464. *
  465. */
  466. function phpsecinfo() {
  467. // modded this to not throw a PHP5 STRICT notice, although I don't like passing by value here
  468. $psi = new PhpSecInfo();
  469. $psi->loadAndRun();
  470. $psi->renderOutput();
  471. }