diff options
-rw-r--r-- | index.php | 168 |
1 files changed, 147 insertions, 21 deletions
@@ -1,5 +1,5 @@ | |||
1 | <?php | 1 | <?php |
2 | // Shaarli 0.0.21 beta - Shaare your links... | 2 | // Shaarli 0.0.22 beta - Shaare your links... |
3 | // The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net | 3 | // The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net |
4 | // http://sebsauvage.net/wiki/doku.php?id=php:shaarli | 4 | // http://sebsauvage.net/wiki/doku.php?id=php:shaarli |
5 | // Licence: http://www.opensource.org/licenses/zlib-license.php | 5 | // Licence: http://www.opensource.org/licenses/zlib-license.php |
@@ -18,7 +18,8 @@ define('BAN_DURATION',1800); // Ban duration for IP address after login failures | |||
18 | define('OPEN_SHAARLI',false); // If true, anyone can add/edit/delete links without having to login | 18 | define('OPEN_SHAARLI',false); // If true, anyone can add/edit/delete links without having to login |
19 | define('HIDE_TIMESTAMPS',false); // If true, the moment when links were saved are not shown to users that are not logged in. | 19 | define('HIDE_TIMESTAMPS',false); // If true, the moment when links were saved are not shown to users that are not logged in. |
20 | define('ENABLE_THUMBNAILS',true); // Enable thumbnails in links. | 20 | define('ENABLE_THUMBNAILS',true); // Enable thumbnails in links. |
21 | 21 | define('CACHEDIR','cache'); // Cache directory for thumbnails for SLOW services (like flickr) | |
22 | |||
22 | // ----------------------------------------------------------------------------------------------- | 23 | // ----------------------------------------------------------------------------------------------- |
23 | // Program config (touch at your own risks !) | 24 | // Program config (touch at your own risks !) |
24 | define('UPDATECHECK_FILENAME',DATADIR.'/lastupdatecheck.txt'); // For updates check of Shaarli. | 25 | define('UPDATECHECK_FILENAME',DATADIR.'/lastupdatecheck.txt'); // For updates check of Shaarli. |
@@ -48,9 +49,11 @@ header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); | |||
48 | header("Cache-Control: no-store, no-cache, must-revalidate"); | 49 | header("Cache-Control: no-store, no-cache, must-revalidate"); |
49 | header("Cache-Control: post-check=0, pre-check=0", false); | 50 | header("Cache-Control: post-check=0, pre-check=0", false); |
50 | header("Pragma: no-cache"); | 51 | header("Pragma: no-cache"); |
51 | define('shaarli_version','0.0.21 beta'); | 52 | define('shaarli_version','0.0.22 beta'); |
52 | if (!is_dir(DATADIR)) { mkdir(DATADIR,0705); chmod(DATADIR,0705); } | 53 | if (!is_dir(DATADIR)) { mkdir(DATADIR,0705); chmod(DATADIR,0705); } |
54 | if (!is_dir(CACHEDIR)) { mkdir(CACHEDIR,0705); chmod(CACHEDIR,0705); } | ||
53 | if (!is_file(DATADIR.'/.htaccess')) { file_put_contents(DATADIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. | 55 | if (!is_file(DATADIR.'/.htaccess')) { file_put_contents(DATADIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. |
56 | if (!is_file(CACHEDIR.'/.htaccess')) { file_put_contents(CACHEDIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. | ||
54 | if (!is_file(CONFIG_FILE)) install(); | 57 | if (!is_file(CONFIG_FILE)) install(); |
55 | require CONFIG_FILE; // Read login/password hash into $GLOBALS. | 58 | require CONFIG_FILE; // Read login/password hash into $GLOBALS. |
56 | // Small protection against dodgy config files: | 59 | // Small protection against dodgy config files: |
@@ -162,7 +165,9 @@ function isLoggedIn() | |||
162 | logout(); | 165 | logout(); |
163 | return false; | 166 | return false; |
164 | } | 167 | } |
165 | $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // User accessed a page : Update his/her session expiration date. | 168 | if (!empty($_SESSION['longlastingsession'])) $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked. |
169 | else $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date. | ||
170 | |||
166 | return true; | 171 | return true; |
167 | } | 172 | } |
168 | 173 | ||
@@ -225,9 +230,22 @@ if (isset($_POST['login'])) | |||
225 | if (!ban_canLogin()) die('I said: NO. You are banned for the moment. Go away.'); | 230 | if (!ban_canLogin()) die('I said: NO. You are banned for the moment. Go away.'); |
226 | if (isset($_POST['password']) && tokenOk($_POST['token']) && (check_auth($_POST['login'], $_POST['password']))) | 231 | if (isset($_POST['password']) && tokenOk($_POST['token']) && (check_auth($_POST['login'], $_POST['password']))) |
227 | { // Login/password is ok. | 232 | { // Login/password is ok. |
228 | ban_loginOk(); | 233 | ban_loginOk(); |
234 | // If user wants to keep the session cookie even after the browser closes: | ||
235 | if (!empty($_POST['longlastingsession'])) | ||
236 | { | ||
237 | $_SESSION['longlastingsession']=31536000; // (31536000 seconds = 1 year) | ||
238 | $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // Set session expiration on server-side. | ||
239 | session_set_cookie_params($_SESSION['longlastingsession']); // Set session cookie expiration on client side | ||
240 | session_regenerate_id(true); // Send cookie with new expiration date to browser. | ||
241 | } | ||
242 | else // Standard session expiration (=when browser closes) | ||
243 | { | ||
244 | session_set_cookie_params(0); // 0 means "When browser closes" | ||
245 | session_regenerate_id(true); | ||
246 | } | ||
229 | // Optional redirect after login: | 247 | // Optional redirect after login: |
230 | if (isset($_GET['post'])) { header('Location: ?post='.urlencode($_GET['post']).(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); exit; } | 248 | if (isset($_GET['post'])) { header('Location: ?post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); exit; } |
231 | if (isset($_POST['returnurl'])) | 249 | if (isset($_POST['returnurl'])) |
232 | { | 250 | { |
233 | if (endsWith($_POST['returnurl'],'?do=login')) { header('Location: ?'); exit; } // Prevent loops over login screen. | 251 | if (endsWith($_POST['returnurl'],'?do=login')) { header('Location: ?'); exit; } // Prevent loops over login screen. |
@@ -657,13 +675,13 @@ function renderPage() | |||
657 | exit; | 675 | exit; |
658 | } | 676 | } |
659 | $returnurl_html = (isset($_SERVER['HTTP_REFERER']) ? '<input type="hidden" name="returnurl" value="'.htmlspecialchars($_SERVER['HTTP_REFERER']).'">' : ''); | 677 | $returnurl_html = (isset($_SERVER['HTTP_REFERER']) ? '<input type="hidden" name="returnurl" value="'.htmlspecialchars($_SERVER['HTTP_REFERER']).'">' : ''); |
660 | $loginform='<div id="headerform"><form method="post" name="loginform">Login: <input type="text" name="login"> Password : <input type="password" name="password"> <input type="submit" value="Login" class="bigbutton"><input type="hidden" name="token" value="'.getToken().'">'.$returnurl_html.'</form></div>'; | 678 | $loginform='<div id="headerform"><form method="post" name="loginform">Login: <input type="text" name="login"> Password : <input type="password" name="password"> <input type="submit" value="Login" class="bigbutton"><br>'; |
679 | $loginform.='<input style="margin:10 0 0 40;" type="checkbox" name="longlastingsession"> Stay signed in (Do not check on public computers)<input type="hidden" name="token" value="'.getToken().'">'.$returnurl_html.'</form></div>'; | ||
661 | $onload = 'onload="document.loginform.login.focus();"'; | 680 | $onload = 'onload="document.loginform.login.focus();"'; |
662 | $data = array('pageheader'=>$loginform,'body'=>'','onload'=>$onload); | 681 | $data = array('pageheader'=>$loginform,'body'=>'','onload'=>$onload); |
663 | templatePage($data); | 682 | templatePage($data); |
664 | exit; | 683 | exit; |
665 | } | 684 | } |
666 | |||
667 | // -------- User wants to logout. | 685 | // -------- User wants to logout. |
668 | if (startswith($_SERVER["QUERY_STRING"],'do=logout')) | 686 | if (startswith($_SERVER["QUERY_STRING"],'do=logout')) |
669 | { | 687 | { |
@@ -740,7 +758,7 @@ function renderPage() | |||
740 | // Show login screen, then redirect to ?post=... | 758 | // Show login screen, then redirect to ?post=... |
741 | if (isset($_GET['post'])) | 759 | if (isset($_GET['post'])) |
742 | { | 760 | { |
743 | header('Location: ?do=login&post='.urlencode($_GET['post']).(isset($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link. | 761 | header('Location: ?do=login&post='.urlencode($_GET['post']).(isset($_GET['title'])?'&title='.urlencode($_GET['title']):'').(isset($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link. |
744 | exit; | 762 | exit; |
745 | } | 763 | } |
746 | 764 | ||
@@ -1291,6 +1309,10 @@ HTML; | |||
1291 | function thumbnail($url) | 1309 | function thumbnail($url) |
1292 | { | 1310 | { |
1293 | if (!ENABLE_THUMBNAILS) return ''; | 1311 | if (!ENABLE_THUMBNAILS) return ''; |
1312 | |||
1313 | // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link. | ||
1314 | // (eg. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/2.jpg ) | ||
1315 | // ^^^^^^^^^^^ ^^^^^^^^^^^ | ||
1294 | $domain = parse_url($url,PHP_URL_HOST); | 1316 | $domain = parse_url($url,PHP_URL_HOST); |
1295 | if ($domain=='youtube.com' || $domain=='www.youtube.com') | 1317 | if ($domain=='youtube.com' || $domain=='www.youtube.com') |
1296 | { | 1318 | { |
@@ -1315,20 +1337,16 @@ function thumbnail($url) | |||
1315 | return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="'.htmlspecialchars($thumburl).'" width="120" style="height:auto;"></a></div>'; | 1337 | return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="'.htmlspecialchars($thumburl).'" width="120" style="height:auto;"></a></div>'; |
1316 | } | 1338 | } |
1317 | } | 1339 | } |
1318 | if ($domain=='vimeo.com') | 1340 | |
1341 | // Some other hosts are SLOW AS HELL and usually require an extra HTTP request to get the thumbnail URL. | ||
1342 | // So we deport the thumbnail generation in order not to slow down page generation | ||
1343 | // (and we also cache the thumbnail) | ||
1344 | if ($domain=='flickr.com' || endsWith($domain,'.flickr.com') || $domain=='vimeo.com') | ||
1319 | { | 1345 | { |
1320 | // This is more complex: we have to perform a HTTP request, then parse the result. | 1346 | $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation) |
1321 | // This slows down page generation :-( | 1347 | return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url).'" width="120" style="height:auto;"></a></div>'; |
1322 | // Maybe we should deport this to javascript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098 | ||
1323 | $vid = substr(parse_url($url,PHP_URL_PATH),1); | ||
1324 | // We allow 2 seconds for Vimeo servers to respond. | ||
1325 | list($httpstatus,$headers,$data) = getHTTP('http://vimeo.com/api/v2/video/'.htmlspecialchars($vid).'.php',2); | ||
1326 | if (strpos($httpstatus,'200 OK')) | ||
1327 | { | ||
1328 | $t = unserialize($data); | ||
1329 | if (!empty($t[0]['thumbnail_medium'])) return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="'.htmlspecialchars($t[0]['thumbnail_medium']).'" width="120" style="height:auto;"></a></div>'; | ||
1330 | } | ||
1331 | } | 1348 | } |
1349 | |||
1332 | return ''; // No thumbnail. | 1350 | return ''; // No thumbnail. |
1333 | 1351 | ||
1334 | } | 1352 | } |
@@ -1569,6 +1587,113 @@ function writeConfig() | |||
1569 | } | 1587 | } |
1570 | } | 1588 | } |
1571 | 1589 | ||
1590 | /* Because some f*cking services like Flickr require an extra HTTP request to get the thumbnail URL, | ||
1591 | I have deported the thumbnail URL code generation here, otherwise this would slow down page generation. | ||
1592 | The following function takes the URL a link (eg. a flickr page) and return the proper thumbnail. | ||
1593 | This function is called by passing the url: | ||
1594 | http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL] | ||
1595 | [URL] is the URL of the link (eg. a flickr page) | ||
1596 | [HMAC] is the signature for the [URL] (so that these URL cannot be forged). | ||
1597 | The function below will fetch the image from the webservice and store it in the cache. | ||
1598 | */ | ||
1599 | function genThumbnail() | ||
1600 | { | ||
1601 | // Make sure the parameters in the URL were generated by us. | ||
1602 | $sign = hash_hmac('sha256', $_GET['url'], $GLOBALS['salt']); | ||
1603 | if ($sign!=$_GET['hmac']) die('Naughty boy !'); | ||
1604 | |||
1605 | // Let's see if we don't already have the image for this URL in the cache. | ||
1606 | $thumbname=hash('sha1',$_GET['url']).'.jpg'; | ||
1607 | if (is_file(CACHEDIR.'/'.$thumbname)) | ||
1608 | { // We have the thumbnail, just serve it: | ||
1609 | header('Content-Type: image/jpeg'); | ||
1610 | echo file_get_contents(CACHEDIR.'/'.$thumbname); | ||
1611 | return; | ||
1612 | } | ||
1613 | // We may also serve a blank image (if service did not respond) | ||
1614 | $blankname=hash('sha1',$_GET['url']).'.gif'; | ||
1615 | if (is_file(CACHEDIR.'/'.$blankname)) | ||
1616 | { | ||
1617 | header('Content-Type: image/gif'); | ||
1618 | echo file_get_contents(CACHEDIR.'/'.$blankname); | ||
1619 | return; | ||
1620 | } | ||
1621 | |||
1622 | // Otherwise, generate the thumbnail. | ||
1623 | $url = $_GET['url']; | ||
1624 | $domain = parse_url($url,PHP_URL_HOST); | ||
1625 | |||
1626 | if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')) | ||
1627 | { | ||
1628 | // WTF ? I need a flickr API key to get a fucking thumbnail ? No way. | ||
1629 | // I'll extract the thumbnail URL myself. First, we have to get the flickr HTML page. | ||
1630 | // All images in Flickr are in the form: | ||
1631 | // http://farm[farm].static.flickr.com/[server]/[id]_[secret]_[size].jpg | ||
1632 | // Example: http://farm7.static.flickr.com/6205/6088513739_fc158467fe_z.jpg | ||
1633 | // We want the 240x120 format, which is _m.jpg. | ||
1634 | // We search for the first image in the page which does not have the _s size, | ||
1635 | // when use the _m to get the thumbnail. | ||
1636 | |||
1637 | // Is this a link to an image, or to a flickr page ? | ||
1638 | $imageurl=''; | ||
1639 | logm('url: '.$url); | ||
1640 | if (endswith(parse_url($url,PHP_URL_PATH),'.jpg')) | ||
1641 | { // This is a direct link to an image. eg. http://farm1.static.flickr.com/5/5921913_ac83ed27bd_o.jpg | ||
1642 | preg_match('!(http://farm\d+.static.flickr.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches); | ||
1643 | if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg'; | ||
1644 | } | ||
1645 | else // this is a flickr page (html) | ||
1646 | { | ||
1647 | list($httpstatus,$headers,$data) = getHTTP($url,20); // Get the flickr html page. | ||
1648 | if (strpos($httpstatus,'200 OK')) | ||
1649 | { | ||
1650 | preg_match('!(http://farm\d+.static.flickr.com/\d+/\d+_\w+_)[^s].jpg!',$data,$matches); | ||
1651 | if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg'; | ||
1652 | } | ||
1653 | } | ||
1654 | if ($imageurl!='') | ||
1655 | { // Let's download the image. | ||
1656 | list($httpstatus,$headers,$data) = getHTTP($imageurl,10); // Image is 240x120, so 10 seconds to download should be enough. | ||
1657 | if (strpos($httpstatus,'200 OK')) | ||
1658 | { | ||
1659 | file_put_contents(CACHEDIR.'/'.$thumbname,$data); // Save image to cache. | ||
1660 | header('Content-Type: image/jpeg'); | ||
1661 | echo $data; | ||
1662 | return; | ||
1663 | } | ||
1664 | } | ||
1665 | else logm('unkown flickr url: '.$url); | ||
1666 | } | ||
1667 | |||
1668 | if ($domain=='vimeo.com' ) | ||
1669 | { | ||
1670 | // This is more complex: we have to perform a HTTP request, then parse the result. | ||
1671 | // Maybe we should deport this to javascript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098 | ||
1672 | $vid = substr(parse_url($url,PHP_URL_PATH),1); | ||
1673 | list($httpstatus,$headers,$data) = getHTTP('http://vimeo.com/api/v2/video/'.htmlspecialchars($vid).'.php',5); | ||
1674 | if (strpos($httpstatus,'200 OK')) | ||
1675 | { | ||
1676 | $t = unserialize($data); | ||
1677 | $imageurl = $t[0]['thumbnail_medium']; | ||
1678 | // Then we download the image and serve it to our client. | ||
1679 | list($httpstatus,$headers,$data) = getHTTP($imageurl,10); | ||
1680 | if (strpos($httpstatus,'200 OK')) | ||
1681 | { | ||
1682 | file_put_contents(CACHEDIR.'/'.$thumbname,$data); // Save image to cache. | ||
1683 | header('Content-Type: image/jpeg'); | ||
1684 | echo $data; | ||
1685 | return; | ||
1686 | } | ||
1687 | } | ||
1688 | } | ||
1689 | |||
1690 | // Otherwise, return an empty image (8x8 transparent gif) | ||
1691 | $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7'); | ||
1692 | file_put_contents(CACHEDIR.'/'.$blankname,$blankgif); // Also put something in cache so that this URL is not requested twice. | ||
1693 | header('Content-Type: image/gif'); | ||
1694 | echo $blankgif; | ||
1695 | } | ||
1696 | |||
1572 | // Invalidate caches when the database is changed or the user logs out. | 1697 | // Invalidate caches when the database is changed or the user logs out. |
1573 | // (eg. tags cache). | 1698 | // (eg. tags cache). |
1574 | function invalidateCaches() | 1699 | function invalidateCaches() |
@@ -1576,6 +1701,7 @@ function invalidateCaches() | |||
1576 | unset($_SESSION['tags']); | 1701 | unset($_SESSION['tags']); |
1577 | } | 1702 | } |
1578 | 1703 | ||
1704 | if (startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. | ||
1579 | $LINKSDB=new linkdb(isLoggedIn() || OPEN_SHAARLI); // Read links from database (and filter private links if used it not logged in). | 1705 | $LINKSDB=new linkdb(isLoggedIn() || OPEN_SHAARLI); // Read links from database (and filter private links if used it not logged in). |
1580 | if (startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI) | 1706 | if (startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI) |
1581 | if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=LINKS_PER_PAGE; | 1707 | if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=LINKS_PER_PAGE; |