jsonld
Version:
A JSON-LD Processor and API implementation in JavaScript.
118 lines (106 loc) • 3.73 kB
JavaScript
/*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
;
const {parseLinkHeader, buildHeaders} = require('../util');
const {LINK_HEADER_CONTEXT} = require('../constants');
const JsonLdError = require('../JsonLdError');
const RequestQueue = require('../RequestQueue');
const {prependBase} = require('../url');
const REGEX_LINK_HEADER = /(^|(\r\n))link:/i;
/**
* Creates a built-in XMLHttpRequest document loader.
*
* @param options the options to use:
* secure: require all URLs to use HTTPS.
* headers: an object (map) of headers which will be passed as request
* headers for the requested document. Accept is not allowed.
* [xhr]: the XMLHttpRequest API to use.
*
* @return the XMLHttpRequest document loader.
*/
module.exports = ({
secure,
headers = {},
xhr
} = {headers: {}}) => {
headers = buildHeaders(headers);
const queue = new RequestQueue();
return queue.wrapLoader(loader);
async function loader(url) {
if(url.indexOf('http:') !== 0 && url.indexOf('https:') !== 0) {
throw new JsonLdError(
'URL could not be dereferenced; only "http" and "https" URLs are ' +
'supported.',
'jsonld.InvalidUrl', {code: 'loading document failed', url});
}
if(secure && url.indexOf('https') !== 0) {
throw new JsonLdError(
'URL could not be dereferenced; secure mode is enabled and ' +
'the URL\'s scheme is not "https".',
'jsonld.InvalidUrl', {code: 'loading document failed', url});
}
let req;
try {
req = await _get(xhr, url, headers);
} catch(e) {
throw new JsonLdError(
'URL could not be dereferenced, an error occurred.',
'jsonld.LoadDocumentError',
{code: 'loading document failed', url, cause: e});
}
if(req.status >= 400) {
throw new JsonLdError(
'URL could not be dereferenced: ' + req.statusText,
'jsonld.LoadDocumentError', {
code: 'loading document failed',
url,
httpStatusCode: req.status
});
}
let doc = {contextUrl: null, documentUrl: url, document: req.response};
let alternate = null;
// handle Link Header (avoid unsafe header warning by existence testing)
const contentType = req.getResponseHeader('Content-Type');
let linkHeader;
if(REGEX_LINK_HEADER.test(req.getAllResponseHeaders())) {
linkHeader = req.getResponseHeader('Link');
}
if(linkHeader && contentType !== 'application/ld+json') {
// only 1 related link header permitted
const linkHeaders = parseLinkHeader(linkHeader);
const linkedContext = linkHeaders[LINK_HEADER_CONTEXT];
if(Array.isArray(linkedContext)) {
throw new JsonLdError(
'URL could not be dereferenced, it has more than one ' +
'associated HTTP Link Header.',
'jsonld.InvalidUrl',
{code: 'multiple context link headers', url});
}
if(linkedContext) {
doc.contextUrl = linkedContext.target;
}
// "alternate" link header is a redirect
alternate = linkHeaders.alternate;
if(alternate &&
alternate.type == 'application/ld+json' &&
!(contentType || '').match(/^application\/(\w*\+)?json$/)) {
doc = await loader(prependBase(url, alternate.target));
}
}
return doc;
}
};
function _get(xhr, url, headers) {
xhr = xhr || XMLHttpRequest;
const req = new xhr();
return new Promise((resolve, reject) => {
req.onload = () => resolve(req);
req.onerror = err => reject(err);
req.open('GET', url, true);
for(const k in headers) {
req.setRequestHeader(k, headers[k]);
}
req.send();
});
}