From 9c5416fe4a12acf5631b8822feb942143bf2408f Mon Sep 17 00:00:00 2001 From: kondou Date: Thu, 15 Aug 2013 08:49:19 +0200 Subject: [PATCH 001/198] Clean up \OC\Util - Use camelCase - Add some phpdoc - Fix some indents - Use some more spacing --- core/lostpassword/controller.php | 2 +- core/minimizer.php | 4 +- core/setup.php | 4 +- lib/app.php | 8 +- lib/base.php | 8 +- lib/public/share.php | 2 +- lib/setup.php | 2 +- lib/setup/mysql.php | 2 +- lib/setup/oci.php | 2 +- lib/setup/postgresql.php | 2 +- lib/templatelayout.php | 4 +- lib/user.php | 2 +- lib/util.php | 458 ++++++++++++++++++------------- settings/admin.php | 4 +- tests/lib/db.php | 2 +- tests/lib/dbschema.php | 2 +- tests/lib/util.php | 4 +- 17 files changed, 290 insertions(+), 222 deletions(-) diff --git a/core/lostpassword/controller.php b/core/lostpassword/controller.php index 74a5be2b96..f761e45d25 100644 --- a/core/lostpassword/controller.php +++ b/core/lostpassword/controller.php @@ -42,7 +42,7 @@ class OC_Core_LostPassword_Controller { } if (OC_User::userExists($_POST['user']) && $continue) { - $token = hash('sha256', OC_Util::generate_random_bytes(30).OC_Config::getValue('passwordsalt', '')); + $token = hash('sha256', OC_Util::generateRandomBytes(30).OC_Config::getValue('passwordsalt', '')); OC_Preferences::setValue($_POST['user'], 'owncloud', 'lostpassword', hash('sha256', $token)); // Hash the token again to prevent timing attacks $email = OC_Preferences::getValue($_POST['user'], 'settings', 'email', ''); diff --git a/core/minimizer.php b/core/minimizer.php index 4da9037c41..eeeddf86a8 100644 --- a/core/minimizer.php +++ b/core/minimizer.php @@ -5,11 +5,11 @@ OC_App::loadApps(); if ($service == 'core.css') { $minimizer = new OC_Minimizer_CSS(); - $files = OC_TemplateLayout::findStylesheetFiles(OC_Util::$core_styles); + $files = OC_TemplateLayout::findStylesheetFiles(OC_Util::$coreStyles); $minimizer->output($files, $service); } else if ($service == 'core.js') { $minimizer = new OC_Minimizer_JS(); - $files = OC_TemplateLayout::findJavascriptFiles(OC_Util::$core_scripts); + $files = OC_TemplateLayout::findJavascriptFiles(OC_Util::$coreScripts); $minimizer->output($files, $service); } diff --git a/core/setup.php b/core/setup.php index 40e30db533..1a2eac1603 100644 --- a/core/setup.php +++ b/core/setup.php @@ -33,8 +33,8 @@ $opts = array( 'hasOracle' => $hasOracle, 'hasMSSQL' => $hasMSSQL, 'directory' => $datadir, - 'secureRNG' => OC_Util::secureRNG_available(), - 'htaccessWorking' => OC_Util::ishtaccessworking(), + 'secureRNG' => OC_Util::secureRNGAvailable(), + 'htaccessWorking' => OC_Util::isHtaccessWorking(), 'vulnerableToNullByte' => $vulnerableToNullByte, 'errors' => array(), ); diff --git a/lib/app.php b/lib/app.php index 5fa650044f..2f5a952d9f 100644 --- a/lib/app.php +++ b/lib/app.php @@ -73,11 +73,11 @@ class OC_App{ if (!defined('DEBUG') || !DEBUG) { if (is_null($types) - && empty(OC_Util::$core_scripts) - && empty(OC_Util::$core_styles)) { - OC_Util::$core_scripts = OC_Util::$scripts; + && empty(OC_Util::$coreScripts) + && empty(OC_Util::$coreStyles)) { + OC_Util::$coreScripts = OC_Util::$scripts; OC_Util::$scripts = array(); - OC_Util::$core_styles = OC_Util::$styles; + OC_Util::$coreStyles = OC_Util::$styles; OC_Util::$styles = array(); } } diff --git a/lib/base.php b/lib/base.php index eaee842465..7a4f5fc7ce 100644 --- a/lib/base.php +++ b/lib/base.php @@ -413,7 +413,7 @@ class OC { } self::initPaths(); - OC_Util::issetlocaleworking(); + OC_Util::isSetlocaleWorking(); // set debug mode if an xdebug session is active if (!defined('DEBUG') || !DEBUG) { @@ -522,7 +522,7 @@ class OC { } // write error into log if locale can't be set - if (OC_Util::issetlocaleworking() == false) { + if (OC_Util::isSetlocaleWorking() == false) { OC_Log::write('core', 'setting locale to en_US.UTF-8/en_US.UTF8 failed. Support is probably not installed on your system', OC_Log::ERROR); @@ -735,7 +735,7 @@ class OC { if (in_array($_COOKIE['oc_token'], $tokens, true)) { // replace successfully used token with a new one OC_Preferences::deleteKey($_COOKIE['oc_username'], 'login_token', $_COOKIE['oc_token']); - $token = OC_Util::generate_random_bytes(32); + $token = OC_Util::generateRandomBytes(32); OC_Preferences::setValue($_COOKIE['oc_username'], 'login_token', $token, time()); OC_User::setMagicInCookie($_COOKIE['oc_username'], $token); // login @@ -774,7 +774,7 @@ class OC { if (defined("DEBUG") && DEBUG) { OC_Log::write('core', 'Setting remember login to cookie', OC_Log::DEBUG); } - $token = OC_Util::generate_random_bytes(32); + $token = OC_Util::generateRandomBytes(32); OC_Preferences::setValue($_POST['user'], 'login_token', $token, time()); OC_User::setMagicInCookie($_POST["user"], $token); } else { diff --git a/lib/public/share.php b/lib/public/share.php index 63645e6fa3..7714837769 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -463,7 +463,7 @@ class Share { if (isset($oldToken)) { $token = $oldToken; } else { - $token = \OC_Util::generate_random_bytes(self::TOKEN_LENGTH); + $token = \OC_Util::generateRandomBytes(self::TOKEN_LENGTH); } $result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token); diff --git a/lib/setup.php b/lib/setup.php index 05a4989097..6bf3c88370 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -61,7 +61,7 @@ class OC_Setup { } //generate a random salt that is used to salt the local user passwords - $salt = OC_Util::generate_random_bytes(30); + $salt = OC_Util::generateRandomBytes(30); OC_Config::setValue('passwordsalt', $salt); //write the config file diff --git a/lib/setup/mysql.php b/lib/setup/mysql.php index 0cf04fde5a..d97b6d2602 100644 --- a/lib/setup/mysql.php +++ b/lib/setup/mysql.php @@ -23,7 +23,7 @@ class MySQL extends AbstractDatabase { $this->dbuser=substr('oc_'.$username, 0, 16); if($this->dbuser!=$oldUser) { //hash the password so we don't need to store the admin config in the config file - $this->dbpassword=\OC_Util::generate_random_bytes(30); + $this->dbpassword=\OC_Util::generateRandomBytes(30); $this->createDBUser($connection); diff --git a/lib/setup/oci.php b/lib/setup/oci.php index 86b53de45a..326d7a0053 100644 --- a/lib/setup/oci.php +++ b/lib/setup/oci.php @@ -65,7 +65,7 @@ class OCI extends AbstractDatabase { //add prefix to the oracle user name to prevent collisions $this->dbuser='oc_'.$username; //create a new password so we don't need to store the admin config in the config file - $this->dbpassword=\OC_Util::generate_random_bytes(30); + $this->dbpassword=\OC_Util::generateRandomBytes(30); //oracle passwords are treated as identifiers: // must start with aphanumeric char diff --git a/lib/setup/postgresql.php b/lib/setup/postgresql.php index 49fcbf0326..89d328ada1 100644 --- a/lib/setup/postgresql.php +++ b/lib/setup/postgresql.php @@ -33,7 +33,7 @@ class PostgreSQL extends AbstractDatabase { //add prefix to the postgresql user name to prevent collisions $this->dbuser='oc_'.$username; //create a new password so we don't need to store the admin config in the config file - $this->dbpassword=\OC_Util::generate_random_bytes(30); + $this->dbpassword=\OC_Util::generateRandomBytes(30); $this->createDBUser($connection); diff --git a/lib/templatelayout.php b/lib/templatelayout.php index 0024c9d496..0b868a39e4 100644 --- a/lib/templatelayout.php +++ b/lib/templatelayout.php @@ -58,7 +58,7 @@ class OC_TemplateLayout extends OC_Template { if (OC_Config::getValue('installed', false) && $renderas!='error') { $this->append( 'jsfiles', OC_Helper::linkToRoute('js_config') . $versionParameter); } - if (!empty(OC_Util::$core_scripts)) { + if (!empty(OC_Util::$coreScripts)) { $this->append( 'jsfiles', OC_Helper::linkToRemoteBase('core.js', false) . $versionParameter); } foreach($jsfiles as $info) { @@ -71,7 +71,7 @@ class OC_TemplateLayout extends OC_Template { // Add the css files $cssfiles = self::findStylesheetFiles(OC_Util::$styles); $this->assign('cssfiles', array()); - if (!empty(OC_Util::$core_styles)) { + if (!empty(OC_Util::$coreStyles)) { $this->append( 'cssfiles', OC_Helper::linkToRemoteBase('core.css', false) . $versionParameter); } foreach($cssfiles as $info) { diff --git a/lib/user.php b/lib/user.php index 93c7c9d4cd..0f6f40aec9 100644 --- a/lib/user.php +++ b/lib/user.php @@ -353,7 +353,7 @@ class OC_User { * generates a password */ public static function generatePassword() { - return OC_Util::generate_random_bytes(30); + return OC_Util::generateRandomBytes(30); } /** diff --git a/lib/util.php b/lib/util.php index 25632ac1ea..24ae7d3d1c 100755 --- a/lib/util.php +++ b/lib/util.php @@ -11,12 +11,16 @@ class OC_Util { public static $headers=array(); private static $rootMounted=false; private static $fsSetup=false; - public static $core_styles=array(); - public static $core_scripts=array(); + public static $coreStyles=array(); + public static $coreScripts=array(); - // Can be set up - public static function setupFS( $user = '' ) {// configure the initial filesystem based on the configuration - if(self::$fsSetup) {//setting up the filesystem twice can only lead to trouble + /** + * @brief Can be set up + * @param user string + * @return boolean + */ + public static function setupFS( $user = '' ) { // configure the initial filesystem based on the configuration + if(self::$fsSetup) { //setting up the filesystem twice can only lead to trouble return false; } @@ -37,42 +41,45 @@ class OC_Util { self::$fsSetup=true; } - $CONFIG_DATADIRECTORY = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + $configDataDirectory = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); //first set up the local "root" storage \OC\Files\Filesystem::initMounts(); if(!self::$rootMounted) { - \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir'=>$CONFIG_DATADIRECTORY), '/'); - self::$rootMounted=true; + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir'=>$configDataDirectory), '/'); + self::$rootMounted = true; } if( $user != "" ) { //if we aren't logged in, there is no use to set up the filesystem - $user_dir = '/'.$user.'/files'; - $user_root = OC_User::getHome($user); - $userdirectory = $user_root . '/files'; - if( !is_dir( $userdirectory )) { - mkdir( $userdirectory, 0755, true ); + $userDir = '/'.$user.'/files'; + $userRoot = OC_User::getHome($user); + $userDirectory = $userRoot . '/files'; + if( !is_dir( $userDirectory )) { + mkdir( $userDirectory, 0755, true ); } //jail the user into his "home" directory - \OC\Files\Filesystem::init($user, $user_dir); + \OC\Files\Filesystem::init($user, $userDir); - $quotaProxy=new OC_FileProxy_Quota(); + $quotaProxy = new OC_FileProxy_Quota(); $fileOperationProxy = new OC_FileProxy_FileOperations(); OC_FileProxy::register($quotaProxy); OC_FileProxy::register($fileOperationProxy); - OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $user_dir)); + OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $userDir)); } return true; } + /** + * @return void + */ public static function tearDownFS() { \OC\Files\Filesystem::tearDown(); self::$fsSetup=false; - self::$rootMounted=false; + self::$rootMounted=false; } /** - * get the current installed version of ownCloud + * @brief get the current installed version of ownCloud * @return array */ public static function getVersion() { @@ -82,7 +89,7 @@ class OC_Util { } /** - * get the current installed version string of ownCloud + * @brief get the current installed version string of ownCloud * @return string */ public static function getVersionString() { @@ -90,7 +97,7 @@ class OC_Util { } /** - * get the current installed edition of ownCloud. There is the community + * @description 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". * @return string @@ -100,37 +107,39 @@ class OC_Util { } /** - * add a javascript file + * @brief add a javascript file * - * @param appid $application - * @param filename $file + * @param appid $application + * @param filename $file + * @return void */ public static function addScript( $application, $file = null ) { - if( is_null( $file )) { + if ( is_null( $file )) { $file = $application; $application = ""; } - if( !empty( $application )) { + if ( !empty( $application )) { self::$scripts[] = "$application/js/$file"; - }else{ + } else { self::$scripts[] = "js/$file"; } } /** - * add a css file + * @brief add a css file * - * @param appid $application - * @param filename $file + * @param appid $application + * @param filename $file + * @return void */ public static function addStyle( $application, $file = null ) { - if( is_null( $file )) { + if ( is_null( $file )) { $file = $application; $application = ""; } - if( !empty( $application )) { + if ( !empty( $application )) { self::$styles[] = "$application/css/$file"; - }else{ + } else { self::$styles[] = "css/$file"; } } @@ -140,63 +149,74 @@ class OC_Util { * @param string tag tag name of the element * @param array $attributes array of attributes for the element * @param string $text the text content for the element + * @return void */ public static function addHeader( $tag, $attributes, $text='') { - self::$headers[] = array('tag'=>$tag, 'attributes'=>$attributes, 'text'=>$text); + self::$headers[] = array( + 'tag'=>$tag, + 'attributes'=>$attributes, + 'text'=>$text + ); } /** - * formats a timestamp in the "right" way + * @brief formats a timestamp in the "right" way * * @param int timestamp $timestamp * @param bool dateOnly option to omit time from the result + * @return string timestamp */ public static function formatDate( $timestamp, $dateOnly=false) { - if(\OC::$session->exists('timezone')) {//adjust to clients timezone if we know it + if(\OC::$session->exists('timezone')) { //adjust to clients timezone if we know it $systemTimeZone = intval(date('O')); - $systemTimeZone=(round($systemTimeZone/100, 0)*60)+($systemTimeZone%100); - $clientTimeZone=\OC::$session->get('timezone')*60; - $offset=$clientTimeZone-$systemTimeZone; - $timestamp=$timestamp+$offset*60; + $systemTimeZone = (round($systemTimeZone/100, 0)*60) + ($systemTimeZone%100); + $clientTimeZone = \OC::$session->get('timezone')*60; + $offset = $clientTimeZone - $systemTimeZone; + $timestamp = $timestamp + $offset*60; } - $l=OC_L10N::get('lib'); + $l = OC_L10N::get('lib'); return $l->l($dateOnly ? 'date' : 'datetime', $timestamp); } /** - * check if the current server configuration is suitable for ownCloud + * @brief check if the current server configuration is suitable for ownCloud * @return array arrays with error messages and hints */ public static function checkServer() { // Assume that if checkServer() succeeded before in this session, then all is fine. - if(\OC::$session->exists('checkServer_suceeded') && \OC::$session->get('checkServer_suceeded')) + if(\OC::$session->exists('checkServer_suceeded') && \OC::$session->get('checkServer_suceeded')) { return array(); + } - $errors=array(); + $errors = array(); $defaults = new \OC_Defaults(); - $web_server_restart= false; + $webServerRestart = false; //check for database drivers if(!(is_callable('sqlite_open') or class_exists('SQLite3')) and !is_callable('mysql_connect') and !is_callable('pg_connect') and !is_callable('oci_connect')) { - $errors[]=array('error'=>'No database drivers (sqlite, mysql, or postgresql) installed.', - 'hint'=>'');//TODO: sane hint - $web_server_restart= true; + $errors[] = array( + 'error'=>'No database drivers (sqlite, mysql, or postgresql) installed.', + 'hint'=>'' //TODO: sane hint + ); + $webServerRestart = true; } //common hint for all file permissons error messages $permissionsHint = 'Permissions can usually be fixed by ' - .'giving the webserver write access to the root directory.'; + .'giving the webserver write access to the root directory.'; // Check if config folder is writable. if(!is_writable(OC::$SERVERROOT."/config/") or !is_readable(OC::$SERVERROOT."/config/")) { $errors[] = array( 'error' => "Can't write into config directory", 'hint' => 'This can usually be fixed by ' - .'giving the webserver write access to the config directory.' + .'giving the webserver write access to the config directory.' ); } @@ -208,7 +228,8 @@ class OC_Util { $errors[] = array( 'error' => "Can't write into apps directory", 'hint' => 'This can usually be fixed by ' - .'giving the webserver write access to the apps directory ' + .'giving the webserver write access to the apps directory ' .'or disabling the appstore in the config file.' ); } @@ -223,94 +244,131 @@ class OC_Util { $errors[] = array( 'error' => "Can't create data directory (".$CONFIG_DATADIRECTORY.")", 'hint' => 'This can usually be fixed by ' - .'giving the webserver write access to the root directory.' + .'giving the webserver write access to the root directory.' ); } } else if(!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) { - $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') not writable by ownCloud', - 'hint'=>$permissionsHint); + $errors[] = array( + 'error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') not writable by ownCloud', + 'hint'=>$permissionsHint + ); } else { $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); } + + $moduleHint = "Please ask your server administrator to install the module."; // check if all required php modules are present if(!class_exists('ZipArchive')) { - $errors[]=array('error'=>'PHP module zip not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module zip not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if(!class_exists('DOMDocument')) { - $errors[] = array('error' => 'PHP module dom not installed.', - 'hint' => 'Please ask your server administrator to install the module.'); - $web_server_restart =true; + $errors[] = array( + 'error' => 'PHP module dom not installed.', + 'hint' => $moduleHint + ); + $webServerRestart =true; } if(!function_exists('xml_parser_create')) { - $errors[] = array('error' => 'PHP module libxml not installed.', - 'hint' => 'Please ask your server administrator to install the module.'); - $web_server_restart =true; + $errors[] = array( + 'error' => 'PHP module libxml not installed.', + 'hint' => $moduleHint + ); + $webServerRestart = true; } if(!function_exists('mb_detect_encoding')) { - $errors[]=array('error'=>'PHP module mb multibyte not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module mb multibyte not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if(!function_exists('ctype_digit')) { - $errors[]=array('error'=>'PHP module ctype is not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module ctype is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if(!function_exists('json_encode')) { - $errors[]=array('error'=>'PHP module JSON is not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module JSON is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if(!extension_loaded('gd') || !function_exists('gd_info')) { - $errors[]=array('error'=>'PHP module GD is not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module GD is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if(!function_exists('gzencode')) { - $errors[]=array('error'=>'PHP module zlib is not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module zlib is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if(!function_exists('iconv')) { - $errors[]=array('error'=>'PHP module iconv is not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module iconv is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if(!function_exists('simplexml_load_string')) { - $errors[]=array('error'=>'PHP module SimpleXML is not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP module SimpleXML is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } - if(floatval(phpversion())<5.3) { - $errors[]=array('error'=>'PHP 5.3 is required.', + if(floatval(phpversion()) < 5.3) { + $errors[] = array( + 'error'=>'PHP 5.3 is required.', '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.'); - $web_server_restart=true; + .' PHP 5.2 is no longer supported by ownCloud and the PHP community.' + ); + $webServerRestart = true; } if(!defined('PDO::ATTR_DRIVER_NAME')) { - $errors[]=array('error'=>'PHP PDO module is not installed.', - 'hint'=>'Please ask your server administrator to install the module.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP PDO module is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; } if (((strtolower(@ini_get('safe_mode')) == 'on') || (strtolower(@ini_get('safe_mode')) == 'yes') || (strtolower(@ini_get('safe_mode')) == 'true') || (ini_get("safe_mode") == 1 ))) { - $errors[]=array('error'=>'PHP Safe Mode is enabled. ownCloud requires that it is disabled to work properly.', - 'hint'=>'PHP Safe Mode is a deprecated and mostly useless setting that should be disabled. Please ask your server administrator to disable it in php.ini or in your webserver config.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'PHP Safe Mode is enabled. ownCloud requires that it is disabled to work properly.', + 'hint'=>'PHP Safe Mode is a deprecated and mostly useless setting that should be disabled. ' + .'Please ask your server administrator to disable it in php.ini or in your webserver config.' + ); + $webServerRestart = true; } if (get_magic_quotes_gpc() == 1 ) { - $errors[]=array('error'=>'Magic Quotes is enabled. ownCloud requires that it is disabled to work properly.', - 'hint'=>'Magic Quotes is a deprecated and mostly useless setting that should be disabled. Please ask your server administrator to disable it in php.ini or in your webserver config.'); - $web_server_restart=true; + $errors[] = array( + 'error'=>'Magic Quotes is enabled. ownCloud requires that it is disabled to work properly.', + 'hint'=>'Magic Quotes is a deprecated and mostly useless setting that should be disabled. ' + .'Please ask your server administrator to disable it in php.ini or in your webserver config.' + ); + $webServerRestart = true; } - if($web_server_restart) { - $errors[]=array('error'=>'PHP modules have been installed, but they are still listed as missing?', - 'hint'=>'Please ask your server administrator to restart the web server.'); + if($webServerRestart) { + $errors[] = array( + 'error'=>'PHP modules have been installed, but they are still listed as missing?', + 'hint'=>'Please ask your server administrator to restart the web server.' + ); } // Cache the result of this function @@ -330,20 +388,25 @@ class OC_Util { } else { $permissionsModHint = 'Please change the permissions to 0770 so that the directory' .' cannot be listed by other users.'; - $prems = substr(decoct(@fileperms($dataDirectory)), -3); - if (substr($prems, -1) != '0') { + $perms = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($perms, -1) != '0') { OC_Helper::chmodr($dataDirectory, 0770); clearstatcache(); - $prems = substr(decoct(@fileperms($dataDirectory)), -3); - if (substr($prems, 2, 1) != '0') { - $errors[] = array('error' => 'Data directory ('.$dataDirectory.') is readable for other users', - 'hint' => $permissionsModHint); + $perms = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($perms, 2, 1) != '0') { + $errors[] = array( + 'error' => 'Data directory ('.$dataDirectory.') is readable for other users', + 'hint' => $permissionsModHint + ); } } } return $errors; } + /** + * @return void + */ public static function displayLoginPage($errors = array()) { $parameters = array(); foreach( $errors as $key => $value ) { @@ -357,8 +420,8 @@ class OC_Util { $parameters['user_autofocus'] = true; } if (isset($_REQUEST['redirect_url'])) { - $redirect_url = $_REQUEST['redirect_url']; - $parameters['redirect_url'] = urlencode($redirect_url); + $redirectUrl = $_REQUEST['redirect_url']; + $parameters['redirect_url'] = urlencode($redirectUrl); } $parameters['alt_login'] = OC_App::getAlternativeLogIns(); @@ -367,7 +430,8 @@ class OC_Util { /** - * Check if the app is enabled, redirects to home if not + * @brief Check if the app is enabled, redirects to home if not + * @return void */ public static function checkAppEnabled($app) { if( !OC_App::isEnabled($app)) { @@ -379,18 +443,21 @@ class OC_Util { /** * Check if the user is logged in, redirects to home if not. With * redirect URL parameter to the request URI. + * @return void */ public static function checkLoggedIn() { // Check if we are a user if( !OC_User::isLoggedIn()) { header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php', - array('redirect_url' => OC_Request::requestUri()))); + array('redirectUrl' => OC_Request::requestUri()) + )); exit(); } } /** - * Check if the user is a admin, redirects to home if not + * @brief Check if the user is a admin, redirects to home if not + * @return void */ public static function checkAdminUser() { if( !OC_User::isAdminUser(OC_User::getUser())) { @@ -400,7 +467,7 @@ class OC_Util { } /** - * Check if the user is a subadmin, redirects to home if not + * @brief Check if the user is a subadmin, redirects to home if not * @return array $groups where the current user is subadmin */ public static function checkSubAdminUser() { @@ -412,7 +479,8 @@ class OC_Util { } /** - * Redirect to the user default page + * @brief Redirect to the user default page + * @return void */ public static function redirectToDefaultPage() { if(isset($_REQUEST['redirect_url'])) { @@ -420,13 +488,11 @@ class OC_Util { } else if (isset(OC::$REQUESTEDAPP) && !empty(OC::$REQUESTEDAPP)) { $location = OC_Helper::linkToAbsolute( OC::$REQUESTEDAPP, 'index.php' ); - } - else { - $defaultpage = OC_Appconfig::getValue('core', 'defaultpage'); - if ($defaultpage) { - $location = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/'.$defaultpage); - } - else { + } else { + $defaultPage = OC_Appconfig::getValue('core', 'defaultpage'); + if ($defaultPage) { + $location = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/'.$defaultPage); + } else { $location = OC_Helper::linkToAbsolute( 'files', 'index.php' ); } } @@ -435,19 +501,19 @@ class OC_Util { exit(); } - /** - * get an id unique for this instance - * @return string - */ - public static function getInstanceId() { - $id = OC_Config::getValue('instanceid', null); - if(is_null($id)) { - // We need to guarantee at least one letter in instanceid so it can be used as the session_name - $id = 'oc' . OC_Util::generate_random_bytes(10); - OC_Config::setValue('instanceid', $id); - } - return $id; - } + /** + * @brief get an id unique for this instance + * @return string + */ + public static function getInstanceId() { + $id = OC_Config::getValue('instanceid', null); + if(is_null($id)) { + // We need to guarantee at least one letter in instanceid so it can be used as the session_name + $id = 'oc' . self::generateRandomBytes(10); + OC_Config::setValue('instanceid', $id); + } + return $id; + } /** * @brief Static lifespan (in seconds) when a request token expires. @@ -476,7 +542,7 @@ class OC_Util { // Check if a token exists if(!\OC::$session->exists('requesttoken')) { // No valid token found, generate a new one. - $requestToken = self::generate_random_bytes(20); + $requestToken = self::generateRandomBytes(20); \OC::$session->set('requesttoken', $requestToken); } else { // Valid token already exists, send it @@ -497,11 +563,11 @@ class OC_Util { } if(isset($_GET['requesttoken'])) { - $token=$_GET['requesttoken']; + $token = $_GET['requesttoken']; } elseif(isset($_POST['requesttoken'])) { - $token=$_POST['requesttoken']; + $token = $_POST['requesttoken']; } elseif(isset($_SERVER['HTTP_REQUESTTOKEN'])) { - $token=$_SERVER['HTTP_REQUESTTOKEN']; + $token = $_SERVER['HTTP_REQUESTTOKEN']; } else { //no token found. return false; @@ -519,11 +585,12 @@ class OC_Util { /** * @brief Check an ajax get/post call if the request token is valid. exit if not. - * Todo: Write howto + * @todo Write howto + * @return void */ public static function callCheck() { if(!OC_Util::isCallRegistered()) { - exit; + exit(); } } @@ -562,12 +629,13 @@ class OC_Util { } /** - * Check if the htaccess file is working by creating a test file in the data directory and trying to access via http + * @brief Check if the htaccess file is working by creating a test file in the data directory and trying to access via http + * @return bool */ - public static function ishtaccessworking() { + public static function isHtaccessWorking() { // testdata - $filename='/htaccesstest.txt'; - $testcontent='testcontent'; + $filename = '/htaccesstest.txt'; + $testcontent = 'testcontent'; // creating a test file $testfile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$filename; @@ -591,19 +659,20 @@ class OC_Util { // does it work ? if($content==$testcontent) { - return(false); - }else{ - return(true); + return false; + } else { + return true; } } /** - * we test if webDAV is working properly - * + * @brief test if webDAV is working properly + * @return bool + * @description * The basic assumption is that if the server returns 401/Not Authenticated for an unauthenticated PROPFIND * the web server it self is setup properly. * - * Why not an authenticated PROFIND and other verbs? + * Why not an authenticated PROPFIND and other verbs? * - We don't have the password available * - We have no idea about other auth methods implemented (e.g. OAuth with Bearer header) * @@ -617,7 +686,7 @@ class OC_Util { ); // save the old timeout so that we can restore it later - $old_timeout=ini_get("default_socket_timeout"); + $oldTimeout = ini_get("default_socket_timeout"); // use a 5 sec timeout for the check. Should be enough for local requests. ini_set("default_socket_timeout", 5); @@ -631,15 +700,15 @@ class OC_Util { try { // test PROPFIND $client->propfind('', array('{DAV:}resourcetype')); - } catch(\Sabre_DAV_Exception_NotAuthenticated $e) { + } catch (\Sabre_DAV_Exception_NotAuthenticated $e) { $return = true; - } catch(\Exception $e) { + } catch (\Exception $e) { OC_Log::write('core', 'isWebDAVWorking: NO - Reason: '.$e->getMessage(). ' ('.get_class($e).')', OC_Log::WARN); $return = false; } // restore the original timeout - ini_set("default_socket_timeout", $old_timeout); + ini_set("default_socket_timeout", $oldTimeout); return $return; } @@ -647,8 +716,9 @@ class OC_Util { /** * Check if the setlocal call doesn't work. This can happen if the right * local packages are not available on the server. + * @return bool */ - public static function issetlocaleworking() { + public static function isSetlocaleWorking() { // setlocale test is pointless on Windows if (OC_Util::runningOnWindows() ) { return true; @@ -662,7 +732,7 @@ class OC_Util { } /** - * Check if the PHP module fileinfo is loaded. + * @brief Check if the PHP module fileinfo is loaded. * @return bool */ public static function fileInfoLoaded() { @@ -670,7 +740,8 @@ class OC_Util { } /** - * Check if the ownCloud server can connect to the internet + * @brief Check if the ownCloud server can connect to the internet + * @return bool */ public static function isInternetConnectionWorking() { // in case there is no internet connection on purpose return false @@ -683,30 +754,29 @@ class OC_Util { if ($connected) { fclose($connected); return true; - }else{ - + } else { // second try in case one server is down $connected = @fsockopen("apps.owncloud.com", 80); if ($connected) { fclose($connected); return true; - }else{ + } else { return false; } - } - } /** - * Check if the connection to the internet is disabled on purpose + * @brief Check if the connection to the internet is disabled on purpose + * @return bool */ public static function isInternetConnectionEnabled(){ return \OC_Config::getValue("has_internet_connection", true); } /** - * clear all levels of output buffering + * @brief clear all levels of output buffering + * @return void */ public static function obEnd(){ while (ob_get_level()) { @@ -719,44 +789,44 @@ class OC_Util { * @brief Generates a cryptographical secure pseudorandom string * @param Int with the length of the random string * @return String - * Please also update secureRNG_available if you change something here + * Please also update secureRNGAvailable if you change something here */ - public static function generate_random_bytes($length = 30) { - + public static function generateRandomBytes($length = 30) { // Try to use openssl_random_pseudo_bytes - if(function_exists('openssl_random_pseudo_bytes')) { - $pseudo_byte = bin2hex(openssl_random_pseudo_bytes($length, $strong)); + if (function_exists('openssl_random_pseudo_bytes')) { + $pseudoByte = bin2hex(openssl_random_pseudo_bytes($length, $strong)); if($strong == true) { - return substr($pseudo_byte, 0, $length); // Truncate it to match the length + return substr($pseudoByte, 0, $length); // Truncate it to match the length } } // Try to use /dev/urandom - $fp = @file_get_contents('/dev/urandom', false, null, 0, $length); - if ($fp !== false) { - $string = substr(bin2hex($fp), 0, $length); - return $string; + if (!self::runningOnWindows()) { + $fp = @file_get_contents('/dev/urandom', false, null, 0, $length); + if ($fp !== false) { + $string = substr(bin2hex($fp), 0, $length); + return $string; + } } // Fallback to mt_rand() $characters = '0123456789'; $characters .= 'abcdefghijklmnopqrstuvwxyz'; $charactersLength = strlen($characters)-1; - $pseudo_byte = ""; + $pseudoByte = ""; // Select some random characters for ($i = 0; $i < $length; $i++) { - $pseudo_byte .= $characters[mt_rand(0, $charactersLength)]; + $pseudoByte .= $characters[mt_rand(0, $charactersLength)]; } - return $pseudo_byte; + return $pseudoByte; } /** * @brief Checks if a secure random number generator is available * @return bool */ - public static function secureRNG_available() { - + public static function secureRNGAvailable() { // Check openssl_random_pseudo_bytes if(function_exists('openssl_random_pseudo_bytes')) { openssl_random_pseudo_bytes(1, $strong); @@ -766,9 +836,11 @@ class OC_Util { } // Check /dev/urandom - $fp = @file_get_contents('/dev/urandom', false, null, 0, 1); - if ($fp !== false) { - return true; + if (!self::runningOnWindows()) { + $fp = @file_get_contents('/dev/urandom', false, null, 0, 1); + if ($fp !== false) { + return true; + } } return false; @@ -781,11 +853,8 @@ class OC_Util { * This function get the content of a page via curl, if curl is enabled. * If not, file_get_element is used. */ - public static function getUrlContent($url){ - - if (function_exists('curl_init')) { - + if (function_exists('curl_init')) { $curl = curl_init(); curl_setopt($curl, CURLOPT_HEADER, 0); @@ -796,10 +865,10 @@ class OC_Util { curl_setopt($curl, CURLOPT_MAXREDIRS, 10); curl_setopt($curl, CURLOPT_USERAGENT, "ownCloud Server Crawler"); - if(OC_Config::getValue('proxy', '')<>'') { + if(OC_Config::getValue('proxy', '') != '') { curl_setopt($curl, CURLOPT_PROXY, OC_Config::getValue('proxy')); } - if(OC_Config::getValue('proxyuserpwd', '')<>'') { + if(OC_Config::getValue('proxyuserpwd', '') != '') { curl_setopt($curl, CURLOPT_PROXYUSERPWD, OC_Config::getValue('proxyuserpwd')); } $data = curl_exec($curl); @@ -808,7 +877,7 @@ class OC_Util { } else { $contextArray = null; - if(OC_Config::getValue('proxy', '')<>'') { + if(OC_Config::getValue('proxy', '') != '') { $contextArray = array( 'http' => array( 'timeout' => 10, @@ -823,11 +892,10 @@ class OC_Util { ); } - $ctx = stream_context_create( $contextArray ); - $data=@file_get_contents($url, 0, $ctx); + $data = @file_get_contents($url, 0, $ctx); } return $data; @@ -840,7 +908,6 @@ class OC_Util { return (substr(PHP_OS, 0, 3) === "WIN"); } - /** * Handles the case that there may not be a theme, then check if a "default" * theme exists and take that one @@ -850,20 +917,19 @@ class OC_Util { $theme = OC_Config::getValue("theme", ''); if($theme === '') { - if(is_dir(OC::$SERVERROOT . '/themes/default')) { $theme = 'default'; } - } return $theme; } /** - * Clear the opcode cache if one exists + * @brief Clear the opcode cache if one exists * This is necessary for writing to the config file * in case the opcode cache doesn't revalidate files + * @return void */ public static function clearOpcodeCache() { // APC @@ -902,8 +968,10 @@ class OC_Util { return $value; } - public static function basename($file) - { + /** + * @return string + */ + public static function basename($file) { $file = rtrim($file, '/'); $t = explode('/', $file); return array_pop($t); diff --git a/settings/admin.php b/settings/admin.php index 10e239204f..d721593eb7 100755 --- a/settings/admin.php +++ b/settings/admin.php @@ -15,7 +15,7 @@ OC_App::setActiveNavigationEntry( "admin" ); $tmpl = new OC_Template( 'settings', 'admin', 'user'); $forms=OC_App::getForms('admin'); -$htaccessworking=OC_Util::ishtaccessworking(); +$htaccessworking=OC_Util::isHtaccessWorking(); $entries=OC_Log_Owncloud::getEntries(3); $entriesremain=(count(OC_Log_Owncloud::getEntries(4)) > 3)?true:false; @@ -25,7 +25,7 @@ $tmpl->assign('entries', $entries); $tmpl->assign('entriesremain', $entriesremain); $tmpl->assign('htaccessworking', $htaccessworking); $tmpl->assign('internetconnectionworking', OC_Util::isInternetConnectionEnabled() ? OC_Util::isInternetConnectionWorking() : false); -$tmpl->assign('islocaleworking', OC_Util::issetlocaleworking()); +$tmpl->assign('islocaleworking', OC_Util::isSetlocaleWorking()); $tmpl->assign('isWebDavWorking', OC_Util::isWebDAVWorking()); $tmpl->assign('has_fileinfo', OC_Util::fileInfoLoaded()); $tmpl->assign('backgroundjobs_mode', OC_Appconfig::getValue('core', 'backgroundjobs_mode', 'ajax')); diff --git a/tests/lib/db.php b/tests/lib/db.php index 51edbf7b30..1977025cf1 100644 --- a/tests/lib/db.php +++ b/tests/lib/db.php @@ -15,7 +15,7 @@ class Test_DB extends PHPUnit_Framework_TestCase { public function setUp() { $dbfile = OC::$SERVERROOT.'/tests/data/db_structure.xml'; - $r = '_'.OC_Util::generate_random_bytes('4').'_'; + $r = '_'.OC_Util::generateRandomBytes('4').'_'; $content = file_get_contents( $dbfile ); $content = str_replace( '*dbprefix*', '*dbprefix*'.$r, $content ); file_put_contents( self::$schema_file, $content ); diff --git a/tests/lib/dbschema.php b/tests/lib/dbschema.php index c2e55eabf4..7de90c047c 100644 --- a/tests/lib/dbschema.php +++ b/tests/lib/dbschema.php @@ -16,7 +16,7 @@ class Test_DBSchema extends PHPUnit_Framework_TestCase { $dbfile = OC::$SERVERROOT.'/tests/data/db_structure.xml'; $dbfile2 = OC::$SERVERROOT.'/tests/data/db_structure2.xml'; - $r = '_'.OC_Util::generate_random_bytes('4').'_'; + $r = '_'.OC_Util::generateRandomBytes('4').'_'; $content = file_get_contents( $dbfile ); $content = str_replace( '*dbprefix*', '*dbprefix*'.$r, $content ); file_put_contents( $this->schema_file, $content ); diff --git a/tests/lib/util.php b/tests/lib/util.php index 13aa49c8c6..d607a3e772 100644 --- a/tests/lib/util.php +++ b/tests/lib/util.php @@ -71,8 +71,8 @@ class Test_Util extends PHPUnit_Framework_TestCase { $this->assertTrue(\OC_Util::isInternetConnectionEnabled()); } - function testGenerate_random_bytes() { - $result = strlen(OC_Util::generate_random_bytes(59)); + function testGenerateRandomBytes() { + $result = strlen(OC_Util::generateRandomBytes(59)); $this->assertEquals(59, $result); } From 33bb2238aeb0fddd3ddd3fe18307c5e7548e00bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 16 Aug 2013 08:06:25 +0200 Subject: [PATCH 002/198] updating 3rdparty repo commit --- 3rdparty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty b/3rdparty index 2f3ae9f56a..8d68fa1eab 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit 2f3ae9f56a9838b45254393e13c14f8a8c380d6b +Subproject commit 8d68fa1eabe8c1d033cb89676b31f0eaaf99335b From a0b7bf78a6e1c269adc8ee1c84b72a8d205a6f28 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 16 Aug 2013 19:05:07 +0200 Subject: [PATCH 003/198] Remove disconnect function from OC_DB --- lib/db.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/db.php b/lib/db.php index ebd012c72f..f090f47424 100644 --- a/lib/db.php +++ b/lib/db.php @@ -329,18 +329,6 @@ class OC_DB { self::$connection->commit(); } - /** - * @brief Disconnect - * - * This is good bye, good bye, yeah! - */ - public static function disconnect() { - // Cut connection if required - if(self::$connection) { - self::$connection->close(); - } - } - /** * @brief saves database schema to xml file * @param string $file name of file From d43b4c52ae0169d80e10532db4ce2103f211cd56 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 17 Aug 2013 10:57:31 +0200 Subject: [PATCH 004/198] also emit hooks for views that are a subfolder of the user folder --- lib/files/view.php | 89 +++++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index c9727fe498..21c902ccc4 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -267,18 +267,18 @@ class View { $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) and Filesystem::isValidPath($path) - and !Filesystem::isFileBlacklisted($path) + and !Filesystem::isFileBlacklisted($path) ) { $path = $this->getRelativePath($absolutePath); $exists = $this->file_exists($path); $run = true; - if ($this->fakeRoot == Filesystem::getRoot() && !Cache\Scanner::isPartialFile($path)) { + if ($this->shouldEmitHooks($path)) { if (!$exists) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_create, array( - Filesystem::signal_param_path => $path, + Filesystem::signal_param_path => $this->getHookPath($path), Filesystem::signal_param_run => &$run ) ); @@ -287,7 +287,7 @@ class View { Filesystem::CLASSNAME, Filesystem::signal_write, array( - Filesystem::signal_param_path => $path, + Filesystem::signal_param_path => $this->getHookPath($path), Filesystem::signal_param_run => &$run ) ); @@ -300,18 +300,18 @@ class View { list ($count, $result) = \OC_Helper::streamCopy($data, $target); fclose($target); fclose($data); - if ($this->fakeRoot == Filesystem::getRoot() && !Cache\Scanner::isPartialFile($path) && $result !== false) { + if ($this->shouldEmitHooks($path) && $result !== false) { if (!$exists) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_create, - array(Filesystem::signal_param_path => $path) + array(Filesystem::signal_param_path => $this->getHookPath($path)) ); } \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_write, - array(Filesystem::signal_param_path => $path) + array(Filesystem::signal_param_path => $this->getHookPath($path)) ); } \OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); @@ -353,21 +353,21 @@ class View { return false; } $run = true; - if ($this->fakeRoot == Filesystem::getRoot() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { + if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { // if it was a rename from a part file to a regular file it was a write and not a rename operation \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_write, array( - Filesystem::signal_param_path => $path2, + Filesystem::signal_param_path => $this->getHookPath($path2), Filesystem::signal_param_run => &$run ) ); - } elseif ($this->fakeRoot == Filesystem::getRoot()) { + } elseif ($this->shouldEmitHooks()) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_rename, array( - Filesystem::signal_param_oldpath => $path1, - Filesystem::signal_param_newpath => $path2, + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), Filesystem::signal_param_run => &$run ) ); @@ -407,22 +407,22 @@ class View { } } } - if ($this->fakeRoot == Filesystem::getRoot() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { + if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { // if it was a rename from a part file to a regular file it was a write and not a rename operation \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_write, array( - Filesystem::signal_param_path => $path2, + Filesystem::signal_param_path => $this->getHookPath($path2), ) ); - } elseif ($this->fakeRoot == Filesystem::getRoot() && $result !== false) { + } elseif ($this->shouldEmitHooks() && $result !== false) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_rename, array( - Filesystem::signal_param_oldpath => $path1, - Filesystem::signal_param_newpath => $path2 + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) ) ); } @@ -454,13 +454,13 @@ class View { } $run = true; $exists = $this->file_exists($path2); - if ($this->fakeRoot == Filesystem::getRoot()) { + if ($this->shouldEmitHooks()) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_copy, array( - Filesystem::signal_param_oldpath => $path1, - Filesystem::signal_param_newpath => $path2, + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), Filesystem::signal_param_run => &$run ) ); @@ -469,7 +469,7 @@ class View { Filesystem::CLASSNAME, Filesystem::signal_create, array( - Filesystem::signal_param_path => $path2, + Filesystem::signal_param_path => $this->getHookPath($path2), Filesystem::signal_param_run => &$run ) ); @@ -479,7 +479,7 @@ class View { Filesystem::CLASSNAME, Filesystem::signal_write, array( - Filesystem::signal_param_path => $path2, + Filesystem::signal_param_path => $this->getHookPath($path2), Filesystem::signal_param_run => &$run ) ); @@ -510,26 +510,26 @@ class View { list($count, $result) = \OC_Helper::streamCopy($source, $target); } } - if ($this->fakeRoot == Filesystem::getRoot() && $result !== false) { + if ($this->shouldEmitHooks() && $result !== false) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_copy, array( - Filesystem::signal_param_oldpath => $path1, - Filesystem::signal_param_newpath => $path2 + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) ) ); if (!$exists) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_create, - array(Filesystem::signal_param_path => $path2) + array(Filesystem::signal_param_path => $this->getHookPath($path2)) ); } \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_write, - array(Filesystem::signal_param_path => $path2) + array(Filesystem::signal_param_path => $this->getHookPath($path2)) ); } return $result; @@ -620,11 +620,11 @@ class View { if ($path == null) { return false; } - if (Filesystem::$loaded && $this->fakeRoot == Filesystem::getRoot()) { + if ($this->shouldEmitHooks($path)) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_read, - array(Filesystem::signal_param_path => $path) + array(Filesystem::signal_param_path => $this->getHookPath($path)) ); } list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); @@ -658,7 +658,7 @@ class View { $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); if (\OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and Filesystem::isValidPath($path) - and !Filesystem::isFileBlacklisted($path) + and !Filesystem::isFileBlacklisted($path) ) { $path = $this->getRelativePath($absolutePath); if ($path == null) { @@ -674,7 +674,7 @@ class View { $result = $storage->$operation($internalPath); } $result = \OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result); - if (Filesystem::$loaded and $this->fakeRoot == Filesystem::getRoot() && $result !== false) { + if ($this->shouldEmitHooks($path) && $result !== false) { if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open $this->runHooks($hooks, $path, true); } @@ -685,10 +685,35 @@ class View { return null; } + /** + * get the path relative to the default root for hook usage + * + * @param string $path + * @return string + */ + private function getHookPath($path) { + if (!Filesystem::getView()) { + return $path; + } + return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path)); + } + + private function shouldEmitHooks($path = '') { + if ($path && Cache\Scanner::isPartialFile($path)) { + return false; + } + if (!Filesystem::$loaded) { + return false; + } + $defaultRoot = Filesystem::getRoot(); + return (strlen($this->fakeRoot) >= strlen($defaultRoot)) && (substr($this->fakeRoot, 0, strlen($defaultRoot)) === $defaultRoot); + } + private function runHooks($hooks, $path, $post = false) { + $path = $this->getHookPath($path); $prefix = ($post) ? 'post_' : ''; $run = true; - if (Filesystem::$loaded and $this->fakeRoot == Filesystem::getRoot() && !Cache\Scanner::isPartialFile($path)) { + if ($this->shouldEmitHooks($path)) { foreach ($hooks as $hook) { if ($hook != 'read') { \OC_Hook::emit( From fde9cabe9774b67e88ee8aa8fa39fe044fe2da2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 17 Aug 2013 11:16:48 +0200 Subject: [PATCH 005/198] initial import of appframework --- lib/appframework/app.php | 97 ++++ lib/appframework/controller/controller.php | 154 +++++ lib/appframework/core/api.php | 524 ++++++++++++++++++ .../dependencyinjection/dicontainer.php | 125 +++++ lib/appframework/http/dispatcher.php | 98 ++++ lib/appframework/http/downloadresponse.php | 51 ++ lib/appframework/http/http.php | 208 +++++++ lib/appframework/http/jsonresponse.php | 74 +++ lib/appframework/http/redirectresponse.php | 54 ++ lib/appframework/http/request.php | 217 ++++++++ lib/appframework/http/response.php | 169 ++++++ lib/appframework/http/templateresponse.php | 126 +++++ lib/appframework/middleware/middleware.php | 100 ++++ .../middleware/middlewaredispatcher.php | 159 ++++++ .../middleware/security/securityexception.php | 41 ++ .../security/securitymiddleware.php | 141 +++++ .../routing/routeactionhandler.php | 42 ++ lib/appframework/routing/routeconfig.php | 186 +++++++ .../utility/methodannotationreader.php | 61 ++ lib/appframework/utility/timefactory.php | 42 ++ tests/lib/appframework/AppTest.php | 107 ++++ tests/lib/appframework/classloader.php | 45 ++ .../controller/ControllerTest.php | 161 ++++++ .../dependencyinjection/DIContainerTest.php | 98 ++++ .../lib/appframework/http/DispatcherTest.php | 218 ++++++++ .../http/DownloadResponseTest.php | 51 ++ tests/lib/appframework/http/HttpTest.php | 87 +++ .../appframework/http/JSONResponseTest.php | 96 ++++ .../http/RedirectResponseTest.php | 55 ++ tests/lib/appframework/http/RequestTest.php | 78 +++ tests/lib/appframework/http/ResponseTest.php | 119 ++++ .../http/TemplateResponseTest.php | 157 ++++++ .../middleware/MiddlewareDispatcherTest.php | 280 ++++++++++ .../middleware/MiddlewareTest.php | 82 +++ .../security/SecurityMiddlewareTest.php | 388 +++++++++++++ .../lib/appframework/routing/RoutingTest.php | 214 +++++++ .../utility/MethodAnnotationReaderTest.php | 58 ++ 37 files changed, 4963 insertions(+) create mode 100644 lib/appframework/app.php create mode 100644 lib/appframework/controller/controller.php create mode 100644 lib/appframework/core/api.php create mode 100644 lib/appframework/dependencyinjection/dicontainer.php create mode 100644 lib/appframework/http/dispatcher.php create mode 100644 lib/appframework/http/downloadresponse.php create mode 100644 lib/appframework/http/http.php create mode 100644 lib/appframework/http/jsonresponse.php create mode 100644 lib/appframework/http/redirectresponse.php create mode 100644 lib/appframework/http/request.php create mode 100644 lib/appframework/http/response.php create mode 100644 lib/appframework/http/templateresponse.php create mode 100644 lib/appframework/middleware/middleware.php create mode 100644 lib/appframework/middleware/middlewaredispatcher.php create mode 100644 lib/appframework/middleware/security/securityexception.php create mode 100644 lib/appframework/middleware/security/securitymiddleware.php create mode 100644 lib/appframework/routing/routeactionhandler.php create mode 100644 lib/appframework/routing/routeconfig.php create mode 100644 lib/appframework/utility/methodannotationreader.php create mode 100644 lib/appframework/utility/timefactory.php create mode 100644 tests/lib/appframework/AppTest.php create mode 100644 tests/lib/appframework/classloader.php create mode 100644 tests/lib/appframework/controller/ControllerTest.php create mode 100644 tests/lib/appframework/dependencyinjection/DIContainerTest.php create mode 100644 tests/lib/appframework/http/DispatcherTest.php create mode 100644 tests/lib/appframework/http/DownloadResponseTest.php create mode 100644 tests/lib/appframework/http/HttpTest.php create mode 100644 tests/lib/appframework/http/JSONResponseTest.php create mode 100644 tests/lib/appframework/http/RedirectResponseTest.php create mode 100644 tests/lib/appframework/http/RequestTest.php create mode 100644 tests/lib/appframework/http/ResponseTest.php create mode 100644 tests/lib/appframework/http/TemplateResponseTest.php create mode 100644 tests/lib/appframework/middleware/MiddlewareDispatcherTest.php create mode 100644 tests/lib/appframework/middleware/MiddlewareTest.php create mode 100644 tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php create mode 100644 tests/lib/appframework/routing/RoutingTest.php create mode 100644 tests/lib/appframework/utility/MethodAnnotationReaderTest.php diff --git a/lib/appframework/app.php b/lib/appframework/app.php new file mode 100644 index 0000000000..6224b858bb --- /dev/null +++ b/lib/appframework/app.php @@ -0,0 +1,97 @@ +. + * + */ + + +namespace OC\AppFramework; + +use OC\AppFramework\DependencyInjection\DIContainer; + + +/** + * Entry point for every request in your app. You can consider this as your + * public static void main() method + * + * Handles all the dependency injection, controllers and output flow + */ +class App { + + + /** + * Shortcut for calling a controller method and printing the result + * @param string $controllerName the name of the controller under which it is + * stored in the DI container + * @param string $methodName the method that you want to call + * @param array $urlParams an array with variables extracted from the routes + * @param DIContainer $container an instance of a pimple container. + */ + public static function main($controllerName, $methodName, array $urlParams, + DIContainer $container) { + $container['urlParams'] = $urlParams; + $controller = $container[$controllerName]; + + // initialize the dispatcher and run all the middleware before the controller + $dispatcher = $container['Dispatcher']; + + list($httpHeaders, $responseHeaders, $output) = + $dispatcher->dispatch($controller, $methodName); + + if(!is_null($httpHeaders)) { + header($httpHeaders); + } + + foreach($responseHeaders as $name => $value) { + header($name . ': ' . $value); + } + + if(!is_null($output)) { + header('Content-Length: ' . strlen($output)); + print($output); + } + + } + + /** + * Shortcut for calling a controller method and printing the result. + * Similar to App:main except that no headers will be sent. + * This should be used for example when registering sections via + * \OC\AppFramework\Core\API::registerAdmin() + * + * @param string $controllerName the name of the controller under which it is + * stored in the DI container + * @param string $methodName the method that you want to call + * @param array $urlParams an array with variables extracted from the routes + * @param DIContainer $container an instance of a pimple container. + */ + public static function part($controllerName, $methodName, array $urlParams, + DIContainer $container){ + + $container['urlParams'] = $urlParams; + $controller = $container[$controllerName]; + + $dispatcher = $container['Dispatcher']; + + list(, , $output) = $dispatcher->dispatch($controller, $methodName); + return $output; + } + +} diff --git a/lib/appframework/controller/controller.php b/lib/appframework/controller/controller.php new file mode 100644 index 0000000000..3e8166050d --- /dev/null +++ b/lib/appframework/controller/controller.php @@ -0,0 +1,154 @@ +. + * + */ + + +namespace OC\AppFramework\Controller; + +use OC\AppFramework\Http\TemplateResponse; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Core\API; + + +/** + * Base class to inherit your controllers from + */ +abstract class Controller { + + /** + * @var API instance of the api layer + */ + protected $api; + + protected $request; + + /** + * @param API $api an api wrapper instance + * @param Request $request an instance of the request + */ + public function __construct(API $api, Request $request){ + $this->api = $api; + $this->request = $request; + } + + + /** + * Lets you access post and get parameters by the index + * @param string $key the key which you want to access in the URL Parameter + * placeholder, $_POST or $_GET array. + * The priority how they're returned is the following: + * 1. URL parameters + * 2. POST parameters + * 3. GET parameters + * @param mixed $default If the key is not found, this value will be returned + * @return mixed the content of the array + */ + public function params($key, $default=null){ + return isset($this->request->parameters[$key]) + ? $this->request->parameters[$key] + : $default; + } + + + /** + * Returns all params that were received, be it from the request + * (as GET or POST) or throuh the URL by the route + * @return array the array with all parameters + */ + public function getParams() { + return $this->request->parameters; + } + + + /** + * Returns the method of the request + * @return string the method of the request (POST, GET, etc) + */ + public function method() { + return $this->request->method; + } + + + /** + * Shortcut for accessing an uploaded file through the $_FILES array + * @param string $key the key that will be taken from the $_FILES array + * @return array the file in the $_FILES element + */ + public function getUploadedFile($key) { + return isset($this->request->files[$key]) ? $this->request->files[$key] : null; + } + + + /** + * Shortcut for getting env variables + * @param string $key the key that will be taken from the $_ENV array + * @return array the value in the $_ENV element + */ + public function env($key) { + return isset($this->request->env[$key]) ? $this->request->env[$key] : null; + } + + + /** + * Shortcut for getting session variables + * @param string $key the key that will be taken from the $_SESSION array + * @return array the value in the $_SESSION element + */ + public function session($key) { + return isset($this->request->session[$key]) ? $this->request->session[$key] : null; + } + + + /** + * Shortcut for getting cookie variables + * @param string $key the key that will be taken from the $_COOKIE array + * @return array the value in the $_COOKIE element + */ + public function cookie($key) { + return isset($this->request->cookies[$key]) ? $this->request->cookies[$key] : null; + } + + + /** + * Shortcut for rendering a template + * @param string $templateName the name of the template + * @param array $params the template parameters in key => value structure + * @param string $renderAs user renders a full page, blank only your template + * admin an entry in the admin settings + * @param array $headers set additional headers in name/value pairs + * @return \OC\AppFramework\Http\TemplateResponse containing the page + */ + public function render($templateName, array $params=array(), + $renderAs='user', array $headers=array()){ + $response = new TemplateResponse($this->api, $templateName); + $response->setParams($params); + $response->renderAs($renderAs); + + foreach($headers as $name => $value){ + $response->addHeader($name, $value); + } + + return $response; + } + + +} diff --git a/lib/appframework/core/api.php b/lib/appframework/core/api.php new file mode 100644 index 0000000000..eb8ee01e5d --- /dev/null +++ b/lib/appframework/core/api.php @@ -0,0 +1,524 @@ +. + * + */ + + +namespace OC\AppFramework\Core; + + +/** + * This is used to wrap the owncloud static api calls into an object to make the + * code better abstractable for use in the dependency injection container + * + * Should you find yourself in need for more methods, simply inherit from this + * class and add your methods + */ +class API { + + private $appName; + + /** + * constructor + * @param string $appName the name of your application + */ + public function __construct($appName){ + $this->appName = $appName; + } + + + /** + * used to return the appname of the set application + * @return string the name of your application + */ + public function getAppName(){ + return $this->appName; + } + + + /** + * Creates a new navigation entry + * @param array $entry containing: id, name, order, icon and href key + */ + public function addNavigationEntry(array $entry){ + \OCP\App::addNavigationEntry($entry); + } + + + /** + * Gets the userid of the current user + * @return string the user id of the current user + */ + public function getUserId(){ + return \OCP\User::getUser(); + } + + + /** + * Sets the current navigation entry to the currently running app + */ + public function activateNavigationEntry(){ + \OCP\App::setActiveNavigationEntry($this->appName); + } + + + /** + * Adds a new javascript file + * @param string $scriptName the name of the javascript in js/ without the suffix + * @param string $appName the name of the app, defaults to the current one + */ + public function addScript($scriptName, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + \OCP\Util::addScript($appName, $scriptName); + } + + + /** + * Adds a new css file + * @param string $styleName the name of the css file in css/without the suffix + * @param string $appName the name of the app, defaults to the current one + */ + public function addStyle($styleName, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + \OCP\Util::addStyle($appName, $styleName); + } + + + /** + * shorthand for addScript for files in the 3rdparty directory + * @param string $name the name of the file without the suffix + */ + public function add3rdPartyScript($name){ + \OCP\Util::addScript($this->appName . '/3rdparty', $name); + } + + + /** + * shorthand for addStyle for files in the 3rdparty directory + * @param string $name the name of the file without the suffix + */ + public function add3rdPartyStyle($name){ + \OCP\Util::addStyle($this->appName . '/3rdparty', $name); + } + + /** + * Looks up a systemwide defined value + * @param string $key the key of the value, under which it was saved + * @return string the saved value + */ + public function getSystemValue($key){ + return \OCP\Config::getSystemValue($key, ''); + } + + + /** + * Sets a new systemwide value + * @param string $key the key of the value, under which will be saved + * @param string $value the value that should be stored + */ + public function setSystemValue($key, $value){ + return \OCP\Config::setSystemValue($key, $value); + } + + + /** + * Looks up an appwide defined value + * @param string $key the key of the value, under which it was saved + * @return string the saved value + */ + public function getAppValue($key, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Config::getAppValue($appName, $key, ''); + } + + + /** + * Writes a new appwide value + * @param string $key the key of the value, under which will be saved + * @param string $value the value that should be stored + */ + public function setAppValue($key, $value, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Config::setAppValue($appName, $key, $value); + } + + + + /** + * Shortcut for setting a user defined value + * @param string $key the key under which the value is being stored + * @param string $value the value that you want to store + * @param string $userId the userId of the user that we want to store the value under, defaults to the current one + */ + public function setUserValue($key, $value, $userId=null){ + if($userId === null){ + $userId = $this->getUserId(); + } + \OCP\Config::setUserValue($userId, $this->appName, $key, $value); + } + + + /** + * Shortcut for getting a user defined value + * @param string $key the key under which the value is being stored + * @param string $userId the userId of the user that we want to store the value under, defaults to the current one + */ + public function getUserValue($key, $userId=null){ + if($userId === null){ + $userId = $this->getUserId(); + } + return \OCP\Config::getUserValue($userId, $this->appName, $key); + } + + + /** + * Returns the translation object + * @return \OC_L10N the translation object + */ + public function getTrans(){ + # TODO: use public api + return \OC_L10N::get($this->appName); + } + + + /** + * Used to abstract the owncloud database access away + * @param string $sql the sql query with ? placeholder for params + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \OCP\DB a query object + */ + public function prepareQuery($sql, $limit=null, $offset=null){ + return \OCP\DB::prepare($sql, $limit, $offset); + } + + + /** + * Used to get the id of the just inserted element + * @param string $tableName the name of the table where we inserted the item + * @return int the id of the inserted element + */ + public function getInsertId($tableName){ + return \OCP\DB::insertid($tableName); + } + + + /** + * Returns the URL for a route + * @param string $routeName the name of the route + * @param array $arguments an array with arguments which will be filled into the url + * @return string the url + */ + public function linkToRoute($routeName, $arguments=array()){ + return \OCP\Util::linkToRoute($routeName, $arguments); + } + + + /** + * Returns an URL for an image or file + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + */ + public function linkTo($file, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Util::linkTo($appName, $file); + } + + + /** + * Returns the link to an image, like link to but only with prepending img/ + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + */ + public function imagePath($file, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Util::imagePath($appName, $file); + } + + + /** + * Makes an URL absolute + * @param string $url the url + * @return string the absolute url + */ + public function getAbsoluteURL($url){ + # TODO: use public api + return \OC_Helper::makeURLAbsolute($url); + } + + + /** + * links to a file + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + * @deprecated replaced with linkToRoute() + * @return string the url + */ + public function linkToAbsolute($file, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Util::linkToAbsolute($appName, $file); + } + + + /** + * Checks if the current user is logged in + * @return bool true if logged in + */ + public function isLoggedIn(){ + return \OCP\User::isLoggedIn(); + } + + + /** + * Checks if a user is an admin + * @param string $userId the id of the user + * @return bool true if admin + */ + public function isAdminUser($userId){ + # TODO: use public api + return \OC_User::isAdminUser($userId); + } + + + /** + * Checks if a user is an subadmin + * @param string $userId the id of the user + * @return bool true if subadmin + */ + public function isSubAdminUser($userId){ + # TODO: use public api + return \OC_SubAdmin::isSubAdmin($userId); + } + + + /** + * Checks if the CSRF check was correct + * @return bool true if CSRF check passed + */ + public function passesCSRFCheck(){ + # TODO: use public api + return \OC_Util::isCallRegistered(); + } + + + /** + * Checks if an app is enabled + * @param string $appName the name of an app + * @return bool true if app is enabled + */ + public function isAppEnabled($appName){ + return \OCP\App::isEnabled($appName); + } + + + /** + * Writes a function into the error log + * @param string $msg the error message to be logged + * @param int $level the error level + */ + public function log($msg, $level=null){ + switch($level){ + case 'debug': + $level = \OCP\Util::DEBUG; + break; + case 'info': + $level = \OCP\Util::INFO; + break; + case 'warn': + $level = \OCP\Util::WARN; + break; + case 'fatal': + $level = \OCP\Util::FATAL; + break; + default: + $level = \OCP\Util::ERROR; + break; + } + \OCP\Util::writeLog($this->appName, $msg, $level); + } + + + /** + * Returns a template + * @param string $templateName the name of the template + * @param string $renderAs how it should be rendered + * @param string $appName the name of the app + * @return \OCP\Template a new template + */ + public function getTemplate($templateName, $renderAs='user', $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + + if($renderAs === 'blank'){ + return new \OCP\Template($appName, $templateName); + } else { + return new \OCP\Template($appName, $templateName, $renderAs); + } + } + + + /** + * turns an owncloud path into a path on the filesystem + * @param string path the path to the file on the oc filesystem + * @return string the filepath in the filesystem + */ + public function getLocalFilePath($path){ + # TODO: use public api + return \OC_Filesystem::getLocalFile($path); + } + + + /** + * used to return and open a new eventsource + * @return \OC_EventSource a new open EventSource class + */ + public function openEventSource(){ + # TODO: use public api + return new \OC_EventSource(); + } + + /** + * @brief connects a function to a hook + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param string $slotClass class name of slot + * @param string $slotName name of slot, in another word, this is the + * name of the method that will be called when registered + * signal is emitted. + * @return bool, always true + */ + public function connectHook($signalClass, $signalName, $slotClass, $slotName) { + return \OCP\Util::connectHook($signalClass, $signalName, $slotClass, $slotName); + } + + /** + * @brief Emits a signal. To get data from the slot use references! + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param array $params defautl: array() array with additional data + * @return bool, true if slots exists or false if not + */ + public function emitHook($signalClass, $signalName, $params = array()) { + return \OCP\Util::emitHook($signalClass, $signalName, $params); + } + + /** + * @brief clear hooks + * @param string $signalClass + * @param string $signalName + */ + public function clearHook($signalClass=false, $signalName=false) { + if ($signalClass) { + \OC_Hook::clear($signalClass, $signalName); + } + } + + /** + * Gets the content of an URL by using CURL or a fallback if it is not + * installed + * @param string $url the url that should be fetched + * @return string the content of the webpage + */ + public function getUrlContent($url) { + return \OC_Util::getUrlContent($url); + } + + /** + * Register a backgroundjob task + * @param string $className full namespace and class name of the class + * @param string $methodName the name of the static method that should be + * called + */ + public function addRegularTask($className, $methodName) { + \OCP\Backgroundjob::addRegularTask($className, $methodName); + } + + /** + * Tells ownCloud to include a template in the admin overview + * @param string $mainPath the path to the main php file without the php + * suffix, relative to your apps directory! not the template directory + * @param string $appName the name of the app, defaults to the current one + */ + public function registerAdmin($mainPath, $appName=null) { + if($appName === null){ + $appName = $this->appName; + } + + \OCP\App::registerAdmin($appName, $mainPath); + } + + /** + * Do a user login + * @param string $user the username + * @param string $password the password + * @return bool true if successful + */ + public function login($user, $password) { + return \OC_User::login($user, $password); + } + + /** + * @brief Loggs the user out including all the session data + * Logout, destroys session + */ + public function logout() { + return \OCP\User::logout(); + } + + /** + * get the filesystem info + * + * @param string $path + * @return array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public function getFileInfo($path) { + return \OC\Files\Filesystem::getFileInfo($path); + } + + /** + * get the view + * + * @return OC\Files\View instance + */ + public function getView() { + return \OC\Files\Filesystem::getView(); + } +} diff --git a/lib/appframework/dependencyinjection/dicontainer.php b/lib/appframework/dependencyinjection/dicontainer.php new file mode 100644 index 0000000000..34f64e72cb --- /dev/null +++ b/lib/appframework/dependencyinjection/dicontainer.php @@ -0,0 +1,125 @@ +. + * + */ + + +namespace OC\AppFramework\DependencyInjection; + +use OC\AppFramework\Http\Http; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\Dispatcher; +use OC\AppFramework\Core\API; +use OC\AppFramework\Middleware\MiddlewareDispatcher; +use OC\AppFramework\Middleware\Http\HttpMiddleware; +use OC\AppFramework\Middleware\Security\SecurityMiddleware; +use OC\AppFramework\Utility\TimeFactory; + +// register 3rdparty autoloaders +require_once __DIR__ . '/../../../../3rdparty/Pimple/Pimple.php'; + + +/** + * This class extends Pimple (http://pimple.sensiolabs.org/) for reusability + * To use this class, extend your own container from this. Should you require it + * you can overwrite the dependencies with your own classes by simply redefining + * a dependency + */ +class DIContainer extends \Pimple { + + + /** + * Put your class dependencies in here + * @param string $appName the name of the app + */ + public function __construct($appName){ + + $this['AppName'] = $appName; + + $this['API'] = $this->share(function($c){ + return new API($c['AppName']); + }); + + /** + * Http + */ + $this['Request'] = $this->share(function($c) { + $params = json_decode(file_get_contents('php://input'), true); + $params = is_array($params) ? $params: array(); + + return new Request( + array( + 'get' => $_GET, + 'post' => $_POST, + 'files' => $_FILES, + 'server' => $_SERVER, + 'env' => $_ENV, + 'session' => $_SESSION, + 'cookies' => $_COOKIE, + 'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD'])) + ? $_SERVER['REQUEST_METHOD'] + : null, + 'params' => $params, + 'urlParams' => $c['urlParams'] + ) + ); + }); + + $this['Protocol'] = $this->share(function($c){ + if(isset($_SERVER['SERVER_PROTOCOL'])) { + return new Http($_SERVER, $_SERVER['SERVER_PROTOCOL']); + } else { + return new Http($_SERVER); + } + }); + + $this['Dispatcher'] = $this->share(function($c) { + return new Dispatcher($c['Protocol'], $c['MiddlewareDispatcher']); + }); + + + /** + * Middleware + */ + $this['SecurityMiddleware'] = $this->share(function($c){ + return new SecurityMiddleware($c['API'], $c['Request']); + }); + + $this['MiddlewareDispatcher'] = $this->share(function($c){ + $dispatcher = new MiddlewareDispatcher(); + $dispatcher->registerMiddleware($c['SecurityMiddleware']); + + return $dispatcher; + }); + + + /** + * Utilities + */ + $this['TimeFactory'] = $this->share(function($c){ + return new TimeFactory(); + }); + + + } + + +} diff --git a/lib/appframework/http/dispatcher.php b/lib/appframework/http/dispatcher.php new file mode 100644 index 0000000000..ab5644274f --- /dev/null +++ b/lib/appframework/http/dispatcher.php @@ -0,0 +1,98 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + +use \OC\AppFramework\Controller\Controller; +use \OC\AppFramework\Middleware\MiddlewareDispatcher; + + +/** + * Class to dispatch the request to the middleware disptacher + */ +class Dispatcher { + + private $middlewareDispatcher; + private $protocol; + + + /** + * @param Http $protocol the http protocol with contains all status headers + * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which + * runs the middleware + */ + public function __construct(Http $protocol, + MiddlewareDispatcher $middlewareDispatcher) { + $this->protocol = $protocol; + $this->middlewareDispatcher = $middlewareDispatcher; + } + + + /** + * Handles a request and calls the dispatcher on the controller + * @param Controller $controller the controller which will be called + * @param string $methodName the method name which will be called on + * the controller + * @return array $array[0] contains a string with the http main header, + * $array[1] contains headers in the form: $key => value, $array[2] contains + * the response output + */ + public function dispatch(Controller $controller, $methodName) { + $out = array(null, array(), null); + + try { + + $this->middlewareDispatcher->beforeController($controller, + $methodName); + $response = $controller->$methodName(); + + + // if an exception appears, the middleware checks if it can handle the + // exception and creates a response. If no response is created, it is + // assumed that theres no middleware who can handle it and the error is + // thrown again + } catch(\Exception $exception){ + $response = $this->middlewareDispatcher->afterException( + $controller, $methodName, $exception); + } + + $response = $this->middlewareDispatcher->afterController( + $controller, $methodName, $response); + + // get the output which should be printed and run the after output + // middleware to modify the response + $output = $response->render(); + $out[2] = $this->middlewareDispatcher->beforeOutput( + $controller, $methodName, $output); + + // depending on the cache object the headers need to be changed + $out[0] = $this->protocol->getStatusHeader($response->getStatus(), + $response->getLastModified(), $response->getETag()); + $out[1] = $response->getHeaders(); + + return $out; + } + + +} diff --git a/lib/appframework/http/downloadresponse.php b/lib/appframework/http/downloadresponse.php new file mode 100644 index 0000000000..5a0db325fe --- /dev/null +++ b/lib/appframework/http/downloadresponse.php @@ -0,0 +1,51 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +/** + * Prompts the user to download the a file + */ +abstract class DownloadResponse extends Response { + + private $content; + private $filename; + private $contentType; + + /** + * Creates a response that prompts the user to download the file + * @param string $filename the name that the downloaded file should have + * @param string $contentType the mimetype that the downloaded file should have + */ + public function __construct($filename, $contentType) { + $this->filename = $filename; + $this->contentType = $contentType; + + $this->addHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); + $this->addHeader('Content-Type', $contentType); + } + + +} diff --git a/lib/appframework/http/http.php b/lib/appframework/http/http.php new file mode 100644 index 0000000000..73f32d13b3 --- /dev/null +++ b/lib/appframework/http/http.php @@ -0,0 +1,208 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +class Http { + + const STATUS_CONTINUE = 100; + const STATUS_SWITCHING_PROTOCOLS = 101; + const STATUS_PROCESSING = 102; + const STATUS_OK = 200; + const STATUS_CREATED = 201; + const STATUS_ACCEPTED = 202; + const STATUS_NON_AUTHORATIVE_INFORMATION = 203; + const STATUS_NO_CONTENT = 204; + const STATUS_RESET_CONTENT = 205; + const STATUS_PARTIAL_CONTENT = 206; + const STATUS_MULTI_STATUS = 207; + const STATUS_ALREADY_REPORTED = 208; + const STATUS_IM_USED = 226; + const STATUS_MULTIPLE_CHOICES = 300; + const STATUS_MOVED_PERMANENTLY = 301; + const STATUS_FOUND = 302; + const STATUS_SEE_OTHER = 303; + const STATUS_NOT_MODIFIED = 304; + const STATUS_USE_PROXY = 305; + const STATUS_RESERVED = 306; + const STATUS_TEMPORARY_REDIRECT = 307; + const STATUS_BAD_REQUEST = 400; + const STATUS_UNAUTHORIZED = 401; + const STATUS_PAYMENT_REQUIRED = 402; + const STATUS_FORBIDDEN = 403; + const STATUS_NOT_FOUND = 404; + const STATUS_METHOD_NOT_ALLOWED = 405; + const STATUS_NOT_ACCEPTABLE = 406; + const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407; + const STATUS_REQUEST_TIMEOUT = 408; + const STATUS_CONFLICT = 409; + const STATUS_GONE = 410; + const STATUS_LENGTH_REQUIRED = 411; + const STATUS_PRECONDITION_FAILED = 412; + const STATUS_REQUEST_ENTITY_TOO_LARGE = 413; + const STATUS_REQUEST_URI_TOO_LONG = 414; + const STATUS_UNSUPPORTED_MEDIA_TYPE = 415; + const STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416; + const STATUS_EXPECTATION_FAILED = 417; + const STATUS_IM_A_TEAPOT = 418; + const STATUS_UNPROCESSABLE_ENTITY = 422; + const STATUS_LOCKED = 423; + const STATUS_FAILED_DEPENDENCY = 424; + const STATUS_UPGRADE_REQUIRED = 426; + const STATUS_PRECONDITION_REQUIRED = 428; + const STATUS_TOO_MANY_REQUESTS = 429; + const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + const STATUS_INTERNAL_SERVER_ERROR = 500; + const STATUS_NOT_IMPLEMENTED = 501; + const STATUS_BAD_GATEWAY = 502; + const STATUS_SERVICE_UNAVAILABLE = 503; + const STATUS_GATEWAY_TIMEOUT = 504; + const STATUS_HTTP_VERSION_NOT_SUPPORTED = 505; + const STATUS_VARIANT_ALSO_NEGOTIATES = 506; + const STATUS_INSUFFICIENT_STORAGE = 507; + const STATUS_LOOP_DETECTED = 508; + const STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509; + const STATUS_NOT_EXTENDED = 510; + const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511; + + private $server; + private $protocolVersion; + protected $headers; + + /** + * @param $_SERVER $server + * @param string $protocolVersion the http version to use defaults to HTTP/1.1 + */ + public function __construct($server, $protocolVersion='HTTP/1.1') { + $this->server = $server; + $this->protocolVersion = $protocolVersion; + + $this->headers = array( + self::STATUS_CONTINUE => 'Continue', + self::STATUS_SWITCHING_PROTOCOLS => 'Switching Protocols', + self::STATUS_PROCESSING => 'Processing', + self::STATUS_OK => 'OK', + self::STATUS_CREATED => 'Created', + self::STATUS_ACCEPTED => 'Accepted', + self::STATUS_NON_AUTHORATIVE_INFORMATION => 'Non-Authorative Information', + self::STATUS_NO_CONTENT => 'No Content', + self::STATUS_RESET_CONTENT => 'Reset Content', + self::STATUS_PARTIAL_CONTENT => 'Partial Content', + self::STATUS_MULTI_STATUS => 'Multi-Status', // RFC 4918 + self::STATUS_ALREADY_REPORTED => 'Already Reported', // RFC 5842 + self::STATUS_IM_USED => 'IM Used', // RFC 3229 + self::STATUS_MULTIPLE_CHOICES => 'Multiple Choices', + self::STATUS_MOVED_PERMANENTLY => 'Moved Permanently', + self::STATUS_FOUND => 'Found', + self::STATUS_SEE_OTHER => 'See Other', + self::STATUS_NOT_MODIFIED => 'Not Modified', + self::STATUS_USE_PROXY => 'Use Proxy', + self::STATUS_RESERVED => 'Reserved', + self::STATUS_TEMPORARY_REDIRECT => 'Temporary Redirect', + self::STATUS_BAD_REQUEST => 'Bad request', + self::STATUS_UNAUTHORIZED => 'Unauthorized', + self::STATUS_PAYMENT_REQUIRED => 'Payment Required', + self::STATUS_FORBIDDEN => 'Forbidden', + self::STATUS_NOT_FOUND => 'Not Found', + self::STATUS_METHOD_NOT_ALLOWED => 'Method Not Allowed', + self::STATUS_NOT_ACCEPTABLE => 'Not Acceptable', + self::STATUS_PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', + self::STATUS_REQUEST_TIMEOUT => 'Request Timeout', + self::STATUS_CONFLICT => 'Conflict', + self::STATUS_GONE => 'Gone', + self::STATUS_LENGTH_REQUIRED => 'Length Required', + self::STATUS_PRECONDITION_FAILED => 'Precondition failed', + self::STATUS_REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large', + self::STATUS_REQUEST_URI_TOO_LONG => 'Request-URI Too Long', + self::STATUS_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', + self::STATUS_REQUEST_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable', + self::STATUS_EXPECTATION_FAILED => 'Expectation Failed', + self::STATUS_IM_A_TEAPOT => 'I\'m a teapot', // RFC 2324 + self::STATUS_UNPROCESSABLE_ENTITY => 'Unprocessable Entity', // RFC 4918 + self::STATUS_LOCKED => 'Locked', // RFC 4918 + self::STATUS_FAILED_DEPENDENCY => 'Failed Dependency', // RFC 4918 + self::STATUS_UPGRADE_REQUIRED => 'Upgrade required', + self::STATUS_PRECONDITION_REQUIRED => 'Precondition required', // draft-nottingham-http-new-status + self::STATUS_TOO_MANY_REQUESTS => 'Too Many Requests', // draft-nottingham-http-new-status + self::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', // draft-nottingham-http-new-status + self::STATUS_INTERNAL_SERVER_ERROR => 'Internal Server Error', + self::STATUS_NOT_IMPLEMENTED => 'Not Implemented', + self::STATUS_BAD_GATEWAY => 'Bad Gateway', + self::STATUS_SERVICE_UNAVAILABLE => 'Service Unavailable', + self::STATUS_GATEWAY_TIMEOUT => 'Gateway Timeout', + self::STATUS_HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported', + self::STATUS_VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates', + self::STATUS_INSUFFICIENT_STORAGE => 'Insufficient Storage', // RFC 4918 + self::STATUS_LOOP_DETECTED => 'Loop Detected', // RFC 5842 + self::STATUS_BANDWIDTH_LIMIT_EXCEEDED => 'Bandwidth Limit Exceeded', // non-standard + self::STATUS_NOT_EXTENDED => 'Not extended', + self::STATUS_NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required', // draft-nottingham-http-new-status + ); + } + + + /** + * Gets the correct header + * @param Http::CONSTANT $status the constant from the Http class + * @param \DateTime $lastModified formatted last modified date + * @param string $Etag the etag + */ + public function getStatusHeader($status, \DateTime $lastModified=null, + $ETag=null) { + + if(!is_null($lastModified)) { + $lastModified = $lastModified->format(\DateTime::RFC2822); + } + + // if etag or lastmodified have not changed, return a not modified + if ((isset($this->server['HTTP_IF_NONE_MATCH']) + && trim($this->server['HTTP_IF_NONE_MATCH']) === $ETag) + + || + + (isset($this->server['HTTP_IF_MODIFIED_SINCE']) + && trim($this->server['HTTP_IF_MODIFIED_SINCE']) === + $lastModified)) { + + $status = self::STATUS_NOT_MODIFIED; + } + + // we have one change currently for the http 1.0 header that differs + // from 1.1: STATUS_TEMPORARY_REDIRECT should be STATUS_FOUND + // if this differs any more, we want to create childclasses for this + if($status === self::STATUS_TEMPORARY_REDIRECT + && $this->protocolVersion === 'HTTP/1.0') { + + $status = self::STATUS_FOUND; + } + + return $this->protocolVersion . ' ' . $status . ' ' . + $this->headers[$status]; + } + + +} + + diff --git a/lib/appframework/http/jsonresponse.php b/lib/appframework/http/jsonresponse.php new file mode 100644 index 0000000000..750f8a2ad1 --- /dev/null +++ b/lib/appframework/http/jsonresponse.php @@ -0,0 +1,74 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +/** + * A renderer for JSON calls + */ +class JSONResponse extends Response { + + protected $data; + + + /** + * @param array|object $data the object or array that should be transformed + * @param int $statusCode the Http status code, defaults to 200 + */ + public function __construct($data=array(), $statusCode=Http::STATUS_OK) { + $this->data = $data; + $this->setStatus($statusCode); + $this->addHeader('X-Content-Type-Options', 'nosniff'); + $this->addHeader('Content-type', 'application/json; charset=utf-8'); + } + + + /** + * Returns the rendered json + * @return string the rendered json + */ + public function render(){ + return json_encode($this->data); + } + + /** + * Sets values in the data json array + * @param array|object $params an array or object which will be transformed + * to JSON + */ + public function setData($data){ + $this->data = $data; + } + + + /** + * Used to get the set parameters + * @return array the data + */ + public function getData(){ + return $this->data; + } + +} diff --git a/lib/appframework/http/redirectresponse.php b/lib/appframework/http/redirectresponse.php new file mode 100644 index 0000000000..727e0fb642 --- /dev/null +++ b/lib/appframework/http/redirectresponse.php @@ -0,0 +1,54 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +/** + * Redirects to a different URL + */ +class RedirectResponse extends Response { + + private $redirectURL; + + /** + * Creates a response that redirects to a url + * @param string $redirectURL the url to redirect to + */ + public function __construct($redirectURL) { + $this->redirectURL = $redirectURL; + $this->setStatus(Http::STATUS_TEMPORARY_REDIRECT); + $this->addHeader('Location', $redirectURL); + } + + + /** + * @return string the url to redirect + */ + public function getRedirectURL() { + return $this->redirectURL; + } + + +} diff --git a/lib/appframework/http/request.php b/lib/appframework/http/request.php new file mode 100644 index 0000000000..7d024c8605 --- /dev/null +++ b/lib/appframework/http/request.php @@ -0,0 +1,217 @@ +. + * + */ + +namespace OC\AppFramework\Http; + +/** + * Class for accessing variables in the request. + * This class provides an immutable object with request variables. + */ + +class Request implements \ArrayAccess, \Countable { + + protected $items = array(); + protected $allowedKeys = array( + 'get', + 'post', + 'files', + 'server', + 'env', + 'session', + 'cookies', + 'urlParams', + 'params', + 'parameters', + 'method' + ); + + /** + * @param array $vars An associative array with the following optional values: + * @param array 'params' the parsed json array + * @param array 'urlParams' the parameters which were matched from the URL + * @param array 'get' the $_GET array + * @param array 'post' the $_POST array + * @param array 'files' the $_FILES array + * @param array 'server' the $_SERVER array + * @param array 'env' the $_ENV array + * @param array 'session' the $_SESSION array + * @param array 'cookies' the $_COOKIE array + * @param string 'method' the request method (GET, POST etc) + * @see http://www.php.net/manual/en/reserved.variables.php + */ + public function __construct(array $vars=array()) { + + foreach($this->allowedKeys as $name) { + $this->items[$name] = isset($vars[$name]) + ? $vars[$name] + : array(); + } + + $this->items['parameters'] = array_merge( + $this->items['params'], + $this->items['get'], + $this->items['post'], + $this->items['urlParams'] + ); + + } + + // Countable method. + public function count() { + return count(array_keys($this->items['parameters'])); + } + + /** + * ArrayAccess methods + * + * Gives access to the combined GET, POST and urlParams arrays + * + * Examples: + * + * $var = $request['myvar']; + * + * or + * + * if(!isset($request['myvar']) { + * // Do something + * } + * + * $request['myvar'] = 'something'; // This throws an exception. + * + * @param string $offset The key to lookup + * @return string|null + */ + public function offsetExists($offset) { + return isset($this->items['parameters'][$offset]); + } + + /** + * @see offsetExists + */ + public function offsetGet($offset) { + return isset($this->items['parameters'][$offset]) + ? $this->items['parameters'][$offset] + : null; + } + + /** + * @see offsetExists + */ + public function offsetSet($offset, $value) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * @see offsetExists + */ + public function offsetUnset($offset) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + // Magic property accessors + public function __set($name, $value) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * Access request variables by method and name. + * Examples: + * + * $request->post['myvar']; // Only look for POST variables + * $request->myvar; or $request->{'myvar'}; or $request->{$myvar} + * Looks in the combined GET, POST and urlParams array. + * + * if($request->method !== 'POST') { + * throw new Exception('This function can only be invoked using POST'); + * } + * + * @param string $name The key to look for. + * @return mixed|null + */ + public function __get($name) { + switch($name) { + case 'get': + case 'post': + case 'files': + case 'server': + case 'env': + case 'session': + case 'cookies': + case 'parameters': + case 'params': + case 'urlParams': + return isset($this->items[$name]) + ? $this->items[$name] + : null; + break; + case 'method': + return $this->items['method']; + break; + default; + return isset($this[$name]) + ? $this[$name] + : null; + break; + } + } + + + public function __isset($name) { + return isset($this->items['parameters'][$name]); + } + + + public function __unset($id) { + throw new \RunTimeException('You cannot change the contents of the request object'); + } + + /** + * Returns the value for a specific http header. + * + * This method returns null if the header did not exist. + * + * @param string $name + * @return string + */ + public function getHeader($name) { + + $name = strtoupper(str_replace(array('-'),array('_'),$name)); + if (isset($this->server['HTTP_' . $name])) { + return $this->server['HTTP_' . $name]; + } + + // There's a few headers that seem to end up in the top-level + // server array. + switch($name) { + case 'CONTENT_TYPE' : + case 'CONTENT_LENGTH' : + if (isset($this->server[$name])) { + return $this->server[$name]; + } + break; + + } + + return null; + } + +} diff --git a/lib/appframework/http/response.php b/lib/appframework/http/response.php new file mode 100644 index 0000000000..50778105f2 --- /dev/null +++ b/lib/appframework/http/response.php @@ -0,0 +1,169 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +/** + * Base class for responses. Also used to just send headers + */ +class Response { + + /** + * @var array default headers + */ + private $headers = array( + 'Cache-Control' => 'no-cache, must-revalidate' + ); + + + /** + * @var string + */ + private $status = Http::STATUS_OK; + + + /** + * @var \DateTime + */ + private $lastModified; + + + /** + * @var string + */ + private $ETag; + + + /** + * Caches the response + * @param int $cacheSeconds the amount of seconds that should be cached + * if 0 then caching will be disabled + */ + public function cacheFor($cacheSeconds) { + + if($cacheSeconds > 0) { + $this->addHeader('Cache-Control', 'max-age=' . $cacheSeconds . + ', must-revalidate'); + } else { + $this->addHeader('Cache-Control', 'no-cache, must-revalidate'); + } + + } + + + /** + * Adds a new header to the response that will be called before the render + * function + * @param string $name The name of the HTTP header + * @param string $value The value, null will delete it + */ + public function addHeader($name, $value) { + if(is_null($value)) { + unset($this->headers[$name]); + } else { + $this->headers[$name] = $value; + } + } + + + /** + * Returns the set headers + * @return array the headers + */ + public function getHeaders() { + $mergeWith = array(); + + if($this->lastModified) { + $mergeWith['Last-Modified'] = + $this->lastModified->format(\DateTime::RFC2822); + } + + if($this->ETag) { + $mergeWith['ETag'] = '"' . $this->ETag . '"'; + } + + return array_merge($mergeWith, $this->headers); + } + + + /** + * By default renders no output + * @return null + */ + public function render() { + return null; + } + + + /** + * Set response status + * @param int $status a HTTP status code, see also the STATUS constants + */ + public function setStatus($status) { + $this->status = $status; + } + + + /** + * Get response status + */ + public function getStatus() { + return $this->status; + } + + + /** + * @return string the etag + */ + public function getETag() { + return $this->ETag; + } + + + /** + * @return string RFC2822 formatted last modified date + */ + public function getLastModified() { + return $this->lastModified; + } + + + /** + * @param string $ETag + */ + public function setETag($ETag) { + $this->ETag = $ETag; + } + + + /** + * @param \DateTime $lastModified + */ + public function setLastModified($lastModified) { + $this->lastModified = $lastModified; + } + + +} diff --git a/lib/appframework/http/templateresponse.php b/lib/appframework/http/templateresponse.php new file mode 100644 index 0000000000..0a32da4b1b --- /dev/null +++ b/lib/appframework/http/templateresponse.php @@ -0,0 +1,126 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + +use OC\AppFramework\Core\API; + + +/** + * Response for a normal template + */ +class TemplateResponse extends Response { + + protected $templateName; + protected $params; + protected $api; + protected $renderAs; + protected $appName; + + /** + * @param API $api an API instance + * @param string $templateName the name of the template + * @param string $appName optional if you want to include a template from + * a different app + */ + public function __construct(API $api, $templateName, $appName=null) { + $this->templateName = $templateName; + $this->appName = $appName; + $this->api = $api; + $this->params = array(); + $this->renderAs = 'user'; + } + + + /** + * Sets template parameters + * @param array $params an array with key => value structure which sets template + * variables + */ + public function setParams(array $params){ + $this->params = $params; + } + + + /** + * Used for accessing the set parameters + * @return array the params + */ + public function getParams(){ + return $this->params; + } + + + /** + * Used for accessing the name of the set template + * @return string the name of the used template + */ + public function getTemplateName(){ + return $this->templateName; + } + + + /** + * Sets the template page + * @param string $renderAs admin, user or blank. Admin also prints the admin + * settings header and footer, user renders the normal + * normal page including footer and header and blank + * just renders the plain template + */ + public function renderAs($renderAs){ + $this->renderAs = $renderAs; + } + + + /** + * Returns the set renderAs + * @return string the renderAs value + */ + public function getRenderAs(){ + return $this->renderAs; + } + + + /** + * Returns the rendered html + * @return string the rendered html + */ + public function render(){ + + if($this->appName !== null){ + $appName = $this->appName; + } else { + $appName = $this->api->getAppName(); + } + + $template = $this->api->getTemplate($this->templateName, $this->renderAs, $appName); + + foreach($this->params as $key => $value){ + $template->assign($key, $value); + } + + return $template->fetchPage(); + } + +} diff --git a/lib/appframework/middleware/middleware.php b/lib/appframework/middleware/middleware.php new file mode 100644 index 0000000000..4df8849046 --- /dev/null +++ b/lib/appframework/middleware/middleware.php @@ -0,0 +1,100 @@ +. + * + */ + + +namespace OC\AppFramework\Middleware; + +use OC\AppFramework\Http\Response; + + +/** + * Middleware is used to provide hooks before or after controller methods and + * deal with possible exceptions raised in the controller methods. + * They're modeled after Django's middleware system: + * https://docs.djangoproject.com/en/dev/topics/http/middleware/ + */ +abstract class Middleware { + + + /** + * This is being run in normal order before the controller is being + * called which allows several modifications and checks + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + */ + public function beforeController($controller, $methodName){ + + } + + + /** + * This is being run when either the beforeController method or the + * controller method itself is throwing an exception. The middleware is + * asked in reverse order to handle the exception and to return a response. + * If the response is null, it is assumed that the exception could not be + * handled and the error will be thrown again + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @throws \Exception the passed in exception if it cant handle it + * @return Response a Response object in case that the exception was handled + */ + public function afterException($controller, $methodName, \Exception $exception){ + throw $exception; + } + + + /** + * This is being run after a successful controllermethod call and allows + * the manipulation of a Response object. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param Response $response the generated response from the controller + * @return Response a Response object + */ + public function afterController($controller, $methodName, Response $response){ + return $response; + } + + + /** + * This is being run after the response object has been rendered and + * allows the manipulation of the output. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param string $output the generated output from a response + * @return string the output that should be printed + */ + public function beforeOutput($controller, $methodName, $output){ + return $output; + } + +} diff --git a/lib/appframework/middleware/middlewaredispatcher.php b/lib/appframework/middleware/middlewaredispatcher.php new file mode 100644 index 0000000000..c2d16134dc --- /dev/null +++ b/lib/appframework/middleware/middlewaredispatcher.php @@ -0,0 +1,159 @@ +. + * + */ + + +namespace OC\AppFramework\Middleware; + +use OC\AppFramework\Controller\Controller; +use OC\AppFramework\Http\Response; + + +/** + * This class is used to store and run all the middleware in correct order + */ +class MiddlewareDispatcher { + + /** + * @var array array containing all the middlewares + */ + private $middlewares; + + /** + * @var int counter which tells us what middlware was executed once an + * exception occurs + */ + private $middlewareCounter; + + + /** + * Constructor + */ + public function __construct(){ + $this->middlewares = array(); + $this->middlewareCounter = 0; + } + + + /** + * Adds a new middleware + * @param Middleware $middleware the middleware which will be added + */ + public function registerMiddleware(Middleware $middleWare){ + array_push($this->middlewares, $middleWare); + } + + + /** + * returns an array with all middleware elements + * @return array the middlewares + */ + public function getMiddlewares(){ + return $this->middlewares; + } + + + /** + * This is being run in normal order before the controller is being + * called which allows several modifications and checks + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + */ + public function beforeController(Controller $controller, $methodName){ + // we need to count so that we know which middlewares we have to ask in + // case theres an exception + for($i=0; $imiddlewares); $i++){ + $this->middlewareCounter++; + $middleware = $this->middlewares[$i]; + $middleware->beforeController($controller, $methodName); + } + } + + + /** + * This is being run when either the beforeController method or the + * controller method itself is throwing an exception. The middleware is asked + * in reverse order to handle the exception and to return a response. + * If the response is null, it is assumed that the exception could not be + * handled and the error will be thrown again + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @return Response a Response object if the middleware can handle the + * exception + * @throws \Exception the passed in exception if it cant handle it + */ + public function afterException(Controller $controller, $methodName, \Exception $exception){ + for($i=$this->middlewareCounter-1; $i>=0; $i--){ + $middleware = $this->middlewares[$i]; + try { + return $middleware->afterException($controller, $methodName, $exception); + } catch(\Exception $exception){ + continue; + } + } + throw $exception; + } + + + /** + * This is being run after a successful controllermethod call and allows + * the manipulation of a Response object. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param Response $response the generated response from the controller + * @return Response a Response object + */ + public function afterController(Controller $controller, $methodName, Response $response){ + for($i=count($this->middlewares)-1; $i>=0; $i--){ + $middleware = $this->middlewares[$i]; + $response = $middleware->afterController($controller, $methodName, $response); + } + return $response; + } + + + /** + * This is being run after the response object has been rendered and + * allows the manipulation of the output. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param string $output the generated output from a response + * @return string the output that should be printed + */ + public function beforeOutput(Controller $controller, $methodName, $output){ + for($i=count($this->middlewares)-1; $i>=0; $i--){ + $middleware = $this->middlewares[$i]; + $output = $middleware->beforeOutput($controller, $methodName, $output); + } + return $output; + } + +} diff --git a/lib/appframework/middleware/security/securityexception.php b/lib/appframework/middleware/security/securityexception.php new file mode 100644 index 0000000000..b32a2769ff --- /dev/null +++ b/lib/appframework/middleware/security/securityexception.php @@ -0,0 +1,41 @@ +. + * + */ + + +namespace OC\AppFramework\Middleware\Security; + + +/** + * Thrown when the security middleware encounters a security problem + */ +class SecurityException extends \Exception { + + /** + * @param string $msg the security error message + * @param bool $ajax true if it resulted because of an ajax request + */ + public function __construct($msg, $code = 0) { + parent::__construct($msg, $code); + } + +} diff --git a/lib/appframework/middleware/security/securitymiddleware.php b/lib/appframework/middleware/security/securitymiddleware.php new file mode 100644 index 0000000000..7a715f309a --- /dev/null +++ b/lib/appframework/middleware/security/securitymiddleware.php @@ -0,0 +1,141 @@ +. + * + */ + + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Controller\Controller; +use OC\AppFramework\Http\Http; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\Response; +use OC\AppFramework\Http\JSONResponse; +use OC\AppFramework\Http\RedirectResponse; +use OC\AppFramework\Utility\MethodAnnotationReader; +use OC\AppFramework\Middleware\Middleware; +use OC\AppFramework\Core\API; + + +/** + * Used to do all the authentication and checking stuff for a controller method + * It reads out the annotations of a controller method and checks which if + * security things should be checked and also handles errors in case a security + * check fails + */ +class SecurityMiddleware extends Middleware { + + private $api; + + /** + * @var \OC\AppFramework\Http\Request + */ + private $request; + + /** + * @param API $api an instance of the api + */ + public function __construct(API $api, Request $request){ + $this->api = $api; + $this->request = $request; + } + + + /** + * This runs all the security checks before a method call. The + * security checks are determined by inspecting the controller method + * annotations + * @param string/Controller $controller the controllername or string + * @param string $methodName the name of the method + * @throws SecurityException when a security check fails + */ + public function beforeController($controller, $methodName){ + + // get annotations from comments + $annotationReader = new MethodAnnotationReader($controller, $methodName); + + // this will set the current navigation entry of the app, use this only + // for normal HTML requests and not for AJAX requests + $this->api->activateNavigationEntry(); + + // security checks + if(!$annotationReader->hasAnnotation('IsLoggedInExemption')) { + if(!$this->api->isLoggedIn()) { + throw new SecurityException('Current user is not logged in', Http::STATUS_UNAUTHORIZED); + } + } + + if(!$annotationReader->hasAnnotation('IsAdminExemption')) { + if(!$this->api->isAdminUser($this->api->getUserId())) { + throw new SecurityException('Logged in user must be an admin', Http::STATUS_FORBIDDEN); + } + } + + if(!$annotationReader->hasAnnotation('IsSubAdminExemption')) { + if(!$this->api->isSubAdminUser($this->api->getUserId())) { + throw new SecurityException('Logged in user must be a subadmin', Http::STATUS_FORBIDDEN); + } + } + + if(!$annotationReader->hasAnnotation('CSRFExemption')) { + if(!$this->api->passesCSRFCheck()) { + throw new SecurityException('CSRF check failed', Http::STATUS_PRECONDITION_FAILED); + } + } + + } + + + /** + * If an SecurityException is being caught, ajax requests return a JSON error + * response and non ajax requests redirect to the index + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @throws \Exception the passed in exception if it cant handle it + * @return Response a Response object or null in case that the exception could not be handled + */ + public function afterException($controller, $methodName, \Exception $exception){ + if($exception instanceof SecurityException){ + + if (stripos($this->request->getHeader('Accept'),'html')===false) { + + $response = new JSONResponse( + array('message' => $exception->getMessage()), + $exception->getCode() + ); + $this->api->log($exception->getMessage(), 'debug'); + } else { + + $url = $this->api->linkToAbsolute('index.php', ''); // TODO: replace with link to route + $response = new RedirectResponse($url); + $this->api->log($exception->getMessage(), 'debug'); + } + + return $response; + + } + + throw $exception; + } + +} diff --git a/lib/appframework/routing/routeactionhandler.php b/lib/appframework/routing/routeactionhandler.php new file mode 100644 index 0000000000..7fb56f14ea --- /dev/null +++ b/lib/appframework/routing/routeactionhandler.php @@ -0,0 +1,42 @@ +. + * + */ + +namespace OC\AppFramework\routing; + +use \OC\AppFramework\App; +use \OC\AppFramework\DependencyInjection\DIContainer; + +class RouteActionHandler { + private $controllerName; + private $actionName; + private $container; + + public function __construct(DIContainer $container, $controllerName, $actionName) { + $this->controllerName = $controllerName; + $this->actionName = $actionName; + $this->container = $container; + } + + public function __invoke($params) { + App::main($this->controllerName, $this->actionName, $params, $this->container); + } +} diff --git a/lib/appframework/routing/routeconfig.php b/lib/appframework/routing/routeconfig.php new file mode 100644 index 0000000000..53ab11bf2f --- /dev/null +++ b/lib/appframework/routing/routeconfig.php @@ -0,0 +1,186 @@ +. + * + */ + +namespace OC\AppFramework\routing; + +use OC\AppFramework\DependencyInjection\DIContainer; + +/** + * Class RouteConfig + * @package OC\AppFramework\routing + */ +class RouteConfig { + private $container; + private $router; + private $routes; + private $appName; + + /** + * @param \OC\AppFramework\DependencyInjection\DIContainer $container + * @param \OC_Router $router + * @param string $pathToYml + * @internal param $appName + */ + public function __construct(DIContainer $container, \OC_Router $router, $routes) { + $this->routes = $routes; + $this->container = $container; + $this->router = $router; + $this->appName = $container['AppName']; + } + + /** + * The routes and resource will be registered to the \OC_Router + */ + public function register() { + + // parse simple + $this->processSimpleRoutes($this->routes); + + // parse resources + $this->processResources($this->routes); + } + + /** + * Creates one route base on the give configuration + * @param $routes + * @throws \UnexpectedValueException + */ + private function processSimpleRoutes($routes) + { + $simpleRoutes = isset($routes['routes']) ? $routes['routes'] : array(); + foreach ($simpleRoutes as $simpleRoute) { + $name = $simpleRoute['name']; + $url = $simpleRoute['url']; + $verb = isset($simpleRoute['verb']) ? strtoupper($simpleRoute['verb']) : 'GET'; + + $split = explode('#', $name, 2); + if (count($split) != 2) { + throw new \UnexpectedValueException('Invalid route name'); + } + $controller = $split[0]; + $action = $split[1]; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($action); + + // register the route + $handler = new RouteActionHandler($this->container, $controllerName, $actionName); + $this->router->create($this->appName.'.'.$controller.'.'.$action, $url)->method($verb)->action($handler); + } + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - new + * - create + * - update + * - destroy + * + * @param $routes + */ + private function processResources($routes) + { + // declaration of all restful actions + $actions = array( + array('name' => 'index', 'verb' => 'GET', 'on-collection' => true), + array('name' => 'show', 'verb' => 'GET'), + array('name' => 'create', 'verb' => 'POST', 'on-collection' => true), + array('name' => 'update', 'verb' => 'PUT'), + array('name' => 'destroy', 'verb' => 'DELETE'), + ); + + $resources = isset($routes['resources']) ? $routes['resources'] : array(); + foreach ($resources as $resource => $config) { + + // the url parameter used as id to the resource + $resourceId = $this->buildResourceId($resource); + foreach($actions as $action) { + $url = $config['url']; + $method = $action['name']; + $verb = isset($action['verb']) ? strtoupper($action['verb']) : 'GET'; + $collectionAction = isset($action['on-collection']) ? $action['on-collection'] : false; + if (!$collectionAction) { + $url = $url . '/' . $resourceId; + } + if (isset($action['url-postfix'])) { + $url = $url . '/' . $action['url-postfix']; + } + + $controller = $resource; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($method); + + $routeName = $this->appName . '.' . strtolower($resource) . '.' . strtolower($method); + + $this->router->create($routeName, $url)->method($verb)->action( + new RouteActionHandler($this->container, $controllerName, $actionName) + ); + } + } + } + + /** + * Based on a given route name the controller name is generated + * @param $controller + * @return string + */ + private function buildControllerName($controller) + { + return $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; + } + + /** + * Based on the action part of the route name the controller method name is generated + * @param $action + * @return string + */ + private function buildActionName($action) { + return $this->underScoreToCamelCase($action); + } + + /** + * Generates the id used in the url part o the route url + * @param $resource + * @return string + */ + private function buildResourceId($resource) { + return '{'.$this->underScoreToCamelCase(rtrim($resource, 's')).'Id}'; + } + + /** + * Underscored strings are converted to camel case strings + * @param $str string + * @return string + */ + private function underScoreToCamelCase($str) { + $pattern = "/_[a-z]?/"; + return preg_replace_callback( + $pattern, + function ($matches) { + return strtoupper(ltrim($matches[0], "_")); + }, + $str); + } +} diff --git a/lib/appframework/utility/methodannotationreader.php b/lib/appframework/utility/methodannotationreader.php new file mode 100644 index 0000000000..42060a0852 --- /dev/null +++ b/lib/appframework/utility/methodannotationreader.php @@ -0,0 +1,61 @@ +. + * + */ + + +namespace OC\AppFramework\Utility; + + +/** + * Reads and parses annotations from doc comments + */ +class MethodAnnotationReader { + + private $annotations; + + /** + * @param object $object an object or classname + * @param string $method the method which we want to inspect for annotations + */ + public function __construct($object, $method){ + $this->annotations = array(); + + $reflection = new \ReflectionMethod($object, $method); + $docs = $reflection->getDocComment(); + + // extract everything prefixed by @ and first letter uppercase + preg_match_all('/@([A-Z]\w+)/', $docs, $matches); + $this->annotations = $matches[1]; + } + + + /** + * Check if a method contains an annotation + * @param string $name the name of the annotation + * @return bool true if the annotation is found + */ + public function hasAnnotation($name){ + return in_array($name, $this->annotations); + } + + +} diff --git a/lib/appframework/utility/timefactory.php b/lib/appframework/utility/timefactory.php new file mode 100644 index 0000000000..2c3dd6cf5e --- /dev/null +++ b/lib/appframework/utility/timefactory.php @@ -0,0 +1,42 @@ +. + * + */ + + +namespace OC\AppFramework\Utility; + + +/** + * Needed to mock calls to time() + */ +class TimeFactory { + + + /** + * @return int the result of a call to time() + */ + public function getTime() { + return time(); + } + + +} diff --git a/tests/lib/appframework/AppTest.php b/tests/lib/appframework/AppTest.php new file mode 100644 index 0000000000..000094d07c --- /dev/null +++ b/tests/lib/appframework/AppTest.php @@ -0,0 +1,107 @@ +. + * + */ + + +namespace OC\AppFramework; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Core\API; +use OC\AppFramework\Middleware\MiddlewareDispatcher; + +// FIXME: loading pimpl correctly from 3rdparty repo +require_once __DIR__ . '/../../../../3rdparty/Pimple/Pimple.php'; +require_once __DIR__ . "/classloader.php"; + + +class AppTest extends \PHPUnit_Framework_TestCase { + + private $container; + private $api; + private $controller; + private $dispatcher; + private $params; + private $headers; + private $output; + private $controllerName; + private $controllerMethod; + + protected function setUp() { + $this->container = new \Pimple(); + $this->controller = $this->getMockBuilder( + 'OC\AppFramework\Controller\Controller') + ->disableOriginalConstructor() + ->getMock(); + $this->dispatcher = $this->getMockBuilder( + 'OC\AppFramework\Http\Dispatcher') + ->disableOriginalConstructor() + ->getMock(); + + + $this->headers = array('key' => 'value'); + $this->output = 'hi'; + $this->controllerName = 'Controller'; + $this->controllerMethod = 'method'; + + $this->container[$this->controllerName] = $this->controller; + $this->container['Dispatcher'] = $this->dispatcher; + } + + + public function testControllerNameAndMethodAreBeingPassed(){ + $return = array(null, array(), null); + $this->dispatcher->expects($this->once()) + ->method('dispatch') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod)) + ->will($this->returnValue($return)); + + $this->expectOutputString(''); + + App::main($this->controllerName, $this->controllerMethod, array(), + $this->container); + } + + + /* + FIXME: this complains about shit headers which are already sent because + of the content length. Would be cool if someone could fix this + + public function testOutputIsPrinted(){ + $return = array(null, array(), $this->output); + $this->dispatcher->expects($this->once()) + ->method('dispatch') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod)) + ->will($this->returnValue($return)); + + $this->expectOutputString($this->output); + + App::main($this->controllerName, $this->controllerMethod, array(), + $this->container); + } + */ + + // FIXME: if someone manages to test the headers output, I'd be grateful + + +} diff --git a/tests/lib/appframework/classloader.php b/tests/lib/appframework/classloader.php new file mode 100644 index 0000000000..ae485e67b2 --- /dev/null +++ b/tests/lib/appframework/classloader.php @@ -0,0 +1,45 @@ +. + * + */ + +// to execute without ownCloud, we need to create our own class loader +spl_autoload_register(function ($className){ + if (strpos($className, 'OC\\AppFramework') === 0) { + $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); + $relPath = __DIR__ . '/../../../lib/' . $path; + + if(file_exists($relPath)){ + require_once $relPath; + } + } + + // FIXME: this will most probably not work anymore + if (strpos($className, 'OCA\\') === 0) { + + $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); + $relPath = __DIR__ . '/../..' . $path; + + if(file_exists($relPath)){ + require_once $relPath; + } + } +}); diff --git a/tests/lib/appframework/controller/ControllerTest.php b/tests/lib/appframework/controller/ControllerTest.php new file mode 100644 index 0000000000..d8357c2a68 --- /dev/null +++ b/tests/lib/appframework/controller/ControllerTest.php @@ -0,0 +1,161 @@ +. + * + */ + + +namespace Test\AppFramework\Controller; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\JSONResponse; +use OC\AppFramework\Http\TemplateResponse; +use OC\AppFramework\Controller\Controller; + + +require_once(__DIR__ . "/../classloader.php"); + + +class ChildController extends Controller {}; + +class ControllerTest extends \PHPUnit_Framework_TestCase { + + /** + * @var Controller + */ + private $controller; + private $api; + + protected function setUp(){ + $request = new Request( + array( + 'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'), + 'post' => array('name' => 'Jane Doe', 'nickname' => 'Janey'), + 'urlParams' => array('name' => 'Johnny Weissmüller'), + 'files' => array('file' => 'filevalue'), + 'env' => array('PATH' => 'daheim'), + 'session' => array('sezession' => 'kein'), + 'method' => 'hi', + ) + ); + + $this->api = $this->getMock('OC\AppFramework\Core\API', + array('getAppName'), array('test')); + $this->api->expects($this->any()) + ->method('getAppName') + ->will($this->returnValue('apptemplate_advanced')); + + $this->controller = new ChildController($this->api, $request); + } + + + public function testParamsGet(){ + $this->assertEquals('Johnny Weissmüller', $this->controller->params('name', 'Tarzan')); + } + + + public function testParamsGetDefault(){ + $this->assertEquals('Tarzan', $this->controller->params('Ape Man', 'Tarzan')); + } + + + public function testParamsFile(){ + $this->assertEquals('filevalue', $this->controller->params('file', 'filevalue')); + } + + + public function testGetUploadedFile(){ + $this->assertEquals('filevalue', $this->controller->getUploadedFile('file')); + } + + + + public function testGetUploadedFileDefault(){ + $this->assertEquals('default', $this->controller->params('files', 'default')); + } + + + public function testGetParams(){ + $params = array( + 'name' => 'Johnny Weissmüller', + 'nickname' => 'Janey', + ); + + $this->assertEquals($params, $this->controller->getParams()); + } + + + public function testRender(){ + $this->assertTrue($this->controller->render('') instanceof TemplateResponse); + } + + + public function testSetParams(){ + $params = array('john' => 'foo'); + $response = $this->controller->render('home', $params); + + $this->assertEquals($params, $response->getParams()); + } + + + public function testRenderRenderAs(){ + $ocTpl = $this->getMock('Template', array('fetchPage')); + $ocTpl->expects($this->once()) + ->method('fetchPage'); + + $api = $this->getMock('OC\AppFramework\Core\API', + array('getAppName', 'getTemplate'), array('app')); + $api->expects($this->any()) + ->method('getAppName') + ->will($this->returnValue('app')); + $api->expects($this->once()) + ->method('getTemplate') + ->with($this->equalTo('home'), $this->equalTo('admin'), $this->equalTo('app')) + ->will($this->returnValue($ocTpl)); + + $this->controller = new ChildController($api, new Request()); + $this->controller->render('home', array(), 'admin')->render(); + } + + + public function testRenderHeaders(){ + $headers = array('one', 'two'); + $response = $this->controller->render('', array(), '', $headers); + + $this->assertTrue(in_array($headers[0], $response->getHeaders())); + $this->assertTrue(in_array($headers[1], $response->getHeaders())); + } + + + public function testGetRequestMethod(){ + $this->assertEquals('hi', $this->controller->method()); + } + + + public function testGetEnvVariable(){ + $this->assertEquals('daheim', $this->controller->env('PATH')); + } + + public function testGetSessionVariable(){ + $this->assertEquals('kein', $this->controller->session('sezession')); + } + + +} diff --git a/tests/lib/appframework/dependencyinjection/DIContainerTest.php b/tests/lib/appframework/dependencyinjection/DIContainerTest.php new file mode 100644 index 0000000000..ce346f0a76 --- /dev/null +++ b/tests/lib/appframework/dependencyinjection/DIContainerTest.php @@ -0,0 +1,98 @@ +. + * + */ + + +namespace OC\AppFramework\DependencyInjection; + +use \OC\AppFramework\Http\Request; + + +require_once(__DIR__ . "/../classloader.php"); + + +class DIContainerTest extends \PHPUnit_Framework_TestCase { + + private $container; + + protected function setUp(){ + $this->container = new DIContainer('name'); + $this->api = $this->getMock('OC\AppFramework\Core\API', array('getTrans'), array('hi')); + } + + private function exchangeAPI(){ + $this->api->expects($this->any()) + ->method('getTrans') + ->will($this->returnValue('yo')); + $this->container['API'] = $this->api; + } + + public function testProvidesAPI(){ + $this->assertTrue(isset($this->container['API'])); + } + + + public function testProvidesRequest(){ + $this->assertTrue(isset($this->container['Request'])); + } + + + public function testProvidesSecurityMiddleware(){ + $this->assertTrue(isset($this->container['SecurityMiddleware'])); + } + + + public function testProvidesMiddlewareDispatcher(){ + $this->assertTrue(isset($this->container['MiddlewareDispatcher'])); + } + + + public function testProvidesAppName(){ + $this->assertTrue(isset($this->container['AppName'])); + } + + + public function testAppNameIsSetCorrectly(){ + $this->assertEquals('name', $this->container['AppName']); + } + + + public function testMiddlewareDispatcherIncludesSecurityMiddleware(){ + $this->container['Request'] = new Request(); + $security = $this->container['SecurityMiddleware']; + $dispatcher = $this->container['MiddlewareDispatcher']; + + $this->assertContains($security, $dispatcher->getMiddlewares()); + } + + + public function testMiddlewareDispatcherDoesNotIncludeTwigWhenTplDirectoryNotSet(){ + $this->container['Request'] = new Request(); + $this->exchangeAPI(); + $dispatcher = $this->container['MiddlewareDispatcher']; + + $this->assertEquals(1, count($dispatcher->getMiddlewares())); + } + +} diff --git a/tests/lib/appframework/http/DispatcherTest.php b/tests/lib/appframework/http/DispatcherTest.php new file mode 100644 index 0000000000..2e3db11050 --- /dev/null +++ b/tests/lib/appframework/http/DispatcherTest.php @@ -0,0 +1,218 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + +use OC\AppFramework\Core\API; +use OC\AppFramework\Middleware\MiddlewareDispatcher; + +require_once(__DIR__ . "/../classloader.php"); + + +class DispatcherTest extends \PHPUnit_Framework_TestCase { + + + private $middlewareDispatcher; + private $dispatcher; + private $controllerMethod; + private $response; + private $lastModified; + private $etag; + private $http; + + protected function setUp() { + $this->controllerMethod = 'test'; + + $api = $this->getMockBuilder( + '\OC\AppFramework\Core\API') + ->disableOriginalConstructor() + ->getMock(); + $request = $this->getMockBuilder( + '\OC\AppFramework\Http\Request') + ->disableOriginalConstructor() + ->getMock(); + $this->http = $this->getMockBuilder( + '\OC\AppFramework\Http\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->middlewareDispatcher = $this->getMockBuilder( + '\OC\AppFramework\Middleware\MiddlewareDispatcher') + ->disableOriginalConstructor() + ->getMock(); + $this->controller = $this->getMock( + '\OC\AppFramework\Controller\Controller', + array($this->controllerMethod), array($api, $request)); + + $this->dispatcher = new Dispatcher( + $this->http, $this->middlewareDispatcher); + + $this->response = $this->getMockBuilder( + '\OC\AppFramework\Http\Response') + ->disableOriginalConstructor() + ->getMock(); + + $this->lastModified = new \DateTime(null, new \DateTimeZone('GMT')); + $this->etag = 'hi'; + } + + + private function setMiddlewareExpections($out=null, + $httpHeaders=null, $responseHeaders=array(), + $ex=false, $catchEx=true) { + + if($ex) { + $exception = new \Exception(); + $this->middlewareDispatcher->expects($this->once()) + ->method('beforeController') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod)) + ->will($this->throwException($exception)); + if($catchEx) { + $this->middlewareDispatcher->expects($this->once()) + ->method('afterException') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod), + $this->equalTo($exception)) + ->will($this->returnValue($this->response)); + } else { + $this->middlewareDispatcher->expects($this->once()) + ->method('afterException') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod), + $this->equalTo($exception)) + ->will($this->returnValue(null)); + return; + } + } else { + $this->middlewareDispatcher->expects($this->once()) + ->method('beforeController') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod)); + $this->controller->expects($this->once()) + ->method($this->controllerMethod) + ->will($this->returnValue($this->response)); + } + + $this->response->expects($this->once()) + ->method('render') + ->will($this->returnValue($out)); + $this->response->expects($this->once()) + ->method('getStatus') + ->will($this->returnValue(Http::STATUS_OK)); + $this->response->expects($this->once()) + ->method('getLastModified') + ->will($this->returnValue($this->lastModified)); + $this->response->expects($this->once()) + ->method('getETag') + ->will($this->returnValue($this->etag)); + $this->response->expects($this->once()) + ->method('getHeaders') + ->will($this->returnValue($responseHeaders)); + $this->http->expects($this->once()) + ->method('getStatusHeader') + ->with($this->equalTo(Http::STATUS_OK), + $this->equalTo($this->lastModified), + $this->equalTo($this->etag)) + ->will($this->returnValue($httpHeaders)); + + $this->middlewareDispatcher->expects($this->once()) + ->method('afterController') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod), + $this->equalTo($this->response)) + ->will($this->returnValue($this->response)); + + $this->middlewareDispatcher->expects($this->once()) + ->method('afterController') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod), + $this->equalTo($this->response)) + ->will($this->returnValue($this->response)); + + $this->middlewareDispatcher->expects($this->once()) + ->method('beforeOutput') + ->with($this->equalTo($this->controller), + $this->equalTo($this->controllerMethod), + $this->equalTo($out)) + ->will($this->returnValue($out)); + + + } + + + public function testDispatcherReturnsArrayWith2Entries() { + $this->setMiddlewareExpections(); + + $response = $this->dispatcher->dispatch($this->controller, + $this->controllerMethod); + $this->assertNull($response[0]); + $this->assertEquals(array(), $response[1]); + $this->assertNull($response[2]); + } + + + public function testHeadersAndOutputAreReturned(){ + $out = 'yo'; + $httpHeaders = 'Http'; + $responseHeaders = array('hell' => 'yeah'); + $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders); + + $response = $this->dispatcher->dispatch($this->controller, + $this->controllerMethod); + + $this->assertEquals($httpHeaders, $response[0]); + $this->assertEquals($responseHeaders, $response[1]); + $this->assertEquals($out, $response[2]); + } + + + public function testExceptionCallsAfterException() { + $out = 'yo'; + $httpHeaders = 'Http'; + $responseHeaders = array('hell' => 'yeah'); + $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true); + + $response = $this->dispatcher->dispatch($this->controller, + $this->controllerMethod); + + $this->assertEquals($httpHeaders, $response[0]); + $this->assertEquals($responseHeaders, $response[1]); + $this->assertEquals($out, $response[2]); + } + + + public function testExceptionThrowsIfCanNotBeHandledByAfterException() { + $out = 'yo'; + $httpHeaders = 'Http'; + $responseHeaders = array('hell' => 'yeah'); + $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true, false); + + $this->setExpectedException('\Exception'); + $response = $this->dispatcher->dispatch($this->controller, + $this->controllerMethod); + + } + +} diff --git a/tests/lib/appframework/http/DownloadResponseTest.php b/tests/lib/appframework/http/DownloadResponseTest.php new file mode 100644 index 0000000000..103cfe7588 --- /dev/null +++ b/tests/lib/appframework/http/DownloadResponseTest.php @@ -0,0 +1,51 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +require_once(__DIR__ . "/../classloader.php"); + + +class ChildDownloadResponse extends DownloadResponse {}; + + +class DownloadResponseTest extends \PHPUnit_Framework_TestCase { + + protected $response; + + protected function setUp(){ + $this->response = new ChildDownloadResponse('file', 'content'); + } + + + public function testHeaders() { + $headers = $this->response->getHeaders(); + + $this->assertContains('attachment; filename="file"', $headers['Content-Disposition']); + $this->assertContains('content', $headers['Content-Type']); + } + + +} diff --git a/tests/lib/appframework/http/HttpTest.php b/tests/lib/appframework/http/HttpTest.php new file mode 100644 index 0000000000..306bc3caf4 --- /dev/null +++ b/tests/lib/appframework/http/HttpTest.php @@ -0,0 +1,87 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +require_once(__DIR__ . "/../classloader.php"); + + + +class HttpTest extends \PHPUnit_Framework_TestCase { + + private $server; + private $http; + + protected function setUp(){ + $this->server = array(); + $this->http = new Http($this->server); + } + + + public function testProtocol() { + $header = $this->http->getStatusHeader(Http::STATUS_TEMPORARY_REDIRECT); + $this->assertEquals('HTTP/1.1 307 Temporary Redirect', $header); + } + + + public function testProtocol10() { + $this->http = new Http($this->server, 'HTTP/1.0'); + $header = $this->http->getStatusHeader(Http::STATUS_OK); + $this->assertEquals('HTTP/1.0 200 OK', $header); + } + + + public function testEtagMatchReturnsNotModified() { + $http = new Http(array('HTTP_IF_NONE_MATCH' => 'hi')); + + $header = $http->getStatusHeader(Http::STATUS_OK, null, 'hi'); + $this->assertEquals('HTTP/1.1 304 Not Modified', $header); + } + + + public function testLastModifiedMatchReturnsNotModified() { + $dateTime = new \DateTime(null, new \DateTimeZone('GMT')); + $dateTime->setTimestamp('12'); + + $http = new Http( + array( + 'HTTP_IF_MODIFIED_SINCE' => 'Thu, 01 Jan 1970 00:00:12 +0000') + ); + + $header = $http->getStatusHeader(Http::STATUS_OK, $dateTime); + $this->assertEquals('HTTP/1.1 304 Not Modified', $header); + } + + + + public function testTempRedirectBecomesFoundInHttp10() { + $http = new Http(array(), 'HTTP/1.0'); + + $header = $http->getStatusHeader(Http::STATUS_TEMPORARY_REDIRECT); + $this->assertEquals('HTTP/1.0 302 Found', $header); + } + // TODO: write unittests for http codes + +} diff --git a/tests/lib/appframework/http/JSONResponseTest.php b/tests/lib/appframework/http/JSONResponseTest.php new file mode 100644 index 0000000000..d15e08f6ce --- /dev/null +++ b/tests/lib/appframework/http/JSONResponseTest.php @@ -0,0 +1,96 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +require_once(__DIR__ . "/../classloader.php"); + + + +class JSONResponseTest extends \PHPUnit_Framework_TestCase { + + /** + * @var JSONResponse + */ + private $json; + + protected function setUp() { + $this->json = new JSONResponse(); + } + + + public function testHeader() { + $headers = $this->json->getHeaders(); + $this->assertEquals('application/json; charset=utf-8', $headers['Content-type']); + } + + + public function testSetData() { + $params = array('hi', 'yo'); + $this->json->setData($params); + + $this->assertEquals(array('hi', 'yo'), $this->json->getData()); + } + + + public function testSetRender() { + $params = array('test' => 'hi'); + $this->json->setData($params); + + $expected = '{"test":"hi"}'; + + $this->assertEquals($expected, $this->json->render()); + } + + + public function testRender() { + $params = array('test' => 'hi'); + $this->json->setData($params); + + $expected = '{"test":"hi"}'; + + $this->assertEquals($expected, $this->json->render()); + } + + + public function testShouldHaveXContentHeaderByDefault() { + $headers = $this->json->getHeaders(); + $this->assertEquals('nosniff', $headers['X-Content-Type-Options']); + } + + + public function testConstructorAllowsToSetData() { + $data = array('hi'); + $code = 300; + $response = new JSONResponse($data, $code); + + $expected = '["hi"]'; + $this->assertEquals($expected, $response->render()); + $this->assertEquals($code, $response->getStatus()); + } + +} diff --git a/tests/lib/appframework/http/RedirectResponseTest.php b/tests/lib/appframework/http/RedirectResponseTest.php new file mode 100644 index 0000000000..a8577feed2 --- /dev/null +++ b/tests/lib/appframework/http/RedirectResponseTest.php @@ -0,0 +1,55 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +require_once(__DIR__ . "/../classloader.php"); + + + +class RedirectResponseTest extends \PHPUnit_Framework_TestCase { + + + protected $response; + + protected function setUp(){ + $this->response = new RedirectResponse('/url'); + } + + + public function testHeaders() { + $headers = $this->response->getHeaders(); + $this->assertEquals('/url', $headers['Location']); + $this->assertEquals(Http::STATUS_TEMPORARY_REDIRECT, + $this->response->getStatus()); + } + + + public function testGetRedirectUrl(){ + $this->assertEquals('/url', $this->response->getRedirectUrl()); + } + + +} diff --git a/tests/lib/appframework/http/RequestTest.php b/tests/lib/appframework/http/RequestTest.php new file mode 100644 index 0000000000..c1f56c0163 --- /dev/null +++ b/tests/lib/appframework/http/RequestTest.php @@ -0,0 +1,78 @@ + array('name' => 'John Q. Public', 'nickname' => 'Joey'), + ); + + $request = new Request($vars); + + // Countable + $this->assertEquals(2, count($request)); + // Array access + $this->assertEquals('Joey', $request['nickname']); + // "Magic" accessors + $this->assertEquals('Joey', $request->{'nickname'}); + $this->assertTrue(isset($request['nickname'])); + $this->assertTrue(isset($request->{'nickname'})); + $this->assertEquals(false, isset($request->{'flickname'})); + // Only testing 'get', but same approach for post, files etc. + $this->assertEquals('Joey', $request->get['nickname']); + // Always returns null if variable not set. + $this->assertEquals(null, $request->{'flickname'}); + } + + // urlParams has precedence over POST which has precedence over GET + public function testPrecedence() { + $vars = array( + 'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'), + 'post' => array('name' => 'Jane Doe', 'nickname' => 'Janey'), + 'urlParams' => array('user' => 'jw', 'name' => 'Johnny Weissmüller'), + ); + + $request = new Request($vars); + + $this->assertEquals(3, count($request)); + $this->assertEquals('Janey', $request->{'nickname'}); + $this->assertEquals('Johnny Weissmüller', $request->{'name'}); + } + + + /** + * @expectedException RuntimeException + */ + public function testImmutableArrayAccess() { + $vars = array( + 'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'), + ); + + $request = new Request($vars); + $request['nickname'] = 'Janey'; + } + + /** + * @expectedException RuntimeException + */ + public function testImmutableMagicAccess() { + $vars = array( + 'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'), + ); + + $request = new Request($vars); + $request->{'nickname'} = 'Janey'; + } + +} diff --git a/tests/lib/appframework/http/ResponseTest.php b/tests/lib/appframework/http/ResponseTest.php new file mode 100644 index 0000000000..621ba66545 --- /dev/null +++ b/tests/lib/appframework/http/ResponseTest.php @@ -0,0 +1,119 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + + +require_once(__DIR__ . "/../classloader.php"); + + + +class ResponseTest extends \PHPUnit_Framework_TestCase { + + + private $childResponse; + + protected function setUp(){ + $this->childResponse = new Response(); + } + + + public function testAddHeader(){ + $this->childResponse->addHeader('hello', 'world'); + $headers = $this->childResponse->getHeaders(); + $this->assertEquals('world', $headers['hello']); + } + + + public function testAddHeaderValueNullDeletesIt(){ + $this->childResponse->addHeader('hello', 'world'); + $this->childResponse->addHeader('hello', null); + $this->assertEquals(1, count($this->childResponse->getHeaders())); + } + + + public function testCacheHeadersAreDisabledByDefault(){ + $headers = $this->childResponse->getHeaders(); + $this->assertEquals('no-cache, must-revalidate', $headers['Cache-Control']); + } + + + public function testRenderReturnNullByDefault(){ + $this->assertEquals(null, $this->childResponse->render()); + } + + + public function testGetStatus() { + $default = $this->childResponse->getStatus(); + + $this->childResponse->setStatus(Http::STATUS_NOT_FOUND); + + $this->assertEquals(Http::STATUS_OK, $default); + $this->assertEquals(Http::STATUS_NOT_FOUND, $this->childResponse->getStatus()); + } + + + public function testGetEtag() { + $this->childResponse->setEtag('hi'); + $this->assertEquals('hi', $this->childResponse->getEtag()); + } + + + public function testGetLastModified() { + $lastModified = new \DateTime(null, new \DateTimeZone('GMT')); + $lastModified->setTimestamp(1); + $this->childResponse->setLastModified($lastModified); + $this->assertEquals($lastModified, $this->childResponse->getLastModified()); + } + + + + public function testCacheSecondsZero() { + $this->childResponse->cacheFor(0); + + $headers = $this->childResponse->getHeaders(); + $this->assertEquals('no-cache, must-revalidate', $headers['Cache-Control']); + } + + + public function testCacheSeconds() { + $this->childResponse->cacheFor(33); + + $headers = $this->childResponse->getHeaders(); + $this->assertEquals('max-age=33, must-revalidate', + $headers['Cache-Control']); + } + + + + public function testEtagLastModifiedHeaders() { + $lastModified = new \DateTime(null, new \DateTimeZone('GMT')); + $lastModified->setTimestamp(1); + $this->childResponse->setLastModified($lastModified); + $headers = $this->childResponse->getHeaders(); + $this->assertEquals('Thu, 01 Jan 1970 00:00:01 +0000', $headers['Last-Modified']); + } + + +} diff --git a/tests/lib/appframework/http/TemplateResponseTest.php b/tests/lib/appframework/http/TemplateResponseTest.php new file mode 100644 index 0000000000..30684725b7 --- /dev/null +++ b/tests/lib/appframework/http/TemplateResponseTest.php @@ -0,0 +1,157 @@ +. + * + */ + + +namespace OC\AppFramework\Http; + +use OC\AppFramework\Core\API; + + +require_once(__DIR__ . "/../classloader.php"); + + +class TemplateResponseTest extends \PHPUnit_Framework_TestCase { + + private $tpl; + private $api; + + protected function setUp() { + $this->api = $this->getMock('OC\AppFramework\Core\API', + array('getAppName'), array('test')); + $this->api->expects($this->any()) + ->method('getAppName') + ->will($this->returnValue('app')); + + $this->tpl = new TemplateResponse($this->api, 'home'); + } + + + public function testSetParams(){ + $params = array('hi' => 'yo'); + $this->tpl->setParams($params); + + $this->assertEquals(array('hi' => 'yo'), $this->tpl->getParams()); + } + + + public function testGetTemplateName(){ + $this->assertEquals('home', $this->tpl->getTemplateName()); + } + + + public function testRender(){ + $ocTpl = $this->getMock('Template', array('fetchPage')); + $ocTpl->expects($this->once()) + ->method('fetchPage'); + + $api = $this->getMock('OC\AppFramework\Core\API', + array('getAppName', 'getTemplate'), array('app')); + $api->expects($this->any()) + ->method('getAppName') + ->will($this->returnValue('app')); + $api->expects($this->once()) + ->method('getTemplate') + ->with($this->equalTo('home'), $this->equalTo('user'), $this->equalTo('app')) + ->will($this->returnValue($ocTpl)); + + $tpl = new TemplateResponse($api, 'home'); + + $tpl->render(); + } + + + public function testRenderAssignsParams(){ + $params = array('john' => 'doe'); + + $ocTpl = $this->getMock('Template', array('assign', 'fetchPage')); + $ocTpl->expects($this->once()) + ->method('assign') + ->with($this->equalTo('john'), $this->equalTo('doe')); + + $api = $this->getMock('OC\AppFramework\Core\API', + array('getAppName', 'getTemplate'), array('app')); + $api->expects($this->any()) + ->method('getAppName') + ->will($this->returnValue('app')); + $api->expects($this->once()) + ->method('getTemplate') + ->with($this->equalTo('home'), $this->equalTo('user'), $this->equalTo('app')) + ->will($this->returnValue($ocTpl)); + + $tpl = new TemplateResponse($api, 'home'); + $tpl->setParams($params); + + $tpl->render(); + } + + + public function testRenderDifferentApp(){ + $ocTpl = $this->getMock('Template', array('fetchPage')); + $ocTpl->expects($this->once()) + ->method('fetchPage'); + + $api = $this->getMock('OC\AppFramework\Core\API', + array('getAppName', 'getTemplate'), array('app')); + $api->expects($this->any()) + ->method('getAppName') + ->will($this->returnValue('app')); + $api->expects($this->once()) + ->method('getTemplate') + ->with($this->equalTo('home'), $this->equalTo('user'), $this->equalTo('app2')) + ->will($this->returnValue($ocTpl)); + + $tpl = new TemplateResponse($api, 'home', 'app2'); + + $tpl->render(); + } + + + public function testRenderDifferentRenderAs(){ + $ocTpl = $this->getMock('Template', array('fetchPage')); + $ocTpl->expects($this->once()) + ->method('fetchPage'); + + $api = $this->getMock('OC\AppFramework\Core\API', + array('getAppName', 'getTemplate'), array('app')); + $api->expects($this->any()) + ->method('getAppName') + ->will($this->returnValue('app')); + $api->expects($this->once()) + ->method('getTemplate') + ->with($this->equalTo('home'), $this->equalTo('admin'), $this->equalTo('app')) + ->will($this->returnValue($ocTpl)); + + $tpl = new TemplateResponse($api, 'home'); + $tpl->renderAs('admin'); + + $tpl->render(); + } + + + public function testGetRenderAs(){ + $render = 'myrender'; + $this->tpl->renderAs($render); + $this->assertEquals($render, $this->tpl->getRenderAs()); + } + +} diff --git a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php new file mode 100644 index 0000000000..bfa54a48ea --- /dev/null +++ b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php @@ -0,0 +1,280 @@ +. + * + */ + + +namespace OC\AppFramework; + +use OC\AppFramework\Controller\Controller; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\Response; +use OC\AppFramework\Middleware\Middleware; +use OC\AppFramework\Middleware\MiddlewareDispatcher; + + +require_once(__DIR__ . "/../classloader.php"); + + +// needed to test ordering +class TestMiddleware extends Middleware { + public static $beforeControllerCalled = 0; + public static $afterControllerCalled = 0; + public static $afterExceptionCalled = 0; + public static $beforeOutputCalled = 0; + + public $beforeControllerOrder = 0; + public $afterControllerOrder = 0; + public $afterExceptionOrder = 0; + public $beforeOutputOrder = 0; + + public $controller; + public $methodName; + public $exception; + public $response; + public $output; + + private $beforeControllerThrowsEx; + + public function __construct($beforeControllerThrowsEx) { + self::$beforeControllerCalled = 0; + self::$afterControllerCalled = 0; + self::$afterExceptionCalled = 0; + self::$beforeOutputCalled = 0; + $this->beforeControllerThrowsEx = $beforeControllerThrowsEx; + } + + public function beforeController($controller, $methodName){ + self::$beforeControllerCalled++; + $this->beforeControllerOrder = self::$beforeControllerCalled; + $this->controller = $controller; + $this->methodName = $methodName; + if($this->beforeControllerThrowsEx){ + throw new \Exception(); + } + } + + public function afterException($controller, $methodName, \Exception $exception){ + self::$afterExceptionCalled++; + $this->afterExceptionOrder = self::$afterExceptionCalled; + $this->controller = $controller; + $this->methodName = $methodName; + $this->exception = $exception; + parent::afterException($controller, $methodName, $exception); + } + + public function afterController($controller, $methodName, Response $response){ + self::$afterControllerCalled++; + $this->afterControllerOrder = self::$afterControllerCalled; + $this->controller = $controller; + $this->methodName = $methodName; + $this->response = $response; + return parent::afterController($controller, $methodName, $response); + } + + public function beforeOutput($controller, $methodName, $output){ + self::$beforeOutputCalled++; + $this->beforeOutputOrder = self::$beforeOutputCalled; + $this->controller = $controller; + $this->methodName = $methodName; + $this->output = $output; + return parent::beforeOutput($controller, $methodName, $output); + } +} + + +class MiddlewareDispatcherTest extends \PHPUnit_Framework_TestCase { + + private $dispatcher; + + + public function setUp() { + $this->dispatcher = new MiddlewareDispatcher(); + $this->controller = $this->getControllerMock(); + $this->method = 'method'; + $this->response = new Response(); + $this->output = 'hi'; + $this->exception = new \Exception(); + } + + + private function getAPIMock(){ + return $this->getMock('OC\AppFramework\Core\API', + array('getAppName'), array('app')); + } + + + private function getControllerMock(){ + return $this->getMock('OC\AppFramework\Controller\Controller', array('method'), + array($this->getAPIMock(), new Request())); + } + + + private function getMiddleware($beforeControllerThrowsEx=false){ + $m1 = new TestMiddleware($beforeControllerThrowsEx); + $this->dispatcher->registerMiddleware($m1); + return $m1; + } + + + public function testAfterExceptionShouldReturnResponseOfMiddleware(){ + $response = new Response(); + $m1 = $this->getMock('\OC\AppFramework\Middleware\Middleware', + array('afterException', 'beforeController')); + $m1->expects($this->never()) + ->method('afterException'); + + $m2 = $this->getMock('OC\AppFramework\Middleware\Middleware', + array('afterException', 'beforeController')); + $m2->expects($this->once()) + ->method('afterException') + ->will($this->returnValue($response)); + + $this->dispatcher->registerMiddleware($m1); + $this->dispatcher->registerMiddleware($m2); + + $this->dispatcher->beforeController($this->controller, $this->method); + $this->assertEquals($response, $this->dispatcher->afterException($this->controller, $this->method, $this->exception)); + } + + + public function testAfterExceptionShouldThrowAgainWhenNotHandled(){ + $m1 = new TestMiddleware(false); + $m2 = new TestMiddleware(true); + + $this->dispatcher->registerMiddleware($m1); + $this->dispatcher->registerMiddleware($m2); + + $this->setExpectedException('\Exception'); + $this->dispatcher->beforeController($this->controller, $this->method); + $this->dispatcher->afterException($this->controller, $this->method, $this->exception); + } + + + public function testBeforeControllerCorrectArguments(){ + $m1 = $this->getMiddleware(); + $this->dispatcher->beforeController($this->controller, $this->method); + + $this->assertEquals($this->controller, $m1->controller); + $this->assertEquals($this->method, $m1->methodName); + } + + + public function testAfterControllerCorrectArguments(){ + $m1 = $this->getMiddleware(); + + $this->dispatcher->afterController($this->controller, $this->method, $this->response); + + $this->assertEquals($this->controller, $m1->controller); + $this->assertEquals($this->method, $m1->methodName); + $this->assertEquals($this->response, $m1->response); + } + + + public function testAfterExceptionCorrectArguments(){ + $m1 = $this->getMiddleware(); + + $this->setExpectedException('\Exception'); + + $this->dispatcher->beforeController($this->controller, $this->method); + $this->dispatcher->afterException($this->controller, $this->method, $this->exception); + + $this->assertEquals($this->controller, $m1->controller); + $this->assertEquals($this->method, $m1->methodName); + $this->assertEquals($this->exception, $m1->exception); + } + + + public function testBeforeOutputCorrectArguments(){ + $m1 = $this->getMiddleware(); + + $this->dispatcher->beforeOutput($this->controller, $this->method, $this->output); + + $this->assertEquals($this->controller, $m1->controller); + $this->assertEquals($this->method, $m1->methodName); + $this->assertEquals($this->output, $m1->output); + } + + + public function testBeforeControllerOrder(){ + $m1 = $this->getMiddleware(); + $m2 = $this->getMiddleware(); + + $this->dispatcher->beforeController($this->controller, $this->method); + + $this->assertEquals(1, $m1->beforeControllerOrder); + $this->assertEquals(2, $m2->beforeControllerOrder); + } + + public function testAfterControllerOrder(){ + $m1 = $this->getMiddleware(); + $m2 = $this->getMiddleware(); + + $this->dispatcher->afterController($this->controller, $this->method, $this->response); + + $this->assertEquals(2, $m1->afterControllerOrder); + $this->assertEquals(1, $m2->afterControllerOrder); + } + + + public function testAfterExceptionOrder(){ + $m1 = $this->getMiddleware(); + $m2 = $this->getMiddleware(); + + $this->setExpectedException('\Exception'); + $this->dispatcher->beforeController($this->controller, $this->method); + $this->dispatcher->afterException($this->controller, $this->method, $this->exception); + + $this->assertEquals(1, $m1->afterExceptionOrder); + $this->assertEquals(1, $m2->afterExceptionOrder); + } + + + public function testBeforeOutputOrder(){ + $m1 = $this->getMiddleware(); + $m2 = $this->getMiddleware(); + + $this->dispatcher->beforeOutput($this->controller, $this->method, $this->output); + + $this->assertEquals(2, $m1->beforeOutputOrder); + $this->assertEquals(1, $m2->beforeOutputOrder); + } + + + public function testExceptionShouldRunAfterExceptionOfOnlyPreviouslyExecutedMiddlewares(){ + $m1 = $this->getMiddleware(); + $m2 = $this->getMiddleware(true); + $m3 = $this->getMock('\OC\AppFramework\Middleware\Middleware'); + $m3->expects($this->never()) + ->method('afterException'); + $m3->expects($this->never()) + ->method('beforeController'); + $m3->expects($this->never()) + ->method('afterController'); + + $this->dispatcher->registerMiddleware($m3); + + $this->dispatcher->beforeOutput($this->controller, $this->method, $this->output); + + $this->assertEquals(2, $m1->beforeOutputOrder); + $this->assertEquals(1, $m2->beforeOutputOrder); + } +} diff --git a/tests/lib/appframework/middleware/MiddlewareTest.php b/tests/lib/appframework/middleware/MiddlewareTest.php new file mode 100644 index 0000000000..1adce6b3d4 --- /dev/null +++ b/tests/lib/appframework/middleware/MiddlewareTest.php @@ -0,0 +1,82 @@ +. + * + */ + + +namespace OC\AppFramework; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Middleware\Middleware; + + +require_once(__DIR__ . "/../classloader.php"); + + +class ChildMiddleware extends Middleware {}; + + +class MiddlewareTest extends \PHPUnit_Framework_TestCase { + + private $middleware; + private $controller; + private $exception; + private $api; + + protected function setUp(){ + $this->middleware = new ChildMiddleware(); + + $this->api = $this->getMock('OC\AppFramework\Core\API', + array(), array('test')); + + $this->controller = $this->getMock('OC\AppFramework\Controller\Controller', + array(), array($this->api, new Request())); + $this->exception = new \Exception(); + $this->response = $this->getMock('OC\AppFramework\Http\Response'); + } + + + public function testBeforeController() { + $this->middleware->beforeController($this->controller, null, $this->exception); + } + + + public function testAfterExceptionRaiseAgainWhenUnhandled() { + $this->setExpectedException('Exception'); + $afterEx = $this->middleware->afterException($this->controller, null, $this->exception); + } + + + public function testAfterControllerReturnResponseWhenUnhandled() { + $response = $this->middleware->afterController($this->controller, null, $this->response); + + $this->assertEquals($this->response, $response); + } + + + public function testBeforeOutputReturnOutputhenUnhandled() { + $output = $this->middleware->beforeOutput($this->controller, null, 'test'); + + $this->assertEquals('test', $output); + } + + +} diff --git a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php new file mode 100644 index 0000000000..0b2103564e --- /dev/null +++ b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php @@ -0,0 +1,388 @@ +. + * + */ + + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Http\Http; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\RedirectResponse; +use OC\AppFramework\Http\JSONResponse; +use OC\AppFramework\Middleware\Middleware; + + +require_once(__DIR__ . "/../../classloader.php"); + + +class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { + + private $middleware; + private $controller; + private $secException; + private $secAjaxException; + private $request; + + public function setUp() { + $api = $this->getMock('OC\AppFramework\Core\API', array(), array('test')); + $this->controller = $this->getMock('OC\AppFramework\Controller\Controller', + array(), array($api, new Request())); + + $this->request = new Request(); + $this->middleware = new SecurityMiddleware($api, $this->request); + $this->secException = new SecurityException('hey', false); + $this->secAjaxException = new SecurityException('hey', true); + } + + + private function getAPI(){ + return $this->getMock('OC\AppFramework\Core\API', + array('isLoggedIn', 'passesCSRFCheck', 'isAdminUser', + 'isSubAdminUser', 'activateNavigationEntry', + 'getUserId'), + array('app')); + } + + + private function checkNavEntry($method, $shouldBeActivated=false){ + $api = $this->getAPI(); + + if($shouldBeActivated){ + $api->expects($this->once()) + ->method('activateNavigationEntry'); + } else { + $api->expects($this->never()) + ->method('activateNavigationEntry'); + } + + $sec = new SecurityMiddleware($api, $this->request); + $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method); + } + + + /** + * @IsLoggedInExemption + * @CSRFExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testSetNavigationEntry(){ + $this->checkNavEntry('testSetNavigationEntry', true); + } + + + private function ajaxExceptionCheck($method, $shouldBeAjax=false){ + $api = $this->getAPI(); + $api->expects($this->any()) + ->method('passesCSRFCheck') + ->will($this->returnValue(false)); + + $sec = new SecurityMiddleware($api, $this->request); + + try { + $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', + $method); + } catch (SecurityException $ex){ + if($shouldBeAjax){ + $this->assertTrue($ex->isAjax()); + } else { + $this->assertFalse($ex->isAjax()); + } + + } + } + + + /** + * @Ajax + * @IsLoggedInExemption + * @CSRFExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testAjaxException(){ + $this->ajaxExceptionCheck('testAjaxException'); + } + + + /** + * @IsLoggedInExemption + * @CSRFExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testNoAjaxException(){ + $this->ajaxExceptionCheck('testNoAjaxException'); + } + + + private function ajaxExceptionStatus($method, $test, $status) { + $api = $this->getAPI(); + $api->expects($this->any()) + ->method($test) + ->will($this->returnValue(false)); + + $sec = new SecurityMiddleware($api, $this->request); + + try { + $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', + $method); + } catch (SecurityException $ex){ + $this->assertEquals($status, $ex->getCode()); + } + } + + /** + * @Ajax + */ + public function testAjaxStatusLoggedInCheck() { + $this->ajaxExceptionStatus( + 'testAjaxStatusLoggedInCheck', + 'isLoggedIn', + Http::STATUS_UNAUTHORIZED + ); + } + + /** + * @Ajax + * @IsLoggedInExemption + */ + public function testAjaxNotAdminCheck() { + $this->ajaxExceptionStatus( + 'testAjaxNotAdminCheck', + 'isAdminUser', + Http::STATUS_FORBIDDEN + ); + } + + /** + * @Ajax + * @IsLoggedInExemption + * @IsAdminExemption + */ + public function testAjaxNotSubAdminCheck() { + $this->ajaxExceptionStatus( + 'testAjaxNotSubAdminCheck', + 'isSubAdminUser', + Http::STATUS_FORBIDDEN + ); + } + + /** + * @Ajax + * @IsLoggedInExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testAjaxStatusCSRFCheck() { + $this->ajaxExceptionStatus( + 'testAjaxStatusCSRFCheck', + 'passesCSRFCheck', + Http::STATUS_PRECONDITION_FAILED + ); + } + + /** + * @Ajax + * @CSRFExemption + * @IsLoggedInExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testAjaxStatusAllGood() { + $this->ajaxExceptionStatus( + 'testAjaxStatusAllGood', + 'isLoggedIn', + 0 + ); + $this->ajaxExceptionStatus( + 'testAjaxStatusAllGood', + 'isAdminUser', + 0 + ); + $this->ajaxExceptionStatus( + 'testAjaxStatusAllGood', + 'isSubAdminUser', + 0 + ); + $this->ajaxExceptionStatus( + 'testAjaxStatusAllGood', + 'passesCSRFCheck', + 0 + ); + } + + /** + * @IsLoggedInExemption + * @CSRFExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testNoChecks(){ + $api = $this->getAPI(); + $api->expects($this->never()) + ->method('passesCSRFCheck') + ->will($this->returnValue(true)); + $api->expects($this->never()) + ->method('isAdminUser') + ->will($this->returnValue(true)); + $api->expects($this->never()) + ->method('isSubAdminUser') + ->will($this->returnValue(true)); + $api->expects($this->never()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + + $sec = new SecurityMiddleware($api, $this->request); + $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', + 'testNoChecks'); + } + + + private function securityCheck($method, $expects, $shouldFail=false){ + $api = $this->getAPI(); + $api->expects($this->once()) + ->method($expects) + ->will($this->returnValue(!$shouldFail)); + + $sec = new SecurityMiddleware($api, $this->request); + + if($shouldFail){ + $this->setExpectedException('\OC\AppFramework\Middleware\Security\SecurityException'); + } + + $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method); + } + + + /** + * @IsLoggedInExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testCsrfCheck(){ + $this->securityCheck('testCsrfCheck', 'passesCSRFCheck'); + } + + + /** + * @IsLoggedInExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testFailCsrfCheck(){ + $this->securityCheck('testFailCsrfCheck', 'passesCSRFCheck', true); + } + + + /** + * @CSRFExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testLoggedInCheck(){ + $this->securityCheck('testLoggedInCheck', 'isLoggedIn'); + } + + + /** + * @CSRFExemption + * @IsAdminExemption + * @IsSubAdminExemption + */ + public function testFailLoggedInCheck(){ + $this->securityCheck('testFailLoggedInCheck', 'isLoggedIn', true); + } + + + /** + * @IsLoggedInExemption + * @CSRFExemption + * @IsSubAdminExemption + */ + public function testIsAdminCheck(){ + $this->securityCheck('testIsAdminCheck', 'isAdminUser'); + } + + + /** + * @IsLoggedInExemption + * @CSRFExemption + * @IsSubAdminExemption + */ + public function testFailIsAdminCheck(){ + $this->securityCheck('testFailIsAdminCheck', 'isAdminUser', true); + } + + + /** + * @IsLoggedInExemption + * @CSRFExemption + * @IsAdminExemption + */ + public function testIsSubAdminCheck(){ + $this->securityCheck('testIsSubAdminCheck', 'isSubAdminUser'); + } + + + /** + * @IsLoggedInExemption + * @CSRFExemption + * @IsAdminExemption + */ + public function testFailIsSubAdminCheck(){ + $this->securityCheck('testFailIsSubAdminCheck', 'isSubAdminUser', true); + } + + + + public function testAfterExceptionNotCaughtThrowsItAgain(){ + $ex = new \Exception(); + $this->setExpectedException('\Exception'); + $this->middleware->afterException($this->controller, 'test', $ex); + } + + + public function testAfterExceptionReturnsRedirect(){ + $api = $this->getMock('OC\AppFramework\Core\API', array(), array('test')); + $this->controller = $this->getMock('OC\AppFramework\Controller\Controller', + array(), array($api, new Request())); + + $this->request = new Request( + array('server' => array('HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'))); + $this->middleware = new SecurityMiddleware($api, $this->request); + $response = $this->middleware->afterException($this->controller, 'test', + $this->secException); + + $this->assertTrue($response instanceof RedirectResponse); + } + + + public function testAfterAjaxExceptionReturnsJSONError(){ + $response = $this->middleware->afterException($this->controller, 'test', + $this->secAjaxException); + + $this->assertTrue($response instanceof JSONResponse); + } + + +} diff --git a/tests/lib/appframework/routing/RoutingTest.php b/tests/lib/appframework/routing/RoutingTest.php new file mode 100644 index 0000000000..92ad461471 --- /dev/null +++ b/tests/lib/appframework/routing/RoutingTest.php @@ -0,0 +1,214 @@ + array( + array('name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'GET') + )); + + $this->assertSimpleRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open'); + } + + public function testSimpleRouteWithMissingVerb() + { + $routes = array('routes' => array( + array('name' => 'folders#open', 'url' => '/folders/{folderId}/open') + )); + + $this->assertSimpleRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open'); + } + + public function testSimpleRouteWithLowercaseVerb() + { + $routes = array('routes' => array( + array('name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete') + )); + + $this->assertSimpleRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open'); + } + + /** + * @expectedException \UnexpectedValueException + */ + public function testSimpleRouteWithBrokenName() + { + $routes = array('routes' => array( + array('name' => 'folders_open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete') + )); + + // router mock + $router = $this->getMock("\OC_Router", array('create')); + + // load route configuration + $container = new DIContainer('app1'); + $config = new RouteConfig($container, $router, $routes); + + $config->register(); + } + + public function testSimpleRouteWithUnderScoreNames() + { + $routes = array('routes' => array( + array('name' => 'admin_folders#open_current', 'url' => '/folders/{folderId}/open', 'verb' => 'delete') + )); + + $this->assertSimpleRoute($routes, 'admin_folders.open_current', 'DELETE', '/folders/{folderId}/open', 'AdminFoldersController', 'openCurrent'); + } + + public function testResource() + { + $routes = array('resources' => array('accounts' => array('url' => '/accounts'))); + + $this->assertResource($routes, 'accounts', '/accounts', 'AccountsController', 'accountId'); + } + + public function testResourceWithUnderScoreName() + { + $routes = array('resources' => array('admin_accounts' => array('url' => '/admin/accounts'))); + + $this->assertResource($routes, 'admin_accounts', '/admin/accounts', 'AdminAccountsController', 'adminAccountId'); + } + + private function assertSimpleRoute($routes, $name, $verb, $url, $controllerName, $actionName) + { + // route mocks + $route = $this->mockRoute($verb, $controllerName, $actionName); + + // router mock + $router = $this->getMock("\OC_Router", array('create')); + + // we expect create to be called once: + $router + ->expects($this->once()) + ->method('create') + ->with($this->equalTo('app1.' . $name), $this->equalTo($url)) + ->will($this->returnValue($route)); + + // load route configuration + $container = new DIContainer('app1'); + $config = new RouteConfig($container, $router, $routes); + + $config->register(); + } + + private function assertResource($yaml, $resourceName, $url, $controllerName, $paramName) + { + // router mock + $router = $this->getMock("\OC_Router", array('create')); + + // route mocks + $indexRoute = $this->mockRoute('GET', $controllerName, 'index'); + $showRoute = $this->mockRoute('GET', $controllerName, 'show'); + $createRoute = $this->mockRoute('POST', $controllerName, 'create'); + $updateRoute = $this->mockRoute('PUT', $controllerName, 'update'); + $destroyRoute = $this->mockRoute('DELETE', $controllerName, 'destroy'); + + $urlWithParam = $url . '/{' . $paramName . '}'; + + // we expect create to be called once: + $router + ->expects($this->at(0)) + ->method('create') + ->with($this->equalTo('app1.' . $resourceName . '.index'), $this->equalTo($url)) + ->will($this->returnValue($indexRoute)); + + $router + ->expects($this->at(1)) + ->method('create') + ->with($this->equalTo('app1.' . $resourceName . '.show'), $this->equalTo($urlWithParam)) + ->will($this->returnValue($showRoute)); + + $router + ->expects($this->at(2)) + ->method('create') + ->with($this->equalTo('app1.' . $resourceName . '.create'), $this->equalTo($url)) + ->will($this->returnValue($createRoute)); + + $router + ->expects($this->at(3)) + ->method('create') + ->with($this->equalTo('app1.' . $resourceName . '.update'), $this->equalTo($urlWithParam)) + ->will($this->returnValue($updateRoute)); + + $router + ->expects($this->at(4)) + ->method('create') + ->with($this->equalTo('app1.' . $resourceName . '.destroy'), $this->equalTo($urlWithParam)) + ->will($this->returnValue($destroyRoute)); + + // load route configuration + $container = new DIContainer('app1'); + $config = new RouteConfig($container, $router, $yaml); + + $config->register(); + } + + /** + * @param $verb + * @param $controllerName + * @param $actionName + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function mockRoute($verb, $controllerName, $actionName) + { + $container = new DIContainer('app1'); + $route = $this->getMock("\OC_Route", array('method', 'action'), array(), '', false); + $route + ->expects($this->exactly(1)) + ->method('method') + ->with($this->equalTo($verb)) + ->will($this->returnValue($route)); + + $route + ->expects($this->exactly(1)) + ->method('action') + ->with($this->equalTo(new RouteActionHandler($container, $controllerName, $actionName))) + ->will($this->returnValue($route)); + return $route; + } + +} + +/* +# +# sample routes.yaml for ownCloud +# +# the section simple describes one route + +routes: + - name: folders#open + url: /folders/{folderId}/open + verb: GET + # controller: name.split()[0] + # action: name.split()[1] + +# for a resource following actions will be generated: +# - index +# - create +# - show +# - update +# - destroy +# - new +resources: + accounts: + url: /accounts + + folders: + url: /accounts/{accountId}/folders + # actions can be used to define additional actions on the resource + actions: + - name: validate + verb: GET + on-collection: false + + * */ diff --git a/tests/lib/appframework/utility/MethodAnnotationReaderTest.php b/tests/lib/appframework/utility/MethodAnnotationReaderTest.php new file mode 100644 index 0000000000..bcdcf3de37 --- /dev/null +++ b/tests/lib/appframework/utility/MethodAnnotationReaderTest.php @@ -0,0 +1,58 @@ +. + * + */ + + +namespace OC\AppFramework\Utility; + + +require_once __DIR__ . "/../classloader.php"; + + +class MethodAnnotationReaderTest extends \PHPUnit_Framework_TestCase { + + + /** + * @Annotation + */ + public function testReadAnnotation(){ + $reader = new MethodAnnotationReader('\OC\AppFramework\Utility\MethodAnnotationReaderTest', + 'testReadAnnotation'); + + $this->assertTrue($reader->hasAnnotation('Annotation')); + } + + + /** + * @Annotation + * @param test + */ + public function testReadAnnotationNoLowercase(){ + $reader = new MethodAnnotationReader('\OC\AppFramework\Utility\MethodAnnotationReaderTest', + 'testReadAnnotationNoLowercase'); + + $this->assertTrue($reader->hasAnnotation('Annotation')); + $this->assertFalse($reader->hasAnnotation('param')); + } + + +} From 3324495a7882cb7957c5ffd498b1b6275a192b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 17 Aug 2013 18:26:53 +0200 Subject: [PATCH 006/198] pulling in 3rdparty submodule --- 3rdparty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty b/3rdparty index 8d68fa1eab..75a05d76ab 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit 8d68fa1eabe8c1d033cb89676b31f0eaaf99335b +Subproject commit 75a05d76ab86ba7454b4312fd0ff2ca5bd5828cf From 65d802329f8307cd010a306073d2d3ffd7dc7b74 Mon Sep 17 00:00:00 2001 From: kondou Date: Sun, 18 Aug 2013 10:33:09 +0200 Subject: [PATCH 007/198] Fix some naming and spacing in lib/util.php --- core/setup.php | 2 +- lib/base.php | 4 ++-- lib/util.php | 25 +++++++++++++++---------- settings/admin.php | 4 ++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/core/setup.php b/core/setup.php index 1a2eac1603..4758c23b04 100644 --- a/core/setup.php +++ b/core/setup.php @@ -34,7 +34,7 @@ $opts = array( 'hasMSSQL' => $hasMSSQL, 'directory' => $datadir, 'secureRNG' => OC_Util::secureRNGAvailable(), - 'htaccessWorking' => OC_Util::isHtaccessWorking(), + 'htaccessWorking' => OC_Util::isHtAccessWorking(), 'vulnerableToNullByte' => $vulnerableToNullByte, 'errors' => array(), ); diff --git a/lib/base.php b/lib/base.php index 7a4f5fc7ce..32e0ebe27e 100644 --- a/lib/base.php +++ b/lib/base.php @@ -413,7 +413,7 @@ class OC { } self::initPaths(); - OC_Util::isSetlocaleWorking(); + OC_Util::isSetLocaleWorking(); // set debug mode if an xdebug session is active if (!defined('DEBUG') || !DEBUG) { @@ -522,7 +522,7 @@ class OC { } // write error into log if locale can't be set - if (OC_Util::isSetlocaleWorking() == false) { + if (OC_Util::isSetLocaleWorking() == false) { OC_Log::write('core', 'setting locale to en_US.UTF-8/en_US.UTF8 failed. Support is probably not installed on your system', OC_Log::ERROR); diff --git a/lib/util.php b/lib/util.php index 24ae7d3d1c..10b526ef6b 100755 --- a/lib/util.php +++ b/lib/util.php @@ -18,9 +18,11 @@ class OC_Util { * @brief Can be set up * @param user string * @return boolean + * @description configure the initial filesystem based on the configuration */ - public static function setupFS( $user = '' ) { // configure the initial filesystem based on the configuration - if(self::$fsSetup) { //setting up the filesystem twice can only lead to trouble + public static function setupFS( $user = '' ) { + //setting up the filesystem twice can only lead to trouble + if(self::$fsSetup) { return false; } @@ -71,11 +73,11 @@ class OC_Util { /** * @return void - */ + */ public static function tearDownFS() { \OC\Files\Filesystem::tearDown(); self::$fsSetup=false; - self::$rootMounted=false; + self::$rootMounted=false; } /** @@ -165,9 +167,10 @@ class OC_Util { * @param int timestamp $timestamp * @param bool dateOnly option to omit time from the result * @return string timestamp + * @description adjust to clients timezone if we know it */ public static function formatDate( $timestamp, $dateOnly=false) { - if(\OC::$session->exists('timezone')) { //adjust to clients timezone if we know it + if(\OC::$session->exists('timezone')) { $systemTimeZone = intval(date('O')); $systemTimeZone = (round($systemTimeZone/100, 0)*60) + ($systemTimeZone%100); $clientTimeZone = \OC::$session->get('timezone')*60; @@ -629,13 +632,15 @@ class OC_Util { } /** - * @brief Check if the htaccess file is working by creating a test file in the data directory and trying to access via http + * @brief Check if the htaccess file is working * @return bool + * @description Check if the htaccess file is working by creating a test + * file in the data directory and trying to access via http */ - public static function isHtaccessWorking() { + public static function isHtAccessWorking() { // testdata - $filename = '/htaccesstest.txt'; - $testcontent = 'testcontent'; + $fileName = '/htaccesstest.txt'; + $testContent = 'testcontent'; // creating a test file $testfile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$filename; @@ -718,7 +723,7 @@ class OC_Util { * local packages are not available on the server. * @return bool */ - public static function isSetlocaleWorking() { + public static function isSetLocaleWorking() { // setlocale test is pointless on Windows if (OC_Util::runningOnWindows() ) { return true; diff --git a/settings/admin.php b/settings/admin.php index d721593eb7..0cbb98756d 100755 --- a/settings/admin.php +++ b/settings/admin.php @@ -15,7 +15,7 @@ OC_App::setActiveNavigationEntry( "admin" ); $tmpl = new OC_Template( 'settings', 'admin', 'user'); $forms=OC_App::getForms('admin'); -$htaccessworking=OC_Util::isHtaccessWorking(); +$htaccessworking=OC_Util::isHtAccessWorking(); $entries=OC_Log_Owncloud::getEntries(3); $entriesremain=(count(OC_Log_Owncloud::getEntries(4)) > 3)?true:false; @@ -25,7 +25,7 @@ $tmpl->assign('entries', $entries); $tmpl->assign('entriesremain', $entriesremain); $tmpl->assign('htaccessworking', $htaccessworking); $tmpl->assign('internetconnectionworking', OC_Util::isInternetConnectionEnabled() ? OC_Util::isInternetConnectionWorking() : false); -$tmpl->assign('islocaleworking', OC_Util::isSetlocaleWorking()); +$tmpl->assign('islocaleworking', OC_Util::isSetLocaleWorking()); $tmpl->assign('isWebDavWorking', OC_Util::isWebDAVWorking()); $tmpl->assign('has_fileinfo', OC_Util::fileInfoLoaded()); $tmpl->assign('backgroundjobs_mode', OC_Appconfig::getValue('core', 'backgroundjobs_mode', 'ajax')); From 72e1a8d83b3a21875cac6948879471661d120c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 20 Aug 2013 12:47:23 +0200 Subject: [PATCH 008/198] fixing require to Pimple --- lib/appframework/dependencyinjection/dicontainer.php | 2 +- tests/lib/appframework/AppTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/appframework/dependencyinjection/dicontainer.php b/lib/appframework/dependencyinjection/dicontainer.php index 34f64e72cb..d6cf4d5502 100644 --- a/lib/appframework/dependencyinjection/dicontainer.php +++ b/lib/appframework/dependencyinjection/dicontainer.php @@ -34,7 +34,7 @@ use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Utility\TimeFactory; // register 3rdparty autoloaders -require_once __DIR__ . '/../../../../3rdparty/Pimple/Pimple.php'; +require_once __DIR__ . '/../../../3rdparty/Pimple/Pimple.php'; /** diff --git a/tests/lib/appframework/AppTest.php b/tests/lib/appframework/AppTest.php index 000094d07c..6e647f68e6 100644 --- a/tests/lib/appframework/AppTest.php +++ b/tests/lib/appframework/AppTest.php @@ -29,7 +29,7 @@ use OC\AppFramework\Core\API; use OC\AppFramework\Middleware\MiddlewareDispatcher; // FIXME: loading pimpl correctly from 3rdparty repo -require_once __DIR__ . '/../../../../3rdparty/Pimple/Pimple.php'; +require_once __DIR__ . '/../../../3rdparty/Pimple/Pimple.php'; require_once __DIR__ . "/classloader.php"; From 0fa2e1b3d91d243452ffdfd36dbd0bed3f27e387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 20 Aug 2013 12:48:45 +0200 Subject: [PATCH 009/198] there is no HttpMiddleware --- lib/appframework/dependencyinjection/dicontainer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/appframework/dependencyinjection/dicontainer.php b/lib/appframework/dependencyinjection/dicontainer.php index d6cf4d5502..69c645b1be 100644 --- a/lib/appframework/dependencyinjection/dicontainer.php +++ b/lib/appframework/dependencyinjection/dicontainer.php @@ -29,7 +29,6 @@ use OC\AppFramework\Http\Request; use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Core\API; use OC\AppFramework\Middleware\MiddlewareDispatcher; -use OC\AppFramework\Middleware\Http\HttpMiddleware; use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Utility\TimeFactory; From 0fa8f380767369b4aa85f5944a8e921009b1ed27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 20 Aug 2013 16:51:12 +0200 Subject: [PATCH 010/198] fixing broken test --- tests/lib/appframework/AppTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/appframework/AppTest.php b/tests/lib/appframework/AppTest.php index 6e647f68e6..dcf0e6f77e 100644 --- a/tests/lib/appframework/AppTest.php +++ b/tests/lib/appframework/AppTest.php @@ -46,7 +46,7 @@ class AppTest extends \PHPUnit_Framework_TestCase { private $controllerMethod; protected function setUp() { - $this->container = new \Pimple(); + $this->container = new \OC\AppFramework\DependencyInjection\DIContainer('test'); $this->controller = $this->getMockBuilder( 'OC\AppFramework\Controller\Controller') ->disableOriginalConstructor() From cdada78aa4acd2880e0344a476d3c1d838645ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 20 Aug 2013 17:20:36 +0200 Subject: [PATCH 011/198] typos & unused var fixed --- lib/appframework/http/dispatcher.php | 11 +++++------ lib/appframework/http/downloadresponse.php | 1 - 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/appframework/http/dispatcher.php b/lib/appframework/http/dispatcher.php index ab5644274f..183854650f 100644 --- a/lib/appframework/http/dispatcher.php +++ b/lib/appframework/http/dispatcher.php @@ -29,7 +29,7 @@ use \OC\AppFramework\Middleware\MiddlewareDispatcher; /** - * Class to dispatch the request to the middleware disptacher + * Class to dispatch the request to the middleware dispatcher */ class Dispatcher { @@ -67,11 +67,10 @@ class Dispatcher { $methodName); $response = $controller->$methodName(); - - // if an exception appears, the middleware checks if it can handle the - // exception and creates a response. If no response is created, it is - // assumed that theres no middleware who can handle it and the error is - // thrown again + // if an exception appears, the middleware checks if it can handle the + // exception and creates a response. If no response is created, it is + // assumed that theres no middleware who can handle it and the error is + // thrown again } catch(\Exception $exception){ $response = $this->middlewareDispatcher->afterException( $controller, $methodName, $exception); diff --git a/lib/appframework/http/downloadresponse.php b/lib/appframework/http/downloadresponse.php index 5a0db325fe..096e4fc833 100644 --- a/lib/appframework/http/downloadresponse.php +++ b/lib/appframework/http/downloadresponse.php @@ -30,7 +30,6 @@ namespace OC\AppFramework\Http; */ abstract class DownloadResponse extends Response { - private $content; private $filename; private $contentType; From 93194bb39617d4b11a0a84b8cd4caf0491155961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 20 Aug 2013 17:21:14 +0200 Subject: [PATCH 012/198] Introducing IContainer into public api --- .../dependencyinjection/dicontainer.php | 22 +++++----- lib/appframework/utility/simplecontainer.php | 44 +++++++++++++++++++ tests/lib/appframework/classloader.php | 9 ++++ 3 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 lib/appframework/utility/simplecontainer.php diff --git a/lib/appframework/dependencyinjection/dicontainer.php b/lib/appframework/dependencyinjection/dicontainer.php index 69c645b1be..88ad2cd414 100644 --- a/lib/appframework/dependencyinjection/dicontainer.php +++ b/lib/appframework/dependencyinjection/dicontainer.php @@ -30,19 +30,11 @@ use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Core\API; use OC\AppFramework\Middleware\MiddlewareDispatcher; use OC\AppFramework\Middleware\Security\SecurityMiddleware; +use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; -// register 3rdparty autoloaders -require_once __DIR__ . '/../../../3rdparty/Pimple/Pimple.php'; - -/** - * This class extends Pimple (http://pimple.sensiolabs.org/) for reusability - * To use this class, extend your own container from this. Should you require it - * you can overwrite the dependencies with your own classes by simply redefining - * a dependency - */ -class DIContainer extends \Pimple { +class DIContainer extends SimpleContainer { /** @@ -61,8 +53,14 @@ class DIContainer extends \Pimple { * Http */ $this['Request'] = $this->share(function($c) { - $params = json_decode(file_get_contents('php://input'), true); - $params = is_array($params) ? $params: array(); + + $params = array(); + + // we json decode the body only in case of content type json + if (isset($_SERVER['CONTENT_TYPE']) && stripos($_SERVER['CONTENT_TYPE'],'json') === true ) { + $params = json_decode(file_get_contents('php://input'), true); + $params = is_array($params) ? $params: array(); + } return new Request( array( diff --git a/lib/appframework/utility/simplecontainer.php b/lib/appframework/utility/simplecontainer.php new file mode 100644 index 0000000000..04b6cd727b --- /dev/null +++ b/lib/appframework/utility/simplecontainer.php @@ -0,0 +1,44 @@ +offsetGet($name); + } + + function registerParameter($name, $value) + { + $this[$name] = $value; + } + + /** + * The given closure is call the first time the given service is queried. + * The closure has to return the instance for the given service. + * Created instance will be cached in case $shared is true. + * + * @param string $name name of the service to register another backend for + * @param callable $closure the closure to be called on service creation + */ + function registerService($name, \Closure $closure, $shared = true) + { + if ($shared) { + $this[$name] = \Pimple::share($closure); + } else { + $this[$name] = $closure; + } + } +} diff --git a/tests/lib/appframework/classloader.php b/tests/lib/appframework/classloader.php index ae485e67b2..cd9f893df3 100644 --- a/tests/lib/appframework/classloader.php +++ b/tests/lib/appframework/classloader.php @@ -32,6 +32,15 @@ spl_autoload_register(function ($className){ } } + if (strpos($className, 'OCP\\') === 0) { + $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); + $relPath = __DIR__ . '/../../../lib/public' . $path; + + if(file_exists($relPath)){ + require_once $relPath; + } + } + // FIXME: this will most probably not work anymore if (strpos($className, 'OCA\\') === 0) { From 6e1946ab00cca760d555222df008ba92b0185eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 20 Aug 2013 17:22:33 +0200 Subject: [PATCH 013/198] Introducing IContainer into public api --- lib/public/core/icontainer.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lib/public/core/icontainer.php diff --git a/lib/public/core/icontainer.php b/lib/public/core/icontainer.php new file mode 100644 index 0000000000..a6c93abec6 --- /dev/null +++ b/lib/public/core/icontainer.php @@ -0,0 +1,19 @@ + Date: Tue, 20 Aug 2013 17:53:58 +0200 Subject: [PATCH 014/198] Introducing IRequest --- lib/appframework/http/request.php | 111 +++++++++++++++++++++++++++++- lib/public/core/irequest.php | 88 +++++++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 lib/public/core/irequest.php diff --git a/lib/appframework/http/request.php b/lib/appframework/http/request.php index 7d024c8605..ab72a8db69 100644 --- a/lib/appframework/http/request.php +++ b/lib/appframework/http/request.php @@ -22,12 +22,14 @@ namespace OC\AppFramework\Http; +use OCP\Core\IRequest; + /** * Class for accessing variables in the request. * This class provides an immutable object with request variables. */ -class Request implements \ArrayAccess, \Countable { +class Request implements \ArrayAccess, \Countable, IRequest { protected $items = array(); protected $allowedKeys = array( @@ -214,4 +216,111 @@ class Request implements \ArrayAccess, \Countable { return null; } + /** + * Lets you access post and get parameters by the index + * In case of json requests the encoded json body is accessed + * + * @param string $key the key which you want to access in the URL Parameter + * placeholder, $_POST or $_GET array. + * The priority how they're returned is the following: + * 1. URL parameters + * 2. POST parameters + * 3. GET parameters + * @param mixed $default If the key is not found, this value will be returned + * @return mixed the content of the array + */ + public function getParam($key, $default = null) + { + return isset($this->parameters[$key]) + ? $this->parameters[$key] + : $default; + } + + /** + * Returns all params that were received, be it from the request + * (as GET or POST) or throuh the URL by the route + * @return array the array with all parameters + */ + public function getParams() + { + return $this->parameters; + } + + /** + * Returns the method of the request + * @return string the method of the request (POST, GET, etc) + */ + public function getMethod() + { + return $this->method; + } + + /** + * Shortcut for accessing an uploaded file through the $_FILES array + * @param string $key the key that will be taken from the $_FILES array + * @return array the file in the $_FILES element + */ + public function getUploadedFile($key) + { + return isset($this->files[$key]) ? $this->files[$key] : null; + } + + /** + * Shortcut for getting env variables + * @param string $key the key that will be taken from the $_ENV array + * @return array the value in the $_ENV element + */ + public function getEnv($key) + { + return isset($this->env[$key]) ? $this->env[$key] : null; + } + + /** + * Shortcut for getting session variables + * @param string $key the key that will be taken from the $_SESSION array + * @return array the value in the $_SESSION element + */ + function getSession($key) + { + return isset($this->session[$key]) ? $this->session[$key] : null; + } + + /** + * Shortcut for getting cookie variables + * @param string $key the key that will be taken from the $_COOKIE array + * @return array the value in the $_COOKIE element + */ + function getCookie($key) + { + return isset($this->cookies[$key]) ? $this->cookies[$key] : null; + } + + /** + * Returns the request body content. + * + * @param Boolean $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream. + * + * @throws \LogicException + */ + function getContent($asResource = false) + { + return null; +// if (false === $this->content || (true === $asResource && null !== $this->content)) { +// throw new \LogicException('getContent() can only be called once when using the resource return type.'); +// } +// +// if (true === $asResource) { +// $this->content = false; +// +// return fopen('php://input', 'rb'); +// } +// +// if (null === $this->content) { +// $this->content = file_get_contents('php://input'); +// } +// +// return $this->content; + } } diff --git a/lib/public/core/irequest.php b/lib/public/core/irequest.php new file mode 100644 index 0000000000..f283e9cb25 --- /dev/null +++ b/lib/public/core/irequest.php @@ -0,0 +1,88 @@ + Date: Tue, 20 Aug 2013 21:05:55 +0200 Subject: [PATCH 015/198] controller reuses IRequest methods --- lib/appframework/controller/controller.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/appframework/controller/controller.php b/lib/appframework/controller/controller.php index 3e8166050d..f6f34618ec 100644 --- a/lib/appframework/controller/controller.php +++ b/lib/appframework/controller/controller.php @@ -63,9 +63,7 @@ abstract class Controller { * @return mixed the content of the array */ public function params($key, $default=null){ - return isset($this->request->parameters[$key]) - ? $this->request->parameters[$key] - : $default; + return $this->request->getParam($key, $default); } @@ -75,7 +73,7 @@ abstract class Controller { * @return array the array with all parameters */ public function getParams() { - return $this->request->parameters; + return $this->request->getParams(); } @@ -84,7 +82,7 @@ abstract class Controller { * @return string the method of the request (POST, GET, etc) */ public function method() { - return $this->request->method; + return $this->request->getMethod(); } @@ -94,7 +92,7 @@ abstract class Controller { * @return array the file in the $_FILES element */ public function getUploadedFile($key) { - return isset($this->request->files[$key]) ? $this->request->files[$key] : null; + return $this->request->getUploadedFile($key); } @@ -104,7 +102,7 @@ abstract class Controller { * @return array the value in the $_ENV element */ public function env($key) { - return isset($this->request->env[$key]) ? $this->request->env[$key] : null; + return $this->request->getEnv($key); } @@ -114,7 +112,7 @@ abstract class Controller { * @return array the value in the $_SESSION element */ public function session($key) { - return isset($this->request->session[$key]) ? $this->request->session[$key] : null; + return $this->request->getSession($key); } @@ -124,7 +122,7 @@ abstract class Controller { * @return array the value in the $_COOKIE element */ public function cookie($key) { - return isset($this->request->cookies[$key]) ? $this->request->cookies[$key] : null; + return $this->request->getCookie($key); } From 395deacc6760564544a76338023d9b0bf39e0bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 20 Aug 2013 21:21:21 +0200 Subject: [PATCH 016/198] reducing controller annotations to: @PublicPage - No user logon is expected @NoAdminRequired - the login user requires no admin rights @NoCSRFRequired - the incoming request will not check for CSRF token --- .../security/securitymiddleware.php | 19 +-- .../security/SecurityMiddlewareTest.php | 156 ++++-------------- 2 files changed, 41 insertions(+), 134 deletions(-) diff --git a/lib/appframework/middleware/security/securitymiddleware.php b/lib/appframework/middleware/security/securitymiddleware.php index 7a715f309a..52818b1b53 100644 --- a/lib/appframework/middleware/security/securitymiddleware.php +++ b/lib/appframework/middleware/security/securitymiddleware.php @@ -77,25 +77,20 @@ class SecurityMiddleware extends Middleware { $this->api->activateNavigationEntry(); // security checks - if(!$annotationReader->hasAnnotation('IsLoggedInExemption')) { + $isPublicPage = $annotationReader->hasAnnotation('PublicPage'); + if(!$isPublicPage) { if(!$this->api->isLoggedIn()) { throw new SecurityException('Current user is not logged in', Http::STATUS_UNAUTHORIZED); } - } - if(!$annotationReader->hasAnnotation('IsAdminExemption')) { - if(!$this->api->isAdminUser($this->api->getUserId())) { - throw new SecurityException('Logged in user must be an admin', Http::STATUS_FORBIDDEN); + if(!$annotationReader->hasAnnotation('NoAdminRequired')) { + if(!$this->api->isAdminUser($this->api->getUserId())) { + throw new SecurityException('Logged in user must be an admin', Http::STATUS_FORBIDDEN); + } } } - if(!$annotationReader->hasAnnotation('IsSubAdminExemption')) { - if(!$this->api->isSubAdminUser($this->api->getUserId())) { - throw new SecurityException('Logged in user must be a subadmin', Http::STATUS_FORBIDDEN); - } - } - - if(!$annotationReader->hasAnnotation('CSRFExemption')) { + if(!$annotationReader->hasAnnotation('NoCSRFRequired')) { if(!$this->api->passesCSRFCheck()) { throw new SecurityException('CSRF check failed', Http::STATUS_PRECONDITION_FAILED); } diff --git a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php index 0b2103564e..90a19c9999 100644 --- a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php +++ b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php @@ -80,67 +80,27 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { /** - * @IsLoggedInExemption - * @CSRFExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @PublicPage + * @NoCSRFRequired */ public function testSetNavigationEntry(){ $this->checkNavEntry('testSetNavigationEntry', true); } - private function ajaxExceptionCheck($method, $shouldBeAjax=false){ - $api = $this->getAPI(); - $api->expects($this->any()) - ->method('passesCSRFCheck') - ->will($this->returnValue(false)); - - $sec = new SecurityMiddleware($api, $this->request); - - try { - $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', - $method); - } catch (SecurityException $ex){ - if($shouldBeAjax){ - $this->assertTrue($ex->isAjax()); - } else { - $this->assertFalse($ex->isAjax()); - } - - } - } - - - /** - * @Ajax - * @IsLoggedInExemption - * @CSRFExemption - * @IsAdminExemption - * @IsSubAdminExemption - */ - public function testAjaxException(){ - $this->ajaxExceptionCheck('testAjaxException'); - } - - - /** - * @IsLoggedInExemption - * @CSRFExemption - * @IsAdminExemption - * @IsSubAdminExemption - */ - public function testNoAjaxException(){ - $this->ajaxExceptionCheck('testNoAjaxException'); - } - - private function ajaxExceptionStatus($method, $test, $status) { $api = $this->getAPI(); $api->expects($this->any()) ->method($test) ->will($this->returnValue(false)); + // isAdminUser requires isLoggedIn call to return true + if ($test === 'isAdminUser') { + $api->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + } + $sec = new SecurityMiddleware($api, $this->request); try { @@ -151,9 +111,6 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { } } - /** - * @Ajax - */ public function testAjaxStatusLoggedInCheck() { $this->ajaxExceptionStatus( 'testAjaxStatusLoggedInCheck', @@ -163,8 +120,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { } /** - * @Ajax - * @IsLoggedInExemption + * @NoCSRFRequired + * @NoAdminRequired */ public function testAjaxNotAdminCheck() { $this->ajaxExceptionStatus( @@ -175,23 +132,7 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { } /** - * @Ajax - * @IsLoggedInExemption - * @IsAdminExemption - */ - public function testAjaxNotSubAdminCheck() { - $this->ajaxExceptionStatus( - 'testAjaxNotSubAdminCheck', - 'isSubAdminUser', - Http::STATUS_FORBIDDEN - ); - } - - /** - * @Ajax - * @IsLoggedInExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @PublicPage */ public function testAjaxStatusCSRFCheck() { $this->ajaxExceptionStatus( @@ -202,11 +143,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { } /** - * @Ajax - * @CSRFExemption - * @IsLoggedInExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @PublicPage + * @NoCSRFRequired */ public function testAjaxStatusAllGood() { $this->ajaxExceptionStatus( @@ -231,11 +169,10 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ); } + /** - * @IsLoggedInExemption - * @CSRFExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @PublicPage + * @NoCSRFRequired */ public function testNoChecks(){ $api = $this->getAPI(); @@ -245,9 +182,6 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { $api->expects($this->never()) ->method('isAdminUser') ->will($this->returnValue(true)); - $api->expects($this->never()) - ->method('isSubAdminUser') - ->will($this->returnValue(true)); $api->expects($this->never()) ->method('isLoggedIn') ->will($this->returnValue(true)); @@ -264,10 +198,19 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ->method($expects) ->will($this->returnValue(!$shouldFail)); + // admin check requires login + if ($expects === 'isAdminUser') { + $api->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + } + $sec = new SecurityMiddleware($api, $this->request); if($shouldFail){ $this->setExpectedException('\OC\AppFramework\Middleware\Security\SecurityException'); + } else { + $this->setExpectedException(null); } $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method); @@ -275,9 +218,7 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { /** - * @IsLoggedInExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @PublicPage */ public function testCsrfCheck(){ $this->securityCheck('testCsrfCheck', 'passesCSRFCheck'); @@ -285,9 +226,7 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { /** - * @IsLoggedInExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @PublicPage */ public function testFailCsrfCheck(){ $this->securityCheck('testFailCsrfCheck', 'passesCSRFCheck', true); @@ -295,9 +234,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { /** - * @CSRFExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @NoCSRFRequired + * @NoAdminRequired */ public function testLoggedInCheck(){ $this->securityCheck('testLoggedInCheck', 'isLoggedIn'); @@ -305,9 +243,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { /** - * @CSRFExemption - * @IsAdminExemption - * @IsSubAdminExemption + * @NoCSRFRequired + * @NoAdminRequired */ public function testFailLoggedInCheck(){ $this->securityCheck('testFailLoggedInCheck', 'isLoggedIn', true); @@ -315,9 +252,7 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { /** - * @IsLoggedInExemption - * @CSRFExemption - * @IsSubAdminExemption + * @NoCSRFRequired */ public function testIsAdminCheck(){ $this->securityCheck('testIsAdminCheck', 'isAdminUser'); @@ -325,36 +260,13 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { /** - * @IsLoggedInExemption - * @CSRFExemption - * @IsSubAdminExemption + * @NoCSRFRequired */ public function testFailIsAdminCheck(){ $this->securityCheck('testFailIsAdminCheck', 'isAdminUser', true); } - /** - * @IsLoggedInExemption - * @CSRFExemption - * @IsAdminExemption - */ - public function testIsSubAdminCheck(){ - $this->securityCheck('testIsSubAdminCheck', 'isSubAdminUser'); - } - - - /** - * @IsLoggedInExemption - * @CSRFExemption - * @IsAdminExemption - */ - public function testFailIsSubAdminCheck(){ - $this->securityCheck('testFailIsSubAdminCheck', 'isSubAdminUser', true); - } - - - public function testAfterExceptionNotCaughtThrowsItAgain(){ $ex = new \Exception(); $this->setExpectedException('\Exception'); From 33db8a3089760947eec93149a2029164b676eae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 21 Aug 2013 00:41:20 +0200 Subject: [PATCH 017/198] kill superfluent classloader from tests - this approach might be of interest within the apps --- tests/lib/appframework/AppTest.php | 2 +- tests/lib/appframework/classloader.php | 54 ------------------- .../controller/ControllerTest.php | 5 +- .../dependencyinjection/DIContainerTest.php | 2 +- .../lib/appframework/http/DispatcherTest.php | 6 +-- .../http/DownloadResponseTest.php | 2 +- tests/lib/appframework/http/HttpTest.php | 2 +- .../appframework/http/JSONResponseTest.php | 4 +- .../http/RedirectResponseTest.php | 2 +- tests/lib/appframework/http/RequestTest.php | 2 - tests/lib/appframework/http/ResponseTest.php | 7 +-- .../http/TemplateResponseTest.php | 12 +++-- .../middleware/MiddlewareDispatcherTest.php | 6 +-- .../middleware/MiddlewareTest.php | 11 ++-- .../security/SecurityMiddlewareTest.php | 6 +-- .../lib/appframework/routing/RoutingTest.php | 1 - .../utility/MethodAnnotationReaderTest.php | 3 -- 17 files changed, 33 insertions(+), 94 deletions(-) delete mode 100644 tests/lib/appframework/classloader.php diff --git a/tests/lib/appframework/AppTest.php b/tests/lib/appframework/AppTest.php index dcf0e6f77e..e8ae8c8f67 100644 --- a/tests/lib/appframework/AppTest.php +++ b/tests/lib/appframework/AppTest.php @@ -30,7 +30,7 @@ use OC\AppFramework\Middleware\MiddlewareDispatcher; // FIXME: loading pimpl correctly from 3rdparty repo require_once __DIR__ . '/../../../3rdparty/Pimple/Pimple.php'; -require_once __DIR__ . "/classloader.php"; +//require_once __DIR__ . "/classloader.php"; class AppTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/lib/appframework/classloader.php b/tests/lib/appframework/classloader.php deleted file mode 100644 index cd9f893df3..0000000000 --- a/tests/lib/appframework/classloader.php +++ /dev/null @@ -1,54 +0,0 @@ -. - * - */ - -// to execute without ownCloud, we need to create our own class loader -spl_autoload_register(function ($className){ - if (strpos($className, 'OC\\AppFramework') === 0) { - $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); - $relPath = __DIR__ . '/../../../lib/' . $path; - - if(file_exists($relPath)){ - require_once $relPath; - } - } - - if (strpos($className, 'OCP\\') === 0) { - $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); - $relPath = __DIR__ . '/../../../lib/public' . $path; - - if(file_exists($relPath)){ - require_once $relPath; - } - } - - // FIXME: this will most probably not work anymore - if (strpos($className, 'OCA\\') === 0) { - - $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); - $relPath = __DIR__ . '/../..' . $path; - - if(file_exists($relPath)){ - require_once $relPath; - } - } -}); diff --git a/tests/lib/appframework/controller/ControllerTest.php b/tests/lib/appframework/controller/ControllerTest.php index d8357c2a68..246371d249 100644 --- a/tests/lib/appframework/controller/ControllerTest.php +++ b/tests/lib/appframework/controller/ControllerTest.php @@ -25,12 +25,11 @@ namespace Test\AppFramework\Controller; use OC\AppFramework\Http\Request; -use OC\AppFramework\Http\JSONResponse; -use OC\AppFramework\Http\TemplateResponse; use OC\AppFramework\Controller\Controller; +use OCP\AppFramework\Http\TemplateResponse; -require_once(__DIR__ . "/../classloader.php"); +//require_once __DIR__ . "/../classloader.php"; class ChildController extends Controller {}; diff --git a/tests/lib/appframework/dependencyinjection/DIContainerTest.php b/tests/lib/appframework/dependencyinjection/DIContainerTest.php index ce346f0a76..25fdd20283 100644 --- a/tests/lib/appframework/dependencyinjection/DIContainerTest.php +++ b/tests/lib/appframework/dependencyinjection/DIContainerTest.php @@ -29,7 +29,7 @@ namespace OC\AppFramework\DependencyInjection; use \OC\AppFramework\Http\Request; -require_once(__DIR__ . "/../classloader.php"); +//require_once(__DIR__ . "/../classloader.php"); class DIContainerTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/lib/appframework/http/DispatcherTest.php b/tests/lib/appframework/http/DispatcherTest.php index 2e3db11050..849b0ca97a 100644 --- a/tests/lib/appframework/http/DispatcherTest.php +++ b/tests/lib/appframework/http/DispatcherTest.php @@ -27,7 +27,7 @@ namespace OC\AppFramework\Http; use OC\AppFramework\Core\API; use OC\AppFramework\Middleware\MiddlewareDispatcher; -require_once(__DIR__ . "/../classloader.php"); +//require_once(__DIR__ . "/../classloader.php"); class DispatcherTest extends \PHPUnit_Framework_TestCase { @@ -69,7 +69,7 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { $this->http, $this->middlewareDispatcher); $this->response = $this->getMockBuilder( - '\OC\AppFramework\Http\Response') + '\OCP\AppFramework\Http\Response') ->disableOriginalConstructor() ->getMock(); @@ -207,7 +207,7 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = array('hell' => 'yeah'); - $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true, false); + $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true, false); $this->setExpectedException('\Exception'); $response = $this->dispatcher->dispatch($this->controller, diff --git a/tests/lib/appframework/http/DownloadResponseTest.php b/tests/lib/appframework/http/DownloadResponseTest.php index 103cfe7588..64fe7992b6 100644 --- a/tests/lib/appframework/http/DownloadResponseTest.php +++ b/tests/lib/appframework/http/DownloadResponseTest.php @@ -25,7 +25,7 @@ namespace OC\AppFramework\Http; -require_once(__DIR__ . "/../classloader.php"); +//require_once(__DIR__ . "/../classloader.php"); class ChildDownloadResponse extends DownloadResponse {}; diff --git a/tests/lib/appframework/http/HttpTest.php b/tests/lib/appframework/http/HttpTest.php index 306bc3caf4..382d511b11 100644 --- a/tests/lib/appframework/http/HttpTest.php +++ b/tests/lib/appframework/http/HttpTest.php @@ -25,7 +25,7 @@ namespace OC\AppFramework\Http; -require_once(__DIR__ . "/../classloader.php"); +//require_once(__DIR__ . "/../classloader.php"); diff --git a/tests/lib/appframework/http/JSONResponseTest.php b/tests/lib/appframework/http/JSONResponseTest.php index d15e08f6ce..534c54cbce 100644 --- a/tests/lib/appframework/http/JSONResponseTest.php +++ b/tests/lib/appframework/http/JSONResponseTest.php @@ -27,7 +27,9 @@ namespace OC\AppFramework\Http; -require_once(__DIR__ . "/../classloader.php"); +use OCP\AppFramework\Http\JSONResponse; + +//require_once(__DIR__ . "/../classloader.php"); diff --git a/tests/lib/appframework/http/RedirectResponseTest.php b/tests/lib/appframework/http/RedirectResponseTest.php index a8577feed2..1946655b0f 100644 --- a/tests/lib/appframework/http/RedirectResponseTest.php +++ b/tests/lib/appframework/http/RedirectResponseTest.php @@ -25,7 +25,7 @@ namespace OC\AppFramework\Http; -require_once(__DIR__ . "/../classloader.php"); +//require_once(__DIR__ . "/../classloader.php"); diff --git a/tests/lib/appframework/http/RequestTest.php b/tests/lib/appframework/http/RequestTest.php index c1f56c0163..0371c870cf 100644 --- a/tests/lib/appframework/http/RequestTest.php +++ b/tests/lib/appframework/http/RequestTest.php @@ -9,8 +9,6 @@ namespace OC\AppFramework\Http; -require_once(__DIR__ . "/../classloader.php"); - class RequestTest extends \PHPUnit_Framework_TestCase { public function testRequestAccessors() { diff --git a/tests/lib/appframework/http/ResponseTest.php b/tests/lib/appframework/http/ResponseTest.php index 621ba66545..7e09086f80 100644 --- a/tests/lib/appframework/http/ResponseTest.php +++ b/tests/lib/appframework/http/ResponseTest.php @@ -25,13 +25,14 @@ namespace OC\AppFramework\Http; -require_once(__DIR__ . "/../classloader.php"); - +use OCP\AppFramework\Http\Response; class ResponseTest extends \PHPUnit_Framework_TestCase { - + /** + * @var \OCP\AppFramework\Http\Response + */ private $childResponse; protected function setUp(){ diff --git a/tests/lib/appframework/http/TemplateResponseTest.php b/tests/lib/appframework/http/TemplateResponseTest.php index 30684725b7..3c6d29cd33 100644 --- a/tests/lib/appframework/http/TemplateResponseTest.php +++ b/tests/lib/appframework/http/TemplateResponseTest.php @@ -24,15 +24,19 @@ namespace OC\AppFramework\Http; -use OC\AppFramework\Core\API; - - -require_once(__DIR__ . "/../classloader.php"); +use OCP\AppFramework\Http\TemplateResponse; class TemplateResponseTest extends \PHPUnit_Framework_TestCase { + /** + * @var \OCP\AppFramework\Http\TemplateResponse + */ private $tpl; + + /** + * @var \OCP\AppFramework\IApi + */ private $api; protected function setUp() { diff --git a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php index bfa54a48ea..d1b2fedee5 100644 --- a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php +++ b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php @@ -24,14 +24,10 @@ namespace OC\AppFramework; -use OC\AppFramework\Controller\Controller; use OC\AppFramework\Http\Request; -use OC\AppFramework\Http\Response; use OC\AppFramework\Middleware\Middleware; use OC\AppFramework\Middleware\MiddlewareDispatcher; - - -require_once(__DIR__ . "/../classloader.php"); +use OCP\AppFramework\Http\Response; // needed to test ordering diff --git a/tests/lib/appframework/middleware/MiddlewareTest.php b/tests/lib/appframework/middleware/MiddlewareTest.php index 1adce6b3d4..5e2930ac6a 100644 --- a/tests/lib/appframework/middleware/MiddlewareTest.php +++ b/tests/lib/appframework/middleware/MiddlewareTest.php @@ -28,14 +28,14 @@ use OC\AppFramework\Http\Request; use OC\AppFramework\Middleware\Middleware; -require_once(__DIR__ . "/../classloader.php"); - - class ChildMiddleware extends Middleware {}; class MiddlewareTest extends \PHPUnit_Framework_TestCase { + /** + * @var Middleware + */ private $middleware; private $controller; private $exception; @@ -50,12 +50,13 @@ class MiddlewareTest extends \PHPUnit_Framework_TestCase { $this->controller = $this->getMock('OC\AppFramework\Controller\Controller', array(), array($this->api, new Request())); $this->exception = new \Exception(); - $this->response = $this->getMock('OC\AppFramework\Http\Response'); + $this->response = $this->getMock('OCP\AppFramework\Http\Response'); } public function testBeforeController() { - $this->middleware->beforeController($this->controller, null, $this->exception); + $this->middleware->beforeController($this->controller, null); + $this->assertNull(null); } diff --git a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php index 90a19c9999..3ed44282a7 100644 --- a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php +++ b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php @@ -27,11 +27,7 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Http\Http; use OC\AppFramework\Http\Request; use OC\AppFramework\Http\RedirectResponse; -use OC\AppFramework\Http\JSONResponse; -use OC\AppFramework\Middleware\Middleware; - - -require_once(__DIR__ . "/../../classloader.php"); +use OCP\AppFramework\Http\JSONResponse; class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/lib/appframework/routing/RoutingTest.php b/tests/lib/appframework/routing/RoutingTest.php index 92ad461471..a7aa922db1 100644 --- a/tests/lib/appframework/routing/RoutingTest.php +++ b/tests/lib/appframework/routing/RoutingTest.php @@ -5,7 +5,6 @@ namespace OC\AppFramework\Routing; use OC\AppFramework\DependencyInjection\DIContainer; use OC\AppFramework\routing\RouteConfig; -require_once(__DIR__ . "/../classloader.php"); class RouteConfigTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/lib/appframework/utility/MethodAnnotationReaderTest.php b/tests/lib/appframework/utility/MethodAnnotationReaderTest.php index bcdcf3de37..c68812aa5c 100644 --- a/tests/lib/appframework/utility/MethodAnnotationReaderTest.php +++ b/tests/lib/appframework/utility/MethodAnnotationReaderTest.php @@ -25,9 +25,6 @@ namespace OC\AppFramework\Utility; -require_once __DIR__ . "/../classloader.php"; - - class MethodAnnotationReaderTest extends \PHPUnit_Framework_TestCase { From aa979f5dff4234a3db9e6fb1ddc50335c04c194b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 21 Aug 2013 00:44:39 +0200 Subject: [PATCH 018/198] cleanup of tests --- tests/lib/appframework/AppTest.php | 8 -------- .../middleware/MiddlewareDispatcherTest.php | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/lib/appframework/AppTest.php b/tests/lib/appframework/AppTest.php index e8ae8c8f67..80abaefc43 100644 --- a/tests/lib/appframework/AppTest.php +++ b/tests/lib/appframework/AppTest.php @@ -24,14 +24,6 @@ namespace OC\AppFramework; -use OC\AppFramework\Http\Request; -use OC\AppFramework\Core\API; -use OC\AppFramework\Middleware\MiddlewareDispatcher; - -// FIXME: loading pimpl correctly from 3rdparty repo -require_once __DIR__ . '/../../../3rdparty/Pimple/Pimple.php'; -//require_once __DIR__ . "/classloader.php"; - class AppTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php index d1b2fedee5..43727846dc 100644 --- a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php +++ b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php @@ -99,6 +99,15 @@ class TestMiddleware extends Middleware { class MiddlewareDispatcherTest extends \PHPUnit_Framework_TestCase { + public $exception; + public $response; + private $out; + private $method; + private $controller; + + /** + * @var MiddlewareDispatcher + */ private $dispatcher; @@ -107,7 +116,7 @@ class MiddlewareDispatcherTest extends \PHPUnit_Framework_TestCase { $this->controller = $this->getControllerMock(); $this->method = 'method'; $this->response = new Response(); - $this->output = 'hi'; + $this->out = 'hi'; $this->exception = new \Exception(); } @@ -202,11 +211,11 @@ class MiddlewareDispatcherTest extends \PHPUnit_Framework_TestCase { public function testBeforeOutputCorrectArguments(){ $m1 = $this->getMiddleware(); - $this->dispatcher->beforeOutput($this->controller, $this->method, $this->output); + $this->dispatcher->beforeOutput($this->controller, $this->method, $this->out); $this->assertEquals($this->controller, $m1->controller); $this->assertEquals($this->method, $m1->methodName); - $this->assertEquals($this->output, $m1->output); + $this->assertEquals($this->out, $m1->output); } @@ -248,7 +257,7 @@ class MiddlewareDispatcherTest extends \PHPUnit_Framework_TestCase { $m1 = $this->getMiddleware(); $m2 = $this->getMiddleware(); - $this->dispatcher->beforeOutput($this->controller, $this->method, $this->output); + $this->dispatcher->beforeOutput($this->controller, $this->method, $this->out); $this->assertEquals(2, $m1->beforeOutputOrder); $this->assertEquals(1, $m2->beforeOutputOrder); @@ -268,7 +277,7 @@ class MiddlewareDispatcherTest extends \PHPUnit_Framework_TestCase { $this->dispatcher->registerMiddleware($m3); - $this->dispatcher->beforeOutput($this->controller, $this->method, $this->output); + $this->dispatcher->beforeOutput($this->controller, $this->method, $this->out); $this->assertEquals(2, $m1->beforeOutputOrder); $this->assertEquals(1, $m2->beforeOutputOrder); From ba029ef4b27cfeabbc67523131fa473397b77f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 21 Aug 2013 00:58:15 +0200 Subject: [PATCH 019/198] initial setup of the server container --- lib/base.php | 8 ++++++++ lib/public/core/iservercontainer.php | 14 ++++++++++++++ lib/server.php | 15 +++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 lib/public/core/iservercontainer.php create mode 100644 lib/server.php diff --git a/lib/base.php b/lib/base.php index eaee842465..a81f1a59b8 100644 --- a/lib/base.php +++ b/lib/base.php @@ -84,6 +84,11 @@ class OC { */ public static $loader = null; + /** + * @var \OC\Server + */ + public static $server = null; + public static function initPaths() { // calculate the root directories OC::$SERVERROOT = str_replace("\\", '/', substr(__DIR__, 0, -4)); @@ -361,6 +366,9 @@ class OC { self::$loader->registerPrefix('Patchwork', '3rdparty'); spl_autoload_register(array(self::$loader, 'load')); + // setup the basic server + self::$server = new \OC\Server(); + // set some stuff //ob_start(); error_reporting(E_ALL | E_STRICT); diff --git a/lib/public/core/iservercontainer.php b/lib/public/core/iservercontainer.php new file mode 100644 index 0000000000..df744ab6fd --- /dev/null +++ b/lib/public/core/iservercontainer.php @@ -0,0 +1,14 @@ + Date: Wed, 21 Aug 2013 00:58:33 +0200 Subject: [PATCH 020/198] typo --- lib/public/core/irequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/core/irequest.php b/lib/public/core/irequest.php index f283e9cb25..fc2004d183 100644 --- a/lib/public/core/irequest.php +++ b/lib/public/core/irequest.php @@ -32,7 +32,7 @@ interface IRequest { /** * Returns all params that were received, be it from the request - * (as GET or POST) or throuh the URL by the route + * (as GET or POST) or through the URL by the route * @return array the array with all parameters */ public function getParams(); From 911bd3c16f508eb8f3cb9b03a5a21e2aa72ebf79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 21 Aug 2013 01:00:26 +0200 Subject: [PATCH 021/198] moving response classes over to OCP --- lib/appframework/controller/controller.php | 4 +- lib/appframework/http/dispatcher.php | 3 + lib/appframework/http/downloadresponse.php | 2 +- lib/appframework/http/http.php | 62 +------------ lib/appframework/http/redirectresponse.php | 2 + lib/appframework/middleware/middleware.php | 2 +- .../middleware/middlewaredispatcher.php | 2 +- .../security/securitymiddleware.php | 4 +- lib/public/appframework/http/http.php | 89 +++++++++++++++++++ .../appframework/http/jsonresponse.php | 2 +- .../appframework/http/response.php | 2 +- .../appframework/http/templateresponse.php | 2 +- 12 files changed, 105 insertions(+), 71 deletions(-) create mode 100644 lib/public/appframework/http/http.php rename lib/{ => public}/appframework/http/jsonresponse.php (98%) rename lib/{ => public}/appframework/http/response.php (98%) rename lib/{ => public}/appframework/http/templateresponse.php (98%) diff --git a/lib/appframework/controller/controller.php b/lib/appframework/controller/controller.php index f6f34618ec..a7498ba0e1 100644 --- a/lib/appframework/controller/controller.php +++ b/lib/appframework/controller/controller.php @@ -24,9 +24,9 @@ namespace OC\AppFramework\Controller; -use OC\AppFramework\Http\TemplateResponse; use OC\AppFramework\Http\Request; use OC\AppFramework\Core\API; +use OCP\AppFramework\Http\TemplateResponse; /** @@ -133,7 +133,7 @@ abstract class Controller { * @param string $renderAs user renders a full page, blank only your template * admin an entry in the admin settings * @param array $headers set additional headers in name/value pairs - * @return \OC\AppFramework\Http\TemplateResponse containing the page + * @return \OCP\AppFramework\Http\TemplateResponse containing the page */ public function render($templateName, array $params=array(), $renderAs='user', array $headers=array()){ diff --git a/lib/appframework/http/dispatcher.php b/lib/appframework/http/dispatcher.php index 183854650f..ea57a6860c 100644 --- a/lib/appframework/http/dispatcher.php +++ b/lib/appframework/http/dispatcher.php @@ -74,6 +74,9 @@ class Dispatcher { } catch(\Exception $exception){ $response = $this->middlewareDispatcher->afterException( $controller, $methodName, $exception); + if (is_null($response)) { + throw $exception; + } } $response = $this->middlewareDispatcher->afterController( diff --git a/lib/appframework/http/downloadresponse.php b/lib/appframework/http/downloadresponse.php index 096e4fc833..67b9542dba 100644 --- a/lib/appframework/http/downloadresponse.php +++ b/lib/appframework/http/downloadresponse.php @@ -28,7 +28,7 @@ namespace OC\AppFramework\Http; /** * Prompts the user to download the a file */ -abstract class DownloadResponse extends Response { +class DownloadResponse extends \OCP\AppFramework\Http\Response { private $filename; private $contentType; diff --git a/lib/appframework/http/http.php b/lib/appframework/http/http.php index 73f32d13b3..e00dc9cdc4 100644 --- a/lib/appframework/http/http.php +++ b/lib/appframework/http/http.php @@ -25,67 +25,7 @@ namespace OC\AppFramework\Http; -class Http { - - const STATUS_CONTINUE = 100; - const STATUS_SWITCHING_PROTOCOLS = 101; - const STATUS_PROCESSING = 102; - const STATUS_OK = 200; - const STATUS_CREATED = 201; - const STATUS_ACCEPTED = 202; - const STATUS_NON_AUTHORATIVE_INFORMATION = 203; - const STATUS_NO_CONTENT = 204; - const STATUS_RESET_CONTENT = 205; - const STATUS_PARTIAL_CONTENT = 206; - const STATUS_MULTI_STATUS = 207; - const STATUS_ALREADY_REPORTED = 208; - const STATUS_IM_USED = 226; - const STATUS_MULTIPLE_CHOICES = 300; - const STATUS_MOVED_PERMANENTLY = 301; - const STATUS_FOUND = 302; - const STATUS_SEE_OTHER = 303; - const STATUS_NOT_MODIFIED = 304; - const STATUS_USE_PROXY = 305; - const STATUS_RESERVED = 306; - const STATUS_TEMPORARY_REDIRECT = 307; - const STATUS_BAD_REQUEST = 400; - const STATUS_UNAUTHORIZED = 401; - const STATUS_PAYMENT_REQUIRED = 402; - const STATUS_FORBIDDEN = 403; - const STATUS_NOT_FOUND = 404; - const STATUS_METHOD_NOT_ALLOWED = 405; - const STATUS_NOT_ACCEPTABLE = 406; - const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407; - const STATUS_REQUEST_TIMEOUT = 408; - const STATUS_CONFLICT = 409; - const STATUS_GONE = 410; - const STATUS_LENGTH_REQUIRED = 411; - const STATUS_PRECONDITION_FAILED = 412; - const STATUS_REQUEST_ENTITY_TOO_LARGE = 413; - const STATUS_REQUEST_URI_TOO_LONG = 414; - const STATUS_UNSUPPORTED_MEDIA_TYPE = 415; - const STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416; - const STATUS_EXPECTATION_FAILED = 417; - const STATUS_IM_A_TEAPOT = 418; - const STATUS_UNPROCESSABLE_ENTITY = 422; - const STATUS_LOCKED = 423; - const STATUS_FAILED_DEPENDENCY = 424; - const STATUS_UPGRADE_REQUIRED = 426; - const STATUS_PRECONDITION_REQUIRED = 428; - const STATUS_TOO_MANY_REQUESTS = 429; - const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; - const STATUS_INTERNAL_SERVER_ERROR = 500; - const STATUS_NOT_IMPLEMENTED = 501; - const STATUS_BAD_GATEWAY = 502; - const STATUS_SERVICE_UNAVAILABLE = 503; - const STATUS_GATEWAY_TIMEOUT = 504; - const STATUS_HTTP_VERSION_NOT_SUPPORTED = 505; - const STATUS_VARIANT_ALSO_NEGOTIATES = 506; - const STATUS_INSUFFICIENT_STORAGE = 507; - const STATUS_LOOP_DETECTED = 508; - const STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509; - const STATUS_NOT_EXTENDED = 510; - const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511; +class Http extends \OCP\AppFramework\Http\Http{ private $server; private $protocolVersion; diff --git a/lib/appframework/http/redirectresponse.php b/lib/appframework/http/redirectresponse.php index 727e0fb642..688447f161 100644 --- a/lib/appframework/http/redirectresponse.php +++ b/lib/appframework/http/redirectresponse.php @@ -24,6 +24,8 @@ namespace OC\AppFramework\Http; +use OCP\AppFramework\Http\Response; + /** * Redirects to a different URL diff --git a/lib/appframework/middleware/middleware.php b/lib/appframework/middleware/middleware.php index 4df8849046..b12c03c3eb 100644 --- a/lib/appframework/middleware/middleware.php +++ b/lib/appframework/middleware/middleware.php @@ -24,7 +24,7 @@ namespace OC\AppFramework\Middleware; -use OC\AppFramework\Http\Response; +use OCP\AppFramework\Http\Response; /** diff --git a/lib/appframework/middleware/middlewaredispatcher.php b/lib/appframework/middleware/middlewaredispatcher.php index c2d16134dc..70ab108e6b 100644 --- a/lib/appframework/middleware/middlewaredispatcher.php +++ b/lib/appframework/middleware/middlewaredispatcher.php @@ -25,7 +25,7 @@ namespace OC\AppFramework\Middleware; use OC\AppFramework\Controller\Controller; -use OC\AppFramework\Http\Response; +use OCP\AppFramework\Http\Response; /** diff --git a/lib/appframework/middleware/security/securitymiddleware.php b/lib/appframework/middleware/security/securitymiddleware.php index 52818b1b53..4f1447e1af 100644 --- a/lib/appframework/middleware/security/securitymiddleware.php +++ b/lib/appframework/middleware/security/securitymiddleware.php @@ -27,12 +27,12 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Controller\Controller; use OC\AppFramework\Http\Http; use OC\AppFramework\Http\Request; -use OC\AppFramework\Http\Response; -use OC\AppFramework\Http\JSONResponse; use OC\AppFramework\Http\RedirectResponse; use OC\AppFramework\Utility\MethodAnnotationReader; use OC\AppFramework\Middleware\Middleware; use OC\AppFramework\Core\API; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\JSONResponse; /** diff --git a/lib/public/appframework/http/http.php b/lib/public/appframework/http/http.php new file mode 100644 index 0000000000..9eafe78272 --- /dev/null +++ b/lib/public/appframework/http/http.php @@ -0,0 +1,89 @@ +. + * + */ + + +namespace OCP\AppFramework\Http; + + +class Http { + + const STATUS_CONTINUE = 100; + const STATUS_SWITCHING_PROTOCOLS = 101; + const STATUS_PROCESSING = 102; + const STATUS_OK = 200; + const STATUS_CREATED = 201; + const STATUS_ACCEPTED = 202; + const STATUS_NON_AUTHORATIVE_INFORMATION = 203; + const STATUS_NO_CONTENT = 204; + const STATUS_RESET_CONTENT = 205; + const STATUS_PARTIAL_CONTENT = 206; + const STATUS_MULTI_STATUS = 207; + const STATUS_ALREADY_REPORTED = 208; + const STATUS_IM_USED = 226; + const STATUS_MULTIPLE_CHOICES = 300; + const STATUS_MOVED_PERMANENTLY = 301; + const STATUS_FOUND = 302; + const STATUS_SEE_OTHER = 303; + const STATUS_NOT_MODIFIED = 304; + const STATUS_USE_PROXY = 305; + const STATUS_RESERVED = 306; + const STATUS_TEMPORARY_REDIRECT = 307; + const STATUS_BAD_REQUEST = 400; + const STATUS_UNAUTHORIZED = 401; + const STATUS_PAYMENT_REQUIRED = 402; + const STATUS_FORBIDDEN = 403; + const STATUS_NOT_FOUND = 404; + const STATUS_METHOD_NOT_ALLOWED = 405; + const STATUS_NOT_ACCEPTABLE = 406; + const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407; + const STATUS_REQUEST_TIMEOUT = 408; + const STATUS_CONFLICT = 409; + const STATUS_GONE = 410; + const STATUS_LENGTH_REQUIRED = 411; + const STATUS_PRECONDITION_FAILED = 412; + const STATUS_REQUEST_ENTITY_TOO_LARGE = 413; + const STATUS_REQUEST_URI_TOO_LONG = 414; + const STATUS_UNSUPPORTED_MEDIA_TYPE = 415; + const STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416; + const STATUS_EXPECTATION_FAILED = 417; + const STATUS_IM_A_TEAPOT = 418; + const STATUS_UNPROCESSABLE_ENTITY = 422; + const STATUS_LOCKED = 423; + const STATUS_FAILED_DEPENDENCY = 424; + const STATUS_UPGRADE_REQUIRED = 426; + const STATUS_PRECONDITION_REQUIRED = 428; + const STATUS_TOO_MANY_REQUESTS = 429; + const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + const STATUS_INTERNAL_SERVER_ERROR = 500; + const STATUS_NOT_IMPLEMENTED = 501; + const STATUS_BAD_GATEWAY = 502; + const STATUS_SERVICE_UNAVAILABLE = 503; + const STATUS_GATEWAY_TIMEOUT = 504; + const STATUS_HTTP_VERSION_NOT_SUPPORTED = 505; + const STATUS_VARIANT_ALSO_NEGOTIATES = 506; + const STATUS_INSUFFICIENT_STORAGE = 507; + const STATUS_LOOP_DETECTED = 508; + const STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509; + const STATUS_NOT_EXTENDED = 510; + const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511; +} diff --git a/lib/appframework/http/jsonresponse.php b/lib/public/appframework/http/jsonresponse.php similarity index 98% rename from lib/appframework/http/jsonresponse.php rename to lib/public/appframework/http/jsonresponse.php index 750f8a2ad1..085fdbed2f 100644 --- a/lib/appframework/http/jsonresponse.php +++ b/lib/public/appframework/http/jsonresponse.php @@ -22,7 +22,7 @@ */ -namespace OC\AppFramework\Http; +namespace OCP\AppFramework\Http; /** diff --git a/lib/appframework/http/response.php b/lib/public/appframework/http/response.php similarity index 98% rename from lib/appframework/http/response.php rename to lib/public/appframework/http/response.php index 50778105f2..6447725894 100644 --- a/lib/appframework/http/response.php +++ b/lib/public/appframework/http/response.php @@ -22,7 +22,7 @@ */ -namespace OC\AppFramework\Http; +namespace OCP\AppFramework\Http; /** diff --git a/lib/appframework/http/templateresponse.php b/lib/public/appframework/http/templateresponse.php similarity index 98% rename from lib/appframework/http/templateresponse.php rename to lib/public/appframework/http/templateresponse.php index 0a32da4b1b..97678c96cb 100644 --- a/lib/appframework/http/templateresponse.php +++ b/lib/public/appframework/http/templateresponse.php @@ -22,7 +22,7 @@ */ -namespace OC\AppFramework\Http; +namespace OCP\AppFramework\Http; use OC\AppFramework\Core\API; From 38f9df429397619482e3e3f7ffb0db5274222e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 21 Aug 2013 01:02:15 +0200 Subject: [PATCH 022/198] introducing OCP\AppFramework\App --- lib/appframework/app.php | 3 +- lib/appframework/core/api.php | 3 +- .../dependencyinjection/dicontainer.php | 21 +- lib/public/appframework/App.php | 61 +++++ lib/public/appframework/iapi.php | 238 ++++++++++++++++++ lib/public/appframework/iappcontainer.php | 25 ++ 6 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 lib/public/appframework/App.php create mode 100644 lib/public/appframework/iapi.php create mode 100644 lib/public/appframework/iappcontainer.php diff --git a/lib/appframework/app.php b/lib/appframework/app.php index 6224b858bb..7ff55bb809 100644 --- a/lib/appframework/app.php +++ b/lib/appframework/app.php @@ -25,6 +25,7 @@ namespace OC\AppFramework; use OC\AppFramework\DependencyInjection\DIContainer; +use OCP\AppFramework\IAppContainer; /** @@ -45,7 +46,7 @@ class App { * @param DIContainer $container an instance of a pimple container. */ public static function main($controllerName, $methodName, array $urlParams, - DIContainer $container) { + IAppContainer $container) { $container['urlParams'] = $urlParams; $controller = $container[$controllerName]; diff --git a/lib/appframework/core/api.php b/lib/appframework/core/api.php index eb8ee01e5d..337e3b57d6 100644 --- a/lib/appframework/core/api.php +++ b/lib/appframework/core/api.php @@ -23,6 +23,7 @@ namespace OC\AppFramework\Core; +use OCP\AppFramework\IApi; /** @@ -32,7 +33,7 @@ namespace OC\AppFramework\Core; * Should you find yourself in need for more methods, simply inherit from this * class and add your methods */ -class API { +class API implements IApi{ private $appName; diff --git a/lib/appframework/dependencyinjection/dicontainer.php b/lib/appframework/dependencyinjection/dicontainer.php index 88ad2cd414..43f6eee29b 100644 --- a/lib/appframework/dependencyinjection/dicontainer.php +++ b/lib/appframework/dependencyinjection/dicontainer.php @@ -32,9 +32,11 @@ use OC\AppFramework\Middleware\MiddlewareDispatcher; use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; +use OCP\AppFramework\IApi; +use OCP\AppFramework\IAppContainer; -class DIContainer extends SimpleContainer { +class DIContainer extends SimpleContainer implements IAppContainer{ /** @@ -45,6 +47,8 @@ class DIContainer extends SimpleContainer { $this['AppName'] = $appName; + $this->registerParameter('ServerContainer', \OC::$server); + $this['API'] = $this->share(function($c){ return new API($c['AppName']); }); @@ -119,4 +123,19 @@ class DIContainer extends SimpleContainer { } + /** + * @return IApi + */ + function getCoreApi() + { + return $this->query('API'); + } + + /** + * @return \OCP\Core\IServerContainer + */ + function getServer() + { + return $this->query('ServerContainer'); + } } diff --git a/lib/public/appframework/App.php b/lib/public/appframework/App.php new file mode 100644 index 0000000000..0c27fcb2ac --- /dev/null +++ b/lib/public/appframework/App.php @@ -0,0 +1,61 @@ +container = new \OC\AppFramework\DependencyInjection\DIContainer($appName); + } + + private $container; + + /** + * @return IAppContainer + */ + public function getContainer() { + return $this->container; + } + + /** + * This function is called by the routing component to fire up the frameworks dispatch mechanism. + * + * Example code in routes.php of the task app: + * $this->create('tasks_index', '/')->get()->action( + * function($params){ + * $app = new TaskApp(); + * $app->dispatch('PageController', 'index', $params); + * } + * ); + * + * + * Example for for TaskApp implementation: + * class TaskApp extends \OCP\AppFramework\App { + * + * public function __construct(){ + * parent::__construct('tasks'); + * + * $this->getContainer()->registerService('PageController', function(IAppContainer $c){ + * $a = $c->query('API'); + * $r = $c->query('Request'); + * return new PageController($a, $r); + * }); + * } + * } + * + * @param string $controllerName the name of the controller under which it is + * stored in the DI container + * @param string $methodName the method that you want to call + * @param array $urlParams an array with variables extracted from the routes + */ + public function dispatch($controllerName, $methodName, array $urlParams) { + \OC\AppFramework\App::main($controllerName, $methodName, $urlParams, $this->container); + } +} diff --git a/lib/public/appframework/iapi.php b/lib/public/appframework/iapi.php new file mode 100644 index 0000000000..5374f0dcaf --- /dev/null +++ b/lib/public/appframework/iapi.php @@ -0,0 +1,238 @@ +. + * + */ + + +namespace OCP\AppFramework; + + +/** + * A few very basic and frequently used API functions are combined in here + */ +interface IApi { + + /** + * used to return the appname of the set application + * @return string the name of your application + */ + function getAppName(); + + + /** + * Creates a new navigation entry + * @param array $entry containing: id, name, order, icon and href key + */ + function addNavigationEntry(array $entry); + + + /** + * Gets the userid of the current user + * @return string the user id of the current user + */ + function getUserId(); + + + /** + * Sets the current navigation entry to the currently running app + */ + function activateNavigationEntry(); + + + /** + * Adds a new javascript file + * @param string $scriptName the name of the javascript in js/ without the suffix + * @param string $appName the name of the app, defaults to the current one + */ + function addScript($scriptName, $appName = null); + + + /** + * Adds a new css file + * @param string $styleName the name of the css file in css/without the suffix + * @param string $appName the name of the app, defaults to the current one + */ + function addStyle($styleName, $appName = null); + + + /** + * shorthand for addScript for files in the 3rdparty directory + * @param string $name the name of the file without the suffix + */ + function add3rdPartyScript($name); + + + /** + * shorthand for addStyle for files in the 3rdparty directory + * @param string $name the name of the file without the suffix + */ + function add3rdPartyStyle($name); + + /** + * Looks up a system-wide defined value + * @param string $key the key of the value, under which it was saved + * @return string the saved value + */ + function getSystemValue($key); + + /** + * Sets a new system-wide value + * @param string $key the key of the value, under which will be saved + * @param string $value the value that should be stored + */ + function setSystemValue($key, $value); + + + /** + * Looks up an app-specific defined value + * @param string $key the key of the value, under which it was saved + * @return string the saved value + */ + function getAppValue($key, $appName = null); + + + /** + * Writes a new app-specific value + * @param string $key the key of the value, under which will be saved + * @param string $value the value that should be stored + */ + function setAppValue($key, $value, $appName = null); + + + /** + * Shortcut for setting a user defined value + * @param string $key the key under which the value is being stored + * @param string $value the value that you want to store + * @param string $userId the userId of the user that we want to store the value under, defaults to the current one + */ + function setUserValue($key, $value, $userId = null); + + + /** + * Shortcut for getting a user defined value + * @param string $key the key under which the value is being stored + * @param string $userId the userId of the user that we want to store the value under, defaults to the current one + */ + function getUserValue($key, $userId = null); + + /** + * Returns the translation object + * @return \OC_L10N the translation object + * + * FIXME: returns private object / should be retrieved from teh ServerContainer + */ + function getTrans(); + + + /** + * Used to abstract the owncloud database access away + * @param string $sql the sql query with ? placeholder for params + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \OCP\DB a query object + * + * FIXME: returns non public interface / object + */ + function prepareQuery($sql, $limit=null, $offset=null); + + + /** + * Used to get the id of the just inserted element + * @param string $tableName the name of the table where we inserted the item + * @return int the id of the inserted element + * + * FIXME: move to db object + */ + function getInsertId($tableName); + + + /** + * Returns the URL for a route + * @param string $routeName the name of the route + * @param array $arguments an array with arguments which will be filled into the url + * @return string the url + */ + function linkToRoute($routeName, $arguments=array()); + + + /** + * Returns an URL for an image or file + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + */ + function linkTo($file, $appName=null); + + + /** + * Returns the link to an image, like link to but only with prepending img/ + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + */ + function imagePath($file, $appName = null); + + + /** + * Makes an URL absolute + * @param string $url the url + * @return string the absolute url + * + * FIXME: function should live in Request / Response + */ + function getAbsoluteURL($url); + + + /** + * links to a file + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + * @deprecated replaced with linkToRoute() + * @return string the url + */ + function linkToAbsolute($file, $appName = null); + + + /** + * Checks if an app is enabled + * @param string $appName the name of an app + * @return bool true if app is enabled + */ + public function isAppEnabled($appName); + + + /** + * Writes a function into the error log + * @param string $msg the error message to be logged + * @param int $level the error level + * + * FIXME: add logger instance to ServerContainer + */ + function log($msg, $level = null); + + + /** + * Returns a template + * @param string $templateName the name of the template + * @param string $renderAs how it should be rendered + * @param string $appName the name of the app + * @return \OCP\Template a new template + */ + function getTemplate($templateName, $renderAs='user', $appName=null); +} diff --git a/lib/public/appframework/iappcontainer.php b/lib/public/appframework/iappcontainer.php new file mode 100644 index 0000000000..c2faea07b9 --- /dev/null +++ b/lib/public/appframework/iappcontainer.php @@ -0,0 +1,25 @@ + Date: Thu, 22 Aug 2013 01:20:28 +0200 Subject: [PATCH 023/198] fixing typos --- lib/util.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/util.php b/lib/util.php index dbda3ac0ad..dacfd5b56f 100755 --- a/lib/util.php +++ b/lib/util.php @@ -228,7 +228,7 @@ class OC_Util { $webServerRestart = true; } - //common hint for all file permissons error messages + //common hint for all file permissions error messages $permissionsHint = 'Permissions can usually be fixed by ' .'giving the webserver write access to the root directory.'; @@ -560,9 +560,9 @@ class OC_Util { * @see OC_Util::callRegister() * @see OC_Util::isCallRegistered() * @description - * Also required for the client side to compute the piont in time when to + * Also required for the client side to compute the point in time when to * request a fresh token. The client will do so when nearly 97% of the - * timespan coded here has expired. + * time span coded here has expired. */ public static $callLifespan = 3600; // 3600 secs = 1 hour @@ -640,14 +640,15 @@ class OC_Util { * This function is used to sanitize HTML and should be applied on any * string or array of strings before displaying it on a web page. * - * @param string or array of strings + * @param string|array of strings * @return array with sanitized strings or a single sanitized string, depends on the input parameter. */ public static function sanitizeHTML( &$value ) { if (is_array($value)) { array_walk_recursive($value, 'OC_Util::sanitizeHTML'); } else { - $value = htmlentities((string)$value, ENT_QUOTES, 'UTF-8'); //Specify encoding for PHP<5.4 + //Specify encoding for PHP<5.4 + $value = htmlentities((string)$value, ENT_QUOTES, 'UTF-8'); } return $value; } @@ -756,7 +757,7 @@ class OC_Util { } /** - * Check if the setlocal call doesn't work. This can happen if the right + * Check if the setlocal call does not work. This can happen if the right * local packages are not available on the server. * @return bool */ @@ -828,8 +829,8 @@ class OC_Util { /** - * @brief Generates a cryptographical secure pseudorandom string - * @param Int with the length of the random string + * @brief Generates a cryptographic secure pseudo-random string + * @param Int $length of the random string * @return String * Please also update secureRNGAvailable if you change something here */ @@ -970,7 +971,7 @@ class OC_Util { /** * @brief Clear the opcode cache if one exists * This is necessary for writing to the config file - * in case the opcode cache doesn't revalidate files + * in case the opcode cache does not re-validate files * @return void */ public static function clearOpcodeCache() { From 8dd93c8c0288a11f04816bea2a58aee661ef9e97 Mon Sep 17 00:00:00 2001 From: kondou Date: Fri, 23 Aug 2013 07:30:42 +0200 Subject: [PATCH 024/198] Fix some phpdoc and camelcase --- lib/util.php | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/util.php b/lib/util.php index dacfd5b56f..f343e78320 100755 --- a/lib/util.php +++ b/lib/util.php @@ -16,7 +16,7 @@ class OC_Util { /** * @brief Can be set up - * @param user string + * @param string $user * @return boolean * @description configure the initial filesystem based on the configuration */ @@ -51,7 +51,8 @@ class OC_Util { self::$rootMounted = true; } - if( $user != "" ) { //if we aren't logged in, there is no use to set up the filesystem + //if we aren't logged in, there is no use to set up the filesystem + if( $user != "" ) { $quota = self::getUserQuota($user); if ($quota !== \OC\Files\SPACE_UNLIMITED) { \OC\Files\Filesystem::addStorageWrapper(function($mountPoint, $storage) use ($quota, $user) { @@ -131,7 +132,7 @@ class OC_Util { /** * @brief add a javascript file * - * @param appid $application + * @param string $application * @param filename $file * @return void */ @@ -150,7 +151,7 @@ class OC_Util { /** * @brief add a css file * - * @param appid $application + * @param string $application * @param filename $file * @return void */ @@ -168,7 +169,7 @@ class OC_Util { /** * @brief Add a custom element to the header - * @param string tag tag name of the element + * @param string $tag tag name of the element * @param array $attributes array of attributes for the element * @param string $text the text content for the element * @return void @@ -184,8 +185,8 @@ class OC_Util { /** * @brief formats a timestamp in the "right" way * - * @param int timestamp $timestamp - * @param bool dateOnly option to omit time from the result + * @param int $timestamp + * @param bool $dateOnly option to omit time from the result * @return string timestamp * @description adjust to clients timezone if we know it */ @@ -418,12 +419,13 @@ class OC_Util { } /** - * Check for correct file permissions of data directory - * @return array arrays with error messages and hints - */ + * @brief Check for correct file permissions of data directory + * @paran string $dataDirectory + * @return array arrays with error messages and hints + */ public static function checkDataDirectoryPermissions($dataDirectory) { $errors = array(); - if (stristr(PHP_OS, 'WIN')) { + if (self::runningOnWindows()) { //TODO: permissions checks for windows hosts } else { $permissionsModHint = 'Please change the permissions to 0770 so that the directory' @@ -681,9 +683,9 @@ class OC_Util { $testContent = 'testcontent'; // creating a test file - $testfile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$filename; + $testFile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$fileName; - if(file_exists($testfile)) {// already running this test, possible recursive call + if(file_exists($testFile)) {// already running this test, possible recursive call return false; } @@ -692,7 +694,7 @@ class OC_Util { @fclose($fp); // accessing the file via http - $url = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/data'.$filename); + $url = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/data'.$fileName); $fp = @fopen($url, 'r'); $content=@fread($fp, 2048); @fclose($fp); @@ -701,7 +703,7 @@ class OC_Util { @unlink($testfile); // does it work ? - if($content==$testcontent) { + if($content==$testContent) { return false; } else { return true; From 4a08f7d710ced1c564e05471e1f873ecfb9ca161 Mon Sep 17 00:00:00 2001 From: kondou Date: Fri, 26 Jul 2013 12:20:11 +0200 Subject: [PATCH 025/198] Add basic avatars and gravatar --- config/config.sample.php | 6 ++++ core/img/defaultavatar.png | Bin 0 -> 12444 bytes core/templates/layout.user.php | 1 + lib/avatar.php | 59 ++++++++++++++++++++++++++++++++ lib/public/avatar.php | 15 ++++++++ lib/templatelayout.php | 5 +++ settings/admin.php | 1 + settings/ajax/setavatarmode.php | 12 +++++++ settings/js/admin.js | 6 ++++ settings/personal.php | 1 + settings/routes.php | 2 ++ settings/templates/admin.php | 37 ++++++++++++++++++++ settings/templates/personal.php | 13 +++++++ 13 files changed, 158 insertions(+) create mode 100644 core/img/defaultavatar.png create mode 100644 lib/avatar.php create mode 100644 lib/public/avatar.php create mode 100644 settings/ajax/setavatarmode.php diff --git a/config/config.sample.php b/config/config.sample.php index 24ba541ac5..fb2271339b 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -65,6 +65,12 @@ $CONFIG = array( /* URL to the parent directory of the 3rdparty directory, as seen by the browser */ "3rdpartyurl" => "", +/* What avatars to use. + * May be "none" for none, "local" for uploaded avatars, or "gravatar" for gravatars. + * Default is "local". + */ +"avatars" => "local", + /* Default app to load on login */ "defaultapp" => "files", diff --git a/core/img/defaultavatar.png b/core/img/defaultavatar.png new file mode 100644 index 0000000000000000000000000000000000000000..e9572080bbf3fb403a8b07b11d9271546c89c78e GIT binary patch literal 12444 zcmeHt{XdlX_y4)Z7?PWGt5mE8?GjTeNin-89GmOhUp3HRyEgK;;j*@=lexY3thKv#rjD62{0{u#gfPs%gv)! zN3Jq=lgys8+-`*qfH_E}e?Ncoqqk4j-ArF`$V|;%sF>F(-g(@_eOCH7YE0Iuu`2?s zN(asxc*7{UkLRt06Dt{An;g-%%{sUB-!gs+ni&2Xe~-W_%9Z~n-*EP7f$d>!o#9m{ zhN`C>H=I842mXpd11(IQy6UfH53{zfxZM+erF(~YOzld|g_I{{?Bu&`vpD_!`}JQ0 z{)@nW5%~WCfu{XdJZMT*UUX1(9n?7OTOOo%@VZ$3@TIU?5g-X)-Sz&UB4B-PwC!vV zQvgp*+TTmUC#jCb(X?+VTf7ReI9577lt-n!N|1!>*ci zzt2^=<|&Kw>N~%@X0x{S^w9#4sq?zxhg~h6LpC=R`kG6MDH#nr$FFaCDfBL=av7`*qX9DWO4ykDR0lnN{p-1k0l5W1yLoW&b<}hK z$Kl<+$~SS6yQUh)2C@&{zi|6Ab`5-_^@Q6~0NslW!zn-rwLC@-Etkv;N}b zZuzpw-S3lboDWD%hT?(R5E(NHKwYY4j|lTjHNi`R6xG?OJiy(&>3Uu9T&b&9>r#dE z=koG3iXo@ol(lFB{W5|N^cf@8+2O)VHf8*)HxE3o_E(=A&AB*!q$aSv&@az=%ltk- z#P~I+<=%>j4?h>rxFi}wZT|)c82S6^+3f-Cg|o0qa)b15m?5C_dgJ zV;3EE(IPIRe^V-J6CcPGCHI3Vs4++Fo9-jrs#0O|U*@WpLY+k+8GCrJz5+|Z=+-~# zGwl5{Tm}FmKnG+FQLd3IqMpBXt-f@qp{454HZ*F{ISRxG0d2P&;%7tG{#{zQ)-IQtM6%Q4<1Rj%o0@)}<-Ne1~A52>c^kIU{=^?;ctGmQ@gIjFj5est5CY>2?|6FM+3$G}v;>jyI;s4U6tIu~ zIC6~YTGgYx859g4OAl)-Ox^XM1{Z2305~Yugeq>|uQxc~yigIHTVRCa$gt$3Soz9T z8@oIyID`7Q&3`aU5v3TuPv8)Frl8%s6u41)-SpY=YuA)p-SvsrGM&@=Q4y`jb%8Mk zn4GC>DQ{>kp0HY^fJwi%z~uiov{1rulVou5+K zHB;dzyvEBZta^r#w`>I(v!ZR~;EG{jOCwatS;_^->u24D*)qGT9=Q(r*O;x>)fK!E zO68YQE|mvY+<9yJXoE3`7GMbFkC%QblvG(><6XJG@Qd|QkR>2y`8oA$bF4hMuI2SP z9gZmWl})FEjR-sFXSA`K+d=jt$(ui=4>ZDqme=~{e${Fz(JKF^UxNFQu?e*Infjr8 zTATQMmr^-G;xg;3l|gdT8pYGxf{N)B<44l{o}sDcHC^!foGw`_CFz(2(|f5OSm)2D zox*hRAo8Nyb;OI205eN5VB;gNtG(lLW@kvagin}i!JCpqpq6#+-2)Xuu#Iy@&~_^y zrK&!&($$sURFAu$Q#a2CM3Lx5zh9@t$|s{6*Ye25v$BCQu6xKQsFy_f@i$MTa6@F& zTW;?O9stQVEomqZl)k&wz!NtqQSZc3Bn*slnepW%r4D*T=b3db#&turdnL@_Z=TsJ z;q%?e={`h55ibf8Vl>zu%)=kP(v86iBjNW(Z&&9=j}}1eEi|d^RrrJ{r-%*A9PM-| zH;cdCx9nW+0?Z<5?^7Aya5q$Zhw;%XN;)^w(KTUV+hbk&+F4Y4N*tRfp(<>-_n}%h&{tsOy_(Qn`>pnrOTGB#%zg2Yn~iagE9hRHQF@NkV5Tfl zSN)a)960MB1pWL~xs3ny=+wU-q28N_-uOX2!4JBLla1dPWB=olc#{*3-)J;&+ur|} z_m7CR&y>2H0R7sFy%)IK|1-V3f-)xhMn`o+FKq)W`h4{Fe< zu(9a1cDwJ4|4>z*ar>8r`aX640a&Jyi!qfR$N{w73_Wev>&V;x8Xj9QYyv)S+K1KM zs}ssudLLWLd_s1ZQ6hJY?38Sqe>`6C-Gp8@i+FbPN_xX!Ce!%$SEm~HI+dg;WBI>b z4kZJ7qeX01d~}NJ^L9Z$`hrsFoV4A% z&sJGf$h&1@K{TJ(bgJr6oE~hg(<*T#nATi%#9$rB1#8CR2d^*o?Oot;N0zQSfeYCB zHcPnJvJpux@+uFQCsL$oZ$*wI#_%H6A7w~$O}M-$3P}h#<>KuwPH@U^Z5Elwa#$^* z*U;9~2^Vh-!5vIH3K_AVw7^xhLQ?ttr9~lT@f7_VQa1C$A63sh!X^D9%NNDUi9Vuj zd#TC^RV)F5=F!G#j8$@5r*Q@y+#ixlkV|amOM3zpPZgP3E^BgorlFC82_^kejYb)B zM%6kYfPA|2D{JPJHp1&zt=xhrBp!}1f6LOUo*~I!KE!|}(IM01EPgSRy+|{#KoQPZQUBh)Z7B^85EO3__jVP=Ad`}8QVtv-su|_528F;_uy7DiflD| zl#dQKge#=0^TlY1$r!9HY-fbL>k&3uUi-dH0b6{J!B;Y9o?D$&D+}G%flwG3|s>dBAfo^>)7?;^X&L z&1^BGaR;e!p;(Y{=6%jt`C_OR?L;Pd{jMzbYNdrKF^-EuifsgN<1WTv z;D@8S>a`SCJ!TG2t_fD$mTdBf8QL{kwKe1&q5VyWansJ%bh|>Ba^@fk7WG`Q#ESi> zvPBZ^)4#kDi-E^W0npgUn~`&zt^2HONUJv^{)nvZi^<1_bB)LoM@S0TS^6IoJPvbG z{aE#=@hH%J$gEqDvM>9EHCbjuHB_BV@ww?BuAcv3ujhGam&A-IQm7uYxZ^<(sbd;F zbqW#9!Bay!b!gM>8~rH=oG-mJ?jf&cjKReEW1i16a$7wpN=FPQub#{tN#-Ape55u4 z$IsD3>sx`aR&kDnu)f5soY&?ZX~LS!_n3iogNt)<<%6>e;n}S4!tq7B;w3F@uU(XH3*gsO8Y>e9s__>Jn&c=zxLs^1Vr1mrO(XhJ=8u9!)F zI*BI&$p5)^t_5UzXJu$#PHCXW&%6`Nf-H&DXEygMc z-gbY9(fWoXLVK^K{Vh1#aMIX~zMmEpITeUwh-B&|c^iY|}d-o8P%%`^bH{7t$KsB#YPPMt^5W zLZtE?Pv)j@OnPE%aK7o7u)=!F)q?h`lL(88qde$KiU9ZUxbZcyr&0qVCN=xc#){7AKX_b|+2` z+f7YDl2$)OS#}J2XucnPO5$a`<#a*&8E#3YTfpKW&mzfDvrYJ?V*9=Z9m!(#XE8@b zmwsdK3aztZim~Rx(lre^$lHfAIoC*IWaV@~r7RvKzxeh6IqW_gpv^IEO=a;Yl}hMc z`gljz%Ouae+G(3?IYpjqSJxhHNPVp?US#pHW)XLC{$U{TMP2+gJPmpWhu@G(E%a~V zQJxe#dPIkWEo%Abq_&>2ev{e+lgzt@nF0hyAD2(?^!&G2ZP6U z`aTGLO4f~2((-4LOk%kkLTJrrXw_FUg1`% zk*mdd0kc|H^*YMfj^dkisM#w*+*K+)bjDp6u5LaGvQg-xga|dBk!Q#gC0bkGCkYb^ zsx-4WD>_@%Tr=d!7z2}p5XnfSpZNpfNdmS#UJTO|A5pzr5-T@Jejp51zPWUiu#l+b zn*4-vM~Z4T8|BUyEaIh|RVi02>uEuHLEt5C{-6?tt_q#Yi~Qe+Fz8lfpZpbFNcb?7J>)>;Mi z=|sI=;E_wVxrB{+an`kNYog_rW6GBP+Qdss^jUdYL!?#Vk6POwM&IvWx5Nh$lF*70 zlWyOv7QqDMfSoFRyvR*RF`9Sh9o>-7-Mx=G=}Fggp<-j+BTodYmHaKBK1PW zGCLhUhNY}iC-(hPh1;T#_j`DFRMow$ki zuQ}LIpzOl75@uo34btK18!ML?Rw*tANNWdaGa`(W%pe@?`Rs41{Qm2zN5w<9{$7cz zXuG|&(`$zxiH`a#qdwvh{v?=3Va}GzJ2BCJ5$YVLY*o#v+pXsG;iAW@guUzO@~QDp zS8*R;&VSZJ!i!@gV&$2m_6nUGdsr)y@#U*JgH#QzxugDq$qRUwv-n(n>uhi7WF^Rq zF)Xvk1s^yK5YWehGj5xZupxvyLSZ&hw zPu$Atyv|qcun^#O!kA!p0$F;2=En_1$*M+5Zl_1L;Ng|&j$>WR8)DZYz=Kao7Gn!y z230u%x9(TY-E6kd+kykh{MM!4@_u zhVK`M6%C(+iRY_a(4VbE7?}uTc-x-b=vrVZ8ApiV^dA>x=x*% zU!4_cN1mmU>N?Z0gdJMq%4FqQJI#~Y93^W-Mt8F7t%5baC#ZZ&x1_YeTFi5zR!B*) zSuUL0aJHa*0JjWQUL~6*sP172!G*1;TTB1(OQ8`~(p(nE9+t47cHI%L%w#T$JqWvC z@2@-#9Y;L8s#7QBlAB@*n(`U@FJaB~tVWnumaT!oFE@w0%jWF14euKc)Nq>{eyHnXJCWJsgYH-8{l6@9VKIeGVIxTC#5qOMQJ|%k3FW zMmqRNYbC7-LYUkbrpPXZ{AlExSXLRAl;&|3g?e2~R%KIiOFa@g3*y*qip#00;B06* zU6^Ib1f$$xZa|MUVxv~JzN%FlRYrHL2>0y^igs5! ziCPi<0=t;dbR}M5`1w0vZeU9-L3KM;PG6gp((q*|)J!r3X7v0A9nZ^0GnD}zt>Tb( zzMS`uD@WdKNRprW3ry1bBK@Juo*WtlOl2Idbr*wD&^$*mZz&W_MG5;5S7Bok1~HQv zc&Fh9rf>!o7BLZ9?c4B_AkqtwEbgbpm7s!?%B!iW3v#&e;veU}7^>+A4*&M=k*gu$ z)(NhQ`7@7e|LNaDnxlOt?`v2+7M`RSfNYNPjk9F&uY6`z;OuZbP);S3NY@*(K_0CO z6wb#f(VAeSfD|m^dSf{SM+N=n~0EZSFJXN0Bm}ScGk1$ zp6h#pk^~SG{;#Me8vyoty~F*6!S>Pvo_73h+%M4GJyEyRM$GKD9<2!`V1--Zl@*`N zXoI3d4Tv2w^fGi>(>ZdCdvar;qVYN*m#s>r#$07AfqcqZ`DXE0Scd10$}-%+6orbn z$pSEqJYUeBR|*NK29TMU?%1uBFG-Y==IV`d0oK#fPw#|z?mT+T=5qz@mzEO$J~h)s zS#7aSs+deM3x?y~@2St2pnpHAI&kf^_l>4Q!U2>vMET~lr=6zlp(~tk#uCiBceX8P z+!SR(iqXHE)F9(ha@liKDh}V8;K6rgQ;X0V_4x%QG?UhS{C%?!5ZU-DhOf$vw$hW? zqUC|Y7WyQB(MR&P030mC5tid7^jJ*f$OY1;sVb*C7=JRuwG@4fLv=0-ddF+7y>E6TwdC(=tgHLYO+VrTBFz)mUdtfJ)@T6SIg zb-^z)S~7A7Io$hYD!Za2h7umFy=@_%sYE0qS9jL#Z&<3(LJj)tJ&zTNJ~Iak#)}QV z)|=6m>{PzU4+Y`zGZ2q|562ilauXu4ZY)g0d~Dh?4{?~>+F{I?mo!cJ$%cU%58|!u9Llo zbZGkRcIsMcyPaNU9;y^SOk*~#TdQ(evt;BTqC(M?d3Y#n3=rskBy@pon!wZmjSe!? z1afYl<^$0PuHVk=8Nfi!ityjYi0yH;S4w5O)rIjr_xKRqfv+cnc2iBGkXbGt8Q-lX zGLOr4drX49*awg5=?Xq*Ma<#G~w9* z3y|?go>caboMA>wA5=eYb%h&CChVoi@atZCfHw9)3b;2N%I8XZ)+shhKxEOJThMVG zB99v+Sju=sNipvbL3SjjzH+QgxhSbe#kciH!l-0xx7%lQV8J z2&`A@eK!Q_&&Vz@EEKeh$m zNtl;SCjzc1l{$Ew;6VUi7k@5s{)PwciYI~64lX!iaHrp(F@vk+)ugf#xhD!Aj^bXo zp=~vndvxgL5hs;LINN2O)$G$v`cS>-moWqz{qlQ$EcQ7*OyGXqdSQX*6x1ei?f@#^ z|NbY|1|t|8BZ>;zvHK8+0ba9b6arCXS;noUyTFt*fIlW+jATh4u(QJpg;1B=PU<49 z%0U=#cMRDM1Vc6rxkC>!tM|qeAEVFJ%LJf~va0;P1~gdVa(6`jXI~+93D`}wdoei_lZ>GCm274}!YPHe+oRSYjvx!^i+lifLu5j-wTZrI1k zk=!?p@A0GPhjWx#dcYVB0u!p^>r~Jz*2c`=@jEd6a(o}`AW@q+Pr1fTp(CWg;Y5HA zYS}ntpXwLvE3RdCkikcu8)!p$dR}wy>iA86UTp#Ek1I8Hs`V0J%6s4RARC`d607W3l~nOCw+%MOz}jH2I=FGO5|$C<0t=VNi!SC%f;IYpf5an zimgI@#sYKt>a9i=@cn<3&Z!NHmYBpQHqkm$_EAPP{igB9j<2*md(-0f6IQx;|4)6t z74DlcIJVgI{_Mk5NvZeT8@Hu@t@9yM;&w&w6*+_)HV1* z5oLu7GX^?PG*SykUQ0t6ZN2(=-5sC#`jJpPd&S?A-O4{`{bjStM} zY<#0{bXa+DoEEN|{d~0+l$WjIiSb?SDpY11jxM?fP`v`z3p+-^>Dx5OcsJ)u?GFb5 z51*pI!}`D!3?;@61!lw36ymOVMGnYJw@xeV%83DIo*n`S$UtkuVkjtb(L&}HXu+f( z@9{YqP}ikz4p8AMJ(ydNYe~>WtwWFXK?2>xS$Ibqie?fE7tb63bjWxd-P0CHLBKI0 zGy)?+mG!U1o}}Pn*?7MxBS@d3o5^5mia&<^!ox(lWe;({%G-U&GEe;Q`!{qUT{1P= zfAfkyc&d;$Q+%i!eVH`^9^l;d-vRB=ruX&IoDThj_rnGf?W(#3{m^m*614&tzAF)U z%;hbCMkw%DSWZ89bw_6#YBFh0E{=Ytd~-_=hoPe@UGpd+MTBqsnC{R0%yAu^He)fQ z6d&k=VB?3#&D6TeMZmSo^K_xD(U+!ik#gr=Ru;hBke8#TiKB47XS%dZPTAc2$kOK#?g z7pPKkhZSBB{PE9C^Yt@4NepyqX#gs$D4H+-7g>74A`c-o=`%L;^*U(-i zGeeuJ9yvRpu>6C{t@jpg3+V%X13wTyUHRN&2DTM$WLmzxn=U2W!rv|iXn&>q4c^C0 zc_#8t`J;mD$E}!Frtzx$kYI1IiydUJAy2gP3+6fPW5fSwN&0x9MOPXAc)K-TSPy(OY;Mzuz)G{sS8uGrdlWZmr&4)v0njE~BsH{tG=uQXgnhYLZ%Ocxu|1DT)R`YVMnm02G>0~&Ak$j9 zafP-RXwStI(6)=Ua0T6$QDWFv`q~w5&$%kl2Y(J6-Iu5(7b<);Hg|819CJcXc*ET(w$6ffdE{=(F+26r{0Fw{y6*7#4N4$uHUqAlX# zM!nhDfBu6LgAkdsXf*APv4e1I;G^+q0i6gGy{&iLl+)~UT z(Yr!05i7)HDE!Uq6p&T-D7P;8M^z*_u^qAyP9!SfhbCXPf8OA87d?+t3F`PE5)dGn zq#Nt`aBhP$zexbN?FPtk_~FKh$rX;nkC=S@bJ>1=9FD^E*^#sH_J45Azt{dp4Y%j^ z0Q;IRkx&iajuWqM6;b9m?W?9_Hq(FmSjrqP=>OCl1v5F5x^&=z3!!`S>mvQ2T!1rI zd+o@SCz3$A86L`m-5*taoQogKouThpN^E6D@%a$;K?iu7b^lTE$`%m?(=R$wsC@E! zLT<##K|qPK>^Whrt9WqJLBGV83)~BioSkUDyoDrFod9Qufj0b%7Y!dMF