files.inc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <?php
  2. /**
  3. * @file
  4. * General file handling code for Backup and Migrate.
  5. */
  6. define('BACKUP_MIGRATE_FILENAME_MAXLENGTH', 255);
  7. /**
  8. * Add a file to the temporary files list for deletion when we're done.
  9. */
  10. function backup_migrate_temp_files_add($filepath = NULL) {
  11. $files = &drupal_static('backup_migrate_temp_files_add', array());
  12. if (!$filepath) {
  13. return $files;
  14. }
  15. else {
  16. $files[] = $filepath;
  17. }
  18. }
  19. /**
  20. * Delete all temporary files.
  21. */
  22. function _backup_migrate_temp_files_delete() {
  23. if (variable_get('backup_migrate_cleanup_temp_files', BACKUP_MIGRATE_CLEANUP_TEMP_FILES)) {
  24. // Delete the temp files created during this run.
  25. foreach (backup_migrate_temp_files_add() as $file) {
  26. if (file_exists($file) && is_writable($file)) {
  27. _backup_migrate_temp_files_delete_file($file);
  28. }
  29. }
  30. // Delete temp files abandoned for 6 or more hours.
  31. $dir = file_directory_temp();
  32. $expire = time() - variable_get('backup_migrate_cleanup_time', BACKUP_MIGRATE_CLEANUP_TIME);
  33. if (file_exists($dir) && is_dir($dir) && is_readable($dir) && $handle = opendir($dir)) {
  34. while (FALSE !== ($file = @readdir($handle))) {
  35. // Delete 'backup_migrate_' files in the temp directory that are older than the expire time.
  36. // We should only attempt to delete writable files to prevent errors in shared environments.
  37. // This could still cause issues in shared environments with poorly configured file permissions.
  38. if (strpos($file, 'backup_migrate_') === 0 && is_writable("$dir/$file") && @filectime("$dir/$file") < $expire) {
  39. _backup_migrate_temp_files_delete_file("$dir/$file");
  40. }
  41. }
  42. closedir($handle);
  43. }
  44. }
  45. }
  46. /**
  47. * Delete a temporary file or folder.
  48. */
  49. function _backup_migrate_temp_files_delete_file($file) {
  50. if (file_exists($file) && (is_writable($file) || is_link($file))) {
  51. if (!is_link($file) && is_dir($file) && is_readable($file) && $handle = opendir($file)) {
  52. $dir = $file;
  53. while (FALSE !== ($file = @readdir($handle))) {
  54. if ($file != '..' && $file != '.') {
  55. _backup_migrate_temp_files_delete_file("$dir/$file");
  56. }
  57. }
  58. closedir($handle);
  59. rmdir($dir);
  60. }
  61. else {
  62. unlink($file);
  63. }
  64. }
  65. }
  66. /**
  67. * Move files recursively.
  68. */
  69. function _backup_migrate_move_files($from, $to) {
  70. if (is_readable($from)) {
  71. if (is_dir($from) && is_readable($from) && $handle = opendir($from)) {
  72. if (!file_exists($to)) {
  73. mkdir($to);
  74. }
  75. while (FALSE !== ($file = @readdir($handle))) {
  76. if ($file != '..' && $file != '.') {
  77. _backup_migrate_move_files("$from/$file", "$to/$file");
  78. }
  79. }
  80. closedir($handle);
  81. }
  82. else {
  83. rename($from, $to);
  84. }
  85. }
  86. return FALSE;
  87. }
  88. /**
  89. * Create a temporary directory.
  90. */
  91. function backup_migrate_temp_directory() {
  92. $tmp = realpath(file_directory_temp());
  93. // Check the writability of the temp directory.
  94. if (!is_writable(realpath(file_directory_temp()))) {
  95. _backup_migrate_message('Your temporary directory %tmp is not writable. Backup and migrate needs to be able to create temporary files.', array('%tmp' => $tmp), 'error');
  96. }
  97. // Use a full path so that the files can be deleted during the shutdown function if needed.
  98. $file = $tmp . '/' . uniqid('backup_migrate_');
  99. mkdir($file);
  100. backup_migrate_temp_files_add($file);
  101. return $file;
  102. }
  103. /**
  104. * Return a list of backup filetypes.
  105. */
  106. function _backup_migrate_filetypes() {
  107. require_once dirname(__FILE__) . '/filters.inc';
  108. $out = backup_migrate_filters_file_types();
  109. foreach ($out as $key => $info) {
  110. $out[$key]['id'] = empty($info['id']) ? $key : $info['id'];
  111. }
  112. return $out;
  113. }
  114. /**
  115. * Adjust the length of a filename to allow for a string to be appended,
  116. * staying within the maximum filename limit.
  117. */
  118. function _backup_migrate_filename_append_prepare($filename, $append_str) {
  119. $max_name_len = BACKUP_MIGRATE_FILENAME_MAXLENGTH - drupal_strlen($append_str);
  120. if (drupal_strlen($filename) > $max_name_len) {
  121. $filename = drupal_substr($filename, 0, $max_name_len);
  122. }
  123. return $filename;
  124. }
  125. /**
  126. * Construct a filename using token and some cleaning.
  127. */
  128. function _backup_migrate_construct_filename($settings) {
  129. // Get the raw filename from the settings.
  130. $filename = $settings->filename;
  131. // Replace any tokens.
  132. if (module_exists('token') && function_exists('token_replace')) {
  133. $filename = token_replace($filename);
  134. }
  135. // Remove illegal characters.
  136. $filename = _backup_migrate_clean_filename($filename);
  137. // Generate a timestamp if needed.
  138. $timestamp = '';
  139. if ($settings->append_timestamp == 1 && $settings->timestamp_format) {
  140. $timestamp = format_date(time(), 'custom', $settings->timestamp_format);
  141. }
  142. // Trim to length if needed to allow the timestamp to fit into the max filename.
  143. $filename = _backup_migrate_filename_append_prepare($filename, $timestamp);
  144. $filename .= '-' . $timestamp;
  145. $filename = trim($filename, '-');
  146. // If there is no filename, then just call it untitled.
  147. if (drupal_strlen($filename) == 0) {
  148. $filename = 'untitled';
  149. }
  150. return $filename;
  151. }
  152. /**
  153. * Construct a default filename using the site's name.
  154. */
  155. function _backup_migrate_default_filename() {
  156. if (module_exists('token')) {
  157. return '[site:name]';
  158. }
  159. else {
  160. // Cleaning the string isn't strictly necessary but it looks better in the
  161. // settings field.
  162. return _backup_migrate_clean_filename(variable_get('site_name', "backup_migrate"));
  163. }
  164. }
  165. /**
  166. * Clean up a filename to remove unsafe characters.
  167. */
  168. function _backup_migrate_clean_filename($filename) {
  169. $filename = preg_replace("/[^a-zA-Z0-9\.\-_]/", "", $filename);
  170. return $filename;
  171. }
  172. /**
  173. * An output buffer callback which simply throws away the buffer instead of sending it to the browser.
  174. */
  175. function _backup_migrate_file_dispose_buffer($buffer) {
  176. return "";
  177. }
  178. /**
  179. * A backup file which allows for saving to and reading from the server.
  180. */
  181. class backup_file {
  182. public $file_info = array();
  183. public $type = array();
  184. public $ext = array();
  185. public $path = "";
  186. public $name = "";
  187. public $handle = NULL;
  188. /**
  189. * Construct a file object given a file path, or create a temp file for writing.
  190. */
  191. public function __construct($params = array()) {
  192. if (isset($params['filepath']) && file_exists($params['filepath'])) {
  193. $this->set_filepath($params['filepath']);
  194. }
  195. else {
  196. $this->set_file_info($params);
  197. $this->temporary_file();
  198. }
  199. }
  200. /**
  201. * Get the file_id if the file has been saved to a destination.
  202. */
  203. public function file_id() {
  204. // The default file_id is the filename. Destinations can override the file_id if needed.
  205. return isset($this->file_info['file_id']) ? $this->file_info['file_id'] : $this->filename();
  206. }
  207. /**
  208. * Get the current filepath.
  209. */
  210. public function filepath() {
  211. if ($filepath = drupal_realpath($this->path)) {
  212. return $filepath;
  213. }
  214. return $this->path;
  215. }
  216. /**
  217. * Get the final filename.
  218. */
  219. public function filename($name = NULL) {
  220. if ($name) {
  221. $this->name = $name;
  222. }
  223. $extension_str = '.' . $this->extension();
  224. $this->name = _backup_migrate_filename_append_prepare($this->name, $extension_str);
  225. return $this->name . $extension_str;
  226. }
  227. /**
  228. * Set the current filepath.
  229. */
  230. public function set_filepath($path) {
  231. $this->path = $path;
  232. $params = array(
  233. 'filename' => basename($path),
  234. 'file_id' => basename($path),
  235. );
  236. if (file_exists($path)) {
  237. $params['filesize'] = filesize($path);
  238. $params['filetime'] = filectime($path);
  239. }
  240. $this->set_file_info($params);
  241. }
  242. /**
  243. * Get one or all pieces of info for the file.
  244. */
  245. public function info($key = NULL) {
  246. if ($key) {
  247. return @$this->file_info[$key];
  248. }
  249. return $this->file_info;
  250. }
  251. /**
  252. * Get one or all pieces of info for the file.
  253. */
  254. public function info_set($key, $value) {
  255. $this->file_info[$key] = $value;
  256. }
  257. /**
  258. * Get the file extension.
  259. */
  260. public function extension() {
  261. return implode(".", $this->ext);
  262. }
  263. /**
  264. * Get the file type.
  265. */
  266. public function type() {
  267. return $this->type;
  268. }
  269. /**
  270. * Get the file mimetype.
  271. */
  272. public function mimetype() {
  273. return @$this->type['filemime'] ? $this->type['filemime'] : 'application/octet-stream';
  274. }
  275. /**
  276. * Get the file mimetype.
  277. */
  278. public function type_id() {
  279. return @$this->type['id'];
  280. }
  281. /**
  282. *
  283. */
  284. public function filesize() {
  285. if (empty($this->file_info['filesize'])) {
  286. $this->calculate_filesize();
  287. }
  288. return $this->file_info['filesize'];
  289. }
  290. /**
  291. *
  292. */
  293. public function calculate_filesize() {
  294. $this->file_info['filesize'] = '';
  295. if (!empty($this->path) && file_exists($this->path)) {
  296. $this->file_info['filesize'] = filesize($this->path);
  297. }
  298. }
  299. /**
  300. * Can this file be used to backup to.
  301. */
  302. public function can_backup() {
  303. return @$this->type['backup'];
  304. }
  305. /**
  306. * Can this file be used to restore to.
  307. */
  308. public function can_restore() {
  309. return @$this->type['restore'];
  310. }
  311. /**
  312. * Can this file be used to restore to.
  313. */
  314. public function is_recognized_type() {
  315. return @$this->type['restore'] || @$this->type['backup'];
  316. }
  317. /**
  318. * Open a file for reading or writing.
  319. */
  320. public function open($write = FALSE, $binary = FALSE) {
  321. if (!$this->handle) {
  322. $path = $this->filepath();
  323. // Check if the file can be read/written.
  324. if ($write && ((file_exists($path) && !is_writable($path)) || !is_writable(dirname($path)))) {
  325. _backup_migrate_message('The file %path cannot be written to.', array('%path' => $path), 'error');
  326. return FALSE;
  327. }
  328. if (!$write && !is_readable($path)) {
  329. _backup_migrate_message('The file %path cannot be read.', array('%path' => $path), 'error');
  330. return FALSE;
  331. }
  332. // Open the file.
  333. $mode = ($write ? "w" : "r") . ($binary ? "b" : "");
  334. $this->handle = fopen($path, $mode);
  335. return $this->handle;
  336. }
  337. return NULL;
  338. }
  339. /**
  340. * Close a file when we're done reading/writing.
  341. */
  342. public function close() {
  343. fclose($this->handle);
  344. $this->handle = NULL;
  345. }
  346. /**
  347. * Write a line to the file.
  348. */
  349. public function write($data) {
  350. if (!$this->handle) {
  351. $this->handle = $this->open(TRUE);
  352. }
  353. if ($this->handle) {
  354. fwrite($this->handle, $data);
  355. }
  356. }
  357. /**
  358. * Read a line from the file.
  359. */
  360. public function read($size = NULL) {
  361. if (!$this->handle) {
  362. $this->handle = $this->open();
  363. }
  364. if ($this->handle && !feof($this->handle)) {
  365. return $size ? fread($this->handle, $size) : fgets($this->handle);
  366. }
  367. return NULL;
  368. }
  369. /**
  370. * Write data to the file.
  371. */
  372. public function put_contents($data) {
  373. file_put_contents($this->filepath(), $data);
  374. }
  375. /**
  376. * Read data from the file.
  377. */
  378. public function get_contents() {
  379. return file_get_contents($this->filepath());
  380. }
  381. /**
  382. * Transfer file using http to client. Similar to the built in file_transfer,
  383. * but it calls module_invoke_all('exit') so that temp files can be deleted.
  384. */
  385. public function transfer() {
  386. $headers = array(
  387. array('key' => 'Content-Type', 'value' => $this->mimetype()),
  388. array('key' => 'Content-Disposition', 'value' => 'attachment; filename="' . $this->filename() . '"'),
  389. );
  390. // In some circumstances, web-servers will double compress gzipped files.
  391. // This may help aleviate that issue by disabling mod-deflate.
  392. if ($this->mimetype() == 'application/x-gzip') {
  393. $headers[] = array('key' => 'Content-Encoding', 'value' => 'gzip');
  394. }
  395. if ($size = $this->info('filesize')) {
  396. $headers[] = array('key' => 'Content-Length', 'value' => $size);
  397. }
  398. // Suppress the warning you get when the buffer is empty.
  399. @ob_end_clean();
  400. if ($this->open(FALSE, TRUE)) {
  401. foreach ($headers as $header) {
  402. // To prevent HTTP header injection, we delete new lines that are
  403. // not followed by a space or a tab.
  404. // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
  405. $header['value'] = preg_replace('/\r?\n(?!\t| )/', '', $header['value']);
  406. drupal_add_http_header($header['key'], $header['value']);
  407. }
  408. // Transfer file in 1024 byte chunks to save memory usage.
  409. while ($data = $this->read(1024)) {
  410. print $data;
  411. }
  412. $this->close();
  413. // Ask devel.module not to print it's footer.
  414. $GLOBALS['devel_shutdown'] = FALSE;
  415. }
  416. else {
  417. drupal_not_found();
  418. }
  419. // Start buffering and throw away the results so that errors don't get appended to the file.
  420. ob_start('_backup_migrate_file_dispose_buffer');
  421. backup_migrate_cleanup();
  422. module_invoke_all('exit');
  423. exit();
  424. }
  425. /**
  426. * Push a file extension onto the file and return the previous file path.
  427. */
  428. public function push_type($extension) {
  429. $types = _backup_migrate_filetypes();
  430. if ($type = @$types[$extension]) {
  431. $this->push_filetype($type);
  432. }
  433. $out = $this->filepath();
  434. $this->temporary_file();
  435. return $out;
  436. }
  437. /**
  438. * Push a file extension onto the file and return the previous file path.
  439. */
  440. public function pop_type() {
  441. $out = new backup_file(array('filepath' => $this->filepath()));
  442. $this->pop_filetype();
  443. $this->temporary_file();
  444. return $out;
  445. }
  446. /**
  447. * Set the current file type.
  448. */
  449. public function set_filetype($type) {
  450. $this->type = $type;
  451. $this->ext = array($type['extension']);
  452. }
  453. /**
  454. * Set the current file type.
  455. */
  456. public function push_filetype($type) {
  457. $this->ext[] = $type['extension'];
  458. $this->type = $type;
  459. }
  460. /**
  461. * Pop the current file type.
  462. */
  463. public function pop_filetype() {
  464. array_pop($this->ext);
  465. $this->detect_filetype_from_extension();
  466. }
  467. /**
  468. * Set the file info.
  469. */
  470. public function set_file_info($file_info) {
  471. $this->file_info = $file_info;
  472. $this->ext = explode('.', @$this->file_info['filename']);
  473. // Remove the underscores added to file extensions by Drupal's upload security.
  474. foreach ($this->ext as $key => $val) {
  475. $this->ext[$key] = trim($val, '_');
  476. }
  477. $this->filename(array_shift($this->ext));
  478. $this->detect_filetype_from_extension();
  479. }
  480. /**
  481. * Get the filetype info of the given file, or false if the file is not a valid type.
  482. */
  483. public function detect_filetype_from_extension() {
  484. $ext = end($this->ext);
  485. $this->type = array();
  486. $types = _backup_migrate_filetypes();
  487. foreach ($types as $key => $type) {
  488. if (trim($ext, "_0123456789") === $type['extension']) {
  489. $this->type = $type;
  490. $this->type['id'] = $key;
  491. }
  492. }
  493. }
  494. /**
  495. * Get a temporary file name with path.
  496. */
  497. public function temporary_file() {
  498. $file = drupal_tempnam('temporary://', 'backup_migrate_');
  499. // Add the version without the extension. The tempnam function creates this for us.
  500. backup_migrate_temp_files_add($file);
  501. if ($this->extension()) {
  502. $file .= '.' . $this->extension();
  503. // Add the version with the extension. This is the one we will actually use.
  504. backup_migrate_temp_files_add($file);
  505. }
  506. $this->path = $file;
  507. }
  508. }