aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/AppFramework
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/AppFramework')
-rw-r--r--lib/private/AppFramework/App.php171
-rw-r--r--lib/private/AppFramework/Core/API.php200
-rw-r--r--lib/private/AppFramework/Db/Db.php287
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php461
-rw-r--r--lib/private/AppFramework/Http.php154
-rw-r--r--lib/private/AppFramework/Http/Dispatcher.php179
-rw-r--r--lib/private/AppFramework/Http/Output.php92
-rw-r--r--lib/private/AppFramework/Http/Request.php771
-rw-r--r--lib/private/AppFramework/Middleware/MiddlewareDispatcher.php162
-rw-r--r--lib/private/AppFramework/Middleware/Security/CORSMiddleware.php155
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php38
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php38
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php38
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php38
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php32
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php215
-rw-r--r--lib/private/AppFramework/Middleware/SessionMiddleware.php81
-rw-r--r--lib/private/AppFramework/Routing/RouteActionHandler.php47
-rw-r--r--lib/private/AppFramework/Routing/RouteConfig.php200
-rw-r--r--lib/private/AppFramework/Utility/ControllerMethodReflector.php118
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php162
-rw-r--r--lib/private/AppFramework/Utility/TimeFactory.php44
22 files changed, 3683 insertions, 0 deletions
diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php
new file mode 100644
index 00000000000..376a8559454
--- /dev/null
+++ b/lib/private/AppFramework/App.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * @author Andreas Fischer <bantu@owncloud.com>
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework;
+
+use OC\AppFramework\Http\Dispatcher;
+use OC_App;
+use OC\AppFramework\DependencyInjection\DIContainer;
+use OCP\AppFramework\QueryException;
+use OCP\AppFramework\Http\ICallbackResponse;
+
+/**
+ * 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 {
+
+
+ /**
+ * Turns an app id into a namespace by either reading the appinfo.xml's
+ * namespace tag or uppercasing the appid's first letter
+ * @param string $appId the app id
+ * @param string $topNamespace the namespace which should be prepended to
+ * the transformed app id, defaults to OCA\
+ * @return string the starting namespace for the app
+ */
+ public static function buildAppNamespace($appId, $topNamespace='OCA\\') {
+ // first try to parse the app's appinfo/info.xml <namespace> tag
+ $appPath = OC_App::getAppPath($appId);
+ if ($appPath !== false) {
+ $filePath = "$appPath/appinfo/info.xml";
+ if (is_file($filePath)) {
+ $loadEntities = libxml_disable_entity_loader(false);
+ $xml = @simplexml_load_file($filePath);
+ libxml_disable_entity_loader($loadEntities);
+ if ($xml) {
+ $result = $xml->xpath('/info/namespace');
+ if ($result && count($result) > 0) {
+ // take first namespace result
+ return $topNamespace . trim((string) $result[0]);
+ }
+ }
+ }
+ }
+ // if the tag is not found, fall back to uppercasing the first letter
+ return $topNamespace . ucfirst($appId);
+ }
+
+
+ /**
+ * 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 DIContainer $container an instance of a pimple container.
+ * @param array $urlParams list of URL parameters (optional)
+ */
+ public static function main($controllerName, $methodName, DIContainer $container, array $urlParams = null) {
+ if (!is_null($urlParams)) {
+ $container['OCP\\IRequest']->setUrlParameters($urlParams);
+ } else if (isset($container['urlParams']) && !is_null($container['urlParams'])) {
+ $container['OCP\\IRequest']->setUrlParameters($container['urlParams']);
+ }
+ $appName = $container['AppName'];
+
+ // first try $controllerName then go for \OCA\AppName\Controller\$controllerName
+ try {
+ $controller = $container->query($controllerName);
+ } catch(QueryException $e) {
+ $appNameSpace = self::buildAppNamespace($appName);
+ $controllerName = $appNameSpace . '\\Controller\\' . $controllerName;
+ $controller = $container->query($controllerName);
+ }
+
+ // initialize the dispatcher and run all the middleware before the controller
+ /** @var Dispatcher $dispatcher */
+ $dispatcher = $container['Dispatcher'];
+
+ list(
+ $httpHeaders,
+ $responseHeaders,
+ $responseCookies,
+ $output,
+ $response
+ ) = $dispatcher->dispatch($controller, $methodName);
+
+ $io = $container['OCP\\AppFramework\\Http\\IOutput'];
+
+ if(!is_null($httpHeaders)) {
+ $io->setHeader($httpHeaders);
+ }
+
+ foreach($responseHeaders as $name => $value) {
+ $io->setHeader($name . ': ' . $value);
+ }
+
+ foreach($responseCookies as $name => $value) {
+ $expireDate = null;
+ if($value['expireDate'] instanceof \DateTime) {
+ $expireDate = $value['expireDate']->getTimestamp();
+ }
+ $io->setCookie(
+ $name,
+ $value['value'],
+ $expireDate,
+ $container->getServer()->getWebRoot(),
+ null,
+ $container->getServer()->getRequest()->getServerProtocol() === 'https',
+ true
+ );
+ }
+
+ if ($response instanceof ICallbackResponse) {
+ $response->callback($io);
+ } else if(!is_null($output)) {
+ $io->setHeader('Content-Length: ' . strlen($output));
+ $io->setOutput($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/private/AppFramework/Core/API.php b/lib/private/AppFramework/Core/API.php
new file mode 100644
index 00000000000..67b696948f0
--- /dev/null
+++ b/lib/private/AppFramework/Core/API.php
@@ -0,0 +1,200 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework\Core;
+use OCP\AppFramework\IApi;
+
+
+/**
+ * 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
+ * @deprecated
+ */
+class API implements IApi{
+
+ private $appName;
+
+ /**
+ * constructor
+ * @param string $appName the name of your application
+ */
+ public function __construct($appName){
+ $this->appName = $appName;
+ }
+
+
+ /**
+ * Gets the userid of the current user
+ * @return string the user id of the current user
+ * @deprecated Use \OC::$server->getUserSession()->getUser()->getUID()
+ */
+ public function getUserId(){
+ return \OCP\User::getUser();
+ }
+
+
+ /**
+ * Adds a new javascript file
+ * @deprecated include javascript and css in template files
+ * @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
+ * @deprecated include javascript and css in template files
+ * @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);
+ }
+
+
+ /**
+ * @deprecated include javascript and css in template files
+ * 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);
+ }
+
+
+ /**
+ * @deprecated include javascript and css in template files
+ * 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);
+ }
+
+
+ /**
+ * @deprecated communication between apps should happen over built in
+ * callbacks or interfaces (check the contacts and calendar managers)
+ * Checks if an app is enabled
+ * also use \OC::$server->getAppManager()->isEnabledForUser($appName)
+ * @param string $appName the name of an app
+ * @return bool true if app is enabled
+ */
+ public function isAppEnabled($appName){
+ return \OCP\App::isEnabled($appName);
+ }
+
+
+ /**
+ * used to return and open a new event source
+ * @return \OCP\IEventSource a new open EventSource class
+ * @deprecated Use \OC::$server->createEventSource();
+ */
+ public function openEventSource(){
+ return \OC::$server->createEventSource();
+ }
+
+ /**
+ * @deprecated register hooks directly for class that build in hook interfaces
+ * 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);
+ }
+
+ /**
+ * @deprecated implement the emitter interface instead
+ * 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 default: 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);
+ }
+
+ /**
+ * clear hooks
+ * @deprecated clear hooks directly for class that build in hook interfaces
+ * @param string $signalClass
+ * @param string $signalName
+ */
+ public function clearHook($signalClass=false, $signalName=false) {
+ if ($signalClass) {
+ \OC_Hook::clear($signalClass, $signalName);
+ }
+ }
+
+
+ /**
+ * 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
+ * @deprecated Use \OC::$server->getJobList()->add();
+ */
+ 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);
+ }
+
+
+}
diff --git a/lib/private/AppFramework/Db/Db.php b/lib/private/AppFramework/Db/Db.php
new file mode 100644
index 00000000000..0d17d7bc225
--- /dev/null
+++ b/lib/private/AppFramework/Db/Db.php
@@ -0,0 +1,287 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Db;
+
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDb;
+use OCP\IDBConnection;
+
+/**
+ * @deprecated use IDBConnection directly, will be removed in ownCloud 10
+ * Small Facade for being able to inject the database connection for tests
+ */
+class Db implements IDb {
+ /**
+ * @var IDBConnection
+ */
+ protected $connection;
+
+ /**
+ * @param IDBConnection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ }
+
+ /**
+ * Gets the ExpressionBuilder for the connection.
+ *
+ * @return \OCP\DB\QueryBuilder\IQueryBuilder
+ */
+ public function getQueryBuilder() {
+ return $this->connection->getQueryBuilder();
+ }
+
+ /**
+ * 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
+ * @deprecated use prepare instead, will be removed in ownCloud 10
+ * @return \OC_DB_StatementWrapper prepared SQL query
+ */
+ public function prepareQuery($sql, $limit = null, $offset = null) {
+ $isManipulation = \OC_DB::isManipulation($sql);
+ $statement = $this->connection->prepare($sql, $limit, $offset);
+ return new \OC_DB_StatementWrapper($statement, $isManipulation);
+ }
+
+
+ /**
+ * Used to get the id of the just inserted element
+ *
+ * @deprecated use lastInsertId instead, will be removed in ownCloud 10
+ * @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 $this->connection->lastInsertId($tableName);
+ }
+
+ /**
+ * 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 \Doctrine\DBAL\Driver\Statement The prepared statement.
+ */
+ public function prepare($sql, $limit=null, $offset=null) {
+ return $this->connection->prepare($sql, $limit, $offset);
+ }
+
+ /**
+ * Executes an, optionally parameterized, SQL query.
+ *
+ * If the query is parameterized, a prepared statement is used.
+ * If an SQLLogger is configured, the execution is logged.
+ *
+ * @param string $query The SQL query to execute.
+ * @param string[] $params The parameters to bind to the query, if any.
+ * @param array $types The types the previous parameters are in.
+ * @return \Doctrine\DBAL\Driver\Statement The executed statement.
+ */
+ public function executeQuery($query, array $params = array(), $types = array()) {
+ return $this->connection->executeQuery($query, $params, $types);
+ }
+
+ /**
+ * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
+ * and returns the number of affected rows.
+ *
+ * This method supports PDO binding types as well as DBAL mapping types.
+ *
+ * @param string $query The SQL query.
+ * @param array $params The query parameters.
+ * @param array $types The parameter types.
+ * @return integer The number of affected rows.
+ */
+ public function executeUpdate($query, array $params = array(), array $types = array()) {
+ return $this->connection->executeUpdate($query, $params, $types);
+ }
+
+ /**
+ * Used to get the id of the just inserted element
+ * @param string $table the name of the table where we inserted the item
+ * @return int the id of the inserted element
+ */
+ public function lastInsertId($table = null) {
+ return $this->connection->lastInsertId($table);
+ }
+
+ /**
+ * Insert a row if the matching row does not exists.
+ *
+ * @param string $table The table name (will replace *PREFIX* with the actual prefix)
+ * @param array $input data that should be inserted into the table (column name => value)
+ * @param array|null $compare List of values that should be checked for "if not exists"
+ * If this is null or an empty array, all keys of $input will be compared
+ * Please note: text fields (clob) must not be used in the compare array
+ * @return int number of inserted rows
+ * @throws \Doctrine\DBAL\DBALException
+ */
+ public function insertIfNotExist($table, $input, array $compare = null) {
+ return $this->connection->insertIfNotExist($table, $input, $compare);
+ }
+
+ /**
+ * Insert or update a row value
+ *
+ * @param string $table
+ * @param array $keys (column name => value)
+ * @param array $values (column name => value)
+ * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
+ * @return int number of new rows
+ * @throws \Doctrine\DBAL\DBALException
+ * @throws PreconditionNotMetException
+ */
+ public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
+ return $this->connection->setValues($table, $keys, $values, $updatePreconditionValues);
+ }
+
+ /**
+ * Start a transaction
+ */
+ public function beginTransaction() {
+ $this->connection->beginTransaction();
+ }
+
+ /**
+ * Check if a transaction is active
+ *
+ * @return bool
+ */
+ public function inTransaction() {
+ return $this->connection->inTransaction();
+ }
+
+ /**
+ * Commit the database changes done during a transaction that is in progress
+ */
+ public function commit() {
+ $this->connection->commit();
+ }
+
+ /**
+ * Rollback the database changes done during a transaction that is in progress
+ */
+ public function rollBack() {
+ $this->connection->rollBack();
+ }
+
+ /**
+ * Gets the error code and message as a string for logging
+ * @return string
+ */
+ public function getError() {
+ return $this->connection->getError();
+ }
+
+ /**
+ * Fetch the SQLSTATE associated with the last database operation.
+ *
+ * @return integer The last error code.
+ */
+ public function errorCode() {
+ return $this->connection->errorCode();
+ }
+
+ /**
+ * Fetch extended error information associated with the last database operation.
+ *
+ * @return array The last error information.
+ */
+ public function errorInfo() {
+ return $this->connection->errorInfo();
+ }
+
+ /**
+ * Establishes the connection with the database.
+ *
+ * @return bool
+ */
+ public function connect() {
+ return $this->connection->connect();
+ }
+
+ /**
+ * Close the database connection
+ */
+ public function close() {
+ $this->connection->close();
+ }
+
+ /**
+ * Quotes a given input parameter.
+ *
+ * @param mixed $input Parameter to be quoted.
+ * @param int $type Type of the parameter.
+ * @return string The quoted parameter.
+ */
+ public function quote($input, $type = IQueryBuilder::PARAM_STR) {
+ return $this->connection->quote($input, $type);
+ }
+
+ /**
+ * Gets the DatabasePlatform instance that provides all the metadata about
+ * the platform this driver connects to.
+ *
+ * @return \Doctrine\DBAL\Platforms\AbstractPlatform The database platform.
+ */
+ public function getDatabasePlatform() {
+ return $this->connection->getDatabasePlatform();
+ }
+
+ /**
+ * Drop a table from the database if it exists
+ *
+ * @param string $table table name without the prefix
+ */
+ public function dropTable($table) {
+ $this->connection->dropTable($table);
+ }
+
+ /**
+ * Check if a table exists
+ *
+ * @param string $table table name without the prefix
+ * @return bool
+ */
+ public function tableExists($table) {
+ return $this->connection->tableExists($table);
+ }
+
+ /**
+ * Espace a parameter to be used in a LIKE query
+ *
+ * @param string $param
+ * @return string
+ */
+ public function escapeLikeParameter($param) {
+ return $this->connection->escapeLikeParameter($param);
+ }
+}
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
new file mode 100644
index 00000000000..f74fe4aeb99
--- /dev/null
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -0,0 +1,461 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Roeland Jago Douma <rullzer@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Tanghus <thomas@tanghus.net>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework\DependencyInjection;
+
+use OC;
+use OC\AppFramework\Http;
+use OC\AppFramework\Http\Dispatcher;
+use OC\AppFramework\Http\Output;
+use OC\AppFramework\Core\API;
+use OC\AppFramework\Middleware\MiddlewareDispatcher;
+use OC\AppFramework\Middleware\Security\SecurityMiddleware;
+use OC\AppFramework\Middleware\Security\CORSMiddleware;
+use OC\AppFramework\Middleware\SessionMiddleware;
+use OC\AppFramework\Utility\SimpleContainer;
+use OCP\AppFramework\IApi;
+use OCP\AppFramework\IAppContainer;
+
+
+class DIContainer extends SimpleContainer implements IAppContainer {
+
+ /**
+ * @var array
+ */
+ private $middleWares = array();
+
+ /**
+ * Put your class dependencies in here
+ * @param string $appName the name of the app
+ */
+ public function __construct($appName, $urlParams = array()){
+ parent::__construct();
+ $this['AppName'] = $appName;
+ $this['urlParams'] = $urlParams;
+
+ /** @var \OC\ServerContainer $server */
+ $server = $this->getServer();
+ $server->registerAppContainer($appName, $this);
+
+ // aliases
+ $this->registerAlias('appName', 'AppName');
+ $this->registerAlias('webRoot', 'WebRoot');
+ $this->registerAlias('userId', 'UserId');
+
+ /**
+ * Core services
+ */
+ $this->registerService('OCP\\IAppConfig', function($c) {
+ return $this->getServer()->getAppConfig();
+ });
+
+ $this->registerService('OCP\\App\\IAppManager', function($c) {
+ return $this->getServer()->getAppManager();
+ });
+
+ $this->registerService('OCP\\AppFramework\\Http\\IOutput', function($c){
+ return new Output($this->getServer()->getWebRoot());
+ });
+
+ $this->registerService('OCP\\IAvatarManager', function($c) {
+ return $this->getServer()->getAvatarManager();
+ });
+
+ $this->registerService('OCP\\Activity\\IManager', function($c) {
+ return $this->getServer()->getActivityManager();
+ });
+
+ $this->registerService('OCP\\ICache', function($c) {
+ return $this->getServer()->getCache();
+ });
+
+ $this->registerService('OCP\\ICacheFactory', function($c) {
+ return $this->getServer()->getMemCacheFactory();
+ });
+
+ $this->registerService('OC\\CapabilitiesManager', function($c) {
+ return $this->getServer()->getCapabilitiesManager();
+ });
+
+ $this->registerService('OCP\Comments\ICommentsManager', function($c) {
+ return $this->getServer()->getCommentsManager();
+ });
+
+ $this->registerService('OCP\\IConfig', function($c) {
+ return $this->getServer()->getConfig();
+ });
+
+ $this->registerService('OCP\\Contacts\\IManager', function($c) {
+ return $this->getServer()->getContactsManager();
+ });
+
+ $this->registerService('OCP\\IDateTimeZone', function($c) {
+ return $this->getServer()->getDateTimeZone();
+ });
+
+ $this->registerService('OCP\\IDb', function($c) {
+ return $this->getServer()->getDb();
+ });
+
+ $this->registerService('OCP\\IDBConnection', function($c) {
+ return $this->getServer()->getDatabaseConnection();
+ });
+
+ $this->registerService('OCP\\Diagnostics\\IEventLogger', function($c) {
+ return $this->getServer()->getEventLogger();
+ });
+
+ $this->registerService('OCP\\Diagnostics\\IQueryLogger', function($c) {
+ return $this->getServer()->getQueryLogger();
+ });
+
+ $this->registerService('OCP\\Files\\IMimeTypeDetector', function($c) {
+ return $this->getServer()->getMimeTypeDetector();
+ });
+
+ $this->registerService('OCP\\Files\\Config\\IMountProviderCollection', function($c) {
+ return $this->getServer()->getMountProviderCollection();
+ });
+
+ $this->registerService('OCP\\Files\\IRootFolder', function($c) {
+ return $this->getServer()->getRootFolder();
+ });
+
+ $this->registerService('OCP\\IGroupManager', function($c) {
+ return $this->getServer()->getGroupManager();
+ });
+
+ $this->registerService('OCP\\IL10N', function($c) {
+ return $this->getServer()->getL10N($c->query('AppName'));
+ });
+
+ $this->registerService('OCP\\L10N\\IFactory', function($c) {
+ return $this->getServer()->getL10NFactory();
+ });
+
+ $this->registerService('OCP\\ILogger', function($c) {
+ return $this->getServer()->getLogger();
+ });
+
+ $this->registerService('OCP\\BackgroundJob\\IJobList', function($c) {
+ return $this->getServer()->getJobList();
+ });
+
+ $this->registerAlias('OCP\\AppFramework\\Utility\\IControllerMethodReflector', 'OC\AppFramework\Utility\ControllerMethodReflector');
+ $this->registerAlias('ControllerMethodReflector', 'OCP\\AppFramework\\Utility\\IControllerMethodReflector');
+
+ $this->registerService('OCP\\Files\\IMimeTypeDetector', function($c) {
+ return $this->getServer()->getMimeTypeDetector();
+ });
+
+ $this->registerService('OCP\\INavigationManager', function($c) {
+ return $this->getServer()->getNavigationManager();
+ });
+
+ $this->registerService('OCP\\Notification\IManager', function($c) {
+ return $this->getServer()->getNotificationManager();
+ });
+
+ $this->registerService('OCP\\IPreview', function($c) {
+ return $this->getServer()->getPreviewManager();
+ });
+
+ $this->registerService('OCP\\IRequest', function () {
+ return $this->getServer()->getRequest();
+ });
+ $this->registerAlias('Request', 'OCP\\IRequest');
+
+ $this->registerService('OCP\\ITagManager', function($c) {
+ return $this->getServer()->getTagManager();
+ });
+
+ $this->registerService('OCP\\ITempManager', function($c) {
+ return $this->getServer()->getTempManager();
+ });
+
+ $this->registerAlias('OCP\\AppFramework\\Utility\\ITimeFactory', 'OC\AppFramework\Utility\TimeFactory');
+ $this->registerAlias('TimeFactory', 'OCP\\AppFramework\\Utility\\ITimeFactory');
+
+
+ $this->registerService('OCP\\Route\\IRouter', function($c) {
+ return $this->getServer()->getRouter();
+ });
+
+ $this->registerService('OCP\\ISearch', function($c) {
+ return $this->getServer()->getSearch();
+ });
+
+ $this->registerService('OCP\\ISearch', function($c) {
+ return $this->getServer()->getSearch();
+ });
+
+ $this->registerService('OCP\\Security\\ICrypto', function($c) {
+ return $this->getServer()->getCrypto();
+ });
+
+ $this->registerService('OCP\\Security\\IHasher', function($c) {
+ return $this->getServer()->getHasher();
+ });
+
+ $this->registerService('OCP\\Security\\ICredentialsManager', function($c) {
+ return $this->getServer()->getCredentialsManager();
+ });
+
+ $this->registerService('OCP\\Security\\ISecureRandom', function($c) {
+ return $this->getServer()->getSecureRandom();
+ });
+
+ $this->registerService('OCP\\Share\\IManager', function($c) {
+ return $this->getServer()->getShareManager();
+ });
+
+ $this->registerService('OCP\\SystemTag\\ISystemTagManager', function() {
+ return $this->getServer()->getSystemTagManager();
+ });
+
+ $this->registerService('OCP\\SystemTag\\ISystemTagObjectMapper', function() {
+ return $this->getServer()->getSystemTagObjectMapper();
+ });
+
+ $this->registerService('OCP\\IURLGenerator', function($c) {
+ return $this->getServer()->getURLGenerator();
+ });
+
+ $this->registerService('OCP\\IUserManager', function($c) {
+ return $this->getServer()->getUserManager();
+ });
+
+ $this->registerService('OCP\\IUserSession', function($c) {
+ return $this->getServer()->getUserSession();
+ });
+
+ $this->registerService('OCP\\ISession', function($c) {
+ return $this->getServer()->getSession();
+ });
+
+ $this->registerService('OCP\\Security\\IContentSecurityPolicyManager', function($c) {
+ return $this->getServer()->getContentSecurityPolicyManager();
+ });
+
+ $this->registerService('ServerContainer', function ($c) {
+ return $this->getServer();
+ });
+ $this->registerAlias('OCP\\IServerContainer', 'ServerContainer');
+
+ $this->registerService('Symfony\Component\EventDispatcher\EventDispatcherInterface', function ($c) {
+ return $this->getServer()->getEventDispatcher();
+ });
+
+ $this->registerService('OCP\\AppFramework\\IAppContainer', function ($c) {
+ return $c;
+ });
+
+ // commonly used attributes
+ $this->registerService('UserId', function ($c) {
+ return $c->query('OCP\\IUserSession')->getSession()->get('user_id');
+ });
+
+ $this->registerService('WebRoot', function ($c) {
+ return $c->query('ServerContainer')->getWebRoot();
+ });
+
+
+ /**
+ * App Framework APIs
+ */
+ $this->registerService('API', function($c){
+ $c->query('OCP\\ILogger')->debug(
+ 'Accessing the API class is deprecated! Use the appropriate ' .
+ 'services instead!'
+ );
+ return new API($c['AppName']);
+ });
+
+ $this->registerService('Protocol', function($c){
+ /** @var \OC\Server $server */
+ $server = $c->query('ServerContainer');
+ $protocol = $server->getRequest()->getHttpProtocol();
+ return new Http($_SERVER, $protocol);
+ });
+
+ $this->registerService('Dispatcher', function($c) {
+ return new Dispatcher(
+ $c['Protocol'],
+ $c['MiddlewareDispatcher'],
+ $c['ControllerMethodReflector'],
+ $c['Request']
+ );
+ });
+
+
+ /**
+ * Middleware
+ */
+ $app = $this;
+ $this->registerService('SecurityMiddleware', function($c) use ($app){
+ return new SecurityMiddleware(
+ $c['Request'],
+ $c['ControllerMethodReflector'],
+ $app->getServer()->getNavigationManager(),
+ $app->getServer()->getURLGenerator(),
+ $app->getServer()->getLogger(),
+ $c['AppName'],
+ $app->isLoggedIn(),
+ $app->isAdminUser(),
+ $app->getServer()->getContentSecurityPolicyManager()
+ );
+ });
+
+ $this->registerService('CORSMiddleware', function($c) {
+ return new CORSMiddleware(
+ $c['Request'],
+ $c['ControllerMethodReflector'],
+ $c['OCP\IUserSession']
+ );
+ });
+
+ $this->registerService('SessionMiddleware', function($c) use ($app) {
+ return new SessionMiddleware(
+ $c['Request'],
+ $c['ControllerMethodReflector'],
+ $app->getServer()->getSession()
+ );
+ });
+
+ $middleWares = &$this->middleWares;
+ $this->registerService('MiddlewareDispatcher', function($c) use (&$middleWares) {
+ $dispatcher = new MiddlewareDispatcher();
+ $dispatcher->registerMiddleware($c['CORSMiddleware']);
+ $dispatcher->registerMiddleware($c['SecurityMiddleware']);
+
+ foreach($middleWares as $middleWare) {
+ $dispatcher->registerMiddleware($c[$middleWare]);
+ }
+
+ $dispatcher->registerMiddleware($c['SessionMiddleware']);
+ return $dispatcher;
+ });
+
+ }
+
+
+ /**
+ * @deprecated implements only deprecated methods
+ * @return IApi
+ */
+ function getCoreApi()
+ {
+ return $this->query('API');
+ }
+
+ /**
+ * @return \OCP\IServerContainer
+ */
+ function getServer()
+ {
+ return OC::$server;
+ }
+
+ /**
+ * @param string $middleWare
+ * @return boolean|null
+ */
+ function registerMiddleWare($middleWare) {
+ array_push($this->middleWares, $middleWare);
+ }
+
+ /**
+ * used to return the appname of the set application
+ * @return string the name of your application
+ */
+ function getAppName() {
+ return $this->query('AppName');
+ }
+
+ /**
+ * @deprecated use IUserSession->isLoggedIn()
+ * @return boolean
+ */
+ function isLoggedIn() {
+ return \OC_User::isLoggedIn();
+ }
+
+ /**
+ * @deprecated use IGroupManager->isAdmin($userId)
+ * @return boolean
+ */
+ function isAdminUser() {
+ $uid = $this->getUserId();
+ return \OC_User::isAdminUser($uid);
+ }
+
+ private function getUserId() {
+ return $this->getServer()->getSession()->get('user_id');
+ }
+
+ /**
+ * @deprecated use the ILogger instead
+ * @param string $message
+ * @param string $level
+ * @return mixed
+ */
+ function log($message, $level) {
+ 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->getAppName(), $message, $level);
+ }
+
+ /**
+ * Register a capability
+ *
+ * @param string $serviceName e.g. 'OCA\Files\Capabilities'
+ */
+ public function registerCapability($serviceName) {
+ $this->query('OC\CapabilitiesManager')->registerCapability(function() use ($serviceName) {
+ return $this->query($serviceName);
+ });
+
+ }
+}
diff --git a/lib/private/AppFramework/Http.php b/lib/private/AppFramework/Http.php
new file mode 100644
index 00000000000..a99f7ea437f
--- /dev/null
+++ b/lib/private/AppFramework/Http.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Roeland Jago Douma <rullzer@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Tanghus <thomas@tanghus.net>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework;
+
+use OCP\AppFramework\Http as BaseHttp;
+
+class Http extends BaseHttp {
+
+ private $server;
+ private $protocolVersion;
+ protected $headers;
+
+ /**
+ * @param array $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
+ * @return string
+ */
+ 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(trim($this->server['HTTP_IF_NONE_MATCH']), '"') === (string)$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/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php
new file mode 100644
index 00000000000..641339c0d94
--- /dev/null
+++ b/lib/private/AppFramework/Http/Dispatcher.php
@@ -0,0 +1,179 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Georg Ehrke <georg@owncloud.com>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Tanghus <thomas@tanghus.net>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework\Http;
+
+use \OC\AppFramework\Middleware\MiddlewareDispatcher;
+use \OC\AppFramework\Http;
+use \OC\AppFramework\Utility\ControllerMethodReflector;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\IRequest;
+
+
+/**
+ * Class to dispatch the request to the middleware dispatcher
+ */
+class Dispatcher {
+
+ private $middlewareDispatcher;
+ private $protocol;
+ private $reflector;
+ private $request;
+
+ /**
+ * @param Http $protocol the http protocol with contains all status headers
+ * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
+ * runs the middleware
+ * @param ControllerMethodReflector $reflector the reflector that is used to inject
+ * the arguments for the controller
+ * @param IRequest $request the incoming request
+ */
+ public function __construct(Http $protocol,
+ MiddlewareDispatcher $middlewareDispatcher,
+ ControllerMethodReflector $reflector,
+ IRequest $request) {
+ $this->protocol = $protocol;
+ $this->middlewareDispatcher = $middlewareDispatcher;
+ $this->reflector = $reflector;
+ $this->request = $request;
+ }
+
+
+ /**
+ * 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
+ * @throws \Exception
+ */
+ public function dispatch(Controller $controller, $methodName) {
+ $out = array(null, array(), null);
+
+ try {
+ // prefill reflector with everything thats needed for the
+ // middlewares
+ $this->reflector->reflect($controller, $methodName);
+
+ $this->middlewareDispatcher->beforeController($controller,
+ $methodName);
+ $response = $this->executeController($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);
+ if (is_null($response)) {
+ throw $exception;
+ }
+ }
+
+ $response = $this->middlewareDispatcher->afterController(
+ $controller, $methodName, $response);
+
+ // depending on the cache object the headers need to be changed
+ $out[0] = $this->protocol->getStatusHeader($response->getStatus(),
+ $response->getLastModified(), $response->getETag());
+ $out[1] = array_merge($response->getHeaders());
+ $out[2] = $response->getCookies();
+ $out[3] = $this->middlewareDispatcher->beforeOutput(
+ $controller, $methodName, $response->render()
+ );
+ $out[4] = $response;
+
+ return $out;
+ }
+
+
+ /**
+ * Uses the reflected parameters, types and request parameters to execute
+ * the controller
+ * @param Controller $controller the controller to be executed
+ * @param string $methodName the method on the controller that should be executed
+ * @return Response
+ */
+ private function executeController($controller, $methodName) {
+ $arguments = array();
+
+ // valid types that will be casted
+ $types = array('int', 'integer', 'bool', 'boolean', 'float');
+
+ foreach($this->reflector->getParameters() as $param => $default) {
+
+ // try to get the parameter from the request object and cast
+ // it to the type annotated in the @param annotation
+ $value = $this->request->getParam($param, $default);
+ $type = $this->reflector->getType($param);
+
+ // if this is submitted using GET or a POST form, 'false' should be
+ // converted to false
+ if(($type === 'bool' || $type === 'boolean') &&
+ $value === 'false' &&
+ (
+ $this->request->method === 'GET' ||
+ strpos($this->request->getHeader('Content-Type'),
+ 'application/x-www-form-urlencoded') !== false
+ )
+ ) {
+ $value = false;
+
+ } elseif($value !== null && in_array($type, $types)) {
+ settype($value, $type);
+ }
+
+ $arguments[] = $value;
+ }
+
+ $response = call_user_func_array(array($controller, $methodName), $arguments);
+
+ // format response
+ if($response instanceof DataResponse || !($response instanceof Response)) {
+
+ // get format from the url format or request format parameter
+ $format = $this->request->getParam('format');
+
+ // if none is given try the first Accept header
+ if($format === null) {
+ $headers = $this->request->getHeader('Accept');
+ $format = $controller->getResponderByHTTPHeader($headers);
+ }
+
+ $response = $controller->buildResponse($response, $format);
+ }
+
+ return $response;
+ }
+
+}
diff --git a/lib/private/AppFramework/Http/Output.php b/lib/private/AppFramework/Http/Output.php
new file mode 100644
index 00000000000..469c809c21c
--- /dev/null
+++ b/lib/private/AppFramework/Http/Output.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Http;
+
+use OCP\AppFramework\Http\IOutput;
+
+/**
+ * Very thin wrapper class to make output testable
+ */
+class Output implements IOutput {
+ /** @var string */
+ private $webRoot;
+
+ /**
+ * @param $webRoot
+ */
+ public function __construct($webRoot) {
+ $this->webRoot = $webRoot;
+ }
+
+ /**
+ * @param string $out
+ */
+ public function setOutput($out) {
+ print($out);
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return bool false if an error occurred
+ */
+ public function setReadfile($path) {
+ return @readfile($path);
+ }
+
+ /**
+ * @param string $header
+ */
+ public function setHeader($header) {
+ header($header);
+ }
+
+ /**
+ * @param int $code sets the http status code
+ */
+ public function setHttpResponseCode($code) {
+ http_response_code($code);
+ }
+
+ /**
+ * @return int returns the current http response code
+ */
+ public function getHttpResponseCode() {
+ return http_response_code();
+ }
+
+ /**
+ * @param string $name
+ * @param string $value
+ * @param int $expire
+ * @param string $path
+ * @param string $domain
+ * @param bool $secure
+ * @param bool $httpOnly
+ */
+ public function setCookie($name, $value, $expire, $path, $domain, $secure, $httpOnly) {
+ $path = $this->webRoot ? : '/';
+ setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly);
+ }
+
+}
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
new file mode 100644
index 00000000000..7cd8cedcfdd
--- /dev/null
+++ b/lib/private/AppFramework/Http/Request.php
@@ -0,0 +1,771 @@
+<?php
+/**
+ * @author Bart Visscher <bartv@thisnet.nl>
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Mitar <mitar.git@tnode.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Roeland Jago Douma <rullzer@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Tanghus <thomas@tanghus.net>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Http;
+
+use OC\Security\CSRF\CsrfToken;
+use OC\Security\CSRF\CsrfTokenManager;
+use OC\Security\TrustedDomainHelper;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\Security\ICrypto;
+use OCP\Security\ISecureRandom;
+
+/**
+ * Class for accessing variables in the request.
+ * This class provides an immutable object with request variables.
+ *
+ * @property mixed[] cookies
+ * @property mixed[] env
+ * @property mixed[] files
+ * @property string method
+ * @property mixed[] parameters
+ * @property mixed[] server
+ */
+class Request implements \ArrayAccess, \Countable, IRequest {
+
+ const USER_AGENT_IE = '/(MSIE)|(Trident)/';
+ const USER_AGENT_IE_8 = '/MSIE 8.0/';
+ // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
+ const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/';
+ // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
+ const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
+ // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
+ const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/';
+ // Safari User Agent from http://www.useragentstring.com/pages/Safari/
+ const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
+ // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
+ const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
+ const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
+ const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) ownCloud\-iOS.*$/';
+ const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/';
+ const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/';
+ const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost)$/';
+
+ protected $inputStream;
+ protected $content;
+ protected $items = array();
+ protected $allowedKeys = array(
+ 'get',
+ 'post',
+ 'files',
+ 'server',
+ 'env',
+ 'cookies',
+ 'urlParams',
+ 'parameters',
+ 'method',
+ 'requesttoken',
+ );
+ /** @var ISecureRandom */
+ protected $secureRandom;
+ /** @var IConfig */
+ protected $config;
+ /** @var string */
+ protected $requestId = '';
+ /** @var ICrypto */
+ protected $crypto;
+ /** @var CsrfTokenManager|null */
+ protected $csrfTokenManager;
+
+ /** @var bool */
+ protected $contentDecoded = false;
+
+ /**
+ * @param array $vars An associative array with the following optional values:
+ * - array 'urlParams' the parameters which were matched from the URL
+ * - array 'get' the $_GET array
+ * - array|string 'post' the $_POST array or JSON string
+ * - array 'files' the $_FILES array
+ * - array 'server' the $_SERVER array
+ * - array 'env' the $_ENV array
+ * - array 'cookies' the $_COOKIE array
+ * - string 'method' the request method (GET, POST etc)
+ * - string|false 'requesttoken' the requesttoken or false when not available
+ * @param ISecureRandom $secureRandom
+ * @param IConfig $config
+ * @param CsrfTokenManager|null $csrfTokenManager
+ * @param string $stream
+ * @see http://www.php.net/manual/en/reserved.variables.php
+ */
+ public function __construct(array $vars=array(),
+ ISecureRandom $secureRandom = null,
+ IConfig $config,
+ CsrfTokenManager $csrfTokenManager = null,
+ $stream = 'php://input') {
+ $this->inputStream = $stream;
+ $this->items['params'] = array();
+ $this->secureRandom = $secureRandom;
+ $this->config = $config;
+ $this->csrfTokenManager = $csrfTokenManager;
+
+ if(!array_key_exists('method', $vars)) {
+ $vars['method'] = 'GET';
+ }
+
+ foreach($this->allowedKeys as $name) {
+ $this->items[$name] = isset($vars[$name])
+ ? $vars[$name]
+ : array();
+ }
+
+ $this->items['parameters'] = array_merge(
+ $this->items['get'],
+ $this->items['post'],
+ $this->items['urlParams'],
+ $this->items['params']
+ );
+
+ }
+ /**
+ * @param array $parameters
+ */
+ public function setUrlParameters(array $parameters) {
+ $this->items['urlParams'] = $parameters;
+ $this->items['parameters'] = array_merge(
+ $this->items['parameters'],
+ $this->items['urlParams']
+ );
+ }
+
+ /**
+ * Countable method
+ * @return int
+ */
+ 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 boolean
+ */
+ 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
+ * @param string $name
+ * @param mixed $value
+ */
+ 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 you access e.g. ->post but the current HTTP request method
+ * is GET a \LogicException will be thrown.
+ *
+ * @param string $name The key to look for.
+ * @throws \LogicException
+ * @return mixed|null
+ */
+ public function __get($name) {
+ switch($name) {
+ case 'put':
+ case 'patch':
+ case 'get':
+ case 'post':
+ if($this->method !== strtoupper($name)) {
+ throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
+ }
+ return $this->getContent();
+ case 'files':
+ case 'server':
+ case 'env':
+ case 'cookies':
+ case 'urlParams':
+ case 'method':
+ return isset($this->items[$name])
+ ? $this->items[$name]
+ : null;
+ case 'parameters':
+ case 'params':
+ return $this->getContent();
+ default;
+ return isset($this[$name])
+ ? $this[$name]
+ : null;
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name) {
+ return isset($this->items['parameters'][$name]);
+ }
+
+ /**
+ * @param string $id
+ */
+ 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;
+ }
+
+ /**
+ * 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 cookie variables
+ * @param string $key the key that will be taken from the $_COOKIE array
+ * @return string the value in the $_COOKIE element
+ */
+ public function getCookie($key) {
+ return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
+ }
+
+ /**
+ * Returns the request body content.
+ *
+ * If the HTTP request method is PUT and the body
+ * not application/x-www-form-urlencoded or application/json a stream
+ * resource is returned, otherwise an array.
+ *
+ * @return array|string|resource The request body content or a resource to read the body stream.
+ *
+ * @throws \LogicException
+ */
+ protected function getContent() {
+ // If the content can't be parsed into an array then return a stream resource.
+ if ($this->method === 'PUT'
+ && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
+ && strpos($this->getHeader('Content-Type'), 'application/json') === false
+ ) {
+ if ($this->content === false) {
+ throw new \LogicException(
+ '"put" can only be accessed once if not '
+ . 'application/x-www-form-urlencoded or application/json.'
+ );
+ }
+ $this->content = false;
+ return fopen($this->inputStream, 'rb');
+ } else {
+ $this->decodeContent();
+ return $this->items['parameters'];
+ }
+ }
+
+ /**
+ * Attempt to decode the content and populate parameters
+ */
+ protected function decodeContent() {
+ if ($this->contentDecoded) {
+ return;
+ }
+ $params = [];
+
+ // 'application/json' must be decoded manually.
+ if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
+ $params = json_decode(file_get_contents($this->inputStream), true);
+ if(count($params) > 0) {
+ $this->items['params'] = $params;
+ if($this->method === 'POST') {
+ $this->items['post'] = $params;
+ }
+ }
+
+ // Handle application/x-www-form-urlencoded for methods other than GET
+ // or post correctly
+ } elseif($this->method !== 'GET'
+ && $this->method !== 'POST'
+ && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
+
+ parse_str(file_get_contents($this->inputStream), $params);
+ if(is_array($params)) {
+ $this->items['params'] = $params;
+ }
+ }
+
+ if (is_array($params)) {
+ $this->items['parameters'] = array_merge($this->items['parameters'], $params);
+ }
+ $this->contentDecoded = true;
+ }
+
+
+ /**
+ * Checks if the CSRF check was correct
+ * @return bool true if CSRF check passed
+ */
+ public function passesCSRFCheck() {
+ if($this->csrfTokenManager === null) {
+ return false;
+ }
+
+ if (isset($this->items['get']['requesttoken'])) {
+ $token = $this->items['get']['requesttoken'];
+ } elseif (isset($this->items['post']['requesttoken'])) {
+ $token = $this->items['post']['requesttoken'];
+ } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
+ $token = $this->items['server']['HTTP_REQUESTTOKEN'];
+ } else {
+ //no token found.
+ return false;
+ }
+ $token = new CsrfToken($token);
+
+ return $this->csrfTokenManager->isTokenValid($token);
+ }
+
+ /**
+ * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
+ * If `mod_unique_id` is installed this value will be taken.
+ * @return string
+ */
+ public function getId() {
+ if(isset($this->server['UNIQUE_ID'])) {
+ return $this->server['UNIQUE_ID'];
+ }
+
+ if(empty($this->requestId)) {
+ $this->requestId = $this->secureRandom->generate(20);
+ }
+
+ return $this->requestId;
+ }
+
+ /**
+ * Returns the remote address, if the connection came from a trusted proxy
+ * and `forwarded_for_headers` has been configured then the IP address
+ * specified in this header will be returned instead.
+ * Do always use this instead of $_SERVER['REMOTE_ADDR']
+ * @return string IP address
+ */
+ public function getRemoteAddress() {
+ $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
+ $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
+
+ if(is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) {
+ $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
+ 'HTTP_X_FORWARDED_FOR'
+ // only have one default, so we cannot ship an insecure product out of the box
+ ]);
+
+ foreach($forwardedForHeaders as $header) {
+ if(isset($this->server[$header])) {
+ foreach(explode(',', $this->server[$header]) as $IP) {
+ $IP = trim($IP);
+ if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
+ return $IP;
+ }
+ }
+ }
+ }
+ }
+
+ return $remoteAddress;
+ }
+
+ /**
+ * Check overwrite condition
+ * @param string $type
+ * @return bool
+ */
+ private function isOverwriteCondition($type = '') {
+ $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '') . '/';
+ $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
+ return $regex === '//' || preg_match($regex, $remoteAddr) === 1
+ || $type !== 'protocol';
+ }
+
+ /**
+ * Returns the server protocol. It respects one or more reverse proxies servers
+ * and load balancers
+ * @return string Server protocol (http or https)
+ */
+ public function getServerProtocol() {
+ if($this->config->getSystemValue('overwriteprotocol') !== ''
+ && $this->isOverwriteCondition('protocol')) {
+ return $this->config->getSystemValue('overwriteprotocol');
+ }
+
+ if (isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
+ if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
+ $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
+ $proto = strtolower(trim($parts[0]));
+ } else {
+ $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
+ }
+
+ // Verify that the protocol is always HTTP or HTTPS
+ // default to http if an invalid value is provided
+ return $proto === 'https' ? 'https' : 'http';
+ }
+
+ if (isset($this->server['HTTPS'])
+ && $this->server['HTTPS'] !== null
+ && $this->server['HTTPS'] !== 'off'
+ && $this->server['HTTPS'] !== '') {
+ return 'https';
+ }
+
+ return 'http';
+ }
+
+ /**
+ * Returns the used HTTP protocol.
+ *
+ * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
+ */
+ public function getHttpProtocol() {
+ $claimedProtocol = strtoupper($this->server['SERVER_PROTOCOL']);
+
+ $validProtocols = [
+ 'HTTP/1.0',
+ 'HTTP/1.1',
+ 'HTTP/2',
+ ];
+
+ if(in_array($claimedProtocol, $validProtocols, true)) {
+ return $claimedProtocol;
+ }
+
+ return 'HTTP/1.1';
+ }
+
+ /**
+ * Returns the request uri, even if the website uses one or more
+ * reverse proxies
+ * @return string
+ */
+ public function getRequestUri() {
+ $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
+ if($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
+ $uri = $this->getScriptName() . substr($uri, strlen($this->server['SCRIPT_NAME']));
+ }
+ return $uri;
+ }
+
+ /**
+ * Get raw PathInfo from request (not urldecoded)
+ * @throws \Exception
+ * @return string Path info
+ */
+ public function getRawPathInfo() {
+ $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
+ // remove too many leading slashes - can be caused by reverse proxy configuration
+ if (strpos($requestUri, '/') === 0) {
+ $requestUri = '/' . ltrim($requestUri, '/');
+ }
+
+ $requestUri = preg_replace('%/{2,}%', '/', $requestUri);
+
+ // Remove the query string from REQUEST_URI
+ if ($pos = strpos($requestUri, '?')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+
+ $scriptName = $this->server['SCRIPT_NAME'];
+ $pathInfo = $requestUri;
+
+ // strip off the script name's dir and file name
+ // FIXME: Sabre does not really belong here
+ list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($scriptName);
+ if (!empty($path)) {
+ if($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
+ $pathInfo = substr($pathInfo, strlen($path));
+ } else {
+ throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
+ }
+ }
+ if (strpos($pathInfo, '/'.$name) === 0) {
+ $pathInfo = substr($pathInfo, strlen($name) + 1);
+ }
+ if (strpos($pathInfo, $name) === 0) {
+ $pathInfo = substr($pathInfo, strlen($name));
+ }
+ if($pathInfo === false || $pathInfo === '/'){
+ return '';
+ } else {
+ return $pathInfo;
+ }
+ }
+
+ /**
+ * Get PathInfo from request
+ * @throws \Exception
+ * @return string|false Path info or false when not found
+ */
+ public function getPathInfo() {
+ if(isset($this->server['PATH_INFO'])) {
+ return $this->server['PATH_INFO'];
+ }
+
+ $pathInfo = $this->getRawPathInfo();
+ // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
+ $pathInfo = rawurldecode($pathInfo);
+ $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
+
+ switch($encoding) {
+ case 'ISO-8859-1' :
+ $pathInfo = utf8_encode($pathInfo);
+ }
+ // end copy
+
+ return $pathInfo;
+ }
+
+ /**
+ * Returns the script name, even if the website uses one or more
+ * reverse proxies
+ * @return string the script name
+ */
+ public function getScriptName() {
+ $name = $this->server['SCRIPT_NAME'];
+ $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot');
+ if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
+ // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
+ $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -strlen('lib/private/appframework/http/')));
+ $suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), strlen($serverRoot)));
+ $name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
+ }
+ return $name;
+ }
+
+ /**
+ * Checks whether the user agent matches a given regex
+ * @param array $agent array of agent names
+ * @return bool true if at least one of the given agent matches, false otherwise
+ */
+ public function isUserAgent(array $agent) {
+ if (!isset($this->server['HTTP_USER_AGENT'])) {
+ return false;
+ }
+ foreach ($agent as $regex) {
+ if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the unverified server host from the headers without checking
+ * whether it is a trusted domain
+ * @return string Server host
+ */
+ public function getInsecureServerHost() {
+ $host = 'localhost';
+ if (isset($this->server['HTTP_X_FORWARDED_HOST'])) {
+ if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
+ $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
+ $host = trim(current($parts));
+ } else {
+ $host = $this->server['HTTP_X_FORWARDED_HOST'];
+ }
+ } else {
+ if (isset($this->server['HTTP_HOST'])) {
+ $host = $this->server['HTTP_HOST'];
+ } else if (isset($this->server['SERVER_NAME'])) {
+ $host = $this->server['SERVER_NAME'];
+ }
+ }
+ return $host;
+ }
+
+
+ /**
+ * Returns the server host from the headers, or the first configured
+ * trusted domain if the host isn't in the trusted list
+ * @return string Server host
+ */
+ public function getServerHost() {
+ // overwritehost is always trusted
+ $host = $this->getOverwriteHost();
+ if ($host !== null) {
+ return $host;
+ }
+
+ // get the host from the headers
+ $host = $this->getInsecureServerHost();
+
+ // Verify that the host is a trusted domain if the trusted domains
+ // are defined
+ // If no trusted domain is provided the first trusted domain is returned
+ $trustedDomainHelper = new TrustedDomainHelper($this->config);
+ if ($trustedDomainHelper->isTrustedDomain($host)) {
+ return $host;
+ } else {
+ $trustedList = $this->config->getSystemValue('trusted_domains', []);
+ if(!empty($trustedList)) {
+ return $trustedList[0];
+ } else {
+ return '';
+ }
+ }
+ }
+
+ /**
+ * Returns the overwritehost setting from the config if set and
+ * if the overwrite condition is met
+ * @return string|null overwritehost value or null if not defined or the defined condition
+ * isn't met
+ */
+ private function getOverwriteHost() {
+ if($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
+ return $this->config->getSystemValue('overwritehost');
+ }
+ return null;
+ }
+
+}
diff --git a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
new file mode 100644
index 00000000000..4bd25f79bba
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Tanghus <thomas@tanghus.net>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework\Middleware;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\MiddleWare;
+
+/**
+ * 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 there is an exception
+ $middlewareCount = count($this->middlewares);
+ for($i = 0; $i < $middlewareCount; $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 can't 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/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
new file mode 100644
index 00000000000..258119b326a
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Middleware\Security;
+
+use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
+use OC\AppFramework\Utility\ControllerMethodReflector;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IRequest;
+use OCP\IUserSession;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Middleware;
+
+/**
+ * This middleware sets the correct CORS headers on a response if the
+ * controller has the @CORS annotation. This is needed for webapps that want
+ * to access an API and don't run on the same domain, see
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
+ */
+class CORSMiddleware extends Middleware {
+
+ /**
+ * @var IRequest
+ */
+ private $request;
+
+ /**
+ * @var ControllerMethodReflector
+ */
+ private $reflector;
+
+ /**
+ * @var IUserSession
+ */
+ private $session;
+
+ /**
+ * @param IRequest $request
+ * @param ControllerMethodReflector $reflector
+ * @param IUserSession $session
+ */
+ public function __construct(IRequest $request,
+ ControllerMethodReflector $reflector,
+ IUserSession $session) {
+ $this->request = $request;
+ $this->reflector = $reflector;
+ $this->session = $session;
+ }
+
+ /**
+ * 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
+ * @throws SecurityException
+ * @since 6.0.0
+ */
+ public function beforeController($controller, $methodName){
+ // ensure that @CORS annotated API routes are not used in conjunction
+ // with session authentication since this enables CSRF attack vectors
+ if ($this->reflector->hasAnnotation('CORS') &&
+ !$this->reflector->hasAnnotation('PublicPage')) {
+ $user = $this->request->server['PHP_AUTH_USER'];
+ $pass = $this->request->server['PHP_AUTH_PW'];
+
+ $this->session->logout();
+ if(!$this->session->login($user, $pass)) {
+ throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED);
+ }
+ }
+ }
+
+ /**
+ * 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
+ * @throws SecurityException
+ */
+ public function afterController($controller, $methodName, Response $response){
+ // only react if its a CORS request and if the request sends origin and
+
+ if(isset($this->request->server['HTTP_ORIGIN']) &&
+ $this->reflector->hasAnnotation('CORS')) {
+
+ // allow credentials headers must not be true or CSRF is possible
+ // otherwise
+ foreach($response->getHeaders() as $header => $value) {
+ if(strtolower($header) === 'access-control-allow-credentials' &&
+ strtolower(trim($value)) === 'true') {
+ $msg = 'Access-Control-Allow-Credentials must not be '.
+ 'set to true in order to prevent CSRF';
+ throw new SecurityException($msg);
+ }
+ }
+
+ $origin = $this->request->server['HTTP_ORIGIN'];
+ $response->addHeader('Access-Control-Allow-Origin', $origin);
+ }
+ return $response;
+ }
+
+ /**
+ * If an SecurityException is being caught return a JSON error response
+ *
+ * @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 can't 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){
+ $response = new JSONResponse(['message' => $exception->getMessage()]);
+ if($exception->getCode() !== 0) {
+ $response->setStatus($exception->getCode());
+ } else {
+ $response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ return $response;
+ }
+
+ throw $exception;
+ }
+
+}
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php
new file mode 100644
index 00000000000..59e247f3307
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Appframework\Middleware\Security\Exceptions;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class AppNotEnabledException is thrown when a resource for an application is
+ * requested that is not enabled.
+ *
+ * @package OC\Appframework\Middleware\Security\Exceptions
+ */
+class AppNotEnabledException extends SecurityException {
+ public function __construct() {
+ parent::__construct('App is not enabled', Http::STATUS_PRECONDITION_FAILED);
+ }
+}
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php
new file mode 100644
index 00000000000..0eeb81730d4
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Appframework\Middleware\Security\Exceptions;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class CrossSiteRequestForgeryException is thrown when a CSRF exception has
+ * been encountered.
+ *
+ * @package OC\Appframework\Middleware\Security\Exceptions
+ */
+class CrossSiteRequestForgeryException extends SecurityException {
+ public function __construct() {
+ parent::__construct('CSRF check failed', Http::STATUS_PRECONDITION_FAILED);
+ }
+}
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php
new file mode 100644
index 00000000000..be0f2f9d2a9
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Appframework\Middleware\Security\Exceptions;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class NotAdminException is thrown when a resource has been requested by a
+ * non-admin user that is not accessible to non-admin users.
+ *
+ * @package OC\Appframework\Middleware\Security\Exceptions
+ */
+class NotAdminException extends SecurityException {
+ public function __construct() {
+ parent::__construct('Logged in user must be an admin', Http::STATUS_FORBIDDEN);
+ }
+}
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php
new file mode 100644
index 00000000000..f5b2e032032
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Appframework\Middleware\Security\Exceptions;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class NotLoggedInException is thrown when a resource has been requested by a
+ * guest user that is not accessible to the public.
+ *
+ * @package OC\Appframework\Middleware\Security\Exceptions
+ */
+class NotLoggedInException extends SecurityException {
+ public function __construct() {
+ parent::__construct('Current user is not logged in', Http::STATUS_UNAUTHORIZED);
+ }
+}
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
new file mode 100644
index 00000000000..c86614ec477
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Middleware\Security\Exceptions;
+
+/**
+ * Class SecurityException is the base class for security exceptions thrown by
+ * the security middleware.
+ *
+ * @package OC\AppFramework\Middleware\Security\Exceptions
+ */
+class SecurityException extends \Exception {}
diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
new file mode 100644
index 00000000000..4afd29cd060
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Tanghus <thomas@tanghus.net>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework\Middleware\Security;
+
+use OC\Appframework\Middleware\Security\Exceptions\AppNotEnabledException;
+use OC\Appframework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
+use OC\Appframework\Middleware\Security\Exceptions\NotAdminException;
+use OC\Appframework\Middleware\Security\Exceptions\NotLoggedInException;
+use OC\AppFramework\Utility\ControllerMethodReflector;
+use OC\Security\CSP\ContentSecurityPolicyManager;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Middleware;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\INavigationManager;
+use OCP\IURLGenerator;
+use OCP\IRequest;
+use OCP\ILogger;
+use OCP\AppFramework\Controller;
+use OCP\Util;
+use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
+
+/**
+ * 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 {
+ /** @var INavigationManager */
+ private $navigationManager;
+ /** @var IRequest */
+ private $request;
+ /** @var ControllerMethodReflector */
+ private $reflector;
+ /** @var string */
+ private $appName;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var ILogger */
+ private $logger;
+ /** @var bool */
+ private $isLoggedIn;
+ /** @var bool */
+ private $isAdminUser;
+ /** @var ContentSecurityPolicyManager */
+ private $contentSecurityPolicyManager;
+
+ /**
+ * @param IRequest $request
+ * @param ControllerMethodReflector $reflector
+ * @param INavigationManager $navigationManager
+ * @param IURLGenerator $urlGenerator
+ * @param ILogger $logger
+ * @param string $appName
+ * @param bool $isLoggedIn
+ * @param bool $isAdminUser
+ * @param ContentSecurityPolicyManager $contentSecurityPolicyManager
+ */
+ public function __construct(IRequest $request,
+ ControllerMethodReflector $reflector,
+ INavigationManager $navigationManager,
+ IURLGenerator $urlGenerator,
+ ILogger $logger,
+ $appName,
+ $isLoggedIn,
+ $isAdminUser,
+ ContentSecurityPolicyManager $contentSecurityPolicyManager) {
+ $this->navigationManager = $navigationManager;
+ $this->request = $request;
+ $this->reflector = $reflector;
+ $this->appName = $appName;
+ $this->urlGenerator = $urlGenerator;
+ $this->logger = $logger;
+ $this->isLoggedIn = $isLoggedIn;
+ $this->isAdminUser = $isAdminUser;
+ $this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
+ }
+
+
+ /**
+ * This runs all the security checks before a method call. The
+ * security checks are determined by inspecting the controller method
+ * annotations
+ * @param string $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) {
+
+ // this will set the current navigation entry of the app, use this only
+ // for normal HTML requests and not for AJAX requests
+ $this->navigationManager->setActiveEntry($this->appName);
+
+ // security checks
+ $isPublicPage = $this->reflector->hasAnnotation('PublicPage');
+ if(!$isPublicPage) {
+ if(!$this->isLoggedIn) {
+ throw new NotLoggedInException();
+ }
+
+ if(!$this->reflector->hasAnnotation('NoAdminRequired')) {
+ if(!$this->isAdminUser) {
+ throw new NotAdminException();
+ }
+ }
+ }
+
+ // CSRF check - also registers the CSRF token since the session may be closed later
+ Util::callRegister();
+ if(!$this->reflector->hasAnnotation('NoCSRFRequired')) {
+ if(!$this->request->passesCSRFCheck()) {
+ throw new CrossSiteRequestForgeryException();
+ }
+ }
+
+ /**
+ * FIXME: Use DI once available
+ * Checks if app is enabled (also includes a check whether user is allowed to access the resource)
+ * The getAppPath() check is here since components such as settings also use the AppFramework and
+ * therefore won't pass this check.
+ */
+ if(\OC_App::getAppPath($this->appName) !== false && !\OC_App::isEnabled($this->appName)) {
+ throw new AppNotEnabledException();
+ }
+
+ }
+
+ /**
+ * Performs the default CSP modifications that may be injected by other
+ * applications
+ *
+ * @param Controller $controller
+ * @param string $methodName
+ * @param Response $response
+ * @return Response
+ */
+ public function afterController($controller, $methodName, Response $response) {
+ $policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy();
+
+ $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
+ $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
+
+ $response->setContentSecurityPolicy($defaultPolicy);
+
+ return $response;
+ }
+
+ /**
+ * 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 can't 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()
+ );
+ } else {
+ if($exception instanceof NotLoggedInException) {
+ $url = $this->urlGenerator->linkToRoute(
+ 'core.login.showLoginForm',
+ [
+ 'redirect_url' => urlencode($this->request->server['REQUEST_URI']),
+ ]
+ );
+ $response = new RedirectResponse($url);
+ } else {
+ $response = new TemplateResponse('core', '403', ['file' => $exception->getMessage()], 'guest');
+ $response->setStatus($exception->getCode());
+ }
+ }
+
+ $this->logger->debug($exception->getMessage());
+ return $response;
+ }
+
+ throw $exception;
+ }
+
+}
diff --git a/lib/private/AppFramework/Middleware/SessionMiddleware.php b/lib/private/AppFramework/Middleware/SessionMiddleware.php
new file mode 100644
index 00000000000..b218b48ea11
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/SessionMiddleware.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Middleware;
+
+use OC\AppFramework\Utility\ControllerMethodReflector;
+use OCP\IRequest;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Middleware;
+use OCP\ISession;
+
+class SessionMiddleware extends Middleware {
+
+ /**
+ * @var IRequest
+ */
+ private $request;
+
+ /**
+ * @var ControllerMethodReflector
+ */
+ private $reflector;
+
+ /**
+ * @param IRequest $request
+ * @param ControllerMethodReflector $reflector
+ */
+ public function __construct(IRequest $request,
+ ControllerMethodReflector $reflector,
+ ISession $session
+) {
+ $this->request = $request;
+ $this->reflector = $reflector;
+ $this->session = $session;
+ }
+
+ /**
+ * @param \OCP\AppFramework\Controller $controller
+ * @param string $methodName
+ */
+ public function beforeController($controller, $methodName) {
+ $useSession = $this->reflector->hasAnnotation('UseSession');
+ if (!$useSession) {
+ $this->session->close();
+ }
+ }
+
+ /**
+ * @param \OCP\AppFramework\Controller $controller
+ * @param string $methodName
+ * @param Response $response
+ * @return Response
+ */
+ public function afterController($controller, $methodName, Response $response){
+ $useSession = $this->reflector->hasAnnotation('UseSession');
+ if ($useSession) {
+ $this->session->close();
+ }
+ return $response;
+ }
+
+}
diff --git a/lib/private/AppFramework/Routing/RouteActionHandler.php b/lib/private/AppFramework/Routing/RouteActionHandler.php
new file mode 100644
index 00000000000..e8a7b8a1c61
--- /dev/null
+++ b/lib/private/AppFramework/Routing/RouteActionHandler.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Routing;
+
+use \OC\AppFramework\App;
+use \OC\AppFramework\DependencyInjection\DIContainer;
+
+class RouteActionHandler {
+ private $controllerName;
+ private $actionName;
+ private $container;
+
+ /**
+ * @param string $controllerName
+ * @param string $actionName
+ */
+ 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, $this->container, $params);
+ }
+}
diff --git a/lib/private/AppFramework/Routing/RouteConfig.php b/lib/private/AppFramework/Routing/RouteConfig.php
new file mode 100644
index 00000000000..f2ec5c0c881
--- /dev/null
+++ b/lib/private/AppFramework/Routing/RouteConfig.php
@@ -0,0 +1,200 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Patrick Paysant <ppaysant@linagora.com>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Routing;
+
+use OC\AppFramework\DependencyInjection\DIContainer;
+use OCP\Route\IRouter;
+
+/**
+ * Class RouteConfig
+ * @package OC\AppFramework\routing
+ */
+class RouteConfig {
+ private $container;
+ private $router;
+ private $routes;
+ private $appName;
+
+ /**
+ * @param \OC\AppFramework\DependencyInjection\DIContainer $container
+ * @param \OCP\Route\IRouter $router
+ * @internal param $appName
+ */
+ public function __construct(DIContainer $container, IRouter $router, $routes) {
+ $this->routes = $routes;
+ $this->container = $container;
+ $this->router = $router;
+ $this->appName = $container['AppName'];
+ }
+
+ /**
+ * The routes and resource will be registered to the \OCP\Route\IRouter
+ */
+ public function register() {
+
+ // parse simple
+ $this->processSimpleRoutes($this->routes);
+
+ // parse resources
+ $this->processResources($this->routes);
+ }
+
+ /**
+ * Creates one route base on the give configuration
+ * @param array $routes
+ * @throws \UnexpectedValueException
+ */
+ private function processSimpleRoutes($routes)
+ {
+ $simpleRoutes = isset($routes['routes']) ? $routes['routes'] : array();
+ foreach ($simpleRoutes as $simpleRoute) {
+ $name = $simpleRoute['name'];
+ $postfix = '';
+
+ if (isset($simpleRoute['postfix'])) {
+ $postfix = $simpleRoute['postfix'];
+ }
+
+ $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);
+ $router = $this->router->create($this->appName.'.'.$controller.'.'.$action . $postfix, $url)
+ ->method($verb)
+ ->action($handler);
+
+ // optionally register requirements for route. This is used to
+ // tell the route parser how url parameters should be matched
+ if(array_key_exists('requirements', $simpleRoute)) {
+ $router->requirements($simpleRoute['requirements']);
+ }
+
+ // optionally register defaults for route. This is used to
+ // tell the route parser how url parameters should be default valued
+ if(array_key_exists('defaults', $simpleRoute)) {
+ $router->defaults($simpleRoute['defaults']);
+ }
+ }
+ }
+
+ /**
+ * For a given name and url restful routes are created:
+ * - index
+ * - show
+ * - new
+ * - create
+ * - update
+ * - destroy
+ *
+ * @param array $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
+ 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 . '/{id}';
+ }
+ 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 string $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 string $action
+ * @return string
+ */
+ private function buildActionName($action) {
+ return $this->underScoreToCamelCase($action);
+ }
+
+ /**
+ * Underscored strings are converted to camel case strings
+ * @param string $str
+ * @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/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
new file mode 100644
index 00000000000..de83749fbaf
--- /dev/null
+++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Olivier Paroz <github@oparoz.com>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework\Utility;
+
+use \OCP\AppFramework\Utility\IControllerMethodReflector;
+
+
+/**
+ * Reads and parses annotations from doc comments
+ */
+class ControllerMethodReflector implements IControllerMethodReflector{
+
+ private $annotations;
+ private $types;
+ private $parameters;
+
+ public function __construct() {
+ $this->types = array();
+ $this->parameters = array();
+ $this->annotations = array();
+ }
+
+
+ /**
+ * @param object $object an object or classname
+ * @param string $method the method which we want to inspect
+ */
+ public function reflect($object, $method){
+ $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];
+
+ // extract type parameter information
+ preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches);
+ $this->types = array_combine($matches['var'], $matches['type']);
+
+ foreach ($reflection->getParameters() as $param) {
+ // extract type information from PHP 7 scalar types and prefer them
+ // over phpdoc annotations
+ if (method_exists($param, 'getType')) {
+ $type = $param->getType();
+ if ($type !== null) {
+ $this->types[$param->getName()] = (string) $type;
+ }
+ }
+
+ if($param->isOptional()) {
+ $default = $param->getDefaultValue();
+ } else {
+ $default = null;
+ }
+ $this->parameters[$param->name] = $default;
+ }
+ }
+
+
+ /**
+ * Inspects the PHPDoc parameters for types
+ * @param string $parameter the parameter whose type comments should be
+ * parsed
+ * @return string|null type in the type parameters (@param int $something)
+ * would return int or null if not existing
+ */
+ public function getType($parameter) {
+ if(array_key_exists($parameter, $this->types)) {
+ return $this->types[$parameter];
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * @return array the arguments of the method with key => default value
+ */
+ public function getParameters() {
+ return $this->parameters;
+ }
+
+
+ /**
+ * 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/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
new file mode 100644
index 00000000000..78ded39735e
--- /dev/null
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\AppFramework\Utility;
+
+use ReflectionClass;
+use ReflectionException;
+use Closure;
+use Pimple\Container;
+use OCP\AppFramework\QueryException;
+use OCP\IContainer;
+
+/**
+ * Class SimpleContainer
+ *
+ * SimpleContainer is a simple implementation of IContainer on basis of Pimple
+ */
+class SimpleContainer extends Container implements IContainer {
+
+
+ /**
+ * @param ReflectionClass $class the class to instantiate
+ * @return \stdClass the created class
+ */
+ private function buildClass(ReflectionClass $class) {
+ $constructor = $class->getConstructor();
+ if ($constructor === null) {
+ return $class->newInstance();
+ } else {
+ $parameters = [];
+ foreach ($constructor->getParameters() as $parameter) {
+ $parameterClass = $parameter->getClass();
+
+ // try to find out if it is a class or a simple parameter
+ if ($parameterClass === null) {
+ $resolveName = $parameter->getName();
+ } else {
+ $resolveName = $parameterClass->name;
+ }
+
+ $parameters[] = $this->query($resolveName);
+ }
+ return $class->newInstanceArgs($parameters);
+ }
+ }
+
+
+ /**
+ * If a parameter is not registered in the container try to instantiate it
+ * by using reflection to find out how to build the class
+ * @param string $name the class name to resolve
+ * @return \stdClass
+ * @throws QueryException if the class could not be found or instantiated
+ */
+ public function resolve($name) {
+ $baseMsg = 'Could not resolve ' . $name . '!';
+ try {
+ $class = new ReflectionClass($name);
+ if ($class->isInstantiable()) {
+ return $this->buildClass($class);
+ } else {
+ throw new QueryException($baseMsg .
+ ' Class can not be instantiated');
+ }
+ } catch(ReflectionException $e) {
+ throw new QueryException($baseMsg . ' ' . $e->getMessage());
+ }
+ }
+
+
+ /**
+ * @param string $name name of the service to query for
+ * @return mixed registered service for the given $name
+ * @throws QueryException if the query could not be resolved
+ */
+ public function query($name) {
+ $name = $this->sanitizeName($name);
+ if ($this->offsetExists($name)) {
+ return $this->offsetGet($name);
+ } else {
+ $object = $this->resolve($name);
+ $this->registerService($name, function () use ($object) {
+ return $object;
+ });
+ return $object;
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ */
+ public 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 Closure $closure the closure to be called on service creation
+ * @param bool $shared
+ */
+ public function registerService($name, Closure $closure, $shared = true) {
+ $name = $this->sanitizeName($name);
+ if (isset($this[$name])) {
+ unset($this[$name]);
+ }
+ if ($shared) {
+ $this[$name] = $closure;
+ } else {
+ $this[$name] = parent::factory($closure);
+ }
+ }
+
+ /**
+ * Shortcut for returning a service from a service under a different key,
+ * e.g. to tell the container to return a class when queried for an
+ * interface
+ * @param string $alias the alias that should be registered
+ * @param string $target the target that should be resolved instead
+ */
+ public function registerAlias($alias, $target) {
+ $this->registerService($alias, function (IContainer $container) use ($target) {
+ return $container->query($target);
+ }, false);
+ }
+
+ /*
+ * @param string $name
+ * @return string
+ */
+ protected function sanitizeName($name) {
+ return ltrim($name, '\\');
+ }
+
+}
diff --git a/lib/private/AppFramework/Utility/TimeFactory.php b/lib/private/AppFramework/Utility/TimeFactory.php
new file mode 100644
index 00000000000..5241b367069
--- /dev/null
+++ b/lib/private/AppFramework/Utility/TimeFactory.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\AppFramework\Utility;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+
+
+/**
+ * Needed to mock calls to time()
+ */
+class TimeFactory implements ITimeFactory {
+
+
+ /**
+ * @return int the result of a call to time()
+ */
+ public function getTime() {
+ return time();
+ }
+
+
+}