diff --git a/core/lib/Drupal/Component/Utility/UriHelper.php b/core/lib/Drupal/Component/Utility/UriHelper.php
new file mode 100644
index 0000000..632f6da
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/UriHelper.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\Component\Utility;
+
+/**
+ * Helper class URI based methods.
+ *
+ * @ingroup utility
+ */
+class UriHelper {
+
+  /**
+   * Verifies the syntax of the given URI.
+   *
+   * This function should only be used on actual URIs. It should not be used for
+   * Drupal menu paths, which can contain arbitrary characters.
+   * Valid values per RFC 3986.
+   *
+   * @param string $uri
+   *   The URI to verify.
+   *
+   * @return bool
+   *   TRUE if the URL is in a valid format, FALSE otherwise.
+   */
+  public static function isValid($uri) {
+
+    $regex = '=
+    # URI scheme RFC 3986 (http://tools.ietf.org/html/rfc3986)
+    
+    (?(DEFINE)
+    
+      # ABNF notation of RFC 2234 (http://tools.ietf.org/html/rfc2234#section-6.1)
+    
+      (?<ALPHA>     [\x41-\x5A\x61-\x7A] )    # Latin character (A-Z, a-z)
+      (?<CR>        \x0D )                    # Carriage return (\r)
+      (?<DIGIT>     [\x30-\x39] )             # Decimal number (0-9)
+      (?<DQUOTE>    \x22 )                    # Double quote (")
+      (?<HEXDIG>    (?&DIGIT) | [\x41-\x46] ) # Hexadecimal number (0-9, A-F)
+      (?<LF>        \x0A )                    # Line feed (\n)
+      (?<SP>        \x20 )                    # Space
+    
+      # RFC 3986 body
+    
+      (?<uri>    (?&scheme) \: (?&hier_part) (?: \? (?&query) )? (?: \# (?&fragment) )? )
+    
+      (?<hier_part>    \/\/ (?&authority) (?&path_abempty)
+                     | (?&path_absolute)
+                     | (?&path_rootless)
+                     | (?&path_empty) )
+    
+      (?<uri_reference>    (?&uri) | (?&relative_ref) )
+    
+      (?<absolute_uri>    (?&scheme) \: (?&hier_part) (?: \? (?&query) )? )
+    
+      (?<relative_ref>    (?&relative_part) (?: \? (?&query) )? (?: \# (?&fragment) )? )
+    
+      (?<relative_part>     \/\/ (?&authority) (?&path_abempty)
+                          | (?&path_absolute)
+                          | (?&path_noscheme)
+                          | (?&path_empty) )
+    
+      (?<scheme>    (?&ALPHA) (?: (?&ALPHA) | (?&DIGIT) | \+ | \- | \. )* )
+    
+      (?<authority>    (?: (?&userinfo) \@ )? (?&host) (?: \: (?&port) )? )
+      (?<userinfo>     (?: (?&unreserved) | (?&pct_encoded) | (?&sub_delims) | \: )* )
+      (?<host>         (?&ip_literal) | (?&ipv4_address) | (?&reg_name) )
+      (?<port>         (?&DIGIT)* )
+    
+      (?<ip_literal>    \[ (?: (?&ipv6_address) | (?&ipv_future) ) \] )
+    
+      (?<ipv_future>    \x76 (?&HEXDIG)+ \. (?: (?&unreserved) | (?&sub_delims) | \: )+ )
+    
+      (?<ipv6_address>                                              (?: (?&h16) \: ){6} (?&ls32)
+                        |                                      \:\: (?: (?&h16) \: ){5} (?&ls32)
+                        |                           (?&h16)?   \:\: (?: (?&h16) \: ){4} (?&ls32)
+                        | (?: (?: (?&h16) \: ){0,1} (?&h16) )? \:\: (?: (?&h16) \: ){3} (?&ls32)
+                        | (?: (?: (?&h16) \: ){0,2} (?&h16) )? \:\: (?: (?&h16) \: ){2} (?&ls32)
+                        | (?: (?: (?&h16) \: ){0,3} (?&h16) )? \:\:     (?&h16) \:      (?&ls32)
+                        | (?: (?: (?&h16) \: ){0,4} (?&h16) )? \:\:                     (?&ls32)
+                        | (?: (?: (?&h16) \: ){0,5} (?&h16) )? \:\:                     (?&h16)
+                        | (?: (?: (?&h16) \: ){0,6} (?&h16) )? \:\: )
+    
+      (?<h16>             (?&HEXDIG){1,4} )
+      (?<ls32>            (?: (?&h16) \: (?&h16) ) | (?&ipv4_address) )
+      (?<ipv4_address>    (?&dec_octet) \. (?&dec_octet) \. (?&dec_octet) \. (?&dec_octet) )
+    
+      (?<dec_octet>    (?&DIGIT)
+                     | [\x31-\x39] (?&DIGIT)
+                     | \x31 (?&DIGIT){2}
+                     | \x32 [\x30-\x34] (?&DIGIT)
+                     | \x32\x35 [\x30-\x35] )
+    
+      (?<reg_name>     (?: (?&unreserved) | (?&pct_encoded) | (?&sub_delims) )* )
+    
+      (?<path>    (?&path_abempty)
+                | (?&path_absolute)
+                | (?&path_noscheme)
+                | (?&path_rootless)
+                | (?&path_empty) )
+    
+      (?<path_abempty>     (?: \/ (?&segment) )* )
+      (?<path_absolute>    \/ (?: (?&segment_nz) (?: \/ (?&segment) )* )? )
+      (?<path_noscheme>    (?&segment_nz_nc) (?: \/ (?&segment) )* )
+      (?<path_rootless>    (?&segment_nz) (?: \/ (?&segment) )* )
+      (?<path_empty>       (?&pchar){0} ) # For explicity only
+    
+      (?<segment>       (?&pchar)* )
+      (?<segment_nz>    (?&pchar)+ )
+      (?<segment_nz_nc> (?: (?&unreserved) | (?&pct_encoded) | (?&sub_delims) | \@ )+ )
+    
+      (?<pchar>    (?&unreserved) | (?&pct_encoded) | (?&sub_delims) | \: | \@ )
+    
+      (?<query>    (?: (?&pchar) | \/ | \? )* )
+    
+      (?<fragment>    (?: (?&pchar) | \/ | \? )* )
+    
+      (?<pct_encoded>    \% (?&HEXDIG) (?&HEXDIG) )
+    
+      (?<unreserved>    (?&ALPHA) | (?&DIGIT) | \- | \. | \_ | \~ )
+      (?<reserved>      (?&gen_delims) | (?&sub_delims) )
+      (?<gen_delims>    \: | \/ | \? | \# | \[ | \] | \@ )
+      (?<sub_delims>    \! | \$ | \& | \' | \( | \)
+                      | \* | \+ | \, | \; | \= )
+    
+    )
+    ^(?&uri)$
+    =xi';
+    return (bool) preg_match($regex, $uri);
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Utility/UrlHelper.php b/core/lib/Drupal/Component/Utility/UrlHelper.php
index 1395d81..b20410f 100644
--- a/core/lib/Drupal/Component/Utility/UrlHelper.php
+++ b/core/lib/Drupal/Component/Utility/UrlHelper.php
@@ -7,7 +7,7 @@
  *
  * @ingroup utility
  */
-class UrlHelper {
+class UrlHelper extends UriHelper {
 
   /**
    * The list of allowed protocols.
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
index 5829e39..092ea93 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
 
+use Drupal\Component\Utility\UriHelper;
 use Drupal\Core\TypedData\Type\BinaryInterface;
 use Drupal\Core\TypedData\Type\BooleanInterface;
 use Drupal\Core\TypedData\Type\DateTimeInterface;
@@ -53,7 +54,7 @@ public function validate($value, Constraint $constraint) {
     // - That it is well formed (parse_url() returns FALSE if not).
     // - That it contains a scheme (parse_url(, PHP_URL_SCHEME) returns NULL if
     //   not).
-    if ($typed_data instanceof UriInterface && in_array(parse_url($value, PHP_URL_SCHEME), [NULL, FALSE], TRUE)) {
+    if ($typed_data instanceof UriInterface && !UriHelper::isValid($value)) {
       $valid = FALSE;
     }
     // @todo: Move those to separate constraint validators.
diff --git a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php
index 0de416f..6b291f5 100644
--- a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php
+++ b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php
@@ -109,4 +109,41 @@ public function testValidateIgnoresInternalUrls() {
     $validator->validate($link, $constraint);
   }
 
+  /**
+   * @covers ::validate
+   *
+   * @dataProvider provideTestValidateTel
+   *
+   * @see \Drupal\Core\Url::fromUri
+   */
+  public function testValidateTel($uri) {
+    $link = $this->getMock('Drupal\link\LinkItemInterface');
+    $link->expects($this->any())
+      ->method('getUrl')
+      ->willReturn(Url::fromUri($uri));
+
+    $context = $this->getMock('Symfony\Component\Validator\ExecutionContextInterface');
+    $context->expects($this->never())
+      ->method('addViolation');
+
+    $constraint = new LinkExternalProtocolsConstraint();
+
+    $validator = new LinkExternalProtocolsConstraintValidator();
+    $validator->initialize($context);
+    $validator->validate($link, $constraint);
+  }
+
+  /**
+   * Data provider for ::testValidateTel
+   */
+  public function provideTestValidateTel() {
+    $data = [];
+
+    $data[] = ['tel:911'];
+    $data[] = ['tel:65536'];
+    $data[] = ['tel:0123456789'];
+
+    return $data;
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php
index 6647e5f..52ad707 100644
--- a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php
+++ b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php
@@ -66,16 +66,22 @@ public function provideTestValidate() {
     $data[] = [new StringData(DataDefinition::create('string')), [], FALSE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'http://www.drupal.org', TRUE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'https://www.drupal.org', TRUE];
+    $data[] = [new Uri(DataDefinition::create('uri')), 'https://www.drupal.org?kitten=llama', TRUE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'Invalid', FALSE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'entity:node/1', TRUE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'base:', TRUE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'base:node', TRUE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'internal:', TRUE];
-    $data[] = [new Uri(DataDefinition::create('uri')), 'public://', FALSE];
+    // This is a valid URI.
+    //$data[] = [new Uri(DataDefinition::create('uri')), 'public://', FALSE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'public://foo.png', TRUE];
-    $data[] = [new Uri(DataDefinition::create('uri')), 'private://', FALSE];
+    // This is a valid URI.
+    //$data[] = [new Uri(DataDefinition::create('uri')), 'private://', FALSE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'private://foo.png', TRUE];
     $data[] = [new Uri(DataDefinition::create('uri')), 'drupal.org', FALSE];
+    $data[] = [new Uri(DataDefinition::create('uri')), 'tel:911', TRUE];
+    $data[] = [new Uri(DataDefinition::create('uri')), 'tel:65536', TRUE];
+    $data[] = [new Uri(DataDefinition::create('uri')), 'tel:0123456789', TRUE];
 
     return $data;
   }
