installer.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Robin Appelman
  6. * @copyright 2012 Frank Karlitschek frank@owncloud.org
  7. *
  8. * @author Georg Ehrke
  9. * @copytight 2014 Georg Ehrke georg@ownCloud.com
  10. *
  11. * This library is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  13. * License as published by the Free Software Foundation; either
  14. * version 3 of the License, or any later version.
  15. *
  16. * This library is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public
  22. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  23. *
  24. */
  25. /**
  26. * This class provides the functionality needed to install, update and remove plugins/apps
  27. */
  28. class OC_Installer{
  29. /**
  30. *
  31. * This function installs an app. All information needed are passed in the
  32. * associative array $data.
  33. * The following keys are required:
  34. * - source: string, can be "path" or "http"
  35. *
  36. * One of the following keys is required:
  37. * - path: path to the file containing the app
  38. * - href: link to the downloadable file containing the app
  39. *
  40. * The following keys are optional:
  41. * - pretend: boolean, if set true the system won't do anything
  42. * - noinstall: boolean, if true appinfo/install.php won't be loaded
  43. * - inactive: boolean, if set true the appconfig/app.sample.php won't be
  44. * renamed
  45. *
  46. * This function works as follows
  47. * -# fetching the file
  48. * -# unzipping it
  49. * -# check the code
  50. * -# installing the database at appinfo/database.xml
  51. * -# including appinfo/install.php
  52. * -# setting the installed version
  53. *
  54. * It is the task of oc_app_install to create the tables and do whatever is
  55. * needed to get the app working.
  56. *
  57. * Installs an app
  58. * @param array $data with all information
  59. * @throws \Exception
  60. * @return integer
  61. */
  62. public static function installApp( $data = array()) {
  63. $l = \OC_L10N::get('lib');
  64. list($extractDir, $path) = self::downloadApp($data);
  65. $info = self::checkAppsIntegrity($data, $extractDir, $path);
  66. $basedir=OC_App::getInstallPath().'/'.$info['id'];
  67. //check if the destination directory already exists
  68. if(is_dir($basedir)) {
  69. OC_Helper::rmdirr($extractDir);
  70. if($data['source']=='http') {
  71. unlink($path);
  72. }
  73. throw new \Exception($l->t("App directory already exists"));
  74. }
  75. if(!empty($data['pretent'])) {
  76. return false;
  77. }
  78. //copy the app to the correct place
  79. if(@!mkdir($basedir)) {
  80. OC_Helper::rmdirr($extractDir);
  81. if($data['source']=='http') {
  82. unlink($path);
  83. }
  84. throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir)));
  85. }
  86. $extractDir .= '/' . $info['id'];
  87. OC_Helper::copyr($extractDir, $basedir);
  88. //remove temporary files
  89. OC_Helper::rmdirr($extractDir);
  90. //install the database
  91. if(is_file($basedir.'/appinfo/database.xml')) {
  92. if (OC_Appconfig::getValue($info['id'], 'installed_version') === null) {
  93. OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
  94. } else {
  95. OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
  96. }
  97. }
  98. //run appinfo/install.php
  99. if((!isset($data['noinstall']) or $data['noinstall']==false) and file_exists($basedir.'/appinfo/install.php')) {
  100. include $basedir.'/appinfo/install.php';
  101. }
  102. //set the installed version
  103. OC_Appconfig::setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id']));
  104. OC_Appconfig::setValue($info['id'], 'enabled', 'no');
  105. //set remote/public handelers
  106. foreach($info['remote'] as $name=>$path) {
  107. OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
  108. }
  109. foreach($info['public'] as $name=>$path) {
  110. OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
  111. }
  112. OC_App::setAppTypes($info['id']);
  113. return $info['id'];
  114. }
  115. /**
  116. * @brief checks whether or not an app is installed
  117. * @param string $app app
  118. * @returns bool
  119. *
  120. * Checks whether or not an app is installed, i.e. registered in apps table.
  121. */
  122. public static function isInstalled( $app ) {
  123. return (OC_Appconfig::getValue($app, "installed_version") !== null);
  124. }
  125. /**
  126. * @brief Update an application
  127. * @param array $info
  128. * @param bool $isShipped
  129. *
  130. * This function could work like described below, but currently it disables and then
  131. * enables the app again. This does result in an updated app.
  132. *
  133. *
  134. * This function installs an app. All information needed are passed in the
  135. * associative array $info.
  136. * The following keys are required:
  137. * - source: string, can be "path" or "http"
  138. *
  139. * One of the following keys is required:
  140. * - path: path to the file containing the app
  141. * - href: link to the downloadable file containing the app
  142. *
  143. * The following keys are optional:
  144. * - pretend: boolean, if set true the system won't do anything
  145. * - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded
  146. *
  147. * This function works as follows
  148. * -# fetching the file
  149. * -# removing the old files
  150. * -# unzipping new file
  151. * -# including appinfo/upgrade.php
  152. * -# setting the installed version
  153. *
  154. * upgrade.php can determine the current installed version of the app using
  155. * "OC_Appconfig::getValue($appid, 'installed_version')"
  156. */
  157. public static function updateApp( $info=array(), $isShipped=false) {
  158. list($extractDir, $path) = self::downloadApp($info);
  159. $info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped);
  160. $currentDir = OC_App::getAppPath($info['id']);
  161. $basedir = OC_App::getInstallPath();
  162. $basedir .= '/';
  163. $basedir .= $info['id'];
  164. if($currentDir !== false && is_writable($currentDir)) {
  165. $basedir = $currentDir;
  166. }
  167. if(is_dir($basedir)) {
  168. OC_Helper::rmdirr($basedir);
  169. }
  170. $appInExtractDir = $extractDir;
  171. if (substr($extractDir, -1) !== '/') {
  172. $appInExtractDir .= '/';
  173. }
  174. $appInExtractDir .= $info['id'];
  175. OC_Helper::copyr($appInExtractDir, $basedir);
  176. OC_Helper::rmdirr($extractDir);
  177. return OC_App::updateApp($info['id']);
  178. }
  179. /**
  180. * update an app by it's id
  181. * @param integer $ocsid
  182. * @param bool $isShipped
  183. * @return bool
  184. * @throws Exception
  185. */
  186. public static function updateAppByOCSId($ocsid, $isShipped=false) {
  187. $appdata = OC_OCSClient::getApplication($ocsid);
  188. $download = OC_OCSClient::getApplicationDownload($ocsid, 1);
  189. if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') {
  190. $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
  191. $info = array(
  192. 'source' => 'http',
  193. 'href' => $download['downloadlink'],
  194. 'appdata' => $appdata
  195. );
  196. } else {
  197. throw new \Exception('Could not fetch app info!');
  198. }
  199. return self::updateApp($info);
  200. }
  201. /**
  202. * @param array $data
  203. * @return array
  204. * @throws Exception
  205. */
  206. public static function downloadApp($data = array()) {
  207. $l = \OC_L10N::get('lib');
  208. if(!isset($data['source'])) {
  209. throw new \Exception($l->t("No source specified when installing app"));
  210. }
  211. //download the file if necessary
  212. if($data['source']=='http') {
  213. $pathInfo = pathinfo($data['href']);
  214. $path=OC_Helper::tmpFile('.' . $pathInfo['extension']);
  215. if(!isset($data['href'])) {
  216. throw new \Exception($l->t("No href specified when installing app from http"));
  217. }
  218. copy($data['href'], $path);
  219. }else{
  220. if(!isset($data['path'])) {
  221. throw new \Exception($l->t("No path specified when installing app from local file"));
  222. }
  223. $path=$data['path'];
  224. }
  225. //detect the archive type
  226. $mime=OC_Helper::getMimeType($path);
  227. if ($mime !=='application/zip' && $mime !== 'application/x-gzip') {
  228. throw new \Exception($l->t("Archives of type %s are not supported", array($mime)));
  229. }
  230. //extract the archive in a temporary folder
  231. $extractDir=OC_Helper::tmpFolder();
  232. OC_Helper::rmdirr($extractDir);
  233. mkdir($extractDir);
  234. if($archive=OC_Archive::open($path)) {
  235. $archive->extract($extractDir);
  236. } else {
  237. OC_Helper::rmdirr($extractDir);
  238. if($data['source']=='http') {
  239. unlink($path);
  240. }
  241. throw new \Exception($l->t("Failed to open archive when installing app"));
  242. }
  243. return array(
  244. $extractDir,
  245. $path
  246. );
  247. }
  248. /**
  249. * check an app's integrity
  250. * @param array $data
  251. * @param string $extractDir
  252. * @param bool $isShipped
  253. * @return array
  254. * @throws \Exception
  255. */
  256. public static function checkAppsIntegrity($data = array(), $extractDir, $path, $isShipped=false) {
  257. $l = \OC_L10N::get('lib');
  258. //load the info.xml file of the app
  259. if(!is_file($extractDir.'/appinfo/info.xml')) {
  260. //try to find it in a subdir
  261. $dh=opendir($extractDir);
  262. if(is_resource($dh)) {
  263. while (($folder = readdir($dh)) !== false) {
  264. if($folder[0]!='.' and is_dir($extractDir.'/'.$folder)) {
  265. if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')) {
  266. $extractDir.='/'.$folder;
  267. }
  268. }
  269. }
  270. }
  271. }
  272. if(!is_file($extractDir.'/appinfo/info.xml')) {
  273. OC_Helper::rmdirr($extractDir);
  274. if($data['source']=='http') {
  275. unlink($path);
  276. }
  277. throw new \Exception($l->t("App does not provide an info.xml file"));
  278. }
  279. $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true);
  280. // check the code for not allowed calls
  281. if(!$isShipped && !OC_Installer::checkCode($info['id'], $extractDir)) {
  282. OC_Helper::rmdirr($extractDir);
  283. throw new \Exception($l->t("App can't be installed because of not allowed code in the App"));
  284. }
  285. // check if the app is compatible with this version of ownCloud
  286. if(!OC_App::isAppCompatible(OC_Util::getVersion(), $info)) {
  287. OC_Helper::rmdirr($extractDir);
  288. throw new \Exception($l->t("App can't be installed because it is not compatible with this version of ownCloud"));
  289. }
  290. // check if shipped tag is set which is only allowed for apps that are shipped with ownCloud
  291. if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) {
  292. OC_Helper::rmdirr($extractDir);
  293. throw new \Exception($l->t("App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps"));
  294. }
  295. // check if the ocs version is the same as the version in info.xml/version
  296. $versionFile= $extractDir.'/appinfo/version';
  297. if(is_file($versionFile)) {
  298. $version = trim(file_get_contents($versionFile));
  299. }else{
  300. $version = trim($info['version']);
  301. }
  302. if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) {
  303. OC_Helper::rmdirr($extractDir);
  304. throw new \Exception($l->t("App can't be installed because the version in info.xml/version is not the same as the version reported from the app store"));
  305. }
  306. return $info;
  307. }
  308. /**
  309. * Check if an update for the app is available
  310. * @param string $app
  311. * @return string|false false or the version number of the update
  312. *
  313. * The function will check if an update for a version is available
  314. */
  315. public static function isUpdateAvailable( $app ) {
  316. static $isInstanceReadyForUpdates = null;
  317. if ($isInstanceReadyForUpdates === null) {
  318. $installPath = OC_App::getInstallPath();
  319. if ($installPath === false || $installPath === null) {
  320. $isInstanceReadyForUpdates = false;
  321. } else {
  322. $isInstanceReadyForUpdates = true;
  323. }
  324. }
  325. if ($isInstanceReadyForUpdates === false) {
  326. return false;
  327. }
  328. $ocsid=OC_Appconfig::getValue( $app, 'ocsid', '');
  329. if($ocsid<>'') {
  330. $ocsdata=OC_OCSClient::getApplication($ocsid);
  331. $ocsversion= (string) $ocsdata['version'];
  332. $currentversion=OC_App::getAppVersion($app);
  333. if (version_compare($ocsversion, $currentversion, '>')) {
  334. return($ocsversion);
  335. }else{
  336. return false;
  337. }
  338. }else{
  339. return false;
  340. }
  341. }
  342. /**
  343. * Check if app is already downloaded
  344. * @param string $name name of the application to remove
  345. * @return boolean
  346. *
  347. * The function will check if the app is already downloaded in the apps repository
  348. */
  349. public static function isDownloaded( $name ) {
  350. foreach(OC::$APPSROOTS as $dir) {
  351. $dirToTest = $dir['path'];
  352. $dirToTest .= '/';
  353. $dirToTest .= $name;
  354. $dirToTest .= '/';
  355. if (is_dir($dirToTest)) {
  356. return true;
  357. }
  358. }
  359. return false;
  360. }
  361. /**
  362. * Removes an app
  363. * @param string $name name of the application to remove
  364. * @param array $options options
  365. * @return boolean
  366. *
  367. * This function removes an app. $options is an associative array. The
  368. * following keys are optional:ja
  369. * - keeppreferences: boolean, if true the user preferences won't be deleted
  370. * - keepappconfig: boolean, if true the config will be kept
  371. * - keeptables: boolean, if true the database will be kept
  372. * - keepfiles: boolean, if true the user files will be kept
  373. *
  374. * This function works as follows
  375. * -# including appinfo/remove.php
  376. * -# removing the files
  377. *
  378. * The function will not delete preferences, tables and the configuration,
  379. * this has to be done by the function oc_app_uninstall().
  380. */
  381. public static function removeApp( $name, $options = array()) {
  382. if(isset($options['keeppreferences']) and $options['keeppreferences']==false ) {
  383. // todo
  384. // remove preferences
  385. }
  386. if(isset($options['keepappconfig']) and $options['keepappconfig']==false ) {
  387. // todo
  388. // remove app config
  389. }
  390. if(isset($options['keeptables']) and $options['keeptables']==false ) {
  391. // todo
  392. // remove app database tables
  393. }
  394. if(isset($options['keepfiles']) and $options['keepfiles']==false ) {
  395. // todo
  396. // remove user files
  397. }
  398. if(OC_Installer::isDownloaded( $name )) {
  399. $appdir=OC_App::getInstallPath().'/'.$name;
  400. OC_Helper::rmdirr($appdir);
  401. return true;
  402. }else{
  403. OC_Log::write('core', 'can\'t remove app '.$name.'. It is not installed.', OC_Log::ERROR);
  404. return false;
  405. }
  406. }
  407. /**
  408. * Installs shipped apps
  409. *
  410. * This function installs all apps found in the 'apps' directory that should be enabled by default;
  411. */
  412. public static function installShippedApps() {
  413. foreach(OC::$APPSROOTS as $app_dir) {
  414. if($dir = opendir( $app_dir['path'] )) {
  415. while( false !== ( $filename = readdir( $dir ))) {
  416. if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) {
  417. if( file_exists( $app_dir['path']."/$filename/appinfo/app.php" )) {
  418. if(!OC_Installer::isInstalled($filename)) {
  419. $info=OC_App::getAppInfo($filename);
  420. $enabled = isset($info['default_enable']);
  421. if( $enabled ) {
  422. OC_Installer::installShippedApp($filename);
  423. OC_Appconfig::setValue($filename, 'enabled', 'yes');
  424. }
  425. }
  426. }
  427. }
  428. }
  429. closedir( $dir );
  430. }
  431. }
  432. }
  433. /**
  434. * install an app already placed in the app folder
  435. * @param string $app id of the app to install
  436. * @return integer
  437. */
  438. public static function installShippedApp($app) {
  439. //install the database
  440. if(is_file(OC_App::getAppPath($app)."/appinfo/database.xml")) {
  441. OC_DB::createDbFromStructure(OC_App::getAppPath($app)."/appinfo/database.xml");
  442. }
  443. //run appinfo/install.php
  444. if(is_file(OC_App::getAppPath($app)."/appinfo/install.php")) {
  445. include OC_App::getAppPath($app)."/appinfo/install.php";
  446. }
  447. $info=OC_App::getAppInfo($app);
  448. if (is_null($info)) {
  449. return false;
  450. }
  451. OC_Appconfig::setValue($app, 'installed_version', OC_App::getAppVersion($app));
  452. if (array_key_exists('ocsid', $info)) {
  453. OC_Appconfig::setValue($app, 'ocsid', $info['ocsid']);
  454. }
  455. //set remote/public handelers
  456. foreach($info['remote'] as $name=>$path) {
  457. OCP\CONFIG::setAppValue('core', 'remote_'.$name, $app.'/'.$path);
  458. }
  459. foreach($info['public'] as $name=>$path) {
  460. OCP\CONFIG::setAppValue('core', 'public_'.$name, $app.'/'.$path);
  461. }
  462. OC_App::setAppTypes($info['id']);
  463. return $info['id'];
  464. }
  465. /**
  466. * check the code of an app with some static code checks
  467. * @param string $folder the folder of the app to check
  468. * @return boolean true for app is o.k. and false for app is not o.k.
  469. */
  470. public static function checkCode($appname, $folder) {
  471. $blacklist=array(
  472. 'exec(',
  473. 'eval(',
  474. // more evil pattern will go here later
  475. // classes replaced by the public api
  476. 'OC_API::',
  477. 'OC_App::',
  478. 'OC_AppConfig::',
  479. 'OC_Avatar',
  480. 'OC_BackgroundJob::',
  481. 'OC_Config::',
  482. 'OC_DB::',
  483. 'OC_Files::',
  484. 'OC_Helper::',
  485. 'OC_Hook::',
  486. 'OC_Image::',
  487. 'OC_JSON::',
  488. 'OC_L10N::',
  489. 'OC_Log::',
  490. 'OC_Mail::',
  491. 'OC_Preferences::',
  492. 'OC_Request::',
  493. 'OC_Response::',
  494. 'OC_Template::',
  495. 'OC_User::',
  496. 'OC_Util::',
  497. );
  498. // is the code checker enabled?
  499. if(OC_Config::getValue('appcodechecker', true)) {
  500. // check if grep is installed
  501. $grep = exec('command -v grep');
  502. if($grep=='') {
  503. OC_Log::write('core',
  504. 'grep not installed. So checking the code of the app "'.$appname.'" was not possible',
  505. OC_Log::ERROR);
  506. return true;
  507. }
  508. // iterate the bad patterns
  509. foreach($blacklist as $bl) {
  510. $cmd = 'grep -ri '.escapeshellarg($bl).' '.$folder.'';
  511. $result = exec($cmd);
  512. // bad pattern found
  513. if($result<>'') {
  514. OC_Log::write('core',
  515. 'App "'.$appname.'" is using a not allowed call "'.$bl.'". Installation refused.',
  516. OC_Log::ERROR);
  517. return false;
  518. }
  519. }
  520. return true;
  521. }else{
  522. return true;
  523. }
  524. }
  525. }