You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 line
9.3KB

  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. return true;
  109. }
  110. /**
  111. * @param HTMLPurifier_Config $config
  112. * @return bool
  113. */
  114. public function cleanup($config)
  115. {
  116. if (!$this->_prepareDir($config)) {
  117. return false;
  118. }
  119. $dir = $this->generateDirectoryPath($config);
  120. $dh = opendir($dir);
  121. // See #49 (and above).
  122. if (false === $dh) {
  123. return false;
  124. }
  125. while (false !== ($filename = readdir($dh))) {
  126. if (empty($filename)) {
  127. continue;
  128. }
  129. if ($filename[0] === '.') {
  130. continue;
  131. }
  132. $key = substr($filename, 0, strlen($filename) - 4);
  133. if ($this->isOld($key, $config)) {
  134. unlink($dir . '/' . $filename);
  135. }
  136. }
  137. return true;
  138. }
  139. /**
  140. * Generates the file path to the serial file corresponding to
  141. * the configuration and definition name
  142. * @param HTMLPurifier_Config $config
  143. * @return string
  144. * @todo Make protected
  145. */
  146. public function generateFilePath($config)
  147. {
  148. $key = $this->generateKey($config);
  149. return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
  150. }
  151. /**
  152. * Generates the path to the directory contain this cache's serial files
  153. * @param HTMLPurifier_Config $config
  154. * @return string
  155. * @note No trailing slash
  156. * @todo Make protected
  157. */
  158. public function generateDirectoryPath($config)
  159. {
  160. $base = $this->generateBaseDirectoryPath($config);
  161. return $base . '/' . $this->type;
  162. }
  163. /**
  164. * Generates path to base directory that contains all definition type
  165. * serials
  166. * @param HTMLPurifier_Config $config
  167. * @return mixed|string
  168. * @todo Make protected
  169. */
  170. public function generateBaseDirectoryPath($config)
  171. {
  172. $base = $config->get('Cache.SerializerPath');
  173. $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
  174. return $base;
  175. }
  176. /**
  177. * Convenience wrapper function for file_put_contents
  178. * @param string $file File name to write to
  179. * @param string $data Data to write into file
  180. * @param HTMLPurifier_Config $config
  181. * @return int|bool Number of bytes written if success, or false if failure.
  182. */
  183. private function _write($file, $data, $config)
  184. {
  185. $result = file_put_contents($file, $data);
  186. if ($result !== false) {
  187. // set permissions of the new file (no execute)
  188. $chmod = $config->get('Cache.SerializerPermissions');
  189. if ($chmod === null) {
  190. // don't do anything
  191. } else {
  192. $chmod = $chmod & 0666;
  193. chmod($file, $chmod);
  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 (!is_dir($directory)) {
  208. $base = $this->generateBaseDirectoryPath($config);
  209. if (!is_dir($base)) {
  210. trigger_error(
  211. 'Base directory ' . $base . ' does not exist,
  212. please create or change using %Cache.SerializerPath',
  213. E_USER_WARNING
  214. );
  215. return false;
  216. } elseif (!$this->_testPermissions($base, $chmod)) {
  217. return false;
  218. }
  219. if ($chmod === null) {
  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. }
  227. if ($chmod !== null) {
  228. mkdir($directory, $chmod);
  229. } else {
  230. mkdir($directory);
  231. }
  232. if (!$this->_testPermissions($directory, $chmod)) {
  233. trigger_error(
  234. 'Base directory ' . $base . ' does not exist,
  235. please create or change using %Cache.SerializerPath',
  236. E_USER_WARNING
  237. );
  238. return false;
  239. }
  240. } elseif (!$this->_testPermissions($directory, $chmod)) {
  241. return false;
  242. }
  243. return true;
  244. }
  245. /**
  246. * Tests permissions on a directory and throws out friendly
  247. * error messages and attempts to chmod it itself if possible
  248. * @param string $dir Directory path
  249. * @param int $chmod Permissions
  250. * @return bool True if directory is writable
  251. */
  252. private function _testPermissions($dir, $chmod)
  253. {
  254. // early abort, if it is writable, everything is hunky-dory
  255. if (is_writable($dir)) {
  256. return true;
  257. }
  258. if (!is_dir($dir)) {
  259. // generally, you'll want to handle this beforehand
  260. // so a more specific error message can be given
  261. trigger_error(
  262. 'Directory ' . $dir . ' does not exist',
  263. E_USER_WARNING
  264. );
  265. return false;
  266. }
  267. if (function_exists('posix_getuid') && $chmod !== null) {
  268. // POSIX system, we can give more specific advice
  269. if (fileowner($dir) === posix_getuid()) {
  270. // we can chmod it ourselves
  271. $chmod = $chmod | 0700;
  272. if (chmod($dir, $chmod)) {
  273. return true;
  274. }
  275. } elseif (filegroup($dir) === posix_getgid()) {
  276. $chmod = $chmod | 0070;
  277. } else {
  278. // PHP's probably running as nobody, so we'll
  279. // need to give global permissions
  280. $chmod = $chmod | 0777;
  281. }
  282. trigger_error(
  283. 'Directory ' . $dir . ' not writable, ' .
  284. '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