PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/include/Savant/Savant2/Savant2_Compiler_basic.php

https://github.com/radicaldesigns/amp
PHP | 847 lines | 407 code | 120 blank | 320 comment | 24 complexity | 54b763d8cd190be2e46acb3bc9c7b5de MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * Basic compiler for Savant2.
  5. *
  6. * This is a simple compiler provided as an example. It probably won't
  7. * work with streams, but it does limit the template syntax in a
  8. * relatively strict way. It's not perfect, but it's OK for many
  9. * purposes. Basically, the compiler converts specialized instances of
  10. * curly braces into PHP commands or Savant2 method calls. It will
  11. * probably mess up embedded JavaScript unless you change the prefix
  12. * and suffix to something else (e.g., '<!-- ' and ' -->', but then it
  13. * will mess up your HTML comments ;-).
  14. *
  15. * When in "restrict" mode, ise of PHP commands not in the whitelists
  16. * will cause the compiler to * fail. Use of various constructs and
  17. * superglobals, likewise.
  18. *
  19. * Use {$var} or {$this->var} to print a variable.
  20. *
  21. * Use {: function-list} to print the results of function calls.
  22. *
  23. * Use {['pluginName', 'arg1', $arg2, $this->arg3]} to call plugins.
  24. *
  25. * Use these for looping:
  26. * {foreach ():} ... {endforeach}
  27. * {for ():} ... {endfor}
  28. * {while ():} ... {endwhile}
  29. *
  30. * Use these for conditionals (normal PHP can go in the parens):
  31. * {if (...):}
  32. * {elseif (...):}
  33. * {else:}
  34. * {endif}
  35. * {switch (...):}
  36. * {case ...:}
  37. * {default:}
  38. * {endswitch}
  39. *
  40. * {break} and {continue} are supported as well.
  41. *
  42. * Use this to include a template:
  43. * {tpl 'template.tpl.php'}
  44. * {tpl $tplname}
  45. * {tpl $this->tplname}
  46. *
  47. * $Id: Savant2_Compiler_basic.php,v 1.13 2005/01/29 14:17:50 pmjones Exp $
  48. *
  49. * @author Paul M. Jones <pmjones@ciaweb.net>
  50. *
  51. * @package Savant2
  52. *
  53. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  54. *
  55. * This program is free software; you can redistribute it and/or modify
  56. * it under the terms of the GNU Lesser General Public License as
  57. * published by the Free Software Foundation; either version 2.1 of the
  58. * License, or (at your option) any later version.
  59. *
  60. * This program is distributed in the hope that it will be useful, but
  61. * WITHOUT ANY WARRANTY; without even the implied warranty of
  62. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  63. * Lesser General Public License for more details.
  64. *
  65. */
  66. require_once 'Savant2/Compiler.php';
  67. require_once 'Savant2/Error.php';
  68. require_once 'Savant2/PHPCodeAnalyzer.php';
  69. class Savant2_Compiler_basic extends Savant2_Compiler {
  70. /**
  71. *
  72. * The template directive prefix.
  73. *
  74. * @access public
  75. *
  76. * @var array
  77. *
  78. */
  79. var $prefix = '{';
  80. /**
  81. *
  82. * The template directive suffix.
  83. *
  84. * @access public
  85. *
  86. * @var array
  87. *
  88. */
  89. var $suffix = '}';
  90. /**
  91. *
  92. * The conversion regular expressions.
  93. *
  94. * @access public
  95. *
  96. * @var array
  97. *
  98. */
  99. var $convert = array(
  100. // branching
  101. '(if\s*(.+):)' => '$1',
  102. '(elseif\s*(.+):)' => '$1',
  103. '(else\s*(.+):)' => '$1',
  104. '(endif)' => '$1',
  105. '(switch\s*(.+):)' => '$1',
  106. '(case\s*(.+):)' => '$1',
  107. '(default:)' => '$1',
  108. '(endswitch)' => '$1',
  109. '(break)' => '$1',
  110. // looping
  111. '(foreach\s*(.+):)' => '$1',
  112. '(endforeach)' => '$1',
  113. '(for\s*(.+):)' => '$1',
  114. '(endfor)' => '$1',
  115. '(while\s*(.+):)' => '$1',
  116. '(endwhile)' => '$1',
  117. '(continue)' => '$1',
  118. // simple variable printing
  119. '(\$(.+))' => 'print $1',
  120. // extended printing
  121. '(\:(.+))' => 'print ($2)',
  122. // comments
  123. '\*(.*)?\*' => '/**$1*/',
  124. // template includes
  125. 'tpl (.*)' => 'include $this->findTemplate($1)',
  126. // plugins
  127. '\[\s*(.+)?\s*\]' => '$this->plugin($1)',
  128. );
  129. /**
  130. *
  131. * The list of allowed functions when in restricted mode.
  132. *
  133. * @access public
  134. *
  135. * @var array
  136. *
  137. */
  138. var $allowedFunctions = array();
  139. /**
  140. *
  141. * The list of allowed static methods when in restricted mode.
  142. *
  143. * @access public
  144. *
  145. * @var array
  146. *
  147. */
  148. var $allowedStatic = array();
  149. /**
  150. *
  151. * The directory where compiled templates are saved.
  152. *
  153. * @access public
  154. *
  155. * @var string
  156. *
  157. */
  158. var $compileDir = null;
  159. /**
  160. *
  161. * Whether or not to force every template to be compiled every time.
  162. *
  163. * @access public
  164. *
  165. * @var bool
  166. *
  167. */
  168. var $forceCompile = false;
  169. /**
  170. *
  171. * Whether or not to strict-check the compiled template.
  172. *
  173. * Strict-checks are off by default until all problems with
  174. * PhpCodeAnalyzer have been resolved.
  175. *
  176. * @access public
  177. *
  178. * @var bool
  179. *
  180. */
  181. var $strict = false;
  182. /**
  183. *
  184. * Constructor.
  185. *
  186. */
  187. function Savant2_Compiler_basic($conf = array())
  188. {
  189. parent::Savant2_Compiler($conf);
  190. $this->ca = new PHPCodeAnalyzer();
  191. $this->allowedFunctions = $this->allowedFunctions();
  192. $this->allowedStatic = $this->allowedStatic();
  193. }
  194. /**
  195. *
  196. * Has the source template changed since it was last compiled?
  197. *
  198. * @access public
  199. *
  200. * @var string $tpl The source template file.
  201. *
  202. */
  203. function changed($tpl)
  204. {
  205. // the path for the compiled file
  206. $file = $this->getPath($tpl);
  207. // if the copmiled file does not exist, or if the mod-time of
  208. // the source is later than that of the existing compiled file,
  209. // then the source template file has changed.
  210. if (! file_exists($file) ||
  211. filemtime($tpl) > filemtime($file)) {
  212. return true;
  213. } else {
  214. return false;
  215. }
  216. }
  217. /**
  218. *
  219. * Saves the PHP compiled from template source.
  220. *
  221. * @access public
  222. *
  223. * @var string $tpl The source template file.
  224. *
  225. */
  226. function saveCompiled($tpl, $php)
  227. {
  228. $fp = fopen($this->getPath($tpl), 'w');
  229. if (! $fp) {
  230. return false;
  231. } else {
  232. $result = fwrite($fp, $php);
  233. fclose($fp);
  234. return $result;
  235. }
  236. }
  237. /**
  238. *
  239. * Gets the path to the compiled PHP for a template source.
  240. *
  241. * @access public
  242. *
  243. * @var string $tpl The source template file.
  244. *
  245. */
  246. function getPath($tpl)
  247. {
  248. $dir = $this->compileDir;
  249. if (substr($dir, -1) != DIRECTORY_SEPARATOR) {
  250. $dir .= DIRECTORY_SEPARATOR;
  251. }
  252. return $dir . 'Savant2_' . md5($tpl);
  253. }
  254. /**
  255. *
  256. * Compiles a template source into PHP code for Savant.
  257. *
  258. * @access public
  259. *
  260. * @var string $tpl The source template file.
  261. *
  262. */
  263. function compile($tpl)
  264. {
  265. // create a end-tag so that text editors don't
  266. // stop colorizing text
  267. $end = '?' . '>';
  268. // recompile only if we are forcing compiled, or
  269. // if the template source has changed.
  270. if ($this->forceCompile || $this->changed($tpl)) {
  271. // get the template source text
  272. $php = file_get_contents($tpl);
  273. /**
  274. * @todo Do we really care about PHP tags? The code analyzer
  275. * will disallow any offending PHP regardless.
  276. */
  277. // disallow PHP long tags
  278. $php = str_replace('<?php', '&lt;?php', $php);
  279. // disallow PHP short tags (if turned on)
  280. if (ini_get('short_open_tag')) {
  281. $php = str_replace('<?', '&lt;?', $php);
  282. $php = str_replace('<?=', '&lt;?=', $php);
  283. }
  284. // disallow closing tags
  285. $php = str_replace($end, '?&gt;', $php);
  286. // convert each template command
  287. foreach ($this->convert as $find => $replace) {
  288. // allow whitespace around the command
  289. $find = preg_quote($this->prefix) . '\s*' . $find .
  290. '\s*' . preg_quote($this->suffix);
  291. // actually do the find-and-replace
  292. $php = preg_replace(
  293. "/$find/U",
  294. "<?php $replace $end",
  295. $php
  296. );
  297. }
  298. // +++ DEBUG
  299. // $this->saveCompiled($tpl, $php);
  300. // --- DEBUG
  301. // are we doing strict checking?
  302. if ($this->strict) {
  303. // analyze the code for restriction violations.
  304. $report = $this->analyze($php);
  305. if (count($report) > 0) {
  306. // there were violations, report them as a generic
  307. // Savant error and return. Savant will wrap this
  308. // generic rror with another error that will report
  309. // properly to the customized error handler (if any).
  310. return new Savant2_Error(
  311. array(
  312. 'code' => SAVANT2_ERROR_COMPILE_FAIL,
  313. 'text' => $GLOBALS['_SAVANT2']['error'][SAVANT2_ERROR_COMPILE_FAIL],
  314. 'info' => $report
  315. )
  316. );
  317. }
  318. }
  319. // otherwise, save the compiled template
  320. $this->saveCompiled($tpl, $php);
  321. }
  322. // return the path to the compiled PHP script
  323. return $this->getPath($tpl);
  324. }
  325. /**
  326. *
  327. * Analyze a compiled template for restriction violations.
  328. *
  329. * @access public
  330. *
  331. * @var string $php The compiled PHP code from a template source.
  332. *
  333. * @return array An array of restriction violations; if empty, then
  334. * there were no violations discovered by analysis.
  335. *
  336. */
  337. function analyze(&$php)
  338. {
  339. // analyze the compiled code
  340. $ca = $this->ca;
  341. $ca->source = $php;
  342. $ca->analyze();
  343. // array of captured restriction violations
  344. $report = array();
  345. // -------------------------------------------------------------
  346. //
  347. // go through the list of called functions and make sure each
  348. // one is allowed via the whitelist. if not, record each non-
  349. // allowed function. this also restricts variable-functions
  350. // such as $var().
  351. //
  352. foreach ($ca->calledFunctions as $func => $lines) {
  353. if (! in_array($func, $this->allowedFunctions)) {
  354. $report[$func] = $lines;
  355. }
  356. }
  357. // -------------------------------------------------------------
  358. //
  359. // disallow use of various constructs (include is allowed, we
  360. // need it for {tpl}).
  361. //
  362. $tmp = array(
  363. 'eval',
  364. 'global',
  365. 'include_once',
  366. 'require',
  367. 'require_once',
  368. 'parent',
  369. 'self'
  370. );
  371. foreach ($tmp as $val) {
  372. if (isset($ca->calledConstructs[$val])) {
  373. $report[$val] = $ca->calledConstructs[$val];
  374. }
  375. }
  376. // -------------------------------------------------------------
  377. //
  378. // disallow instantiation of new classes
  379. //
  380. foreach ($ca->classesInstantiated as $key => $val) {
  381. $report['new ' . $key] = $val;
  382. }
  383. // -------------------------------------------------------------
  384. //
  385. // disallow access to the various superglobals
  386. // so that templates cannot manipulate them.
  387. //
  388. $tmp = array(
  389. '$_COOKIE',
  390. '$_ENV',
  391. '$_FILES',
  392. '$_GET',
  393. '$_POST',
  394. '$_REQUEST',
  395. '$_SERVER',
  396. '$_SESSION',
  397. '$GLOBALS',
  398. '$HTTP_COOKIE_VARS',
  399. '$HTTP_ENV_VARS',
  400. '$HTTP_GET_VARS',
  401. '$HTTP_POST_FILES',
  402. '$HTTP_POST_VARS',
  403. '$HTTP_SERVER_VARS',
  404. '$HTTP_SESSION_VARS'
  405. );
  406. foreach ($ca->usedVariables as $var => $lines) {
  407. if (in_array(strtoupper($var), $tmp)) {
  408. $report[$var] = $lines;
  409. }
  410. }
  411. // -------------------------------------------------------------
  412. //
  413. // allow only certain $this methods
  414. //
  415. $tmp = array('plugin', 'splugin', 'findTemplate');
  416. if (isset($ca->calledMethods['$this'])) {
  417. foreach ($ca->calledMethods['$this'] as $method => $lines) {
  418. if (! in_array($method, $tmp)) {
  419. $report['$this->' . $method] = $lines;
  420. }
  421. }
  422. }
  423. // -------------------------------------------------------------
  424. //
  425. // disallow private and variable-variable $this properties
  426. //
  427. if (isset($ca->usedMemberVariables['$this'])) {
  428. foreach ($ca->usedMemberVariables['$this'] as $prop => $lines) {
  429. $char = substr($prop, 0, 1);
  430. if ($char == '_' || $char == '$') {
  431. $report['$this->' . $prop] = $lines;
  432. }
  433. }
  434. }
  435. // -------------------------------------------------------------
  436. //
  437. // allow only certain static method calls
  438. //
  439. foreach ($ca->calledStaticMethods as $class => $methods) {
  440. foreach ($methods as $method => $lines) {
  441. if (! array_key_exists($class, $this->allowedStatic)) {
  442. // the class itself is not allowed
  443. $report["$class::$method"] = $lines;
  444. } elseif (! in_array('*', $this->allowedStatic[$class]) &&
  445. ! in_array($method, $this->allowedStatic[$class])){
  446. // the specific method is not allowed,
  447. // and there is no wildcard for the class methods.
  448. $report["$class::$method"] = $lines;
  449. }
  450. }
  451. }
  452. // -------------------------------------------------------------
  453. //
  454. // only allow includes via $this->findTemplate(*)
  455. //
  456. foreach ($ca->filesIncluded as $text => $lines) {
  457. // in each include statment, look for $this->findTemplate.
  458. preg_match(
  459. '/(.*)?\$this->findTemplate\((.*)?\)(.*)/i',
  460. $text,
  461. $matches
  462. );
  463. if (! empty($matches[1]) || ! empty($matches[3]) ||
  464. empty($matches[2])) {
  465. // there is something before or after the findTemplate call,
  466. // or it's a direct include (which is not allowed)
  467. $report["include $text"] = $lines;
  468. }
  469. }
  470. // -------------------------------------------------------------
  471. //
  472. // do not allow the use of "$this" by itself;
  473. // it must be always be followed by "->" or another
  474. // valid variable-name character (a-z, 0-9, or _).
  475. //
  476. $regex = '/(.*)?\$this(?!(\-\>)|([a-z0-9_]))(.*)?/i';
  477. preg_match_all($regex, $php, $matches, PREG_SET_ORDER);
  478. foreach ($matches as $val) {
  479. $report['\'$this\' without \'->\''][] = $val[0];
  480. }
  481. /** @todo disallow standalone variable-variables, $$var */
  482. /** @todo disallow vars from static classes? class::$var */
  483. // -------------------------------------------------------------
  484. //
  485. // done!
  486. //
  487. // +++ DEBUG
  488. //echo "<pre>";
  489. //print_r($ca);
  490. //echo "</pre>";
  491. // --- DEBUG
  492. return $report;
  493. }
  494. /**
  495. *
  496. * A list of allowed static method calls for templates.
  497. *
  498. * The format is ...
  499. *
  500. * array(
  501. * 'Class1' => array('method1', 'method2'),
  502. * 'Class2' => array('methodA', 'methodB'),
  503. * 'Class3' => '*'
  504. * );
  505. *
  506. * If you want to allow all methods from the static class to be allowed,
  507. * use a '*' in the method name list.
  508. *
  509. */
  510. function allowedStatic()
  511. {
  512. return array();
  513. }
  514. /**
  515. *
  516. * A list of allowed functions for templates.
  517. *
  518. */
  519. function allowedFunctions()
  520. {
  521. return array(
  522. // arrays
  523. 'array_count_values',
  524. 'array_key_exists',
  525. 'array_keys',
  526. 'array_sum',
  527. 'array_values',
  528. 'compact',
  529. 'count',
  530. 'current',
  531. 'each',
  532. 'end',
  533. 'extract',
  534. 'in_array',
  535. 'key',
  536. 'list',
  537. 'next',
  538. 'pos',
  539. 'prev',
  540. 'reset',
  541. 'sizeof',
  542. // calendar
  543. 'cal_days_in_month',
  544. 'cal_from_jd',
  545. 'cal_to_jd',
  546. 'easter_date',
  547. 'easter_days',
  548. 'FrenchToJD',
  549. 'GregorianToJD',
  550. 'JDDayOfWeek',
  551. 'JDMonthName',
  552. 'JDToFrench',
  553. 'JDToGregorian',
  554. 'jdtojewish',
  555. 'JDToJulian',
  556. 'jdtounix',
  557. 'JewishToJD',
  558. 'JulianToJD',
  559. 'unixtojd',
  560. // date
  561. 'checkdate',
  562. 'date_sunrise',
  563. 'date_sunset',
  564. 'date',
  565. 'getdate',
  566. 'gettimeofday',
  567. 'gmdate',
  568. 'gmmktime',
  569. 'gmstrftime',
  570. 'idate',
  571. 'localtime',
  572. 'microtime',
  573. 'mktime',
  574. 'strftime',
  575. 'strptime',
  576. 'strtotime',
  577. 'time',
  578. // gettext
  579. '_',
  580. 'gettext',
  581. 'ngettext',
  582. // math
  583. 'abs',
  584. 'acos',
  585. 'acosh',
  586. 'asin',
  587. 'asinh',
  588. 'atan2',
  589. 'atan',
  590. 'atanh',
  591. 'base_convert',
  592. 'bindec',
  593. 'ceil',
  594. 'cos',
  595. 'cosh',
  596. 'decbin',
  597. 'dechex',
  598. 'decoct',
  599. 'deg2rad',
  600. 'exp',
  601. 'expm1',
  602. 'floor',
  603. 'fmod',
  604. 'getrandmax',
  605. 'hexdec',
  606. 'hypot',
  607. 'is_finite',
  608. 'is_infinite',
  609. 'is_nan',
  610. 'lcg_value',
  611. 'log10',
  612. 'log1p',
  613. 'log',
  614. 'max',
  615. 'min',
  616. 'mt_getrandmax',
  617. 'mt_rand',
  618. 'mt_srand',
  619. 'octdec',
  620. 'pi',
  621. 'pow',
  622. 'rad2deg',
  623. 'rand',
  624. 'round',
  625. 'sin',
  626. 'sinh',
  627. 'sqrt',
  628. 'srand',
  629. 'tan',
  630. 'tanh',
  631. // strings
  632. 'chop',
  633. 'count_chars',
  634. 'echo',
  635. 'explode',
  636. 'hebrev',
  637. 'hebrevc',
  638. 'html_entity_decode',
  639. 'htmlentities',
  640. 'htmlspecialchars',
  641. 'implode',
  642. 'join',
  643. 'localeconv',
  644. 'ltrim',
  645. 'money_format',
  646. 'nl_langinfo',
  647. 'nl2br',
  648. 'number_format',
  649. 'ord',
  650. 'print',
  651. 'printf',
  652. 'quoted_printable_decode',
  653. 'rtrim',
  654. 'sprintf',
  655. 'sscanf',
  656. 'str_pad',
  657. 'str_repeat',
  658. 'str_replace',
  659. 'str_rot13',
  660. 'str_shuffle',
  661. 'str_word_count',
  662. 'strcasecmp',
  663. 'strchr',
  664. 'strcmp',
  665. 'strcoll',
  666. 'strcspn',
  667. 'strip_tags',
  668. 'stripcslashes',
  669. 'stripos',
  670. 'stripslashes',
  671. 'stristr',
  672. 'strlen',
  673. 'strnatcasecmp',
  674. 'strnatcmp',
  675. 'strncasecmp',
  676. 'strncmp',
  677. 'strpbrk',
  678. 'strpos',
  679. 'strrchr',
  680. 'strrev',
  681. 'strripos',
  682. 'strrpos',
  683. 'strspn',
  684. 'strstr',
  685. 'strtok',
  686. 'strtolower',
  687. 'strtoupper',
  688. 'strtr',
  689. 'substr_compare',
  690. 'substr_count',
  691. 'substr_replace',
  692. 'substr',
  693. 'trim',
  694. 'ucfirst',
  695. 'ucwords',
  696. 'wordwrap',
  697. // url
  698. 'base64_decode',
  699. 'base64_encode',
  700. 'rawurldecode',
  701. 'rawurlencode',
  702. 'urldecode',
  703. 'urlencode',
  704. // variables
  705. 'empty',
  706. 'is_array',
  707. 'is_bool',
  708. 'is_double',
  709. 'is_float',
  710. 'is_int',
  711. 'is_integer',
  712. 'is_long',
  713. 'is_null',
  714. 'is_numeric',
  715. 'is_object',
  716. 'is_real',
  717. 'is_resource',
  718. 'is_scalar',
  719. 'is_string',
  720. 'isset',
  721. 'print_r',
  722. 'unset',
  723. 'var_dump',
  724. );
  725. }
  726. }
  727. ?>