secure-custom-fields
/
vendor
/
justinrainbow
/
json-schema
/
src
/
JsonSchema
/
Uri
/
UriRetriever.php
secure-custom-fields
/
vendor
/
justinrainbow
/
json-schema
/
src
/
JsonSchema
/
Uri
Last commit date
Retrievers
9 months ago
UriResolver.php
9 months ago
UriRetriever.php
9 months ago
UriRetriever.php
350 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\Uri; |
| 11 | |
| 12 | use JsonSchema\Exception\InvalidSchemaMediaTypeException; |
| 13 | use JsonSchema\Exception\JsonDecodingException; |
| 14 | use JsonSchema\Exception\ResourceNotFoundException; |
| 15 | use JsonSchema\Uri\Retrievers\FileGetContents; |
| 16 | use JsonSchema\Uri\Retrievers\UriRetrieverInterface; |
| 17 | use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface; |
| 18 | use JsonSchema\Validator; |
| 19 | |
| 20 | /** |
| 21 | * Retrieves JSON Schema URIs |
| 22 | * |
| 23 | * @author Tyler Akins <fidian@rumkin.com> |
| 24 | */ |
| 25 | class UriRetriever implements BaseUriRetrieverInterface |
| 26 | { |
| 27 | /** |
| 28 | * @var array Map of URL translations |
| 29 | */ |
| 30 | protected $translationMap = array( |
| 31 | // use local copies of the spec schemas |
| 32 | '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json' |
| 33 | ); |
| 34 | |
| 35 | /** |
| 36 | * @var array A list of endpoints for media type check exclusion |
| 37 | */ |
| 38 | protected $allowedInvalidContentTypeEndpoints = array( |
| 39 | 'http://json-schema.org/', |
| 40 | 'https://json-schema.org/' |
| 41 | ); |
| 42 | |
| 43 | /** |
| 44 | * @var null|UriRetrieverInterface |
| 45 | */ |
| 46 | protected $uriRetriever = null; |
| 47 | |
| 48 | /** |
| 49 | * @var array|object[] |
| 50 | * |
| 51 | * @see loadSchema |
| 52 | */ |
| 53 | private $schemaCache = array(); |
| 54 | |
| 55 | /** |
| 56 | * Adds an endpoint to the media type validation exclusion list |
| 57 | * |
| 58 | * @param string $endpoint |
| 59 | */ |
| 60 | public function addInvalidContentTypeEndpoint($endpoint) |
| 61 | { |
| 62 | $this->allowedInvalidContentTypeEndpoints[] = $endpoint; |
| 63 | } |
| 64 | |
| 65 | /** |
| 66 | * Guarantee the correct media type was encountered |
| 67 | * |
| 68 | * @param UriRetrieverInterface $uriRetriever |
| 69 | * @param string $uri |
| 70 | * |
| 71 | * @return bool|void |
| 72 | */ |
| 73 | public function confirmMediaType($uriRetriever, $uri) |
| 74 | { |
| 75 | $contentType = $uriRetriever->getContentType(); |
| 76 | |
| 77 | if (is_null($contentType)) { |
| 78 | // Well, we didn't get an invalid one |
| 79 | return; |
| 80 | } |
| 81 | |
| 82 | if (in_array($contentType, array(Validator::SCHEMA_MEDIA_TYPE, 'application/json'))) { |
| 83 | return; |
| 84 | } |
| 85 | |
| 86 | foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) { |
| 87 | if (strpos($uri, $endpoint) === 0) { |
| 88 | return true; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE)); |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Get a URI Retriever |
| 97 | * |
| 98 | * If none is specified, sets a default FileGetContents retriever and |
| 99 | * returns that object. |
| 100 | * |
| 101 | * @return UriRetrieverInterface |
| 102 | */ |
| 103 | public function getUriRetriever() |
| 104 | { |
| 105 | if (is_null($this->uriRetriever)) { |
| 106 | $this->setUriRetriever(new FileGetContents()); |
| 107 | } |
| 108 | |
| 109 | return $this->uriRetriever; |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Resolve a schema based on pointer |
| 114 | * |
| 115 | * URIs can have a fragment at the end in the format of |
| 116 | * #/path/to/object and we are to look up the 'path' property of |
| 117 | * the first object then the 'to' and 'object' properties. |
| 118 | * |
| 119 | * @param object $jsonSchema JSON Schema contents |
| 120 | * @param string $uri JSON Schema URI |
| 121 | * |
| 122 | * @throws ResourceNotFoundException |
| 123 | * |
| 124 | * @return object JSON Schema after walking down the fragment pieces |
| 125 | */ |
| 126 | public function resolvePointer($jsonSchema, $uri) |
| 127 | { |
| 128 | $resolver = new UriResolver(); |
| 129 | $parsed = $resolver->parse($uri); |
| 130 | if (empty($parsed['fragment'])) { |
| 131 | return $jsonSchema; |
| 132 | } |
| 133 | |
| 134 | $path = explode('/', $parsed['fragment']); |
| 135 | while ($path) { |
| 136 | $pathElement = array_shift($path); |
| 137 | if (!empty($pathElement)) { |
| 138 | $pathElement = str_replace('~1', '/', $pathElement); |
| 139 | $pathElement = str_replace('~0', '~', $pathElement); |
| 140 | if (!empty($jsonSchema->$pathElement)) { |
| 141 | $jsonSchema = $jsonSchema->$pathElement; |
| 142 | } else { |
| 143 | throw new ResourceNotFoundException( |
| 144 | 'Fragment "' . $parsed['fragment'] . '" not found' |
| 145 | . ' in ' . $uri |
| 146 | ); |
| 147 | } |
| 148 | |
| 149 | if (!is_object($jsonSchema)) { |
| 150 | throw new ResourceNotFoundException( |
| 151 | 'Fragment part "' . $pathElement . '" is no object ' |
| 152 | . ' in ' . $uri |
| 153 | ); |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | return $jsonSchema; |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * {@inheritdoc} |
| 163 | */ |
| 164 | public function retrieve($uri, $baseUri = null, $translate = true) |
| 165 | { |
| 166 | $resolver = new UriResolver(); |
| 167 | $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); |
| 168 | |
| 169 | //fetch URL without #fragment |
| 170 | $arParts = $resolver->parse($resolvedUri); |
| 171 | if (isset($arParts['fragment'])) { |
| 172 | unset($arParts['fragment']); |
| 173 | $fetchUri = $resolver->generate($arParts); |
| 174 | } |
| 175 | |
| 176 | // apply URI translations |
| 177 | if ($translate) { |
| 178 | $fetchUri = $this->translate($fetchUri); |
| 179 | } |
| 180 | |
| 181 | $jsonSchema = $this->loadSchema($fetchUri); |
| 182 | |
| 183 | // Use the JSON pointer if specified |
| 184 | $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); |
| 185 | |
| 186 | if ($jsonSchema instanceof \stdClass) { |
| 187 | $jsonSchema->id = $resolvedUri; |
| 188 | } |
| 189 | |
| 190 | return $jsonSchema; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Fetch a schema from the given URI, json-decode it and return it. |
| 195 | * Caches schema objects. |
| 196 | * |
| 197 | * @param string $fetchUri Absolute URI |
| 198 | * |
| 199 | * @return object JSON schema object |
| 200 | */ |
| 201 | protected function loadSchema($fetchUri) |
| 202 | { |
| 203 | if (isset($this->schemaCache[$fetchUri])) { |
| 204 | return $this->schemaCache[$fetchUri]; |
| 205 | } |
| 206 | |
| 207 | $uriRetriever = $this->getUriRetriever(); |
| 208 | $contents = $this->uriRetriever->retrieve($fetchUri); |
| 209 | $this->confirmMediaType($uriRetriever, $fetchUri); |
| 210 | $jsonSchema = json_decode($contents); |
| 211 | |
| 212 | if (JSON_ERROR_NONE < $error = json_last_error()) { |
| 213 | throw new JsonDecodingException($error); |
| 214 | } |
| 215 | |
| 216 | $this->schemaCache[$fetchUri] = $jsonSchema; |
| 217 | |
| 218 | return $jsonSchema; |
| 219 | } |
| 220 | |
| 221 | /** |
| 222 | * Set the URI Retriever |
| 223 | * |
| 224 | * @param UriRetrieverInterface $uriRetriever |
| 225 | * |
| 226 | * @return $this for chaining |
| 227 | */ |
| 228 | public function setUriRetriever(UriRetrieverInterface $uriRetriever) |
| 229 | { |
| 230 | $this->uriRetriever = $uriRetriever; |
| 231 | |
| 232 | return $this; |
| 233 | } |
| 234 | |
| 235 | /** |
| 236 | * Parses a URI into five main components |
| 237 | * |
| 238 | * @param string $uri |
| 239 | * |
| 240 | * @return array |
| 241 | */ |
| 242 | public function parse($uri) |
| 243 | { |
| 244 | preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); |
| 245 | |
| 246 | $components = array(); |
| 247 | if (5 < count($match)) { |
| 248 | $components = array( |
| 249 | 'scheme' => $match[2], |
| 250 | 'authority' => $match[4], |
| 251 | 'path' => $match[5] |
| 252 | ); |
| 253 | } |
| 254 | |
| 255 | if (7 < count($match)) { |
| 256 | $components['query'] = $match[7]; |
| 257 | } |
| 258 | |
| 259 | if (9 < count($match)) { |
| 260 | $components['fragment'] = $match[9]; |
| 261 | } |
| 262 | |
| 263 | return $components; |
| 264 | } |
| 265 | |
| 266 | /** |
| 267 | * Builds a URI based on n array with the main components |
| 268 | * |
| 269 | * @param array $components |
| 270 | * |
| 271 | * @return string |
| 272 | */ |
| 273 | public function generate(array $components) |
| 274 | { |
| 275 | $uri = $components['scheme'] . '://' |
| 276 | . $components['authority'] |
| 277 | . $components['path']; |
| 278 | |
| 279 | if (array_key_exists('query', $components)) { |
| 280 | $uri .= $components['query']; |
| 281 | } |
| 282 | |
| 283 | if (array_key_exists('fragment', $components)) { |
| 284 | $uri .= $components['fragment']; |
| 285 | } |
| 286 | |
| 287 | return $uri; |
| 288 | } |
| 289 | |
| 290 | /** |
| 291 | * Resolves a URI |
| 292 | * |
| 293 | * @param string $uri Absolute or relative |
| 294 | * @param string $baseUri Optional base URI |
| 295 | * |
| 296 | * @return string |
| 297 | */ |
| 298 | public function resolve($uri, $baseUri = null) |
| 299 | { |
| 300 | $components = $this->parse($uri); |
| 301 | $path = $components['path']; |
| 302 | |
| 303 | if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) { |
| 304 | return $uri; |
| 305 | } |
| 306 | |
| 307 | $baseComponents = $this->parse($baseUri); |
| 308 | $basePath = $baseComponents['path']; |
| 309 | |
| 310 | $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath); |
| 311 | |
| 312 | return $this->generate($baseComponents); |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * @param string $uri |
| 317 | * |
| 318 | * @return bool |
| 319 | */ |
| 320 | public function isValid($uri) |
| 321 | { |
| 322 | $components = $this->parse($uri); |
| 323 | |
| 324 | return !empty($components); |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Set a URL translation rule |
| 329 | */ |
| 330 | public function setTranslation($from, $to) |
| 331 | { |
| 332 | $this->translationMap[$from] = $to; |
| 333 | } |
| 334 | |
| 335 | /** |
| 336 | * Apply URI translation rules |
| 337 | */ |
| 338 | public function translate($uri) |
| 339 | { |
| 340 | foreach ($this->translationMap as $from => $to) { |
| 341 | $uri = preg_replace($from, $to, $uri); |
| 342 | } |
| 343 | |
| 344 | // translate references to local files within the json-schema package |
| 345 | $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri); |
| 346 | |
| 347 | return $uri; |
| 348 | } |
| 349 | } |
| 350 |