mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-08-26 20:22:18 +00:00
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:
parent
ffceb4092e
commit
0e0a17cc30
@ -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 Page $page
|
||||||
* @param bool $ignorePermissions
|
* @param bool $blankIncludes
|
||||||
* @return mixed|string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function renderPage(Page $page, $ignorePermissions = false)
|
public function renderPage(Page $page, bool $blankIncludes = false) : string
|
||||||
{
|
{
|
||||||
$content = $page->html;
|
$content = $page->html;
|
||||||
|
|
||||||
if (!config('app.allow_content_scripts')) {
|
if (!config('app.allow_content_scripts')) {
|
||||||
$content = $this->escapeScripts($content);
|
$content = $this->escapeScripts($content);
|
||||||
}
|
}
|
||||||
|
|
||||||
$matches = [];
|
if ($blankIncludes) {
|
||||||
preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
|
$content = $this->blankPageIncludes($content);
|
||||||
if (count($matches[0]) === 0) {
|
} else {
|
||||||
|
$content = $this->parsePageIncludes($content);
|
||||||
|
}
|
||||||
|
|
||||||
return $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'];
|
$topLevelTags = ['table', 'ul', 'ol'];
|
||||||
foreach ($matches[1] as $index => $includeId) {
|
foreach ($matches[1] as $index => $includeId) {
|
||||||
$splitInclude = explode('#', $includeId, 2);
|
$splitInclude = explode('#', $includeId, 2);
|
||||||
@ -625,14 +649,14 @@ class EntityRepo
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$matchedPage = $this->getById('page', $pageId, false, $ignorePermissions);
|
$matchedPage = $this->getById('page', $pageId);
|
||||||
if ($matchedPage === null) {
|
if ($matchedPage === null) {
|
||||||
$content = str_replace($matches[0][$index], '', $content);
|
$html = str_replace($matches[0][$index], '', $html);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($splitInclude) === 1) {
|
if (count($splitInclude) === 1) {
|
||||||
$content = str_replace($matches[0][$index], $matchedPage->html, $content);
|
$html = str_replace($matches[0][$index], $matchedPage->html, $html);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -640,7 +664,7 @@ class EntityRepo
|
|||||||
$doc->loadHTML(mb_convert_encoding('<body>'.$matchedPage->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
|
$doc->loadHTML(mb_convert_encoding('<body>'.$matchedPage->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
|
||||||
$matchingElem = $doc->getElementById($splitInclude[1]);
|
$matchingElem = $doc->getElementById($splitInclude[1]);
|
||||||
if ($matchingElem === null) {
|
if ($matchingElem === null) {
|
||||||
$content = str_replace($matches[0][$index], '', $content);
|
$html = str_replace($matches[0][$index], '', $html);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$innerContent = '';
|
$innerContent = '';
|
||||||
@ -652,25 +676,22 @@ class EntityRepo
|
|||||||
$innerContent .= $doc->saveHTML($childNode);
|
$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.
|
* Escape script tags within HTML content.
|
||||||
* @param string $html
|
* @param string $html
|
||||||
* @return mixed
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function escapeScripts(string $html)
|
protected function escapeScripts(string $html) : string
|
||||||
{
|
{
|
||||||
$scriptSearchRegex = '/<script.*?>.*?<\/script>/ms';
|
$scriptSearchRegex = '/<script.*?>.*?<\/script>/ms';
|
||||||
$matches = [];
|
$matches = [];
|
||||||
preg_match_all($scriptSearchRegex, $html, $matches);
|
preg_match_all($scriptSearchRegex, $html, $matches);
|
||||||
if (count($matches) === 0) {
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($matches[0] as $match) {
|
foreach ($matches[0] as $match) {
|
||||||
$html = str_replace($match, htmlentities($match), $html);
|
$html = str_replace($match, htmlentities($match), $html);
|
||||||
|
@ -194,9 +194,9 @@ class PageRepo extends EntityRepo
|
|||||||
* @param \BookStack\Entities\Page $page
|
* @param \BookStack\Entities\Page $page
|
||||||
* @return string
|
* @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);
|
return strip_tags($html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,15 +40,18 @@ class PageContentTest extends TestCase
|
|||||||
{
|
{
|
||||||
$page = Page::first();
|
$page = Page::first();
|
||||||
$secondPage = Page::where('id', '!=', $page->id)->first();
|
$secondPage = Page::where('id', '!=', $page->id)->first();
|
||||||
|
|
||||||
$this->asEditor();
|
$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 = $this->put($page->getUrl(), ['name' => $page->name, 'html' => $page->html, 'summary' => '']);
|
||||||
|
|
||||||
$resp->assertStatus(302);
|
$resp->assertStatus(302);
|
||||||
|
|
||||||
$page = Page::find($page->id);
|
$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()
|
public function test_page_includes_do_not_break_tables()
|
||||||
|
Loading…
Reference in New Issue
Block a user