Prevented page text content includes

Avoids possible permission issues where included content shown in search or preview
where the user would not normally have permission to view the included content.

Closes #1178
This commit is contained in:
Dan Brown 2019-01-05 17:18:40 +00:00
parent ffceb4092e
commit 0e0a17cc30
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
3 changed files with 47 additions and 23 deletions

View File

@ -599,24 +599,48 @@ class EntityRepo
}
/**
* Render the page for viewing, Parsing and performing features such as page transclusion.
* Render the page for viewing
* @param Page $page
* @param bool $ignorePermissions
* @return mixed|string
* @param bool $blankIncludes
* @return string
*/
public function renderPage(Page $page, $ignorePermissions = false)
public function renderPage(Page $page, bool $blankIncludes = false) : string
{
$content = $page->html;
if (!config('app.allow_content_scripts')) {
$content = $this->escapeScripts($content);
}
$matches = [];
preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
if (count($matches[0]) === 0) {
return $content;
if ($blankIncludes) {
$content = $this->blankPageIncludes($content);
} else {
$content = $this->parsePageIncludes($content);
}
return $content;
}
/**
* Remove any page include tags within the given HTML.
* @param string $html
* @return string
*/
protected function blankPageIncludes(string $html) : string
{
return preg_replace("/{{@\s?([0-9].*?)}}/", '', $html);
}
/**
* Parse any include tags "{{@<page_id>#section}}" to be part of the page.
* @param string $html
* @return mixed|string
*/
protected function parsePageIncludes(string $html) : string
{
$matches = [];
preg_match_all("/{{@\s?([0-9].*?)}}/", $html, $matches);
$topLevelTags = ['table', 'ul', 'ol'];
foreach ($matches[1] as $index => $includeId) {
$splitInclude = explode('#', $includeId, 2);
@ -625,14 +649,14 @@ class EntityRepo
continue;
}
$matchedPage = $this->getById('page', $pageId, false, $ignorePermissions);
$matchedPage = $this->getById('page', $pageId);
if ($matchedPage === null) {
$content = str_replace($matches[0][$index], '', $content);
$html = str_replace($matches[0][$index], '', $html);
continue;
}
if (count($splitInclude) === 1) {
$content = str_replace($matches[0][$index], $matchedPage->html, $content);
$html = str_replace($matches[0][$index], $matchedPage->html, $html);
continue;
}
@ -640,7 +664,7 @@ class EntityRepo
$doc->loadHTML(mb_convert_encoding('<body>'.$matchedPage->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
$matchingElem = $doc->getElementById($splitInclude[1]);
if ($matchingElem === null) {
$content = str_replace($matches[0][$index], '', $content);
$html = str_replace($matches[0][$index], '', $html);
continue;
}
$innerContent = '';
@ -652,25 +676,22 @@ class EntityRepo
$innerContent .= $doc->saveHTML($childNode);
}
}
$content = str_replace($matches[0][$index], trim($innerContent), $content);
$html = str_replace($matches[0][$index], trim($innerContent), $html);
}
return $content;
return $html;
}
/**
* Escape script tags within HTML content.
* @param string $html
* @return mixed
* @return string
*/
protected function escapeScripts(string $html)
protected function escapeScripts(string $html) : string
{
$scriptSearchRegex = '/<script.*?>.*?<\/script>/ms';
$matches = [];
preg_match_all($scriptSearchRegex, $html, $matches);
if (count($matches) === 0) {
return $html;
}
foreach ($matches[0] as $match) {
$html = str_replace($match, htmlentities($match), $html);

View File

@ -194,9 +194,9 @@ class PageRepo extends EntityRepo
* @param \BookStack\Entities\Page $page
* @return string
*/
public function pageToPlainText(Page $page)
protected function pageToPlainText(Page $page) : string
{
$html = $this->renderPage($page);
$html = $this->renderPage($page, true);
return strip_tags($html);
}

View File

@ -40,15 +40,18 @@ class PageContentTest extends TestCase
{
$page = Page::first();
$secondPage = Page::where('id', '!=', $page->id)->first();
$this->asEditor();
$page->html = "<p>{{@$secondPage->id}}</p>";
$includeTag = '{{@' . $secondPage->id . '}}';
$page->html = '<p>' . $includeTag . '</p>';
$resp = $this->put($page->getUrl(), ['name' => $page->name, 'html' => $page->html, 'summary' => '']);
$resp->assertStatus(302);
$page = Page::find($page->id);
$this->assertContains("{{@$secondPage->id}}", $page->html);
$this->assertContains($includeTag, $page->html);
$this->assertEquals('', $page->text);
}
public function test_page_includes_do_not_break_tables()