Skip to content

Commit 080e374

Browse files
committed
Support rel=alternate.
Note XHR and Node loaders don't have their own tests.
1 parent ff95ee2 commit 080e374

File tree

4 files changed

+51
-19
lines changed

4 files changed

+51
-19
lines changed

lib/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
77
const XSD = 'http://www.w3.org/2001/XMLSchema#';
88

99
module.exports = {
10-
LINK_HEADER_REL: 'http://www.w3.org/ns/json-ld#context',
10+
LINK_HEADER_CONTEXT: 'http://www.w3.org/ns/json-ld#context',
1111

1212
RDF,
1313
RDF_LIST: RDF + 'List',

lib/documentLoaders/node.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
'use strict';
55

66
const {parseLinkHeader, buildHeaders} = require('../util');
7-
const {LINK_HEADER_REL} = require('../constants');
7+
const {LINK_HEADER_CONTEXT} = require('../constants');
88
const JsonLdError = require('../JsonLdError');
99
const RequestQueue = require('../RequestQueue');
10+
const {prependBase} = require('../url');
1011

1112
/**
1213
* Creates a built-in node document loader.
@@ -61,6 +62,7 @@ module.exports = ({
6162
}
6263

6364
let result;
65+
let alternate = null;
6466
try {
6567
result = await _request(request, {
6668
url,
@@ -95,21 +97,30 @@ module.exports = ({
9597
if(res.headers.link &&
9698
res.headers['content-type'] !== 'application/ld+json') {
9799
// only 1 related link header permitted
98-
const linkHeader = parseLinkHeader(res.headers.link)[LINK_HEADER_REL];
99-
if(Array.isArray(linkHeader)) {
100+
const linkHeaders = parseLinkHeader(res.headers.link);
101+
const linkedContext = linkHeaders[LINK_HEADER_CONTEXT];
102+
if(Array.isArray(linkedContext)) {
100103
throw new JsonLdError(
101104
'URL could not be dereferenced, it has more than one associated ' +
102105
'HTTP Link Header.',
103106
'jsonld.InvalidUrl',
104107
{code: 'multiple context link headers', url});
105108
}
106-
if(linkHeader) {
107-
doc.contextUrl = linkHeader.target;
109+
if(linkedContext) {
110+
doc.contextUrl = linkedContext.target;
111+
}
112+
113+
// "alternate" link header is a redirect
114+
alternate = linkHeaders['alternate'];
115+
if(alternate &&
116+
alternate.type == 'application/ld+json' &&
117+
!(res.headers['content-type'] || '').match(/^application\/(\w*\+)?json$/)) {
118+
res.headers.location = prependBase(url, alternate.target);
108119
}
109120
}
110121

111122
// handle redirect
112-
if(res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
123+
if((alternate || res.statusCode >= 300 && res.statusCode < 400) && res.headers.location) {
113124
if(redirects.length === maxRedirects) {
114125
throw new JsonLdError(
115126
'URL could not be dereferenced; there were too many redirects.',

lib/documentLoaders/xhr.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
'use strict';
55

66
const {parseLinkHeader, buildHeaders} = require('../util');
7-
const {LINK_HEADER_REL} = require('../constants');
7+
const {LINK_HEADER_CONTEXT} = require('../constants');
88
const JsonLdError = require('../JsonLdError');
99
const RequestQueue = require('../RequestQueue');
10+
const {prependBase} = require('../url');
1011

1112
const REGEX_LINK_HEADER = /(^|(\r\n))link:/i;
1213

@@ -65,6 +66,7 @@ module.exports = ({
6566
}
6667

6768
const doc = {contextUrl: null, documentUrl: url, document: req.response};
69+
let alternate = null;
6870

6971
// handle Link Header (avoid unsafe header warning by existence testing)
7072
const contentType = req.getResponseHeader('Content-Type');
@@ -74,16 +76,25 @@ module.exports = ({
7476
}
7577
if(linkHeader && contentType !== 'application/ld+json') {
7678
// only 1 related link header permitted
77-
linkHeader = parseLinkHeader(linkHeader)[LINK_HEADER_REL];
78-
if(Array.isArray(linkHeader)) {
79+
const linkHeaders = parseLinkHeader(res.headers.link);
80+
const linkedContext = linkHeaders[LINK_HEADER_CONTEXT];
81+
if(Array.isArray(linkedContext)) {
7982
throw new JsonLdError(
8083
'URL could not be dereferenced, it has more than one ' +
8184
'associated HTTP Link Header.',
8285
'jsonld.InvalidUrl',
8386
{code: 'multiple context link headers', url});
8487
}
85-
if(linkHeader) {
86-
doc.contextUrl = linkHeader.target;
88+
if(linkedContext) {
89+
doc.contextUrl = linkedContext.target;
90+
}
91+
92+
// "alternate" link header is a redirect
93+
alternate = linkHeaders['alternate'];
94+
if(alternate &&
95+
alternate.type == 'application/ld+json' &&
96+
!(contentType || '').match(/^application\/(\w*\+)?json$/)) {
97+
doc = await loader(prependBase(url, alternate.target));
8798
}
8899
}
89100

tests/test-common.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const EarlReport = require('./earl-report');
66
const benchmark = require('benchmark');
77
const join = require('join-path-js');
88
const rdfCanonize = require('rdf-canonize');
9+
const {prependBase} = require('../lib/url');
910

1011
module.exports = function(options) {
1112

@@ -102,8 +103,6 @@ const TEST_TYPES = {
102103
/expand-manifest.jsonld#thc05$/,
103104
// remote
104105
/remote-doc-manifest.jsonld#t0013$/, // HTML
105-
/remote-doc-manifest.jsonld#tla01$/,
106-
/remote-doc-manifest.jsonld#tla05$/,
107106
// @import
108107
/expand-manifest.jsonld#tso01$/,
109108
/expand-manifest.jsonld#tso02$/,
@@ -1120,17 +1119,28 @@ function createDocumentLoader(test) {
11201119
if(!contentType && url.indexOf('.jsonld', url.length - 7) !== -1) {
11211120
contentType = 'application/ld+json';
11221121
}
1122+
if(!contentType && url.indexOf('.json', url.length - 5) !== -1) {
1123+
contentType = 'application/json';
1124+
}
11231125
let linkHeader = options.httpLink;
11241126
if(Array.isArray(linkHeader)) {
11251127
linkHeader = linkHeader.join(',');
11261128
}
1127-
linkHeader = jsonld.parseLinkHeader(
1128-
linkHeader)['http://www.w3.org/ns/json-ld#context'];
1129-
if(linkHeader && contentType !== 'application/ld+json') {
1130-
if(Array.isArray(linkHeader)) {
1129+
const linkHeaders = jsonld.parseLinkHeader(
1130+
linkHeader)
1131+
const linkedContext = linkHeaders['http://www.w3.org/ns/json-ld#context'];
1132+
if(linkedContext && contentType !== 'application/ld+json') {
1133+
if(Array.isArray(linkedContext)) {
11311134
throw {name: 'multiple context link headers'};
11321135
}
1133-
doc.contextUrl = linkHeader.target;
1136+
doc.contextUrl = linkedContext.target;
1137+
}
1138+
1139+
// If not JSON-LD, alternate may point there
1140+
if(linkHeaders['alternate'] &&
1141+
linkHeaders['alternate'].type == 'application/ld+json' &&
1142+
!(contentType || '').match(/^application\/(\w*\+)?json$/)) {
1143+
doc.documentUrl = prependBase(url, linkHeaders['alternate'].target);
11341144
}
11351145
}
11361146
}

0 commit comments

Comments
 (0)