util.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <?php
  2. /**
  3. * Class for utility functions
  4. *
  5. */
  6. class OC_Util {
  7. public static $scripts=array();
  8. public static $styles=array();
  9. public static $headers=array();
  10. private static $rootMounted=false;
  11. private static $fsSetup=false;
  12. public static $core_styles=array();
  13. public static $core_scripts=array();
  14. // Can be set up
  15. public static function setupFS( $user = '' ) {// configure the initial filesystem based on the configuration
  16. if(self::$fsSetup) {//setting up the filesystem twice can only lead to trouble
  17. return false;
  18. }
  19. // If we are not forced to load a specific user we load the one that is logged in
  20. if( $user == "" && OC_User::isLoggedIn()) {
  21. $user = OC_User::getUser();
  22. }
  23. // the filesystem will finish when $user is not empty,
  24. // mark fs setup here to avoid doing the setup from loading
  25. // OC_Filesystem
  26. if ($user != '') {
  27. self::$fsSetup=true;
  28. }
  29. $CONFIG_DATADIRECTORY = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" );
  30. //first set up the local "root" storage
  31. if(!self::$rootMounted) {
  32. OC_Filesystem::mount('OC_Filestorage_Local',array('datadir'=>$CONFIG_DATADIRECTORY),'/');
  33. self::$rootMounted=true;
  34. }
  35. if( $user != "" ) { //if we aren't logged in, there is no use to set up the filesystem
  36. $user_dir = '/'.$user.'/files';
  37. $user_root = OC_User::getHome($user);
  38. $userdirectory = $user_root . '/files';
  39. if( !is_dir( $userdirectory )) {
  40. mkdir( $userdirectory, 0755, true );
  41. }
  42. //jail the user into his "home" directory
  43. OC_Filesystem::mount('OC_Filestorage_Local', array('datadir' => $user_root), $user);
  44. OC_Filesystem::init($user_dir);
  45. $quotaProxy=new OC_FileProxy_Quota();
  46. OC_FileProxy::register($quotaProxy);
  47. // Load personal mount config
  48. if (is_file($user_root.'/mount.php')) {
  49. $mountConfig = include($user_root.'/mount.php');
  50. if (isset($mountConfig['user'][$user])) {
  51. foreach ($mountConfig['user'][$user] as $mountPoint => $options) {
  52. OC_Filesystem::mount($options['class'], $options['options'], $mountPoint);
  53. }
  54. }
  55. $mtime=filemtime($user_root.'/mount.php');
  56. $previousMTime=OC_Preferences::getValue($user,'files','mountconfigmtime',0);
  57. if($mtime>$previousMTime) {//mount config has changed, filecache needs to be updated
  58. OC_FileCache::triggerUpdate($user);
  59. OC_Preferences::setValue($user,'files','mountconfigmtime',$mtime);
  60. }
  61. }
  62. OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $user_dir));
  63. }
  64. }
  65. public static function tearDownFS() {
  66. OC_Filesystem::tearDown();
  67. self::$fsSetup=false;
  68. }
  69. /**
  70. * get the current installed version of ownCloud
  71. * @return array
  72. */
  73. public static function getVersion() {
  74. // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.90.0. This is not visible to the user
  75. return array(4,91,00);
  76. }
  77. /**
  78. * get the current installed version string of ownCloud
  79. * @return string
  80. */
  81. public static function getVersionString() {
  82. return '5.0 pre alpha';
  83. }
  84. /**
  85. * get the current installed edition of ownCloud. There is the community edition that just returns an empty string and the enterprise edition that returns "Enterprise".
  86. * @return string
  87. */
  88. public static function getEditionString() {
  89. return '';
  90. }
  91. /**
  92. * add a javascript file
  93. *
  94. * @param appid $application
  95. * @param filename $file
  96. */
  97. public static function addScript( $application, $file = null ) {
  98. if( is_null( $file )) {
  99. $file = $application;
  100. $application = "";
  101. }
  102. if( !empty( $application )) {
  103. self::$scripts[] = "$application/js/$file";
  104. }else{
  105. self::$scripts[] = "js/$file";
  106. }
  107. }
  108. /**
  109. * add a css file
  110. *
  111. * @param appid $application
  112. * @param filename $file
  113. */
  114. public static function addStyle( $application, $file = null ) {
  115. if( is_null( $file )) {
  116. $file = $application;
  117. $application = "";
  118. }
  119. if( !empty( $application )) {
  120. self::$styles[] = "$application/css/$file";
  121. }else{
  122. self::$styles[] = "css/$file";
  123. }
  124. }
  125. /**
  126. * @brief Add a custom element to the header
  127. * @param string tag tag name of the element
  128. * @param array $attributes array of attributes for the element
  129. * @param string $text the text content for the element
  130. */
  131. public static function addHeader( $tag, $attributes, $text='') {
  132. self::$headers[]=array('tag'=>$tag,'attributes'=>$attributes,'text'=>$text);
  133. }
  134. /**
  135. * formats a timestamp in the "right" way
  136. *
  137. * @param int timestamp $timestamp
  138. * @param bool dateOnly option to ommit time from the result
  139. */
  140. public static function formatDate( $timestamp,$dateOnly=false) {
  141. if(isset($_SESSION['timezone'])) {//adjust to clients timezone if we know it
  142. $systemTimeZone = intval(date('O'));
  143. $systemTimeZone=(round($systemTimeZone/100,0)*60)+($systemTimeZone%100);
  144. $clientTimeZone=$_SESSION['timezone']*60;
  145. $offset=$clientTimeZone-$systemTimeZone;
  146. $timestamp=$timestamp+$offset*60;
  147. }
  148. $timeformat=$dateOnly?'F j, Y':'F j, Y, H:i';
  149. return date($timeformat,$timestamp);
  150. }
  151. /**
  152. * Shows a pagenavi widget where you can jump to different pages.
  153. *
  154. * @param int $pagecount
  155. * @param int $page
  156. * @param string $url
  157. * @return OC_Template
  158. */
  159. public static function getPageNavi($pagecount,$page,$url) {
  160. $pagelinkcount=8;
  161. if ($pagecount>1) {
  162. $pagestart=$page-$pagelinkcount;
  163. if($pagestart<0) $pagestart=0;
  164. $pagestop=$page+$pagelinkcount;
  165. if($pagestop>$pagecount) $pagestop=$pagecount;
  166. $tmpl = new OC_Template( '', 'part.pagenavi', '' );
  167. $tmpl->assign('page',$page);
  168. $tmpl->assign('pagecount',$pagecount);
  169. $tmpl->assign('pagestart',$pagestart);
  170. $tmpl->assign('pagestop',$pagestop);
  171. $tmpl->assign('url',$url);
  172. return $tmpl;
  173. }
  174. }
  175. /**
  176. * check if the current server configuration is suitable for ownCloud
  177. * @return array arrays with error messages and hints
  178. */
  179. public static function checkServer() {
  180. $errors=array();
  181. $web_server_restart= false;
  182. //check for database drivers
  183. if(!(is_callable('sqlite_open') or class_exists('SQLite3')) and !is_callable('mysql_connect') and !is_callable('pg_connect')) {
  184. $errors[]=array('error'=>'No database drivers (sqlite, mysql, or postgresql) installed.<br/>','hint'=>'');//TODO: sane hint
  185. $web_server_restart= true;
  186. }
  187. //common hint for all file permissons error messages
  188. $permissionsHint="Permissions can usually be fixed by giving the webserver write access to the ownCloud directory";
  189. // Check if config folder is writable.
  190. if(!is_writable(OC::$SERVERROOT."/config/") or !is_readable(OC::$SERVERROOT."/config/")) {
  191. $errors[]=array('error'=>"Can't write into config directory 'config'",'hint'=>"You can usually fix this by giving the webserver user write access to the config directory in owncloud");
  192. }
  193. // Check if there is a writable install folder.
  194. if(OC_Config::getValue('appstoreenabled', true)) {
  195. if( OC_App::getInstallPath() === null || !is_writable(OC_App::getInstallPath()) || !is_readable(OC_App::getInstallPath()) ) {
  196. $errors[]=array('error'=>"Can't write into apps directory",'hint'=>"You can usually fix this by giving the webserver user write access to the apps directory
  197. in owncloud or disabling the appstore in the config file.");
  198. }
  199. }
  200. $CONFIG_DATADIRECTORY = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" );
  201. //check for correct file permissions
  202. if(!stristr(PHP_OS, 'WIN')) {
  203. $permissionsModHint="Please change the permissions to 0770 so that the directory cannot be listed by other users.";
  204. $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)),-3);
  205. if(substr($prems,-1)!='0') {
  206. OC_Helper::chmodr($CONFIG_DATADIRECTORY,0770);
  207. clearstatcache();
  208. $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)),-3);
  209. if(substr($prems,2,1)!='0') {
  210. $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') is readable for other users<br/>','hint'=>$permissionsModHint);
  211. }
  212. }
  213. if( OC_Config::getValue( "enablebackup", false )) {
  214. $CONFIG_BACKUPDIRECTORY = OC_Config::getValue( "backupdirectory", OC::$SERVERROOT."/backup" );
  215. $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)),-3);
  216. if(substr($prems,-1)!='0') {
  217. OC_Helper::chmodr($CONFIG_BACKUPDIRECTORY,0770);
  218. clearstatcache();
  219. $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)),-3);
  220. if(substr($prems,2,1)!='0') {
  221. $errors[]=array('error'=>'Data directory ('.$CONFIG_BACKUPDIRECTORY.') is readable for other users<br/>','hint'=>$permissionsModHint);
  222. }
  223. }
  224. }
  225. }else{
  226. //TODO: permissions checks for windows hosts
  227. }
  228. // Create root dir.
  229. if(!is_dir($CONFIG_DATADIRECTORY)) {
  230. $success=@mkdir($CONFIG_DATADIRECTORY);
  231. if(!$success) {
  232. $errors[]=array('error'=>"Can't create data directory (".$CONFIG_DATADIRECTORY.")",'hint'=>"You can usually fix this by giving the webserver write access to the ownCloud directory '".OC::$SERVERROOT."' (in a terminal, use the command 'chown -R www-data:www-data /path/to/your/owncloud/install/data' ");
  233. }
  234. } else if(!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
  235. $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') not writable by ownCloud<br/>','hint'=>$permissionsHint);
  236. }
  237. // check if all required php modules are present
  238. if(!class_exists('ZipArchive')) {
  239. $errors[]=array('error'=>'PHP module zip not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  240. $web_server_restart= false;
  241. }
  242. if(!function_exists('mb_detect_encoding')) {
  243. $errors[]=array('error'=>'PHP module mb multibyte not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  244. $web_server_restart= false;
  245. }
  246. if(!function_exists('ctype_digit')) {
  247. $errors[]=array('error'=>'PHP module ctype is not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  248. $web_server_restart= false;
  249. }
  250. if(!function_exists('json_encode')) {
  251. $errors[]=array('error'=>'PHP module JSON is not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  252. $web_server_restart= false;
  253. }
  254. if(!function_exists('imagepng')) {
  255. $errors[]=array('error'=>'PHP module GD is not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  256. $web_server_restart= false;
  257. }
  258. if(!function_exists('gzencode')) {
  259. $errors[]=array('error'=>'PHP module zlib is not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  260. $web_server_restart= false;
  261. }
  262. if(!function_exists('simplexml_load_string')) {
  263. $errors[]=array('error'=>'PHP module SimpleXML is not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  264. $web_server_restart= false;
  265. }
  266. if(floatval(phpversion())<5.3) {
  267. $errors[]=array('error'=>'PHP 5.3 is required.<br/>','hint'=>'Please ask your server administrator to update PHP to version 5.3 or higher. PHP 5.2 is no longer supported by ownCloud and the PHP community.');
  268. $web_server_restart= false;
  269. }
  270. if(!defined('PDO::ATTR_DRIVER_NAME')) {
  271. $errors[]=array('error'=>'PHP PDO module is not installed.<br/>','hint'=>'Please ask your server administrator to install the module.');
  272. $web_server_restart= false;
  273. }
  274. if($web_server_restart) {
  275. $errors[]=array('error'=>'PHP modules have been installed, but they are still listed as missing?<br/>','hint'=>'Please ask your server administrator to restart the web server.');
  276. }
  277. return $errors;
  278. }
  279. public static function displayLoginPage($display_lostpassword) {
  280. $parameters = array();
  281. $parameters['display_lostpassword'] = $display_lostpassword;
  282. if (!empty($_POST['user'])) {
  283. $parameters["username"] =
  284. OC_Util::sanitizeHTML($_POST['user']).'"';
  285. $parameters['user_autofocus'] = false;
  286. } else {
  287. $parameters["username"] = '';
  288. $parameters['user_autofocus'] = true;
  289. }
  290. if (isset($_REQUEST['redirect_url'])) {
  291. $redirect_url = OC_Util::sanitizeHTML($_REQUEST['redirect_url']);
  292. } else {
  293. $redirect_url = $_SERVER['REQUEST_URI'];
  294. }
  295. $parameters['redirect_url'] = $redirect_url;
  296. OC_Template::printGuestPage("", "login", $parameters);
  297. }
  298. /**
  299. * Check if the app is enabled, redirects to home if not
  300. */
  301. public static function checkAppEnabled($app) {
  302. if( !OC_App::isEnabled($app)) {
  303. header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' ));
  304. exit();
  305. }
  306. }
  307. /**
  308. * Check if the user is logged in, redirects to home if not. With
  309. * redirect URL parameter to the request URI.
  310. */
  311. public static function checkLoggedIn() {
  312. // Check if we are a user
  313. if( !OC_User::isLoggedIn()) {
  314. header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php', array('redirect_url' => $_SERVER["REQUEST_URI"])));
  315. exit();
  316. }
  317. }
  318. /**
  319. * Check if the user is a admin, redirects to home if not
  320. */
  321. public static function checkAdminUser() {
  322. // Check if we are a user
  323. self::checkLoggedIn();
  324. if( !OC_Group::inGroup( OC_User::getUser(), 'admin' )) {
  325. header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' ));
  326. exit();
  327. }
  328. }
  329. /**
  330. * Check if the user is a subadmin, redirects to home if not
  331. * @return array $groups where the current user is subadmin
  332. */
  333. public static function checkSubAdminUser() {
  334. // Check if we are a user
  335. self::checkLoggedIn();
  336. if(OC_Group::inGroup(OC_User::getUser(),'admin')) {
  337. return true;
  338. }
  339. if(!OC_SubAdmin::isSubAdmin(OC_User::getUser())) {
  340. header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' ));
  341. exit();
  342. }
  343. return true;
  344. }
  345. /**
  346. * Redirect to the user default page
  347. */
  348. public static function redirectToDefaultPage() {
  349. if(isset($_REQUEST['redirect_url']) && (substr($_REQUEST['redirect_url'], 0, strlen(OC::$WEBROOT)) == OC::$WEBROOT || $_REQUEST['redirect_url'][0] == '/')) {
  350. $location = $_REQUEST['redirect_url'];
  351. }
  352. else if (isset(OC::$REQUESTEDAPP) && !empty(OC::$REQUESTEDAPP)) {
  353. $location = OC_Helper::linkToAbsolute( OC::$REQUESTEDAPP, 'index.php' );
  354. }
  355. else {
  356. $defaultpage = OC_Appconfig::getValue('core', 'defaultpage');
  357. if ($defaultpage) {
  358. $location = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/'.$defaultpage);
  359. }
  360. else {
  361. $location = OC_Helper::linkToAbsolute( 'files', 'index.php' );
  362. }
  363. }
  364. OC_Log::write('core', 'redirectToDefaultPage: '.$location, OC_Log::DEBUG);
  365. header( 'Location: '.$location );
  366. exit();
  367. }
  368. /**
  369. * get an id unqiue for this instance
  370. * @return string
  371. */
  372. public static function getInstanceId() {
  373. $id=OC_Config::getValue('instanceid',null);
  374. if(is_null($id)) {
  375. $id=uniqid();
  376. OC_Config::setValue('instanceid',$id);
  377. }
  378. return $id;
  379. }
  380. /**
  381. * @brief Static lifespan (in seconds) when a request token expires.
  382. * @see OC_Util::callRegister()
  383. * @see OC_Util::isCallRegistered()
  384. * @description
  385. * Also required for the client side to compute the piont in time when to
  386. * request a fresh token. The client will do so when nearly 97% of the
  387. * timespan coded here has expired.
  388. */
  389. public static $callLifespan = 3600; // 3600 secs = 1 hour
  390. /**
  391. * @brief Register an get/post call. Important to prevent CSRF attacks.
  392. * @todo Write howto: CSRF protection guide
  393. * @return $token Generated token.
  394. * @description
  395. * Creates a 'request token' (random) and stores it inside the session.
  396. * Ever subsequent (ajax) request must use such a valid token to succeed,
  397. * otherwise the request will be denied as a protection against CSRF.
  398. * The tokens expire after a fixed lifespan.
  399. * @see OC_Util::$callLifespan
  400. * @see OC_Util::isCallRegistered()
  401. */
  402. public static function callRegister() {
  403. // generate a random token.
  404. $token = self::generate_random_bytes(20);
  405. // store the token together with a timestamp in the session.
  406. $_SESSION['requesttoken-'.$token]=time();
  407. // cleanup old tokens garbage collector
  408. // only run every 20th time so we don't waste cpu cycles
  409. if(rand(0,20)==0) {
  410. foreach($_SESSION as $key=>$value) {
  411. // search all tokens in the session
  412. if(substr($key,0,12)=='requesttoken') {
  413. // check if static lifespan has expired
  414. if($value+self::$callLifespan<time()) {
  415. // remove outdated tokens
  416. unset($_SESSION[$key]);
  417. }
  418. }
  419. }
  420. }
  421. // return the token
  422. return($token);
  423. }
  424. /**
  425. * @brief Check an ajax get/post call if the request token is valid.
  426. * @return boolean False if request token is not set or is invalid.
  427. * @see OC_Util::$callLifespan
  428. * @see OC_Util::calLRegister()
  429. */
  430. public static function isCallRegistered() {
  431. if(isset($_GET['requesttoken'])) {
  432. $token=$_GET['requesttoken'];
  433. }elseif(isset($_POST['requesttoken'])) {
  434. $token=$_POST['requesttoken'];
  435. }elseif(isset($_SERVER['HTTP_REQUESTTOKEN'])) {
  436. $token=$_SERVER['HTTP_REQUESTTOKEN'];
  437. }else{
  438. //no token found.
  439. return false;
  440. }
  441. if(isset($_SESSION['requesttoken-'.$token])) {
  442. $timestamp=$_SESSION['requesttoken-'.$token];
  443. // check if static lifespan has expired
  444. if($timestamp+self::$callLifespan<time()) {
  445. return false;
  446. }else{
  447. //token valid
  448. return true;
  449. }
  450. }else{
  451. return false;
  452. }
  453. }
  454. /**
  455. * @brief Check an ajax get/post call if the request token is valid. exit if not.
  456. * Todo: Write howto
  457. */
  458. public static function callCheck() {
  459. if(!OC_Util::isCallRegistered()) {
  460. exit;
  461. }
  462. }
  463. /**
  464. * @brief Public function to sanitize HTML
  465. *
  466. * This function is used to sanitize HTML and should be applied on any
  467. * string or array of strings before displaying it on a web page.
  468. *
  469. * @param string or array of strings
  470. * @return array with sanitized strings or a single sanitized string, depends on the input parameter.
  471. */
  472. public static function sanitizeHTML( &$value ) {
  473. if (is_array($value) || is_object($value)) array_walk_recursive($value,'OC_Util::sanitizeHTML');
  474. else $value = htmlentities($value, ENT_QUOTES, 'UTF-8'); //Specify encoding for PHP<5.4
  475. return $value;
  476. }
  477. /**
  478. * Check if the htaccess file is working by creating a test file in the data directory and trying to access via http
  479. */
  480. public static function ishtaccessworking() {
  481. // testdata
  482. $filename='/htaccesstest.txt';
  483. $testcontent='testcontent';
  484. // creating a test file
  485. $testfile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$filename;
  486. $fp = @fopen($testfile, 'w');
  487. @fwrite($fp, $testcontent);
  488. @fclose($fp);
  489. // accessing the file via http
  490. $url = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/data'.$filename);
  491. $fp = @fopen($url, 'r');
  492. $content=@fread($fp, 2048);
  493. @fclose($fp);
  494. // cleanup
  495. @unlink($testfile);
  496. // does it work ?
  497. if($content==$testcontent) {
  498. return(false);
  499. }else{
  500. return(true);
  501. }
  502. }
  503. /*
  504. * @brief Generates random bytes with "openssl_random_pseudo_bytes" with a fallback for systems without openssl
  505. * Inspired by gorgo on php.net
  506. * @param Int with the length of the random
  507. * @return String with the random bytes
  508. */
  509. public static function generate_random_bytes($length = 30) {
  510. if(function_exists('openssl_random_pseudo_bytes')) {
  511. $pseudo_byte = bin2hex(openssl_random_pseudo_bytes($length, $strong));
  512. if($strong == TRUE) {
  513. return substr($pseudo_byte, 0, $length); // Truncate it to match the length
  514. }
  515. }
  516. // fallback to mt_rand()
  517. $characters = '0123456789';
  518. $characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  519. $charactersLength = strlen($characters)-1;
  520. $pseudo_byte = "";
  521. // Select some random characters
  522. for ($i = 0; $i < $length; $i++) {
  523. $pseudo_byte .= $characters[mt_rand(0, $charactersLength)];
  524. }
  525. return $pseudo_byte;
  526. }
  527. }