1 <?php
  2 /**
  3  * @package     Joomla.Platform
  4  * @subpackage  Form
  5  *
  6  * @copyright   Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
  7  * @license     GNU General Public License version 2 or later; see LICENSE
  8  */
  9 
 10 defined('JPATH_PLATFORM') or die;
 11 
 12 use Joomla\Registry\Registry;
 13 use Joomla\String\StringHelper;
 14 use Joomla\Uri\UriHelper;
 15 
 16 /**
 17  * Form Rule class for the Joomla Platform.
 18  *
 19  * @since  11.1
 20  */
 21 class JFormRuleUrl extends JFormRule
 22 {
 23     /**
 24      * Method to test an external or internal url for all valid parts.
 25      *
 26      * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
 27      * @param   mixed             $value    The form field value to validate.
 28      * @param   string            $group    The field name group control value. This acts as an array container for the field.
 29      *                                      For example if the field has name="foo" and the group value is set to "bar" then the
 30      *                                      full field name would end up being "bar[foo]".
 31      * @param   Registry          $input    An optional Registry object with the entire data set to validate against the entire form.
 32      * @param   JForm             $form     The form object for which the field is being tested.
 33      *
 34      * @return  boolean  True if the value is valid, false otherwise.
 35      *
 36      * @since   11.1
 37      * @link    http://www.w3.org/Addressing/URL/url-spec.txt
 38      * @see     JString
 39      */
 40     public function test(SimpleXMLElement $element, $value, $group = null, Registry $input = null, JForm $form = null)
 41     {
 42         // If the field is empty and not required, the field is valid.
 43         $required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');
 44 
 45         if (!$required && empty($value))
 46         {
 47             return true;
 48         }
 49 
 50         $urlParts = UriHelper::parse_url($value);
 51 
 52         // See http://www.w3.org/Addressing/URL/url-spec.txt
 53         // Use the full list or optionally specify a list of permitted schemes.
 54         if ($element['schemes'] == '')
 55         {
 56             $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'mailto', 'news', 'prospero', 'telnet', 'rlogin', 'sftp', 'tn3270', 'wais', 'url',
 57                 'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', 'fax', 'modem', 'git');
 58         }
 59         else
 60         {
 61             $scheme = explode(',', $element['schemes']);
 62         }
 63 
 64         /*
 65          * Note that parse_url() does not always parse accurately without a scheme,
 66          * but at least the path should be set always. Note also that parse_url()
 67          * returns False for seriously malformed URLs instead of an associative array.
 68          * @link https://secure.php.net/manual/en/function.parse-url.php
 69          */
 70         if ($urlParts === false or !array_key_exists('scheme', $urlParts))
 71         {
 72             /*
 73              * The function parse_url() returned false (seriously malformed URL) or no scheme
 74              * was found and the relative option is not set: in both cases the field is not valid.
 75              */
 76             if ($urlParts === false or !$element['relative'])
 77             {
 78                 $element->addAttribute('message', JText::sprintf('JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value, implode(', ', $scheme)));
 79 
 80                 return false;
 81             }
 82             // The best we can do for the rest is make sure that the path exists and is valid UTF-8.
 83             if (!array_key_exists('path', $urlParts) || !StringHelper::valid((string) $urlParts['path']))
 84             {
 85                 return false;
 86             }
 87             // The internal URL seems to be good.
 88             return true;
 89         }
 90 
 91         // Scheme found, check all parts found.
 92         $urlScheme = (string) $urlParts['scheme'];
 93         $urlScheme = strtolower($urlScheme);
 94 
 95         if (in_array($urlScheme, $scheme) == false)
 96         {
 97             return false;
 98         }
 99 
100         // For some schemes here must be two slashes.
101         $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'wais', 'prospero', 'sftp', 'telnet', 'git');
102 
103         if (in_array($urlScheme, $scheme) && substr($value, strlen($urlScheme), 3) !== '://')
104         {
105             return false;
106         }
107 
108         // The best we can do for the rest is make sure that the strings are valid UTF-8
109         // and the port is an integer.
110         if (array_key_exists('host', $urlParts) && !StringHelper::valid((string) $urlParts['host']))
111         {
112             return false;
113         }
114 
115         if (array_key_exists('port', $urlParts) && !is_int((int) $urlParts['port']))
116         {
117             return false;
118         }
119 
120         if (array_key_exists('path', $urlParts) && !StringHelper::valid((string) $urlParts['path']))
121         {
122             return false;
123         }
124 
125         return true;
126     }
127 }
128