From 2e78c27c6c5e6a9b463dc68d3496b35c3ccfa282 Mon Sep 17 00:00:00 2001 From: Colin O'Dell Date: Mon, 31 Jul 2023 17:26:40 -0400 Subject: [PATCH] Make allowed and default protocols for autolinks configurable --- CHANGELOG.md | 3 ++ docs/2.5/extensions/autolinks.md | 19 ++++++- src/Extension/Autolink/AutolinkExtension.php | 19 +++++-- src/Extension/Autolink/UrlAutolinkParser.php | 10 ++-- .../Autolink/UrlAutolinkParserTest.php | 50 +++++++++++++++++++ 5 files changed, 94 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53920b982a..a0425dfd32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi ### Added - The `AttributesExtension` now supports attributes without values (#985, #986) +- The `AutolinkExtension` exposes two new configuration options to override the default behavior (#969): + - `autolink/allowed_protocols` - an array of protocols to allow autolinking for + - `autolink/default_protocol` - the default protocol to use when none is specified ## [2.4.0] - 2023-03-24 diff --git a/docs/2.5/extensions/autolinks.md b/docs/2.5/extensions/autolinks.md index 9319fc97ee..22848050b2 100644 --- a/docs/2.5/extensions/autolinks.md +++ b/docs/2.5/extensions/autolinks.md @@ -32,7 +32,12 @@ use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; use League\CommonMark\MarkdownConverter; // Define your configuration, if needed -$config = []; +$config = [ + 'autolink' => [ + 'allowed_protocols' => ['https'], // defaults to ['https', 'http', 'ftp'] + 'default_protocols' => 'https', // defaults to 'http' + ], +]; // Configure the Environment with all the CommonMark parsers/renderers $environment = new Environment($config); @@ -46,6 +51,18 @@ $converter = new MarkdownConverter($environment); echo $converter->convert('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!'); ``` +## Configuration + +As of version 2.5.0, this extension supports the following configuration options under the `autolink` configuration: + +### `allowed_protocols` option + +This option defines which types of URLs will be autolinked. The default value of `['https', 'http', 'ftp']` means that only URLs using those protocols will be autolinked. Setting this to just `['https']` means that only HTTPS URLs will be autolinked. + +### `default_protocol` option + +This option defines the default protocol for URLs that start with `www.` and don't have an explicit protocol set. For example, setting this to `https` would convert `www.example.com` to `https://www.example.com`. + ## `@mention`-style Autolinking As of v1.5, [mention autolinking is now handled by a Mention Parser outside of this extension](/2.5/extensions/mentions/). diff --git a/src/Extension/Autolink/AutolinkExtension.php b/src/Extension/Autolink/AutolinkExtension.php index 3516ebba8f..54aafd4d3d 100644 --- a/src/Extension/Autolink/AutolinkExtension.php +++ b/src/Extension/Autolink/AutolinkExtension.php @@ -14,13 +14,26 @@ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\Environment\EnvironmentBuilderInterface; -use League\CommonMark\Extension\ExtensionInterface; +use League\CommonMark\Extension\ConfigurableExtensionInterface; +use League\Config\ConfigurationBuilderInterface; +use Nette\Schema\Expect; -final class AutolinkExtension implements ExtensionInterface +final class AutolinkExtension implements ConfigurableExtensionInterface { + public function configureSchema(ConfigurationBuilderInterface $builder): void + { + $builder->addSchema('autolink', Expect::structure([ + 'allowed_protocols' => Expect::listOf('string')->default(['http', 'https', 'ftp'])->mergeDefaults(false), + 'default_protocol' => Expect::string()->default('http'), + ])); + } + public function register(EnvironmentBuilderInterface $environment): void { $environment->addInlineParser(new EmailAutolinkParser()); - $environment->addInlineParser(new UrlAutolinkParser()); + $environment->addInlineParser(new UrlAutolinkParser( + $environment->getConfiguration()->get('autolink.allowed_protocols'), + $environment->getConfiguration()->get('autolink.default_protocol'), + )); } } diff --git a/src/Extension/Autolink/UrlAutolinkParser.php b/src/Extension/Autolink/UrlAutolinkParser.php index f44da3357e..9ed0e70923 100644 --- a/src/Extension/Autolink/UrlAutolinkParser.php +++ b/src/Extension/Autolink/UrlAutolinkParser.php @@ -65,10 +65,12 @@ final class UrlAutolinkParser implements InlineParserInterface */ private string $finalRegex; + private string $defaultProtocol; + /** * @param array $allowedProtocols */ - public function __construct(array $allowedProtocols = ['http', 'https', 'ftp']) + public function __construct(array $allowedProtocols = ['http', 'https', 'ftp'], string $defaultProtocol = 'http') { /** * @psalm-suppress PropertyTypeCoercion @@ -78,6 +80,8 @@ public function __construct(array $allowedProtocols = ['http', 'https', 'ftp']) foreach ($allowedProtocols as $protocol) { $this->prefixes[] = $protocol . '://'; } + + $this->defaultProtocol = $defaultProtocol; } public function getMatchDefinition(): InlineParserMatch @@ -120,9 +124,9 @@ public function parse(InlineParserContext $inlineContext): bool $cursor->advanceBy(\mb_strlen($url, 'UTF-8')); - // Auto-prefix 'http://' onto 'www' URLs + // Auto-prefix 'http(s)://' onto 'www' URLs if (\substr($url, 0, 4) === 'www.') { - $inlineContext->getContainer()->appendChild(new Link('http://' . $url, $url)); + $inlineContext->getContainer()->appendChild(new Link($this->defaultProtocol . '://' . $url, $url)); return true; } diff --git a/tests/functional/Extension/Autolink/UrlAutolinkParserTest.php b/tests/functional/Extension/Autolink/UrlAutolinkParserTest.php index 0c280f6154..9d98d6eae1 100644 --- a/tests/functional/Extension/Autolink/UrlAutolinkParserTest.php +++ b/tests/functional/Extension/Autolink/UrlAutolinkParserTest.php @@ -108,4 +108,54 @@ public function testUrlAutolinksWithStrikethrough(): void $html ); } + + public function testDisallowedProtocols(): void + { + $environment = new Environment([ + 'autolink' => [ + 'allowed_protocols' => ['https'], + ], + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new AutolinkExtension()); + + $converter = new MarkdownConverter($environment); + $html = $converter->convert('http://insecure.example.com')->getContent(); + + $this->assertSame("

http://insecure.example.com

\n", $html); + } + + /** + * @dataProvider dataProviderForSchemes + */ + public function testUrlAutolinksWithConfigurableSchemes(string $scheme): void + { + $markdown = 'www.example.com'; + + $environment = new Environment([ + 'autolink' => [ + 'default_protocol' => $scheme, + ], + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new AutolinkExtension()); + + $converter = new MarkdownConverter($environment); + $html = $converter->convert($markdown)->getContent(); + + $this->assertSame( + '

www.example.com

' . "\n", + $html + ); + } + + /** + * @return iterable> + */ + public function dataProviderForSchemes(): iterable + { + yield ['http']; + yield ['https']; + yield ['ftp']; + } }