files.inc 11 KB

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