Limit a story to 64 KB before HTML Purifier runs.
A preview purified any posted story, even without a token or a captcha. A few megabytes took seconds and hundreds of megabytes.
This commit is contained in:
@@ -3,6 +3,13 @@ require_once('open_db.lib.php');
|
|||||||
require_once(__DIR__ . '/lib/purify.inc.php');
|
require_once(__DIR__ . '/lib/purify.inc.php');
|
||||||
require_once(__DIR__ . '/html.lib.php');
|
require_once(__DIR__ . '/html.lib.php');
|
||||||
|
|
||||||
|
// The longest story HTML, in bytes. HTML Purifier needs seconds for a few megabytes.
|
||||||
|
define('MAX_STORY_BYTES', 65536);
|
||||||
|
|
||||||
|
function story_too_long($story) {
|
||||||
|
return strlen((string)$story) > MAX_STORY_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
function display_stories($id = NULL) {
|
function display_stories($id = NULL) {
|
||||||
global $db, $is_admin, $editable, $action;
|
global $db, $is_admin, $editable, $action;
|
||||||
if (is_null($id))
|
if (is_null($id))
|
||||||
@@ -10,17 +17,20 @@ function display_stories($id = NULL) {
|
|||||||
$search = get_search_args();
|
$search = get_search_args();
|
||||||
$page = '';
|
$page = '';
|
||||||
$nolink = NULL;
|
$nolink = NULL;
|
||||||
|
$posted = FALSE;
|
||||||
|
|
||||||
if ($action == 'preview') {
|
if ($action == 'preview') {
|
||||||
global $file_src;
|
global $file_src;
|
||||||
$date = 'Awaiting posting';
|
$date = 'Awaiting posting';
|
||||||
$result = get_post_data(TRUE);
|
$result = get_post_data(TRUE);
|
||||||
$nolink = TRUE;
|
$nolink = TRUE;
|
||||||
|
$posted = TRUE;
|
||||||
} else if ($action == 'submit') {
|
} else if ($action == 'submit') {
|
||||||
$date = 'Posted '.gmdate("Y-m-d H:i");
|
$date = 'Posted '.gmdate("Y-m-d H:i");
|
||||||
if (!$is_admin) {
|
if (!$is_admin) {
|
||||||
$result = get_post_data(TRUE);
|
$result = get_post_data(TRUE);
|
||||||
$nolink = TRUE;
|
$nolink = TRUE;
|
||||||
|
$posted = TRUE;
|
||||||
} else {
|
} else {
|
||||||
$result = get_query($id);
|
$result = get_query($id);
|
||||||
}
|
}
|
||||||
@@ -90,6 +100,8 @@ date("Y-m-d H:i", strtotime($entry['date'])) . '</time>';
|
|||||||
} else {
|
} else {
|
||||||
$page .= '<img src="/display?id='.$entry_id.'" alt="'.$title.'" />';
|
$page .= '<img src="/display?id='.$entry_id.'" alt="'.$title.'" />';
|
||||||
}
|
}
|
||||||
|
} else if ($posted && story_too_long($entry['story'])) {
|
||||||
|
$page .= '<p>The story is too long to show.</p>'."\n";
|
||||||
} else {
|
} else {
|
||||||
$page .= purify_story($entry['story'])."\n";
|
$page .= purify_story($entry['story'])."\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,10 @@ function validate_data() {
|
|||||||
$validate['title'] = "You have not submitted a title.";
|
$validate['title'] = "You have not submitted a title.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (story_too_long($story)) {
|
||||||
|
$validate['story'] = 'The story is too long. Please shorten it.';
|
||||||
|
}
|
||||||
|
|
||||||
if ($remove_img && !$story) {
|
if ($remove_img && !$story) {
|
||||||
$validate['story'] = "You've tried to remove an image without replacing it with text.";
|
$validate['story'] = "You've tried to remove an image without replacing it with text.";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,15 @@ class DisplayStoriesTest extends TestCase {
|
|||||||
$this->assertStringNotContainsString('x()', $page);
|
$this->assertStringNotContainsString('x()', $page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML Purifier needs seconds for a few megabytes, so a posted story over the limit is not shown
|
||||||
|
public function testPreviewDoesNotShowATooLongStory(): void {
|
||||||
|
$GLOBALS['action'] = 'preview';
|
||||||
|
$this->request(array(), array('title' => 'Long', 'author' => 'Me', 'story' => '<p><b>x</b></p>' . str_repeat('a', 65536)));
|
||||||
|
$page = display_stories();
|
||||||
|
$this->assertStringContainsString('<p>The story is too long to show.</p>', $page);
|
||||||
|
$this->assertStringNotContainsString('<b>x</b>', $page);
|
||||||
|
}
|
||||||
|
|
||||||
public function testPreviewShowsAPendingImage(): void {
|
public function testPreviewShowsAPendingImage(): void {
|
||||||
$GLOBALS['action'] = 'preview';
|
$GLOBALS['action'] = 'preview';
|
||||||
$GLOBALS['file_src'] = '/display?pending="><script>';
|
$GLOBALS['file_src'] = '/display?pending="><script>';
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ class ValidateDataTest extends TestCase {
|
|||||||
$this->assertSame(array(), $this->validate(array('remove_img' => '1')));
|
$this->assertSame(array(), $this->validate(array('remove_img' => '1')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testATooLongStoryIsRefused(): void {
|
||||||
|
$this->assertSame(array(), $this->validate(array('story' => str_repeat('a', 65536))));
|
||||||
|
$this->assertSame(array('story' => 'The story is too long. Please shorten it.'),
|
||||||
|
$this->validate(array('story' => str_repeat('a', 65537))));
|
||||||
|
}
|
||||||
|
|
||||||
public function testVisitorsNeedTheCaptcha(): void {
|
public function testVisitorsNeedTheCaptcha(): void {
|
||||||
FakeRecaptcha::install('{"tokenProperties":{"valid":false}}');
|
FakeRecaptcha::install('{"tokenProperties":{"valid":false}}');
|
||||||
$this->assertSame(array('captcha' => 'Please complete the captcha.'), $this->validate(array('g-recaptcha-response' => 'r'), FALSE));
|
$this->assertSame(array('captcha' => 'Please complete the captcha.'), $this->validate(array('g-recaptcha-response' => 'r'), FALSE));
|
||||||
|
|||||||
Reference in New Issue
Block a user