Bläddra i källkod

Print the title of a page as text, and give the RSS stylesheet its own way to name one

header.tx printed the title with mark_raw.  The only title that needed it
was the one rss_style() set, which is an <xsl:value-of> element rather than
text.  Every other title went out unescaped too, including titles that did
not come from the site.

Now the title is escaped.  The Header component takes title_select, the
XPath of the title, and writes the <xsl:value-of> element itself, so the
RSS stylesheet still gets the title of the channel and no title is markup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
troglodyne-bot 3 dagar sedan
förälder
incheckning
9295e4b309

+ 2 - 0
lib/Trog/Component/Header.pm

@@ -26,6 +26,8 @@ Recognized arguments, all optional:
     embed                                 embedded view, which adds a base target
     no_doctype                            leave the doctype off, for the RSS
                                           stylesheet, which is XSL rather than HTML
+    title_select                          in place of title, the XPath of the
+                                          title, for that same stylesheet
 
 The list arguments default to empty rather than to the stock sheets, so that a
 caller which has none -- rss_style(), notably -- gets a header with none, as it

+ 3 - 3
lib/Trog/Routes/HTML.pm

@@ -3461,9 +3461,9 @@ without a doctype.
 =cut
 
 sub rss_style ($query) {
-    $query->{port}       = ":$query->{port}" if $query->{port};
-    $query->{title}      = qq{<xsl:value-of select="rss/channel/title"/>};
-    $query->{no_doctype} = 1;
+    $query->{port}         = ":$query->{port}" if $query->{port};
+    $query->{title_select} = 'rss/channel/title';
+    $query->{no_doctype}   = 1;
 
     return Trog::Renderer->render(
         template    => 'rss-style.tx',

+ 12 - 2
t/Trog-Component-page.t

@@ -51,8 +51,13 @@ subtest 'Header' => sub {
         }
     );
 
-    like( $out, qr/<!doctype[ ]html>/i,            "the doctype is emitted by default" );
-    like( $out, qr{<title>A[ ]Page</title>},       "the title lands" );
+    like( $out, qr/<!doctype[ ]html>/i,      "the doctype is emitted by default" );
+    like( $out, qr{<title>A[ ]Page</title>}, "the title lands" );
+    like(
+        Trog::Component::component( 'Header', { title => '<b>x</b>' } ),
+        qr{<title>&lt;b&gt;x&lt;/b&gt;</title>},
+        "and is text, not markup"
+    );
     like( $out, qr{href="/styles/screen\.css"},    "stylesheets land" );
     like( $out, qr{src="/scripts/post\.js"},       "scripts land" );
     like( $out, qr{lang="en-US"},                  "lang defaults rather than rendering empty" );
@@ -61,6 +66,11 @@ subtest 'Header' => sub {
     # rss_style() renders into an XSL document, so the doctype has to go.
     my $xsl = Trog::Component::component( 'Header', { title => 'RSS', no_doctype => 1 } );
     unlike( $xsl, qr/<!doctype/i, "no_doctype suppresses it" );
+    like(
+        Trog::Component::component( 'Header', { title => 'RSS', title_select => 'rss/channel/title', no_doctype => 1 } ),
+        qr{<title><xsl:value-of[ ]select="rss/channel/title"/></title>},
+        "and title_select makes the title an XSL value"
+    );
 
     # It also has no stylesheets or scripts to give, and must not invent any.
     unlike( $xsl, qr/rel="stylesheet"[ ]type="text\/css"[ ]href="\/styles\/screen/, "no stylesheets are conjured up" );

+ 21 - 0
t/Trog-Routes-HTML-rss-style.t

@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+use 5.040;
+use re '/aasx';
+use warnings FATAL => 'all';
+
+use Test::More;
+
+use FindBin::libs;
+
+require_ok('Trog::Routes::HTML') or BAIL_OUT("Can't find SUT");
+
+subtest 'rss_style' => sub {
+    my $res  = Trog::Routes::HTML::rss_style( { route => '/styles/rss-style.xsl', title => '<b>x</b>' } );
+    my $body = join( '', @{ $res->[2] } );
+
+    is( $res->[0], 200, 'renders' );
+    like( $body, qr{<title><xsl:value-of[ ]select="rss/channel/title"/></title>}, 'with the title of the channel as its title' );
+    unlike( $body, qr{<b>x</b>}, 'and not a title from the query' );
+};
+
+done_testing();

+ 5 - 1
www/templates/html/components/header.tx

@@ -3,7 +3,11 @@
 : }
 <html dir="auto" lang="<: $lang :>">
     <head>
-        <title><: $title | mark_raw :></title>
+: if ($title_select) {
+        <title><xsl:value-of select="<: $title_select :>"/></title>
+: } else {
+        <title><: $title :></title>
+: }
         <meta charset="utf-8" />
 
         <link rel="alternate" title="rss" type="application/rss+xml" href="<: $route :>?format=rss" />

+ 4 - 4
www/templates/xsl/rss-style.tx

@@ -3,10 +3,10 @@
   <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
   <xsl:template match="/">
     <: component('Header', {
-      title      => $title,
-      route      => $route,
-      lang       => $lang,
-      no_doctype => 1,
+      title_select => $title_select,
+      route        => $route,
+      lang         => $lang,
+      no_doctype   => 1,
     }) :>
         <a>
           <xsl:attribute name="href">