Skip to content

Commit 3e7751e

Browse files
committed
Restrict Protocols for Html Hyperlinks
Render hyperlink as text if it begins with a string of word characters followed by colon, unless the string is one of http, https, file, ftp, or s3.
1 parent 219b0b4 commit 3e7751e

File tree

4 files changed

+42
-2
lines changed

4 files changed

+42
-2
lines changed

src/PhpSpreadsheet/Writer/Html.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1528,7 +1528,14 @@ private function generateRow(Worksheet $worksheet, array $values, int $row, stri
15281528

15291529
// Hyperlink?
15301530
if ($worksheet->hyperlinkExists($coordinate) && !$worksheet->getHyperlink($coordinate)->isInternal()) {
1531-
$cellData = '<a href="' . htmlspecialchars($worksheet->getHyperlink($coordinate)->getUrl(), Settings::htmlEntityFlags()) . '" title="' . htmlspecialchars($worksheet->getHyperlink($coordinate)->getTooltip(), Settings::htmlEntityFlags()) . '">' . $cellData . '</a>';
1531+
$url = $worksheet->getHyperlink($coordinate)->getUrl();
1532+
$urldecode = strtolower(html_entity_decode(trim($url), encoding: 'UTF-8'));
1533+
$parseScheme = preg_match('/^(\\w+):/', $urldecode, $matches);
1534+
if ($parseScheme === 1 && !in_array($matches[1], ['http', 'https', 'file', 'ftp', 's3'], true)) {
1535+
$cellData = htmlspecialchars($url, Settings::htmlEntityFlags());
1536+
} else {
1537+
$cellData = '<a href="' . htmlspecialchars($url, Settings::htmlEntityFlags()) . '" title="' . htmlspecialchars($worksheet->getHyperlink($coordinate)->getTooltip(), Settings::htmlEntityFlags()) . '">' . $cellData . '</a>';
1538+
}
15321539
}
15331540

15341541
// Should the cell be written or is it swallowed by a rowspan or colspan?

tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function testURLImageSourceNotFound(): void
5959

6060
public function testURLImageSourceBadProtocol(): void
6161
{
62-
$filename = realpath(__DIR__ . '/../../../data/Reader/XLSX/urlImage.bad.xlsx');
62+
$filename = realpath(__DIR__ . '/../../../data/Reader/XLSX/urlImage.bad.dontuse');
6363
self::assertNotFalse($filename);
6464
$this->expectException(SpreadsheetException::class);
6565
$this->expectExceptionMessage('Invalid protocol for linked drawing');
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
6+
7+
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheet\Writer\Html;
10+
use PHPUnit\Framework\TestCase;
11+
12+
class NoJavascriptLinksTest extends TestCase
13+
{
14+
public function testNoJavascriptLinks(): void
15+
{
16+
$spreadsheet = new Spreadsheet();
17+
$sheet = $spreadsheet->getActiveSheet();
18+
$sheet->getCell('A1')->setValue('Click me');
19+
$hyperlink = new Hyperlink('http://www.example.com');
20+
$sheet->getCell('A1')->setHyperlink($hyperlink);
21+
$sheet->getCell('A2')->setValue('JS link');
22+
$hyperlink2 = new Hyperlink('javascript:alert(\'hello1\')');
23+
$sheet->getCell('A2')->setHyperlink($hyperlink2);
24+
$sheet->getCell('A3')->setValue('=HYPERLINK("javascript:alert(\'hello2\')", "jsfunc click")');
25+
26+
$writer = new Html($spreadsheet);
27+
$html = $writer->generateHTMLAll();
28+
self::assertStringContainsString('<td class="column0 style0 s"><a href="http://www.example.com" title="">Click me</a></td>', $html, 'http hyperlink retained');
29+
self::assertStringContainsString('<td class="column0 style0 s">javascript:alert(\'hello1\')</td>', $html, 'javascript hyperlink dropped');
30+
self::assertStringContainsString('<td class="column0 style0 f">javascript:alert(\'hello2\')</td>', $html, 'javascript hyperlink function dropped');
31+
$spreadsheet->disconnectWorksheets();
32+
}
33+
}

0 commit comments

Comments
 (0)