aboutsummaryrefslogtreecommitdiffhomepage
path: root/index.php
diff options
context:
space:
mode:
Diffstat (limited to 'index.php')
-rw-r--r--index.php205
1 files changed, 117 insertions, 88 deletions
diff --git a/index.php b/index.php
index b4c4347a..4068a828 100644
--- a/index.php
+++ b/index.php
@@ -48,8 +48,8 @@ if (! file_exists(__DIR__ . '/vendor/autoload.php')) {
48 ."If you installed Shaarli through Git or using the development branch,\n" 48 ."If you installed Shaarli through Git or using the development branch,\n"
49 ."please refer to the installation documentation to install PHP" 49 ."please refer to the installation documentation to install PHP"
50 ." dependencies using Composer:\n" 50 ." dependencies using Composer:\n"
51 ."- https://github.com/shaarli/Shaarli/wiki/Server-requirements\n" 51 ."- https://shaarli.readthedocs.io/en/master/Server-requirements/\n"
52 ."- https://github.com/shaarli/Shaarli/wiki/Download-and-Installation"; 52 ."- https://shaarli.readthedocs.io/en/master/Download-and-Installation/";
53 exit; 53 exit;
54} 54}
55require_once 'inc/rain.tpl.class.php'; 55require_once 'inc/rain.tpl.class.php';
@@ -88,7 +88,7 @@ try {
88 exit; 88 exit;
89} 89}
90 90
91define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE)); 91define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
92 92
93// Force cookie path (but do not change lifetime) 93// Force cookie path (but do not change lifetime)
94$cookie = session_get_cookie_params(); 94$cookie = session_get_cookie_params();
@@ -133,15 +133,6 @@ date_default_timezone_set($conf->get('general.timezone', 'UTC'));
133 133
134ob_start(); // Output buffering for the page cache. 134ob_start(); // Output buffering for the page cache.
135 135
136// In case stupid admin has left magic_quotes enabled in php.ini:
137if (get_magic_quotes_gpc())
138{
139 function stripslashes_deep($value) { $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value); return $value; }
140 $_POST = array_map('stripslashes_deep', $_POST);
141 $_GET = array_map('stripslashes_deep', $_GET);
142 $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
143}
144
145// Prevent caching on client side or proxy: (yes, it's ugly) 136// Prevent caching on client side or proxy: (yes, it's ugly)
146header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); 137header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
147header("Cache-Control: no-store, no-cache, must-revalidate"); 138header("Cache-Control: no-store, no-cache, must-revalidate");
@@ -186,42 +177,42 @@ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
186 */ 177 */
187function setup_login_state($conf) 178function setup_login_state($conf)
188{ 179{
189 if ($conf->get('security.open_shaarli')) { 180 if ($conf->get('security.open_shaarli')) {
190 return true; 181 return true;
191 } 182 }
192 $userIsLoggedIn = false; // By default, we do not consider the user as logged in; 183 $userIsLoggedIn = false; // By default, we do not consider the user as logged in;
193 $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met. 184 $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
194 if (! $conf->exists('credentials.login')) { 185 if (! $conf->exists('credentials.login')) {
195 $userIsLoggedIn = false; // Shaarli is not configured yet. 186 $userIsLoggedIn = false; // Shaarli is not configured yet.
196 $loginFailure = true; 187 $loginFailure = true;
197 } 188 }
198 if (isset($_COOKIE['shaarli_staySignedIn']) && 189 if (isset($_COOKIE['shaarli_staySignedIn']) &&
199 $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN && 190 $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
200 !$loginFailure) 191 !$loginFailure)
201 { 192 {
202 fillSessionInfo($conf); 193 fillSessionInfo($conf);
203 $userIsLoggedIn = true; 194 $userIsLoggedIn = true;
204 } 195 }
205 // If session does not exist on server side, or IP address has changed, or session has expired, logout. 196 // If session does not exist on server side, or IP address has changed, or session has expired, logout.
206 if (empty($_SESSION['uid']) 197 if (empty($_SESSION['uid'])
207 || ($conf->get('security.session_protection_disabled') === false && $_SESSION['ip'] != allIPs()) 198 || ($conf->get('security.session_protection_disabled') === false && $_SESSION['ip'] != allIPs())
208 || time() >= $_SESSION['expires_on']) 199 || time() >= $_SESSION['expires_on'])
209 { 200 {
210 logout(); 201 logout();
211 $userIsLoggedIn = false; 202 $userIsLoggedIn = false;
212 $loginFailure = true; 203 $loginFailure = true;
213 } 204 }
214 if (!empty($_SESSION['longlastingsession'])) { 205 if (!empty($_SESSION['longlastingsession'])) {
215 $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked. 206 $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
216 } 207 }
217 else { 208 else {
218 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date. 209 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
219 } 210 }
220 if (!$loginFailure) { 211 if (!$loginFailure) {
221 $userIsLoggedIn = true; 212 $userIsLoggedIn = true;
222 } 213 }
223 214
224 return $userIsLoggedIn; 215 return $userIsLoggedIn;
225} 216}
226$userIsLoggedIn = setup_login_state($conf); 217$userIsLoggedIn = setup_login_state($conf);
227 218
@@ -245,10 +236,10 @@ function allIPs()
245 */ 236 */
246function fillSessionInfo($conf) 237function fillSessionInfo($conf)
247{ 238{
248 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid) 239 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid)
249 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked. 240 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
250 $_SESSION['username']= $conf->get('credentials.login'); 241 $_SESSION['username']= $conf->get('credentials.login');
251 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration. 242 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
252} 243}
253 244
254/** 245/**
@@ -265,7 +256,7 @@ function check_auth($login, $password, $conf)
265 $hash = sha1($password . $login . $conf->get('credentials.salt')); 256 $hash = sha1($password . $login . $conf->get('credentials.salt'));
266 if ($login == $conf->get('credentials.login') && $hash == $conf->get('credentials.hash')) 257 if ($login == $conf->get('credentials.login') && $hash == $conf->get('credentials.hash'))
267 { // Login/password is correct. 258 { // Login/password is correct.
268 fillSessionInfo($conf); 259 fillSessionInfo($conf);
269 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login successful'); 260 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login successful');
270 return true; 261 return true;
271 } 262 }
@@ -394,9 +385,10 @@ if (isset($_POST['login']))
394 // If user wants to keep the session cookie even after the browser closes: 385 // If user wants to keep the session cookie even after the browser closes:
395 if (!empty($_POST['longlastingsession'])) 386 if (!empty($_POST['longlastingsession']))
396 { 387 {
397 setcookie('shaarli_staySignedIn', STAY_SIGNED_IN_TOKEN, time()+31536000, WEB_PATH); 388 $_SESSION['longlastingsession'] = 31536000; // (31536000 seconds = 1 year)
398 $_SESSION['longlastingsession']=31536000; // (31536000 seconds = 1 year) 389 $expiration = time() + $_SESSION['longlastingsession']; // calculate relative cookie expiration (1 year from now)
399 $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // Set session expiration on server-side. 390 setcookie('shaarli_staySignedIn', STAY_SIGNED_IN_TOKEN, $expiration, WEB_PATH);
391 $_SESSION['expires_on'] = $expiration; // Set session expiration on server-side.
400 392
401 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/'; 393 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
402 session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side 394 session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side
@@ -591,20 +583,29 @@ function showDailyRSS($conf) {
591 */ 583 */
592function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager) 584function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
593{ 585{
594 $day=date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 586 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
595 if (isset($_GET['day'])) $day=$_GET['day']; 587 if (isset($_GET['day'])) {
588 $day = $_GET['day'];
589 }
596 590
597 $days = $LINKSDB->days(); 591 $days = $LINKSDB->days();
598 $i = array_search($day,$days); 592 $i = array_search($day, $days);
599 if ($i===false) { $i=count($days)-1; $day=$days[$i]; } 593 if ($i === false && count($days)) {
600 $previousday=''; 594 // no links for day, but at least one day with links
601 $nextday=''; 595 $i = count($days) - 1;
602 if ($i!==false) 596 $day = $days[$i];
603 {
604 if ($i>=1) $previousday=$days[$i-1];
605 if ($i<count($days)-1) $nextday=$days[$i+1];
606 } 597 }
598 $previousday = '';
599 $nextday = '';
607 600
601 if ($i !== false) {
602 if ($i >= 1) {
603 $previousday=$days[$i - 1];
604 }
605 if ($i < count($days) - 1) {
606 $nextday = $days[$i + 1];
607 }
608 }
608 try { 609 try {
609 $linksToDisplay = $LINKSDB->filterDay($day); 610 $linksToDisplay = $LINKSDB->filterDay($day);
610 } catch (Exception $exc) { 611 } catch (Exception $exc) {
@@ -613,9 +614,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
613 } 614 }
614 615
615 // We pre-format some fields for proper output. 616 // We pre-format some fields for proper output.
616 foreach($linksToDisplay as $key=>$link) 617 foreach($linksToDisplay as $key => $link) {
617 {
618
619 $taglist = explode(' ',$link['tags']); 618 $taglist = explode(' ',$link['tags']);
620 uasort($taglist, 'strcasecmp'); 619 uasort($taglist, 'strcasecmp');
621 $linksToDisplay[$key]['taglist']=$taglist; 620 $linksToDisplay[$key]['taglist']=$taglist;
@@ -629,21 +628,22 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
629 so I manually spread entries with a simple method: I roughly evaluate the 628 so I manually spread entries with a simple method: I roughly evaluate the
630 height of a div according to title and description length. 629 height of a div according to title and description length.
631 */ 630 */
632 $columns=array(array(),array(),array()); // Entries to display, for each column. 631 $columns = array(array(), array(), array()); // Entries to display, for each column.
633 $fill=array(0,0,0); // Rough estimate of columns fill. 632 $fill = array(0, 0, 0); // Rough estimate of columns fill.
634 foreach($linksToDisplay as $key=>$link) 633 foreach($linksToDisplay as $key => $link) {
635 {
636 // Roughly estimate length of entry (by counting characters) 634 // Roughly estimate length of entry (by counting characters)
637 // Title: 30 chars = 1 line. 1 line is 30 pixels height. 635 // Title: 30 chars = 1 line. 1 line is 30 pixels height.
638 // Description: 836 characters gives roughly 342 pixel height. 636 // Description: 836 characters gives roughly 342 pixel height.
639 // This is not perfect, but it's usually OK. 637 // This is not perfect, but it's usually OK.
640 $length=strlen($link['title'])+(342*strlen($link['description']))/836; 638 $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836;
641 if ($link['thumbnail']) $length +=100; // 1 thumbnails roughly takes 100 pixels height. 639 if ($link['thumbnail']) {
640 $length += 100; // 1 thumbnails roughly takes 100 pixels height.
641 }
642 // Then put in column which is the less filled: 642 // Then put in column which is the less filled:
643 $smallest=min($fill); // find smallest value in array. 643 $smallest = min($fill); // find smallest value in array.
644 $index=array_search($smallest,$fill); // find index of this smallest value. 644 $index = array_search($smallest, $fill); // find index of this smallest value.
645 array_push($columns[$index],$link); // Put entry in this column. 645 array_push($columns[$index], $link); // Put entry in this column.
646 $fill[$index]+=$length; 646 $fill[$index] += $length;
647 } 647 }
648 648
649 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); 649 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
@@ -718,6 +718,23 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
718 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; 718 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
719 $targetPage = Router::findPage($query, $_GET, isLoggedIn()); 719 $targetPage = Router::findPage($query, $_GET, isLoggedIn());
720 720
721 if (
722 // if the user isn't logged in
723 !isLoggedIn() &&
724 // and Shaarli doesn't have public content...
725 $conf->get('privacy.hide_public_links') &&
726 // and is configured to enforce the login
727 $conf->get('privacy.force_login') &&
728 // and the current page isn't already the login page
729 $targetPage !== Router::$PAGE_LOGIN &&
730 // and the user is not requesting a feed (which would lead to a different content-type as expected)
731 $targetPage !== Router::$PAGE_FEED_ATOM &&
732 $targetPage !== Router::$PAGE_FEED_RSS
733 ) {
734 // force current page to be the login page
735 $targetPage = Router::$PAGE_LOGIN;
736 }
737
721 // Call plugin hooks for header, footer and includes, specifying which page will be rendered. 738 // Call plugin hooks for header, footer and includes, specifying which page will be rendered.
722 // Then assign generated data to RainTPL. 739 // Then assign generated data to RainTPL.
723 $common_hooks = array( 740 $common_hooks = array(
@@ -745,6 +762,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
745 $PAGE->assign('username', escape($_GET['username'])); 762 $PAGE->assign('username', escape($_GET['username']));
746 } 763 }
747 $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); 764 $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
765 // add default state of the 'remember me' checkbox
766 $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default'));
748 $PAGE->renderPage('loginform'); 767 $PAGE->renderPage('loginform');
749 exit; 768 exit;
750 } 769 }
@@ -803,7 +822,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
803 $maxcount = max($maxcount, $value); 822 $maxcount = max($maxcount, $value);
804 } 823 }
805 824
806 alphabetical_sort($tags, true, true); 825 alphabetical_sort($tags, false, true);
807 826
808 $tagList = array(); 827 $tagList = array();
809 foreach($tags as $key => $value) { 828 foreach($tags as $key => $value) {
@@ -821,7 +840,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
821 } 840 }
822 841
823 $data = array( 842 $data = array(
824 'search_tags' => implode(' ', $filteringTags), 843 'search_tags' => implode(' ', escape($filteringTags)),
825 'tags' => $tagList, 844 'tags' => $tagList,
826 ); 845 );
827 $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); 846 $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn()));
@@ -851,7 +870,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
851 } 870 }
852 871
853 $data = [ 872 $data = [
854 'search_tags' => implode(' ', $filteringTags), 873 'search_tags' => implode(' ', escape($filteringTags)),
855 'tags' => $tags, 874 'tags' => $tags,
856 ]; 875 ];
857 $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]); 876 $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]);
@@ -1063,10 +1082,10 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
1063 // -------- Display the Tools menu if requested (import/export/bookmarklet...) 1082 // -------- Display the Tools menu if requested (import/export/bookmarklet...)
1064 if ($targetPage == Router::$PAGE_TOOLS) 1083 if ($targetPage == Router::$PAGE_TOOLS)
1065 { 1084 {
1066 $data = array( 1085 $data = [
1067 'pageabsaddr' => index_url($_SERVER), 1086 'pageabsaddr' => index_url($_SERVER),
1068 'sslenabled' => !empty($_SERVER['HTTPS']) 1087 'sslenabled' => is_https($_SERVER),
1069 ); 1088 ];
1070 $pluginManager->executeHooks('render_tools', $data); 1089 $pluginManager->executeHooks('render_tools', $data);
1071 1090
1072 foreach ($data as $key => $value) { 1091 foreach ($data as $key => $value) {
@@ -1233,7 +1252,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
1233 // Linkdate is kept here to: 1252 // Linkdate is kept here to:
1234 // - use the same permalink for notes as they're displayed when creating them 1253 // - use the same permalink for notes as they're displayed when creating them
1235 // - let users hack creation date of their posts 1254 // - let users hack creation date of their posts
1236 // See: https://github.com/shaarli/Shaarli/wiki/Datastore-hacks#changing-the-timestamp-for-a-link 1255 // See: https://shaarli.readthedocs.io/en/master/Various-hacks/#changing-the-timestamp-for-a-shaare
1237 $linkdate = escape($_POST['lf_linkdate']); 1256 $linkdate = escape($_POST['lf_linkdate']);
1238 if (isset($LINKSDB[$id])) { 1257 if (isset($LINKSDB[$id])) {
1239 // Edit 1258 // Edit
@@ -1256,6 +1275,9 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
1256 // Remove duplicates. 1275 // Remove duplicates.
1257 $tags = implode(' ', array_unique(explode(' ', $tags))); 1276 $tags = implode(' ', array_unique(explode(' ', $tags)));
1258 1277
1278 if (empty(trim($_POST['lf_url']))) {
1279 $_POST['lf_url'] = '?' . smallHash($linkdate . $id);
1280 }
1259 $url = whitelist_protocols(trim($_POST['lf_url']), $conf->get('security.allowed_protocols')); 1281 $url = whitelist_protocols(trim($_POST['lf_url']), $conf->get('security.allowed_protocols'));
1260 1282
1261 $link = array( 1283 $link = array(
@@ -1325,10 +1347,17 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
1325 die('Wrong token.'); 1347 die('Wrong token.');
1326 } 1348 }
1327 1349
1328 if (strpos($_GET['lf_linkdate'], ' ') !== false) { 1350 $ids = trim($_GET['lf_linkdate']);
1329 $ids = array_values(array_filter(preg_split('/\s+/', escape($_GET['lf_linkdate'])))); 1351 if (strpos($ids, ' ') !== false) {
1352 // multiple, space-separated ids provided
1353 $ids = array_values(array_filter(preg_split('/\s+/', escape($ids))));
1330 } else { 1354 } else {
1331 $ids = [$_GET['lf_linkdate']]; 1355 // only a single id provided
1356 $ids = [$ids];
1357 }
1358 // assert at least one id is given
1359 if(!count($ids)){
1360 die('no id provided');
1332 } 1361 }
1333 foreach ($ids as $id) { 1362 foreach ($ids as $id) {
1334 $id = (int) escape($id); 1363 $id = (int) escape($id);
@@ -1414,7 +1443,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
1414 1443
1415 if ($url == '') { 1444 if ($url == '') {
1416 $url = '?' . smallHash($linkdate . $LINKSDB->getNextId()); 1445 $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
1417 $title = 'Note: '; 1446 $title = $conf->get('general.default_note_title', 'Note: ');
1418 } 1447 }
1419 $url = escape($url); 1448 $url = escape($url);
1420 $title = escape($title); 1449 $title = escape($title);