diff options
Diffstat (limited to 'application')
-rw-r--r-- | application/ApplicationUtils.php | 20 | ||||
-rw-r--r-- | application/FileUtils.php | 26 | ||||
-rw-r--r-- | application/HttpUtils.php | 28 | ||||
-rw-r--r-- | application/LinkDB.php | 2 | ||||
-rw-r--r-- | application/LinkFilter.php | 131 | ||||
-rw-r--r-- | application/LinkUtils.php | 2 | ||||
-rw-r--r-- | application/PageBuilder.php | 9 | ||||
-rw-r--r-- | application/ThemeUtils.php | 1 | ||||
-rw-r--r-- | application/Updater.php | 4 | ||||
-rw-r--r-- | application/config/ConfigManager.php | 8 |
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 | */ | ||
413 | function 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 | ||
255 | To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page. | 255 | To 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 | */ |
110 | function text2clickable($text, $redirector = '') | 110 | function 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 | */ |
15 | class ConfigManager | 15 | class 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); |