Project

General

Profile

Defect #44718 ยป 0001-Fix-broken-links-to-headings-containing-quotes.patch

Katsuya HIDAKA, 2026-11-05 06:45

View differences:

app/helpers/application_helper.rb
1444 1444

  
1445 1445
    text.gsub!(HEADING_RE) do
1446 1446
      level, attrs, content = $2.to_i, $3, $4
1447
      item = strip_tags(content).strip
1447
      # Remove HTML tags using the same sanitizer as strip_tags, without escaping quotes.
1448
      # Keep quotes literal so sanitize_anchor_name removes them rather than leaving
1449
      # parts of HTML entities in the anchor (e.g. ' becoming 39).
1450
      item = self.class.full_sanitizer.sanitize(content).strip
1448 1451
      anchor = sanitize_anchor_name(item)
1449 1452
      # used for single-file wiki export
1450 1453
      if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) ||
......
1456 1459
      if idx > 1
1457 1460
        anchor = "#{anchor}-#{idx}"
1458 1461
      end
1459
      @parsed_headings << [level, anchor, item]
1462
      @parsed_headings << [level, anchor, ERB::Util.html_escape_once(item)]
1460 1463
      "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}" \
1461 1464
        "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
1462 1465
    end
test/helpers/application_helper_test.rb
1374 1374
  end
1375 1375

  
1376 1376
  def test_headings_with_special_chars
1377
    # This test makes sure that the generated anchor names match the expected
1378
    # ones even if the heading text contains unconventional characters
1379
    raw = 'h1. Some heading related to version 0.5'
1380
    anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1381
    expected =
1382
      %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5| +
1383
        %|<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1384
    with_settings :text_formatting => 'textile' do
1385
      assert_equal expected, textilizable(raw)
1377
    # Each value is [expected anchor name, expected HTML-escaped TOC text].
1378
    headings = {
1379
      'Some heading related to version 0.5' =>
1380
        ['Some-heading-related-to-version-05', 'Some heading related to version 0.5'],
1381
      "Bob's guide" => ['Bobs-guide', 'Bob&#39;s guide'],
1382
      'The "quoted" guide' => ['The-quoted-guide', 'The &quot;quoted&quot; guide'],
1383
      "*Bob's* & B" => ['Bobs-amp-B', 'Bob&#39;s &amp; B'],
1384
      '&lt;img src=x onerror=alert(1)&gt;' =>
1385
        ['ltimg-srcx-onerroralert1gt', '&lt;img src=x onerror=alert(1)&gt;']
1386
    }
1387

  
1388
    {'textile' => 'h1.', 'common_mark' => '#'}.each do |format, prefix|
1389
      with_settings :text_formatting => format do
1390
        headings.each do |heading, (anchor, escaped_heading)|
1391
          raw = "{{toc}}\n\n#{prefix} #{heading}\n\n[[##{anchor}|Wiki link]]"
1392
          html = textilizable(raw)
1393

  
1394
          assert_select_in html, 'a[name=?]', anchor, :count => 1
1395
          assert_select_in html, 'h1 a.wiki-anchor[href=?]', "##{anchor}", :count => 1
1396
          assert_include %(<a href="##{anchor}">#{escaped_heading}</a>), html
1397
          assert_select_in html, 'a.wiki-page[href=?]', "##{anchor}", :text => 'Wiki link'
1398
        end
1399
      end
1386 1400
    end
1387 1401
  end
1388 1402

  
    (1-1/1)