]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #496 from ArthurHoaro/cross-search
authorArthur <arthur@hoa.ro>
Sun, 28 Feb 2016 13:26:46 +0000 (14:26 +0100)
committerArthur <arthur@hoa.ro>
Sun, 28 Feb 2016 13:26:46 +0000 (14:26 +0100)
Allow crossed search between terms and tags

application/Utils.php
inc/shaarli.css
index.php
plugins/markdown/markdown.php
tests/plugins/PluginMarkdownTest.php
tpl/pluginsadmin.html

index 10d606987c826990e94fad0489f36aad66f6a969..3d819716316e78f1a884cb662723668a1529560d 100644 (file)
@@ -62,13 +62,29 @@ function endsWith($haystack, $needle, $case=true)
 }
 
 /**
- * htmlspecialchars wrapper
+ * Htmlspecialchars wrapper
+ *
+ * @param string $str the string to escape.
+ *
+ * @return string escaped.
  */
 function escape($str)
 {
     return htmlspecialchars($str, ENT_COMPAT, 'UTF-8', false);
 }
 
+/**
+ * Reverse the escape function.
+ *
+ * @param string $str the string to unescape.
+ *
+ * @return string unescaped string.
+ */
+function unescape($str)
+{
+    return htmlspecialchars_decode($str);
+}
+
 /**
  * Link sanitization before templating
  */
@@ -213,3 +229,28 @@ function space2nbsp($text)
 function format_description($description, $redirector) {
     return nl2br(space2nbsp(text2clickable($description, $redirector)));
 }
+
+/**
+ * Sniff browser language to set the locale automatically.
+ * Note that is may not work on your server if the corresponding locale is not installed.
+ *
+ * @param string $headerLocale Locale send in HTTP headers (e.g. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3").
+ **/
+function autoLocale($headerLocale)
+{
+    // Default if browser does not send HTTP_ACCEPT_LANGUAGE
+    $attempts = array('en_US');
+    if (isset($headerLocale)) {
+        // (It's a bit crude, but it works very well. Preferred language is always presented first.)
+        if (preg_match('/([a-z]{2})-?([a-z]{2})?/i', $headerLocale, $matches)) {
+            $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
+            $attempts = array(
+                $loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
+                $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
+                $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
+                $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc
+            );
+        }
+    }
+    setlocale(LC_ALL, $attempts);
+}
\ No newline at end of file
index 2e41988e8e0f5a90030dd23494e29f31fc15b4c9..7a9d9afbe5ccbeb605bc7bfb9d50ae60964c52c7 100644 (file)
@@ -1151,6 +1151,10 @@ ul.errors {
     margin: 10px 0;
 }
 
+#pluginsadmin label {
+    cursor: pointer;
+}
+
 #pluginsadmin .plugin_parameter {
     padding: 5px 0;
     border-width: 1px 0;
index c2bec1db39afc5bcc82d5ad8e3b417ffed611643..6712f90ea7e4029d4cc949ac1b341a9d106d3a75 100644 (file)
--- a/index.php
+++ b/index.php
@@ -268,7 +268,7 @@ $GLOBALS['redirector'] = !empty($GLOBALS['redirector']) ? escape($GLOBALS['redir
 // a token depending of deployment salt, user password, and the current ip
 define('STAY_SIGNED_IN_TOKEN', sha1($GLOBALS['hash'].$_SERVER["REMOTE_ADDR"].$GLOBALS['salt']));
 
-autoLocale(); // Sniff browser language and set date format accordingly.
+autoLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']); // Sniff browser language and set date format accordingly.
 header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling.
 
 //==================================================================================================
@@ -315,26 +315,6 @@ function setup_login_state() {
 }
 $userIsLoggedIn = setup_login_state();
 
-
-// ------------------------------------------------------------------------------------------
-// Sniff browser language to display dates in the right format automatically.
-// (Note that is may not work on your server if the corresponding local is not installed.)
-function autoLocale()
-{
-    $attempts = array('en_US'); // Default if browser does not send HTTP_ACCEPT_LANGUAGE
-    if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) // e.g. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3"
-    {   // (It's a bit crude, but it works very well. Preferred language is always presented first.)
-        if (preg_match('/([a-z]{2})-?([a-z]{2})?/i',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches)) {
-            $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
-            $attempts = array($loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
-                $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
-                $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
-                $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc);
-        }
-    }
-    setlocale(LC_TIME, $attempts);  // LC_TIME = Set local for date/time format only.
-}
-
 // ------------------------------------------------------------------------------------------
 // PubSubHubbub protocol support (if enabled)  [UNTESTED]
 // (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ )
@@ -1243,11 +1223,12 @@ function renderPage()
         uksort($tags, function($a, $b) {
             // Collator is part of PHP intl.
             if (class_exists('Collator')) {
-                $c = new Collator(setlocale(LC_ALL, 0));
-                return $c->compare($a, $b);
-            } else {
-                return strcasecmp($a, $b);
+                $c = new Collator(setlocale(LC_COLLATE, 0));
+                if (!intl_is_failure(intl_get_error_code())) {
+                    return $c->compare($a, $b);
+                }
             }
+            return strcasecmp($a, $b);
         });
 
         $tagList=array();
index 3630ef14aa91540a0b06d4c965806fe3390be746..a45b6574c1888d8470933022eaaa4868ce924f7d 100644 (file)
@@ -117,23 +117,43 @@ function reverse_space2nbsp($description)
 }
 
 /**
- * Remove '&gt;' at start of line auto generated by Shaarli core system
- * to allow markdown blockquotes.
+ * Remove dangerous HTML tags (tags, iframe, etc.).
+ * Doesn't affect <code> content (already escaped by Parsedown).
  *
  * @param string $description input description text.
  *
- * @return string $description without HTML links.
+ * @return string given string escaped.
  */
-function reset_quote_tags($description)
+function sanitize_html($description)
 {
-    return preg_replace('/^( *)&gt; /m', '$1> ', $description);
+    $escapeTags = array(
+        'script',
+        'style',
+        'link',
+        'iframe',
+        'frameset',
+        'frame',
+    );
+    foreach ($escapeTags as $tag) {
+        $description = preg_replace_callback(
+            '#<\s*'. $tag .'[^>]*>(.*</\s*'. $tag .'[^>]*>)?#is',
+            function ($match) { return escape($match[0]); },
+            $description);
+    }
+    $description = preg_replace(
+        '#(<[^>]+)on[a-z]*="[^"]*"#is',
+        '$1',
+        $description);
+    return $description;
 }
 
 /**
  * Render shaare contents through Markdown parser.
  *   1. Remove HTML generated by Shaarli core.
- *   2. Generate markdown descriptions.
- *   3. Wrap description in 'markdown' CSS class.
+ *   2. Reverse the escape function.
+ *   3. Generate markdown descriptions.
+ *   4. Sanitize sensible HTML tags for security.
+ *   5. Wrap description in 'markdown' CSS class.
  *
  * @param string $description input description text.
  *
@@ -147,11 +167,12 @@ function process_markdown($description)
     $processedDescription = reverse_text2clickable($processedDescription);
     $processedDescription = reverse_nl2br($processedDescription);
     $processedDescription = reverse_space2nbsp($processedDescription);
-    $processedDescription = reset_quote_tags($processedDescription);
+    $processedDescription = unescape($processedDescription);
     $processedDescription = $parsedown
         ->setMarkupEscaped(false)
         ->setBreaksEnabled(true)
         ->text($processedDescription);
+    $processedDescription = sanitize_html($processedDescription);
     $processedDescription = '<div class="markdown">'. $processedDescription . '</div>';
 
     return $processedDescription;
index 455f5ba7c7d95038b93fdd58af34f76adeea2f52..8e1a128acc6d2b00218611af2f7257fd0ff437a7 100644 (file)
@@ -100,13 +100,18 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     }
 
     /**
-     * Test reset_quote_tags()
+     * Test sanitize_html().
      */
-    function testResetQuoteTags()
-    {
-        $text = '> quote1'. PHP_EOL . ' > quote2 ' . PHP_EOL . 'noquote';
-        $processedText = escape($text);
-        $reversedText = reset_quote_tags($processedText);
-        $this->assertEquals($text, $reversedText);
+    function testSanitizeHtml() {
+        $input = '< script src="js.js"/>';
+        $input .= '< script attr>alert(\'xss\');</script>';
+        $input .= '<style> * { display: none }</style>';
+        $output = escape($input);
+        $input .= '<a href="#" onmouseHover="alert(\'xss\');" attr="tt">link</a>';
+        $output .= '<a href="#"  attr="tt">link</a>';
+        $this->assertEquals($output, sanitize_html($input));
+        // Do not touch escaped HTML.
+        $input = escape($input);
+        $this->assertEquals($input, sanitize_html($input));
     }
 }
index 4f7d091e206961708813c0be63ac32fbfbb63c2c..5ddcf0612cc6bb12d37a9c94b5bf9657598582ea 100644 (file)
@@ -36,7 +36,7 @@
             <tbody>
             {loop="$enabledPlugins"}
               <tr data-line="{$key}" data-order="{$counter}">
-                <td class="center"><input type="checkbox" name="{$key}" checked="checked"></td>
+                <td class="center"><input type="checkbox" name="{$key}" id="{$key}" checked="checked"></td>
                 <td class="center">
                   <a href="#"
                      onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));">
@@ -48,8 +48,8 @@
                   </a>
                   <input type="hidden" name="order_{$key}" value="{$counter}">
                 </td>
-                <td>{$key}</td>
-                <td>{$value.description}</td>
+                <td><label for="{$key}">{function="str_replace('_', ' ', $key)"}</label></td>
+                <td><label for="{$key}">{$value.description}</label></td>
               </tr>
             {/loop}
             </tbody>
@@ -73,9 +73,9 @@
             </tr>
             {loop="$disabledPlugins"}
               <tr>
-                <td class="center"><input type="checkbox" name="{$key}"></td>
-                <td>{$key}</td>
-                <td>{$value.description}</td>
+                <td class="center"><input type="checkbox" name="{$key}" id="{$key}"></td>
+                <td><label for="{$key}">{function="str_replace('_', ' ', $key)"}</label></td>
+                <td><label for="{$key}">{$value.description}</label></td>
               </tr>
             {/loop}
           </table>
@@ -99,7 +99,7 @@
           {loop="$enabledPlugins"}
             {if="count($value.parameters) > 0"}
               <div class="plugin_parameters">
-                <h2>{$key}</h2>
+                <h2>{function="str_replace('_', ' ', $key)"}</h2>
                 {loop="$value.parameters"}
                   <div class="plugin_parameter">
                     <div class="float_label">
 
 <script src="inc/plugin_admin.js#"></script>
 </body>
-</html>
\ No newline at end of file
+</html>