Skip to content

Commit 817db2f

Browse files
committed
[ticket/13880] Automatically remove quotes that are nested too deep
PHPBB3-13880
1 parent 7d7b536 commit 817db2f

3 files changed

Lines changed: 142 additions & 23 deletions

File tree

phpBB/includes/message_parser.php

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -791,28 +791,6 @@ function bbcode_quote($in)
791791
else if (preg_match('#^quote(?:="(.*?)")?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
792792
{
793793
$this->parsed_items['quote']++;
794-
795-
// the buffer holds a valid opening tag
796-
if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])
797-
{
798-
if ($config['max_quote_depth'] == 1)
799-
{
800-
// Depth 1 - no nesting is allowed
801-
$error_ary['quote_depth'] = $user->lang('QUOTE_NO_NESTING');
802-
}
803-
else
804-
{
805-
// There are too many nested quotes
806-
$error_ary['quote_depth'] = $user->lang('QUOTE_DEPTH_EXCEEDED', (int) $config['max_quote_depth']);
807-
}
808-
809-
$out .= $buffer . $tok;
810-
$tok = '[]';
811-
$buffer = '';
812-
813-
continue;
814-
}
815-
816794
array_push($close_tags, '/quote:' . $this->bbcode_uid);
817795

818796
if (isset($m[1]) && $m[1])
@@ -1277,6 +1255,12 @@ function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcod
12771255
return $update_this_message ? $this->warn_msg : $return_message;
12781256
}
12791257

1258+
// Remove quotes that are nested too deep
1259+
if ($config['max_quote_depth'] > 0)
1260+
{
1261+
$this->remove_nested_quotes($config['max_quote_depth']);
1262+
}
1263+
12801264
// Check for "empty" message. We do not check here for maximum length, because bbcode, smilies, etc. can add to the length.
12811265
// The maximum length check happened before any parsings.
12821266
if ($mode === 'post' && utf8_clean_string($this->message) === '')
@@ -1855,6 +1839,50 @@ function parse_poll(&$poll)
18551839
$poll['poll_max_options'] = ($poll['poll_max_options'] < 1) ? 1 : (($poll['poll_max_options'] > $config['max_poll_options']) ? $config['max_poll_options'] : $poll['poll_max_options']);
18561840
}
18571841

1842+
/**
1843+
* Remove nested quotes at given depth in current parsed message
1844+
*
1845+
* @param integer $max_depth Depth limit
1846+
* @return null
1847+
*/
1848+
public function remove_nested_quotes($max_depth)
1849+
{
1850+
// Capture all [quote] and [/quote] tags
1851+
preg_match_all('(\\[/?quote(?:=[^]]+)?:' . $this->bbcode_uid . '\\])', $this->message, $matches, PREG_OFFSET_CAPTURE);
1852+
1853+
// Iterate over the quote tags to mark the ranges that must be removed
1854+
$depth = 0;
1855+
$ranges = array();
1856+
$start_pos = 0;
1857+
foreach ($matches[0] as $match)
1858+
{
1859+
if ($match[0][1] === '/')
1860+
{
1861+
--$depth;
1862+
if ($depth == $max_depth)
1863+
{
1864+
$end_pos = $match[1] + strlen($match[0]);
1865+
$length = $end_pos - $start_pos;
1866+
$ranges[] = array($start_pos, $length);
1867+
}
1868+
}
1869+
else
1870+
{
1871+
++$depth;
1872+
if ($depth == $max_depth + 1)
1873+
{
1874+
$start_pos = $match[1];
1875+
}
1876+
}
1877+
}
1878+
1879+
foreach (array_reverse($ranges) as $range)
1880+
{
1881+
list($start_pos, $length) = $range;
1882+
$this->message = substr_replace($this->message, '', $start_pos, $length);
1883+
}
1884+
}
1885+
18581886
/**
18591887
* Setter function for passing the plupload object
18601888
*

phpBB/posting.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1579,11 +1579,21 @@
15791579
}
15801580
}
15811581

1582+
// Remove quotes that would become nested too deep before decoding the text
1583+
$generate_quote = ($mode == 'quote' && !$submit && !$preview && !$refresh);
1584+
if ($generate_quote && $config['max_quote_depth'] > 0)
1585+
{
1586+
$tmp_bbcode_uid = $message_parser->bbcode_uid;
1587+
$message_parser->bbcode_uid = $post_data['bbcode_uid'];
1588+
$message_parser->remove_nested_quotes($config['max_quote_depth'] - 1);
1589+
$message_parser->bbcode_uid = $tmp_bbcode_uid;
1590+
}
1591+
15821592
// Decode text for message display
15831593
$post_data['bbcode_uid'] = ($mode == 'quote' && !$preview && !$refresh && !sizeof($error)) ? $post_data['bbcode_uid'] : $message_parser->bbcode_uid;
15841594
$message_parser->decode_message($post_data['bbcode_uid']);
15851595

1586-
if ($mode == 'quote' && !$submit && !$preview && !$refresh)
1596+
if ($generate_quote)
15871597
{
15881598
if ($config['allow_bbcode'])
15891599
{

tests/functional/posting_test.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,85 @@ public function test_unsupported_characters()
5959
'Your subject contains the following unsupported characters'
6060
);
6161
}
62+
63+
/**
64+
* @testdox max_quote_depth is applied to the text populating the posting form
65+
*/
66+
public function test_quote_depth_form()
67+
{
68+
$text = '0[quote]1[quote]2[/quote]1[/quote]0';
69+
$expected = array(
70+
0 => '[quote="admin"]0[quote]1[quote]2[/quote]1[/quote]0[/quote]',
71+
1 => '[quote="admin"]00[/quote]',
72+
2 => '[quote="admin"]0[quote]11[/quote]0[/quote]',
73+
3 => '[quote="admin"]0[quote]1[quote]2[/quote]1[/quote]0[/quote]',
74+
);
75+
76+
$this->login();
77+
$topic = $this->create_topic(2, 'Test Topic 1', 'Test topic');
78+
$post = $this->create_post(2, $topic['topic_id'], 'Re: Test Topic 1', $text);
79+
$quote_url = "posting.php?mode=quote&f=2&t={$post['topic_id']}&p={$post['post_id']}&sid={$this->sid}";
80+
81+
$this->admin_login();
82+
foreach ($expected as $quote_depth => $expected_text)
83+
{
84+
$this->set_quote_depth($quote_depth);
85+
$crawler = self::request('GET', $quote_url);
86+
$this->assertContains($expected_text, $crawler->filter('textarea#message')->text());
87+
}
88+
}
89+
90+
/**
91+
* @testdox max_quote_depth is applied to the submitted text
92+
*/
93+
public function test_quote_depth_submit()
94+
{
95+
$text = 'depth:0[quote]depth:1[quote]depth:2[quote]depth:3[/quote][/quote][/quote]';
96+
$contains = array(
97+
0 => array('depth:0', 'depth:1', 'depth:2', 'depth:3'),
98+
1 => array('depth:0', 'depth:1'),
99+
2 => array('depth:0', 'depth:1', 'depth:2'),
100+
3 => array('depth:0', 'depth:1', 'depth:2', 'depth:3'),
101+
);
102+
$not_contains = array(
103+
0 => array(),
104+
1 => array('depth:2', 'depth:3'),
105+
2 => array('depth:3'),
106+
3 => array(),
107+
);
108+
109+
$this->login();
110+
$this->admin_login();
111+
$topic = $this->create_topic(2, 'Test Topic 1', 'Test topic');
112+
113+
for ($quote_depth = 0; $quote_depth <= 2; ++$quote_depth)
114+
{
115+
$this->set_quote_depth($quote_depth);
116+
117+
$post = $this->create_post(2, $topic['topic_id'], 'Re: Test Topic 1', $text);
118+
$url = "viewtopic.php?p={$post['post_id']}&sid={$this->sid}";
119+
120+
$crawler = self::request('GET', $url);
121+
$text_content = $crawler->filter('#p' . $post['post_id'])->text();
122+
foreach ($contains[$quote_depth] as $contains_text)
123+
{
124+
$this->assertContains($contains_text, $text_content);
125+
}
126+
foreach ($not_contains[$quote_depth] as $not_contains_text)
127+
{
128+
$this->assertNotContains($not_contains_text, $text_content);
129+
}
130+
}
131+
}
132+
133+
protected function set_quote_depth($depth)
134+
{
135+
$crawler = self::request('GET', 'adm/index.php?sid=' . $this->sid . '&i=acp_board&mode=post');
136+
$form = $crawler->selectButton('Submit')->form();
137+
$values = $form->getValues();
138+
$values['config[max_quote_depth]'] = $depth;
139+
$form->setValues($values);
140+
$crawler = self::submit($form);
141+
$this->assertEquals(1, $crawler->filter('.successbox')->count());
142+
}
62143
}

0 commit comments

Comments
 (0)