mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-05-04 18:08:46 +03:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
446b4a7d3d | ||
|
|
d335b49be0 | ||
|
|
5f5fea7c83 | ||
|
|
6e7cc169d1 | ||
|
|
6216c89f82 | ||
|
|
404e67afbc | ||
|
|
6d64262a61 | ||
|
|
d9b9303a42 | ||
|
|
50a7183b32 | ||
|
|
25ed242f61 | ||
|
|
7aef0a48b3 |
@@ -12,6 +12,8 @@ use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Permissions\Permission;
|
||||
use BookStack\Util\HtmlContentFilter;
|
||||
use BookStack\Util\HtmlContentFilterConfig;
|
||||
use BookStack\Util\SimpleListOptions;
|
||||
use Illuminate\Http\Request;
|
||||
use Ssddanbrown\HtmlDiff\Diff;
|
||||
@@ -101,12 +103,15 @@ class PageRevisionController extends Controller
|
||||
|
||||
$prev = $revision->getPreviousRevision();
|
||||
$prevContent = $prev->html ?? '';
|
||||
$diff = Diff::excecute($prevContent, $revision->html);
|
||||
|
||||
// TODO - Refactor PageContent so we can de-dupe these steps
|
||||
$rawDiff = Diff::excecute($prevContent, $revision->html);
|
||||
$filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering'));
|
||||
$filter = new HtmlContentFilter($filterConfig);
|
||||
$diff = $filter->filterString($rawDiff);
|
||||
|
||||
$page->fill($revision->toArray());
|
||||
// TODO - Refactor PageContent so we don't need to juggle this
|
||||
$page->html = $revision->html;
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$page->html = '';
|
||||
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()]));
|
||||
|
||||
return view('pages.revision', [
|
||||
|
||||
@@ -167,14 +167,26 @@ abstract class Controller extends BaseController
|
||||
|
||||
/**
|
||||
* Redirect to the URL provided in the request as a '_return' parameter.
|
||||
* Will check that the parameter leads to a URL under the root path of the system.
|
||||
* Will check that the parameter leads to a URL under the same origin as the application.
|
||||
*/
|
||||
protected function redirectToRequest(Request $request): RedirectResponse
|
||||
{
|
||||
$basePath = url('/');
|
||||
$returnUrl = $request->input('_return') ?? $basePath;
|
||||
|
||||
if (!str_starts_with($returnUrl, $basePath)) {
|
||||
// Only allow use of _return on requests where we expect CSRF to be active
|
||||
// to prevent it potentially being used as an open redirect
|
||||
$allowedMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
||||
if (!in_array($request->getMethod(), $allowedMethods)) {
|
||||
return redirect($basePath);
|
||||
}
|
||||
|
||||
$intendedUrl = parse_url($returnUrl);
|
||||
$baseUrl = parse_url($basePath);
|
||||
$isSameOrigin = ($intendedUrl['host'] ?? '') === ($baseUrl['host'] ?? '')
|
||||
&& ($intendedUrl['scheme'] ?? '') === ($baseUrl['scheme'] ?? '')
|
||||
&& ($intendedUrl['port'] ?? 0) === ($baseUrl['port'] ?? 0);
|
||||
if (!$isSameOrigin) {
|
||||
return redirect($basePath);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ class ConfiguredHtmlPurifier
|
||||
$config->set('Core.AllowHostnameUnderscore', true);
|
||||
$config->set('CSS.AllowTricky', true);
|
||||
$config->set('HTML.SafeIframe', true);
|
||||
$config->set('HTML.TargetNoopener', false);
|
||||
$config->set('HTML.TargetNoreferrer', false);
|
||||
$config->set('Attr.EnableID', true);
|
||||
$config->set('Attr.ID.HTML5', true);
|
||||
$config->set('Output.FixInnerHTML', false);
|
||||
@@ -141,6 +143,12 @@ class ConfiguredHtmlPurifier
|
||||
'drawio-diagram',
|
||||
'Number',
|
||||
);
|
||||
|
||||
// Allow target="_blank" on links
|
||||
$definition->addAttribute('a', 'target', 'Enum#_blank');
|
||||
|
||||
// Allow mention-ids on links
|
||||
$definition->addAttribute('a', 'data-mention-user-id', 'Number');
|
||||
}
|
||||
|
||||
public function purify(string $html): string
|
||||
|
||||
456
composer.lock
generated
456
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
8e88c0fe2ea1b1bb500f6cd10cf3fcf04198e2d55fe71f292d091d4faa6eb7f3
|
||||
e2acb4ea1edce1e2c9af0d7598a3de9ae1d44ff9e647f018f4f732ab5f560f9d
|
||||
@@ -478,4 +478,25 @@ HTML;
|
||||
$resp->assertSee($expected, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_allow_list_does_not_filter_cases()
|
||||
{
|
||||
$testCasesExpectedByInput = [
|
||||
'<p><a href="https://example.com" target="_blank">New tab linkydoodle</a></p>',
|
||||
'<p><a href="https://example.com/user/1" data-mention-user-id="5">@mentionusertext</a></p>',
|
||||
'<details><summary>Hello</summary><p>Mydetailshere</p></details>',
|
||||
];
|
||||
|
||||
config()->set('app.content_filtering', 'a');
|
||||
$page = $this->entities->page();
|
||||
$this->asEditor();
|
||||
|
||||
foreach ($testCasesExpectedByInput as $input) {
|
||||
$page->html = $input;
|
||||
$page->save();
|
||||
$resp = $this->get($page->getUrl());
|
||||
|
||||
$resp->assertSee($input, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,20 @@ class PageRevisionTest extends TestCase
|
||||
$revisionView->assertSee('new revision content');
|
||||
}
|
||||
|
||||
public function test_page_revision_preview_filters_html_content()
|
||||
{
|
||||
$this->asEditor();
|
||||
$page = $this->entities->page();
|
||||
$this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '<script>dontwantthishere</script><style>dontwantthishere</style><p>expectthisthough</p>']);
|
||||
$pageRevision = $page->revisions->last();
|
||||
$this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '<p>Updated content</p>']);
|
||||
|
||||
$revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id);
|
||||
$revisionView->assertStatus(200);
|
||||
$revisionView->assertSee('expectthisthough');
|
||||
$revisionView->assertDontSee('dontwantthishere');
|
||||
}
|
||||
|
||||
public function test_page_revision_restore_updates_content()
|
||||
{
|
||||
$this->asEditor();
|
||||
@@ -215,6 +229,34 @@ class PageRevisionTest extends TestCase
|
||||
$html->assertElementContains('.item-list > .item-list-row:nth-child(2)', 'Changes');
|
||||
}
|
||||
|
||||
public function test_revision_changes_view_shows_diff()
|
||||
{
|
||||
$this->asEditor();
|
||||
$page = $this->entities->page();
|
||||
$this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '<p id="bkmrk-hello">Hello there dog</p>']);
|
||||
$this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '<p id="bkmrk-hello">Hello there cat</p>']);
|
||||
|
||||
$pageRevision = $page->revisions()->orderBy('id', 'desc')->first();
|
||||
$revisionView = $this->get("{$page->getUrl()}/revisions/{$pageRevision->id}/changes");
|
||||
$revisionView->assertStatus(200);
|
||||
$revisionView->assertSee('<p id="bkmrk-hello">Hello there <del class="diffmod">dog</del><ins class="diffmod">cat</ins></p>', false);
|
||||
}
|
||||
|
||||
public function test_revision_changes_view_filters_html_content()
|
||||
{
|
||||
$this->asEditor();
|
||||
$page = $this->entities->page();
|
||||
$html = '<script>dontwantthishere</script><style>dontwantthishere</style><p>expectthisthough</p>';
|
||||
$this->createRevisions($page, 1, ['name' => 'updated page', 'html' => $html]);
|
||||
$this->createRevisions($page, 1, ['name' => 'updated page', 'html' => $html]);
|
||||
|
||||
$pageRevision = $page->revisions()->orderBy('id', 'desc')->first();
|
||||
$revisionView = $this->get("{$page->getUrl()}/revisions/{$pageRevision->id}/changes");
|
||||
$revisionView->assertStatus(200);
|
||||
$revisionView->assertSee('expectthisthough');
|
||||
$revisionView->assertDontSee('dontwantthishere');
|
||||
}
|
||||
|
||||
public function test_revision_restore_action_only_visible_with_permission()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
|
||||
@@ -153,6 +153,26 @@ class UserPreferencesTest extends TestCase
|
||||
->assertElementNotExists('.content-wrap .entity-list-item');
|
||||
}
|
||||
|
||||
public function test_redirect_on_preference_change_checks_host()
|
||||
{
|
||||
$expectedByRedirect = [
|
||||
'http://localhost/beans' => 'http://localhost/beans',
|
||||
'https://localhost/beans' => 'http://localhost',
|
||||
'http://localhost:9090/beans' => 'http://localhost',
|
||||
'http://localhost.example.com/beans' => 'http://localhost',
|
||||
'http://localhost@example.com/beans' => 'http://localhost',
|
||||
];
|
||||
|
||||
$this->asEditor();
|
||||
foreach ($expectedByRedirect as $url => $expected) {
|
||||
$req = $this->patch("/preferences/change-view/bookshelf", [
|
||||
'view' => 'grid',
|
||||
'_return' => $url,
|
||||
]);
|
||||
$req->assertRedirect($expected);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_update_code_language_favourite()
|
||||
{
|
||||
$editor = $this->users->editor();
|
||||
|
||||
Reference in New Issue
Block a user