diff --git a/lib/Service/ColumnTypes/TextLinkBusiness.php b/lib/Service/ColumnTypes/TextLinkBusiness.php index 29f873ae04..4afce65f99 100644 --- a/lib/Service/ColumnTypes/TextLinkBusiness.php +++ b/lib/Service/ColumnTypes/TextLinkBusiness.php @@ -8,9 +8,21 @@ namespace OCA\Tables\Service\ColumnTypes; use OCA\Tables\Db\Column; +use OCA\Tables\Errors\BadRequestError; +use OCP\IL10N; +use Psr\Log\LoggerInterface; class TextLinkBusiness extends SuperBusiness implements IColumnTypeBusiness { + public const ALLOWED_PROTOCOLS = ['http', 'https']; + + public function __construct( + LoggerInterface $logger, + private IL10N $n, + ) { + parent::__construct($logger); + } + /** * @param mixed $value (string|null) * @param Column|null $column @@ -96,4 +108,29 @@ public function canBeParsed($value, ?Column $column = null): bool { preg_match('/(http|https)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/', $value, $matches); return !empty($matches); } + + public function validateValue(mixed $value, Column $column, string $userId, int $tableId, ?int $rowId): void { + $data = json_decode($value, true); + + // Only allow URLs that start with http or https + if (isset($data['value']) && !$this->isValidUrlProtocol($data['value'])) { + throw new BadRequestError( + 'Column "' . $column->getTitle() . '" contains an invalid protocol. Only http and https are allowed.', + translatedMessage: $this->n->t( + 'Column "%s" contains an invalid protocol. Only http and https are allowed.', + [$column->getTitle()] + ), + ); + } + } + + private function isValidUrlProtocol(string $url): bool { + $parsed = parse_url($url); + + if ($parsed === false || !isset($parsed['scheme'])) { + return false; + } + + return in_array($parsed['scheme'], self::ALLOWED_PROTOCOLS, true); + } } diff --git a/tests/unit/Service/ColumnTypes/TextLinkBusinessTest.php b/tests/unit/Service/ColumnTypes/TextLinkBusinessTest.php index d25cd59726..f85ed9eb3e 100644 --- a/tests/unit/Service/ColumnTypes/TextLinkBusinessTest.php +++ b/tests/unit/Service/ColumnTypes/TextLinkBusinessTest.php @@ -8,17 +8,22 @@ namespace OCA\Tables\Service\ColumnTypes; use OCA\Tables\Db\Column; +use OCP\IL10N; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; class TextLinkBusinessTest extends TestCase { private TextLinkBusiness $textLink; + private Column $column; public function setUp(): void { $this->textLink = new TextLinkBusiness( - $this->createMock(LoggerInterface::class) + $this->createMock(LoggerInterface::class), + $this->createMock(IL10N::class) ); + + $this->column = $this->createMock(Column::class); } public function testCanBeParsed() { @@ -87,4 +92,31 @@ public function testParseValue() { 'providerId' => 'url', ]), $column)); } + + public function testValidateValue() { + // Assert that no exception is thrown for valid values + try { + $this->textLink->validateValue(json_encode([ + 'title' => 'Test link', + 'value' => 'https://nextcloud.com', + 'providerId' => 'url', + ]), $this->column, 'userId', 1, null); + } catch (\Exception $e) { + $this->fail('validateValue threw an exception for valid input: ' . $e->getMessage()); + } + + // Assert that exception is thrown for invalid values + $this->expectException(\OCA\Tables\Errors\BadRequestError::class); + $this->textLink->validateValue(json_encode([ + 'title' => 'Test link', + 'value' => 'invalidurl', + 'providerId' => 'url', + ]), $this->column, 'userId', 1, null); + $this->expectException(\OCA\Tables\Errors\BadRequestError::class); + $this->textLink->validateValue(json_encode([ + 'title' => 'Test link', + 'value' => 'javascript:https://nextcloud.com', + 'providerId' => 'url', + ]), $this->column, 'userId', 1, null); + } }