Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

512 lines
15 KiB

  1. <?php
  2. /**
  3. * @file index.php
  4. * Friendica
  5. */
  6. /**
  7. * Bootstrap the application
  8. */
  9. use Friendica\App;
  10. use Friendica\BaseObject;
  11. use Friendica\Core\System;
  12. use Friendica\Core\Config;
  13. use Friendica\Core\Worker;
  14. use Friendica\Database\DBM;
  15. require_once 'boot.php';
  16. if (empty($a)) {
  17. $a = new App(__DIR__);
  18. }
  19. BaseObject::setApp($a);
  20. // We assume that the index.php is called by a frontend process
  21. // The value is set to "true" by default in boot.php
  22. $a->backend = false;
  23. /**
  24. * Load the configuration file which contains our DB credentials.
  25. * Ignore errors. If the file doesn't exist or is empty, we are running in
  26. * installation mode.
  27. */
  28. $install = ((file_exists('.htconfig.php') && filesize('.htconfig.php')) ? false : true);
  29. // Only load config if found, don't surpress errors
  30. if (!$install) {
  31. include ".htconfig.php";
  32. }
  33. /**
  34. * Try to open the database;
  35. */
  36. require_once "include/dba.php";
  37. if (!$install) {
  38. dba::connect($db_host, $db_user, $db_pass, $db_data, $install);
  39. unset($db_host, $db_user, $db_pass, $db_data);
  40. /**
  41. * Load configs from db. Overwrite configs from .htconfig.php
  42. */
  43. Config::load();
  44. if ($a->max_processes_reached() || $a->maxload_reached()) {
  45. header($_SERVER["SERVER_PROTOCOL"] . ' 503 Service Temporarily Unavailable');
  46. header('Retry-After: 120');
  47. header('Refresh: 120; url=' . System::baseUrl() . "/" . $a->query_string);
  48. die("System is currently unavailable. Please try again later");
  49. }
  50. if (Config::get('system', 'force_ssl') && ($a->get_scheme() == "http")
  51. && (intval(Config::get('system', 'ssl_policy')) == SSL_POLICY_FULL)
  52. && (substr(System::baseUrl(), 0, 8) == "https://")
  53. ) {
  54. header("HTTP/1.1 302 Moved Temporarily");
  55. header("Location: " . System::baseUrl() . "/" . $a->query_string);
  56. exit();
  57. }
  58. require_once 'include/session.php';
  59. load_hooks();
  60. call_hooks('init_1');
  61. $maintenance = Config::get('system', 'maintenance');
  62. }
  63. $lang = get_browser_language();
  64. load_translation_table($lang);
  65. /**
  66. * Important stuff we always need to do.
  67. *
  68. * The order of these may be important so use caution if you think they're all
  69. * intertwingled with no logical order and decide to sort it out. Some of the
  70. * dependencies have changed, but at least at one time in the recent past - the
  71. * order was critical to everything working properly
  72. */
  73. // Exclude the backend processes from the session management
  74. if (!$a->is_backend()) {
  75. $stamp1 = microtime(true);
  76. session_start();
  77. $a->save_timestamp($stamp1, "parser");
  78. } else {
  79. Worker::executeIfIdle();
  80. }
  81. /**
  82. * Language was set earlier, but we can over-ride it in the session.
  83. * We have to do it here because the session was just now opened.
  84. */
  85. if (x($_SESSION, 'authenticated') && !x($_SESSION, 'language')) {
  86. // we didn't loaded user data yet, but we need user language
  87. $r = dba::select('user', array('language'), array('uid' => $_SESSION['uid']), array('limit' => 1));
  88. $_SESSION['language'] = $lang;
  89. if (DBM::is_result($r)) {
  90. $_SESSION['language'] = $r['language'];
  91. }
  92. }
  93. if ((x($_SESSION, 'language')) && ($_SESSION['language'] !== $lang)) {
  94. $lang = $_SESSION['language'];
  95. load_translation_table($lang);
  96. }
  97. if ((x($_GET, 'zrl')) && (!$install && !$maintenance)) {
  98. // Only continue when the given profile link seems valid
  99. // Valid profile links contain a path with "/profile/" and no query parameters
  100. if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "")
  101. && strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/")
  102. ) {
  103. $_SESSION['my_url'] = $_GET['zrl'];
  104. $a->query_string = preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $a->query_string);
  105. zrl_init($a);
  106. } else {
  107. // Someone came with an invalid parameter, maybe as a DDoS attempt
  108. // We simply stop processing here
  109. logger("Invalid ZRL parameter ".$_GET['zrl'], LOGGER_DEBUG);
  110. header('HTTP/1.1 403 Forbidden');
  111. echo "<h1>403 Forbidden</h1>";
  112. killme();
  113. }
  114. }
  115. /**
  116. * For Mozilla auth manager - still needs sorting, and this might conflict with LRDD header.
  117. * Apache/PHP lumps the Link: headers into one - and other services might not be able to parse it
  118. * this way. There's a PHP flag to link the headers because by default this will over-write any other
  119. * link header.
  120. *
  121. * What we really need to do is output the raw headers ourselves so we can keep them separate.
  122. */
  123. // header('Link: <' . System::baseUrl() . '/amcd>; rel="acct-mgmt";');
  124. if (x($_COOKIE["Friendica"]) || (x($_SESSION, 'authenticated')) || (x($_POST, 'auth-params')) || ($a->module === 'login')) {
  125. require "include/auth.php";
  126. }
  127. if (! x($_SESSION, 'authenticated')) {
  128. header('X-Account-Management-Status: none');
  129. }
  130. /* set up page['htmlhead'] and page['end'] for the modules to use */
  131. $a->page['htmlhead'] = '';
  132. $a->page['end'] = '';
  133. if (! x($_SESSION, 'sysmsg')) {
  134. $_SESSION['sysmsg'] = array();
  135. }
  136. if (! x($_SESSION, 'sysmsg_info')) {
  137. $_SESSION['sysmsg_info'] = array();
  138. }
  139. // Array for informations about last received items
  140. if (! x($_SESSION, 'last_updated')) {
  141. $_SESSION['last_updated'] = array();
  142. }
  143. /*
  144. * check_config() is responsible for running update scripts. These automatically
  145. * update the DB schema whenever we push a new one out. It also checks to see if
  146. * any plugins have been added or removed and reacts accordingly.
  147. */
  148. // in install mode, any url loads install module
  149. // but we need "view" module for stylesheet
  150. if ($install && $a->module!="view") {
  151. $a->module = 'install';
  152. } elseif ($maintenance && $a->module!="view") {
  153. $a->module = 'maintenance';
  154. } else {
  155. check_url($a);
  156. check_db(false);
  157. check_plugins($a);
  158. }
  159. nav_set_selected('nothing');
  160. //Don't populate apps_menu if apps are private
  161. $privateapps = Config::get('config', 'private_addons');
  162. if ((local_user()) || (! $privateapps === "1")) {
  163. $arr = array('app_menu' => $a->apps);
  164. call_hooks('app_menu', $arr);
  165. $a->apps = $arr['app_menu'];
  166. }
  167. /**
  168. * We have already parsed the server path into $a->argc and $a->argv
  169. *
  170. * $a->argv[0] is our module name. We will load the file mod/{$a->argv[0]}.php
  171. * and use it for handling our URL request.
  172. * The module file contains a few functions that we call in various circumstances
  173. * and in the following order:
  174. *
  175. * "module"_init
  176. * "module"_post (only called if there are $_POST variables)
  177. * "module"_afterpost
  178. * "module"_content - the string return of this function contains our page body
  179. *
  180. * Modules which emit other serialisations besides HTML (XML,JSON, etc.) should do
  181. * so within the module init and/or post functions and then invoke killme() to terminate
  182. * further processing.
  183. */
  184. if (strlen($a->module)) {
  185. /**
  186. * We will always have a module name.
  187. * First see if we have a plugin which is masquerading as a module.
  188. */
  189. // Compatibility with the Android Diaspora client
  190. if ($a->module == "stream") {
  191. $a->module = "network";
  192. }
  193. // Compatibility with the Firefox App
  194. if (($a->module == "users") && ($a->cmd == "users/sign_in")) {
  195. $a->module = "login";
  196. }
  197. $privateapps = Config::get('config', 'private_addons');
  198. if (is_array($a->plugins) && in_array($a->module, $a->plugins) && file_exists("addon/{$a->module}/{$a->module}.php")) {
  199. //Check if module is an app and if public access to apps is allowed or not
  200. if ((!local_user()) && plugin_is_app($a->module) && $privateapps === "1") {
  201. info(t("You must be logged in to use addons. "));
  202. } else {
  203. include_once "addon/{$a->module}/{$a->module}.php";
  204. if (function_exists($a->module . '_module')) {
  205. $a->module_loaded = true;
  206. }
  207. }
  208. }
  209. /**
  210. * If not, next look for a 'standard' program module in the 'mod' directory
  211. */
  212. if ((! $a->module_loaded) && (file_exists("mod/{$a->module}.php"))) {
  213. include_once "mod/{$a->module}.php";
  214. $a->module_loaded = true;
  215. }
  216. /**
  217. * The URL provided does not resolve to a valid module.
  218. *
  219. * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'.
  220. * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic -
  221. * 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
  222. * this will often succeed and eventually do the right thing.
  223. *
  224. * Otherwise we are going to emit a 404 not found.
  225. */
  226. if (! $a->module_loaded) {
  227. // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
  228. if ((x($_SERVER, 'QUERY_STRING')) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) {
  229. killme();
  230. }
  231. if ((x($_SERVER, 'QUERY_STRING')) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
  232. logger('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']);
  233. goaway(System::baseUrl() . $_SERVER['REQUEST_URI']);
  234. }
  235. logger('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], LOGGER_DEBUG);
  236. header($_SERVER["SERVER_PROTOCOL"] . ' 404 ' . t('Not Found'));
  237. $tpl = get_markup_template("404.tpl");
  238. $a->page['content'] = replace_macros(
  239. $tpl,
  240. array(
  241. '$message' => t('Page not found.'))
  242. );
  243. }
  244. }
  245. /**
  246. * Load current theme info
  247. */
  248. $theme_info_file = "view/theme/".current_theme()."/theme.php";
  249. if (file_exists($theme_info_file)) {
  250. require_once $theme_info_file;
  251. }
  252. /* initialise content region */
  253. if (! x($a->page, 'content')) {
  254. $a->page['content'] = '';
  255. }
  256. if (!$install && !$maintenance) {
  257. call_hooks('page_content_top', $a->page['content']);
  258. }
  259. /**
  260. * Call module functions
  261. */
  262. if ($a->module_loaded) {
  263. $a->page['page_title'] = $a->module;
  264. $placeholder = '';
  265. if (function_exists($a->module . '_init')) {
  266. call_hooks($a->module . '_mod_init', $placeholder);
  267. $func = $a->module . '_init';
  268. $func($a);
  269. }
  270. if (function_exists(str_replace('-', '_', current_theme()) . '_init')) {
  271. $func = str_replace('-', '_', current_theme()) . '_init';
  272. $func($a);
  273. }
  274. if (($_SERVER['REQUEST_METHOD'] === 'POST') && (! $a->error)
  275. && (function_exists($a->module . '_post'))
  276. && (! x($_POST, 'auth-params'))
  277. ) {
  278. call_hooks($a->module . '_mod_post', $_POST);
  279. $func = $a->module . '_post';
  280. $func($a);
  281. }
  282. if ((! $a->error) && (function_exists($a->module . '_afterpost'))) {
  283. call_hooks($a->module . '_mod_afterpost', $placeholder);
  284. $func = $a->module . '_afterpost';
  285. $func($a);
  286. }
  287. if ((! $a->error) && (function_exists($a->module . '_content'))) {
  288. $arr = array('content' => $a->page['content']);
  289. call_hooks($a->module . '_mod_content', $arr);
  290. $a->page['content'] = $arr['content'];
  291. $func = $a->module . '_content';
  292. $arr = array('content' => $func($a));
  293. call_hooks($a->module . '_mod_aftercontent', $arr);
  294. $a->page['content'] .= $arr['content'];
  295. }
  296. if (function_exists(str_replace('-', '_', current_theme()) . '_content_loaded')) {
  297. $func = str_replace('-', '_', current_theme()) . '_content_loaded';
  298. $func($a);
  299. }
  300. }
  301. /*
  302. * Create the page head after setting the language
  303. * and getting any auth credentials.
  304. *
  305. * Moved init_pagehead() and init_page_end() to after
  306. * all the module functions have executed so that all
  307. * theme choices made by the modules can take effect.
  308. */
  309. $a->init_pagehead();
  310. /*
  311. * Build the page ending -- this is stuff that goes right before
  312. * the closing </body> tag
  313. */
  314. $a->init_page_end();
  315. // If you're just visiting, let javascript take you home
  316. if (x($_SESSION, 'visitor_home')) {
  317. $homebase = $_SESSION['visitor_home'];
  318. } elseif (local_user()) {
  319. $homebase = 'profile/' . $a->user['nickname'];
  320. }
  321. if (isset($homebase)) {
  322. $a->page['content'] .= '<script>var homebase="' . $homebase . '" ; </script>';
  323. }
  324. /*
  325. * now that we've been through the module content, see if the page reported
  326. * a permission problem and if so, a 403 response would seem to be in order.
  327. */
  328. if (stristr(implode("", $_SESSION['sysmsg']), t('Permission denied'))) {
  329. header($_SERVER["SERVER_PROTOCOL"] . ' 403 ' . t('Permission denied.'));
  330. }
  331. /*
  332. * Report anything which needs to be communicated in the notification area (before the main body)
  333. */
  334. call_hooks('page_end', $a->page['content']);
  335. /*
  336. * Add the navigation (menu) template
  337. */
  338. if ($a->module != 'install' && $a->module != 'maintenance') {
  339. nav($a);
  340. }
  341. /*
  342. * Add a "toggle mobile" link if we're using a mobile device
  343. */
  344. if ($a->is_mobile || $a->is_tablet) {
  345. if (isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) {
  346. $link = 'toggle_mobile?address=' . curPageURL();
  347. } else {
  348. $link = 'toggle_mobile?off=1&address=' . curPageURL();
  349. }
  350. $a->page['footer'] = replace_macros(
  351. get_markup_template("toggle_mobile_footer.tpl"),
  352. array(
  353. '$toggle_link' => $link,
  354. '$toggle_text' => t('toggle mobile'))
  355. );
  356. }
  357. /**
  358. * Build the page - now that we have all the components
  359. */
  360. if (!$a->theme['stylesheet']) {
  361. $stylesheet = current_theme_url();
  362. } else {
  363. $stylesheet = $a->theme['stylesheet'];
  364. }
  365. $a->page['htmlhead'] = str_replace('{{$stylesheet}}', $stylesheet, $a->page['htmlhead']);
  366. //$a->page['htmlhead'] = replace_macros($a->page['htmlhead'], array('$stylesheet' => $stylesheet));
  367. if (isset($_GET["mode"]) && (($_GET["mode"] == "raw") || ($_GET["mode"] == "minimal"))) {
  368. $doc = new DOMDocument();
  369. $target = new DOMDocument();
  370. $target->loadXML("<root></root>");
  371. $content = mb_convert_encoding($a->page["content"], 'HTML-ENTITIES', "UTF-8");
  372. /// @TODO one day, kill those error-surpressing @ stuff, or PHP should ban it
  373. @$doc->loadHTML($content);
  374. $xpath = new DomXPath($doc);
  375. $list = $xpath->query("//*[contains(@id,'tread-wrapper-')]"); /* */
  376. foreach ($list as $item) {
  377. $item = $target->importNode($item, true);
  378. // And then append it to the target
  379. $target->documentElement->appendChild($item);
  380. }
  381. }
  382. if (isset($_GET["mode"]) && ($_GET["mode"] == "raw")) {
  383. header("Content-type: text/html; charset=utf-8");
  384. echo substr($target->saveHTML(), 6, -8);
  385. killme();
  386. }
  387. $page = $a->page;
  388. $profile = $a->profile;
  389. header("X-Friendica-Version: " . FRIENDICA_VERSION);
  390. header("Content-type: text/html; charset=utf-8");
  391. if (Config::get('system', 'hsts') && (Config::get('system', 'ssl_policy') == SSL_POLICY_FULL)) {
  392. header("Strict-Transport-Security: max-age=31536000");
  393. }
  394. // Some security stuff
  395. header('X-Content-Type-Options: nosniff');
  396. header('X-XSS-Protection: 1; mode=block');
  397. header('X-Permitted-Cross-Domain-Policies: none');
  398. header('X-Frame-Options: sameorigin');
  399. // Things like embedded OSM maps don't work, when this is enabled
  400. // header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' https: data:; media-src 'self' https:; child-src 'self' https:; object-src 'none'");
  401. /*
  402. * We use $_GET["mode"] for special page templates. So we will check if we have
  403. * to load another page template than the default one.
  404. * The page templates are located in /view/php/ or in the theme directory.
  405. */
  406. if (isset($_GET["mode"])) {
  407. $template = theme_include($_GET["mode"] . '.php');
  408. }
  409. // If there is no page template use the default page template
  410. if (empty($template)) {
  411. $template = theme_include("default.php");
  412. }
  413. /// @TODO Looks unsafe (remote-inclusion), is maybe not but theme_include() uses file_exists() but does not escape anything
  414. require_once $template;
  415. killme();