use Friendica\App\Router;
use Friendica\Capabilities\ICanHandleRequests;
+use Friendica\Capabilities\ICanCreateResponses;
+use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Model\User;
-use Friendica\Module\HTTPException\PageNotFound;
-use Friendica\Network\HTTPException\NoContentException;
+use Friendica\Module\Response;
+use Friendica\Module\Special\HTTPException as ModuleHTTPException;
+use Friendica\Network\HTTPException;
use Friendica\Util\Profiler;
+use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
{
/** @var array */
protected $parameters = [];
-
/** @var L10n */
protected $l10n;
+ /** @var App\BaseURL */
+ protected $baseUrl;
+ /** @var App\Arguments */
+ protected $args;
+ /** @var LoggerInterface */
+ protected $logger;
+ /** @var Profiler */
+ protected $profiler;
+ /** @var array */
+ protected $server;
+ /** @var ICanCreateResponses */
+ protected $response;
- public function __construct(L10n $l10n, array $parameters = [])
+ public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
$this->parameters = $parameters;
$this->l10n = $l10n;
+ $this->baseUrl = $baseUrl;
+ $this->args = $args;
+ $this->logger = $logger;
+ $this->profiler = $profiler;
+ $this->server = $server;
+ $this->response = $response;
}
/**
}
/**
- * {@inheritDoc}
+ * Module GET method to display raw content from technical endpoints
+ *
+ * Extend this method if the module is supposed to return communication data,
+ * e.g. from protocol implementations.
+ *
+ * @param string[] $request The $_REQUEST content
*/
- public function rawContent()
+ protected function rawContent(array $request = [])
{
// echo '';
// exit;
}
/**
- * {@inheritDoc}
+ * Module GET method to display any content
+ *
+ * Extend this method if the module is supposed to return any display
+ * through a GET request. It can be an HTML page through templating or a
+ * XML feed or a JSON output.
+ *
+ * @param string[] $request The $_REQUEST content
*/
- public function content(): string
+ protected function content(array $request = []): string
{
return '';
}
/**
- * {@inheritDoc}
+ * Module DELETE method to process submitted data
+ *
+ * Extend this method if the module is supposed to process DELETE requests.
+ * Doesn't display any content
+ *
+ * @param string[] $request The $_REQUEST content
*/
- public function delete()
+ protected function delete(array $request = [])
{
}
/**
- * {@inheritDoc}
+ * Module PATCH method to process submitted data
+ *
+ * Extend this method if the module is supposed to process PATCH requests.
+ * Doesn't display any content
+ *
+ * @param string[] $request The $_REQUEST content
*/
- public function patch()
+ protected function patch(array $request = [])
{
}
/**
- * {@inheritDoc}
+ * Module POST method to process submitted data
+ *
+ * Extend this method if the module is supposed to process POST requests.
+ * Doesn't display any content
+ *
+ * @param string[] $request The $_REQUEST content
+ *
*/
- public function post()
+ protected function post(array $request = [])
{
- // DI::baseurl()->redirect('module');
+ // $this->baseUrl->redirect('module');
}
/**
- * {@inheritDoc}
+ * Module PUT method to process submitted data
+ *
+ * Extend this method if the module is supposed to process PUT requests.
+ * Doesn't display any content
+ *
+ * @param string[] $request The $_REQUEST content
*/
- public function put()
+ protected function put(array $request = [])
{
}
- /** Gets the name of the current class */
- public function getClassName(): string
- {
- return static::class;
- }
-
- public function run(App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, array $server, array $post)
+ /**
+ * {@inheritDoc}
+ */
+ public function run(array $request = []): ResponseInterface
{
- /* The URL provided does not resolve to a valid module.
- *
- * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'.
- * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic -
- * we are going to trap this and redirect back to the requested page. As long as you don't have a critical error on your page
- * this will often succeed and eventually do the right thing.
- *
- * Otherwise we are going to emit a 404 not found.
- */
- if (static::class === PageNotFound::class) {
- $queryString = $server['QUERY_STRING'];
- // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
- if (!empty($queryString) && preg_match('/{[0-9]}/', $queryString) !== 0) {
- exit();
- }
-
- if (!empty($queryString) && ($queryString === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
- $logger->info('index.php: dreamhost_error_hack invoked.', ['Original URI' => $server['REQUEST_URI']]);
- $baseUrl->redirect($server['REQUEST_URI']);
- }
-
- $logger->debug('index.php: page not found.', ['request_uri' => $server['REQUEST_URI'], 'address' => $server['REMOTE_ADDR'], 'query' => $server['QUERY_STRING']]);
- }
-
// @see https://github.com/tootsuite/mastodon/blob/c3aef491d66aec743a3a53e934a494f653745b61/config/initializers/cors.rb
- if (substr($_REQUEST['pagename'] ?? '', 0, 12) == '.well-known/') {
- header('Access-Control-Allow-Origin: *');
- header('Access-Control-Allow-Headers: *');
- header('Access-Control-Allow-Methods: ' . Router::GET);
- header('Access-Control-Allow-Credentials: false');
- } elseif (substr($_REQUEST['pagename'] ?? '', 0, 8) == 'profile/') {
- header('Access-Control-Allow-Origin: *');
- header('Access-Control-Allow-Headers: *');
- header('Access-Control-Allow-Methods: ' . Router::GET);
- header('Access-Control-Allow-Credentials: false');
- } elseif (substr($_REQUEST['pagename'] ?? '', 0, 4) == 'api/') {
- header('Access-Control-Allow-Origin: *');
- header('Access-Control-Allow-Headers: *');
- header('Access-Control-Allow-Methods: ' . implode(',', Router::ALLOWED_METHODS));
- header('Access-Control-Allow-Credentials: false');
- header('Access-Control-Expose-Headers: Link');
- } elseif (substr($_REQUEST['pagename'] ?? '', 0, 11) == 'oauth/token') {
- header('Access-Control-Allow-Origin: *');
- header('Access-Control-Allow-Headers: *');
- header('Access-Control-Allow-Methods: ' . Router::POST);
- header('Access-Control-Allow-Credentials: false');
- }
-
- // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
- // @todo Check allowed methods per requested path
- if ($server['REQUEST_METHOD'] === Router::OPTIONS) {
- header('Allow: ' . implode(',', Router::ALLOWED_METHODS));
- throw new NoContentException();
+ if (substr($request['pagename'] ?? '', 0, 12) == '.well-known/') {
+ $this->response->setHeader('*', 'Access-Control-Allow-Origin');
+ $this->response->setHeader('*', 'Access-Control-Allow-Headers');
+ $this->response->setHeader(Router::GET, 'Access-Control-Allow-Methods');
+ $this->response->setHeader('false', 'Access-Control-Allow-Credentials');
+ } elseif (substr($request['pagename'] ?? '', 0, 8) == 'profile/') {
+ $this->response->setHeader('*', 'Access-Control-Allow-Origin');
+ $this->response->setHeader('*', 'Access-Control-Allow-Headers');
+ $this->response->setHeader(Router::GET, 'Access-Control-Allow-Methods');
+ $this->response->setHeader('false', 'Access-Control-Allow-Credentials');
+ } elseif (substr($request['pagename'] ?? '', 0, 4) == 'api/') {
+ $this->response->setHeader('*', 'Access-Control-Allow-Origin');
+ $this->response->setHeader('*', 'Access-Control-Allow-Headers');
+ $this->response->setHeader(implode(',', Router::ALLOWED_METHODS), 'Access-Control-Allow-Methods');
+ $this->response->setHeader('false', 'Access-Control-Allow-Credentials');
+ $this->response->setHeader('Link', 'Access-Control-Expose-Headers');
+ } elseif (substr($request['pagename'] ?? '', 0, 11) == 'oauth/token') {
+ $this->response->setHeader('*', 'Access-Control-Allow-Origin');
+ $this->response->setHeader('*', 'Access-Control-Allow-Headers');
+ $this->response->setHeader(Router::POST, 'Access-Control-Allow-Methods');
+ $this->response->setHeader('false', 'Access-Control-Allow-Credentials');
}
$placeholder = '';
- $profiler->set(microtime(true), 'ready');
+ $this->profiler->set(microtime(true), 'ready');
$timestamp = microtime(true);
- Core\Hook::callAll($args->getModuleName() . '_mod_init', $placeholder);
-
- $profiler->set(microtime(true) - $timestamp, 'init');
-
- if ($server['REQUEST_METHOD'] === Router::DELETE) {
- $this->delete();
+ Core\Hook::callAll($this->args->getModuleName() . '_mod_init', $placeholder);
+
+ $this->profiler->set(microtime(true) - $timestamp, 'init');
+
+ switch ($this->server['REQUEST_METHOD'] ?? Router::GET) {
+ case Router::DELETE:
+ $this->delete($request);
+ break;
+ case Router::PATCH:
+ $this->patch($request);
+ break;
+ case Router::POST:
+ Core\Hook::callAll($this->args->getModuleName() . '_mod_post', $request);
+ $this->post($request);
+ break;
+ case Router::PUT:
+ $this->put($request);
+ break;
}
- if ($server['REQUEST_METHOD'] === Router::PATCH) {
- $this->patch();
+ $timestamp = microtime(true);
+ // "rawContent" is especially meant for technical endpoints.
+ // This endpoint doesn't need any theme initialization or other comparable stuff.
+ $this->rawContent($request);
+
+ try {
+ $arr = ['content' => ''];
+ Hook::callAll(static::class . '_mod_content', $arr);
+ $this->response->addContent($arr['content']);
+ $this->response->addContent($this->content($request));
+ } catch (HTTPException $e) {
+ $this->response->addContent((new ModuleHTTPException())->content($e));
+ } finally {
+ $this->profiler->set(microtime(true) - $timestamp, 'content');
}
- if ($server['REQUEST_METHOD'] === Router::POST) {
- Core\Hook::callAll($args->getModuleName() . '_mod_post', $post);
- $this->post();
+ return $this->response->generate();
+ }
+
+ /**
+ * Checks request inputs and sets default parameters
+ *
+ * @param array $defaults Associative array of expected request keys and their default typed value. A null
+ * value will remove the request key from the resulting value array.
+ * @param array $input Custom REQUEST array, superglobal instead
+ *
+ * @return array Request data
+ */
+ protected function checkDefaults(array $defaults, array $input): array
+ {
+ $request = [];
+
+ foreach ($defaults as $parameter => $defaultvalue) {
+ if (is_string($defaultvalue)) {
+ $request[$parameter] = $input[$parameter] ?? $defaultvalue;
+ } elseif (is_int($defaultvalue)) {
+ $request[$parameter] = (int)($input[$parameter] ?? $defaultvalue);
+ } elseif (is_float($defaultvalue)) {
+ $request[$parameter] = (float)($input[$parameter] ?? $defaultvalue);
+ } elseif (is_array($defaultvalue)) {
+ $request[$parameter] = $input[$parameter] ?? [];
+ } elseif (is_bool($defaultvalue)) {
+ $request[$parameter] = in_array(strtolower($input[$parameter] ?? ''), ['true', '1']);
+ } else {
+ $this->logger->notice('Unhandled default value type', ['parameter' => $parameter, 'type' => gettype($defaultvalue)]);
+ }
}
- if ($server['REQUEST_METHOD'] === Router::PUT) {
- $this->put();
+ foreach ($input ?? [] as $parameter => $value) {
+ if ($parameter == 'pagename') {
+ continue;
+ }
+ if (!in_array($parameter, array_keys($defaults))) {
+ $this->logger->notice('Unhandled request field', ['parameter' => $parameter, 'value' => $value, 'command' => $this->args->getCommand()]);
+ }
}
- // "rawContent" is especially meant for technical endpoints.
- // This endpoint doesn't need any theme initialization or other comparable stuff.
- $this->rawContent();
+ $this->logger->debug('Got request parameters', ['request' => $request, 'command' => $this->args->getCommand()]);
+ return $request;
}
/*
*/
public static function getFormSecurityToken($typename = '')
{
- $user = User::getById(DI::app()->getLoggedInUserId(), ['guid', 'prvkey']);
+ $user = User::getById(DI::app()->getLoggedInUserId(), ['guid', 'prvkey']);
$timestamp = time();
- $sec_hash = hash('whirlpool', ($user['guid'] ?? '') . ($user['prvkey'] ?? '') . session_id() . $timestamp . $typename);
+ $sec_hash = hash('whirlpool', ($user['guid'] ?? '') . ($user['prvkey'] ?? '') . session_id() . $timestamp . $typename);
return $timestamp . '.' . $sec_hash;
}