UNPKG

@inrupt/solid-client

Version:
162 lines (159 loc) • 7.4 kB
import LinkHeader from 'http-link-header'; import { clone } from '../rdfjs.mjs'; /** * Copyright 2020 Inrupt Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the * Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * @internal */ function internal_parseResourceInfo(response) { var _a, _b, _c; const contentTypeParts = (_b = (_a = response.headers.get("Content-Type")) === null || _a === void 0 ? void 0 : _a.split(";")) !== null && _b !== void 0 ? _b : []; // If the server offers a Turtle or JSON-LD serialisation on its own accord, // that tells us whether it is RDF data that the server can understand // (and hence can be updated with a PATCH request with SPARQL INSERT and DELETE statements), // in which case our SolidDataset-related functions should handle it. // For more context, see https://github.com/inrupt/solid-client-js/pull/214. const isSolidDataset = contentTypeParts.length > 0 && ["text/turtle", "application/ld+json"].includes(contentTypeParts[0]); const resourceInfo = { sourceIri: response.url, isRawData: !isSolidDataset, contentType: (_c = response.headers.get("Content-Type")) !== null && _c !== void 0 ? _c : undefined, linkedResources: {}, }; const linkHeader = response.headers.get("Link"); if (linkHeader) { const parsedLinks = LinkHeader.parse(linkHeader); // Set ACL link const aclLinks = parsedLinks.get("rel", "acl"); if (aclLinks.length === 1) { resourceInfo.aclUrl = new URL(aclLinks[0].uri, resourceInfo.sourceIri).href; } // Parse all link headers and expose them in a standard way // (this can replace the parsing of the ACL link above): resourceInfo.linkedResources = parsedLinks.refs.reduce((rels, ref) => { var _a; var _b; (_a = rels[_b = ref.rel]) !== null && _a !== void 0 ? _a : (rels[_b] = []); rels[ref.rel].push(new URL(ref.uri, resourceInfo.sourceIri).href); return rels; }, resourceInfo.linkedResources); } const wacAllowHeader = response.headers.get("WAC-Allow"); if (wacAllowHeader) { resourceInfo.permissions = parseWacAllowHeader(wacAllowHeader); } return resourceInfo; } /** * Parse a WAC-Allow header into user and public access booleans. * * @param wacAllowHeader A WAC-Allow header in the format `user="read append write control",public="read"` * @see https://github.com/solid/solid-spec/blob/cb1373a369398d561b909009bd0e5a8c3fec953b/api-rest.md#wac-allow-headers */ function parseWacAllowHeader(wacAllowHeader) { function parsePermissionStatement(permissionStatement) { const permissions = permissionStatement.split(" "); const writePermission = permissions.includes("write"); return writePermission ? { read: permissions.includes("read"), append: true, write: true, control: permissions.includes("control"), } : { read: permissions.includes("read"), append: permissions.includes("append"), write: false, control: permissions.includes("control"), }; } function getStatementFor(header, scope) { const relevantEntries = header .split(",") .map((rawEntry) => rawEntry.split("=")) .filter((parts) => parts.length === 2 && parts[0].trim() === scope); // There should only be one statement with the given scope: if (relevantEntries.length !== 1) { return ""; } const relevantStatement = relevantEntries[0][1].trim(); // The given statement should be wrapped in double quotes to be valid: if (relevantStatement.charAt(0) !== '"' || relevantStatement.charAt(relevantStatement.length - 1) !== '"') { return ""; } // Return the statment without the wrapping quotes, e.g.: read append write control return relevantStatement.substring(1, relevantStatement.length - 1); } return { user: parsePermissionStatement(getStatementFor(wacAllowHeader, "user")), public: parsePermissionStatement(getStatementFor(wacAllowHeader, "public")), }; } /** @hidden Used to instantiate a separate instance from input parameters */ function internal_cloneResource(resource) { let clonedResource; if (typeof resource.slice === "function") { // If given Resource is a File: clonedResource = resource.slice(); } else if (typeof resource.match === "function") { // If given Resource is a SolidDataset: // (We use the existince of a `match` method as a heuristic:) clonedResource = clone(resource); } else { // If it is just a plain object containing metadata: clonedResource = Object.assign({}, resource); } return Object.assign(clonedResource, // Although the RDF/JS data structures use classes and mutation, // we only attach atomic properties that we never mutate. // Hence, `copyNonClassProperties` is a heuristic that allows us to only clone our own data // structures, rather than references to the same mutable instances of RDF/JS data structures: copyNonClassProperties(resource)); } function copyNonClassProperties(source) { const copy = {}; Object.keys(source).forEach((key) => { const value = source[key]; if (typeof value !== "object" || value === null) { copy[key] = value; return; } // Ignore properties that are Class methods, we don't want to copy those // across (e.g., copying over an RDF/JS `.add()` method would result in the // former instance's implementation of `.add()` being invoked). if (typeof value.constructor === "undefined" || value.constructor.name !== "Object") { return; } copy[key] = value; }); return copy; } /** @internal */ function internal_isUnsuccessfulResponse(response) { return !response.ok; } export { internal_cloneResource, internal_isUnsuccessfulResponse, internal_parseResourceInfo };