PageRenderTime 77ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/administrator/components/com_joomlaupdate/restore.php

https://bitbucket.org/asosso/joomla25
PHP | 5744 lines | 3936 code | 669 blank | 1139 comment | 678 complexity | ec97629bbbfd771985a3bbd699df18e5 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * Akeeba Restore
  4. * A JSON-powered JPA, JPS and ZIP archive extraction library
  5. *
  6. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU GPL v2 or - at your option - any later version
  8. */
  9. define('_AKEEBA_RESTORATION', 1);
  10. defined('DS') or define('DS', DIRECTORY_SEPARATOR);
  11. // Unarchiver run states
  12. define('AK_STATE_NOFILE', 0); // File header not read yet
  13. define('AK_STATE_HEADER', 1); // File header read; ready to process data
  14. define('AK_STATE_DATA', 2); // Processing file data
  15. define('AK_STATE_DATAREAD', 3); // Finished processing file data; ready to post-process
  16. define('AK_STATE_POSTPROC', 4); // Post-processing
  17. define('AK_STATE_DONE', 5); // Done with post-processing
  18. /* Windows system detection */
  19. if(!defined('_AKEEBA_IS_WINDOWS'))
  20. {
  21. if (function_exists('php_uname'))
  22. define('_AKEEBA_IS_WINDOWS', stristr(php_uname(), 'windows'));
  23. else
  24. define('_AKEEBA_IS_WINDOWS', DIRECTORY_SEPARATOR == '\\');
  25. }
  26. // Make sure the locale is correct for basename() to work
  27. if(function_exists('setlocale'))
  28. {
  29. @setlocale(LC_ALL, 'en_US.UTF8');
  30. }
  31. // fnmatch not available on non-POSIX systems
  32. // Thanks to soywiz@php.net for this usefull alternative function [http://gr2.php.net/fnmatch]
  33. if (!function_exists('fnmatch')) {
  34. function fnmatch($pattern, $string) {
  35. return @preg_match(
  36. '/^' . strtr(addcslashes($pattern, '/\\.+^$(){}=!<>|'),
  37. array('*' => '.*', '?' => '.?')) . '$/i', $string
  38. );
  39. }
  40. }
  41. // Unicode-safe binary data length function
  42. if(function_exists('mb_strlen')) {
  43. function akstringlen($string) { return mb_strlen($string,'8bit'); }
  44. } else {
  45. function akstringlen($string) { return strlen($string); }
  46. }
  47. /**
  48. * Gets a query parameter from GET or POST data
  49. * @param $key
  50. * @param $default
  51. */
  52. function getQueryParam( $key, $default = null )
  53. {
  54. $value = null;
  55. if(array_key_exists($key, $_REQUEST)) {
  56. $value = $_REQUEST[$key];
  57. } elseif(array_key_exists($key, $_POST)) {
  58. $value = $_POST[$key];
  59. } elseif(array_key_exists($key, $_GET)) {
  60. $value = $_GET[$key];
  61. } else {
  62. return $default;
  63. }
  64. if(get_magic_quotes_gpc() && !is_null($value)) $value=stripslashes($value);
  65. return $value;
  66. }
  67. /**
  68. * Akeeba Backup's JSON compatibility layer
  69. *
  70. * On systems where json_encode and json_decode are not available, Akeeba
  71. * Backup will attempt to use PEAR's Services_JSON library to emulate them.
  72. * A copy of this library is included in this file and will be used if and
  73. * only if it isn't already loaded, e.g. due to PEAR's auto-loading, or a
  74. * 3PD extension loading it for its own purposes.
  75. */
  76. /**
  77. * Converts to and from JSON format.
  78. *
  79. * JSON (JavaScript Object Notation) is a lightweight data-interchange
  80. * format. It is easy for humans to read and write. It is easy for machines
  81. * to parse and generate. It is based on a subset of the JavaScript
  82. * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
  83. * This feature can also be found in Python. JSON is a text format that is
  84. * completely language independent but uses conventions that are familiar
  85. * to programmers of the C-family of languages, including C, C++, C#, Java,
  86. * JavaScript, Perl, TCL, and many others. These properties make JSON an
  87. * ideal data-interchange language.
  88. *
  89. * This package provides a simple encoder and decoder for JSON notation. It
  90. * is intended for use with client-side Javascript applications that make
  91. * use of HTTPRequest to perform server communication functions - data can
  92. * be encoded into JSON notation for use in a client-side javascript, or
  93. * decoded from incoming Javascript requests. JSON format is native to
  94. * Javascript, and can be directly eval()'ed with no further parsing
  95. * overhead
  96. *
  97. * All strings should be in ASCII or UTF-8 format!
  98. *
  99. * LICENSE: Redistribution and use in source and binary forms, with or
  100. * without modification, are permitted provided that the following
  101. * conditions are met: Redistributions of source code must retain the
  102. * above copyright notice, this list of conditions and the following
  103. * disclaimer. Redistributions in binary form must reproduce the above
  104. * copyright notice, this list of conditions and the following disclaimer
  105. * in the documentation and/or other materials provided with the
  106. * distribution.
  107. *
  108. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  109. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  110. * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
  111. * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  112. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  113. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  114. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  115. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  116. * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  117. * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  118. * DAMAGE.
  119. *
  120. * @category
  121. * @package Services_JSON
  122. * @author Michal Migurski <mike-json@teczno.com>
  123. * @author Matt Knapp <mdknapp[at]gmail[dot]com>
  124. * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
  125. * @copyright 2005 Michal Migurski
  126. * @version CVS: $Id: restore.php 612 2011-05-19 08:26:26Z nikosdion $
  127. * @license http://www.opensource.org/licenses/bsd-license.php
  128. * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
  129. */
  130. if(!defined('JSON_FORCE_OBJECT'))
  131. {
  132. define('JSON_FORCE_OBJECT', 1);
  133. }
  134. if(!defined('SERVICES_JSON_SLICE'))
  135. {
  136. /**
  137. * Marker constant for Services_JSON::decode(), used to flag stack state
  138. */
  139. define('SERVICES_JSON_SLICE', 1);
  140. /**
  141. * Marker constant for Services_JSON::decode(), used to flag stack state
  142. */
  143. define('SERVICES_JSON_IN_STR', 2);
  144. /**
  145. * Marker constant for Services_JSON::decode(), used to flag stack state
  146. */
  147. define('SERVICES_JSON_IN_ARR', 3);
  148. /**
  149. * Marker constant for Services_JSON::decode(), used to flag stack state
  150. */
  151. define('SERVICES_JSON_IN_OBJ', 4);
  152. /**
  153. * Marker constant for Services_JSON::decode(), used to flag stack state
  154. */
  155. define('SERVICES_JSON_IN_CMT', 5);
  156. /**
  157. * Behavior switch for Services_JSON::decode()
  158. */
  159. define('SERVICES_JSON_LOOSE_TYPE', 16);
  160. /**
  161. * Behavior switch for Services_JSON::decode()
  162. */
  163. define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
  164. }
  165. /**
  166. * Converts to and from JSON format.
  167. *
  168. * Brief example of use:
  169. *
  170. * <code>
  171. * // create a new instance of Services_JSON
  172. * $json = new Services_JSON();
  173. *
  174. * // convert a complexe value to JSON notation, and send it to the browser
  175. * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
  176. * $output = $json->encode($value);
  177. *
  178. * print($output);
  179. * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
  180. *
  181. * // accept incoming POST data, assumed to be in JSON notation
  182. * $input = file_get_contents('php://input', 1000000);
  183. * $value = $json->decode($input);
  184. * </code>
  185. */
  186. if(!class_exists('Akeeba_Services_JSON'))
  187. {
  188. class Akeeba_Services_JSON
  189. {
  190. /**
  191. * constructs a new JSON instance
  192. *
  193. * @param int $use object behavior flags; combine with boolean-OR
  194. *
  195. * possible values:
  196. * - SERVICES_JSON_LOOSE_TYPE: loose typing.
  197. * "{...}" syntax creates associative arrays
  198. * instead of objects in decode().
  199. * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
  200. * Values which can't be encoded (e.g. resources)
  201. * appear as NULL instead of throwing errors.
  202. * By default, a deeply-nested resource will
  203. * bubble up with an error, so all return values
  204. * from encode() should be checked with isError()
  205. */
  206. function Akeeba_Services_JSON($use = 0)
  207. {
  208. $this->use = $use;
  209. }
  210. /**
  211. * convert a string from one UTF-16 char to one UTF-8 char
  212. *
  213. * Normally should be handled by mb_convert_encoding, but
  214. * provides a slower PHP-only method for installations
  215. * that lack the multibye string extension.
  216. *
  217. * @param string $utf16 UTF-16 character
  218. * @return string UTF-8 character
  219. * @access private
  220. */
  221. function utf162utf8($utf16)
  222. {
  223. // oh please oh please oh please oh please oh please
  224. if(function_exists('mb_convert_encoding')) {
  225. return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
  226. }
  227. $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
  228. switch(true) {
  229. case ((0x7F & $bytes) == $bytes):
  230. // this case should never be reached, because we are in ASCII range
  231. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  232. return chr(0x7F & $bytes);
  233. case (0x07FF & $bytes) == $bytes:
  234. // return a 2-byte UTF-8 character
  235. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  236. return chr(0xC0 | (($bytes >> 6) & 0x1F))
  237. . chr(0x80 | ($bytes & 0x3F));
  238. case (0xFFFF & $bytes) == $bytes:
  239. // return a 3-byte UTF-8 character
  240. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  241. return chr(0xE0 | (($bytes >> 12) & 0x0F))
  242. . chr(0x80 | (($bytes >> 6) & 0x3F))
  243. . chr(0x80 | ($bytes & 0x3F));
  244. }
  245. // ignoring UTF-32 for now, sorry
  246. return '';
  247. }
  248. /**
  249. * convert a string from one UTF-8 char to one UTF-16 char
  250. *
  251. * Normally should be handled by mb_convert_encoding, but
  252. * provides a slower PHP-only method for installations
  253. * that lack the multibye string extension.
  254. *
  255. * @param string $utf8 UTF-8 character
  256. * @return string UTF-16 character
  257. * @access private
  258. */
  259. function utf82utf16($utf8)
  260. {
  261. // oh please oh please oh please oh please oh please
  262. if(function_exists('mb_convert_encoding')) {
  263. return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
  264. }
  265. switch(strlen($utf8)) {
  266. case 1:
  267. // this case should never be reached, because we are in ASCII range
  268. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  269. return $utf8;
  270. case 2:
  271. // return a UTF-16 character from a 2-byte UTF-8 char
  272. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  273. return chr(0x07 & (ord($utf8{0}) >> 2))
  274. . chr((0xC0 & (ord($utf8{0}) << 6))
  275. | (0x3F & ord($utf8{1})));
  276. case 3:
  277. // return a UTF-16 character from a 3-byte UTF-8 char
  278. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  279. return chr((0xF0 & (ord($utf8{0}) << 4))
  280. | (0x0F & (ord($utf8{1}) >> 2)))
  281. . chr((0xC0 & (ord($utf8{1}) << 6))
  282. | (0x7F & ord($utf8{2})));
  283. }
  284. // ignoring UTF-32 for now, sorry
  285. return '';
  286. }
  287. /**
  288. * encodes an arbitrary variable into JSON format
  289. *
  290. * @param mixed $var any number, boolean, string, array, or object to be encoded.
  291. * see argument 1 to Services_JSON() above for array-parsing behavior.
  292. * if var is a strng, note that encode() always expects it
  293. * to be in ASCII or UTF-8 format!
  294. *
  295. * @return mixed JSON string representation of input var or an error if a problem occurs
  296. * @access public
  297. */
  298. function encode($var)
  299. {
  300. switch (gettype($var)) {
  301. case 'boolean':
  302. return $var ? 'true' : 'false';
  303. case 'NULL':
  304. return 'null';
  305. case 'integer':
  306. return (int) $var;
  307. case 'double':
  308. case 'float':
  309. return (float) $var;
  310. case 'string':
  311. // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
  312. $ascii = '';
  313. $strlen_var = strlen($var);
  314. /*
  315. * Iterate over every character in the string,
  316. * escaping with a slash or encoding to UTF-8 where necessary
  317. */
  318. for ($c = 0; $c < $strlen_var; ++$c) {
  319. $ord_var_c = ord($var{$c});
  320. switch (true) {
  321. case $ord_var_c == 0x08:
  322. $ascii .= '\b';
  323. break;
  324. case $ord_var_c == 0x09:
  325. $ascii .= '\t';
  326. break;
  327. case $ord_var_c == 0x0A:
  328. $ascii .= '\n';
  329. break;
  330. case $ord_var_c == 0x0C:
  331. $ascii .= '\f';
  332. break;
  333. case $ord_var_c == 0x0D:
  334. $ascii .= '\r';
  335. break;
  336. case $ord_var_c == 0x22:
  337. case $ord_var_c == 0x2F:
  338. case $ord_var_c == 0x5C:
  339. // double quote, slash, slosh
  340. $ascii .= '\\'.$var{$c};
  341. break;
  342. case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
  343. // characters U-00000000 - U-0000007F (same as ASCII)
  344. $ascii .= $var{$c};
  345. break;
  346. case (($ord_var_c & 0xE0) == 0xC0):
  347. // characters U-00000080 - U-000007FF, mask 110XXXXX
  348. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  349. $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
  350. $c += 1;
  351. $utf16 = $this->utf82utf16($char);
  352. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  353. break;
  354. case (($ord_var_c & 0xF0) == 0xE0):
  355. // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  356. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  357. $char = pack('C*', $ord_var_c,
  358. ord($var{$c + 1}),
  359. ord($var{$c + 2}));
  360. $c += 2;
  361. $utf16 = $this->utf82utf16($char);
  362. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  363. break;
  364. case (($ord_var_c & 0xF8) == 0xF0):
  365. // characters U-00010000 - U-001FFFFF, mask 11110XXX
  366. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  367. $char = pack('C*', $ord_var_c,
  368. ord($var{$c + 1}),
  369. ord($var{$c + 2}),
  370. ord($var{$c + 3}));
  371. $c += 3;
  372. $utf16 = $this->utf82utf16($char);
  373. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  374. break;
  375. case (($ord_var_c & 0xFC) == 0xF8):
  376. // characters U-00200000 - U-03FFFFFF, mask 111110XX
  377. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  378. $char = pack('C*', $ord_var_c,
  379. ord($var{$c + 1}),
  380. ord($var{$c + 2}),
  381. ord($var{$c + 3}),
  382. ord($var{$c + 4}));
  383. $c += 4;
  384. $utf16 = $this->utf82utf16($char);
  385. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  386. break;
  387. case (($ord_var_c & 0xFE) == 0xFC):
  388. // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  389. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  390. $char = pack('C*', $ord_var_c,
  391. ord($var{$c + 1}),
  392. ord($var{$c + 2}),
  393. ord($var{$c + 3}),
  394. ord($var{$c + 4}),
  395. ord($var{$c + 5}));
  396. $c += 5;
  397. $utf16 = $this->utf82utf16($char);
  398. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  399. break;
  400. }
  401. }
  402. return '"'.$ascii.'"';
  403. case 'array':
  404. /*
  405. * As per JSON spec if any array key is not an integer
  406. * we must treat the the whole array as an object. We
  407. * also try to catch a sparsely populated associative
  408. * array with numeric keys here because some JS engines
  409. * will create an array with empty indexes up to
  410. * max_index which can cause memory issues and because
  411. * the keys, which may be relevant, will be remapped
  412. * otherwise.
  413. *
  414. * As per the ECMA and JSON specification an object may
  415. * have any string as a property. Unfortunately due to
  416. * a hole in the ECMA specification if the key is a
  417. * ECMA reserved word or starts with a digit the
  418. * parameter is only accessible using ECMAScript's
  419. * bracket notation.
  420. */
  421. // treat as a JSON object
  422. if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
  423. $properties = array_map(array($this, 'name_value'),
  424. array_keys($var),
  425. array_values($var));
  426. foreach($properties as $property) {
  427. if(Akeeba_Services_JSON::isError($property)) {
  428. return $property;
  429. }
  430. }
  431. return '{' . join(',', $properties) . '}';
  432. }
  433. // treat it like a regular array
  434. $elements = array_map(array($this, 'encode'), $var);
  435. foreach($elements as $element) {
  436. if(Akeeba_Services_JSON::isError($element)) {
  437. return $element;
  438. }
  439. }
  440. return '[' . join(',', $elements) . ']';
  441. case 'object':
  442. $vars = get_object_vars($var);
  443. $properties = array_map(array($this, 'name_value'),
  444. array_keys($vars),
  445. array_values($vars));
  446. foreach($properties as $property) {
  447. if(Akeeba_Services_JSON::isError($property)) {
  448. return $property;
  449. }
  450. }
  451. return '{' . join(',', $properties) . '}';
  452. default:
  453. return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
  454. ? 'null'
  455. : new Akeeba_Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
  456. }
  457. }
  458. /**
  459. * array-walking function for use in generating JSON-formatted name-value pairs
  460. *
  461. * @param string $name name of key to use
  462. * @param mixed $value reference to an array element to be encoded
  463. *
  464. * @return string JSON-formatted name-value pair, like '"name":value'
  465. * @access private
  466. */
  467. function name_value($name, $value)
  468. {
  469. $encoded_value = $this->encode($value);
  470. if(Akeeba_Services_JSON::isError($encoded_value)) {
  471. return $encoded_value;
  472. }
  473. return $this->encode(strval($name)) . ':' . $encoded_value;
  474. }
  475. /**
  476. * reduce a string by removing leading and trailing comments and whitespace
  477. *
  478. * @param $str string string value to strip of comments and whitespace
  479. *
  480. * @return string string value stripped of comments and whitespace
  481. * @access private
  482. */
  483. function reduce_string($str)
  484. {
  485. $str = preg_replace(array(
  486. // eliminate single line comments in '// ...' form
  487. '#^\s*//(.+)$#m',
  488. // eliminate multi-line comments in '/* ... */' form, at start of string
  489. '#^\s*/\*(.+)\*/#Us',
  490. // eliminate multi-line comments in '/* ... */' form, at end of string
  491. '#/\*(.+)\*/\s*$#Us'
  492. ), '', $str);
  493. // eliminate extraneous space
  494. return trim($str);
  495. }
  496. /**
  497. * decodes a JSON string into appropriate variable
  498. *
  499. * @param string $str JSON-formatted string
  500. *
  501. * @return mixed number, boolean, string, array, or object
  502. * corresponding to given JSON input string.
  503. * See argument 1 to Akeeba_Services_JSON() above for object-output behavior.
  504. * Note that decode() always returns strings
  505. * in ASCII or UTF-8 format!
  506. * @access public
  507. */
  508. function decode($str)
  509. {
  510. $str = $this->reduce_string($str);
  511. switch (strtolower($str)) {
  512. case 'true':
  513. return true;
  514. case 'false':
  515. return false;
  516. case 'null':
  517. return null;
  518. default:
  519. $m = array();
  520. if (is_numeric($str)) {
  521. // Lookie-loo, it's a number
  522. // This would work on its own, but I'm trying to be
  523. // good about returning integers where appropriate:
  524. // return (float)$str;
  525. // Return float or int, as appropriate
  526. return ((float)$str == (integer)$str)
  527. ? (integer)$str
  528. : (float)$str;
  529. } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
  530. // STRINGS RETURNED IN UTF-8 FORMAT
  531. $delim = substr($str, 0, 1);
  532. $chrs = substr($str, 1, -1);
  533. $utf8 = '';
  534. $strlen_chrs = strlen($chrs);
  535. for ($c = 0; $c < $strlen_chrs; ++$c) {
  536. $substr_chrs_c_2 = substr($chrs, $c, 2);
  537. $ord_chrs_c = ord($chrs{$c});
  538. switch (true) {
  539. case $substr_chrs_c_2 == '\b':
  540. $utf8 .= chr(0x08);
  541. ++$c;
  542. break;
  543. case $substr_chrs_c_2 == '\t':
  544. $utf8 .= chr(0x09);
  545. ++$c;
  546. break;
  547. case $substr_chrs_c_2 == '\n':
  548. $utf8 .= chr(0x0A);
  549. ++$c;
  550. break;
  551. case $substr_chrs_c_2 == '\f':
  552. $utf8 .= chr(0x0C);
  553. ++$c;
  554. break;
  555. case $substr_chrs_c_2 == '\r':
  556. $utf8 .= chr(0x0D);
  557. ++$c;
  558. break;
  559. case $substr_chrs_c_2 == '\\"':
  560. case $substr_chrs_c_2 == '\\\'':
  561. case $substr_chrs_c_2 == '\\\\':
  562. case $substr_chrs_c_2 == '\\/':
  563. if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
  564. ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
  565. $utf8 .= $chrs{++$c};
  566. }
  567. break;
  568. case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
  569. // single, escaped unicode character
  570. $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
  571. . chr(hexdec(substr($chrs, ($c + 4), 2)));
  572. $utf8 .= $this->utf162utf8($utf16);
  573. $c += 5;
  574. break;
  575. case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
  576. $utf8 .= $chrs{$c};
  577. break;
  578. case ($ord_chrs_c & 0xE0) == 0xC0:
  579. // characters U-00000080 - U-000007FF, mask 110XXXXX
  580. //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  581. $utf8 .= substr($chrs, $c, 2);
  582. ++$c;
  583. break;
  584. case ($ord_chrs_c & 0xF0) == 0xE0:
  585. // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  586. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  587. $utf8 .= substr($chrs, $c, 3);
  588. $c += 2;
  589. break;
  590. case ($ord_chrs_c & 0xF8) == 0xF0:
  591. // characters U-00010000 - U-001FFFFF, mask 11110XXX
  592. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  593. $utf8 .= substr($chrs, $c, 4);
  594. $c += 3;
  595. break;
  596. case ($ord_chrs_c & 0xFC) == 0xF8:
  597. // characters U-00200000 - U-03FFFFFF, mask 111110XX
  598. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  599. $utf8 .= substr($chrs, $c, 5);
  600. $c += 4;
  601. break;
  602. case ($ord_chrs_c & 0xFE) == 0xFC:
  603. // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  604. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  605. $utf8 .= substr($chrs, $c, 6);
  606. $c += 5;
  607. break;
  608. }
  609. }
  610. return $utf8;
  611. } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
  612. // array, or object notation
  613. if ($str{0} == '[') {
  614. $stk = array(SERVICES_JSON_IN_ARR);
  615. $arr = array();
  616. } else {
  617. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  618. $stk = array(SERVICES_JSON_IN_OBJ);
  619. $obj = array();
  620. } else {
  621. $stk = array(SERVICES_JSON_IN_OBJ);
  622. $obj = new stdClass();
  623. }
  624. }
  625. array_push($stk, array('what' => SERVICES_JSON_SLICE,
  626. 'where' => 0,
  627. 'delim' => false));
  628. $chrs = substr($str, 1, -1);
  629. $chrs = $this->reduce_string($chrs);
  630. if ($chrs == '') {
  631. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  632. return $arr;
  633. } else {
  634. return $obj;
  635. }
  636. }
  637. //print("\nparsing {$chrs}\n");
  638. $strlen_chrs = strlen($chrs);
  639. for ($c = 0; $c <= $strlen_chrs; ++$c) {
  640. $top = end($stk);
  641. $substr_chrs_c_2 = substr($chrs, $c, 2);
  642. if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
  643. // found a comma that is not inside a string, array, etc.,
  644. // OR we've reached the end of the character list
  645. $slice = substr($chrs, $top['where'], ($c - $top['where']));
  646. array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
  647. //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  648. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  649. // we are in an array, so just push an element onto the stack
  650. array_push($arr, $this->decode($slice));
  651. } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
  652. // we are in an object, so figure
  653. // out the property name and set an
  654. // element in an associative array,
  655. // for now
  656. $parts = array();
  657. if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
  658. // "name":value pair
  659. $key = $this->decode($parts[1]);
  660. $val = $this->decode($parts[2]);
  661. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  662. $obj[$key] = $val;
  663. } else {
  664. $obj->$key = $val;
  665. }
  666. } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
  667. // name:value pair, where name is unquoted
  668. $key = $parts[1];
  669. $val = $this->decode($parts[2]);
  670. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  671. $obj[$key] = $val;
  672. } else {
  673. $obj->$key = $val;
  674. }
  675. }
  676. }
  677. } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
  678. // found a quote, and we are not inside a string
  679. array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
  680. //print("Found start of string at {$c}\n");
  681. } elseif (($chrs{$c} == $top['delim']) &&
  682. ($top['what'] == SERVICES_JSON_IN_STR) &&
  683. ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
  684. // found a quote, we're in a string, and it's not escaped
  685. // we know that it's not escaped becase there is _not_ an
  686. // odd number of backslashes at the end of the string so far
  687. array_pop($stk);
  688. //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
  689. } elseif (($chrs{$c} == '[') &&
  690. in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  691. // found a left-bracket, and we are in an array, object, or slice
  692. array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
  693. //print("Found start of array at {$c}\n");
  694. } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
  695. // found a right-bracket, and we're in an array
  696. array_pop($stk);
  697. //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  698. } elseif (($chrs{$c} == '{') &&
  699. in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  700. // found a left-brace, and we are in an array, object, or slice
  701. array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
  702. //print("Found start of object at {$c}\n");
  703. } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
  704. // found a right-brace, and we're in an object
  705. array_pop($stk);
  706. //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  707. } elseif (($substr_chrs_c_2 == '/*') &&
  708. in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  709. // found a comment start, and we are in an array, object, or slice
  710. array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
  711. $c++;
  712. //print("Found start of comment at {$c}\n");
  713. } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
  714. // found a comment end, and we're in one now
  715. array_pop($stk);
  716. $c++;
  717. for ($i = $top['where']; $i <= $c; ++$i)
  718. $chrs = substr_replace($chrs, ' ', $i, 1);
  719. //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  720. }
  721. }
  722. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  723. return $arr;
  724. } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
  725. return $obj;
  726. }
  727. }
  728. }
  729. }
  730. function isError($data, $code = null)
  731. {
  732. if (class_exists('pear')) {
  733. return PEAR::isError($data, $code);
  734. } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
  735. is_subclass_of($data, 'services_json_error'))) {
  736. return true;
  737. }
  738. return false;
  739. }
  740. }
  741. class Akeeba_Services_JSON_Error
  742. {
  743. function Akeeba_Services_JSON_Error($message = 'unknown error', $code = null,
  744. $mode = null, $options = null, $userinfo = null)
  745. {
  746. }
  747. }
  748. }
  749. if(!function_exists('json_encode'))
  750. {
  751. function json_encode($value, $options = 0) {
  752. $flags = SERVICES_JSON_LOOSE_TYPE;
  753. if( $options & JSON_FORCE_OBJECT ) $flags = 0;
  754. $encoder = new Akeeba_Services_JSON($flags);
  755. return $encoder->encode($value);
  756. }
  757. }
  758. if(!function_exists('json_decode'))
  759. {
  760. function json_decode($value, $assoc = false)
  761. {
  762. $flags = 0;
  763. if($assoc) $flags = SERVICES_JSON_LOOSE_TYPE;
  764. $decoder = new Akeeba_Services_JSON($flags);
  765. return $decoder->decode($value);
  766. }
  767. }
  768. /**
  769. * The base class of Akeeba Engine objects. Allows for error and warnings logging
  770. * and propagation. Largely based on the Joomla! 1.5 JObject class.
  771. */
  772. abstract class AKAbstractObject
  773. {
  774. /** @var array An array of errors */
  775. private $_errors = array();
  776. /** @var array The queue size of the $_errors array. Set to 0 for infinite size. */
  777. protected $_errors_queue_size = 0;
  778. /** @var array An array of warnings */
  779. private $_warnings = array();
  780. /** @var array The queue size of the $_warnings array. Set to 0 for infinite size. */
  781. protected $_warnings_queue_size = 0;
  782. /**
  783. * Public constructor, makes sure we are instanciated only by the factory class
  784. */
  785. public function __construct()
  786. {
  787. /*
  788. // Assisted Singleton pattern
  789. if(function_exists('debug_backtrace'))
  790. {
  791. $caller=debug_backtrace();
  792. if(
  793. ($caller[1]['class'] != 'AKFactory') &&
  794. ($caller[2]['class'] != 'AKFactory') &&
  795. ($caller[3]['class'] != 'AKFactory') &&
  796. ($caller[4]['class'] != 'AKFactory')
  797. ) {
  798. var_dump(debug_backtrace());
  799. trigger_error("You can't create direct descendants of ".__CLASS__, E_USER_ERROR);
  800. }
  801. }
  802. */
  803. }
  804. /**
  805. * Get the most recent error message
  806. * @param integer $i Optional error index
  807. * @return string Error message
  808. */
  809. public function getError($i = null)
  810. {
  811. return $this->getItemFromArray($this->_errors, $i);
  812. }
  813. /**
  814. * Return all errors, if any
  815. * @return array Array of error messages
  816. */
  817. public function getErrors()
  818. {
  819. return $this->_errors;
  820. }
  821. /**
  822. * Add an error message
  823. * @param string $error Error message
  824. */
  825. public function setError($error)
  826. {
  827. if($this->_errors_queue_size > 0)
  828. {
  829. if(count($this->_errors) >= $this->_errors_queue_size)
  830. {
  831. array_shift($this->_errors);
  832. }
  833. }
  834. array_push($this->_errors, $error);
  835. }
  836. /**
  837. * Resets all error messages
  838. */
  839. public function resetErrors()
  840. {
  841. $this->_errors = array();
  842. }
  843. /**
  844. * Get the most recent warning message
  845. * @param integer $i Optional warning index
  846. * @return string Error message
  847. */
  848. public function getWarning($i = null)
  849. {
  850. return $this->getItemFromArray($this->_warnings, $i);
  851. }
  852. /**
  853. * Return all warnings, if any
  854. * @return array Array of error messages
  855. */
  856. public function getWarnings()
  857. {
  858. return $this->_warnings;
  859. }
  860. /**
  861. * Add an error message
  862. * @param string $error Error message
  863. */
  864. public function setWarning($warning)
  865. {
  866. if($this->_warnings_queue_size > 0)
  867. {
  868. if(count($this->_warnings) >= $this->_warnings_queue_size)
  869. {
  870. array_shift($this->_warnings);
  871. }
  872. }
  873. array_push($this->_warnings, $warning);
  874. }
  875. /**
  876. * Resets all warning messages
  877. */
  878. public function resetWarnings()
  879. {
  880. $this->_warnings = array();
  881. }
  882. /**
  883. * Propagates errors and warnings to a foreign object. The foreign object SHOULD
  884. * implement the setError() and/or setWarning() methods but DOESN'T HAVE TO be of
  885. * AKAbstractObject type. For example, this can even be used to propagate to a
  886. * JObject instance in Joomla!. Propagated items will be removed from ourself.
  887. * @param object $object The object to propagate errors and warnings to.
  888. */
  889. public function propagateToObject(&$object)
  890. {
  891. // Skip non-objects
  892. if(!is_object($object)) return;
  893. if( method_exists($object,'setError') )
  894. {
  895. if(!empty($this->_errors))
  896. {
  897. foreach($this->_errors as $error)
  898. {
  899. $object->setError($error);
  900. }
  901. $this->_errors = array();
  902. }
  903. }
  904. if( method_exists($object,'setWarning') )
  905. {
  906. if(!empty($this->_warnings))
  907. {
  908. foreach($this->_warnings as $warning)
  909. {
  910. $object->setWarning($warning);
  911. }
  912. $this->_warnings = array();
  913. }
  914. }
  915. }
  916. /**
  917. * Propagates errors and warnings from a foreign object. Each propagated list is
  918. * then cleared on the foreign object, as long as it implements resetErrors() and/or
  919. * resetWarnings() methods.
  920. * @param object $object The object to propagate errors and warnings from
  921. */
  922. public function propagateFromObject(&$object)
  923. {
  924. if( method_exists($object,'getErrors') )
  925. {
  926. $errors = $object->getErrors();
  927. if(!empty($errors))
  928. {
  929. foreach($errors as $error)
  930. {
  931. $this->setError($error);
  932. }
  933. }
  934. if(method_exists($object,'resetErrors'))
  935. {
  936. $object->resetErrors();
  937. }
  938. }
  939. if( method_exists($object,'getWarnings') )
  940. {
  941. $warnings = $object->getWarnings();
  942. if(!empty($warnings))
  943. {
  944. foreach($warnings as $warning)
  945. {
  946. $this->setWarning($warning);
  947. }
  948. }
  949. if(method_exists($object,'resetWarnings'))
  950. {
  951. $object->resetWarnings();
  952. }
  953. }
  954. }
  955. /**
  956. * Sets the size of the error queue (acts like a LIFO buffer)
  957. * @param int $newSize The new queue size. Set to 0 for infinite length.
  958. */
  959. protected function setErrorsQueueSize($newSize = 0)
  960. {
  961. $this->_errors_queue_size = (int)$newSize;
  962. }
  963. /**
  964. * Sets the size of the warnings queue (acts like a LIFO buffer)
  965. * @param int $newSize The new queue size. Set to 0 for infinite length.
  966. */
  967. protected function setWarningsQueueSize($newSize = 0)
  968. {
  969. $this->_warnings_queue_size = (int)$newSize;
  970. }
  971. /**
  972. * Returns the last item of a LIFO string message queue, or a specific item
  973. * if so specified.
  974. * @param array $array An array of strings, holding messages
  975. * @param int $i Optional message index
  976. * @return mixed The message string, or false if the key doesn't exist
  977. */
  978. private function getItemFromArray($array, $i = null)
  979. {
  980. // Find the item
  981. if ( $i === null) {
  982. // Default, return the last item
  983. $item = end($array);
  984. }
  985. else
  986. if ( ! array_key_exists($i, $array) ) {
  987. // If $i has been specified but does not exist, return false
  988. return false;
  989. }
  990. else
  991. {
  992. $item = $array[$i];
  993. }
  994. return $item;
  995. }
  996. }
  997. /**
  998. * File post processor engines base class
  999. */
  1000. abstract class AKAbstractPostproc extends AKAbstractObject
  1001. {
  1002. /** @var string The current (real) file path we'll have to process */
  1003. protected $filename = null;
  1004. /** @var int The requested permissions */
  1005. protected $perms = 0755;
  1006. /** @var string The temporary file path we gave to the unarchiver engine */
  1007. protected $tempFilename = null;
  1008. /** @var int The UNIX timestamp of the file's desired modification date */
  1009. public $timestamp = 0;
  1010. /**
  1011. * Processes the current file, e.g. moves it from temp to final location by FTP
  1012. */
  1013. abstract public function process();
  1014. /**
  1015. * The unarchiver tells us the path to the filename it wants to extract and we give it
  1016. * a different path instead.
  1017. * @param string $filename The path to the real file
  1018. * @param int $perms The permissions we need the file to have
  1019. * @return string The path to the temporary file
  1020. */
  1021. abstract public function processFilename($filename, $perms = 0755);
  1022. /**
  1023. * Recursively creates a directory if it doesn't exist
  1024. * @param string $dirName The directory to create
  1025. * @param int $perms The permissions to give to that directory
  1026. */
  1027. abstract public function createDirRecursive( $dirName, $perms );
  1028. abstract public function chmod( $file, $perms );
  1029. abstract public function unlink( $file );
  1030. abstract public function rmdir( $directory );
  1031. abstract public function rename( $from, $to );
  1032. }
  1033. /**
  1034. * The base class of unarchiver classes
  1035. */
  1036. abstract class AKAbstractUnarchiver extends AKAbstractPart
  1037. {
  1038. /** @var string Archive filename */
  1039. protected $filename = null;
  1040. /** @var array List of the names of all archive parts */
  1041. public $archiveList = array();
  1042. /** @var int The total size of all archive parts */
  1043. public $totalSize = array();
  1044. /** @var integer Current archive part number */
  1045. protected $currentPartNumber = -1;
  1046. /** @var integer The offset inside the current part */
  1047. protected $currentPartOffset = 0;
  1048. /** @var bool Should I restore permissions? */
  1049. protected $flagRestorePermissions = false;
  1050. /** @var AKAbstractPostproc Post processing class */
  1051. protected $postProcEngine = null;
  1052. /** @var string Absolute path to prepend to extracted files */
  1053. protected $addPath = '';
  1054. /** @var array Which files to rename */
  1055. public $renameFiles = array();
  1056. /** @var array Which directories to rename */
  1057. public $renameDirs = array();
  1058. /** @var array Which files to skip */
  1059. public $skipFiles = array();
  1060. /** @var integer Chunk size for processing */
  1061. protected $chunkSize = 524288;
  1062. /** @var resource File pointer to the current archive part file */
  1063. protected $fp = null;
  1064. /** @var int Run state when processing the current archive file */
  1065. protected $runState = null;
  1066. /** @var stdClass File header data, as read by the readFileHeader() method */
  1067. protected $fileHeader = null;
  1068. /** @var int How much of the uncompressed data we've read so far */
  1069. protected $dataReadLength = 0;
  1070. /**
  1071. * Public constructor
  1072. */
  1073. public function __construct()
  1074. {
  1075. parent::__construct();
  1076. }
  1077. /**
  1078. * Wakeup function, called whenever the class is unserialized
  1079. */
  1080. public function __wakeup()
  1081. {
  1082. if($this->currentPartNumber >= 0)
  1083. {
  1084. $this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb');
  1085. if( (is_resource($this->fp)) && ($this->currentPartOffset > 0) )
  1086. {
  1087. @fseek($this->fp, $this->currentPartOffset);
  1088. }
  1089. }
  1090. }
  1091. /**
  1092. * Sleep function, called whenever the class is serialized
  1093. */
  1094. public function shutdown()
  1095. {
  1096. if(is_resource($this->fp))
  1097. {
  1098. $this->currentPartOffset = @ftell($this->fp);
  1099. @fclose($this->fp);
  1100. }
  1101. }
  1102. /**
  1103. * Implements the abstract _prepare() method
  1104. */
  1105. final protected function _prepare()
  1106. {
  1107. parent::__construct();
  1108. if( count($this->_parametersArray) > 0 )
  1109. {
  1110. foreach($this->_parametersArray as $key => $value)
  1111. {
  1112. switch($key)
  1113. {
  1114. case 'filename': // Archive's absolute filename
  1115. $this->filename = $value;
  1116. break;
  1117. case 'restore_permissions': // Should I restore permissions?
  1118. $this->flagRestorePermissions = $value;
  1119. break;
  1120. case 'post_proc': // Should I use FTP?
  1121. $this->postProcEngine = AKFactory::getpostProc($value);
  1122. break;
  1123. case 'add_path': // Path to prepend
  1124. $this->addPath = $value;
  1125. $this->addPath = str_replace('\\','/',$this->addPath);
  1126. $this->addPath = rtrim($this->addPath,'/');
  1127. if(!empty($this->addPath)) $this->addPath .= '/';
  1128. break;
  1129. case 'rename_files': // Which files to rename (hash array)
  1130. $this->renameFiles = $value;
  1131. break;
  1132. case 'rename_dirs': // Which files to rename (hash array)
  1133. $this->renameDirs = $value;
  1134. break;
  1135. case 'skip_files': // Which files to skip (indexed array)
  1136. $this->skipFiles = $value;
  1137. break;
  1138. }
  1139. }
  1140. }
  1141. $this->scanArchives();
  1142. $this->readArchiveHeader();
  1143. $errMessage = $this->getError();
  1144. if(!empty($errMessage))
  1145. {
  1146. $this->setState('error', $errMessage);
  1147. }
  1148. else
  1149. {
  1150. $this->runState = AK_STATE_NOFILE;
  1151. $this->setState('prepared');
  1152. }
  1153. }
  1154. protected function _run()
  1155. {
  1156. if($this->getState() == 'postrun') return;
  1157. $this->setState('running');
  1158. $timer = AKFactory::getTimer();
  1159. $status = true;
  1160. while( $status && ($timer->getTimeLeft() > 0) )
  1161. {
  1162. switch( $this->runState )
  1163. {
  1164. case AK_STATE_NOFILE:
  1165. $status = $this->readFileHeader();
  1166. if($status)
  1167. {
  1168. // Send start of file notification
  1169. $message = new stdClass;
  1170. $message->type = 'startfile';
  1171. $message->content = new stdClass;
  1172. if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
  1173. $message->content->realfile = $this->fileHeader->realFile;
  1174. } else {
  1175. $message->content->realfile = $this->fileHeader->file;
  1176. }
  1177. $message->content->file = $this->fileHeader->file;
  1178. if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
  1179. $message->content->compressed = $this->fileHeader->compressed;
  1180. } else {
  1181. $message->content->compressed = 0;
  1182. }
  1183. $message->content->uncompressed = $this->fileHeader->uncompressed;
  1184. $this->notify($message);
  1185. }
  1186. break;
  1187. case AK_STATE_HEADER:
  1188. case AK_STATE_DATA:
  1189. $status = $this->processFileData();
  1190. break;
  1191. case AK_STATE_DATAREAD:
  1192. case AK_STATE_POSTPROC:
  1193. $this->postProcEngine->timestamp = $this->fileHeader->timestamp;
  1194. $status = $this->postProcEngine->process();
  1195. $this->propagateFromObject( $this->postProcEngine );
  1196. $this->runState = AK_STATE_DONE;
  1197. break;
  1198. case AK_STATE_DONE:
  1199. default:
  1200. if($status)
  1201. {
  1202. // Send end of file notification
  1203. $message = new stdClass;
  1204. $message->type = 'endfile';
  1205. $message->content = new stdClass;
  1206. if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
  1207. $message->content->realfile = $this->fileHeader->realFile;
  1208. } else {
  1209. $message->content->realfile = $this->fileHeader->file;
  1210. }
  1211. $message->content->file = $this->fileHeader->file;
  1212. if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
  1213. $message->content->compressed = $this->fileHeader->compressed;
  1214. } else {
  1215. $message->content->compressed = 0;
  1216. }
  1217. $message->content->uncompressed = $this->fileHeader->uncompressed;
  1218. $this->notify($message);
  1219. }
  1220. $this->runState = AK_STATE_NOFILE;
  1221. continue;
  1222. }
  1223. }
  1224. $error = $this->getError();
  1225. if( !$status && ($this->runState == AK_STATE_NOFILE) && empty( $error ) )
  1226. {
  1227. // We just finished
  1228. $this->setState('postrun');
  1229. }
  1230. elseif( !empty($error) )
  1231. {
  1232. $this->setState( 'error', $error );
  1233. }
  1234. }
  1235. protected function _finalize()
  1236. {
  1237. // Nothing to do
  1238. $this->setState('finished');
  1239. }
  1240. /**
  1241. * Returns the base extension of the file, e.g. '.jpa'
  1242. * @return string
  1243. */
  1244. private function getBaseExtension()
  1245. {
  1246. static $baseextension;
  1247. if(empty($baseextension))
  1248. {
  1249. $basename = basename($this->filename);
  1250. $lastdot = strrpos($basename,'.');
  1251. $baseextension = substr($basename, $lastdot);
  1252. }
  1253. return $baseextension;
  1254. }
  1255. /**
  1256. * Scans for archive parts
  1257. */
  1258. private function scanArchives()
  1259. {
  1260. $privateArchiveList = array();
  1261. // Get the components of the archive filename
  1262. $dirname = dirname($this->filename);
  1263. $base_extension = $this->getBaseExtension();
  1264. $basename = basename($this->filename, $base_extension);
  1265. $this->totalSize = 0;
  1266. // Scan for multiple parts until we don't find any more of them
  1267. $count = 0;
  1268. $found = true;
  1269. $this->archiveList = array();
  1270. while($found)
  1271. {
  1272. ++$count;
  1273. $extension = substr($base_extension, 0, 2).sprintf('%02d', $count);
  1274. $filename = $dirname.DIRECTORY_SEPARATOR.$basename.$extension;
  1275. $found = file_exists($filename);
  1276. if($found)
  1277. {
  1278. // Add yet another part, with a numeric-appended filename
  1279. $this->archiveList[] = $filename;
  1280. $filesize = @filesize($filename);
  1281. $this->totalSize += $filesize;
  1282. $privateArchiveList[] = array($filename, $filesize);
  1283. }
  1284. else
  1285. {
  1286. // Add the last part, with the regular extension
  1287. $this->archiveList[] = $this->filename;
  1288. $filename = $this->filename;
  1289. $filesize = @filesize($filename);
  1290. $this->totalSize += $filesize;
  1291. $privateArchiveList[] = array($filename, $filesize);
  1292. }
  1293. }
  1294. $this->currentPartNumber = -1;
  1295. $this->currentPartOffset = 0;
  1296. $this->runState = AK_STATE_NOFILE;
  1297. // Send start of file notification
  1298. $message = new stdClass;
  1299. $message->type = 'totalsize';
  1300. $message->content = new stdClass;
  1301. $message->content->totalsize = $this->totalSize;
  1302. $message->content->filelist = $privateArchiveList;
  1303. $this->notify($message);
  1304. }
  1305. /**
  1306. * Opens the next part file for reading
  1307. */
  1308. protected function nextFile()
  1309. {
  1310. ++$this->currentPartNumber;
  1311. if( $this->currentPartNumber > (count($this->archiveList) - 1) )
  1312. {
  1313. $this->setState('postrun');
  1314. return false;
  1315. }
  1316. else
  1317. {
  1318. if( is_resource($this->fp) ) @fclose($this->fp);
  1319. $this->fp = @fopen( $this->archiveList[$this->currentPartNumber], 'rb' );
  1320. fseek($this->fp, 0);
  1321. $this->currentPartOffset = 0;
  1322. return true;
  1323. }
  1324. }
  1325. /**
  1326. * Returns true if we have reached the end of file
  1327. * @param $local bool True to return EOF of the local file, false (default) to return if we have reached the end of the archive set
  1328. * @return bool True if we have reached End Of File
  1329. */
  1330. protected function isEOF($local = false)
  1331. {
  1332. $eof = @feof($this->fp);
  1333. if(!$eof)
  1334. {
  1335. // Border case: right at the part's end (eeeek!!!). For the life of me, I don't understand why
  1336. // feof() doesn't report true. It expects the fp to be positioned *beyond* the EOF to report
  1337. // true. Incredible! :(
  1338. $position = @ftell($this->fp);
  1339. $filesize = @filesize( $this->archiveList[$this->currentPartNumber] );
  1340. if( $position >= $filesize ) $eof = true;
  1341. }
  1342. if($local)
  1343. {
  1344. return $eof;
  1345. }
  1346. else
  1347. {
  1348. return $eof && ($this->currentPartNumber >= (count($this->archiveList)-1) );
  1349. }
  1350. }
  1351. /**
  1352. * Tries to make a directory user-writable so that we can write a file to it
  1353. * @param $path string A path to a file
  1354. */
  1355. protected function setCorrectPermissions($path)
  1356. {
  1357. static $rootDir = null;
  1358. if(is_null($rootDir)) {
  1359. $rootDir = rtrim(AKFactory::get('kickstart.setup.destdir',''),'/\\');
  1360. }
  1361. $directory = rtrim(dirname($path),'/\\');
  1362. if($directory != $rootDir) {
  1363. // Is this an unwritable directory?
  1364. if(!is_writeable($directory)) {
  1365. $this->postProcEngine->chmod( $directory, 0755 );
  1366. }
  1367. }
  1368. $this->postProcEngine->chmod( $path, 0644 );
  1369. }
  1370. /**
  1371. * Concrete classes are supposed to use this method in order to read the archive's header and
  1372. * prepare themselves to the point of being ready to extract the first file.
  1373. */
  1374. protected abstract function readArchiveHeader();
  1375. /**
  1376. * Concrete classes must use this method to read the file header
  1377. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  1378. */
  1379. protected abstract function readFileHeader();
  1380. /**
  1381. * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
  1382. * it's finished processing the file data.
  1383. * @return bool True if processing the file data was successful, false if an error occured
  1384. */
  1385. protected abstract function processFileData();
  1386. /**
  1387. * Reads data from the archive and notifies the observer with the 'reading' message
  1388. * @param $fp
  1389. * @param $length
  1390. */
  1391. protected function fread($fp, $length = null)
  1392. {
  1393. if(is_numeric($length))
  1394. {
  1395. if($length > 0) {
  1396. $data = fread($fp, $length);
  1397. } else {
  1398. $data = fread($fp);
  1399. }
  1400. }
  1401. else
  1402. {
  1403. $data = fread($fp);
  1404. }
  1405. if($data === false) $data = '';
  1406. // Send start of file notification
  1407. $message = new stdClass;
  1408. $message->type = 'reading';
  1409. $message->content = new stdClass;
  1410. $message->content->length = strlen($data);
  1411. $this->notify($message);
  1412. return $data;
  1413. }
  1414. }
  1415. /**
  1416. * The superclass of all Akeeba Kickstart parts. The "parts" are intelligent stateful
  1417. * classes which perform a single procedure and have preparation, running and
  1418. * finalization phases. The transition between phases is handled automatically by
  1419. * this superclass' tick() final public method, which should be the ONLY public API
  1420. * exposed to the rest of the Akeeba Engine.
  1421. */
  1422. abstract class AKAbstractPart extends AKAbstractObject
  1423. {
  1424. /**
  1425. * Indicates whether this part has finished its initialisation cycle
  1426. * @var boolean
  1427. */
  1428. protected $isPrepared = false;
  1429. /**
  1430. * Indicates whether this part has more work to do (it's in running state)
  1431. * @var boolean
  1432. */
  1433. protected $isRunning = false;
  1434. /**
  1435. * Indicates whether this part has finished its finalization cycle
  1436. * @var boolean
  1437. */
  1438. protected $isFinished = false;
  1439. /**
  1440. * Indicates whether this part has finished its run cycle
  1441. * @var boolean
  1442. */
  1443. protected $hasRan = false;
  1444. /**
  1445. * The name of the engine part (a.k.a. Domain), used in return table
  1446. * generation.
  1447. * @var string
  1448. */
  1449. protected $active_domain = "";
  1450. /**
  1451. * The step this engine part is in. Used verbatim in return table and
  1452. * should be set by the code in the _run() method.
  1453. * @var string
  1454. */
  1455. protected $active_step = "";
  1456. /**
  1457. * A more detailed description of the step this engine part is in. Used
  1458. * verbatim in return table and should be set by the code in the _run()
  1459. * method.
  1460. * @var string
  1461. */
  1462. protected $active_substep = "";
  1463. /**
  1464. * Any configuration variables, in the form of an array.
  1465. * @var array
  1466. */
  1467. protected $_parametersArray = array();
  1468. /** @var string The database root key */
  1469. protected $databaseRoot = array();
  1470. /** @var int Last reported warnings's position in array */
  1471. private $warnings_pointer = -1;
  1472. /** @var array An array of observers */
  1473. protected $observers = array();
  1474. /**
  1475. * Runs the preparation for this part. Should set _isPrepared
  1476. * to true
  1477. */
  1478. abstract protected function _prepare();
  1479. /**
  1480. * Runs the finalisation process for this part. Should set
  1481. * _isFinished to true.
  1482. */
  1483. abstract protected function _finalize();
  1484. /**
  1485. * Runs the main functionality loop for this part. Upon calling,
  1486. * should set the _isRunning to true. When it finished, should set
  1487. * the _hasRan to true. If an error is encountered, setError should
  1488. * be used.
  1489. */
  1490. abstract protected function _run();
  1491. /**
  1492. * Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
  1493. * in fear of timing out.
  1494. */
  1495. protected function setBreakFlag()
  1496. {
  1497. AKFactory::set('volatile.breakflag', true);
  1498. }
  1499. /**
  1500. * Sets the engine part's internal state, in an easy to use manner
  1501. *
  1502. * @param string $state One of init, prepared, running, postrun, finished, error
  1503. * @param string $errorMessage The reported error message, should the state be set to error
  1504. */
  1505. protected function setState($state = 'init', $errorMessage='Invalid setState argument')
  1506. {
  1507. switch($state)
  1508. {
  1509. case 'init':
  1510. $this->isPrepared = false;
  1511. $this->isRunning = false;
  1512. $this->isFinished = false;
  1513. $this->hasRun = false;
  1514. break;
  1515. case 'prepared':
  1516. $this->isPrepared = true;
  1517. $this->isRunning = false;
  1518. $this->isFinished = false;
  1519. $this->hasRun = false;
  1520. break;
  1521. case 'running':
  1522. $this->isPrepared = true;
  1523. $this->isRunning = true;
  1524. $this->isFinished = false;
  1525. $this->hasRun = false;
  1526. break;
  1527. case 'postrun':
  1528. $this->isPrepared = true;
  1529. $this->isRunning = false;
  1530. $this->isFinished = false;
  1531. $this->hasRun = true;
  1532. break;
  1533. case 'finished':
  1534. $this->isPrepared = true;
  1535. $this->isRunning = false;
  1536. $this->isFinished = true;
  1537. $this->hasRun = false;
  1538. break;
  1539. case 'error':
  1540. default:
  1541. $this->setError($errorMessage);
  1542. break;
  1543. }
  1544. }
  1545. /**
  1546. * The public interface to an engine part. This method takes care for
  1547. * calling the correct method in order to perform the initialisation -
  1548. * run - finalisation cycle of operation and return a proper reponse array.
  1549. * @return array A Reponse Array
  1550. */
  1551. final public function tick()
  1552. {
  1553. // Call the right action method, depending on engine part state
  1554. switch( $this->getState() )
  1555. {
  1556. case "init":
  1557. $this->_prepare();
  1558. break;
  1559. case "prepared":
  1560. $this->_run();
  1561. break;
  1562. case "running":
  1563. $this->_run();
  1564. break;
  1565. case "postrun":
  1566. $this->_finalize();
  1567. break;
  1568. }
  1569. // Send a Return Table back to the caller
  1570. $out = $this->_makeReturnTable();
  1571. return $out;
  1572. }
  1573. /**
  1574. * Returns a copy of the class's status array
  1575. * @return array
  1576. */
  1577. public function getStatusArray()
  1578. {
  1579. return $this->_makeReturnTable();
  1580. }
  1581. /**
  1582. * Sends any kind of setup information to the engine part. Using this,
  1583. * we avoid passing parameters to the constructor of the class. These
  1584. * parameters should be passed as an indexed array and should be taken
  1585. * into account during the preparation process only. This function will
  1586. * set the error flag if it's called after the engine part is prepared.
  1587. *
  1588. * @param array $parametersArray The parameters to be passed to the
  1589. * engine part.
  1590. */
  1591. final public function setup( $parametersArray )
  1592. {
  1593. if( $this->isPrepared )
  1594. {
  1595. $this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain);
  1596. }
  1597. else
  1598. {
  1599. $this->_parametersArray = $parametersArray;
  1600. if(array_key_exists('root', $parametersArray))
  1601. {
  1602. $this->databaseRoot = $parametersArray['root'];
  1603. }
  1604. }
  1605. }
  1606. /**
  1607. * Returns the state of this engine part.
  1608. *
  1609. * @return string The state of this engine part. It can be one of
  1610. * error, init, prepared, running, postrun, finished.
  1611. */
  1612. final public function getState()
  1613. {
  1614. if( $this->getError() )
  1615. {
  1616. return "error";
  1617. }
  1618. if( !($this->isPrepared) )
  1619. {
  1620. return "init";
  1621. }
  1622. if( !($this->isFinished) && !($this->isRunning) && !( $this->hasRun ) && ($this->isPrepared) )
  1623. {
  1624. return "prepared";
  1625. }
  1626. if ( !($this->isFinished) && $this->isRunning && !( $this->hasRun ) )
  1627. {
  1628. return "running";
  1629. }
  1630. if ( !($this->isFinished) && !($this->isRunning) && $this->hasRun )
  1631. {
  1632. return "postrun";
  1633. }
  1634. if ( $this->isFinished )
  1635. {
  1636. return "finished";
  1637. }
  1638. }
  1639. /**
  1640. * Constructs a Response Array based on the engine part's state.
  1641. * @return array The Response Array for the current state
  1642. */
  1643. final protected function _makeReturnTable()
  1644. {
  1645. // Get a list of warnings
  1646. $warnings = $this->getWarnings();
  1647. // Report only new warnings if there is no warnings queue size
  1648. if( $this->_warnings_queue_size == 0 )
  1649. {
  1650. if( ($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings)) ) )
  1651. {
  1652. $warnings = array_slice($warnings, $this->warnings_pointer + 1);
  1653. $this->warnings_pointer += count($warnings);
  1654. }
  1655. else
  1656. {
  1657. $this->warnings_pointer = count($warnings);
  1658. }
  1659. }
  1660. $out = array(
  1661. 'HasRun' => (!($this->isFinished)),
  1662. 'Domain' => $this->active_domain,
  1663. 'Step' => $this->active_step,
  1664. 'Substep' => $this->active_substep,
  1665. 'Error' => $this->getError(),
  1666. 'Warnings' => $warnings
  1667. );
  1668. return $out;
  1669. }
  1670. final protected function setDomain($new_domain)
  1671. {
  1672. $this->active_domain = $new_domain;
  1673. }
  1674. final public function getDomain()
  1675. {
  1676. return $this->active_domain;
  1677. }
  1678. final protected function setStep($new_step)
  1679. {
  1680. $this->active_step = $new_step;
  1681. }
  1682. final public function getStep()
  1683. {
  1684. return $this->active_step;
  1685. }
  1686. final protected function setSubstep($new_substep)
  1687. {
  1688. $this->active_substep = $new_substep;
  1689. }
  1690. final public function getSubstep()
  1691. {
  1692. return $this->active_substep;
  1693. }
  1694. /**
  1695. * Attaches an observer object
  1696. * @param AKAbstractPartObserver $obs
  1697. */
  1698. function attach(AKAbstractPartObserver $obs) {
  1699. $this->observers["$obs"] = $obs;
  1700. }
  1701. /**
  1702. * Dettaches an observer object
  1703. * @param AKAbstractPartObserver $obs
  1704. */
  1705. function detach(AKAbstractPartObserver $obs) {
  1706. delete($this->observers["$obs"]);
  1707. }
  1708. /**
  1709. * Notifies observers each time something interesting happened to the part
  1710. * @param mixed $message The event object
  1711. */
  1712. protected function notify($message) {
  1713. foreach ($this->observers as $obs) {
  1714. $obs->update($this, $message);
  1715. }
  1716. }
  1717. }
  1718. /**
  1719. * Descendants of this class can be used in the unarchiver's observer methods (attach, detach and notify)
  1720. * @author Nicholas
  1721. *
  1722. */
  1723. abstract class AKAbstractPartObserver
  1724. {
  1725. abstract public function update($object, $message);
  1726. }
  1727. /**
  1728. * Direct file writer
  1729. */
  1730. class AKPostprocDirect extends AKAbstractPostproc
  1731. {
  1732. public function process()
  1733. {
  1734. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  1735. if($restorePerms)
  1736. {
  1737. @chmod($this->filename, $this->perms);
  1738. }
  1739. else
  1740. {
  1741. if(@is_file($this->filename))
  1742. {
  1743. @chmod($this->filename, 0644);
  1744. }
  1745. else
  1746. {
  1747. @chmod($this->filename, 0755);
  1748. }
  1749. }
  1750. if($this->timestamp > 0)
  1751. {
  1752. @touch($this->filename, $this->timestamp);
  1753. }
  1754. return true;
  1755. }
  1756. public function processFilename($filename, $perms = 0755)
  1757. {
  1758. $this->perms = $perms;
  1759. $this->filename = $filename;
  1760. return $filename;
  1761. }
  1762. public function createDirRecursive( $dirName, $perms )
  1763. {
  1764. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  1765. if (@mkdir($dirName, 0755, true)) {
  1766. @chmod($dirName, 0755);
  1767. return true;
  1768. }
  1769. $root = AKFactory::get('kickstart.setup.destdir');
  1770. $root = rtrim(str_replace('\\','/',$root),'/');
  1771. $dir = rtrim(str_replace('\\','/',$dirName),'/');
  1772. if(strpos($dir, $root) === 0) {
  1773. $dir = ltrim(substr($dir, strlen($root)), '/');
  1774. $root .= '/';
  1775. } else {
  1776. $root = '';
  1777. }
  1778. if(empty($dir)) return true;
  1779. $dirArray = explode('/', $dir);
  1780. $path = '';
  1781. foreach( $dirArray as $dir )
  1782. {
  1783. $path .= $dir . '/';
  1784. $ret = is_dir($root.$path) ? true : @mkdir($root.$path);
  1785. if( !$ret ) {
  1786. // Is this a file instead of a directory?
  1787. if(is_file($root.$path) )
  1788. {
  1789. @unlink($root.$path);
  1790. $ret = @mkdir($root.$path);
  1791. }
  1792. if( !$ret ) {
  1793. $this->setError( AKText::sprintf('COULDNT_CREATE_DIR',$path) );
  1794. return false;
  1795. }
  1796. }
  1797. // Try to set new directory permissions to 0755
  1798. @chmod($root.$path, $perms);
  1799. }
  1800. return true;
  1801. }
  1802. public function chmod( $file, $perms )
  1803. {
  1804. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  1805. return @chmod( $file, $perms );
  1806. }
  1807. public function unlink( $file )
  1808. {
  1809. return @unlink( $file );
  1810. }
  1811. public function rmdir( $directory )
  1812. {
  1813. return @rmdir( $directory );
  1814. }
  1815. public function rename( $from, $to )
  1816. {
  1817. return @rename($from, $to);
  1818. }
  1819. }
  1820. /**
  1821. * FTP file writer
  1822. */
  1823. class AKPostprocFTP extends AKAbstractPostproc
  1824. {
  1825. /** @var bool Should I use FTP over implicit SSL? */
  1826. public $useSSL = false;
  1827. /** @var bool use Passive mode? */
  1828. public $passive = true;
  1829. /** @var string FTP host name */
  1830. public $host = '';
  1831. /** @var int FTP port */
  1832. public $port = 21;
  1833. /** @var string FTP user name */
  1834. public $user = '';
  1835. /** @var string FTP password */
  1836. public $pass = '';
  1837. /** @var string FTP initial directory */
  1838. public $dir = '';
  1839. /** @var resource The FTP handle */
  1840. private $handle = null;
  1841. /** @var string The temporary directory where the data will be stored */
  1842. private $tempDir = '';
  1843. public function __construct()
  1844. {
  1845. parent::__construct();
  1846. $this->useSSL = AKFactory::get('kickstart.ftp.ssl', false);
  1847. $this->passive = AKFactory::get('kickstart.ftp.passive', true);
  1848. $this->host = AKFactory::get('kickstart.ftp.host', '');
  1849. $this->port = AKFactory::get('kickstart.ftp.port', 21);
  1850. if(trim($this->port) == '') $this->port = 21;
  1851. $this->user = AKFactory::get('kickstart.ftp.user', '');
  1852. $this->pass = AKFactory::get('kickstart.ftp.pass', '');
  1853. $this->dir = AKFactory::get('kickstart.ftp.dir', '');
  1854. $this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');
  1855. $connected = $this->connect();
  1856. if($connected)
  1857. {
  1858. if(!empty($this->tempDir))
  1859. {
  1860. $tempDir = rtrim($this->tempDir, '/\\').'/';
  1861. $writable = $this->isDirWritable($tempDir);
  1862. }
  1863. else
  1864. {
  1865. $tempDir = '';
  1866. $writable = false;
  1867. }
  1868. if(!$writable) {
  1869. // Default temporary directory is the current root
  1870. $tempDir = function_exists('getcwd') ? getcwd() : dirname(__FILE__);
  1871. if(empty($tempDir))
  1872. {
  1873. // Oh, we have no directory reported!
  1874. $tempDir = '.';
  1875. }
  1876. $absoluteDirToHere = $tempDir;
  1877. $tempDir = rtrim(str_replace('\\','/',$tempDir),'/');
  1878. if(!empty($tempDir)) $tempDir .= '/';
  1879. $this->tempDir = $tempDir;
  1880. // Is this directory writable?
  1881. $writable = $this->isDirWritable($tempDir);
  1882. }
  1883. if(!$writable)
  1884. {
  1885. // Nope. Let's try creating a temporary directory in the site's root.
  1886. $tempDir = $absoluteDirToHere.'/kicktemp';
  1887. $this->createDirRecursive($tempDir, 0777);
  1888. // Try making it writable...
  1889. $this->fixPermissions($tempDir);
  1890. $writable = $this->isDirWritable($tempDir);
  1891. }
  1892. // Was the new directory writable?
  1893. if(!$writable)
  1894. {
  1895. // Let's see if the user has specified one
  1896. $userdir = AKFactory::get('kickstart.ftp.tempdir', '');
  1897. if(!empty($userdir))
  1898. {
  1899. // Is it an absolute or a relative directory?
  1900. $absolute = false;
  1901. $absolute = $absolute || ( substr($userdir,0,1) == '/' );
  1902. $absolute = $absolute || ( substr($userdir,1,1) == ':' );
  1903. $absolute = $absolute || ( substr($userdir,2,1) == ':' );
  1904. if(!$absolute)
  1905. {
  1906. // Make absolute
  1907. $tempDir = $absoluteDirToHere.$userdir;
  1908. }
  1909. else
  1910. {
  1911. // it's already absolute
  1912. $tempDir = $userdir;
  1913. }
  1914. // Does the directory exist?
  1915. if( is_dir($tempDir) )
  1916. {
  1917. // Yeah. Is it writable?
  1918. $writable = $this->isDirWritable($tempDir);
  1919. }
  1920. }
  1921. }
  1922. $this->tempDir = $tempDir;
  1923. if(!$writable)
  1924. {
  1925. // No writable directory found!!!
  1926. $this->setError(AKText::_('FTP_TEMPDIR_NOT_WRITABLE'));
  1927. }
  1928. else
  1929. {
  1930. AKFactory::set('kickstart.ftp.tempdir', $tempDir);
  1931. $this->tempDir = $tempDir;
  1932. }
  1933. }
  1934. }
  1935. function __wakeup()
  1936. {
  1937. $this->connect();
  1938. }
  1939. public function connect()
  1940. {
  1941. // Connect to server, using SSL if so required
  1942. if($this->useSSL) {
  1943. $this->handle = @ftp_ssl_connect($this->host, $this->port);
  1944. } else {
  1945. $this->handle = @ftp_connect($this->host, $this->port);
  1946. }
  1947. if($this->handle === false)
  1948. {
  1949. $this->setError(AKText::_('WRONG_FTP_HOST'));
  1950. return false;
  1951. }
  1952. // Login
  1953. if(! @ftp_login($this->handle, $this->user, $this->pass))
  1954. {
  1955. $this->setError(AKText::_('WRONG_FTP_USER'));
  1956. @ftp_close($this->handle);
  1957. return false;
  1958. }
  1959. // Change to initial directory
  1960. if(! @ftp_chdir($this->handle, $this->dir))
  1961. {
  1962. $this->setError(AKText::_('WRONG_FTP_PATH1'));
  1963. @ftp_close($this->handle);
  1964. return false;
  1965. }
  1966. // Enable passive mode if the user requested it
  1967. if( $this->passive )
  1968. {
  1969. @ftp_pasv($this->handle, true);
  1970. }
  1971. else
  1972. {
  1973. @ftp_pasv($this->handle, false);
  1974. }
  1975. return true;
  1976. }
  1977. public function process()
  1978. {
  1979. if( is_null($this->tempFilename) )
  1980. {
  1981. // If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
  1982. // the entity was a directory or symlink
  1983. return true;
  1984. }
  1985. $remotePath = dirname($this->filename);
  1986. $removePath = AKFactory::get('kickstart.setup.destdir','');
  1987. if(!empty($removePath))
  1988. {
  1989. $removePath = ltrim($removePath, "/");
  1990. $remotePath = ltrim($remotePath, "/");
  1991. $left = substr($remotePath, 0, strlen($removePath));
  1992. if($left == $removePath)
  1993. {
  1994. $remotePath = substr($remotePath, strlen($removePath));
  1995. }
  1996. }
  1997. $absoluteFSPath = dirname($this->filename);
  1998. $relativeFTPPath = trim($remotePath, '/');
  1999. $absoluteFTPPath = '/'.trim( $this->dir, '/' ).'/'.trim($remotePath, '/');
  2000. $onlyFilename = basename($this->filename);
  2001. $remoteName = $absoluteFTPPath.'/'.$onlyFilename;
  2002. $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
  2003. if($ret === false)
  2004. {
  2005. $ret = $this->createDirRecursive( $absoluteFSPath, 0755);
  2006. if($ret === false) {
  2007. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2008. return false;
  2009. }
  2010. $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
  2011. if($ret === false) {
  2012. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2013. return false;
  2014. }
  2015. }
  2016. $ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);
  2017. if($ret === false)
  2018. {
  2019. // If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
  2020. $this->fixPermissions($this->filename);
  2021. $this->unlink($this->filename);
  2022. $fp = @fopen($this->tempFilename);
  2023. if($fp !== false)
  2024. {
  2025. $ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
  2026. @fclose($fp);
  2027. }
  2028. else
  2029. {
  2030. $ret = false;
  2031. }
  2032. }
  2033. @unlink($this->tempFilename);
  2034. if($ret === false)
  2035. {
  2036. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2037. return false;
  2038. }
  2039. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  2040. if($restorePerms)
  2041. {
  2042. @ftp_chmod($this->_handle, $perms, $remoteName);
  2043. }
  2044. else
  2045. {
  2046. @ftp_chmod($this->_handle, 0644, $remoteName);
  2047. }
  2048. return true;
  2049. }
  2050. public function processFilename($filename, $perms = 0755)
  2051. {
  2052. // Catch some error conditions...
  2053. if($this->getError())
  2054. {
  2055. return false;
  2056. }
  2057. // If a null filename is passed, it means that we shouldn't do any post processing, i.e.
  2058. // the entity was a directory or symlink
  2059. if(is_null($filename))
  2060. {
  2061. $this->filename = null;
  2062. $this->tempFilename = null;
  2063. return null;
  2064. }
  2065. // Strip absolute filesystem path to website's root
  2066. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2067. if(!empty($removePath))
  2068. {
  2069. $left = substr($filename, 0, strlen($removePath));
  2070. if($left == $removePath)
  2071. {
  2072. $filename = substr($filename, strlen($removePath));
  2073. }
  2074. }
  2075. // Trim slash on the left
  2076. $filename = ltrim($filename, '/');
  2077. $this->filename = $filename;
  2078. $this->tempFilename = tempnam($this->tempDir, 'kickstart-');
  2079. $this->perms = $perms;
  2080. if( empty($this->tempFilename) )
  2081. {
  2082. // Oops! Let's try something different
  2083. $this->tempFilename = $this->tempDir.'/kickstart-'.time().'.dat';
  2084. }
  2085. return $this->tempFilename;
  2086. }
  2087. private function isDirWritable($dir)
  2088. {
  2089. $fp = @fopen($dir.'/kickstart.dat', 'wb');
  2090. if($fp === false)
  2091. {
  2092. return false;
  2093. }
  2094. else
  2095. {
  2096. @fclose($fp);
  2097. unlink($dir.'/kickstart.dat');
  2098. return true;
  2099. }
  2100. }
  2101. public function createDirRecursive( $dirName, $perms )
  2102. {
  2103. // Strip absolute filesystem path to website's root
  2104. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2105. if(!empty($removePath))
  2106. {
  2107. // UNIXize the paths
  2108. $removePath = str_replace('\\','/',$removePath);
  2109. $dirName = str_replace('\\','/',$dirName);
  2110. // Make sure they both end in a slash
  2111. $removePath = rtrim($removePath,'/\\').'/';
  2112. $dirName = rtrim($dirName,'/\\').'/';
  2113. // Process the path removal
  2114. $left = substr($dirName, 0, strlen($removePath));
  2115. if($left == $removePath)
  2116. {
  2117. $dirName = substr($dirName, strlen($removePath));
  2118. }
  2119. }
  2120. if(empty($dirName)) $dirName = ''; // 'cause the substr() above may return FALSE.
  2121. $check = '/'.trim($this->dir,'/').'/'.trim($dirName, '/');
  2122. if($this->is_dir($check)) return true;
  2123. $alldirs = explode('/', $dirName);
  2124. $previousDir = '/'.trim($this->dir);
  2125. foreach($alldirs as $curdir)
  2126. {
  2127. $check = $previousDir.'/'.$curdir;
  2128. if(!$this->is_dir($check))
  2129. {
  2130. // Proactively try to delete a file by the same name
  2131. @ftp_delete($this->handle, $check);
  2132. if(@ftp_mkdir($this->handle, $check) === false)
  2133. {
  2134. // If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
  2135. $this->fixPermissions($removePath.$check);
  2136. if(@ftp_mkdir($this->handle, $check) === false)
  2137. {
  2138. // Can we fall back to pure PHP mode, sire?
  2139. if(!@mkdir($check))
  2140. {
  2141. $this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR',$dir));
  2142. return false;
  2143. }
  2144. else
  2145. {
  2146. // Since the directory was built by PHP, change its permissions
  2147. @chmod($check, "0777");
  2148. return true;
  2149. }
  2150. }
  2151. }
  2152. @ftp_chmod($this->handle, $perms, $check);
  2153. }
  2154. $previousDir = $check;
  2155. }
  2156. return true;
  2157. }
  2158. public function close()
  2159. {
  2160. @ftp_close($this->handle);
  2161. }
  2162. /*
  2163. * Tries to fix directory/file permissions in the PHP level, so that
  2164. * the FTP operation doesn't fail.
  2165. * @param $path string The full path to a directory or file
  2166. */
  2167. private function fixPermissions( $path )
  2168. {
  2169. // Turn off error reporting
  2170. if(!defined('KSDEBUG')) {
  2171. $oldErrorReporting = @error_reporting(E_NONE);
  2172. }
  2173. // Get UNIX style paths
  2174. $relPath = str_replace('\\','/',$path);
  2175. $basePath = rtrim(str_replace('\\','/',dirname(__FILE__)),'/');
  2176. $basePath = rtrim($basePath,'/');
  2177. if(!empty($basePath)) $basePath .= '/';
  2178. // Remove the leading relative root
  2179. if( substr($relPath,0,strlen($basePath)) == $basePath )
  2180. $relPath = substr($relPath,strlen($basePath));
  2181. $dirArray = explode('/', $relPath);
  2182. $pathBuilt = rtrim($basePath,'/');
  2183. foreach( $dirArray as $dir )
  2184. {
  2185. if(empty($dir)) continue;
  2186. $oldPath = $pathBuilt;
  2187. $pathBuilt .= '/'.$dir;
  2188. if(is_dir($oldPath.$dir))
  2189. {
  2190. @chmod($oldPath.$dir, 0777);
  2191. }
  2192. else
  2193. {
  2194. if(@chmod($oldPath.$dir, 0777) === false)
  2195. {
  2196. @unlink($oldPath.$dir);
  2197. }
  2198. }
  2199. }
  2200. // Restore error reporting
  2201. if(!defined('KSDEBUG')) {
  2202. @error_reporting($oldErrorReporting);
  2203. }
  2204. }
  2205. public function chmod( $file, $perms )
  2206. {
  2207. return @ftp_chmod($this->handle, $perms, $path);
  2208. }
  2209. private function is_dir( $dir )
  2210. {
  2211. return @ftp_chdir( $this->handle, $dir );
  2212. }
  2213. public function unlink( $file )
  2214. {
  2215. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2216. if(!empty($removePath))
  2217. {
  2218. $left = substr($file, 0, strlen($removePath));
  2219. if($left == $removePath)
  2220. {
  2221. $file = substr($file, strlen($removePath));
  2222. }
  2223. }
  2224. $check = '/'.trim($this->dir,'/').'/'.trim($file, '/');
  2225. return @ftp_delete( $this->handle, $check );
  2226. }
  2227. public function rmdir( $directory )
  2228. {
  2229. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2230. if(!empty($removePath))
  2231. {
  2232. $left = substr($directory, 0, strlen($removePath));
  2233. if($left == $removePath)
  2234. {
  2235. $directory = substr($directory, strlen($removePath));
  2236. }
  2237. }
  2238. $check = '/'.trim($this->dir,'/').'/'.trim($directory, '/');
  2239. return @ftp_rmdir( $this->handle, $check );
  2240. }
  2241. public function rename( $from, $to )
  2242. {
  2243. $originalFrom = $from;
  2244. $originalTo = $to;
  2245. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2246. if(!empty($removePath))
  2247. {
  2248. $left = substr($from, 0, strlen($removePath));
  2249. if($left == $removePath)
  2250. {
  2251. $from = substr($from, strlen($removePath));
  2252. }
  2253. }
  2254. $from = '/'.trim($this->dir,'/').'/'.trim($from, '/');
  2255. if(!empty($removePath))
  2256. {
  2257. $left = substr($to, 0, strlen($removePath));
  2258. if($left == $removePath)
  2259. {
  2260. $to = substr($to, strlen($removePath));
  2261. }
  2262. }
  2263. $to = '/'.trim($this->dir,'/').'/'.trim($to, '/');
  2264. $result = @ftp_rename( $this->handle, $from, $to );
  2265. if($result !== true)
  2266. {
  2267. return @rename($from, $to);
  2268. }
  2269. else
  2270. {
  2271. return true;
  2272. }
  2273. }
  2274. }
  2275. /**
  2276. * JPA archive extraction class
  2277. */
  2278. class AKUnarchiverJPA extends AKAbstractUnarchiver
  2279. {
  2280. private $archiveHeaderData = array();
  2281. protected function readArchiveHeader()
  2282. {
  2283. // Initialize header data array
  2284. $this->archiveHeaderData = new stdClass();
  2285. // Open the first part
  2286. $this->nextFile();
  2287. // Fail for unreadable files
  2288. if( $this->fp === false ) return false;
  2289. // Read the signature
  2290. $sig = fread( $this->fp, 3 );
  2291. if ($sig != 'JPA')
  2292. {
  2293. // Not a JPA file
  2294. $this->setError( AKText::_('ERR_NOT_A_JPA_FILE') );
  2295. return false;
  2296. }
  2297. // Read and parse header length
  2298. $header_length_array = unpack( 'v', fread( $this->fp, 2 ) );
  2299. $header_length = $header_length_array[1];
  2300. // Read and parse the known portion of header data (14 bytes)
  2301. $bin_data = fread($this->fp, 14);
  2302. $header_data = unpack('Cmajor/Cminor/Vcount/Vuncsize/Vcsize', $bin_data);
  2303. // Load any remaining header data (forward compatibility)
  2304. $rest_length = $header_length - 19;
  2305. if( $rest_length > 0 )
  2306. $junk = fread($this->fp, $rest_length);
  2307. else
  2308. $junk = '';
  2309. // Temporary array with all the data we read
  2310. $temp = array(
  2311. 'signature' => $sig,
  2312. 'length' => $header_length,
  2313. 'major' => $header_data['major'],
  2314. 'minor' => $header_data['minor'],
  2315. 'filecount' => $header_data['count'],
  2316. 'uncompressedsize' => $header_data['uncsize'],
  2317. 'compressedsize' => $header_data['csize'],
  2318. 'unknowndata' => $junk
  2319. );
  2320. // Array-to-object conversion
  2321. foreach($temp as $key => $value)
  2322. {
  2323. $this->archiveHeaderData->{$key} = $value;
  2324. }
  2325. $this->currentPartOffset = @ftell($this->fp);
  2326. $this->dataReadLength = 0;
  2327. return true;
  2328. }
  2329. /**
  2330. * Concrete classes must use this method to read the file header
  2331. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  2332. */
  2333. protected function readFileHeader()
  2334. {
  2335. // If the current part is over, proceed to the next part please
  2336. if( $this->isEOF(true) ) {
  2337. $this->nextFile();
  2338. }
  2339. // Get and decode Entity Description Block
  2340. $signature = fread($this->fp, 3);
  2341. $this->fileHeader = new stdClass();
  2342. $this->fileHeader->timestamp = 0;
  2343. // Check signature
  2344. if( $signature != 'JPF' )
  2345. {
  2346. if($this->isEOF(true))
  2347. {
  2348. // This file is finished; make sure it's the last one
  2349. $this->nextFile();
  2350. if(!$this->isEOF(false))
  2351. {
  2352. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  2353. return false;
  2354. }
  2355. // We're just finished
  2356. return false;
  2357. }
  2358. else
  2359. {
  2360. // This is not a file block! The archive is corrupt.
  2361. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  2362. return false;
  2363. }
  2364. }
  2365. // This a JPA Entity Block. Process the header.
  2366. $isBannedFile = false;
  2367. // Read length of EDB and of the Entity Path Data
  2368. $length_array = unpack('vblocksize/vpathsize', fread($this->fp, 4));
  2369. // Read the path data
  2370. if($length_array['pathsize'] > 0) {
  2371. $file = fread( $this->fp, $length_array['pathsize'] );
  2372. } else {
  2373. $file = '';
  2374. }
  2375. // Handle file renaming
  2376. $isRenamed = false;
  2377. if(is_array($this->renameFiles) && (count($this->renameFiles) > 0) )
  2378. {
  2379. if(array_key_exists($file, $this->renameFiles))
  2380. {
  2381. $file = $this->renameFiles[$file];
  2382. $isRenamed = true;
  2383. }
  2384. }
  2385. // Handle directory renaming
  2386. $isDirRenamed = false;
  2387. if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
  2388. if(array_key_exists(dirname($file), $this->renameDirs)) {
  2389. $file = rtrim($this->renameDirs[dirname($file)],'/').'/'.basename($file);
  2390. $isRenamed = true;
  2391. $isDirRenamed = true;
  2392. }
  2393. }
  2394. // Read and parse the known data portion
  2395. $bin_data = fread( $this->fp, 14 );
  2396. $header_data = unpack('Ctype/Ccompression/Vcompsize/Vuncompsize/Vperms', $bin_data);
  2397. // Read any unknown data
  2398. $restBytes = $length_array['blocksize'] - (21 + $length_array['pathsize']);
  2399. if( $restBytes > 0 )
  2400. {
  2401. // Start reading the extra fields
  2402. while($restBytes >= 4)
  2403. {
  2404. $extra_header_data = fread($this->fp, 4);
  2405. $extra_header = unpack('vsignature/vlength', $extra_header_data);
  2406. $restBytes -= 4;
  2407. $extra_header['length'] -= 4;
  2408. switch($extra_header['signature'])
  2409. {
  2410. case 256:
  2411. // File modified timestamp
  2412. if($extra_header['length'] > 0)
  2413. {
  2414. $bindata = fread($this->fp, $extra_header['length']);
  2415. $restBytes -= $extra_header['length'];
  2416. $timestamps = unpack('Vmodified', substr($bindata,0,4));
  2417. $filectime = $timestamps['modified'];
  2418. $this->fileHeader->timestamp = $filectime;
  2419. }
  2420. break;
  2421. default:
  2422. // Unknown field
  2423. if($extra_header['length']>0) {
  2424. $junk = fread($this->fp, $extra_header['length']);
  2425. $restBytes -= $extra_header['length'];
  2426. }
  2427. break;
  2428. }
  2429. }
  2430. if($restBytes > 0) $junk = fread($this->fp, $restBytes);
  2431. }
  2432. $compressionType = $header_data['compression'];
  2433. // Populate the return array
  2434. $this->fileHeader->file = $file;
  2435. $this->fileHeader->compressed = $header_data['compsize'];
  2436. $this->fileHeader->uncompressed = $header_data['uncompsize'];
  2437. switch($header_data['type'])
  2438. {
  2439. case 0:
  2440. $this->fileHeader->type = 'dir';
  2441. break;
  2442. case 1:
  2443. $this->fileHeader->type = 'file';
  2444. break;
  2445. case 2:
  2446. $this->fileHeader->type = 'link';
  2447. break;
  2448. }
  2449. switch( $compressionType )
  2450. {
  2451. case 0:
  2452. $this->fileHeader->compression = 'none';
  2453. break;
  2454. case 1:
  2455. $this->fileHeader->compression = 'gzip';
  2456. break;
  2457. case 2:
  2458. $this->fileHeader->compression = 'bzip2';
  2459. break;
  2460. }
  2461. $this->fileHeader->permissions = $header_data['perms'];
  2462. // Find hard-coded banned files
  2463. if( (basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..") )
  2464. {
  2465. $isBannedFile = true;
  2466. }
  2467. // Also try to find banned files passed in class configuration
  2468. if((count($this->skipFiles) > 0) && (!$isRenamed) )
  2469. {
  2470. if(in_array($this->fileHeader->file, $this->skipFiles))
  2471. {
  2472. $isBannedFile = true;
  2473. }
  2474. }
  2475. // If we have a banned file, let's skip it
  2476. if($isBannedFile)
  2477. {
  2478. // Advance the file pointer, skipping exactly the size of the compressed data
  2479. $seekleft = $this->fileHeader->compressed;
  2480. while($seekleft > 0)
  2481. {
  2482. // Ensure that we can seek past archive part boundaries
  2483. $curSize = @filesize($this->archiveList[$this->currentPartNumber]);
  2484. $curPos = @ftell($this->fp);
  2485. $canSeek = $curSize - $curPos;
  2486. if($canSeek > $seekleft) $canSeek = $seekleft;
  2487. @fseek( $this->fp, $canSeek, SEEK_CUR );
  2488. $seekleft -= $canSeek;
  2489. if($seekleft) $this->nextFile();
  2490. }
  2491. $this->currentPartOffset = @ftell($this->fp);
  2492. $this->runState = AK_STATE_DONE;
  2493. return true;
  2494. }
  2495. // Last chance to prepend a path to the filename
  2496. if(!empty($this->addPath) && !$isDirRenamed)
  2497. {
  2498. $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
  2499. }
  2500. // Get the translated path name
  2501. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  2502. if($this->fileHeader->type == 'file')
  2503. {
  2504. // Regular file; ask the postproc engine to process its filename
  2505. if($restorePerms)
  2506. {
  2507. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file, $this->fileHeader->permissions );
  2508. }
  2509. else
  2510. {
  2511. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
  2512. }
  2513. }
  2514. elseif($this->fileHeader->type == 'dir')
  2515. {
  2516. $dir = $this->fileHeader->file;
  2517. // Directory; just create it
  2518. if($restorePerms)
  2519. {
  2520. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, $this->fileHeader->permissions );
  2521. }
  2522. else
  2523. {
  2524. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
  2525. }
  2526. $this->postProcEngine->processFilename(null);
  2527. }
  2528. else
  2529. {
  2530. // Symlink; do not post-process
  2531. $this->postProcEngine->processFilename(null);
  2532. }
  2533. $this->createDirectory();
  2534. // Header is read
  2535. $this->runState = AK_STATE_HEADER;
  2536. $this->dataReadLength = 0;
  2537. return true;
  2538. }
  2539. /**
  2540. * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
  2541. * it's finished processing the file data.
  2542. * @return bool True if processing the file data was successful, false if an error occured
  2543. */
  2544. protected function processFileData()
  2545. {
  2546. switch( $this->fileHeader->type )
  2547. {
  2548. case 'dir':
  2549. return $this->processTypeDir();
  2550. break;
  2551. case 'link':
  2552. return $this->processTypeLink();
  2553. break;
  2554. case 'file':
  2555. switch($this->fileHeader->compression)
  2556. {
  2557. case 'none':
  2558. return $this->processTypeFileUncompressed();
  2559. break;
  2560. case 'gzip':
  2561. case 'bzip2':
  2562. return $this->processTypeFileCompressedSimple();
  2563. break;
  2564. }
  2565. break;
  2566. }
  2567. }
  2568. private function processTypeFileUncompressed()
  2569. {
  2570. // Uncompressed files are being processed in small chunks, to avoid timeouts
  2571. if( ($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun','0') )
  2572. {
  2573. // Before processing file data, ensure permissions are adequate
  2574. $this->setCorrectPermissions( $this->fileHeader->file );
  2575. }
  2576. // Open the output file
  2577. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  2578. {
  2579. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
  2580. if ($this->dataReadLength == 0) {
  2581. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  2582. } else {
  2583. $outfp = @fopen( $this->fileHeader->realFile, 'ab' );
  2584. }
  2585. // Can we write to the file?
  2586. if( ($outfp === false) && (!$ignore) ) {
  2587. // An error occured
  2588. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  2589. return false;
  2590. }
  2591. }
  2592. // Does the file have any data, at all?
  2593. if( $this->fileHeader->compressed == 0 )
  2594. {
  2595. // No file data!
  2596. if( !AKFactory::get('kickstart.setup.dryrun','0') && is_resource($outfp) ) @fclose($outfp);
  2597. $this->runState = AK_STATE_DATAREAD;
  2598. return true;
  2599. }
  2600. // Reference to the global timer
  2601. $timer = AKFactory::getTimer();
  2602. $toReadBytes = 0;
  2603. $leftBytes = $this->fileHeader->compressed - $this->dataReadLength;
  2604. // Loop while there's data to read and enough time to do it
  2605. while( ($leftBytes > 0) && ($timer->getTimeLeft() > 0) )
  2606. {
  2607. $toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
  2608. $data = $this->fread( $this->fp, $toReadBytes );
  2609. $reallyReadBytes = akstringlen($data);
  2610. $leftBytes -= $reallyReadBytes;
  2611. $this->dataReadLength += $reallyReadBytes;
  2612. if($reallyReadBytes < $toReadBytes)
  2613. {
  2614. // We read less than requested! Why? Did we hit local EOF?
  2615. if( $this->isEOF(true) && !$this->isEOF(false) )
  2616. {
  2617. // Yeap. Let's go to the next file
  2618. $this->nextFile();
  2619. }
  2620. else
  2621. {
  2622. // Nope. The archive is corrupt
  2623. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  2624. return false;
  2625. }
  2626. }
  2627. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  2628. if(is_resource($outfp)) @fwrite( $outfp, $data );
  2629. }
  2630. // Close the file pointer
  2631. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  2632. if(is_resource($outfp)) @fclose($outfp);
  2633. // Was this a pre-timeout bail out?
  2634. if( $leftBytes > 0 )
  2635. {
  2636. $this->runState = AK_STATE_DATA;
  2637. }
  2638. else
  2639. {
  2640. // Oh! We just finished!
  2641. $this->runState = AK_STATE_DATAREAD;
  2642. $this->dataReadLength = 0;
  2643. }
  2644. return true;
  2645. }
  2646. private function processTypeFileCompressedSimple()
  2647. {
  2648. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  2649. {
  2650. // Before processing file data, ensure permissions are adequate
  2651. $this->setCorrectPermissions( $this->fileHeader->file );
  2652. // Open the output file
  2653. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  2654. // Can we write to the file?
  2655. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
  2656. if( ($outfp === false) && (!$ignore) ) {
  2657. // An error occured
  2658. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  2659. return false;
  2660. }
  2661. }
  2662. // Does the file have any data, at all?
  2663. if( $this->fileHeader->compressed == 0 )
  2664. {
  2665. // No file data!
  2666. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  2667. if(is_resource($outfp)) @fclose($outfp);
  2668. $this->runState = AK_STATE_DATAREAD;
  2669. return true;
  2670. }
  2671. // Simple compressed files are processed as a whole; we can't do chunk processing
  2672. $zipData = $this->fread( $this->fp, $this->fileHeader->compressed );
  2673. while( akstringlen($zipData) < $this->fileHeader->compressed )
  2674. {
  2675. // End of local file before reading all data, but have more archive parts?
  2676. if($this->isEOF(true) && !$this->isEOF(false))
  2677. {
  2678. // Yeap. Read from the next file
  2679. $this->nextFile();
  2680. $bytes_left = $this->fileHeader->compressed - akstringlen($zipData);
  2681. $zipData .= $this->fread( $this->fp, $bytes_left );
  2682. }
  2683. else
  2684. {
  2685. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  2686. return false;
  2687. }
  2688. }
  2689. if($this->fileHeader->compression == 'gzip')
  2690. {
  2691. $unzipData = gzinflate( $zipData );
  2692. }
  2693. elseif($this->fileHeader->compression == 'bzip2')
  2694. {
  2695. $unzipData = bzdecompress( $zipData );
  2696. }
  2697. unset($zipData);
  2698. // Write to the file.
  2699. if( !AKFactory::get('kickstart.setup.dryrun','0') && is_resource($outfp) )
  2700. {
  2701. @fwrite( $outfp, $unzipData, $this->fileHeader->uncompressed );
  2702. @fclose( $outfp );
  2703. }
  2704. unset($unzipData);
  2705. $this->runState = AK_STATE_DATAREAD;
  2706. return true;
  2707. }
  2708. /**
  2709. * Process the file data of a link entry
  2710. * @return bool
  2711. */
  2712. private function processTypeLink()
  2713. {
  2714. $readBytes = 0;
  2715. $toReadBytes = 0;
  2716. $leftBytes = $this->fileHeader->compressed;
  2717. $data = '';
  2718. while( $leftBytes > 0)
  2719. {
  2720. $toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
  2721. $mydata = $this->fread( $this->fp, $toReadBytes );
  2722. $reallyReadBytes = akstringlen($mydata);
  2723. $data .= $mydata;
  2724. $leftBytes -= $reallyReadBytes;
  2725. if($reallyReadBytes < $toReadBytes)
  2726. {
  2727. // We read less than requested! Why? Did we hit local EOF?
  2728. if( $this->isEOF(true) && !$this->isEOF(false) )
  2729. {
  2730. // Yeap. Let's go to the next file
  2731. $this->nextFile();
  2732. }
  2733. else
  2734. {
  2735. // Nope. The archive is corrupt
  2736. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  2737. return false;
  2738. }
  2739. }
  2740. }
  2741. // Try to remove an existing file or directory by the same name
  2742. if(file_exists($this->fileHeader->realFile)) { @unlink($this->fileHeader->realFile); @rmdir($this->fileHeader->realFile); }
  2743. // Remove any trailing slash
  2744. if(substr($this->fileHeader->realFile, -1) == '/') $this->fileHeader->realFile = substr($this->fileHeader->realFile, 0, -1);
  2745. // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
  2746. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  2747. @symlink($data, $this->fileHeader->realFile);
  2748. $this->runState = AK_STATE_DATAREAD;
  2749. return true; // No matter if the link was created!
  2750. }
  2751. /**
  2752. * Process the file data of a directory entry
  2753. * @return bool
  2754. */
  2755. private function processTypeDir()
  2756. {
  2757. // Directory entries in the JPA do not have file data, therefore we're done processing the entry
  2758. $this->runState = AK_STATE_DATAREAD;
  2759. return true;
  2760. }
  2761. /**
  2762. * Creates the directory this file points to
  2763. */
  2764. protected function createDirectory()
  2765. {
  2766. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  2767. // Do we need to create a directory?
  2768. if(empty($this->fileHeader->realFile)) $this->fileHeader->realFile = $this->fileHeader->file;
  2769. $lastSlash = strrpos($this->fileHeader->realFile, '/');
  2770. $dirName = substr( $this->fileHeader->realFile, 0, $lastSlash);
  2771. $perms = $this->flagRestorePermissions ? $retArray['permissions'] : 0755;
  2772. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
  2773. if( ($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore) ) {
  2774. $this->setError( AKText::sprintf('COULDNT_CREATE_DIR', $dirName) );
  2775. return false;
  2776. }
  2777. else
  2778. {
  2779. return true;
  2780. }
  2781. }
  2782. }
  2783. /**
  2784. * ZIP archive extraction class
  2785. *
  2786. * Since the file data portion of ZIP and JPA are similarly structured (it's empty for dirs,
  2787. * linked node name for symlinks, dumped binary data for no compressions and dumped gzipped
  2788. * binary data for gzip compression) we just have to subclass AKUnarchiverJPA and change the
  2789. * header reading bits. Reusable code ;)
  2790. */
  2791. class AKUnarchiverZIP extends AKUnarchiverJPA
  2792. {
  2793. var $expectDataDescriptor = false;
  2794. protected function readArchiveHeader()
  2795. {
  2796. // Initialize header data array
  2797. $this->archiveHeaderData = new stdClass();
  2798. // Open the first part
  2799. $this->nextFile();
  2800. // Fail for unreadable files
  2801. if( $this->fp === false ) return false;
  2802. // Read a possible multipart signature
  2803. $sigBinary = fread( $this->fp, 4 );
  2804. $headerData = unpack('Vsig', $sigBinary);
  2805. // Roll back if it's not a multipart archive
  2806. if( $headerData['sig'] == 0x04034b50 ) fseek($this->fp, -4, SEEK_CUR);
  2807. $multiPartSigs = array(
  2808. 0x08074b50, // Multi-part ZIP
  2809. 0x30304b50, // Multi-part ZIP (alternate)
  2810. 0x04034b50 // Single file
  2811. );
  2812. if( !in_array($headerData['sig'], $multiPartSigs) )
  2813. {
  2814. $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
  2815. return false;
  2816. }
  2817. $this->currentPartOffset = @ftell($this->fp);
  2818. $this->dataReadLength = 0;
  2819. return true;
  2820. }
  2821. /**
  2822. * Concrete classes must use this method to read the file header
  2823. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  2824. */
  2825. protected function readFileHeader()
  2826. {
  2827. // If the current part is over, proceed to the next part please
  2828. if( $this->isEOF(true) ) {
  2829. $this->nextFile();
  2830. }
  2831. if($this->expectDataDescriptor)
  2832. {
  2833. // The last file had bit 3 of the general purpose bit flag set. This means that we have a
  2834. // 12 byte data descriptor we need to skip. To make things worse, there might also be a 4
  2835. // byte optional data descriptor header (0x08074b50).
  2836. $junk = @fread($this->fp, 4);
  2837. $junk = unpack('Vsig', $junk);
  2838. if($junk['sig'] == 0x08074b50) {
  2839. // Yes, there was a signature
  2840. $junk = @fread($this->fp, 12);
  2841. if(defined('KSDEBUG')) {
  2842. debugMsg('Data descriptor (w/ header) skipped at '.(ftell($this->fp)-12));
  2843. }
  2844. } else {
  2845. // No, there was no signature, just read another 8 bytes
  2846. $junk = @fread($this->fp, 8);
  2847. if(defined('KSDEBUG')) {
  2848. debugMsg('Data descriptor (w/out header) skipped at '.(ftell($this->fp)-8));
  2849. }
  2850. }
  2851. // And check for EOF, too
  2852. if( $this->isEOF(true) ) {
  2853. if(defined('KSDEBUG')) {
  2854. debugMsg('EOF before reading header');
  2855. }
  2856. $this->nextFile();
  2857. }
  2858. }
  2859. // Get and decode Local File Header
  2860. $headerBinary = fread($this->fp, 30);
  2861. $headerData = unpack('Vsig/C2ver/vbitflag/vcompmethod/vlastmodtime/vlastmoddate/Vcrc/Vcompsize/Vuncomp/vfnamelen/veflen', $headerBinary);
  2862. // Check signature
  2863. if(!( $headerData['sig'] == 0x04034b50 ))
  2864. {
  2865. if(defined('KSDEBUG')) {
  2866. debugMsg('Not a file signature at '.(ftell($this->fp)-4));
  2867. }
  2868. // The signature is not the one used for files. Is this a central directory record (i.e. we're done)?
  2869. if($headerData['sig'] == 0x02014b50)
  2870. {
  2871. if(defined('KSDEBUG')) {
  2872. debugMsg('EOCD signature at '.(ftell($this->fp)-4));
  2873. }
  2874. // End of ZIP file detected. We'll just skip to the end of file...
  2875. while( $this->nextFile() ) {};
  2876. @fseek($this->fp, 0, SEEK_END); // Go to EOF
  2877. return false;
  2878. }
  2879. else
  2880. {
  2881. if(defined('KSDEBUG')) {
  2882. debugMsg( 'Invalid signature ' . dechex($headerData['sig']) . ' at '.ftell($this->fp) );
  2883. }
  2884. $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
  2885. return false;
  2886. }
  2887. }
  2888. // If bit 3 of the bitflag is set, expectDataDescriptor is true
  2889. $this->expectDataDescriptor = ($headerData['bitflag'] & 4) == 4;
  2890. $this->fileHeader = new stdClass();
  2891. $this->fileHeader->timestamp = 0;
  2892. // Read the last modified data and time
  2893. $lastmodtime = $headerData['lastmodtime'];
  2894. $lastmoddate = $headerData['lastmoddate'];
  2895. if($lastmoddate && $lastmodtime)
  2896. {
  2897. // ----- Extract time
  2898. $v_hour = ($lastmodtime & 0xF800) >> 11;
  2899. $v_minute = ($lastmodtime & 0x07E0) >> 5;
  2900. $v_seconde = ($lastmodtime & 0x001F)*2;
  2901. // ----- Extract date
  2902. $v_year = (($lastmoddate & 0xFE00) >> 9) + 1980;
  2903. $v_month = ($lastmoddate & 0x01E0) >> 5;
  2904. $v_day = $lastmoddate & 0x001F;
  2905. // ----- Get UNIX date format
  2906. $this->fileHeader->timestamp = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
  2907. }
  2908. $isBannedFile = false;
  2909. $this->fileHeader->compressed = $headerData['compsize'];
  2910. $this->fileHeader->uncompressed = $headerData['uncomp'];
  2911. $nameFieldLength = $headerData['fnamelen'];
  2912. $extraFieldLength = $headerData['eflen'];
  2913. // Read filename field
  2914. $this->fileHeader->file = fread( $this->fp, $nameFieldLength );
  2915. // Handle file renaming
  2916. $isRenamed = false;
  2917. if(is_array($this->renameFiles) && (count($this->renameFiles) > 0) )
  2918. {
  2919. if(array_key_exists($this->fileHeader->file, $this->renameFiles))
  2920. {
  2921. $this->fileHeader->file = $this->renameFiles[$this->fileHeader->file];
  2922. $isRenamed = true;
  2923. }
  2924. }
  2925. // Handle directory renaming
  2926. $isDirRenamed = false;
  2927. if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
  2928. if(array_key_exists(dirname($file), $this->renameDirs)) {
  2929. $file = rtrim($this->renameDirs[dirname($file)],'/').'/'.basename($file);
  2930. $isRenamed = true;
  2931. $isDirRenamed = true;
  2932. }
  2933. }
  2934. // Read extra field if present
  2935. if($extraFieldLength > 0) {
  2936. $extrafield = fread( $this->fp, $extraFieldLength );
  2937. }
  2938. if(defined('KSDEBUG')) {
  2939. debugMsg( '*'.ftell($this->fp).' IS START OF '.$this->fileHeader->file. ' ('.$this->fileHeader->compressed.' bytes)' );
  2940. }
  2941. // Decide filetype -- Check for directories
  2942. $this->fileHeader->type = 'file';
  2943. if( strrpos($this->fileHeader->file, '/') == strlen($this->fileHeader->file) - 1 ) $this->fileHeader->type = 'dir';
  2944. // Decide filetype -- Check for symbolic links
  2945. if( ($headerData['ver1'] == 10) && ($headerData['ver2'] == 3) )$this->fileHeader->type = 'link';
  2946. switch( $headerData['compmethod'] )
  2947. {
  2948. case 0:
  2949. $this->fileHeader->compression = 'none';
  2950. break;
  2951. case 8:
  2952. $this->fileHeader->compression = 'gzip';
  2953. break;
  2954. }
  2955. // Find hard-coded banned files
  2956. if( (basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..") )
  2957. {
  2958. $isBannedFile = true;
  2959. }
  2960. // Also try to find banned files passed in class configuration
  2961. if((count($this->skipFiles) > 0) && (!$isRenamed))
  2962. {
  2963. if(in_array($this->fileHeader->file, $this->skipFiles))
  2964. {
  2965. $isBannedFile = true;
  2966. }
  2967. }
  2968. // If we have a banned file, let's skip it
  2969. if($isBannedFile)
  2970. {
  2971. // Advance the file pointer, skipping exactly the size of the compressed data
  2972. $seekleft = $this->fileHeader->compressed;
  2973. while($seekleft > 0)
  2974. {
  2975. // Ensure that we can seek past archive part boundaries
  2976. $curSize = @filesize($this->archiveList[$this->currentPartNumber]);
  2977. $curPos = @ftell($this->fp);
  2978. $canSeek = $curSize - $curPos;
  2979. if($canSeek > $seekleft) $canSeek = $seekleft;
  2980. @fseek( $this->fp, $canSeek, SEEK_CUR );
  2981. $seekleft -= $canSeek;
  2982. if($seekleft) $this->nextFile();
  2983. }
  2984. $this->currentPartOffset = @ftell($this->fp);
  2985. $this->runState = AK_STATE_DONE;
  2986. return true;
  2987. }
  2988. // Last chance to prepend a path to the filename
  2989. if(!empty($this->addPath) && !$isDirRenamed)
  2990. {
  2991. $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
  2992. }
  2993. // Get the translated path name
  2994. if($this->fileHeader->type == 'file')
  2995. {
  2996. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
  2997. }
  2998. elseif($this->fileHeader->type == 'dir')
  2999. {
  3000. $this->fileHeader->timestamp = 0;
  3001. $dir = $this->fileHeader->file;
  3002. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
  3003. $this->postProcEngine->processFilename(null);
  3004. }
  3005. else
  3006. {
  3007. // Symlink; do not post-process
  3008. $this->fileHeader->timestamp = 0;
  3009. $this->postProcEngine->processFilename(null);
  3010. }
  3011. $this->createDirectory();
  3012. // Header is read
  3013. $this->runState = AK_STATE_HEADER;
  3014. return true;
  3015. }
  3016. }
  3017. /**
  3018. * Timer class
  3019. */
  3020. class AKCoreTimer extends AKAbstractObject
  3021. {
  3022. /** @var int Maximum execution time allowance per step */
  3023. private $max_exec_time = null;
  3024. /** @var int Timestamp of execution start */
  3025. private $start_time = null;
  3026. /**
  3027. * Public constructor, creates the timer object and calculates the execution time limits
  3028. * @return AECoreTimer
  3029. */
  3030. public function __construct()
  3031. {
  3032. parent::__construct();
  3033. // Initialize start time
  3034. $this->start_time = $this->microtime_float();
  3035. // Get configured max time per step and bias
  3036. $config_max_exec_time = AKFactory::get('kickstart.tuning.max_exec_time', 14);
  3037. $bias = AKFactory::get('kickstart.tuning.run_time_bias', 75)/100;
  3038. // Get PHP's maximum execution time (our upper limit)
  3039. if(@function_exists('ini_get'))
  3040. {
  3041. $php_max_exec_time = @ini_get("maximum_execution_time");
  3042. if ( (!is_numeric($php_max_exec_time)) || ($php_max_exec_time == 0) ) {
  3043. // If we have no time limit, set a hard limit of about 10 seconds
  3044. // (safe for Apache and IIS timeouts, verbose enough for users)
  3045. $php_max_exec_time = 14;
  3046. }
  3047. }
  3048. else
  3049. {
  3050. // If ini_get is not available, use a rough default
  3051. $php_max_exec_time = 14;
  3052. }
  3053. // Apply an arbitrary correction to counter CMS load time
  3054. $php_max_exec_time--;
  3055. // Apply bias
  3056. $php_max_exec_time = $php_max_exec_time * $bias;
  3057. $config_max_exec_time = $config_max_exec_time * $bias;
  3058. // Use the most appropriate time limit value
  3059. if( $config_max_exec_time > $php_max_exec_time )
  3060. {
  3061. $this->max_exec_time = $php_max_exec_time;
  3062. }
  3063. else
  3064. {
  3065. $this->max_exec_time = $config_max_exec_time;
  3066. }
  3067. }
  3068. /**
  3069. * Wake-up function to reset internal timer when we get unserialized
  3070. */
  3071. public function __wakeup()
  3072. {
  3073. // Re-initialize start time on wake-up
  3074. $this->start_time = $this->microtime_float();
  3075. }
  3076. /**
  3077. * Gets the number of seconds left, before we hit the "must break" threshold
  3078. * @return float
  3079. */
  3080. public function getTimeLeft()
  3081. {
  3082. return $this->max_exec_time - $this->getRunningTime();
  3083. }
  3084. /**
  3085. * Gets the time elapsed since object creation/unserialization, effectively how
  3086. * long Akeeba Engine has been processing data
  3087. * @return float
  3088. */
  3089. public function getRunningTime()
  3090. {
  3091. return $this->microtime_float() - $this->start_time;
  3092. }
  3093. /**
  3094. * Returns the current timestampt in decimal seconds
  3095. */
  3096. private function microtime_float()
  3097. {
  3098. list($usec, $sec) = explode(" ", microtime());
  3099. return ((float)$usec + (float)$sec);
  3100. }
  3101. /**
  3102. * Enforce the minimum execution time
  3103. */
  3104. public function enforce_min_exec_time()
  3105. {
  3106. // Try to get a sane value for PHP's maximum_execution_time INI parameter
  3107. if(@function_exists('ini_get'))
  3108. {
  3109. $php_max_exec = @ini_get("maximum_execution_time");
  3110. }
  3111. else
  3112. {
  3113. $php_max_exec = 10;
  3114. }
  3115. if ( ($php_max_exec == "") || ($php_max_exec == 0) ) {
  3116. $php_max_exec = 10;
  3117. }
  3118. // Decrease $php_max_exec time by 500 msec we need (approx.) to tear down
  3119. // the application, as well as another 500msec added for rounding
  3120. // error purposes. Also make sure this is never gonna be less than 0.
  3121. $php_max_exec = max($php_max_exec * 1000 - 1000, 0);
  3122. // Get the "minimum execution time per step" Akeeba Backup configuration variable
  3123. $minexectime = AKFactory::get('kickstart.tuning.min_exec_time',0);
  3124. if(!is_numeric($minexectime)) $minexectime = 0;
  3125. // Make sure we are not over PHP's time limit!
  3126. if($minexectime > $php_max_exec) $minexectime = $php_max_exec;
  3127. // Get current running time
  3128. $elapsed_time = $this->getRunningTime() * 1000;
  3129. // Only run a sleep delay if we haven't reached the minexectime execution time
  3130. if( ($minexectime > $elapsed_time) && ($elapsed_time > 0) )
  3131. {
  3132. $sleep_msec = $minexectime - $elapsed_time;
  3133. if(function_exists('usleep'))
  3134. {
  3135. usleep(1000 * $sleep_msec);
  3136. }
  3137. elseif(function_exists('time_nanosleep'))
  3138. {
  3139. $sleep_sec = floor($sleep_msec / 1000);
  3140. $sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000));
  3141. time_nanosleep($sleep_sec, $sleep_nsec);
  3142. }
  3143. elseif(function_exists('time_sleep_until'))
  3144. {
  3145. $until_timestamp = time() + $sleep_msec / 1000;
  3146. time_sleep_until($until_timestamp);
  3147. }
  3148. elseif(function_exists('sleep'))
  3149. {
  3150. $sleep_sec = ceil($sleep_msec/1000);
  3151. sleep( $sleep_sec );
  3152. }
  3153. }
  3154. elseif( $elapsed_time > 0 )
  3155. {
  3156. // No sleep required, even if user configured us to be able to do so.
  3157. }
  3158. }
  3159. /**
  3160. * Reset the timer. It should only be used in CLI mode!
  3161. */
  3162. public function resetTime()
  3163. {
  3164. $this->start_time = $this->microtime_float();
  3165. }
  3166. }
  3167. /**
  3168. * JPS archive extraction class
  3169. */
  3170. class AKUnarchiverJPS extends AKUnarchiverJPA
  3171. {
  3172. private $archiveHeaderData = array();
  3173. private $password = '';
  3174. public function __construct()
  3175. {
  3176. parent::__construct();
  3177. $this->password = AKFactory::get('kickstart.jps.password','');
  3178. }
  3179. protected function readArchiveHeader()
  3180. {
  3181. // Initialize header data array
  3182. $this->archiveHeaderData = new stdClass();
  3183. // Open the first part
  3184. $this->nextFile();
  3185. // Fail for unreadable files
  3186. if( $this->fp === false ) return false;
  3187. // Read the signature
  3188. $sig = fread( $this->fp, 3 );
  3189. if ($sig != 'JPS')
  3190. {
  3191. // Not a JPA file
  3192. $this->setError( AKText::_('ERR_NOT_A_JPS_FILE') );
  3193. return false;
  3194. }
  3195. // Read and parse the known portion of header data (5 bytes)
  3196. $bin_data = fread($this->fp, 5);
  3197. $header_data = unpack('Cmajor/Cminor/cspanned/vextra', $bin_data);
  3198. // Load any remaining header data (forward compatibility)
  3199. $rest_length = $header_data['extra'];
  3200. if( $rest_length > 0 )
  3201. $junk = fread($this->fp, $rest_length);
  3202. else
  3203. $junk = '';
  3204. // Temporary array with all the data we read
  3205. $temp = array(
  3206. 'signature' => $sig,
  3207. 'major' => $header_data['major'],
  3208. 'minor' => $header_data['minor'],
  3209. 'spanned' => $header_data['spanned']
  3210. );
  3211. // Array-to-object conversion
  3212. foreach($temp as $key => $value)
  3213. {
  3214. $this->archiveHeaderData->{$key} = $value;
  3215. }
  3216. $this->currentPartOffset = @ftell($this->fp);
  3217. $this->dataReadLength = 0;
  3218. return true;
  3219. }
  3220. /**
  3221. * Concrete classes must use this method to read the file header
  3222. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  3223. */
  3224. protected function readFileHeader()
  3225. {
  3226. // If the current part is over, proceed to the next part please
  3227. if( $this->isEOF(true) ) {
  3228. $this->nextFile();
  3229. }
  3230. // Get and decode Entity Description Block
  3231. $signature = fread($this->fp, 3);
  3232. // Check for end-of-archive siganture
  3233. if($signature == 'JPE')
  3234. {
  3235. $this->setState('postrun');
  3236. return true;
  3237. }
  3238. $this->fileHeader = new stdClass();
  3239. $this->fileHeader->timestamp = 0;
  3240. // Check signature
  3241. if( $signature != 'JPF' )
  3242. {
  3243. if($this->isEOF(true))
  3244. {
  3245. // This file is finished; make sure it's the last one
  3246. $this->nextFile();
  3247. if(!$this->isEOF(false))
  3248. {
  3249. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  3250. return false;
  3251. }
  3252. // We're just finished
  3253. return false;
  3254. }
  3255. else
  3256. {
  3257. fseek($this->fp, -6, SEEK_CUR);
  3258. $signature = fread($this->fp, 3);
  3259. if($signature == 'JPE')
  3260. {
  3261. return false;
  3262. }
  3263. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  3264. return false;
  3265. }
  3266. }
  3267. // This a JPA Entity Block. Process the header.
  3268. $isBannedFile = false;
  3269. // Read and decrypt the header
  3270. $edbhData = fread($this->fp, 4);
  3271. $edbh = unpack('vencsize/vdecsize', $edbhData);
  3272. $bin_data = fread($this->fp, $edbh['encsize']);
  3273. // Decrypt and truncate
  3274. $bin_data = AKEncryptionAES::AESDecryptCBC($bin_data, $this->password, 128);
  3275. $bin_data = substr($bin_data,0,$edbh['decsize']);
  3276. // Read length of EDB and of the Entity Path Data
  3277. $length_array = unpack('vpathsize', substr($bin_data,0,2) );
  3278. // Read the path data
  3279. $file = substr($bin_data,2,$length_array['pathsize']);
  3280. // Handle file renaming
  3281. $isRenamed = false;
  3282. if(is_array($this->renameFiles) && (count($this->renameFiles) > 0) )
  3283. {
  3284. if(array_key_exists($file, $this->renameFiles))
  3285. {
  3286. $file = $this->renameFiles[$file];
  3287. $isRenamed = true;
  3288. }
  3289. }
  3290. // Handle directory renaming
  3291. $isDirRenamed = false;
  3292. if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
  3293. if(array_key_exists(dirname($file), $this->renameDirs)) {
  3294. $file = rtrim($this->renameDirs[dirname($file)],'/').'/'.basename($file);
  3295. $isRenamed = true;
  3296. $isDirRenamed = true;
  3297. }
  3298. }
  3299. // Read and parse the known data portion
  3300. $bin_data = substr($bin_data, 2 + $length_array['pathsize']);
  3301. $header_data = unpack('Ctype/Ccompression/Vuncompsize/Vperms/Vfilectime', $bin_data);
  3302. $this->fileHeader->timestamp = $header_data['filectime'];
  3303. $compressionType = $header_data['compression'];
  3304. // Populate the return array
  3305. $this->fileHeader->file = $file;
  3306. $this->fileHeader->uncompressed = $header_data['uncompsize'];
  3307. switch($header_data['type'])
  3308. {
  3309. case 0:
  3310. $this->fileHeader->type = 'dir';
  3311. break;
  3312. case 1:
  3313. $this->fileHeader->type = 'file';
  3314. break;
  3315. case 2:
  3316. $this->fileHeader->type = 'link';
  3317. break;
  3318. }
  3319. switch( $compressionType )
  3320. {
  3321. case 0:
  3322. $this->fileHeader->compression = 'none';
  3323. break;
  3324. case 1:
  3325. $this->fileHeader->compression = 'gzip';
  3326. break;
  3327. case 2:
  3328. $this->fileHeader->compression = 'bzip2';
  3329. break;
  3330. }
  3331. $this->fileHeader->permissions = $header_data['perms'];
  3332. // Find hard-coded banned files
  3333. if( (basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..") )
  3334. {
  3335. $isBannedFile = true;
  3336. }
  3337. // Also try to find banned files passed in class configuration
  3338. if((count($this->skipFiles) > 0) && (!$isRenamed) )
  3339. {
  3340. if(in_array($this->fileHeader->file, $this->skipFiles))
  3341. {
  3342. $isBannedFile = true;
  3343. }
  3344. }
  3345. // If we have a banned file, let's skip it
  3346. if($isBannedFile)
  3347. {
  3348. $done = false;
  3349. while(!$done)
  3350. {
  3351. // Read the Data Chunk Block header
  3352. $binMiniHead = fread($this->fp, 8);
  3353. if( in_array( substr($binMiniHead,0,3), array('JPF','JPE') ) )
  3354. {
  3355. // Not a Data Chunk Block header, I am done skipping the file
  3356. @fseek($this->fp,-8,SEEK_CUR); // Roll back the file pointer
  3357. $done = true; // Mark as done
  3358. continue; // Exit loop
  3359. }
  3360. else
  3361. {
  3362. // Skip forward by the amount of compressed data
  3363. $miniHead = unpack('Vencsize/Vdecsize');
  3364. @fseek($this->fp, $miniHead['encsize'], SEEK_CUR);
  3365. }
  3366. }
  3367. $this->currentPartOffset = @ftell($this->fp);
  3368. $this->runState = AK_STATE_DONE;
  3369. return true;
  3370. }
  3371. // Last chance to prepend a path to the filename
  3372. if(!empty($this->addPath) && !$isDirRenamed)
  3373. {
  3374. $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
  3375. }
  3376. // Get the translated path name
  3377. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  3378. if($this->fileHeader->type == 'file')
  3379. {
  3380. // Regular file; ask the postproc engine to process its filename
  3381. if($restorePerms)
  3382. {
  3383. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file, $this->fileHeader->permissions );
  3384. }
  3385. else
  3386. {
  3387. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
  3388. }
  3389. }
  3390. elseif($this->fileHeader->type == 'dir')
  3391. {
  3392. $dir = $this->fileHeader->file;
  3393. $this->fileHeader->realFile = $dir;
  3394. // Directory; just create it
  3395. if($restorePerms)
  3396. {
  3397. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, $this->fileHeader->permissions );
  3398. }
  3399. else
  3400. {
  3401. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
  3402. }
  3403. $this->postProcEngine->processFilename(null);
  3404. }
  3405. else
  3406. {
  3407. // Symlink; do not post-process
  3408. $this->postProcEngine->processFilename(null);
  3409. }
  3410. $this->createDirectory();
  3411. // Header is read
  3412. $this->runState = AK_STATE_HEADER;
  3413. $this->dataReadLength = 0;
  3414. return true;
  3415. }
  3416. /**
  3417. * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
  3418. * it's finished processing the file data.
  3419. * @return bool True if processing the file data was successful, false if an error occured
  3420. */
  3421. protected function processFileData()
  3422. {
  3423. switch( $this->fileHeader->type )
  3424. {
  3425. case 'dir':
  3426. return $this->processTypeDir();
  3427. break;
  3428. case 'link':
  3429. return $this->processTypeLink();
  3430. break;
  3431. case 'file':
  3432. switch($this->fileHeader->compression)
  3433. {
  3434. case 'none':
  3435. return $this->processTypeFileUncompressed();
  3436. break;
  3437. case 'gzip':
  3438. case 'bzip2':
  3439. return $this->processTypeFileCompressedSimple();
  3440. break;
  3441. }
  3442. break;
  3443. }
  3444. }
  3445. private function processTypeFileUncompressed()
  3446. {
  3447. // Uncompressed files are being processed in small chunks, to avoid timeouts
  3448. if( ($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun','0') )
  3449. {
  3450. // Before processing file data, ensure permissions are adequate
  3451. $this->setCorrectPermissions( $this->fileHeader->file );
  3452. }
  3453. // Open the output file
  3454. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3455. {
  3456. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
  3457. if ($this->dataReadLength == 0) {
  3458. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  3459. } else {
  3460. $outfp = @fopen( $this->fileHeader->realFile, 'ab' );
  3461. }
  3462. // Can we write to the file?
  3463. if( ($outfp === false) && (!$ignore) ) {
  3464. // An error occured
  3465. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  3466. return false;
  3467. }
  3468. }
  3469. // Does the file have any data, at all?
  3470. if( $this->fileHeader->uncompressed == 0 )
  3471. {
  3472. // No file data!
  3473. if( !AKFactory::get('kickstart.setup.dryrun','0') && is_resource($outfp) ) @fclose($outfp);
  3474. $this->runState = AK_STATE_DATAREAD;
  3475. return true;
  3476. }
  3477. else
  3478. {
  3479. $this->setError('An uncompressed file was detected; this is not supported by this archive extraction utility');
  3480. return false;
  3481. }
  3482. return true;
  3483. }
  3484. private function processTypeFileCompressedSimple()
  3485. {
  3486. $timer = AKFactory::getTimer();
  3487. // Files are being processed in small chunks, to avoid timeouts
  3488. if( ($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun','0') )
  3489. {
  3490. // Before processing file data, ensure permissions are adequate
  3491. $this->setCorrectPermissions( $this->fileHeader->file );
  3492. }
  3493. // Open the output file
  3494. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3495. {
  3496. // Open the output file
  3497. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  3498. // Can we write to the file?
  3499. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
  3500. if( ($outfp === false) && (!$ignore) ) {
  3501. // An error occured
  3502. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  3503. return false;
  3504. }
  3505. }
  3506. // Does the file have any data, at all?
  3507. if( $this->fileHeader->uncompressed == 0 )
  3508. {
  3509. // No file data!
  3510. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3511. if(is_resource($outfp)) @fclose($outfp);
  3512. $this->runState = AK_STATE_DATAREAD;
  3513. return true;
  3514. }
  3515. $leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength;
  3516. // Loop while there's data to write and enough time to do it
  3517. while( ($leftBytes > 0) && ($timer->getTimeLeft() > 0) )
  3518. {
  3519. // Read the mini header
  3520. $binMiniHeader = fread($this->fp, 8);
  3521. $reallyReadBytes = akstringlen($binMiniHeader);
  3522. if($reallyReadBytes < 8)
  3523. {
  3524. // We read less than requested! Why? Did we hit local EOF?
  3525. if( $this->isEOF(true) && !$this->isEOF(false) )
  3526. {
  3527. // Yeap. Let's go to the next file
  3528. $this->nextFile();
  3529. // Retry reading the header
  3530. $binMiniHeader = fread($this->fp, 8);
  3531. $reallyReadBytes = akstringlen($binMiniHeader);
  3532. // Still not enough data? If so, the archive is corrupt or missing parts.
  3533. if($reallyReadBytes < 8) {
  3534. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3535. return false;
  3536. }
  3537. }
  3538. else
  3539. {
  3540. // Nope. The archive is corrupt
  3541. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3542. return false;
  3543. }
  3544. }
  3545. // Read the encrypted data
  3546. $miniHeader = unpack('Vencsize/Vdecsize', $binMiniHeader);
  3547. $toReadBytes = $miniHeader['encsize'];
  3548. $data = $this->fread( $this->fp, $toReadBytes );
  3549. $reallyReadBytes = akstringlen($data);
  3550. if($reallyReadBytes < $toReadBytes)
  3551. {
  3552. // We read less than requested! Why? Did we hit local EOF?
  3553. if( $this->isEOF(true) && !$this->isEOF(false) )
  3554. {
  3555. // Yeap. Let's go to the next file
  3556. $this->nextFile();
  3557. // Read the rest of the data
  3558. $toReadBytes -= $reallyReadBytes;
  3559. $restData = $this->fread( $this->fp, $toReadBytes );
  3560. $reallyReadBytes = akstringlen($restData);
  3561. if($reallyReadBytes < $toReadBytes) {
  3562. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3563. return false;
  3564. }
  3565. if(akstringlen($data) == 0) {
  3566. $data = $restData;
  3567. } else {
  3568. $data .= $restData;
  3569. }
  3570. }
  3571. else
  3572. {
  3573. // Nope. The archive is corrupt
  3574. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3575. return false;
  3576. }
  3577. }
  3578. // Decrypt the data
  3579. $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128);
  3580. // Is the length of the decrypted data less than expected?
  3581. $data_length = akstringlen($data);
  3582. if($data_length < $miniHeader['decsize']) {
  3583. $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));
  3584. return false;
  3585. }
  3586. // Trim the data
  3587. $data = substr($data,0,$miniHeader['decsize']);
  3588. // Decompress
  3589. $data = gzinflate($data);
  3590. $unc_len = akstringlen($data);
  3591. // Write the decrypted data
  3592. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3593. if(is_resource($outfp)) @fwrite( $outfp, $data, akstringlen($data) );
  3594. // Update the read length
  3595. $this->dataReadLength += $unc_len;
  3596. $leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength;
  3597. }
  3598. // Close the file pointer
  3599. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3600. if(is_resource($outfp)) @fclose($outfp);
  3601. // Was this a pre-timeout bail out?
  3602. if( $leftBytes > 0 )
  3603. {
  3604. $this->runState = AK_STATE_DATA;
  3605. }
  3606. else
  3607. {
  3608. // Oh! We just finished!
  3609. $this->runState = AK_STATE_DATAREAD;
  3610. $this->dataReadLength = 0;
  3611. }
  3612. }
  3613. /**
  3614. * Process the file data of a link entry
  3615. * @return bool
  3616. */
  3617. private function processTypeLink()
  3618. {
  3619. // Does the file have any data, at all?
  3620. if( $this->fileHeader->uncompressed == 0 )
  3621. {
  3622. // No file data!
  3623. $this->runState = AK_STATE_DATAREAD;
  3624. return true;
  3625. }
  3626. // Read the mini header
  3627. $binMiniHeader = fread($this->fp, 8);
  3628. $reallyReadBytes = akstringlen($binMiniHeader);
  3629. if($reallyReadBytes < 8)
  3630. {
  3631. // We read less than requested! Why? Did we hit local EOF?
  3632. if( $this->isEOF(true) && !$this->isEOF(false) )
  3633. {
  3634. // Yeap. Let's go to the next file
  3635. $this->nextFile();
  3636. // Retry reading the header
  3637. $binMiniHeader = fread($this->fp, 8);
  3638. $reallyReadBytes = akstringlen($binMiniHeader);
  3639. // Still not enough data? If so, the archive is corrupt or missing parts.
  3640. if($reallyReadBytes < 8) {
  3641. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3642. return false;
  3643. }
  3644. }
  3645. else
  3646. {
  3647. // Nope. The archive is corrupt
  3648. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3649. return false;
  3650. }
  3651. }
  3652. // Read the encrypted data
  3653. $miniHeader = unpack('Vencsize/Vdecsize', $binMiniHeader);
  3654. $toReadBytes = $miniHeader['encsize'];
  3655. $data = $this->fread( $this->fp, $toReadBytes );
  3656. $reallyReadBytes = akstringlen($data);
  3657. if($reallyReadBytes < $toReadBytes)
  3658. {
  3659. // We read less than requested! Why? Did we hit local EOF?
  3660. if( $this->isEOF(true) && !$this->isEOF(false) )
  3661. {
  3662. // Yeap. Let's go to the next file
  3663. $this->nextFile();
  3664. // Read the rest of the data
  3665. $toReadBytes -= $reallyReadBytes;
  3666. $restData = $this->fread( $this->fp, $toReadBytes );
  3667. $reallyReadBytes = akstringlen($data);
  3668. if($reallyReadBytes < $toReadBytes) {
  3669. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3670. return false;
  3671. }
  3672. $data .= $restData;
  3673. }
  3674. else
  3675. {
  3676. // Nope. The archive is corrupt
  3677. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3678. return false;
  3679. }
  3680. }
  3681. // Decrypt the data
  3682. $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128);
  3683. // Is the length of the decrypted data less than expected?
  3684. $data_length = akstringlen($data);
  3685. if($data_length < $miniHeader['decsize']) {
  3686. $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));
  3687. return false;
  3688. }
  3689. // Trim the data
  3690. $data = substr($data,0,$miniHeader['decsize']);
  3691. // Try to remove an existing file or directory by the same name
  3692. if(file_exists($this->fileHeader->realFile)) { @unlink($this->fileHeader->realFile); @rmdir($this->fileHeader->realFile); }
  3693. // Remove any trailing slash
  3694. if(substr($this->fileHeader->realFile, -1) == '/') $this->fileHeader->realFile = substr($this->fileHeader->realFile, 0, -1);
  3695. // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
  3696. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3697. @symlink($data, $this->fileHeader->realFile);
  3698. $this->runState = AK_STATE_DATAREAD;
  3699. return true; // No matter if the link was created!
  3700. }
  3701. /**
  3702. * Process the file data of a directory entry
  3703. * @return bool
  3704. */
  3705. private function processTypeDir()
  3706. {
  3707. // Directory entries in the JPA do not have file data, therefore we're done processing the entry
  3708. $this->runState = AK_STATE_DATAREAD;
  3709. return true;
  3710. }
  3711. /**
  3712. * Creates the directory this file points to
  3713. */
  3714. protected function createDirectory()
  3715. {
  3716. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  3717. // Do we need to create a directory?
  3718. $lastSlash = strrpos($this->fileHeader->realFile, '/');
  3719. $dirName = substr( $this->fileHeader->realFile, 0, $lastSlash);
  3720. $perms = $this->flagRestorePermissions ? $retArray['permissions'] : 0755;
  3721. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
  3722. if( ($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore) ) {
  3723. $this->setError( AKText::sprintf('COULDNT_CREATE_DIR', $dirName) );
  3724. return false;
  3725. }
  3726. else
  3727. {
  3728. return true;
  3729. }
  3730. }
  3731. }
  3732. /**
  3733. * A filesystem scanner which uses opendir()
  3734. */
  3735. class AKUtilsLister extends AKAbstractObject
  3736. {
  3737. public function &getFiles($folder, $pattern = '*')
  3738. {
  3739. // Initialize variables
  3740. $arr = array();
  3741. $false = false;
  3742. if(!is_dir($folder)) return $false;
  3743. $handle = @opendir($folder);
  3744. // If directory is not accessible, just return FALSE
  3745. if ($handle === FALSE) {
  3746. $this->setWarning( 'Unreadable directory '.$folder);
  3747. return $false;
  3748. }
  3749. while (($file = @readdir($handle)) !== false)
  3750. {
  3751. if( !fnmatch($pattern, $file) ) continue;
  3752. if (($file != '.') && ($file != '..'))
  3753. {
  3754. $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;
  3755. $dir = $folder . $ds . $file;
  3756. $isDir = is_dir($dir);
  3757. if (!$isDir) {
  3758. $arr[] = $dir;
  3759. }
  3760. }
  3761. }
  3762. @closedir($handle);
  3763. return $arr;
  3764. }
  3765. public function &getFolders($folder, $pattern = '*')
  3766. {
  3767. // Initialize variables
  3768. $arr = array();
  3769. $false = false;
  3770. if(!is_dir($folder)) return $false;
  3771. $handle = @opendir($folder);
  3772. // If directory is not accessible, just return FALSE
  3773. if ($handle === FALSE) {
  3774. $this->setWarning( 'Unreadable directory '.$folder);
  3775. return $false;
  3776. }
  3777. while (($file = @readdir($handle)) !== false)
  3778. {
  3779. if( !fnmatch($pattern, $file) ) continue;
  3780. if (($file != '.') && ($file != '..'))
  3781. {
  3782. $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;
  3783. $dir = $folder . $ds . $file;
  3784. $isDir = is_dir($dir);
  3785. if ($isDir) {
  3786. $arr[] = $dir;
  3787. }
  3788. }
  3789. }
  3790. @closedir($handle);
  3791. return $arr;
  3792. }
  3793. }
  3794. /**
  3795. * A simple INI-based i18n engine
  3796. */
  3797. class AKText extends AKAbstractObject
  3798. {
  3799. /**
  3800. * The default (en_GB) translation used when no other translation is available
  3801. * @var array
  3802. */
  3803. private $default_translation = array(
  3804. 'AUTOMODEON' => 'Auto-mode enabled',
  3805. 'ERR_NOT_A_JPA_FILE' => 'The file is not a JPA archive',
  3806. 'ERR_CORRUPT_ARCHIVE' => 'The archive file is corrupt, truncated or archive parts are missing',
  3807. 'ERR_INVALID_LOGIN' => 'Invalid login',
  3808. 'COULDNT_CREATE_DIR' => 'Could not create %s folder',
  3809. 'COULDNT_WRITE_FILE' => 'Could not open %s for writing.',
  3810. 'WRONG_FTP_HOST' => 'Wrong FTP host or port',
  3811. 'WRONG_FTP_USER' => 'Wrong FTP username or password',
  3812. 'WRONG_FTP_PATH1' => 'Wrong FTP initial directory - the directory doesn\'t exist',
  3813. 'FTP_CANT_CREATE_DIR' => 'Could not create directory %s',
  3814. 'FTP_TEMPDIR_NOT_WRITABLE' => 'Could not find or create a writable temporary directory',
  3815. 'FTP_COULDNT_UPLOAD' => 'Could not upload %s',
  3816. 'THINGS_HEADER' => 'Things you should know about Akeeba Kickstart',
  3817. 'THINGS_01' => 'Kickstart is not an installer. It is an archive extraction tool. The actual installer was put inside the archive file at backup time.',
  3818. 'THINGS_02' => 'Kickstart is not the only way to extract the backup archive. You can use Akeeba eXtract Wizard and upload the extracted files using FTP instead.',
  3819. 'THINGS_03' => 'Kickstart is bound by your server\'s configuration. As such, it may not work at all.',
  3820. 'THINGS_04' => 'You should download and upload your archive files using FTP in Binary transfer mode. Any other method could lead to a corrupt backup archive and restoration failure.',
  3821. 'THINGS_05' => 'Post-restoration site load errors are usually caused by .htaccess or php.ini directives. You should understand that blank pages, 404 and 500 errors can usually be worked around by editing the aforementioned files. It is not our job to mess with your configuration files, because this could be dangerous for your site.',
  3822. 'THINGS_06' => 'Kickstart overwrites files without a warning. If you are not sure that you are OK with that do not continue.',
  3823. 'THINGS_07' => 'Trying to restore to the temporary URL of a cPanel host (e.g. http://1.2.3.4/~username) will lead to restoration failure and your site will appear to be not working. This is normal and it\'s just how your server and CMS software work.',
  3824. 'THINGS_08' => 'You are supposed to read the documentation before using this software. Most issues can be avoided, or easily worked around, by understanding how this software works.',
  3825. 'THINGS_09' => 'This text does not imply that there is a problem detected. It is standard text displayed every time you launch Kickstart.',
  3826. 'CLOSE_LIGHTBOX' => 'Click here or press ESC to close this message',
  3827. 'SELECT_ARCHIVE' => 'Select a backup archive',
  3828. 'ARCHIVE_FILE' => 'Archive file:',
  3829. 'SELECT_EXTRACTION' => 'Select an extraction method',
  3830. 'WRITE_TO_FILES' => 'Write to files:',
  3831. 'WRITE_DIRECTLY' => 'Directly',
  3832. 'WRITE_FTP' => 'Use FTP',
  3833. 'FTP_HOST' => 'FTP host name:',
  3834. 'FTP_PORT' => 'FTP port:',
  3835. 'FTP_FTPS' => 'Use FTP over SSL (FTPS)',
  3836. 'FTP_PASSIVE' => 'Use FTP Passive Mode',
  3837. 'FTP_USER' => 'FTP user name:',
  3838. 'FTP_PASS' => 'FTP password:',
  3839. 'FTP_DIR' => 'FTP directory:',
  3840. 'FTP_TEMPDIR' => 'Temporary directory:',
  3841. 'FTP_CONNECTION_OK' => 'FTP Connection Established',
  3842. 'FTP_CONNECTION_FAILURE' => 'The FTP Connection Failed',
  3843. 'FTP_TEMPDIR_WRITABLE' => 'The temporary directory is writable.',
  3844. 'FTP_TEMPDIR_UNWRITABLE' => 'The temporary directory is not writable. Please check the permissions.',
  3845. 'BTN_CHECK' => 'Check',
  3846. 'BTN_RESET' => 'Reset',
  3847. 'BTN_TESTFTPCON' => 'Test FTP connection',
  3848. 'BTN_GOTOSTART' => 'Start over',
  3849. 'FINE_TUNE' => 'Fine tune',
  3850. 'MIN_EXEC_TIME' => 'Minimum execution time:',
  3851. 'MAX_EXEC_TIME' => 'Maximum execution time:',
  3852. 'SECONDS_PER_STEP' => 'seconds per step',
  3853. 'EXTRACT_FILES' => 'Extract files',
  3854. 'BTN_START' => 'Start',
  3855. 'EXTRACTING' => 'Extracting',
  3856. 'DO_NOT_CLOSE_EXTRACT' => 'Do not close this window while the extraction is in progress',
  3857. 'RESTACLEANUP' => 'Restoration and Clean Up',
  3858. 'BTN_RUNINSTALLER' => 'Run the Installer',
  3859. 'BTN_CLEANUP' => 'Clean Up',
  3860. 'BTN_SITEFE' => 'Visit your site\'s front-end',
  3861. 'BTN_SITEBE' => 'Visit your site\'s back-end',
  3862. 'WARNINGS' => 'Extraction Warnings',
  3863. 'ERROR_OCCURED' => 'An error occured',
  3864. 'STEALTH_MODE' => 'Stealth mode',
  3865. 'STEALTH_URL' => 'HTML file to show to web visitors',
  3866. 'ERR_NOT_A_JPS_FILE' => 'The file is not a JPA archive',
  3867. 'ERR_INVALID_JPS_PASSWORD' => 'The password you gave is wrong or the archive is corrupt',
  3868. 'JPS_PASSWORD' => 'Archive Password (for JPS files)',
  3869. 'INVALID_FILE_HEADER' => 'Invalid header in archive file, part %s, offset %s',
  3870. 'NEEDSOMEHELPKS' => 'Want some help to use this tool? Read this first:',
  3871. 'QUICKSTART' => 'Quick Start Guide',
  3872. 'CANTGETITTOWORK' => 'Can\'t get it to work? Click me!',
  3873. 'NOARCHIVESCLICKHERE' => 'No archives detected. Click here for troubleshooting instructions.',
  3874. 'POSTRESTORATIONTROUBLESHOOTING' => 'Something not working after the restoration? Click here for troubleshooting instructions.',
  3875. 'UPDATE_HEADER' => 'An updated version of Akeeba Kickstart (<span id="update-version">unknown</span>) is available!',
  3876. 'UPDATE_NOTICE' => 'You are advised to always use the latest version of Akeeba Kickstart available. Older versions may be subject to bugs and will not be supported.',
  3877. 'UPDATE_DLNOW' => 'Download now',
  3878. 'UPDATE_MOREINFO' => 'More information'
  3879. );
  3880. /**
  3881. * The array holding the translation keys
  3882. * @var array
  3883. */
  3884. private $strings;
  3885. /**
  3886. * The currently detected language (ISO code)
  3887. * @var string
  3888. */
  3889. private $language;
  3890. /*
  3891. * Initializes the translation engine
  3892. * @return AKText
  3893. */
  3894. public function __construct()
  3895. {
  3896. // Start with the default translation
  3897. $this->strings = $this->default_translation;
  3898. // Try loading the translation file in English, if it exists
  3899. $this->loadTranslation('en-GB');
  3900. // Try loading the translation file in the browser's preferred language, if it exists
  3901. $this->getBrowserLanguage();
  3902. if(!is_null($this->language))
  3903. {
  3904. $this->loadTranslation();
  3905. }
  3906. }
  3907. /**
  3908. * Singleton pattern for Language
  3909. * @return Language The global Language instance
  3910. */
  3911. public static function &getInstance()
  3912. {
  3913. static $instance;
  3914. if(!is_object($instance))
  3915. {
  3916. $instance = new AKText();
  3917. }
  3918. return $instance;
  3919. }
  3920. public static function _($string)
  3921. {
  3922. $text = self::getInstance();
  3923. $key = strtoupper($string);
  3924. $key = substr($key, 0, 1) == '_' ? substr($key, 1) : $key;
  3925. if (isset ($text->strings[$key]))
  3926. {
  3927. $string = $text->strings[$key];
  3928. }
  3929. else
  3930. {
  3931. if (defined($string))
  3932. {
  3933. $string = constant($string);
  3934. }
  3935. }
  3936. return $string;
  3937. }
  3938. public static function sprintf($key)
  3939. {
  3940. $text = self::getInstance();
  3941. $args = func_get_args();
  3942. if (count($args) > 0) {
  3943. $args[0] = $text->_($args[0]);
  3944. return @call_user_func_array('sprintf', $args);
  3945. }
  3946. return '';
  3947. }
  3948. public function dumpLanguage()
  3949. {
  3950. $out = '';
  3951. foreach($this->strings as $key => $value)
  3952. {
  3953. $out .= "$key=$value\n";
  3954. }
  3955. return $out;
  3956. }
  3957. public function asJavascript()
  3958. {
  3959. $out = '';
  3960. foreach($this->strings as $key => $value)
  3961. {
  3962. $key = addcslashes($key, '\\\'"');
  3963. $value = addcslashes($value, '\\\'"');
  3964. if(!empty($out)) $out .= ",\n";
  3965. $out .= "'$key':\t'$value'";
  3966. }
  3967. return $out;
  3968. }
  3969. public function resetTranslation()
  3970. {
  3971. $this->strings = $this->default_translation;
  3972. }
  3973. public function getBrowserLanguage()
  3974. {
  3975. // Detection code from Full Operating system language detection, by Harald Hope
  3976. // Retrieved from http://techpatterns.com/downloads/php_language_detection.php
  3977. $user_languages = array();
  3978. //check to see if language is set
  3979. if ( isset( $_SERVER["HTTP_ACCEPT_LANGUAGE"] ) )
  3980. {
  3981. $languages = strtolower( $_SERVER["HTTP_ACCEPT_LANGUAGE"] );
  3982. // $languages = ' fr-ch;q=0.3, da, en-us;q=0.8, en;q=0.5, fr;q=0.3';
  3983. // need to remove spaces from strings to avoid error
  3984. $languages = str_replace( ' ', '', $languages );
  3985. $languages = explode( ",", $languages );
  3986. foreach ( $languages as $language_list )
  3987. {
  3988. // pull out the language, place languages into array of full and primary
  3989. // string structure:
  3990. $temp_array = array();
  3991. // slice out the part before ; on first step, the part before - on second, place into array
  3992. $temp_array[0] = substr( $language_list, 0, strcspn( $language_list, ';' ) );//full language
  3993. $temp_array[1] = substr( $language_list, 0, 2 );// cut out primary language
  3994. if( (strlen($temp_array[0]) == 5) && ( (substr($temp_array[0],2,1) == '-') || (substr($temp_array[0],2,1) == '_') ) )
  3995. {
  3996. $langLocation = strtoupper(substr($temp_array[0],3,2));
  3997. $temp_array[0] = $temp_array[1].'-'.$langLocation;
  3998. }
  3999. //place this array into main $user_languages language array
  4000. $user_languages[] = $temp_array;
  4001. }
  4002. }
  4003. else// if no languages found
  4004. {
  4005. $user_languages[0] = array( '','' ); //return blank array.
  4006. }
  4007. $this->language = null;
  4008. $basename=basename(__FILE__, '.php') . '.ini';
  4009. // Try to match main language part of the filename, irrespective of the location, e.g. de_DE will do if de_CH doesn't exist.
  4010. $fs = new AKUtilsLister();
  4011. $iniFiles = $fs->getFiles( dirname(__FILE__), '*.'.$basename );
  4012. if(empty($iniFiles) && ($basename != 'kickstart.ini')) {
  4013. $basename = 'kickstart.ini';
  4014. $iniFiles = $fs->getFiles( dirname(__FILE__), '*.'.$basename );
  4015. }
  4016. if (is_array($iniFiles)) {
  4017. foreach($user_languages as $languageStruct)
  4018. {
  4019. if(is_null($this->language))
  4020. {
  4021. // Get files matching the main lang part
  4022. $iniFiles = $fs->getFiles( dirname(__FILE__), $languageStruct[1].'-??.'.$basename );
  4023. if (count($iniFiles) > 0) {
  4024. $filename = $iniFiles[0];
  4025. $filename = substr($filename, strlen(dirname(__FILE__))+1);
  4026. $this->language = substr($filename, 0, 5);
  4027. } else {
  4028. $this->language = null;
  4029. }
  4030. }
  4031. }
  4032. }
  4033. if(is_null($this->language)) {
  4034. // Try to find a full language match
  4035. foreach($user_languages as $languageStruct)
  4036. {
  4037. if (@file_exists($languageStruct[0].'.'.$basename) && is_null($this->language)) {
  4038. $this->language = $languageStruct[0];
  4039. } else {
  4040. }
  4041. }
  4042. } else {
  4043. // Do we have an exact match?
  4044. foreach($user_languages as $languageStruct)
  4045. {
  4046. if(substr($this->language,0,strlen($languageStruct[1])) == $languageStruct[1]) {
  4047. if(file_exists($languageStruct[0].'.'.$basename)) {
  4048. $this->language = $languageStruct[0];
  4049. }
  4050. }
  4051. }
  4052. }
  4053. // Now, scan for full language based on the partial match
  4054. }
  4055. private function loadTranslation( $lang = null )
  4056. {
  4057. $dirname = function_exists('getcwd') ? getcwd() : dirname(__FILE__);
  4058. $basename=basename(__FILE__, '.php') . '.ini';
  4059. if( empty($lang) ) $lang = $this->language;
  4060. $translationFilename = $dirname.DIRECTORY_SEPARATOR.$lang.'.'.$basename;
  4061. if(!@file_exists($translationFilename) && ($basename != 'kickstart.ini')) {
  4062. $basename = 'kickstart.ini';
  4063. $translationFilename = $dirname.DIRECTORY_SEPARATOR.$lang.'.'.$basename;
  4064. }
  4065. if(!@file_exists($translationFilename)) return;
  4066. $temp = self::parse_ini_file($translationFilename, false);
  4067. if(!is_array($this->strings)) $this->strings = array();
  4068. if(empty($temp)) {
  4069. $this->strings = array_merge($this->default_translation, $this->strings);
  4070. } else {
  4071. $this->strings = array_merge($this->strings, $temp);
  4072. }
  4073. }
  4074. /**
  4075. * A PHP based INI file parser.
  4076. *
  4077. * Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on
  4078. * the parse_ini_file page on http://gr.php.net/parse_ini_file
  4079. *
  4080. * @param string $file Filename to process
  4081. * @param bool $process_sections True to also process INI sections
  4082. * @return array An associative array of sections, keys and values
  4083. * @access private
  4084. */
  4085. public static function parse_ini_file($file, $process_sections = false, $raw_data = false)
  4086. {
  4087. $process_sections = ($process_sections !== true) ? false : true;
  4088. if(!$raw_data)
  4089. {
  4090. $ini = @file($file);
  4091. }
  4092. else
  4093. {
  4094. $ini = $file;
  4095. }
  4096. if (count($ini) == 0) {return array();}
  4097. $sections = array();
  4098. $values = array();
  4099. $result = array();
  4100. $globals = array();
  4101. $i = 0;
  4102. if(!empty($ini)) foreach ($ini as $line) {
  4103. $line = trim($line);
  4104. $line = str_replace("\t", " ", $line);
  4105. // Comments
  4106. if (!preg_match('/^[a-zA-Z0-9[]/', $line)) {continue;}
  4107. // Sections
  4108. if ($line{0} == '[') {
  4109. $tmp = explode(']', $line);
  4110. $sections[] = trim(substr($tmp[0], 1));
  4111. $i++;
  4112. continue;
  4113. }
  4114. // Key-value pair
  4115. list($key, $value) = explode('=', $line, 2);
  4116. $key = trim($key);
  4117. $value = trim($value);
  4118. if (strstr($value, ";")) {
  4119. $tmp = explode(';', $value);
  4120. if (count($tmp) == 2) {
  4121. if ((($value{0} != '"') && ($value{0} != "'")) ||
  4122. preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
  4123. preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value) ){
  4124. $value = $tmp[0];
  4125. }
  4126. } else {
  4127. if ($value{0} == '"') {
  4128. $value = preg_replace('/^"(.*)".*/', '$1', $value);
  4129. } elseif ($value{0} == "'") {
  4130. $value = preg_replace("/^'(.*)'.*/", '$1', $value);
  4131. } else {
  4132. $value = $tmp[0];
  4133. }
  4134. }
  4135. }
  4136. $value = trim($value);
  4137. $value = trim($value, "'\"");
  4138. if ($i == 0) {
  4139. if (substr($line, -1, 2) == '[]') {
  4140. $globals[$key][] = $value;
  4141. } else {
  4142. $globals[$key] = $value;
  4143. }
  4144. } else {
  4145. if (substr($line, -1, 2) == '[]') {
  4146. $values[$i-1][$key][] = $value;
  4147. } else {
  4148. $values[$i-1][$key] = $value;
  4149. }
  4150. }
  4151. }
  4152. for($j = 0; $j < $i; $j++) {
  4153. if ($process_sections === true) {
  4154. $result[$sections[$j]] = $values[$j];
  4155. } else {
  4156. $result[] = $values[$j];
  4157. }
  4158. }
  4159. return $result + $globals;
  4160. }
  4161. }
  4162. /**
  4163. * The Akeeba Kickstart Factory class
  4164. * This class is reponssible for instanciating all Akeeba Kicsktart classes
  4165. */
  4166. class AKFactory {
  4167. /** @var array A list of instanciated objects */
  4168. private $objectlist = array();
  4169. /** @var array Simple hash data storage */
  4170. private $varlist = array();
  4171. /** Private constructor makes sure we can't directly instanciate the class */
  4172. private function __construct() {}
  4173. /**
  4174. * Gets a single, internally used instance of the Factory
  4175. * @param string $serialized_data [optional] Serialized data to spawn the instance from
  4176. * @return AKFactory A reference to the unique Factory object instance
  4177. */
  4178. protected static function &getInstance( $serialized_data = null ) {
  4179. static $myInstance;
  4180. if(!is_object($myInstance) || !is_null($serialized_data))
  4181. if(!is_null($serialized_data))
  4182. {
  4183. $myInstance = unserialize($serialized_data);
  4184. }
  4185. else
  4186. {
  4187. $myInstance = new self();
  4188. }
  4189. return $myInstance;
  4190. }
  4191. /**
  4192. * Internal function which instanciates a class named $class_name.
  4193. * The autoloader
  4194. * @param object $class_name
  4195. * @return
  4196. */
  4197. protected static function &getClassInstance($class_name) {
  4198. $self = self::getInstance();
  4199. if(!isset($self->objectlist[$class_name]))
  4200. {
  4201. $self->objectlist[$class_name] = new $class_name;
  4202. }
  4203. return $self->objectlist[$class_name];
  4204. }
  4205. // ========================================================================
  4206. // Public factory interface
  4207. // ========================================================================
  4208. /**
  4209. * Gets a serialized snapshot of the Factory for safekeeping (hibernate)
  4210. * @return string The serialized snapshot of the Factory
  4211. */
  4212. public static function serialize() {
  4213. $engine = self::getUnarchiver();
  4214. $engine->shutdown();
  4215. $serialized = serialize(self::getInstance());
  4216. if(function_exists('base64_encode') && function_exists('base64_decode'))
  4217. {
  4218. $serialized = base64_encode($serialized);
  4219. }
  4220. return $serialized;
  4221. }
  4222. /**
  4223. * Regenerates the full Factory state from a serialized snapshot (resume)
  4224. * @param string $serialized_data The serialized snapshot to resume from
  4225. */
  4226. public static function unserialize($serialized_data) {
  4227. if(function_exists('base64_encode') && function_exists('base64_decode'))
  4228. {
  4229. $serialized_data = base64_decode($serialized_data);
  4230. }
  4231. self::getInstance($serialized_data);
  4232. }
  4233. /**
  4234. * Reset the internal factory state, freeing all previously created objects
  4235. */
  4236. public static function nuke()
  4237. {
  4238. $self = self::getInstance();
  4239. foreach($self->objectlist as $key => $object)
  4240. {
  4241. $self->objectlist[$key] = null;
  4242. }
  4243. $self->objectlist = array();
  4244. }
  4245. // ========================================================================
  4246. // Public hash data storage interface
  4247. // ========================================================================
  4248. public static function set($key, $value)
  4249. {
  4250. $self = self::getInstance();
  4251. $self->varlist[$key] = $value;
  4252. }
  4253. public static function get($key, $default = null)
  4254. {
  4255. $self = self::getInstance();
  4256. if( array_key_exists($key, $self->varlist) )
  4257. {
  4258. return $self->varlist[$key];
  4259. }
  4260. else
  4261. {
  4262. return $default;
  4263. }
  4264. }
  4265. // ========================================================================
  4266. // Akeeba Kickstart classes
  4267. // ========================================================================
  4268. /**
  4269. * Gets the post processing engine
  4270. * @param string $proc_engine
  4271. */
  4272. public static function &getPostProc($proc_engine = null)
  4273. {
  4274. static $class_name;
  4275. if( empty($class_name) )
  4276. {
  4277. if(empty($proc_engine))
  4278. {
  4279. $proc_engine = self::get('kickstart.procengine','direct');
  4280. }
  4281. $class_name = 'AKPostproc'.ucfirst($proc_engine);
  4282. }
  4283. return self::getClassInstance($class_name);
  4284. }
  4285. /**
  4286. * Gets the unarchiver engine
  4287. */
  4288. public static function &getUnarchiver( $configOverride = null )
  4289. {
  4290. static $class_name;
  4291. if(!empty($configOverride))
  4292. {
  4293. if($configOverride['reset']) {
  4294. $class_name = null;
  4295. }
  4296. }
  4297. if( empty($class_name) )
  4298. {
  4299. $filetype = self::get('kickstart.setup.filetype', null);
  4300. if(empty($filetype))
  4301. {
  4302. $filename = self::get('kickstart.setup.sourcefile', null);
  4303. $basename = basename($filename);
  4304. $baseextension = strtoupper(substr($basename,-3));
  4305. switch($baseextension)
  4306. {
  4307. case 'JPA':
  4308. $filetype = 'JPA';
  4309. break;
  4310. case 'JPS':
  4311. $filetype = 'JPS';
  4312. break;
  4313. case 'ZIP':
  4314. $filetype = 'ZIP';
  4315. break;
  4316. default:
  4317. die('Invalid archive type or extension in file '.$filename);
  4318. break;
  4319. }
  4320. }
  4321. $class_name = 'AKUnarchiver'.ucfirst($filetype);
  4322. }
  4323. $destdir = self::get('kickstart.setup.destdir', null);
  4324. if(empty($destdir))
  4325. {
  4326. $destdir = function_exists('getcwd') ? getcwd() : dirname(__FILE__);
  4327. }
  4328. $object = self::getClassInstance($class_name);
  4329. if( $object->getState() == 'init')
  4330. {
  4331. // Initialize the object
  4332. $config = array(
  4333. 'filename' => self::get('kickstart.setup.sourcefile', ''),
  4334. 'restore_permissions' => self::get('kickstart.setup.restoreperms', 0),
  4335. 'post_proc' => self::get('kickstart.procengine', 'direct'),
  4336. 'add_path' => $destdir,
  4337. 'rename_files' => array( '.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak' ),
  4338. 'skip_files' => array( basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak' )
  4339. );
  4340. if(!defined('KICKSTART'))
  4341. {
  4342. // In restore.php mode we have to exclude some more files
  4343. $config['skip_files'][] = 'administrator/components/com_akeeba/restore.php';
  4344. $config['skip_files'][] = 'administrator/components/com_akeeba/restoration.php';
  4345. }
  4346. if(!empty($configOverride))
  4347. {
  4348. foreach($configOverride as $key => $value)
  4349. {
  4350. $config[$key] = $value;
  4351. }
  4352. }
  4353. $object->setup($config);
  4354. }
  4355. return $object;
  4356. }
  4357. /**
  4358. * Get the a reference to the Akeeba Engine's timer
  4359. * @return AKCoreTimer
  4360. */
  4361. public static function &getTimer()
  4362. {
  4363. return self::getClassInstance('AKCoreTimer');
  4364. }
  4365. }
  4366. /**
  4367. * AES implementation in PHP (c) Chris Veness 2005-2011
  4368. * (http://www.movable-type.co.uk/scripts/aes-php.html)
  4369. * I offer these formulæ & scripts for free use and adaptation as my contribution to the
  4370. * open-source info-sphere from which I have received so much. You are welcome to re-use these
  4371. * scripts [under a simple attribution license or a GPL licence, without any warranty express or implied]
  4372. * provided solely that you retain my copyright notice and a link to this page.
  4373. * licence. No warranty of any form is offered.
  4374. *
  4375. * Modified for Akeeba Backup by Nicholas K. Dionysopoulos
  4376. */
  4377. class AKEncryptionAES
  4378. {
  4379. // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
  4380. protected static $Sbox =
  4381. array(0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
  4382. 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
  4383. 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
  4384. 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
  4385. 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
  4386. 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
  4387. 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
  4388. 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
  4389. 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
  4390. 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
  4391. 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
  4392. 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
  4393. 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
  4394. 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
  4395. 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
  4396. 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16);
  4397. // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
  4398. protected static $Rcon = array(
  4399. array(0x00, 0x00, 0x00, 0x00),
  4400. array(0x01, 0x00, 0x00, 0x00),
  4401. array(0x02, 0x00, 0x00, 0x00),
  4402. array(0x04, 0x00, 0x00, 0x00),
  4403. array(0x08, 0x00, 0x00, 0x00),
  4404. array(0x10, 0x00, 0x00, 0x00),
  4405. array(0x20, 0x00, 0x00, 0x00),
  4406. array(0x40, 0x00, 0x00, 0x00),
  4407. array(0x80, 0x00, 0x00, 0x00),
  4408. array(0x1b, 0x00, 0x00, 0x00),
  4409. array(0x36, 0x00, 0x00, 0x00) );
  4410. protected static $passwords = array();
  4411. /**
  4412. * AES Cipher function: encrypt 'input' with Rijndael algorithm
  4413. *
  4414. * @param input message as byte-array (16 bytes)
  4415. * @param w key schedule as 2D byte-array (Nr+1 x Nb bytes) -
  4416. * generated from the cipher key by KeyExpansion()
  4417. * @return ciphertext as byte-array (16 bytes)
  4418. */
  4419. protected static function Cipher($input, $w) { // main Cipher function [�5.1]
  4420. $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
  4421. $Nr = count($w)/$Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
  4422. $state = array(); // initialise 4xNb byte-array 'state' with input [�3.4]
  4423. for ($i=0; $i<4*$Nb; $i++) $state[$i%4][floor($i/4)] = $input[$i];
  4424. $state = self::AddRoundKey($state, $w, 0, $Nb);
  4425. for ($round=1; $round<$Nr; $round++) { // apply Nr rounds
  4426. $state = self::SubBytes($state, $Nb);
  4427. $state = self::ShiftRows($state, $Nb);
  4428. $state = self::MixColumns($state, $Nb);
  4429. $state = self::AddRoundKey($state, $w, $round, $Nb);
  4430. }
  4431. $state = self::SubBytes($state, $Nb);
  4432. $state = self::ShiftRows($state, $Nb);
  4433. $state = self::AddRoundKey($state, $w, $Nr, $Nb);
  4434. $output = array(4*$Nb); // convert state to 1-d array before returning [�3.4]
  4435. for ($i=0; $i<4*$Nb; $i++) $output[$i] = $state[$i%4][floor($i/4)];
  4436. return $output;
  4437. }
  4438. protected static function AddRoundKey($state, $w, $rnd, $Nb) { // xor Round Key into state S [�5.1.4]
  4439. for ($r=0; $r<4; $r++) {
  4440. for ($c=0; $c<$Nb; $c++) $state[$r][$c] ^= $w[$rnd*4+$c][$r];
  4441. }
  4442. return $state;
  4443. }
  4444. protected static function SubBytes($s, $Nb) { // apply SBox to state S [�5.1.1]
  4445. for ($r=0; $r<4; $r++) {
  4446. for ($c=0; $c<$Nb; $c++) $s[$r][$c] = self::$Sbox[$s[$r][$c]];
  4447. }
  4448. return $s;
  4449. }
  4450. protected static function ShiftRows($s, $Nb) { // shift row r of state S left by r bytes [�5.1.2]
  4451. $t = array(4);
  4452. for ($r=1; $r<4; $r++) {
  4453. for ($c=0; $c<4; $c++) $t[$c] = $s[$r][($c+$r)%$Nb]; // shift into temp copy
  4454. for ($c=0; $c<4; $c++) $s[$r][$c] = $t[$c]; // and copy back
  4455. } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
  4456. return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
  4457. }
  4458. protected static function MixColumns($s, $Nb) { // combine bytes of each col of state S [�5.1.3]
  4459. for ($c=0; $c<4; $c++) {
  4460. $a = array(4); // 'a' is a copy of the current column from 's'
  4461. $b = array(4); // 'b' is a�{02} in GF(2^8)
  4462. for ($i=0; $i<4; $i++) {
  4463. $a[$i] = $s[$i][$c];
  4464. $b[$i] = $s[$i][$c]&0x80 ? $s[$i][$c]<<1 ^ 0x011b : $s[$i][$c]<<1;
  4465. }
  4466. // a[n] ^ b[n] is a�{03} in GF(2^8)
  4467. $s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3
  4468. $s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3
  4469. $s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3
  4470. $s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3
  4471. }
  4472. return $s;
  4473. }
  4474. /**
  4475. * Key expansion for Rijndael Cipher(): performs key expansion on cipher key
  4476. * to generate a key schedule
  4477. *
  4478. * @param key cipher key byte-array (16 bytes)
  4479. * @return key schedule as 2D byte-array (Nr+1 x Nb bytes)
  4480. */
  4481. protected static function KeyExpansion($key) { // generate Key Schedule from Cipher Key [�5.2]
  4482. $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
  4483. $Nk = count($key)/4; // key length (in words): 4/6/8 for 128/192/256-bit keys
  4484. $Nr = $Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
  4485. $w = array();
  4486. $temp = array();
  4487. for ($i=0; $i<$Nk; $i++) {
  4488. $r = array($key[4*$i], $key[4*$i+1], $key[4*$i+2], $key[4*$i+3]);
  4489. $w[$i] = $r;
  4490. }
  4491. for ($i=$Nk; $i<($Nb*($Nr+1)); $i++) {
  4492. $w[$i] = array();
  4493. for ($t=0; $t<4; $t++) $temp[$t] = $w[$i-1][$t];
  4494. if ($i % $Nk == 0) {
  4495. $temp = self::SubWord(self::RotWord($temp));
  4496. for ($t=0; $t<4; $t++) $temp[$t] ^= self::$Rcon[$i/$Nk][$t];
  4497. } else if ($Nk > 6 && $i%$Nk == 4) {
  4498. $temp = self::SubWord($temp);
  4499. }
  4500. for ($t=0; $t<4; $t++) $w[$i][$t] = $w[$i-$Nk][$t] ^ $temp[$t];
  4501. }
  4502. return $w;
  4503. }
  4504. protected static function SubWord($w) { // apply SBox to 4-byte word w
  4505. for ($i=0; $i<4; $i++) $w[$i] = self::$Sbox[$w[$i]];
  4506. return $w;
  4507. }
  4508. protected static function RotWord($w) { // rotate 4-byte word w left by one byte
  4509. $tmp = $w[0];
  4510. for ($i=0; $i<3; $i++) $w[$i] = $w[$i+1];
  4511. $w[3] = $tmp;
  4512. return $w;
  4513. }
  4514. /*
  4515. * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints
  4516. *
  4517. * @param a number to be shifted (32-bit integer)
  4518. * @param b number of bits to shift a to the right (0..31)
  4519. * @return a right-shifted and zero-filled by b bits
  4520. */
  4521. protected static function urs($a, $b) {
  4522. $a &= 0xffffffff; $b &= 0x1f; // (bounds check)
  4523. if ($a&0x80000000 && $b>0) { // if left-most bit set
  4524. $a = ($a>>1) & 0x7fffffff; // right-shift one bit & clear left-most bit
  4525. $a = $a >> ($b-1); // remaining right-shifts
  4526. } else { // otherwise
  4527. $a = ($a>>$b); // use normal right-shift
  4528. }
  4529. return $a;
  4530. }
  4531. /**
  4532. * Encrypt a text using AES encryption in Counter mode of operation
  4533. * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
  4534. *
  4535. * Unicode multi-byte character safe
  4536. *
  4537. * @param plaintext source text to be encrypted
  4538. * @param password the password to use to generate a key
  4539. * @param nBits number of bits to be used in the key (128, 192, or 256)
  4540. * @return encrypted text
  4541. */
  4542. public static function AESEncryptCtr($plaintext, $password, $nBits) {
  4543. $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  4544. if (!($nBits==128 || $nBits==192 || $nBits==256)) return ''; // standard allows 128/192/256 bit keys
  4545. // note PHP (5) gives us plaintext and password in UTF8 encoding!
  4546. // use AES itself to encrypt password to get cipher key (using plain password as source for
  4547. // key expansion) - gives us well encrypted key
  4548. $nBytes = $nBits/8; // no bytes in key
  4549. $pwBytes = array();
  4550. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  4551. $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  4552. $key = array_merge($key, array_slice($key, 0, $nBytes-16)); // expand key to 16/24/32 bytes long
  4553. // initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in
  4554. // 1st 8 bytes, block counter in 2nd 8 bytes
  4555. $counterBlock = array();
  4556. $nonce = floor(microtime(true)*1000); // timestamp: milliseconds since 1-Jan-1970
  4557. $nonceSec = floor($nonce/1000);
  4558. $nonceMs = $nonce%1000;
  4559. // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
  4560. for ($i=0; $i<4; $i++) $counterBlock[$i] = self::urs($nonceSec, $i*8) & 0xff;
  4561. for ($i=0; $i<4; $i++) $counterBlock[$i+4] = $nonceMs & 0xff;
  4562. // and convert it to a string to go on the front of the ciphertext
  4563. $ctrTxt = '';
  4564. for ($i=0; $i<8; $i++) $ctrTxt .= chr($counterBlock[$i]);
  4565. // generate key schedule - an expansion of the key into distinct Key Rounds for each round
  4566. $keySchedule = self::KeyExpansion($key);
  4567. $blockCount = ceil(strlen($plaintext)/$blockSize);
  4568. $ciphertxt = array(); // ciphertext as array of strings
  4569. for ($b=0; $b<$blockCount; $b++) {
  4570. // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
  4571. // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
  4572. for ($c=0; $c<4; $c++) $counterBlock[15-$c] = self::urs($b, $c*8) & 0xff;
  4573. for ($c=0; $c<4; $c++) $counterBlock[15-$c-4] = self::urs($b/0x100000000, $c*8);
  4574. $cipherCntr = self::Cipher($counterBlock, $keySchedule); // -- encrypt counter block --
  4575. // block size is reduced on final block
  4576. $blockLength = $b<$blockCount-1 ? $blockSize : (strlen($plaintext)-1)%$blockSize+1;
  4577. $cipherByte = array();
  4578. for ($i=0; $i<$blockLength; $i++) { // -- xor plaintext with ciphered counter byte-by-byte --
  4579. $cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b*$blockSize+$i, 1));
  4580. $cipherByte[$i] = chr($cipherByte[$i]);
  4581. }
  4582. $ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext
  4583. }
  4584. // implode is more efficient than repeated string concatenation
  4585. $ciphertext = $ctrTxt . implode('', $ciphertxt);
  4586. $ciphertext = base64_encode($ciphertext);
  4587. return $ciphertext;
  4588. }
  4589. /**
  4590. * Decrypt a text encrypted by AES in counter mode of operation
  4591. *
  4592. * @param ciphertext source text to be decrypted
  4593. * @param password the password to use to generate a key
  4594. * @param nBits number of bits to be used in the key (128, 192, or 256)
  4595. * @return decrypted text
  4596. */
  4597. public static function AESDecryptCtr($ciphertext, $password, $nBits) {
  4598. $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  4599. if (!($nBits==128 || $nBits==192 || $nBits==256)) return ''; // standard allows 128/192/256 bit keys
  4600. $ciphertext = base64_decode($ciphertext);
  4601. // use AES to encrypt password (mirroring encrypt routine)
  4602. $nBytes = $nBits/8; // no bytes in key
  4603. $pwBytes = array();
  4604. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  4605. $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  4606. $key = array_merge($key, array_slice($key, 0, $nBytes-16)); // expand key to 16/24/32 bytes long
  4607. // recover nonce from 1st element of ciphertext
  4608. $counterBlock = array();
  4609. $ctrTxt = substr($ciphertext, 0, 8);
  4610. for ($i=0; $i<8; $i++) $counterBlock[$i] = ord(substr($ctrTxt,$i,1));
  4611. // generate key schedule
  4612. $keySchedule = self::KeyExpansion($key);
  4613. // separate ciphertext into blocks (skipping past initial 8 bytes)
  4614. $nBlocks = ceil((strlen($ciphertext)-8) / $blockSize);
  4615. $ct = array();
  4616. for ($b=0; $b<$nBlocks; $b++) $ct[$b] = substr($ciphertext, 8+$b*$blockSize, 16);
  4617. $ciphertext = $ct; // ciphertext is now array of block-length strings
  4618. // plaintext will get generated block-by-block into array of block-length strings
  4619. $plaintxt = array();
  4620. for ($b=0; $b<$nBlocks; $b++) {
  4621. // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
  4622. for ($c=0; $c<4; $c++) $counterBlock[15-$c] = self::urs($b, $c*8) & 0xff;
  4623. for ($c=0; $c<4; $c++) $counterBlock[15-$c-4] = self::urs(($b+1)/0x100000000-1, $c*8) & 0xff;
  4624. $cipherCntr = self::Cipher($counterBlock, $keySchedule); // encrypt counter block
  4625. $plaintxtByte = array();
  4626. for ($i=0; $i<strlen($ciphertext[$b]); $i++) {
  4627. // -- xor plaintext with ciphered counter byte-by-byte --
  4628. $plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b],$i,1));
  4629. $plaintxtByte[$i] = chr($plaintxtByte[$i]);
  4630. }
  4631. $plaintxt[$b] = implode('', $plaintxtByte);
  4632. }
  4633. // join array of blocks into single plaintext string
  4634. $plaintext = implode('',$plaintxt);
  4635. return $plaintext;
  4636. }
  4637. /**
  4638. * AES decryption in CBC mode. This is the standard mode (the CTR methods
  4639. * actually use Rijndael-128 in CTR mode, which - technically - isn't AES).
  4640. *
  4641. * Supports AES-128, AES-192 and AES-256. It supposes that the last 4 bytes
  4642. * contained a little-endian unsigned long integer representing the unpadded
  4643. * data length.
  4644. *
  4645. * @since 3.0.1
  4646. * @author Nicholas K. Dionysopoulos
  4647. *
  4648. * @param string $ciphertext The data to encrypt
  4649. * @param string $password Encryption password
  4650. * @param int $nBits Encryption key size. Can be 128, 192 or 256
  4651. * @return string The plaintext
  4652. */
  4653. public static function AESDecryptCBC($ciphertext, $password, $nBits = 128)
  4654. {
  4655. if (!($nBits==128 || $nBits==192 || $nBits==256)) return false; // standard allows 128/192/256 bit keys
  4656. if(!function_exists('mcrypt_module_open')) return false;
  4657. // Try to fetch cached key/iv or create them if they do not exist
  4658. $lookupKey = $password.'-'.$nBits;
  4659. if(array_key_exists($lookupKey, self::$passwords))
  4660. {
  4661. $key = self::$passwords[$lookupKey]['key'];
  4662. $iv = self::$passwords[$lookupKey]['iv'];
  4663. }
  4664. else
  4665. {
  4666. // use AES itself to encrypt password to get cipher key (using plain password as source for
  4667. // key expansion) - gives us well encrypted key
  4668. $nBytes = $nBits/8; // no bytes in key
  4669. $pwBytes = array();
  4670. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  4671. $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  4672. $key = array_merge($key, array_slice($key, 0, $nBytes-16)); // expand key to 16/24/32 bytes long
  4673. $newKey = '';
  4674. foreach($key as $int) { $newKey .= chr($int); }
  4675. $key = $newKey;
  4676. // Create an Initialization Vector (IV) based on the password, using the same technique as for the key
  4677. $nBytes = 16; // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes
  4678. $pwBytes = array();
  4679. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  4680. $iv = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  4681. $newIV = '';
  4682. foreach($iv as $int) { $newIV .= chr($int); }
  4683. $iv = $newIV;
  4684. self::$passwords[$lookupKey]['key'] = $key;
  4685. self::$passwords[$lookupKey]['iv'] = $iv;
  4686. }
  4687. // Read the data size
  4688. $data_size = unpack('V', substr($ciphertext,-4) );
  4689. // Decrypt
  4690. $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
  4691. mcrypt_generic_init($td, $key, $iv);
  4692. $plaintext = mdecrypt_generic($td, substr($ciphertext,0,-4));
  4693. mcrypt_generic_deinit($td);
  4694. // Trim padding, if necessary
  4695. if(strlen($plaintext) > $data_size)
  4696. {
  4697. $plaintext = substr($plaintext, 0, $data_size);
  4698. }
  4699. return $plaintext;
  4700. }
  4701. }
  4702. /**
  4703. * The Master Setup will read the configuration parameters from restoration.php, abiautomation.ini, or
  4704. * the JSON-encoded "configuration" input variable and return the status.
  4705. * @return bool True if the master configuration was applied to the Factory object
  4706. */
  4707. function masterSetup()
  4708. {
  4709. // ------------------------------------------------------------
  4710. // 1. Import basic setup parameters
  4711. // ------------------------------------------------------------
  4712. $ini_data = null;
  4713. // In restore.php mode, require restoration.php or fail
  4714. if(!defined('KICKSTART'))
  4715. {
  4716. // This is the standalone mode, used by Akeeba Backup Professional. It looks for a restoration.php
  4717. // file to perform its magic. If the file is not there, we will abort.
  4718. $setupFile = 'restoration.php';
  4719. if( !file_exists($setupFile) )
  4720. {
  4721. // Uh oh... Somebody tried to pooh on our back yard. Lock the gates! Don't let the traitor inside!
  4722. AKFactory::set('kickstart.enabled', false);
  4723. return false;
  4724. }
  4725. // Load restoration.php. It creates a global variable named $restoration_setup
  4726. require_once $setupFile;
  4727. $ini_data = $restoration_setup;
  4728. if(empty($ini_data))
  4729. {
  4730. // No parameters fetched. Darn, how am I supposed to work like that?!
  4731. AKFactory::set('kickstart.enabled', false);
  4732. return false;
  4733. }
  4734. AKFactory::set('kickstart.enabled', true);
  4735. }
  4736. else
  4737. {
  4738. // Maybe we have $restoration_setup defined in the head of kickstart.php
  4739. global $restoration_setup;
  4740. if(!empty($restoration_setup) && !is_array($restoration_setup)) {
  4741. $ini_data = AKText::parse_ini_file($restoration_setup, false, true);
  4742. } elseif(is_array($restoration_setup)) {
  4743. $ini_data = $restoration_setup;
  4744. }
  4745. }
  4746. // Import any data from $restoration_setup
  4747. if(!empty($ini_data))
  4748. {
  4749. foreach($ini_data as $key => $value)
  4750. {
  4751. AKFactory::set($key, $value);
  4752. }
  4753. AKFactory::set('kickstart.enabled', true);
  4754. }
  4755. // Reinitialize $ini_data
  4756. $ini_data = null;
  4757. // ------------------------------------------------------------
  4758. // 2. Explode JSON parameters into $_REQUEST scope
  4759. // ------------------------------------------------------------
  4760. // Detect a JSON string in the request variable and store it.
  4761. $json = getQueryParam('json', null);
  4762. // Remove everything from the request array
  4763. if(!empty($_REQUEST))
  4764. {
  4765. foreach($_REQUEST as $key => $value)
  4766. {
  4767. unset($_REQUEST[$key]);
  4768. }
  4769. }
  4770. // Decrypt a possibly encrypted JSON string
  4771. if(!empty($json))
  4772. {
  4773. $password = AKFactory::get('kickstart.security.password', null);
  4774. if(!empty($password))
  4775. {
  4776. $json = AKEncryptionAES::AESDecryptCtr($json, $password, 128);
  4777. }
  4778. // Get the raw data
  4779. $raw = json_decode( $json, true );
  4780. // Pass all JSON data to the request array
  4781. if(!empty($raw))
  4782. {
  4783. foreach($raw as $key => $value)
  4784. {
  4785. $_REQUEST[$key] = $value;
  4786. }
  4787. }
  4788. }
  4789. // ------------------------------------------------------------
  4790. // 3. Try the "factory" variable
  4791. // ------------------------------------------------------------
  4792. // A "factory" variable will override all other settings.
  4793. $serialized = getQueryParam('factory', null);
  4794. if( !is_null($serialized) )
  4795. {
  4796. // Get the serialized factory
  4797. AKFactory::unserialize($serialized);
  4798. AKFactory::set('kickstart.enabled', true);
  4799. return true;
  4800. }
  4801. // ------------------------------------------------------------
  4802. // 4. Try abiautomation.ini and the configuration variable for Kickstart
  4803. // ------------------------------------------------------------
  4804. if(defined('KICKSTART'))
  4805. {
  4806. // We are in Kickstart mode. abiautomation.ini has precedence.
  4807. $setupFile = 'abiautomation.ini';
  4808. if( file_exists($setupFile) )
  4809. {
  4810. // abiautomation.ini was found
  4811. $ini_data = AKText::parse_ini_file('restoration.ini', false);
  4812. }
  4813. else
  4814. {
  4815. // abiautomation.ini was not found. Let's try input parameters.
  4816. $configuration = getQueryParam('configuration');
  4817. if( !is_null($configuration) )
  4818. {
  4819. // Let's decode the configuration from JSON to array
  4820. $ini_data = json_decode($configuration, true);
  4821. }
  4822. else
  4823. {
  4824. // Neither exists. Enable Kickstart's interface anyway.
  4825. $ini_data = array('kickstart.enabled'=>true);
  4826. }
  4827. }
  4828. // Import any INI data we might have from other sources
  4829. if(!empty($ini_data))
  4830. {
  4831. foreach($ini_data as $key => $value)
  4832. {
  4833. AKFactory::set($key, $value);
  4834. }
  4835. AKFactory::set('kickstart.enabled', true);
  4836. return true;
  4837. }
  4838. }
  4839. }
  4840. // Mini-controller for restore.php
  4841. if(!defined('KICKSTART'))
  4842. {
  4843. // The observer class, used to report number of files and bytes processed
  4844. class RestorationObserver extends AKAbstractPartObserver
  4845. {
  4846. public $compressedTotal = 0;
  4847. public $uncompressedTotal = 0;
  4848. public $filesProcessed = 0;
  4849. public function update($object, $message)
  4850. {
  4851. if(!is_object($message)) return;
  4852. if( !array_key_exists('type', get_object_vars($message)) ) return;
  4853. if( $message->type == 'startfile' )
  4854. {
  4855. $this->filesProcessed++;
  4856. $this->compressedTotal += $message->content->compressed;
  4857. $this->uncompressedTotal += $message->content->uncompressed;
  4858. }
  4859. }
  4860. public function __toString()
  4861. {
  4862. return __CLASS__;
  4863. }
  4864. }
  4865. // Import configuration
  4866. masterSetup();
  4867. $retArray = array(
  4868. 'status' => true,
  4869. 'message' => null
  4870. );
  4871. $enabled = AKFactory::get('kickstart.enabled', false);
  4872. if($enabled)
  4873. {
  4874. $task = getQueryParam('task');
  4875. switch($task)
  4876. {
  4877. case 'ping':
  4878. // ping task - realy does nothing!
  4879. $timer = AKFactory::getTimer();
  4880. $timer->enforce_min_exec_time();
  4881. break;
  4882. case 'startRestore':
  4883. AKFactory::nuke(); // Reset the factory
  4884. // Let the control flow to the next step (the rest of the code is common!!)
  4885. case 'stepRestore':
  4886. $engine = AKFactory::getUnarchiver(); // Get the engine
  4887. $observer = new RestorationObserver(); // Create a new observer
  4888. $engine->attach($observer); // Attach the observer
  4889. $engine->tick();
  4890. $ret = $engine->getStatusArray();
  4891. if( $ret['Error'] != '' )
  4892. {
  4893. $retArray['status'] = false;
  4894. $retArray['done'] = true;
  4895. $retArray['message'] = $ret['Error'];
  4896. }
  4897. elseif( !$ret['HasRun'] )
  4898. {
  4899. $retArray['files'] = $observer->filesProcessed;
  4900. $retArray['bytesIn'] = $observer->compressedTotal;
  4901. $retArray['bytesOut'] = $observer->uncompressedTotal;
  4902. $retArray['status'] = true;
  4903. $retArray['done'] = true;
  4904. }
  4905. else
  4906. {
  4907. $retArray['files'] = $observer->filesProcessed;
  4908. $retArray['bytesIn'] = $observer->compressedTotal;
  4909. $retArray['bytesOut'] = $observer->uncompressedTotal;
  4910. $retArray['status'] = true;
  4911. $retArray['done'] = false;
  4912. $retArray['factory'] = AKFactory::serialize();
  4913. }
  4914. break;
  4915. case 'finalizeRestore':
  4916. $root = AKFactory::get('kickstart.setup.destdir');
  4917. // Remove the installation directory
  4918. recursive_remove_directory( $root.'/installation' );
  4919. $postproc = AKFactory::getPostProc();
  4920. // Rename htaccess.bak to .htaccess
  4921. if(file_exists($root.'/htaccess.bak'))
  4922. {
  4923. if( file_exists($root.'/.htaccess') )
  4924. {
  4925. $postproc->unlink($root.'/.htaccess');
  4926. }
  4927. $postproc->rename( $root.'/htaccess.bak', $root.'/.htaccess' );
  4928. }
  4929. // Remove restoration.php
  4930. $basepath = dirname(__FILE__);
  4931. $basepath = rtrim( str_replace('\\','/',$basepath), '/' );
  4932. if(!empty($basepath)) $basepath .= '/';
  4933. $postproc->unlink( $basepath.'restoration.php' );
  4934. break;
  4935. default:
  4936. // Invalid task!
  4937. $enabled = false;
  4938. break;
  4939. }
  4940. }
  4941. // Maybe we weren't authorized or the task was invalid?
  4942. if(!$enabled)
  4943. {
  4944. // Maybe the user failed to enter any information
  4945. $retArray['status'] = false;
  4946. $retArray['message'] = AKText::_('ERR_INVALID_LOGIN');
  4947. }
  4948. // JSON encode the message
  4949. $json = json_encode($retArray);
  4950. // Do I have to encrypt?
  4951. $password = AKFactory::get('kickstart.security.password', null);
  4952. if(!empty($password))
  4953. {
  4954. $json = AKEncryptionAES::AESEncryptCtr($json, $password, 128);
  4955. }
  4956. // Return the message
  4957. echo "###$json###";
  4958. }
  4959. // ------------ lixlpixel recursive PHP functions -------------
  4960. // recursive_remove_directory( directory to delete, empty )
  4961. // expects path to directory and optional TRUE / FALSE to empty
  4962. // of course PHP has to have the rights to delete the directory
  4963. // you specify and all files and folders inside the directory
  4964. // ------------------------------------------------------------
  4965. function recursive_remove_directory($directory)
  4966. {
  4967. // if the path has a slash at the end we remove it here
  4968. if(substr($directory,-1) == '/')
  4969. {
  4970. $directory = substr($directory,0,-1);
  4971. }
  4972. // if the path is not valid or is not a directory ...
  4973. if(!file_exists($directory) || !is_dir($directory))
  4974. {
  4975. // ... we return false and exit the function
  4976. return FALSE;
  4977. // ... if the path is not readable
  4978. }elseif(!is_readable($directory))
  4979. {
  4980. // ... we return false and exit the function
  4981. return FALSE;
  4982. // ... else if the path is readable
  4983. }else{
  4984. // we open the directory
  4985. $handle = opendir($directory);
  4986. $postproc = AKFactory::getPostProc();
  4987. // and scan through the items inside
  4988. while (FALSE !== ($item = readdir($handle)))
  4989. {
  4990. // if the filepointer is not the current directory
  4991. // or the parent directory
  4992. if($item != '.' && $item != '..')
  4993. {
  4994. // we build the new path to delete
  4995. $path = $directory.'/'.$item;
  4996. // if the new path is a directory
  4997. if(is_dir($path))
  4998. {
  4999. // we call this function with the new path
  5000. recursive_remove_directory($path);
  5001. // if the new path is a file
  5002. }else{
  5003. // we remove the file
  5004. $postproc->unlink($path);
  5005. }
  5006. }
  5007. }
  5008. // close the directory
  5009. closedir($handle);
  5010. // try to delete the now empty directory
  5011. if(!$postproc->rmdir($directory))
  5012. {
  5013. // return false if not possible
  5014. return FALSE;
  5015. }
  5016. // return success
  5017. return TRUE;
  5018. }
  5019. }
  5020. ?>