mirror of
https://github.com/archtechx/laravel-seo.git
synced 2025-12-12 01:44:03 +00:00
Fix sanitization issues when rendering Blade, add regression tests
This commit is contained in:
parent
567a7b09ef
commit
f6d85e3dfe
6 changed files with 104 additions and 10 deletions
|
|
@ -52,6 +52,7 @@ url(string $url)
|
||||||
title(string $title)
|
title(string $title)
|
||||||
description(string $description)
|
description(string $description)
|
||||||
image(string $url)
|
image(string $url)
|
||||||
|
type(string $type)
|
||||||
locale(string $locale)
|
locale(string $locale)
|
||||||
|
|
||||||
twitterCreator(string $username)
|
twitterCreator(string $username)
|
||||||
|
|
@ -266,7 +267,7 @@ The `previewify()` method also returns a signed URL to the image, which lets you
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
> The `previewify:` prefix will be automatically prepended to all provided data keys.
|
> The `previewify:` prefix will be automatically prepended to all provided data keys.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Service Provider
|
### Service Provider
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
@if(seo('twitter.creator')) <meta name="twitter:creator" content="@seo('twitter.creator')"> @endif
|
@if(seo('twitter.creator')) <meta name="twitter:creator" content="@seo('twitter.creator')" /> @endif
|
||||||
@if(seo('twitter.site')) <meta name="twitter:site" content="@seo('twitter.site')"> @endif
|
@if(seo('twitter.site')) <meta name="twitter:site" content="@seo('twitter.site')" /> @endif
|
||||||
@if(seo('twitter.title')) <meta name="twitter:title" content="@seo('twitter.title')"> @endif
|
@if(seo('twitter.title')) <meta name="twitter:title" content="@seo('twitter.title')" /> @endif
|
||||||
@if(seo('twitter.description')) <meta name="twitter:description" content="@seo('twitter.description')"> @endif
|
@if(seo('twitter.description')) <meta name="twitter:description" content="@seo('twitter.description')" /> @endif
|
||||||
@if(seo('twitter.image')) <meta name="twitter:image" content="@seo('twitter.image')" /> @endif
|
@if(seo('twitter.image')) <meta name="twitter:image" content="@seo('twitter.image')" /> @endif
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if(seo('site')) <meta property="og:site_name" content="@seo('site')"> @endif
|
@if(seo('site')) <meta property="og:site_name" content="@seo('site')" /> @endif
|
||||||
|
|
||||||
@if(seo('locale')) <meta property="og:locale" content="@seo('locale')" /> @endif
|
@if(seo('locale')) <meta property="og:locale" content="@seo('locale')" /> @endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,8 @@ class SEOManager
|
||||||
/** Add a meta tag. */
|
/** Add a meta tag. */
|
||||||
public function tag(string $property, string $content): static
|
public function tag(string $property, string $content): static
|
||||||
{
|
{
|
||||||
|
$content = e($content);
|
||||||
|
|
||||||
$this->rawTag("meta.{$property}", "<meta property=\"{$property}\" content=\"{$content}\" />");
|
$this->rawTag("meta.{$property}", "<meta property=\"{$property}\" content=\"{$content}\" />");
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
@ -326,19 +328,26 @@ class SEOManager
|
||||||
return $this->get($key);
|
return $this->get($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Render blade directive. */
|
/**
|
||||||
|
* Render blade directive.
|
||||||
|
*
|
||||||
|
* This is the only method whose output (returned values) is wrapped in e()
|
||||||
|
* as these values are used in the meta.blade.php file via @seo calls.
|
||||||
|
*/
|
||||||
public function render(...$args): array|string|null
|
public function render(...$args): array|string|null
|
||||||
{
|
{
|
||||||
// Flipp and Previewify support more arguments
|
// Flipp and Previewify support more arguments
|
||||||
if (in_array($args[0], ['flipp', 'previewify'], true)) {
|
if (in_array($args[0], ['flipp', 'previewify'], true)) {
|
||||||
$method = array_shift($args);
|
$method = array_shift($args);
|
||||||
|
|
||||||
|
// The `flipp` and `previewify` methods return image URLs
|
||||||
|
// so we don't sanitize the returned value with e() here
|
||||||
return $this->{$method}(...$args);
|
return $this->{$method}(...$args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two arguments indicate that we're setting a value, e.g. `@seo('title', 'foo')
|
// Two arguments indicate that we're setting a value, e.g. `@seo('title', 'foo')
|
||||||
if (count($args) === 2) {
|
if (count($args) === 2) {
|
||||||
return $this->set($args[0], $args[1]);
|
return e($this->set($args[0], $args[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// An array means we don't return anything, e.g. `@seo(['title' => 'foo'])
|
// An array means we don't return anything, e.g. `@seo(['title' => 'foo'])
|
||||||
|
|
@ -355,7 +364,7 @@ class SEOManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single value means we fetch a value, e.g. `@seo('title')
|
// A single value means we fetch a value, e.g. `@seo('title')
|
||||||
return $this->get($args[0]);
|
return e($this->get($args[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handle magic get. */
|
/** Handle magic get. */
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ test('twitter falls back to the default values', function () {
|
||||||
expect(seo('twitter.description'))->toBe('bar');
|
expect(seo('twitter.description'))->toBe('bar');
|
||||||
expect(seo('description'))->toBe('baz');
|
expect(seo('description'))->toBe('baz');
|
||||||
|
|
||||||
expect(meta())->toContain('<meta name="twitter:title" content="foo">');
|
expect(meta())->toContain('<meta name="twitter:title" content="foo" />');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('extensions are automatically enabled when values for them are set', function () {
|
test('extensions are automatically enabled when values for them are set', function () {
|
||||||
|
|
|
||||||
84
tests/Pest/SanitizationTest.php
Normal file
84
tests/Pest/SanitizationTest.php
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
test('opengraph methods properly sanitize input', function (string $method, string $property) {
|
||||||
|
$unsanitizedContent = 'Testing string " with several \' XSS characters </title> " . \' .';
|
||||||
|
|
||||||
|
seo()->{$method}($unsanitizedContent);
|
||||||
|
|
||||||
|
$meta = meta();
|
||||||
|
|
||||||
|
$sanitizedContent = e($unsanitizedContent);
|
||||||
|
|
||||||
|
// These assertions are equivalent, but included for clarity
|
||||||
|
expect($meta)->not()->toContain('content="Testing string " with several \' XSS characters </title> " . \' ."');
|
||||||
|
expect($meta)->not()->toContain("content=\"{$unsanitizedContent}\"");
|
||||||
|
|
||||||
|
expect($meta)->toContain("<meta property=\"$property\" content=\"{$sanitizedContent}\" />");
|
||||||
|
expect($meta)->toContain("<meta property=\"$property\" content=\"Testing string " with several ' XSS characters </title> " . ' .\" />");
|
||||||
|
})->with([
|
||||||
|
['site', 'og:site_name'],
|
||||||
|
['url', 'og:url'],
|
||||||
|
['image', 'og:image'],
|
||||||
|
['type', 'og:type'],
|
||||||
|
['locale', 'og:locale'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// The Twitter integration is tested separately as it uses `meta name=""` instead of `meta property=""`
|
||||||
|
test('the twitter extension properly sanitizes input', function (string $method, $property) {
|
||||||
|
$unsanitizedContent = 'Testing string " with several \' XSS characters </title> " . \' .';
|
||||||
|
|
||||||
|
seo()->{$method}($unsanitizedContent);
|
||||||
|
|
||||||
|
$meta = meta();
|
||||||
|
|
||||||
|
$sanitizedContent = e($unsanitizedContent);
|
||||||
|
|
||||||
|
// These assertions are equivalent, but included for clarity
|
||||||
|
expect($meta)->not()->toContain('content="Testing string " with several \' XSS characters </title> " . \' ."');
|
||||||
|
expect($meta)->not()->toContain("content=\"{$unsanitizedContent}\"");
|
||||||
|
|
||||||
|
expect($meta)->toContain("<meta name=\"$property\" content=\"{$sanitizedContent}\" />");
|
||||||
|
expect($meta)->toContain("<meta name=\"$property\" content=\"Testing string " with several ' XSS characters </title> " . ' .\" />");
|
||||||
|
})->with([
|
||||||
|
['twitterCreator', 'twitter:creator'],
|
||||||
|
['twitterSite', 'twitter:site'],
|
||||||
|
['twitterTitle', 'twitter:title'],
|
||||||
|
['twitterDescription', 'twitter:description'],
|
||||||
|
['twitterImage', 'twitter:image'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// This method is tested separately as it adds an extra (<title>) tag
|
||||||
|
test('the title method properly sanitizes both tags', function () {
|
||||||
|
$unsanitizedContent = 'Testing string " with several \' XSS characters </title> " . \' .';
|
||||||
|
|
||||||
|
seo()->title($unsanitizedContent);
|
||||||
|
|
||||||
|
$meta = meta();
|
||||||
|
|
||||||
|
$sanitizedContent = e($unsanitizedContent);
|
||||||
|
|
||||||
|
// These assertions are equivalent, but included for clarity
|
||||||
|
expect($meta)->not()->toContain('meta property="og:title" content="Testing string " with several \' XSS characters </title> " . \' ."');
|
||||||
|
expect($meta)->not()->toContain('<title>Testing string " with several \' XSS characters </title> " . \' ."</title>');
|
||||||
|
expect($meta)->not()->toContain("meta property=\"og:title\" content=\"{$unsanitizedContent}\"");
|
||||||
|
expect($meta)->not()->toContain("<title>{$unsanitizedContent}</title>");
|
||||||
|
|
||||||
|
expect($meta)->toContain("<title>{$sanitizedContent}</title>");
|
||||||
|
expect($meta)->toContain("<title>Testing string " with several ' XSS characters </title> " . ' .</title>");
|
||||||
|
expect($meta)->toContain("<meta property=\"og:title\" content=\"{$sanitizedContent}\" />");
|
||||||
|
expect($meta)->toContain("<meta property=\"og:title\" content=\"Testing string " with several ' XSS characters </title> " . ' .\" />");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('seo blade directive calls are sanitized', function () {
|
||||||
|
seo(['image' => $string = 'Testing string " with several \' XSS characters </title> " . \' .']);
|
||||||
|
|
||||||
|
$escaped = e($string);
|
||||||
|
|
||||||
|
// Using @seo() to get a value
|
||||||
|
expect(blade('<img src="@seo(\'image\')">'))
|
||||||
|
->toBe("<img src=\"{$escaped}\">")
|
||||||
|
->not()->toBe('<img src="Testing string " with several \' XSS characters </title> " . \' ."');
|
||||||
|
|
||||||
|
// Using @seo() to set a value
|
||||||
|
expect(blade("@seo('description', 'abc \' def &')"))->toBe('abc ' def &');
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue