secure-custom-fields
/
vendor
/
justinrainbow
/
json-schema
/
src
/
JsonSchema
/
Constraints
/
FormatConstraint.php
secure-custom-fields
/
vendor
/
justinrainbow
/
json-schema
/
src
/
JsonSchema
/
Constraints
Last commit date
TypeCheck
8 months ago
BaseConstraint.php
8 months ago
CollectionConstraint.php
8 months ago
Constraint.php
8 months ago
ConstraintInterface.php
8 months ago
EnumConstraint.php
8 months ago
Factory.php
8 months ago
FormatConstraint.php
8 months ago
NumberConstraint.php
8 months ago
ObjectConstraint.php
8 months ago
SchemaConstraint.php
8 months ago
StringConstraint.php
8 months ago
TypeConstraint.php
8 months ago
UndefinedConstraint.php
8 months ago
FormatConstraint.php
215 lines
| 1 | <?php |
| 2 | |
| 3 | /* |
| 4 | * This file is part of the JsonSchema package. |
| 5 | * |
| 6 | * For the full copyright and license information, please view the LICENSE |
| 7 | * file that was distributed with this source code. |
| 8 | */ |
| 9 | |
| 10 | namespace JsonSchema\Constraints; |
| 11 | |
| 12 | use JsonSchema\Entity\JsonPointer; |
| 13 | use JsonSchema\Rfc3339; |
| 14 | |
| 15 | /** |
| 16 | * Validates against the "format" property |
| 17 | * |
| 18 | * @author Justin Rainbow <justin.rainbow@gmail.com> |
| 19 | * |
| 20 | * @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 |
| 21 | */ |
| 22 | class FormatConstraint extends Constraint |
| 23 | { |
| 24 | /** |
| 25 | * {@inheritdoc} |
| 26 | */ |
| 27 | public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null) |
| 28 | { |
| 29 | if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { |
| 30 | return; |
| 31 | } |
| 32 | |
| 33 | switch ($schema->format) { |
| 34 | case 'date': |
| 35 | if (!$date = $this->validateDateTime($element, 'Y-m-d')) { |
| 36 | $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format)); |
| 37 | } |
| 38 | break; |
| 39 | |
| 40 | case 'time': |
| 41 | if (!$this->validateDateTime($element, 'H:i:s')) { |
| 42 | $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format)); |
| 43 | } |
| 44 | break; |
| 45 | |
| 46 | case 'date-time': |
| 47 | if (null === Rfc3339::createFromString($element)) { |
| 48 | $this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format)); |
| 49 | } |
| 50 | break; |
| 51 | |
| 52 | case 'utc-millisec': |
| 53 | if (!$this->validateDateTime($element, 'U')) { |
| 54 | $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format)); |
| 55 | } |
| 56 | break; |
| 57 | |
| 58 | case 'regex': |
| 59 | if (!$this->validateRegex($element)) { |
| 60 | $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format)); |
| 61 | } |
| 62 | break; |
| 63 | |
| 64 | case 'color': |
| 65 | if (!$this->validateColor($element)) { |
| 66 | $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format)); |
| 67 | } |
| 68 | break; |
| 69 | |
| 70 | case 'style': |
| 71 | if (!$this->validateStyle($element)) { |
| 72 | $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format)); |
| 73 | } |
| 74 | break; |
| 75 | |
| 76 | case 'phone': |
| 77 | if (!$this->validatePhone($element)) { |
| 78 | $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format)); |
| 79 | } |
| 80 | break; |
| 81 | |
| 82 | case 'uri': |
| 83 | if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { |
| 84 | $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); |
| 85 | } |
| 86 | break; |
| 87 | |
| 88 | case 'uriref': |
| 89 | case 'uri-reference': |
| 90 | if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { |
| 91 | // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but |
| 92 | // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them. |
| 93 | // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information. |
| 94 | if (substr($element, 0, 2) === '//') { // network-path reference |
| 95 | $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
| 96 | } elseif (substr($element, 0, 1) === '/') { // absolute-path reference |
| 97 | $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
| 98 | } elseif (strlen($element)) { // relative-path reference |
| 99 | $pathParts = explode('/', $element, 2); |
| 100 | if (strpos($pathParts[0], ':') !== false) { |
| 101 | $validURL = null; |
| 102 | } else { |
| 103 | $validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
| 104 | } |
| 105 | } else { |
| 106 | $validURL = null; |
| 107 | } |
| 108 | if ($validURL === null) { |
| 109 | $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); |
| 110 | } |
| 111 | } |
| 112 | break; |
| 113 | |
| 114 | case 'email': |
| 115 | $filterFlags = FILTER_NULL_ON_FAILURE; |
| 116 | if (defined('FILTER_FLAG_EMAIL_UNICODE')) { |
| 117 | // Only available from PHP >= 7.1.0, so ignore it for coverage checks |
| 118 | $filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore |
| 119 | } |
| 120 | if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) { |
| 121 | $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format)); |
| 122 | } |
| 123 | break; |
| 124 | |
| 125 | case 'ip-address': |
| 126 | case 'ipv4': |
| 127 | if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { |
| 128 | $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); |
| 129 | } |
| 130 | break; |
| 131 | |
| 132 | case 'ipv6': |
| 133 | if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { |
| 134 | $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); |
| 135 | } |
| 136 | break; |
| 137 | |
| 138 | case 'host-name': |
| 139 | case 'hostname': |
| 140 | if (!$this->validateHostname($element)) { |
| 141 | $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format)); |
| 142 | } |
| 143 | break; |
| 144 | |
| 145 | default: |
| 146 | // Empty as it should be: |
| 147 | // The value of this keyword is called a format attribute. It MUST be a string. |
| 148 | // A format attribute can generally only validate a given set of instance types. |
| 149 | // If the type of the instance to validate is not in this set, validation for |
| 150 | // this format attribute and instance SHOULD succeed. |
| 151 | // http://json-schema.org/latest/json-schema-validation.html#anchor105 |
| 152 | break; |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | protected function validateDateTime($datetime, $format) |
| 157 | { |
| 158 | $dt = \DateTime::createFromFormat($format, $datetime); |
| 159 | |
| 160 | if (!$dt) { |
| 161 | return false; |
| 162 | } |
| 163 | |
| 164 | if ($datetime === $dt->format($format)) { |
| 165 | return true; |
| 166 | } |
| 167 | |
| 168 | // handles the case where a non-6 digit microsecond datetime is passed |
| 169 | // which will fail the above string comparison because the passed |
| 170 | // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return |
| 171 | // '2000-05-01T12:12:12.123000Z' |
| 172 | if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) { |
| 173 | return true; |
| 174 | } |
| 175 | |
| 176 | return false; |
| 177 | } |
| 178 | |
| 179 | protected function validateRegex($regex) |
| 180 | { |
| 181 | return false !== @preg_match('/' . $regex . '/u', ''); |
| 182 | } |
| 183 | |
| 184 | protected function validateColor($color) |
| 185 | { |
| 186 | if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia', |
| 187 | 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', |
| 188 | 'red', 'silver', 'teal', 'white', 'yellow'))) { |
| 189 | return true; |
| 190 | } |
| 191 | |
| 192 | return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); |
| 193 | } |
| 194 | |
| 195 | protected function validateStyle($style) |
| 196 | { |
| 197 | $properties = explode(';', rtrim($style, ';')); |
| 198 | $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); |
| 199 | |
| 200 | return empty($invalidEntries); |
| 201 | } |
| 202 | |
| 203 | protected function validatePhone($phone) |
| 204 | { |
| 205 | return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); |
| 206 | } |
| 207 | |
| 208 | protected function validateHostname($host) |
| 209 | { |
| 210 | $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i'; |
| 211 | |
| 212 | return preg_match($hostnameRegex, $host); |
| 213 | } |
| 214 | } |
| 215 |