From ef4e3370e93ed577c6bd020f8e4f92f631f39669 Mon Sep 17 00:00:00 2001
From: Mac_Weber <Mac_Weber@789986.no-reply.drupal.org>
Date: Sat, 19 Mar 2016 23:28:49 -0300
Subject: [PATCH] Issue #2652236 by Mac_Weber: Insufficient link validation for
 external URLs

---
 .../LinkExternalProtocolsConstraintValidator.php   | 34 ++++++++++++++++++++--
 core/modules/system/system.install                 | 17 +++++++++++
 2 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/core/modules/link/src/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidator.php b/core/modules/link/src/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidator.php
index fad7b6d..98604a9 100644
--- a/core/modules/link/src/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidator.php
+++ b/core/modules/link/src/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidator.php
@@ -44,9 +44,37 @@ public function validate($value, Constraint $constraint) {
       catch (\InvalidArgumentException $e) {
         return;
       }
-      // Disallow external URLs using untrusted protocols.
-      if ($url->isExternal() && !in_array(parse_url($url->getUri(), PHP_URL_SCHEME), UrlHelper::getAllowedProtocols())) {
-        $this->context->addViolation($constraint->message, array('@uri' => $value->uri));
+
+      // External URLs validation.
+      if ($url->isExternal()) {
+        $uri = $url->getUri();
+
+        // Disallow external URLs using untrusted protocols. This cannot be
+        // tested together in a single call to UrlHelper::isValid() because this
+        // function does not test all allowed protocols.
+        $protocol = parse_url($uri, PHP_URL_SCHEME);
+        $is_invalid_protocol =  !in_array($protocol, UrlHelper::getAllowedProtocols());
+
+        // Gets the hostname.
+        $hostname = parse_url($uri, PHP_URL_HOST);
+
+        // Validates a domain if the needed PHP extensions are available for
+        // Punycode conversion.
+        if (function_exists('idn_to_ascii')) {
+          // Converts Punycode to ASCII.
+          $hostname = idn_to_ascii($hostname);
+          // Validates the URL.
+          $is_invalid_url = !UrlHelper::isValid($hostname);
+        }
+        // If cannot do Punycode validation, then just checks the presence of
+        // a hostname;
+        else {
+          $is_invalid_url = !boolval($hostname);
+        }
+
+        if ($is_invalid_protocol || $is_invalid_url) {
+          $this->context->addViolation($constraint->message, array('@uri' => $value->uri));
+        }
       }
     }
   }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index afa5892..4f9b191 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -799,6 +799,23 @@ function system_requirements($phase) {
     }
   }
 
+  // Check if the PHP IDN functions are available.
+  if ($phase == 'runtime') {
+    $installation_url = '//secure.php.net/manual/intl.setup.php';
+    $documentation_url = 'https://secure.php.net/manual/en/book.intl.php';
+    $requirements['php_intl'] = [
+      'title' => t('PHP Internationalization Functions'),
+      'severity' => REQUIREMENT_INFO,
+    ];
+    if (!function_exists('idn_to_ascii')) {
+      $requirements['php_intl']['value'] = t('Not available');
+      $requirements['php_intl']['description'] = t('Enabling the PHP Internationalization functions helps on Punycode domains validation. Without this extension there will not be validation for Internationalized Domain Names. See <a href=":url">the installation instructions</a> for more detail.', [':url' => $installation_url]);
+    }
+    else {
+      $requirements['php_intl']['description'] = t('The <a href=":url">PHP Internationalization Functions</a> are available', [':url' => $documentation_url]);
+    }
+  }
+
   // Check xdebug.max_nesting_level, as some pages will not work if it is too
   // low.
   if (extension_loaded('xdebug')) {
-- 
2.1.4

