22
33namespace Sensiolabs \GotenbergBundle \Tests \Twig ;
44
5+ use PHPUnit \Framework \Attributes \DataProvider ;
56use PHPUnit \Framework \Attributes \TestWith ;
67use PHPUnit \Framework \TestCase ;
78use Sensiolabs \GotenbergBundle \Builder \BuilderAssetInterface ;
9+ use Sensiolabs \GotenbergBundle \Twig \GotenbergExtension ;
810use Sensiolabs \GotenbergBundle \Twig \GotenbergRuntime ;
911use Symfony \Component \Asset \Packages ;
1012use Symfony \Component \AssetMapper \AssetMapperRepository ;
13+ use Twig \Environment ;
14+ use Twig \Loader \ArrayLoader ;
15+ use Twig \RuntimeLoader \FactoryRuntimeLoader ;
1116
1217class GotenbergRuntimeTest extends TestCase
1318{
1419 public function testGetAsset (): void
1520 {
16- $ runtime = new GotenbergRuntime ();
1721 $ builder = $ this ->createMock (BuilderAssetInterface::class);
18- $ builder
19- ->expects ($ this ->once ())
20- ->method ('addAsset ' )
21- ->with ('foo ' )
22- ;
22+ $ builder ->expects ($ this ->once ())->method ('addAsset ' )->with ('foo ' );
23+
24+ $ runtime = new GotenbergRuntime ();
2325 $ runtime ->setBuilder ($ builder );
26+
2427 $ this ->assertSame ('foo ' , $ runtime ->getAssetUrl ('foo ' ));
2528 }
2629
@@ -34,14 +37,12 @@ public function testGetAssetThrowsWhenBuilderIsNotSet(): void
3437
3538 public function testGetFontFace (): void
3639 {
37- $ runtime = new GotenbergRuntime ();
3840 $ builder = $ this ->createMock (BuilderAssetInterface::class);
39- $ builder
40- ->expects ($ this ->once ())
41- ->method ('addAsset ' )
42- ->with ('foo.ttf ' )
43- ;
41+ $ builder ->expects ($ this ->once ())->method ('addAsset ' )->with ('foo.ttf ' );
42+
43+ $ runtime = new GotenbergRuntime ();
4444 $ runtime ->setBuilder ($ builder );
45+
4546 $ this ->assertSame (
4647 '@font-face {font-family: "my_font";src: url("foo.ttf");} ' ,
4748 $ runtime ->getFontFace ('foo.ttf ' , 'my_font ' ),
@@ -50,14 +51,12 @@ public function testGetFontFace(): void
5051
5152 public function testGetFontStyleTag (): void
5253 {
53- $ runtime = new GotenbergRuntime ();
5454 $ builder = $ this ->createMock (BuilderAssetInterface::class);
55- $ builder
56- ->expects ($ this ->once ())
57- ->method ('addAsset ' )
58- ->with ('foo.ttf ' )
59- ;
55+ $ builder ->expects ($ this ->once ())->method ('addAsset ' )->with ('foo.ttf ' );
56+
57+ $ runtime = new GotenbergRuntime ();
6058 $ runtime ->setBuilder ($ builder );
59+
6160 $ this ->assertSame (
6261 '<style>@font-face {font-family: "my_font";src: url("foo.ttf");}</style> ' ,
6362 $ runtime ->getFontStyleTag ('foo.ttf ' , 'my_font ' ),
@@ -180,4 +179,57 @@ public function testGetAssetUrlWhenMissingAssetMapperRepositoryAndPackages(): vo
180179
181180 $ this ->assertSame ('origin.png ' , $ path );
182181 }
182+
183+ /**
184+ * @return iterable<string, array{string, string}>
185+ */
186+ public static function provideFontRenderingCases (): iterable
187+ {
188+ yield 'gotenberg_font_face renders correctly inside a style tag ' => [
189+ '<style>{{ gotenberg_font_face("foo.ttf", "my_font") }}</style> ' ,
190+ '<style>@font-face {font-family: "my_font";src: url("foo.ttf");}</style> ' ,
191+ ];
192+ yield 'gotenberg_font_style_tag renders correctly in html context ' => [
193+ '{{ gotenberg_font_style_tag("foo.ttf", "my_font") }} ' ,
194+ '<style>@font-face {font-family: "my_font";src: url("foo.ttf");}</style> ' ,
195+ ];
196+ yield 'gotenberg_font_face escapes html tags in path inside a style tag ' => [
197+ '<style>{{ gotenberg_font_face("fonts/<script>alert(1).ttf", "my_font") }}</style> ' ,
198+ '<style>@font-face {font-family: "my_font";src: url("<script>alert(1).ttf");}</style> ' ,
199+ ];
200+ yield 'gotenberg_font_style_tag escapes html tags in path ' => [
201+ '{{ gotenberg_font_style_tag("fonts/<script>alert(1).ttf", "my_font") }} ' ,
202+ '<style>@font-face {font-family: "my_font";src: url("<script>alert(1).ttf");}</style> ' ,
203+ ];
204+ yield 'gotenberg_font_face escapes html injection in name inside a style tag ' => [
205+ '<style>{{ gotenberg_font_face("foo.ttf", "</style><script>alert( \'xss \')</script>") }}</style> ' ,
206+ '<style>@font-face {font-family: "</style><script>alert('xss')</script>";src: url("foo.ttf");}</style> ' ,
207+ ];
208+ yield 'gotenberg_font_style_tag escapes html injection in name ' => [
209+ '{{ gotenberg_font_style_tag("foo.ttf", "</style><script>alert( \'xss \')</script>") }} ' ,
210+ '<style>@font-face {font-family: "</style><script>alert('xss')</script>";src: url("foo.ttf");}</style> ' ,
211+ ];
212+ yield 'applying e("css") filter to gotenberg_font_face output destroys the css rule structure ' => [
213+ '<style>{{ gotenberg_font_face("foo.ttf", "my_font") | e("css") }}</style> ' ,
214+ '<style>\40 font\2D face\20 \7B font\2D family\3A \20 \22 my\5F font\22 \3B src\3A \20 url\28 \22 foo\2E ttf\22 \29 \3B \7D </style> ' ,
215+ ];
216+ }
217+
218+ #[DataProvider('provideFontRenderingCases ' )]
219+ public function testFontRendering (string $ template , string $ expected ): void
220+ {
221+ $ builder = $ this ->createMock (BuilderAssetInterface::class);
222+ $ builder ->method ('addAsset ' );
223+
224+ $ runtime = new GotenbergRuntime ();
225+ $ runtime ->setBuilder ($ builder );
226+
227+ $ twig = new Environment (new ArrayLoader (), ['autoescape ' => 'html ' ]);
228+ $ twig ->addExtension (new GotenbergExtension ());
229+ $ twig ->addRuntimeLoader (new FactoryRuntimeLoader ([
230+ GotenbergRuntime::class => static fn () => $ runtime ,
231+ ]));
232+
233+ $ this ->assertSame ($ expected , $ twig ->createTemplate ($ template )->render ([]));
234+ }
183235}
0 commit comments