Serializer.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <?php
  2. class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
  3. {
  4. /**
  5. * @param HTMLPurifier_Definition $def
  6. * @param HTMLPurifier_Config $config
  7. * @return int|bool
  8. */
  9. public function add($def, $config)
  10. {
  11. if (!$this->checkDefType($def)) {
  12. return;
  13. }
  14. $file = $this->generateFilePath($config);
  15. if (file_exists($file)) {
  16. return false;
  17. }
  18. if (!$this->_prepareDir($config)) {
  19. return false;
  20. }
  21. return $this->_write($file, serialize($def), $config);
  22. }
  23. /**
  24. * @param HTMLPurifier_Definition $def
  25. * @param HTMLPurifier_Config $config
  26. * @return int|bool
  27. */
  28. public function set($def, $config)
  29. {
  30. if (!$this->checkDefType($def)) {
  31. return;
  32. }
  33. $file = $this->generateFilePath($config);
  34. if (!$this->_prepareDir($config)) {
  35. return false;
  36. }
  37. return $this->_write($file, serialize($def), $config);
  38. }
  39. /**
  40. * @param HTMLPurifier_Definition $def
  41. * @param HTMLPurifier_Config $config
  42. * @return int|bool
  43. */
  44. public function replace($def, $config)
  45. {
  46. if (!$this->checkDefType($def)) {
  47. return;
  48. }
  49. $file = $this->generateFilePath($config);
  50. if (!file_exists($file)) {
  51. return false;
  52. }
  53. if (!$this->_prepareDir($config)) {
  54. return false;
  55. }
  56. return $this->_write($file, serialize($def), $config);
  57. }
  58. /**
  59. * @param HTMLPurifier_Config $config
  60. * @return bool|HTMLPurifier_Config
  61. */
  62. public function get($config)
  63. {
  64. $file = $this->generateFilePath($config);
  65. if (!file_exists($file)) {
  66. return false;
  67. }
  68. return unserialize(file_get_contents($file));
  69. }
  70. /**
  71. * @param HTMLPurifier_Config $config
  72. * @return bool
  73. */
  74. public function remove($config)
  75. {
  76. $file = $this->generateFilePath($config);
  77. if (!file_exists($file)) {
  78. return false;
  79. }
  80. return unlink($file);
  81. }
  82. /**
  83. * @param HTMLPurifier_Config $config
  84. * @return bool
  85. */
  86. public function flush($config)
  87. {
  88. if (!$this->_prepareDir($config)) {
  89. return false;
  90. }
  91. $dir = $this->generateDirectoryPath($config);
  92. $dh = opendir($dir);
  93. // Apparently, on some versions of PHP, readdir will return
  94. // an empty string if you pass an invalid argument to readdir.
  95. // So you need this test. See #49.
  96. if (false === $dh) {
  97. return false;
  98. }
  99. while (false !== ($filename = readdir($dh))) {
  100. if (empty($filename)) {
  101. continue;
  102. }
  103. if ($filename[0] === '.') {
  104. continue;
  105. }
  106. unlink($dir . '/' . $filename);
  107. }
  108. closedir($dh);
  109. return true;
  110. }
  111. /**
  112. * @param HTMLPurifier_Config $config
  113. * @return bool
  114. */
  115. public function cleanup($config)
  116. {
  117. if (!$this->_prepareDir($config)) {
  118. return false;
  119. }
  120. $dir = $this->generateDirectoryPath($config);
  121. $dh = opendir($dir);
  122. // See #49 (and above).
  123. if (false === $dh) {
  124. return false;
  125. }
  126. while (false !== ($filename = readdir($dh))) {
  127. if (empty($filename)) {
  128. continue;
  129. }
  130. if ($filename[0] === '.') {
  131. continue;
  132. }
  133. $key = substr($filename, 0, strlen($filename) - 4);
  134. $file = $dir . '/' . $filename;
  135. if ($this->isOld($key, $config) && file_exists($file)) {
  136. unlink($file);
  137. }
  138. }
  139. closedir($dh);
  140. return true;
  141. }
  142. /**
  143. * Generates the file path to the serial file corresponding to
  144. * the configuration and definition name
  145. * @param HTMLPurifier_Config $config
  146. * @return string
  147. * @todo Make protected
  148. */
  149. public function generateFilePath($config)
  150. {
  151. $key = $this->generateKey($config);
  152. return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
  153. }
  154. /**
  155. * Generates the path to the directory contain this cache's serial files
  156. * @param HTMLPurifier_Config $config
  157. * @return string
  158. * @note No trailing slash
  159. * @todo Make protected
  160. */
  161. public function generateDirectoryPath($config)
  162. {
  163. $base = $this->generateBaseDirectoryPath($config);
  164. return $base . '/' . $this->type;
  165. }
  166. /**
  167. * Generates path to base directory that contains all definition type
  168. * serials
  169. * @param HTMLPurifier_Config $config
  170. * @return mixed|string
  171. * @todo Make protected
  172. */
  173. public function generateBaseDirectoryPath($config)
  174. {
  175. $base = $config->get('Cache.SerializerPath');
  176. $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
  177. return $base;
  178. }
  179. /**
  180. * Convenience wrapper function for file_put_contents
  181. * @param string $file File name to write to
  182. * @param string $data Data to write into file
  183. * @param HTMLPurifier_Config $config
  184. * @return int|bool Number of bytes written if success, or false if failure.
  185. */
  186. private function _write($file, $data, $config)
  187. {
  188. $result = file_put_contents($file, $data);
  189. if ($result !== false) {
  190. // set permissions of the new file (no execute)
  191. $chmod = $config->get('Cache.SerializerPermissions');
  192. if ($chmod !== null) {
  193. chmod($file, $chmod & 0666);
  194. }
  195. }
  196. return $result;
  197. }
  198. /**
  199. * Prepares the directory that this type stores the serials in
  200. * @param HTMLPurifier_Config $config
  201. * @return bool True if successful
  202. */
  203. private function _prepareDir($config)
  204. {
  205. $directory = $this->generateDirectoryPath($config);
  206. $chmod = $config->get('Cache.SerializerPermissions');
  207. if ($chmod === null) {
  208. if (!@mkdir($directory) && !is_dir($directory)) {
  209. trigger_error(
  210. 'Could not create directory ' . $directory . '',
  211. E_USER_WARNING
  212. );
  213. return false;
  214. }
  215. return true;
  216. }
  217. if (!is_dir($directory)) {
  218. $base = $this->generateBaseDirectoryPath($config);
  219. if (!is_dir($base)) {
  220. trigger_error(
  221. 'Base directory ' . $base . ' does not exist,
  222. please create or change using %Cache.SerializerPath',
  223. E_USER_WARNING
  224. );
  225. return false;
  226. } elseif (!$this->_testPermissions($base, $chmod)) {
  227. return false;
  228. }
  229. if (!@mkdir($directory, $chmod) && !is_dir($directory)) {
  230. trigger_error(
  231. 'Could not create directory ' . $directory . '',
  232. E_USER_WARNING
  233. );
  234. return false;
  235. }
  236. if (!$this->_testPermissions($directory, $chmod)) {
  237. return false;
  238. }
  239. } elseif (!$this->_testPermissions($directory, $chmod)) {
  240. return false;
  241. }
  242. return true;
  243. }
  244. /**
  245. * Tests permissions on a directory and throws out friendly
  246. * error messages and attempts to chmod it itself if possible
  247. * @param string $dir Directory path
  248. * @param int $chmod Permissions
  249. * @return bool True if directory is writable
  250. */
  251. private function _testPermissions($dir, $chmod)
  252. {
  253. // early abort, if it is writable, everything is hunky-dory
  254. if (is_writable($dir)) {
  255. return true;
  256. }
  257. if (!is_dir($dir)) {
  258. // generally, you'll want to handle this beforehand
  259. // so a more specific error message can be given
  260. trigger_error(
  261. 'Directory ' . $dir . ' does not exist',
  262. E_USER_WARNING
  263. );
  264. return false;
  265. }
  266. if (function_exists('posix_getuid') && $chmod !== null) {
  267. // POSIX system, we can give more specific advice
  268. if (fileowner($dir) === posix_getuid()) {
  269. // we can chmod it ourselves
  270. $chmod = $chmod | 0700;
  271. if (chmod($dir, $chmod)) {
  272. return true;
  273. }
  274. } elseif (filegroup($dir) === posix_getgid()) {
  275. $chmod = $chmod | 0070;
  276. } else {
  277. // PHP's probably running as nobody, it is
  278. // not obvious how to fix this (777 is probably
  279. // bad if you are multi-user), let the user figure it out
  280. $chmod = null;
  281. }
  282. trigger_error(
  283. 'Directory ' . $dir . ' not writable. ' .
  284. ($chmod === null ? '' : 'Please chmod to ' . decoct($chmod)),
  285. E_USER_WARNING
  286. );
  287. } else {
  288. // generic error message
  289. trigger_error(
  290. 'Directory ' . $dir . ' not writable, ' .
  291. 'please alter file permissions',
  292. E_USER_WARNING
  293. );
  294. }
  295. return false;
  296. }
  297. }
  298. // vim: et sw=4 sts=4