diff --git a/include/api.php b/include/api.php index 698fa2f995..f360dc4116 100644 --- a/include/api.php +++ b/include/api.php @@ -364,7 +364,7 @@ function api_call(App $a) Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call']); throw new NotImplementedException(); } catch (HTTPException $e) { - header("HTTP/1.1 {$e->httpcode} {$e->httpdesc}"); + header("HTTP/1.1 {$e->getCode()} {$e->httpdesc}"); return api_error($type, $e); } } @@ -384,7 +384,7 @@ function api_error($type, $e) /// @TODO: https://dev.twitter.com/overview/api/response-codes $error = ["error" => $error, - "code" => $e->httpcode . " " . $e->httpdesc, + "code" => $e->getCode() . " " . $e->httpdesc, "request" => $a->query_string]; $return = api_format_data('status', $type, ['status' => $error]); diff --git a/mod/bookmarklet.php b/mod/bookmarklet.php index 1c4d191c4f..f15d12d6d1 100644 --- a/mod/bookmarklet.php +++ b/mod/bookmarklet.php @@ -29,7 +29,7 @@ function bookmarklet_content(App $a) if (!strstr($referer, $page)) { if (empty($_REQUEST["url"])) { - System::httpExit(400, ["title" => L10n::t('Bad Request')]); + throw new \Friendica\Network\HTTPException\BadRequestException(L10n::t('This page is missing a url parameter.')); } $content = add_page_info($_REQUEST["url"]); diff --git a/mod/cal.php b/mod/cal.php index cba484344b..e93341583a 100644 --- a/mod/cal.php +++ b/mod/cal.php @@ -31,11 +31,11 @@ function cal_init(App $a) } if (Config::get('system', 'block_public') && !local_user() && !remote_user()) { - System::httpExit(403, ['title' => L10n::t('Access denied.')]); + throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.')); } if ($a->argc < 2) { - System::httpExit(403, ['title' => L10n::t('Access denied.')]); + throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.')); } Nav::setSelected('events'); @@ -43,7 +43,7 @@ function cal_init(App $a) $nick = $a->argv[1]; $user = DBA::selectFirst('user', [], ['nickname' => $nick, 'blocked' => false]); if (!DBA::isResult($user)) { - System::httpExit(404, ['title' => L10n::t('Page not found.')]); + throw new \Slim\Exception\NotFoundException(); } $a->data['user'] = $user; diff --git a/mod/dfrn_notify.php b/mod/dfrn_notify.php index 745411a8ef..1a9f98fa33 100644 --- a/mod/dfrn_notify.php +++ b/mod/dfrn_notify.php @@ -29,7 +29,7 @@ function dfrn_notify_post(App $a) { $user = DBA::selectFirst('user', [], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]); if (!DBA::isResult($user)) { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } dfrn_dispatch_private($user, $postdata); } elseif (!dfrn_dispatch_public($postdata)) { diff --git a/mod/dfrn_poll.php b/mod/dfrn_poll.php index 5b72c0bf94..6c849cb807 100644 --- a/mod/dfrn_poll.php +++ b/mod/dfrn_poll.php @@ -50,7 +50,7 @@ function dfrn_poll_init(App $a) if (($dfrn_id === '') && empty($_POST['dfrn_id'])) { if (Config::get('system', 'block_public') && !local_user() && !remote_user()) { - System::httpExit(403); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } $user = ''; @@ -59,7 +59,7 @@ function dfrn_poll_init(App $a) DBA::escape($a->argv[1]) ); if (!$r) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $hidewall = ($r[0]['hidewall'] && !local_user()); @@ -483,7 +483,7 @@ function dfrn_poll_content(App $a) // heluecht: I don't know why we don't fail immediately when the user or contact hadn't been found. // Since it doesn't make sense to continue from this point on, we now fail here. This should be safe. if (!DBA::isResult($r)) { - System::httpExit(404, ["title" => L10n::t('Page not found.')]); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // URL reply diff --git a/mod/display.php b/mod/display.php index acc2a5b090..fa5b2e1962 100644 --- a/mod/display.php +++ b/mod/display.php @@ -20,6 +20,7 @@ use Friendica\Model\Group; use Friendica\Model\Item; use Friendica\Model\Profile; use Friendica\Module\Objects; +use Friendica\Network\HTTPException; use Friendica\Protocol\ActivityPub; use Friendica\Protocol\DFRN; use Friendica\Util\Strings; @@ -76,7 +77,7 @@ function display_init(App $a) } if (!DBA::isResult($item)) { - System::httpExit(404); + return; } if ($a->argc >= 3 && $nick == 'feed-item') { @@ -200,8 +201,7 @@ function display_fetchauthor($a, $item) function display_content(App $a, $update = false, $update_uid = 0) { if (Config::get('system','block_public') && !local_user() && !remote_user()) { - notice(L10n::t('Public access denied.') . EOL); - return; + throw new HTTPException\ForbiddenException(L10n::t('Public access denied.')); } $o = ''; @@ -254,7 +254,7 @@ function display_content(App $a, $update = false, $update_uid = 0) } if (!$item_id) { - System::httpExit(404); + throw new HTTPException\NotFoundException(L10n::t('The requested item doesn\'t exist or has been deleted.')); } // We are displaying an "alternate" link if that post was public. See issue 2864 @@ -303,8 +303,7 @@ function display_content(App $a, $update = false, $update_uid = 0) $is_owner = (local_user() && (in_array($a->profile['profile_uid'], [local_user(), 0])) ? true : false); if (!empty($a->profile['hidewall']) && !$is_owner && !$is_remote_contact) { - notice(L10n::t('Access to this profile has been restricted.') . EOL); - return; + throw new HTTPException\ForbiddenException(L10n::t('Access to this profile has been restricted.')); } // We need the editor here to be able to reshare an item. @@ -340,7 +339,7 @@ function display_content(App $a, $update = false, $update_uid = 0) $item = Item::selectFirstForUser(local_user(), $fields, $condition); if (!DBA::isResult($item)) { - System::httpExit(404); + throw new HTTPException\NotFoundException(L10n::t('The requested item doesn\'t exist or has been deleted.')); } $item['uri'] = $item['parent-uri']; @@ -415,7 +414,7 @@ function displayShowFeed($item_id, $conversation) { $xml = DFRN::itemFeed($item_id, $conversation); if ($xml == '') { - System::httpExit(500); + throw new HTTPException\InternalServerErrorException(L10n::t('The feed for this item is unavailable.')); } header("Content-type: application/atom+xml"); echo $xml; diff --git a/mod/fetch.php b/mod/fetch.php index 3e9c4e6626..492f319efb 100644 --- a/mod/fetch.php +++ b/mod/fetch.php @@ -14,9 +14,8 @@ use Friendica\Database\DBA; function fetch_init(App $a) { - if (($a->argc != 3) || (!in_array($a->argv[1], ["post", "status_message", "reshare"]))) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $guid = $a->argv[2]; @@ -42,13 +41,13 @@ function fetch_init(App $a) } } - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // Fetch some data from the author (We could combine both queries - but I think this is more readable) $user = User::getOwnerDataById($item["uid"]); if (!$user) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $status = Diaspora::buildStatus($item, $user); diff --git a/mod/follow.php b/mod/follow.php index ba4ff35f19..e0b6e1733b 100644 --- a/mod/follow.php +++ b/mod/follow.php @@ -17,7 +17,7 @@ use Friendica\Util\Strings; function follow_post(App $a) { if (!local_user()) { - System::httpExit(403, ['title' => L10n::t('Access denied.')]); + throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.')); } if (isset($_REQUEST['cancel'])) { diff --git a/mod/hcard.php b/mod/hcard.php index e27ea29be4..828eeaf091 100644 --- a/mod/hcard.php +++ b/mod/hcard.php @@ -17,8 +17,7 @@ function hcard_init(App $a) if ($a->argc > 1) { $which = $a->argv[1]; } else { - notice(L10n::t('No profile') . EOL); - return; + throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('No profile')); } $profile = 0; diff --git a/mod/help.php b/mod/help.php index cdfedac6ff..19b629271e 100644 --- a/mod/help.php +++ b/mod/help.php @@ -62,11 +62,7 @@ function help_content(App $a) } if (!strlen($text)) { - header($_SERVER["SERVER_PROTOCOL"] . ' 404 ' . L10n::t('Not Found')); - $tpl = Renderer::getMarkupTemplate("404.tpl"); - return Renderer::replaceMacros($tpl, [ - '$message' => L10n::t('Page not found.') - ]); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $html = Markdown::convert($text, false); diff --git a/mod/hovercard.php b/mod/hovercard.php index 603c617ca4..ca39919636 100644 --- a/mod/hovercard.php +++ b/mod/hovercard.php @@ -31,7 +31,7 @@ function hovercard_content() // Get out if the system doesn't have public access allowed if (intval(Config::get('system', 'block_public'))) { - System::httpExit(401); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } // Return the raw content of the template. We use this to make templates usable for js functions. diff --git a/mod/notice.php b/mod/notice.php index edcbefdad2..b0a6a54391 100644 --- a/mod/notice.php +++ b/mod/notice.php @@ -16,7 +16,7 @@ function notice_init(App $a) $nick = $r[0]['nickname']; $a->internalRedirect('display/' . $nick . '/' . $id); } else { - notice(L10n::t('Item not found.') . EOL); + throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('Item not found.')); } return; diff --git a/mod/poco.php b/mod/poco.php index 064e0e9a85..c288f6b639 100644 --- a/mod/poco.php +++ b/mod/poco.php @@ -22,7 +22,7 @@ function poco_init(App $a) { $system_mode = false; if (intval(Config::get('system', 'block_public')) || (Config::get('system', 'block_local_dir'))) { - System::httpExit(401); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } if ($a->argc > 1) { @@ -31,7 +31,7 @@ function poco_init(App $a) { if (empty($nickname)) { $c = q("SELECT * FROM `pconfig` WHERE `cat` = 'system' AND `k` = 'suggestme' AND `v` = 1"); if (!DBA::isResult($c)) { - System::httpExit(401); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } $system_mode = true; } @@ -73,7 +73,7 @@ function poco_init(App $a) { DBA::escape($nickname) ); if (! DBA::isResult($users) || $users[0]['hidewall'] || $users[0]['hide-friends']) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $user = $users[0]; @@ -371,8 +371,9 @@ function poco_init(App $a) { $ret['entry'][] = []; } } else { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } + Logger::log("End of poco", Logger::DEBUG); if ($format === 'xml') { @@ -385,6 +386,6 @@ function poco_init(App $a) { echo json_encode($ret); exit(); } else { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } } diff --git a/mod/probe.php b/mod/probe.php index e120ce1724..7fc3a92899 100644 --- a/mod/probe.php +++ b/mod/probe.php @@ -10,9 +10,9 @@ use Friendica\Network\Probe; function probe_content(App $a) { if (!local_user()) { - System::httpExit(403, ["title" => L10n::t("Public access denied."), - "description" => L10n::t("Only logged in users are permitted to perform a probing.")]); - exit(); + $e = new \Friendica\Network\HTTPException\ForbiddenException(L10n::t("Only logged in users are permitted to perform a probing.")); + $e->httpdesc = L10n::t("Public access denied."); + throw $e; } $o = '
'; diff --git a/mod/pubsub.php b/mod/pubsub.php index c5744f399b..e5ede6c80a 100644 --- a/mod/pubsub.php +++ b/mod/pubsub.php @@ -12,10 +12,9 @@ use Friendica\Core\System; function hub_return($valid, $body) { if ($valid) { - header($_SERVER["SERVER_PROTOCOL"] . ' 200 OK'); echo $body; } else { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } exit(); } @@ -24,7 +23,7 @@ function hub_return($valid, $body) function hub_post_return() { - System::httpExit(200); + throw new \Friendica\Network\HTTPException\OKException(); } function pubsub_init(App $a) diff --git a/mod/pubsubhubbub.php b/mod/pubsubhubbub.php index 342facd107..f71984ec18 100644 --- a/mod/pubsubhubbub.php +++ b/mod/pubsubhubbub.php @@ -17,7 +17,7 @@ function pubsubhubbub_init(App $a) { // PuSH subscription must be considered "public" so just block it // if public access isn't enabled. if (Config::get('system', 'block_public')) { - System::httpExit(403); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } // Subscription request from subscriber @@ -44,7 +44,7 @@ function pubsubhubbub_init(App $a) { $subscribe = 0; } else { Logger::log("Invalid hub_mode=$hub_mode, ignoring."); - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } Logger::log("$hub_mode request from " . $_SERVER['REMOTE_ADDR']); @@ -61,7 +61,7 @@ function pubsubhubbub_init(App $a) { if (!$nick) { Logger::log('Bad hub_topic=$hub_topic, ignoring.'); - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // fetch user from database given the nickname @@ -69,13 +69,13 @@ function pubsubhubbub_init(App $a) { $owner = DBA::selectFirst('user', ['uid', 'hidewall', 'nickname'], $condition); if (!DBA::isResult($owner)) { Logger::log('Local account not found: ' . $nick . ' - topic: ' . $hub_topic . ' - callback: ' . $hub_callback); - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // abort if user's wall is supposed to be private if ($owner['hidewall']) { Logger::log('Local user ' . $nick . 'has chosen to hide wall, ignoring.'); - System::httpExit(403); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } // get corresponding row from contact table @@ -84,7 +84,7 @@ function pubsubhubbub_init(App $a) { $contact = DBA::selectFirst('contact', ['poll'], $condition); if (!DBA::isResult($contact)) { Logger::log('Self contact for user ' . $owner['uid'] . ' not found.'); - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // sanity check that topic URLs are the same @@ -93,7 +93,7 @@ function pubsubhubbub_init(App $a) { if (!Strings::compareLink($hub_topic, $contact['poll']) && !Strings::compareLink($hub_topic2, $contact['poll']) && !Strings::compareLink($hub_topic, $self)) { Logger::log('Hub topic ' . $hub_topic . ' != ' . $contact['poll']); - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // do subscriber verification according to the PuSH protocol @@ -121,19 +121,19 @@ function pubsubhubbub_init(App $a) { // give up if the HTTP return code wasn't a success (2xx) if ($ret < 200 || $ret > 299) { Logger::log("Subscriber verification for $hub_topic at $hub_callback returned $ret, ignoring."); - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // check that the correct hub_challenge code was echoed back if (trim($body) !== $hub_challenge) { Logger::log("Subscriber did not echo back hub.challenge, ignoring."); Logger::log("\"$hub_challenge\" != \"".trim($body)."\""); - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } PushSubscriber::renew($owner['uid'], $nick, $subscribe, $hub_callback, $hub_topic, $hub_secret); - System::httpExit(202); + throw new \Friendica\Network\HTTPException\AcceptedException(); } exit(); } diff --git a/mod/receive.php b/mod/receive.php index b0258acbd7..db1287ea6f 100644 --- a/mod/receive.php +++ b/mod/receive.php @@ -22,7 +22,7 @@ function receive_post(App $a) $enabled = intval(Config::get('system', 'diaspora_enabled')); if (!$enabled) { Logger::log('mod-diaspora: disabled'); - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } if (($a->argc == 2) && ($a->argv[1] === 'public')) { @@ -32,13 +32,13 @@ function receive_post(App $a) $public = false; if ($a->argc != 3 || $a->argv[1] !== 'users') { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } $guid = $a->argv[2]; $importer = DBA::selectFirst('user', [], ['guid' => $guid, 'account_expired' => false, 'account_removed' => false]); if (!DBA::isResult($importer)) { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } } @@ -49,7 +49,7 @@ function receive_post(App $a) if (empty($_POST['xml'])) { $postdata = file_get_contents("php://input"); if ($postdata == '') { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } Logger::log('mod-diaspora: message is in the new format', Logger::DEBUG); @@ -71,7 +71,7 @@ function receive_post(App $a) Logger::log('mod-diaspora: decoded msg: ' . print_r($msg, true), Logger::DATA); if (!is_array($msg)) { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } Logger::log('mod-diaspora: dispatching', Logger::DEBUG); @@ -83,6 +83,9 @@ function receive_post(App $a) $ret = Diaspora::dispatch($importer, $msg); } - System::httpExit(($ret) ? 200 : 500); - // NOTREACHED + if ($ret) { + throw new \Friendica\Network\HTTPException\OKException(); + } else { + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); + } } diff --git a/mod/salmon.php b/mod/salmon.php index 84d2942d53..ba1bc8d465 100644 --- a/mod/salmon.php +++ b/mod/salmon.php @@ -28,7 +28,7 @@ function salmon_post(App $a, $xml = '') { DBA::escape($nick) ); if (! DBA::isResult($r)) { - System::httpExit(500); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } $importer = $r[0]; @@ -49,7 +49,7 @@ function salmon_post(App $a, $xml = '') { if (empty($base)) { Logger::log('unable to locate salmon data in xml '); - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } // Stash the signature away for now. We have to find their key or it won't be good for anything. @@ -87,7 +87,7 @@ function salmon_post(App $a, $xml = '') { if(! $author_link) { Logger::log('Could not retrieve author URI.'); - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } // Once we have the author URI, go to the web and try to find their public key @@ -98,7 +98,7 @@ function salmon_post(App $a, $xml = '') { if(! $key) { Logger::log('Could not retrieve author key.'); - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } $key_info = explode('.',$key); @@ -130,7 +130,7 @@ function salmon_post(App $a, $xml = '') { if (! $verify) { Logger::log('Message did not verify. Discarding.'); - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } Logger::log('Message verified with mode '.$mode); @@ -177,8 +177,7 @@ function salmon_post(App $a, $xml = '') { //if((DBA::isResult($r)) && (($r[0]['readonly']) || ($r[0]['rel'] == Contact::FOLLOWER) || ($r[0]['blocked']))) { if (DBA::isResult($r) && $r[0]['blocked']) { Logger::log('Ignoring this author.'); - System::httpExit(202); - // NOTREACHED + throw new \Friendica\Network\HTTPException\AcceptedException(); } // Placeholder for hub discovery. @@ -188,5 +187,5 @@ function salmon_post(App $a, $xml = '') { OStatus::import($data, $importer, $contact_rec, $hub); - System::httpExit(200); + throw new \Friendica\Network\HTTPException\OKException(); } diff --git a/mod/search.php b/mod/search.php index 9a70bcac90..1416f1d89f 100644 --- a/mod/search.php +++ b/mod/search.php @@ -98,12 +98,9 @@ function search_content(App $a) { } if (Config::get('system','local_search') && !local_user() && !remote_user()) { - System::httpExit(403, - ["title" => L10n::t("Public access denied."), - "description" => L10n::t("Only logged in users are permitted to perform a search.")]); - exit(); - //notice(L10n::t('Public access denied.').EOL); - //return; + $e = new \Friendica\Network\HTTPException\ForbiddenException(L10n::t("Only logged in users are permitted to perform a search.")); + $e->httpdesc = L10n::t("Public access denied."); + throw $e; } if (Config::get('system','permit_crawling') && !local_user() && !remote_user()) { @@ -123,10 +120,7 @@ function search_content(App $a) { if (!is_null($result)) { $resultdata = json_decode($result); if (($resultdata->time > (time() - $crawl_permit_period)) && ($resultdata->accesses > $free_crawls)) { - System::httpExit(429, - ["title" => L10n::t("Too Many Requests"), - "description" => L10n::t("Only one search per minute is permitted for not logged in users.")]); - exit(); + throw new \Friendica\Network\HTTPException\TooManyRequestsException(L10n::t("Only one search per minute is permitted for not logged in users.")); } Cache::set("remote_search:".$remote, json_encode(["time" => time(), "accesses" => $resultdata->accesses + 1]), Cache::HOUR); } else diff --git a/mod/viewcontacts.php b/mod/viewcontacts.php index 7c2b96fad1..14919820dd 100644 --- a/mod/viewcontacts.php +++ b/mod/viewcontacts.php @@ -20,18 +20,18 @@ use Friendica\Util\Proxy as ProxyUtils; function viewcontacts_init(App $a) { if (Config::get('system', 'block_public') && !local_user() && !remote_user()) { - System::httpExit(403, ["title" => L10n::t('Access denied.')]); + throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.')); } if ($a->argc < 2) { - System::httpExit(403, ["title" => L10n::t('Access denied.')]); + throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.')); } Nav::setSelected('home'); $user = DBA::selectFirst('user', [], ['nickname' => $a->argv[1], 'blocked' => false]); if (!DBA::isResult($user)) { - System::httpExit(404, ["title" => L10n::t('Page not found.')]); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $a->data['user'] = $user; diff --git a/mod/viewsrc.php b/mod/viewsrc.php index 939c73a142..55eb0b990c 100644 --- a/mod/viewsrc.php +++ b/mod/viewsrc.php @@ -18,8 +18,7 @@ function viewsrc_content(App $a) $item_id = (($a->argc > 1) ? intval($a->argv[1]) : 0); if (!$item_id) { - notice(L10n::t('Item not found.') . EOL); - return; + throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('Item not found.')); } $item = Item::selectFirst(['body'], ['uid' => local_user(), 'id' => $item_id]); diff --git a/src/App.php b/src/App.php index 477e44bf1d..f3b0ab6415 100644 --- a/src/App.php +++ b/src/App.php @@ -14,7 +14,7 @@ use Friendica\Core\Hook; use Friendica\Core\Theme; use Friendica\Database\DBA; use Friendica\Model\Profile; -use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Network\HTTPException; use Friendica\Util\BaseURL; use Friendica\Util\Config\ConfigFileLoader; use Friendica\Util\HTTPSignature; @@ -568,7 +568,7 @@ class App * @param string $origURL * * @return string The cleaned url - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function removeBaseURL($origURL) { @@ -589,7 +589,7 @@ class App * Returns the current UserAgent as a String * * @return string the UserAgent as a String - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getUserAgent() { @@ -719,7 +719,7 @@ class App * @brief Checks if the minimal memory is reached * * @return bool Is the memory limit reached? - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function isMinMemoryReached() { @@ -764,7 +764,7 @@ class App * @brief Checks if the maximum load is reached * * @return bool Is the load reached? - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function isMaxLoadReached() { @@ -797,7 +797,7 @@ class App * * @param string $command The command to execute * @param array $args Arguments to pass to the command ( [ 'key' => value, 'key2' => value2, ... ] - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function proc_run($command, $args) { @@ -838,7 +838,7 @@ class App * Generates the site's default sender email address * * @return string - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getSenderEmailAddress() { @@ -859,7 +859,7 @@ class App * Returns the current theme name. * * @return string the name of the current theme - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getCurrentTheme() { @@ -940,7 +940,7 @@ class App * Provide a sane default if nothing is chosen or the specified theme does not exist. * * @return string - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getCurrentThemeStylesheetPath() { @@ -1006,7 +1006,10 @@ class App { // Missing DB connection: ERROR if ($this->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$this->getMode()->has(App\Mode::DBAVAILABLE)) { - Core\System::httpExit(500, ['title' => 'Error 500 - Internal Server Error', 'description' => 'Apologies but the website is unavailable at the moment.']); + echo Module\Special\HTTPException::rawContent( + new HTTPException\InternalServerErrorException('Apologies but the website is unavailable at the moment.') + ); + exit; } // Max Load Average reached: ERROR @@ -1014,11 +1017,17 @@ class App header('Retry-After: 120'); header('Refresh: 120; url=' . $this->getBaseURL() . "/" . $this->query_string); - Core\System::httpExit(503, ['title' => 'Error 503 - Service Temporarily Unavailable', 'description' => 'Core\System is currently overloaded. Please try again later.']); + echo Module\Special\HTTPException::rawContent( + new HTTPException\ServiceUnavaiableException('The node is currently overloaded. Please try again later.') + ); + exit; } if (strstr($this->query_string, '.well-known/host-meta') && ($this->query_string != '.well-known/host-meta')) { - Core\System::httpExit(404); + echo Module\Special\HTTPException::rawContent( + new HTTPException\NotFoundException() + ); + exit; } if (!$this->getMode()->isInstall()) { @@ -1069,7 +1078,10 @@ class App // Someone came with an invalid parameter, maybe as a DDoS attempt // We simply stop processing here Core\Logger::log("Invalid ZRL parameter " . $_GET['zrl'], Core\Logger::DEBUG); - Core\System::httpExit(403, ['title' => '403 Forbidden']); + echo Module\Special\HTTPException::rawContent( + new HTTPException\ForbiddenException() + ); + exit; } } } @@ -1122,123 +1134,115 @@ class App 'title' => '' ]; - if (strlen($this->module)) { - // Compatibility with the Android Diaspora client - if ($this->module == 'stream') { - $this->internalRedirect('network?f=&order=post'); - } + // Compatibility with the Android Diaspora client + if ($this->module == 'stream') { + $this->internalRedirect('network?f=&order=post'); + } - if ($this->module == 'conversations') { - $this->internalRedirect('message'); - } + if ($this->module == 'conversations') { + $this->internalRedirect('message'); + } - if ($this->module == 'commented') { - $this->internalRedirect('network?f=&order=comment'); - } + if ($this->module == 'commented') { + $this->internalRedirect('network?f=&order=comment'); + } - if ($this->module == 'liked') { - $this->internalRedirect('network?f=&order=comment'); - } + if ($this->module == 'liked') { + $this->internalRedirect('network?f=&order=comment'); + } - if ($this->module == 'activity') { - $this->internalRedirect('network/?f=&conv=1'); - } + if ($this->module == 'activity') { + $this->internalRedirect('network/?f=&conv=1'); + } - if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) { - $this->internalRedirect('bookmarklet'); - } + if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) { + $this->internalRedirect('bookmarklet'); + } - if (($this->module == 'user') && ($this->cmd == 'user/edit')) { - $this->internalRedirect('settings'); - } + if (($this->module == 'user') && ($this->cmd == 'user/edit')) { + $this->internalRedirect('settings'); + } - if (($this->module == 'tag_followings') && ($this->cmd == 'tag_followings/manage')) { - $this->internalRedirect('search'); - } + if (($this->module == 'tag_followings') && ($this->cmd == 'tag_followings/manage')) { + $this->internalRedirect('search'); + } - // Compatibility with the Firefox App - if (($this->module == "users") && ($this->cmd == "users/sign_in")) { - $this->module = "login"; - } + // Compatibility with the Firefox App + if (($this->module == "users") && ($this->cmd == "users/sign_in")) { + $this->module = "login"; + } - /* - * ROUTING - * - * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the - * post() and/or content() static methods can be respectively called to produce a data change or an output. - */ + /* + * ROUTING + * + * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the + * post() and/or content() static methods can be respectively called to produce a data change or an output. + */ - // First we try explicit routes defined in App\Router - $this->router->collectRoutes(); + // First we try explicit routes defined in App\Router + $this->router->collectRoutes(); - $data = $this->router->getRouteCollector(); - Hook::callAll('route_collection', $data); + $data = $this->router->getRouteCollector(); + Hook::callAll('route_collection', $data); - $this->module_class = $this->router->getModuleClass($this->cmd); + $this->module_class = $this->router->getModuleClass($this->cmd); - // Then we try addon-provided modules that we wrap in the LegacyModule class - if (!$this->module_class && Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) { - //Check if module is an app and if public access to apps is allowed or not - $privateapps = $this->config->get('config', 'private_addons', false); - if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { - info(Core\L10n::t("You must be logged in to use addons. ")); - } else { - include_once "addon/{$this->module}/{$this->module}.php"; - if (function_exists($this->module . '_module')) { - LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php"); - $this->module_class = 'Friendica\\LegacyModule'; - } + // Then we try addon-provided modules that we wrap in the LegacyModule class + if (!$this->module_class && Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) { + //Check if module is an app and if public access to apps is allowed or not + $privateapps = $this->config->get('config', 'private_addons', false); + if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { + info(Core\L10n::t("You must be logged in to use addons. ")); + } else { + include_once "addon/{$this->module}/{$this->module}.php"; + if (function_exists($this->module . '_module')) { + LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php"); + $this->module_class = LegacyModule::class; } } - - // Then we try name-matching a Friendica\Module class - if (!$this->module_class && class_exists('Friendica\\Module\\' . ucfirst($this->module))) { - $this->module_class = 'Friendica\\Module\\' . ucfirst($this->module); - } - - /* Finally, we look for a 'standard' program module in the 'mod' directory - * We emulate a Module class through the LegacyModule class - */ - if (!$this->module_class && file_exists("mod/{$this->module}.php")) { - LegacyModule::setModuleFile("mod/{$this->module}.php"); - $this->module_class = 'Friendica\\LegacyModule'; - } - - /* 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 (!$this->module_class) { - // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit. - if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) { - exit(); - } - - if (!empty($_SERVER['QUERY_STRING']) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) { - Core\Logger::log('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']); - $this->internalRedirect($_SERVER['REQUEST_URI']); - } - - Core\Logger::log('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], Core\Logger::DEBUG); - - header($_SERVER["SERVER_PROTOCOL"] . ' 404 ' . Core\L10n::t('Not Found')); - $tpl = Core\Renderer::getMarkupTemplate("404.tpl"); - $this->page['content'] = Core\Renderer::replaceMacros($tpl, [ - '$message' => Core\L10n::t('Page not found.') - ]); - } } - $content = ''; + // Then we try name-matching a Friendica\Module class + if (!$this->module_class && class_exists('Friendica\\Module\\' . ucfirst($this->module))) { + $this->module_class = 'Friendica\\Module\\' . ucfirst($this->module); + } + + /* Finally, we look for a 'standard' program module in the 'mod' directory + * We emulate a Module class through the LegacyModule class + */ + if (!$this->module_class && file_exists("mod/{$this->module}.php")) { + LegacyModule::setModuleFile("mod/{$this->module}.php"); + $this->module_class = LegacyModule::class; + } + + /* 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 (!$this->module_class) { + // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit. + if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) { + exit(); + } + + if (!empty($_SERVER['QUERY_STRING']) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) { + Core\Logger::log('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']); + $this->internalRedirect($_SERVER['REQUEST_URI']); + } + + Core\Logger::log('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], Core\Logger::DEBUG); + + $this->module_class = Module\PageNotFound::class; + } // Initialize module that can set the current theme in the init() method, either directly or via App->profile_uid - if ($this->module_class) { - $this->page['page_title'] = $this->module; + $this->page['page_title'] = $this->module; + try { $placeholder = ''; Core\Hook::callAll($this->module . '_mod_init', $placeholder); @@ -1247,35 +1251,42 @@ class App // "rawContent" is especially meant for technical endpoints. // This endpoint doesn't need any theme initialization or other comparable stuff. - call_user_func([$this->module_class, 'rawContent']); - } + call_user_func([$this->module_class, 'rawContent']); - // Load current theme info after module has been initialized as theme could have been set in module - $theme_info_file = 'view/theme/' . $this->getCurrentTheme() . '/theme.php'; - if (file_exists($theme_info_file)) { - require_once $theme_info_file; - } + // Load current theme info after module has been initialized as theme could have been set in module + $theme_info_file = 'view/theme/' . $this->getCurrentTheme() . '/theme.php'; + if (file_exists($theme_info_file)) { + require_once $theme_info_file; + } - if (function_exists(str_replace('-', '_', $this->getCurrentTheme()) . '_init')) { - $func = str_replace('-', '_', $this->getCurrentTheme()) . '_init'; - $func($this); - } + if (function_exists(str_replace('-', '_', $this->getCurrentTheme()) . '_init')) { + $func = str_replace('-', '_', $this->getCurrentTheme()) . '_init'; + $func($this); + } - if ($this->module_class) { if ($_SERVER['REQUEST_METHOD'] === 'POST') { Core\Hook::callAll($this->module . '_mod_post', $_POST); call_user_func([$this->module_class, 'post']); } - Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder); - call_user_func([$this->module_class, 'afterpost']); + Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder); + call_user_func([$this->module_class, 'afterpost']); + } catch(HTTPException $e) { + echo Module\Special\HTTPException::rawContent($e); + exit; + } - $arr = ['content' => $content]; - Core\Hook::callAll($this->module . '_mod_content', $arr); - $content = $arr['content']; - $arr = ['content' => call_user_func([$this->module_class, 'content'])]; - Core\Hook::callAll($this->module . '_mod_aftercontent', $arr); - $content .= $arr['content']; + $content = ''; + + try { + $arr = ['content' => $content]; + Core\Hook::callAll($this->module . '_mod_content', $arr); + $content = $arr['content']; + $arr = ['content' => call_user_func([$this->module_class, 'content'])]; + Core\Hook::callAll($this->module . '_mod_aftercontent', $arr); + $content .= $arr['content']; + } catch(HTTPException $e) { + $content = Module\Special\HTTPException::content($e); } // initialise content region @@ -1299,13 +1310,6 @@ class App */ $this->initFooter(); - /* now that we've been through the module content, see if the page reported - * a permission problem and if so, a 403 response would seem to be in order. - */ - if (stristr(implode("", $_SESSION['sysmsg']), Core\L10n::t('Permission denied'))) { - header($_SERVER["SERVER_PROTOCOL"] . ' 403 ' . Core\L10n::t('Permission denied.')); - } - if (!$this->isAjax()) { Core\Hook::callAll('page_end', $this->page['content']); } @@ -1397,12 +1401,12 @@ class App * @param string $toUrl The destination URL (Default is empty, which is the default page of the Friendica node) * @param bool $ssl if true, base URL will try to get called with https:// (works just for relative paths) * - * @throws InternalServerErrorException In Case the given URL is not relative to the Friendica node + * @throws HTTPException\InternalServerErrorException In Case the given URL is not relative to the Friendica node */ public function internalRedirect($toUrl = '', $ssl = false) { if (!empty(parse_url($toUrl, PHP_URL_SCHEME))) { - throw new InternalServerErrorException("'$toUrl is not a relative path, please use System::externalRedirectTo"); + throw new HTTPException\InternalServerErrorException("'$toUrl is not a relative path, please use System::externalRedirectTo"); } $redirectTo = $this->getBaseURL($ssl) . '/' . ltrim($toUrl, '/'); @@ -1414,7 +1418,7 @@ class App * Should only be used if it isn't clear if the URL is either internal or external * * @param string $toUrl The target URL - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function redirect($toUrl) { diff --git a/src/BaseModule.php b/src/BaseModule.php index 3878d15073..dd9059bfba 100644 --- a/src/BaseModule.php +++ b/src/BaseModule.php @@ -153,7 +153,7 @@ abstract class BaseModule extends BaseObject Logger::log('checkFormSecurityToken failed: user ' . $a->user['guid'] . ' - form element ' . $typename); Logger::log('checkFormSecurityToken failed: _REQUEST data: ' . print_r($_REQUEST, true), Logger::DATA); - System::httpExit(403); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } } } diff --git a/src/Core/System.php b/src/Core/System.php index 83c3dc9081..e2966a9b0e 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -120,58 +120,17 @@ class System extends BaseObject /** * @brief Send HTTP status header and exit. * - * @param integer $val HTTP status result value - * @param array $description optional message - * 'title' => header title - * 'description' => optional message - * @throws InternalServerErrorException + * @param integer $val HTTP status result value + * @param string $message Error message. Optional. + * @param string $content Response body. Optional. + * @throws \Exception */ - public static function httpExit($val, $description = []) + public static function httpExit($val, $message = '', $content = '') { - $err = ''; - if ($val >= 400) { - if (!empty($description['title'])) { - $err = $description['title']; - } else { - $title = [ - '400' => L10n::t('Error 400 - Bad Request'), - '401' => L10n::t('Error 401 - Unauthorized'), - '403' => L10n::t('Error 403 - Forbidden'), - '404' => L10n::t('Error 404 - Not Found'), - '500' => L10n::t('Error 500 - Internal Server Error'), - '503' => L10n::t('Error 503 - Service Unavailable'), - ]; - $err = defaults($title, $val, 'Error ' . $val); - $description['title'] = $err; - } - if (empty($description['description'])) { - // Explanations are taken from https://en.wikipedia.org/wiki/List_of_HTTP_status_codes - $explanation = [ - '400' => L10n::t('The server cannot or will not process the request due to an apparent client error.'), - '401' => L10n::t('Authentication is required and has failed or has not yet been provided.'), - '403' => L10n::t('The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource, or may need an account.'), - '404' => L10n::t('The requested resource could not be found but may be available in the future.'), - '500' => L10n::t('An unexpected condition was encountered and no more specific message is suitable.'), - '503' => L10n::t('The server is currently unavailable (because it is overloaded or down for maintenance). Please try again later.'), - ]; - if (!empty($explanation[$val])) { - $description['description'] = $explanation[$val]; - } - } - } - - if ($val >= 200 && $val < 300) { - $err = 'OK'; - } - Logger::log('http_status_exit ' . $val); - header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err); + header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $message); - if (isset($description["title"])) { - $tpl = Renderer::getMarkupTemplate('http_status.tpl'); - echo Renderer::replaceMacros($tpl, ['$title' => $description["title"], - '$description' => defaults($description, 'description', '')]); - } + echo $content; exit(); } diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 969c1f6642..7028934d19 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -112,7 +112,6 @@ class Profile if (!DBA::isResult($user) && empty($profiledata)) { Logger::log('profile error: ' . $a->query_string, Logger::DEBUG); - notice(L10n::t('Requested account is not available.') . EOL); return; } @@ -129,7 +128,6 @@ class Profile if (empty($pdata) && empty($profiledata)) { Logger::log('profile error: ' . $a->query_string, Logger::DEBUG); - notice(L10n::t('Requested profile is not available.') . EOL); return; } diff --git a/src/Module/Attach.php b/src/Module/Attach.php index 24e0edc555..e9af90facc 100644 --- a/src/Module/Attach.php +++ b/src/Module/Attach.php @@ -24,7 +24,7 @@ class Attach extends BaseModule { $a = self::getApp(); if ($a->argc != 2) { - System::httpExit(400); // Bad Request. + throw new \Friendica\Network\HTTPException\BadRequestException(); } // @TODO: Replace with parameter from router @@ -33,19 +33,19 @@ class Attach extends BaseModule // Check for existence $item = MAttach::exists(['id' => $item_id]); if ($item === false) { - System::httpExit(404, ['description' => L10n::t('Item was not found.')]); + throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('Item was not found.')); } // Now we'll fetch the item, if we have enough permisson $item = MAttach::getByIdWithPermission($item_id); if ($item === false) { - System::httpExit(403, ['description' => L10n::t('Permission denied.')]); + throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Permission denied.')); } $data = MAttach::getData($item); if (is_null($data)) { Logger::log('NULL data for attachment with id ' . $item['id']); - System::httpExit(404, ['description' => L10n::t('Item was not found.')]); + throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('Item was not found.')); } // Use quotes around the filename to prevent a "multiple Content-Disposition" diff --git a/src/Module/Feed.php b/src/Module/Feed.php index eabd45da23..ba1b085963 100644 --- a/src/Module/Feed.php +++ b/src/Module/Feed.php @@ -33,7 +33,7 @@ class Feed extends BaseModule // @TODO: Replace with parameter from router if ($a->argc < 2) { - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } $type = null; diff --git a/src/Module/Followers.php b/src/Module/Followers.php index 9906dfc33e..79f34021b1 100644 --- a/src/Module/Followers.php +++ b/src/Module/Followers.php @@ -20,13 +20,13 @@ class Followers extends BaseModule // @TODO: Replace with parameter from router if (empty($a->argv[1])) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // @TODO: Replace with parameter from router $owner = User::getOwnerDataByNick($a->argv[1]); if (empty($owner)) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $page = defaults($_REQUEST, 'page', null); diff --git a/src/Module/Following.php b/src/Module/Following.php index 670142c489..3a68e7e0af 100644 --- a/src/Module/Following.php +++ b/src/Module/Following.php @@ -20,13 +20,13 @@ class Following extends BaseModule // @TODO: Replace with parameter from router if (empty($a->argv[1])) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } // @TODO: Replace with parameter from router $owner = User::getOwnerDataByNick($a->argv[1]); if (empty($owner)) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $page = defaults($_REQUEST, 'page', null); diff --git a/src/Module/Group.php b/src/Module/Group.php index eb1389d799..acc969c7e0 100644 --- a/src/Module/Group.php +++ b/src/Module/Group.php @@ -137,7 +137,7 @@ class Group extends BaseModule $change = false; if (!local_user()) { - System::httpExit(403); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } $a = self::getApp(); @@ -276,7 +276,7 @@ class Group extends BaseModule } if (!isset($group)) { - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } $groupeditor = [ diff --git a/src/Module/Inbox.php b/src/Module/Inbox.php index 35160bd88d..5367adb7e1 100644 --- a/src/Module/Inbox.php +++ b/src/Module/Inbox.php @@ -25,7 +25,7 @@ class Inbox extends BaseModule $postdata = file_get_contents('php://input'); if (empty($postdata)) { - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } if (Config::get('debug', 'ap_inbox_log')) { @@ -43,7 +43,7 @@ class Inbox extends BaseModule if (!empty($a->argv[1])) { $user = DBA::selectFirst('user', ['uid'], ['nickname' => $a->argv[1]]); if (!DBA::isResult($user)) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $uid = $user['uid']; } else { @@ -52,6 +52,6 @@ class Inbox extends BaseModule ActivityPub\Receiver::processInbox($postdata, $_SERVER, $uid); - System::httpExit(202); + throw new \Friendica\Network\HTTPException\AcceptedException(); } } diff --git a/src/Module/Install.php b/src/Module/Install.php index b19e69a324..54c10534dd 100644 --- a/src/Module/Install.php +++ b/src/Module/Install.php @@ -51,7 +51,7 @@ class Install extends BaseModule $a = self::getApp(); if (!$a->getMode()->isInstall()) { - Core\System::httpExit(403); + throw new \Friendica\Network\HTTPException\ForbiddenException(); } // route: install/testrwrite @@ -59,7 +59,7 @@ class Install extends BaseModule // @TODO: Replace with parameter from router if ($a->getArgumentValue(1, '') == 'testrewrite') { // Status Code 204 means that it worked without content - Core\System::httpExit(204); + throw new \Friendica\Network\HTTPException\NoContentException(); } self::$installer = new Core\Installer(); diff --git a/src/Module/NodeInfo.php b/src/Module/NodeInfo.php index c8d75b8e16..3261ef6902 100644 --- a/src/Module/NodeInfo.php +++ b/src/Module/NodeInfo.php @@ -18,7 +18,7 @@ class NodeInfo extends BaseModule $config = self::getApp()->getConfig(); if (!$config->get('system', 'nodeinfo')) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } } @@ -41,14 +41,14 @@ class NodeInfo extends BaseModule * * @param App $app * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \Friendica\Network\HTTPException\NotFoundException */ private static function printWellKnown(App $app) { $config = $app->getConfig(); if (!$config->get('system', 'nodeinfo')) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $nodeinfo = [ diff --git a/src/Module/Objects.php b/src/Module/Objects.php index f34af3a080..2104e8042f 100644 --- a/src/Module/Objects.php +++ b/src/Module/Objects.php @@ -20,7 +20,7 @@ class Objects extends BaseModule $a = self::getApp(); if (empty($a->argv[1])) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } if (!ActivityPub::isRequest()) { @@ -38,7 +38,7 @@ class Objects extends BaseModule // @TODO: Replace with parameter from router $item = Item::selectFirst(['id', 'author-link'], ['guid' => $a->argv[1], 'private' => false]); if (!DBA::isResult($item) || !strstr($item['author-link'], System::baseUrl())) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } } diff --git a/src/Module/Outbox.php b/src/Module/Outbox.php index 27dcd5c1f4..1482567791 100644 --- a/src/Module/Outbox.php +++ b/src/Module/Outbox.php @@ -20,12 +20,12 @@ class Outbox extends BaseModule // @TODO: Replace with parameter from router if (empty($a->argv[1])) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $owner = User::getOwnerDataByNick($a->argv[1]); if (empty($owner)) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $page = defaults($_REQUEST, 'page', null); diff --git a/src/Module/PageNotFound.php b/src/Module/PageNotFound.php new file mode 100644 index 0000000000..764903c2a6 --- /dev/null +++ b/src/Module/PageNotFound.php @@ -0,0 +1,15 @@ +argc <= 1 || $a->argc > 4) { - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) { @@ -74,9 +75,7 @@ class Photo extends BaseModule } if ($photo === false) { - // not using System::httpExit() because we don't want html here. - header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found" , true, 404); - exit(); + System::httpExit('404', 'Not Found'); } $cacheable = ($photo["allow_cid"] . $photo["allow_gid"] . $photo["deny_cid"] . $photo["deny_gid"] === "") && (isset($photo["cacheable"]) ? $photo["cacheable"] : true); @@ -85,7 +84,7 @@ class Photo extends BaseModule if (is_null($img) || !$img->isValid()) { Logger::log("Invalid photo with id {$photo["id"]}."); - System::httpExit(500, ["description" => "Invalid photo with id {$photo["id"]}."]); + throw new \Friendica\Network\HTTPException\InternalServerErrorException(L10n::t('Invalid photo with id %s.', $photo["id"])); } // if customsize is set and image is not a gif, resize it diff --git a/src/Module/Profile.php b/src/Module/Profile.php index c3297d261a..fceea726b4 100644 --- a/src/Module/Profile.php +++ b/src/Module/Profile.php @@ -38,7 +38,7 @@ class Profile extends BaseModule // @TODO: Replace with parameter from router if ($a->argc < 2) { - System::httpExit(400); + throw new \Friendica\Network\HTTPException\BadRequestException(); } self::$which = filter_var($a->argv[1], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK); diff --git a/src/Module/Proxy.php b/src/Module/Proxy.php index 387667a1a5..75a1142af6 100644 --- a/src/Module/Proxy.php +++ b/src/Module/Proxy.php @@ -70,7 +70,7 @@ class Proxy extends BaseModule $request = self::getRequestInfo(); if (empty($request['url'])) { - System::httpExit(400, ['title' => L10n::t('Bad Request.')]); + throw new \Friendica\Network\HTTPException\BadRequestException(); } // Webserver already tried direct cache... diff --git a/src/Module/Special/HTTPException.php b/src/Module/Special/HTTPException.php new file mode 100644 index 0000000000..5c6ff79ae6 --- /dev/null +++ b/src/Module/Special/HTTPException.php @@ -0,0 +1,91 @@ + ..., '$description' => ...] + */ + private static function getVars(\Friendica\Network\HTTPException $e) + { + $message = $e->getMessage(); + + $titles = [ + 200 => 'OK', + 400 => L10n::t('Bad Request'), + 401 => L10n::t('Unauthorized'), + 403 => L10n::t('Forbidden'), + 404 => L10n::t('Not Found'), + 500 => L10n::t('Internal Server Error'), + 503 => L10n::t('Service Unavailable'), + ]; + $title = defaults($titles, $e->getCode(), 'Error ' . $e->getCode()); + + if (empty($message)) { + // Explanations are taken from https://en.wikipedia.org/wiki/List_of_HTTP_status_codes + $explanation = [ + 400 => L10n::t('The server cannot or will not process the request due to an apparent client error.'), + 401 => L10n::t('Authentication is required and has failed or has not yet been provided.'), + 403 => L10n::t('The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource, or may need an account.'), + 404 => L10n::t('The requested resource could not be found but may be available in the future.'), + 500 => L10n::t('An unexpected condition was encountered and no more specific message is suitable.'), + 503 => L10n::t('The server is currently unavailable (because it is overloaded or down for maintenance). Please try again later.'), + ]; + + $message = defaults($explanation, $e->getCode(), ''); + } + + return ['$title' => $title, '$description' => $message]; + } + + /** + * Displays a bare message page with no theming at all. + * + * @param \Friendica\Network\HTTPException $e + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(\Friendica\Network\HTTPException $e) + { + $content = ''; + + if ($e->getCode() >= 400) { + $tpl = Renderer::getMarkupTemplate('http_status.tpl'); + $content = Renderer::replaceMacros($tpl, self::getVars($e)); + } + + System::httpExit($e->getCode(), $e->httpdesc, $content); + } + + /** + * Returns a content string that can be integrated in the current theme. + * + * @param \Friendica\Network\HTTPException $e + * @return string + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function content(\Friendica\Network\HTTPException $e) + { + header($_SERVER["SERVER_PROTOCOL"] . ' ' . $e->getCode() . ' ' . $e->httpdesc); + + $tpl = Renderer::getMarkupTemplate('exception.tpl'); + + return Renderer::replaceMacros($tpl, self::getVars($e)); + } +} diff --git a/src/Module/Statistics.php b/src/Module/Statistics.php index 566ace3331..3e64828e7b 100644 --- a/src/Module/Statistics.php +++ b/src/Module/Statistics.php @@ -13,7 +13,7 @@ class Statistics extends BaseModule $config = self::getApp()->getConfig(); if (!$config->get("system", "nodeinfo")) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } } diff --git a/src/Module/WebFinger.php b/src/Module/WebFinger.php index 3afcecb056..0c1a692e1b 100644 --- a/src/Module/WebFinger.php +++ b/src/Module/WebFinger.php @@ -5,7 +5,6 @@ namespace Friendica\Module; use Friendica\BaseModule; use Friendica\Core\L10n; use Friendica\Core\Renderer; -use Friendica\Core\System; use Friendica\Network\Probe; /** @@ -13,22 +12,14 @@ use Friendica\Network\Probe; */ class WebFinger extends BaseModule { - public static function init() - { - if (!local_user()) { - System::httpExit( - 403, - [ - 'title' => L10n::t('Public access denied.'), - 'description' => L10n::t('Only logged in users are permitted to perform a probing.'), - ], - ); - exit(); - } - } - public static function content() { + if (!local_user()) { + $e = new \Friendica\Network\HTTPException\ForbiddenException(L10n::t("Only logged in users are permitted to perform a probing.")); + $e->httpdesc = L10n::t("Public access denied."); + throw $e; + } + $app = self::getApp(); $addr = defaults($_GET, 'addr', ''); diff --git a/src/Module/Xrd.php b/src/Module/Xrd.php index f7e20c9dbd..b6bd875942 100644 --- a/src/Module/Xrd.php +++ b/src/Module/Xrd.php @@ -58,7 +58,7 @@ class Xrd extends BaseModule $user = User::getByNickname($name); if (empty($user)) { - System::httpExit(404); + throw new \Friendica\Network\HTTPException\NotFoundException(); } $profileURL = $app->getBaseURL() . '/profile/' . $user['nickname']; diff --git a/src/Network/HTTPException.php b/src/Network/HTTPException.php index b9bad457da..89c447b714 100644 --- a/src/Network/HTTPException.php +++ b/src/Network/HTTPException.php @@ -3,7 +3,7 @@ /** * Throwable exceptions to return HTTP status code * - * This list of Exception has be extracted from + * This list of Exception has been extracted from * here http://racksburg.com/choosing-an-http-status-code/ */ @@ -11,17 +11,17 @@ namespace Friendica\Network; use Exception; -class HTTPException extends Exception +abstract class HTTPException extends Exception { - var $httpcode = 200; - var $httpdesc = ""; + public $httpdesc = ''; - public function __construct($message = '', $code = 0, Exception $previous = null) + public function __construct($message = '', Exception $previous = null) { - if ($this->httpdesc == '') { + parent::__construct($message, $this->code, $previous); + + if (empty($this->httpdesc)) { $classname = str_replace('Exception', '', str_replace('Friendica\Network\HTTPException\\', '', get_class($this))); $this->httpdesc = preg_replace("|([a-z])([A-Z])|",'$1 $2', $classname); } - parent::__construct($message, $code, $previous); } } diff --git a/src/Network/HTTPException/AcceptedException.php b/src/Network/HTTPException/AcceptedException.php new file mode 100644 index 0000000000..b8c843ec54 --- /dev/null +++ b/src/Network/HTTPException/AcceptedException.php @@ -0,0 +1,10 @@ +assertEquals( - '{"status":{"error":"error_message","code":"200 Friendica\\\\Network\\\\HTTP","request":""}}', - api_error('json', new HTTPException('error_message')) + '{"status":{"error":"error_message","code":"200 OK","request":""}}', + api_error('json', new HTTPException\OKException('error_message')) ); } @@ -628,10 +628,10 @@ class ApiTest extends DatabaseTest 'xmlns:friendica="http://friendi.ca/schema/api/1/" '. 'xmlns:georss="http://www.georss.org/georss">'."\n". ' error_message'."\n". - ' 200 Friendica\Network\HTTP'."\n". + ' 200 OK'."\n". ' '."\n". ''."\n", - api_error('xml', new HTTPException('error_message')) + api_error('xml', new HTTPException\OKException('error_message')) ); } @@ -648,10 +648,10 @@ class ApiTest extends DatabaseTest 'xmlns:friendica="http://friendi.ca/schema/api/1/" '. 'xmlns:georss="http://www.georss.org/georss">'."\n". ' error_message'."\n". - ' 200 Friendica\Network\HTTP'."\n". + ' 200 OK'."\n". ' '."\n". ''."\n", - api_error('rss', new HTTPException('error_message')) + api_error('rss', new HTTPException\OKException('error_message')) ); } @@ -668,10 +668,10 @@ class ApiTest extends DatabaseTest 'xmlns:friendica="http://friendi.ca/schema/api/1/" '. 'xmlns:georss="http://www.georss.org/georss">'."\n". ' error_message'."\n". - ' 200 Friendica\Network\HTTP'."\n". + ' 200 OK'."\n". ' '."\n". ''."\n", - api_error('atom', new HTTPException('error_message')) + api_error('atom', new HTTPException\OKException('error_message')) ); } diff --git a/tests/src/BaseObjectTest.php b/tests/src/BaseObjectTest.php index 749ae77c2a..cb980b47e9 100644 --- a/tests/src/BaseObjectTest.php +++ b/tests/src/BaseObjectTest.php @@ -33,7 +33,7 @@ class BaseObjectTest extends TestCase $this->setUpVfsDir(); $this->mockApp($this->root); - $this->assertNull($baseObject->setApp($this->app)); + $baseObject->setApp($this->app); $this->assertEquals($this->app, $baseObject->getApp()); } @@ -45,7 +45,6 @@ class BaseObjectTest extends TestCase */ public function testGetAppFailed() { - $baseObject = new BaseObject(); - $baseObject->getApp(); + BaseObject::getApp(); } } diff --git a/view/templates/exception.tpl b/view/templates/exception.tpl new file mode 100644 index 0000000000..cc4e6167d0 --- /dev/null +++ b/view/templates/exception.tpl @@ -0,0 +1,4 @@ +
+

{{$title}}

+

{{$message}}

+