PageRenderTime 87ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/administrator/components/com_joomlaupdate/restore.php

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