files.inc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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. static $files = 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. // Delete the temp files created during this run.
  24. foreach (backup_migrate_temp_files_add() as $file) {
  25. $file = drupal_realpath($file);
  26. if (file_exists($file) && is_writable($file)) {
  27. unlink($file);
  28. }
  29. }
  30. // Delete temp files abandoned for 6 or more hours.
  31. $dir = file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath();
  32. $expire = time() - variable_get('backup_migrate_cleanup_time', 21600);
  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. unlink("$dir/$file");
  40. }
  41. }
  42. closedir($handle);
  43. }
  44. }
  45. /**
  46. * Return a list of backup filetypes.
  47. */
  48. function _backup_migrate_filetypes() {
  49. backup_migrate_include('filters');
  50. $out = backup_migrate_filters_file_types();
  51. foreach ($out as $key => $info) {
  52. $out[$key]['id'] = empty($info['id']) ? $key : $info['id'];
  53. }
  54. return $out;
  55. }
  56. /**
  57. * Adjust the length of a filename to allow for a string to be appended,
  58. * staying within the maximum filename limit.
  59. */
  60. function _backup_migrate_filename_append_prepare($filename, $append_str) {
  61. $max_name_len = BACKUP_MIGRATE_FILENAME_MAXLENGTH - drupal_strlen($append_str);
  62. if (drupal_strlen($filename) > $max_name_len) {
  63. $filename = drupal_substr($filename, 0, $max_name_len);
  64. }
  65. return $filename;
  66. }
  67. /**
  68. * Construct a filename using token and some cleaning.
  69. */
  70. function _backup_migrate_construct_filename($filename, $timestamp='') {
  71. if (module_exists('token')) {
  72. $filename = token_replace($filename);
  73. }
  74. $filename = preg_replace("/[^a-zA-Z0-9\.\-_]/", "", $filename);
  75. $filename = _backup_migrate_filename_append_prepare($filename, $timestamp);
  76. $filename .= '-' . $timestamp;
  77. $filename = trim($filename, '-');
  78. if (drupal_strlen($filename) == 0) {
  79. $filename = 'untitled';
  80. }
  81. return $filename;
  82. }
  83. /**
  84. * Construct a default filename using the site's name.
  85. */
  86. function _backup_migrate_default_filename() {
  87. if (module_exists('token')) {
  88. return '[site:name]';
  89. }
  90. else {
  91. // Cleaning the string isn't strictly necessary but it looks better in the settings field.
  92. return variable_get('site_name', 'backup_migrate');
  93. }
  94. }
  95. /**
  96. * An output buffer callback which simply throws away the buffer instead of sending it to the browser.
  97. */
  98. function _backup_migrate_file_dispose_buffer($buffer) {
  99. return "";
  100. }
  101. /**
  102. * A backup file which allows for saving to and reading from the server.
  103. */
  104. class backup_file {
  105. var $file_info = array();
  106. var $type = array();
  107. var $ext = array();
  108. var $path = "";
  109. var $name = "";
  110. var $handle = NULL;
  111. /**
  112. * Construct a file object given a file path, or create a temp file for writing.
  113. */
  114. function backup_file($params = array()) {
  115. if (isset($params['filepath']) && file_exists($params['filepath'])) {
  116. $this->set_filepath($params['filepath']);
  117. }
  118. else {
  119. $this->set_file_info($params);
  120. $this->temporary_file();
  121. }
  122. }
  123. /**
  124. * Get the file_id if the file has been saved to a destination.
  125. */
  126. function file_id() {
  127. // The default file_id is the filename. Destinations can override the file_id if needed.
  128. return isset($this->file_info['file_id']) ? $this->file_info['file_id'] : $this->filename();
  129. }
  130. /**
  131. * Get the current filepath.
  132. */
  133. function filepath() {
  134. return drupal_realpath($this->path);
  135. }
  136. /**
  137. * Get the final filename.
  138. */
  139. function filename($name = NULL) {
  140. if ($name) {
  141. $this->name = $name;
  142. }
  143. return $this->name .'.'. $this->extension();
  144. }
  145. /**
  146. * Set the current filepath.
  147. */
  148. function set_filepath($path) {
  149. $this->path = $path;
  150. $params = array(
  151. 'filename' => basename($path),
  152. );
  153. if (file_exists($path)) {
  154. $params['filesize'] = filesize($path);
  155. $params['filetime'] = filemtime($path);
  156. }
  157. $this->set_file_info($params);
  158. }
  159. /**
  160. * Get one or all pieces of info for the file.
  161. */
  162. function info($key = NULL) {
  163. if ($key) {
  164. return @$this->file_info[$key];
  165. }
  166. return $this->file_info;
  167. }
  168. /**
  169. * Get the file extension.
  170. */
  171. function extension() {
  172. return implode(".", $this->ext);
  173. }
  174. /**
  175. * Get the file type.
  176. */
  177. function type() {
  178. return $this->type;
  179. }
  180. /**
  181. * Get the file mimetype.
  182. */
  183. function mimetype() {
  184. return @$this->type['filemime'] ? $this->type['filemime'] : 'application/octet-stream';
  185. }
  186. /**
  187. * Get the file mimetype.
  188. */
  189. function type_id() {
  190. return @$this->type['id'];
  191. }
  192. /**
  193. * Can this file be used to backup to.
  194. */
  195. function can_backup() {
  196. return @$this->type['backup'];
  197. }
  198. /**
  199. * Can this file be used to restore to.
  200. */
  201. function can_restore() {
  202. return @$this->type['restore'];
  203. }
  204. /**
  205. * Can this file be used to restore to.
  206. */
  207. function is_recognized_type() {
  208. return @$this->type['restore'] || @$this->type['backup'];
  209. }
  210. /**
  211. * Open a file for reading or writing.
  212. */
  213. function open($write = FALSE, $binary = FALSE) {
  214. if (!$this->handle) {
  215. $path = $this->filepath();
  216. // Check if the file can be read/written.
  217. if ($write && ((file_exists($path) && !is_writable($path)) || !is_writable(dirname($path)))) {
  218. _backup_migrate_message('The file %path cannot be written to.', array('%path' => $path), 'error');
  219. return FALSE;
  220. }
  221. if (!$write && !is_readable($path)) {
  222. _backup_migrate_message('The file %path cannot be read.', array('%path' => $path), 'error');
  223. return FALSE;
  224. }
  225. // Open the file.
  226. $mode = ($write ? "w" : "r") . ($binary ? "b" : "");
  227. $this->handle = fopen($path, $mode);
  228. return $this->handle;
  229. }
  230. return NULL;
  231. }
  232. /**
  233. * Close a file when we're done reading/writing.
  234. */
  235. function close() {
  236. fclose($this->handle);
  237. $this->handle = NULL;
  238. }
  239. /**
  240. * Write a line to the file.
  241. */
  242. function write($data) {
  243. if (!$this->handle) {
  244. $this->handle = $this->open(TRUE);
  245. }
  246. if ($this->handle) {
  247. fwrite($this->handle, $data);
  248. }
  249. }
  250. /**
  251. * Read a line from the file.
  252. */
  253. function read($size = NULL) {
  254. if (!$this->handle) {
  255. $this->handle = $this->open();
  256. }
  257. if ($this->handle && !feof($this->handle)) {
  258. return $size ? fread($this->handle, $size) : fgets($this->handle);
  259. }
  260. return NULL;
  261. }
  262. /**
  263. * Write data to the file.
  264. */
  265. function put_contents($data) {
  266. file_put_contents($this->filepath(), $data);
  267. }
  268. /**
  269. * Read data from the file.
  270. */
  271. function get_contents() {
  272. return file_get_contents($this->filepath());
  273. }
  274. /**
  275. * Transfer file using http to client. Similar to the built in file_transfer,
  276. * but it calls module_invoke_all('exit') so that temp files can be deleted.
  277. */
  278. function transfer() {
  279. $headers = array(
  280. array('key' => 'Content-Type', 'value' => $this->mimetype()),
  281. array('key' => 'Content-Disposition', 'value' => 'attachment; filename="'. $this->filename() .'"'),
  282. );
  283. // In some circumstances, web-servers will double compress gzipped files.
  284. // This may help aleviate that issue by disabling mod-deflate.
  285. if ($this->mimetype() == 'application/x-gzip') {
  286. $headers[] = 'Content-Encoding: gzip';
  287. }
  288. if ($size = $this->info('filesize')) {
  289. $headers[] = array('key' => 'Content-Length', 'value' => $size);
  290. }
  291. // Suppress the warning you get when the buffer is empty.
  292. @ob_end_clean();
  293. if ($this->open(FALSE, TRUE)) {
  294. foreach ($headers as $header) {
  295. // To prevent HTTP header injection, we delete new lines that are
  296. // not followed by a space or a tab.
  297. // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
  298. $header['value'] = preg_replace('/\r?\n(?!\t| )/', '', $header['value']);
  299. drupal_add_http_header($header['key'], $header['value']);
  300. }
  301. // Transfer file in 1024 byte chunks to save memory usage.
  302. while ($data = $this->read(1024)) {
  303. print $data;
  304. }
  305. $this->close();
  306. // Ask devel.module not to print it's footer.
  307. $GLOBALS['devel_shutdown'] = FALSE;
  308. }
  309. else {
  310. drupal_not_found();
  311. }
  312. // Start buffering and throw away the results so that errors don't get appended to the file.
  313. ob_start('_backup_migrate_file_dispose_buffer');
  314. backup_migrate_cleanup();
  315. module_invoke_all('exit');
  316. exit();
  317. }
  318. /**
  319. * Push a file extension onto the file and return the previous file path.
  320. */
  321. function push_type($extension) {
  322. $types = _backup_migrate_filetypes();
  323. if ($type = @$types[$extension]) {
  324. $this->push_filetype($type);
  325. }
  326. $out = $this->filepath();
  327. $this->temporary_file();
  328. return $out;
  329. }
  330. /**
  331. * Push a file extension onto the file and return the previous file path.
  332. */
  333. function pop_type() {
  334. $out = new backup_file(array('filepath' => $this->filepath()));
  335. $this->pop_filetype();
  336. $this->temporary_file();
  337. return $out;
  338. }
  339. /**
  340. * Set the current file type.
  341. */
  342. function set_filetype($type) {
  343. $this->type = $type;
  344. $this->ext = array($type['extension']);
  345. }
  346. /**
  347. * Set the current file type.
  348. */
  349. function push_filetype($type) {
  350. $this->ext[] = $type['extension'];
  351. $this->type = $type;
  352. }
  353. /**
  354. * Pop the current file type.
  355. */
  356. function pop_filetype() {
  357. array_pop($this->ext);
  358. $this->detect_filetype_from_extension();
  359. }
  360. /**
  361. * Set the file info.
  362. */
  363. function set_file_info($file_info) {
  364. $this->file_info = $file_info;
  365. $this->ext = explode('.', @$this->file_info['filename']);
  366. // Remove the underscores added to file extensions by Drupal's upload security.
  367. foreach ($this->ext as $key => $val) {
  368. $this->ext[$key] = trim($val, '_');
  369. }
  370. $this->filename(array_shift($this->ext));
  371. $this->detect_filetype_from_extension();
  372. }
  373. /**
  374. * Get the filetype info of the given file, or false if the file is not a valid type.
  375. */
  376. function detect_filetype_from_extension() {
  377. $ext = end($this->ext);
  378. $this->type = array();
  379. $types = _backup_migrate_filetypes();
  380. foreach ($types as $key => $type) {
  381. if (trim($ext, "_0123456789") === $type['extension']) {
  382. $this->type = $type;
  383. $this->type['id'] = $key;
  384. }
  385. }
  386. }
  387. /**
  388. * Get a temporary file name with path.
  389. */
  390. function temporary_file() {
  391. $file = drupal_tempnam('temporary://', 'backup_migrate_');
  392. // Add the version without the extension. The tempnam function creates this for us.
  393. backup_migrate_temp_files_add($file);
  394. if ($this->extension()) {
  395. $file .= '.'. $this->extension();
  396. // Add the version with the extension. This is the one we will actually use.
  397. backup_migrate_temp_files_add($file);
  398. }
  399. $this->path = $file;
  400. }
  401. }