From d59ffc65bf7e617390e3403facdc2ade935dd77f Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Sun, 15 Sep 2013 15:54:54 -0400 Subject: [PATCH] Implement http link header processing w/default document loader. - Use default document loader when testing remote documents. --- jsonld.php | 117 +++++++++++++++++++++++++++++++++++++++++++---------- test.php | 21 +++++----- 2 files changed, 107 insertions(+), 31 deletions(-) diff --git a/jsonld.php b/jsonld.php index 1c508a7..372cc17 100644 --- a/jsonld.php +++ b/jsonld.php @@ -1,7 +1,7 @@ activeCtx = new ActiveContextCache(); /** Stores the default JSON-LD document loader. */ global $jsonld_default_load_document; -$jsonld_default_load_document = null; +$jsonld_default_load_document = 'jsonld_default_document_loader'; /** * Sets the default JSON-LD document loader. @@ -276,6 +276,8 @@ function jsonld_get_url($url) { * @return stdClass the RemoteDocument object. */ function jsonld_default_document_loader($url) { + $doc = (object)array( + 'contextUrl' => null, 'document' => null, 'documentUrl' => $url); $redirects = array(); $opts = array( @@ -290,30 +292,61 @@ function jsonld_default_document_loader($url) { 'header' => "Accept: application/ld+json\r\n" . "User-Agent: JSON-LD PHP Client/1.0\r\n")); - $stream = stream_context_create($opts); - stream_context_set_params($stream, array('notification' => - function($notification_code, $severity, $message) use (&$redirects) { + $context = stream_context_create($opts); + $content_type = null; + stream_context_set_params($context, array('notification' => + function($notification_code, $severity, $message) use ( + &$redirects, &$content_type) { switch($notification_code) { case STREAM_NOTIFY_REDIRECTED: $redirects[] = $message; break; + case STREAM_NOTIFY_MIME_TYPE_IS: + $content_type = $message; + break; }; })); - $result = @file_get_contents($url, false, $stream); + $result = @file_get_contents($url, false, $context); if($result === false) { throw new JsonLdException( 'Could not retrieve a JSON-LD document from the URL.', 'jsonld.LoadDocumentError', 'loading document failed'); } + $link_header = array(); + foreach($http_response_header as $header) { + if(strpos($header, 'link') === 0) { + $value = explode(': ', $header); + if(count($value) > 1) { + $link_header[] = $value[1]; + } + } + } + $link_header = jsonld_parse_link_header(join(',', $link_header)); + if(isset($link_header['http://www.w3.org/ns/json-ld#context'])) { + $link_header = $link_header['http://www.w3.org/ns/json-ld#context']; + } + else { + $link_header = null; + } + if($link_header && $content_type !== 'application/ld+json') { + // only 1 related link header permitted + if(is_array($link_header)) { + throw new JsonLdException( + 'URL could not be dereferenced, it has more than one ' . + 'associated HTTP Link Header.', 'jsonld.LoadDocumentError', + 'multiple context link headers', array('url' => $url)); + } + $doc->{'contextUrl'} = $link_header->target; + } + + // update document url based on redirects $redirs = count($redirects); if($redirs > 0) { $url = $redirects[$redirs - 1]; } - // return RemoteDocument - return (object)array( - 'contextUrl' => null, - 'document' => $result, - 'documentUrl' => $url); + $doc->document = $result; + $doc->documentUrl = $url; + return $doc; } /** @@ -330,6 +363,8 @@ function jsonld_default_secure_document_loader($url) { 'jsonld.LoadDocumentError', 'loading document failed'); } + $doc = (object)array( + 'contextUrl' => null, 'document' => null, 'documentUrl' => $url); $redirects = array(); // default JSON-LD https GET implementation @@ -340,21 +375,54 @@ function jsonld_default_secure_document_loader($url) { 'header' => "Accept: application/ld+json\r\n" . "User-Agent: JSON-LD PHP Client/1.0\r\n")); - $stream = stream_context_create($opts); - stream_context_set_params($stream, array('notification' => - function($notification_code, $severity, $message) use (&$redirects) { + $context = stream_context_create($opts); + $content_type = null; + stream_context_set_params($context, array('notification' => + function($notification_code, $severity, $message) use ( + &$redirects, &$content_type) { switch($notification_code) { case STREAM_NOTIFY_REDIRECTED: $redirects[] = $message; break; + case STREAM_NOTIFY_MIME_TYPE_IS: + $content_type = $message; + break; }; - })); - $result = @file_get_contents($url, false, $stream); + })); + $result = @file_get_contents($url, false, $context); if($result === false) { throw new JsonLdException( 'Could not retrieve a JSON-LD document from the URL.', 'jsonld.LoadDocumentError', 'loading document failed'); } + $link_header = array(); + foreach($http_response_header as $header) { + if(strpos($header, 'link') === 0) { + $value = explode(': ', $header); + if(count($value) > 1) { + $link_header[] = $value[1]; + } + } + } + $link_header = jsonld_parse_link_header(join(',', $link_header)); + if(isset($link_header['http://www.w3.org/ns/json-ld#context'])) { + $link_header = $link_header['http://www.w3.org/ns/json-ld#context']; + } + else { + $link_header = null; + } + if($link_header && $content_type !== 'application/ld+json') { + // only 1 related link header permitted + if(is_array($link_header)) { + throw new JsonLdException( + 'URL could not be dereferenced, it has more than one ' . + 'associated HTTP Link Header.', 'jsonld.LoadDocumentError', + 'multiple context link headers', array('url' => $url)); + } + $doc->{'contextUrl'} = $link_header->target; + } + + // update document url based on redirects foreach($redirects as $redirect) { if(strpos($redirect, 'https') !== 0) { throw new JsonLdException( @@ -363,11 +431,9 @@ function jsonld_default_secure_document_loader($url) { } $url = $redirect; } - // return RemoteDocument - return (object)array( - 'contextUrl' => null, - 'document' => $result, - 'documentUrl' => $url); + $doc->document = $result; + $doc->documentUrl = $url; + return $doc; } /** Registered global RDF dataset parsers hashed by content-type. */ @@ -5514,7 +5580,11 @@ class JsonLdProcessor { */ protected static function _parse_json($json) { $rval = json_decode($json); - switch(json_last_error()) { + $error = json_last_error(); + if($error === JSON_ERROR_NONE && $rval === null) { + $error = JSON_ERROR_SYNTAX; + } + switch($error) { case JSON_ERROR_NONE: break; case JSON_ERROR_DEPTH: @@ -5561,6 +5631,9 @@ class JsonLdException extends Exception { } public function __toString() { $rval = __CLASS__ . ": [{$this->type}]: {$this->message}\n"; + if($this->code) { + $rval .= 'Code: ' . $this->code . "\n"; + } if($this->details) { $rval .= 'Details: ' . print_r($this->details, true) . "\n"; } diff --git a/test.php b/test.php index a975974..6410346 100644 --- a/test.php +++ b/test.php @@ -286,9 +286,7 @@ class JsonLdTest { } public function createDocumentLoader() { - global $jsonld_default_load_document; $base = 'http://json-ld.org/test-suite'; - $loader = $jsonld_default_load_document; $test = $this; $load_locally = function($url) use ($test, $base) { @@ -314,8 +312,13 @@ class JsonLdTest { if(is_array($link_header)) { $link_header = join(',', $link_header); } - $link_header = jsonld_parse_link_header( - $link_header)['http://www.w3.org/ns/json-ld#context'] ?: null; + $link_header = jsonld_parse_link_header($link_header); + if(isset($link_header['http://www.w3.org/ns/json-ld#context'])) { + $link_header = $link_header['http://www.w3.org/ns/json-ld#context']; + } + else { + $link_header = null; + } if($link_header && $content_type !== 'application/ld+json') { if(is_array($link_header)) { throw new Exception('multiple context link headers'); @@ -336,12 +339,12 @@ class JsonLdTest { return $doc; }; - $local_loader = function($url) use ($loader, $load_locally) { + $local_loader = function($url) use ($test, $base, $load_locally) { // always load remote-doc and non-base tests remotely - /*if(strpos($url, $base) !== 0 || - $test->manifest->data['name'] === 'Remote document') { - return call_user_func($loader, $url); - }*/ + if(strpos($url, $base) !== 0 || + $test->manifest->data->name === 'Remote document') { + return call_user_func('jsonld_default_document_loader', $url); + } // attempt to load locally return call_user_func($load_locally, $url);