aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
Diffstat (limited to 'application')
-rw-r--r--application/ApplicationUtils.php20
-rw-r--r--application/FileUtils.php26
-rw-r--r--application/HttpUtils.php28
-rw-r--r--application/LinkDB.php2
-rw-r--r--application/LinkFilter.php131
-rw-r--r--application/LinkUtils.php2
-rw-r--r--application/PageBuilder.php9
-rw-r--r--application/ThemeUtils.php1
-rw-r--r--application/Updater.php4
-rw-r--r--application/config/ConfigManager.php8
10 files changed, 164 insertions, 67 deletions
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 85dcbeeb..5643f4a0 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -168,14 +168,15 @@ class ApplicationUtils
168 public static function checkResourcePermissions($conf) 168 public static function checkResourcePermissions($conf)
169 { 169 {
170 $errors = array(); 170 $errors = array();
171 $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/');
171 172
172 // Check script and template directories are readable 173 // Check script and template directories are readable
173 foreach (array( 174 foreach (array(
174 'application', 175 'application',
175 'inc', 176 'inc',
176 'plugins', 177 'plugins',
177 $conf->get('resource.raintpl_tpl'), 178 $rainTplDir,
178 $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme'), 179 $rainTplDir.'/'.$conf->get('resource.theme'),
179 ) as $path) { 180 ) as $path) {
180 if (! is_readable(realpath($path))) { 181 if (! is_readable(realpath($path))) {
181 $errors[] = '"'.$path.'" directory is not readable'; 182 $errors[] = '"'.$path.'" directory is not readable';
@@ -220,4 +221,19 @@ class ApplicationUtils
220 221
221 return $errors; 222 return $errors;
222 } 223 }
224
225 /**
226 * Returns a salted hash representing the current Shaarli version.
227 *
228 * Useful for assets browser cache.
229 *
230 * @param string $currentVersion of Shaarli
231 * @param string $salt User personal salt, also used for the authentication
232 *
233 * @return string version hash
234 */
235 public static function getVersionHash($currentVersion, $salt)
236 {
237 return hash_hmac('sha256', $currentVersion, $salt);
238 }
223} 239}
diff --git a/application/FileUtils.php b/application/FileUtils.php
index a167f642..918cb83b 100644
--- a/application/FileUtils.php
+++ b/application/FileUtils.php
@@ -50,7 +50,8 @@ class FileUtils
50 50
51 /** 51 /**
52 * Read data from a file containing Shaarli database format content. 52 * Read data from a file containing Shaarli database format content.
53 * If the file isn't readable or doesn't exists, default data will be returned. 53 *
54 * If the file isn't readable or doesn't exist, default data will be returned.
54 * 55 *
55 * @param string $file File path. 56 * @param string $file File path.
56 * @param mixed $default The default value to return if the file isn't readable. 57 * @param mixed $default The default value to return if the file isn't readable.
@@ -61,16 +62,21 @@ class FileUtils
61 { 62 {
62 // Note that gzinflate is faster than gzuncompress. 63 // Note that gzinflate is faster than gzuncompress.
63 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 64 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
64 if (is_readable($file)) { 65 if (! is_readable($file)) {
65 return unserialize( 66 return $default;
66 gzinflate( 67 }
67 base64_decode( 68
68 substr(file_get_contents($file), strlen(self::$phpPrefix), -strlen(self::$phpSuffix)) 69 $data = file_get_contents($file);
69 ) 70 if ($data == '') {
70 ) 71 return $default;
71 );
72 } 72 }
73 73
74 return $default; 74 return unserialize(
75 gzinflate(
76 base64_decode(
77 substr($data, strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
78 )
79 )
80 );
75 } 81 }
76} 82}
diff --git a/application/HttpUtils.php b/application/HttpUtils.php
index 88a1efdb..00835966 100644
--- a/application/HttpUtils.php
+++ b/application/HttpUtils.php
@@ -401,3 +401,31 @@ function getIpAddressFromProxy($server, $trustedIps)
401 401
402 return array_pop($ips); 402 return array_pop($ips);
403} 403}
404
405/**
406 * Returns true if Shaarli's currently browsed in HTTPS.
407 * Supports reverse proxies (if the headers are correctly set).
408 *
409 * @param array $server $_SERVER.
410 *
411 * @return bool true if HTTPS, false otherwise.
412 */
413function is_https($server)
414{
415
416 if (isset($server['HTTP_X_FORWARDED_PORT'])) {
417 // Keep forwarded port
418 if (strpos($server['HTTP_X_FORWARDED_PORT'], ',') !== false) {
419 $ports = explode(',', $server['HTTP_X_FORWARDED_PORT']);
420 $port = trim($ports[0]);
421 } else {
422 $port = $server['HTTP_X_FORWARDED_PORT'];
423 }
424
425 if ($port == '443') {
426 return true;
427 }
428 }
429
430 return ! empty($server['HTTPS']);
431}
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 9308164a..22c1f0ab 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -249,7 +249,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
249 $link = array( 249 $link = array(
250 'id' => 1, 250 'id' => 1,
251 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 251 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
252 'url'=>'https://github.com/shaarli/Shaarli/wiki', 252 'url'=>'https://shaarli.readthedocs.io',
253 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. 253 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
254 254
255To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page. 255To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page.
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index 95519528..99ecd1e2 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -250,6 +250,51 @@ class LinkFilter
250 } 250 }
251 251
252 /** 252 /**
253 * generate a regex fragment out of a tag
254 * @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
255 * @return string generated regex fragment
256 */
257 private static function tag2regex($tag)
258 {
259 $len = strlen($tag);
260 if(!$len || $tag === "-" || $tag === "*"){
261 // nothing to search, return empty regex
262 return '';
263 }
264 if($tag[0] === "-") {
265 // query is negated
266 $i = 1; // use offset to start after '-' character
267 $regex = '(?!'; // create negative lookahead
268 } else {
269 $i = 0; // start at first character
270 $regex = '(?='; // use positive lookahead
271 }
272 $regex .= '.*(?:^| )'; // before tag may only be a space or the beginning
273 // iterate over string, separating it into placeholder and content
274 for(; $i < $len; $i++){
275 if($tag[$i] === '*'){
276 // placeholder found
277 $regex .= '[^ ]*?';
278 } else {
279 // regular characters
280 $offset = strpos($tag, '*', $i);
281 if($offset === false){
282 // no placeholder found, set offset to end of string
283 $offset = $len;
284 }
285 // subtract one, as we want to get before the placeholder or end of string
286 $offset -= 1;
287 // we got a tag name that we want to search for. escape any regex characters to prevent conflicts.
288 $regex .= preg_quote(substr($tag, $i, $offset - $i + 1), '/');
289 // move $i on
290 $i = $offset;
291 }
292 }
293 $regex .= '(?:$| ))'; // after the tag may only be a space or the end
294 return $regex;
295 }
296
297 /**
253 * Returns the list of links associated with a given list of tags 298 * Returns the list of links associated with a given list of tags
254 * 299 *
255 * You can specify one or more tags, separated by space or a comma, e.g. 300 * You can specify one or more tags, separated by space or a comma, e.g.
@@ -263,20 +308,32 @@ class LinkFilter
263 */ 308 */
264 public function filterTags($tags, $casesensitive = false, $visibility = 'all') 309 public function filterTags($tags, $casesensitive = false, $visibility = 'all')
265 { 310 {
266 // Implode if array for clean up. 311 // get single tags (we may get passed an array, even though the docs say different)
267 $tags = is_array($tags) ? trim(implode(' ', $tags)) : $tags; 312 $inputTags = $tags;
268 if (empty($tags)) { 313 if(!is_array($tags)) {
314 // we got an input string, split tags
315 $inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY);
316 }
317
318 if(!count($inputTags)){
319 // no input tags
269 return $this->noFilter($visibility); 320 return $this->noFilter($visibility);
270 } 321 }
271 322
272 $searchtags = self::tagsStrToArray($tags, $casesensitive); 323 // build regex from all tags
273 $filtered = array(); 324 $re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/';
274 if (empty($searchtags)) { 325 if(!$casesensitive) {
275 return $filtered; 326 // make regex case insensitive
327 $re .= 'i';
276 } 328 }
277 329
330 // create resulting array
331 $filtered = array();
332
333 // iterate over each link
278 foreach ($this->links as $key => $link) { 334 foreach ($this->links as $key => $link) {
279 // ignore non private links when 'privatonly' is on. 335 // check level of visibility
336 // ignore non private links when 'privateonly' is on.
280 if ($visibility !== 'all') { 337 if ($visibility !== 'all') {
281 if (! $link['private'] && $visibility === 'private') { 338 if (! $link['private'] && $visibility === 'private') {
282 continue; 339 continue;
@@ -284,25 +341,27 @@ class LinkFilter
284 continue; 341 continue;
285 } 342 }
286 } 343 }
287 344 $search = $link['tags']; // build search string, start with tags of current link
288 $linktags = self::tagsStrToArray($link['tags'], $casesensitive); 345 if(strlen(trim($link['description'])) && strpos($link['description'], '#') !== false){
289 346 // description given and at least one possible tag found
290 $found = true; 347 $descTags = array();
291 for ($i = 0 ; $i < count($searchtags) && $found; $i++) { 348 // find all tags in the form of #tag in the description
292 // Exclusive search, quit if tag found. 349 preg_match_all(
293 // Or, tag not found in the link, quit. 350 '/(?<![' . self::$HASHTAG_CHARS . '])#([' . self::$HASHTAG_CHARS . ']+?)\b/sm',
294 if (($searchtags[$i][0] == '-' 351 $link['description'],
295 && $this->searchTagAndHashTag(substr($searchtags[$i], 1), $linktags, $link['description'])) 352 $descTags
296 || ($searchtags[$i][0] != '-') 353 );
297 && ! $this->searchTagAndHashTag($searchtags[$i], $linktags, $link['description']) 354 if(count($descTags[1])){
298 ) { 355 // there were some tags in the description, add them to the search string
299 $found = false; 356 $search .= ' ' . implode(' ', $descTags[1]);
300 } 357 }
358 };
359 // match regular expression with search string
360 if(!preg_match($re, $search)){
361 // this entry does _not_ match our regex
362 continue;
301 } 363 }
302 364 $filtered[$key] = $link;
303 if ($found) {
304 $filtered[$key] = $link;
305 }
306 } 365 }
307 return $filtered; 366 return $filtered;
308 } 367 }
@@ -364,28 +423,6 @@ class LinkFilter
364 } 423 }
365 424
366 /** 425 /**
367 * Check if a tag is found in the taglist, or as an hashtag in the link description.
368 *
369 * @param string $tag Tag to search.
370 * @param array $taglist List of tags for the current link.
371 * @param string $description Link description.
372 *
373 * @return bool True if found, false otherwise.
374 */
375 protected function searchTagAndHashTag($tag, $taglist, $description)
376 {
377 if (in_array($tag, $taglist)) {
378 return true;
379 }
380
381 if (preg_match('/(^| )#'. $tag .'([^'. self::$HASHTAG_CHARS .']|$)/mui', $description) > 0) {
382 return true;
383 }
384
385 return false;
386 }
387
388 /**
389 * Convert a list of tags (str) to an array. Also 426 * Convert a list of tags (str) to an array. Also
390 * - handle case sensitivity. 427 * - handle case sensitivity.
391 * - accepts spaces commas as separator. 428 * - accepts spaces commas as separator.
diff --git a/application/LinkUtils.php b/application/LinkUtils.php
index 976474de..267e62cd 100644
--- a/application/LinkUtils.php
+++ b/application/LinkUtils.php
@@ -109,7 +109,7 @@ function count_private($links)
109 */ 109 */
110function text2clickable($text, $redirector = '') 110function text2clickable($text, $redirector = '')
111{ 111{
112 $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si'; 112 $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si';
113 113
114 if (empty($redirector)) { 114 if (empty($redirector)) {
115 return preg_replace($regex, '<a href="$1">$1</a>', $text); 115 return preg_replace($regex, '<a href="$1">$1</a>', $text);
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 7a42400d..291860ad 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -49,7 +49,7 @@ class PageBuilder
49 49
50 try { 50 try {
51 $version = ApplicationUtils::checkUpdate( 51 $version = ApplicationUtils::checkUpdate(
52 shaarli_version, 52 SHAARLI_VERSION,
53 $this->conf->get('resource.update_check'), 53 $this->conf->get('resource.update_check'),
54 $this->conf->get('updates.check_updates_interval'), 54 $this->conf->get('updates.check_updates_interval'),
55 $this->conf->get('updates.check_updates'), 55 $this->conf->get('updates.check_updates'),
@@ -75,7 +75,11 @@ class PageBuilder
75 } 75 }
76 $this->tpl->assign('searchcrits', $searchcrits); 76 $this->tpl->assign('searchcrits', $searchcrits);
77 $this->tpl->assign('source', index_url($_SERVER)); 77 $this->tpl->assign('source', index_url($_SERVER));
78 $this->tpl->assign('version', shaarli_version); 78 $this->tpl->assign('version', SHAARLI_VERSION);
79 $this->tpl->assign(
80 'version_hash',
81 ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
82 );
79 $this->tpl->assign('scripturl', index_url($_SERVER)); 83 $this->tpl->assign('scripturl', index_url($_SERVER));
80 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? 84 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
81 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly'])); 85 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
@@ -89,6 +93,7 @@ class PageBuilder
89 $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); 93 $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss');
90 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); 94 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
91 $this->tpl->assign('token', getToken($this->conf)); 95 $this->tpl->assign('token', getToken($this->conf));
96
92 if ($this->linkDB !== null) { 97 if ($this->linkDB !== null) {
93 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); 98 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
94 } 99 }
diff --git a/application/ThemeUtils.php b/application/ThemeUtils.php
index 2718ed13..16f2f6a2 100644
--- a/application/ThemeUtils.php
+++ b/application/ThemeUtils.php
@@ -22,6 +22,7 @@ class ThemeUtils
22 */ 22 */
23 public static function getThemes($tplDir) 23 public static function getThemes($tplDir)
24 { 24 {
25 $tplDir = rtrim($tplDir, '/');
25 $allTheme = glob($tplDir.'/*', GLOB_ONLYDIR); 26 $allTheme = glob($tplDir.'/*', GLOB_ONLYDIR);
26 $themes = []; 27 $themes = [];
27 foreach ($allTheme as $value) { 28 foreach ($allTheme as $value) {
diff --git a/application/Updater.php b/application/Updater.php
index 40a15906..72b2def0 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -398,7 +398,7 @@ class Updater
398 */ 398 */
399 public function updateMethodCheckUpdateRemoteBranch() 399 public function updateMethodCheckUpdateRemoteBranch()
400 { 400 {
401 if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { 401 if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
402 return true; 402 return true;
403 } 403 }
404 404
@@ -413,7 +413,7 @@ class Updater
413 $latestMajor = $matches[1]; 413 $latestMajor = $matches[1];
414 414
415 // Get current major version digit 415 // Get current major version digit
416 preg_match('/(\d+)\.\d+$/', shaarli_version, $matches); 416 preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches);
417 $currentMajor = $matches[1]; 417 $currentMajor = $matches[1];
418 418
419 if ($currentMajor === $latestMajor) { 419 if ($currentMajor === $latestMajor) {
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 8eab26f1..7ff2fe67 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -9,8 +9,8 @@ use Shaarli\Config\Exception\UnauthorizedConfigException;
9 * 9 *
10 * Manages all Shaarli's settings. 10 * Manages all Shaarli's settings.
11 * See the documentation for more information on settings: 11 * See the documentation for more information on settings:
12 * - doc/Shaarli-configuration.html 12 * - doc/md/Shaarli-configuration.md
13 * - https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration 13 * - https://shaarli.readthedocs.io/en/master/Shaarli-configuration/#configuration
14 */ 14 */
15class ConfigManager 15class ConfigManager
16{ 16{
@@ -317,6 +317,7 @@ class ConfigManager
317 $this->setEmpty('general.header_link', '?'); 317 $this->setEmpty('general.header_link', '?');
318 $this->setEmpty('general.links_per_page', 20); 318 $this->setEmpty('general.links_per_page', 20);
319 $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS); 319 $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS);
320 $this->setEmpty('general.default_note_title', 'Note: ');
320 321
321 $this->setEmpty('updates.check_updates', false); 322 $this->setEmpty('updates.check_updates', false);
322 $this->setEmpty('updates.check_updates_branch', 'stable'); 323 $this->setEmpty('updates.check_updates_branch', 'stable');
@@ -327,7 +328,10 @@ class ConfigManager
327 328
328 $this->setEmpty('privacy.default_private_links', false); 329 $this->setEmpty('privacy.default_private_links', false);
329 $this->setEmpty('privacy.hide_public_links', false); 330 $this->setEmpty('privacy.hide_public_links', false);
331 $this->setEmpty('privacy.force_login', false);
330 $this->setEmpty('privacy.hide_timestamps', false); 332 $this->setEmpty('privacy.hide_timestamps', false);
333 // default state of the 'remember me' checkbox of the login form
334 $this->setEmpty('privacy.remember_user_default', true);
331 335
332 $this->setEmpty('thumbnail.enable_thumbnails', true); 336 $this->setEmpty('thumbnail.enable_thumbnails', true);
333 $this->setEmpty('thumbnail.enable_localcache', true); 337 $this->setEmpty('thumbnail.enable_localcache', true);