From 05dbe8a777b31044ddb82c3b5b69c1cf5a86adec Mon Sep 17 00:00:00 2001
From: Christoph Burschka <christoph@burschka.de>
Date: Sun, 13 Dec 2015 10:48:42 +0100
Subject: [PATCH] RegEx fix: Escape quotes in unquoted values.

Unquoted values must escape *all* quotes, to ensure that
they are not mistaken for quoted values if the same quotes are
at the beginning and end of the value.

This patch also simplifies the regular expression, to replace
/([^\\\\]|(\\\\\\\\)*\\\\)*"/ with /([^\\\\"]|\\\\[\\\\"])*/

Bugfix in Element, where escaped spaces in unquoted values were
not actually unescaped after parsing.
---
 src/Element.php                     | 12 ++++++++----
 src/Plugin/Filter/XBBCodeFilter.php |  2 +-
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/Element.php b/src/Element.php
index d433899..3d9edf2 100644
--- a/src/Element.php
+++ b/src/Element.php
@@ -13,7 +13,7 @@ use Drupal\xbbcode\Plugin\TagPluginInterface;
  * A node in the tag tree.
  */
 class Element implements ElementInterface {
-  const RE_ATTR = '/(?<=\s)(?<key>\w+)=(?:\'(?<val1>(?:[^\\\\\']|[^\\\\](?:\\\\\\\\)*\\\\\')*)\'|\"(?<val2>(?:[^\\\\\"]|[^\\\\](?:\\\\\\\\)*\\\\\")*)\"|(?=[^\'"\s])(?<val3>(?:[^\\\\\s]|(?:\\\\\\\\)*\\\\\s)*)(?=\s|$))(?=\s|$)/';
+  const RE_ATTR = '/(?<=\s)(?<key>\w+)=(?:\'(?<val1>(?:[^\\\\\']|\\\\[\\\\\'])*)\'|\"(?<val2>(?:[^\\\\\"]|\\\\[\\\\\"])*)\"|(?=[^\'"\s])(?<val3>(?:[^\\\\\'\"\s]|\\\\[\\\\\'\"\s])*)(?=\s|$))(?=\s|$)/';
 
   private $name;
   private $extra;
@@ -66,14 +66,18 @@ class Element implements ElementInterface {
     preg_match_all(self::RE_ATTR, $string, $assignments, PREG_SET_ORDER);
     $attrs = [];
     foreach ($assignments as $assignment) {
+      // Strip backslashes from the escape sequences in each case.
       if (!empty($assignment['val1'])) {
-        $value = preg_replace('/\\\\([\'\\\\])/', '\1', $assignment['val1']);
+        // Single-quoted values escape single quotes and backslashes.
+        $value = preg_replace('/\\\\([\\\\\'])/', '\1', $assignment['val1']);
       }
       elseif (!empty($assignment['val2'])) {
-        $value = preg_replace('/\\\\(["\\\\])/', '\1', $assignment['val2']);
+        // Double-quoted values escape double quotes and backslashes.
+        $value = preg_replace('/\\\\([\\\\\"])/', '\1', $assignment['val2']);
       }
       else {
-        $value = preg_replace('/\\\\([\\]\\\\])/', '\1', $assignment['val3']);
+        // Unquoted values must escape all quotes, whitespace and backslashes.
+        $value = preg_replace('/\\\\([\\\\\'\"\s])/', '\1', $assignment['val3']);
       }
       $attrs[$assignment['key']] = $value;
     }
diff --git a/src/Plugin/Filter/XBBCodeFilter.php b/src/Plugin/Filter/XBBCodeFilter.php
index 42eebf7..1b2de2f 100644
--- a/src/Plugin/Filter/XBBCodeFilter.php
+++ b/src/Plugin/Filter/XBBCodeFilter.php
@@ -51,7 +51,7 @@ class XBBCodeFilter extends FilterBase {
   private $tags = [];
   private $tagCollection;
 
-  const RE_TAG = '/\[(?<closing>\/)(?<name1>[a-z0-9_]+)\]|\[(?<name2>[a-z0-9_]+)(?<extra>(?<attr>(?:\s+(?<key>\w+)=(?:\'(?<val1>(?:[^\\\\\']|[^\\\\](?:\\\\\\\\)*\\\\\')*)\'|\"(?<val2>(?:[^\\\\\"]|[^\\\\](?:\\\\\\\\)*\\\\\")*)\"|(?=[^\'"\s])(?<val3>(?:[^\\\\\s]|(?:\\\\\\\\)*\\\\\s)*)(?=\s|\]))(?=\s|\]))*)|=(?<option>(?:[^\\\\\]]|(?:\\\\\\\\)*\\\\\])*))\]/';
+  const RE_TAG = '/\[(?<closing>\/)(?<name1>[a-z0-9_]+)\]|\[(?<name2>[a-z0-9_]+)(?<extra>(?<attr>(?:\s+(?<key>\w+)=(?:\'(?<val1>(?:[^\\\\\']|\\\\[\\\\\'])*)\'|\"(?<val2>(?:[^\\\\\"]|\\\\[\\\\\"])*)\"|(?=[^\'"\s])(?<val3>(?:[^\\\\\'\"\s]|\\\\[\\\\\s\'\"])*)(?=\s|\]))(?=\s|\]))*)|=(?<option>(?:[^\\\\\]]|\\\\[\\\\\]])*))\]/';
   const RE_INTERNAL = '/\[(?<closing>\/)xbbcode:(?<name1>[a-z0-9_]+)\]|\[xbbcode:(?<name2>[a-z0-9_]+):(?<extra>[A-Za-z0-9+\/]*=*):(?<start>\d+):(?<end>\d+)\]/';
 
   /**
-- 
1.9.1

