From 93343d2ea7bb185e9e4b04bf53994355c00332c9 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 12 Jun 2018 11:39:59 -0400 Subject: [PATCH 01/27] Refactor LDP API middleware --- lib/api/ldp/api.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 lib/api/ldp/api.js diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js new file mode 100644 index 000000000..2be1f7fac --- /dev/null +++ b/lib/api/ldp/api.js @@ -0,0 +1,73 @@ +'use strict' + +const LdpRequest = require('./ldp-request') +const AclChecker = require('../acl-checker') +const LdpFileStore = require('../storage/ldp-file-store') + +class LdpApi { + /** + * @param options.host {SolidHost} server host / config object + * + * @param options.store {LdpFileStore|LdpMemoryStore|LdpQuadStore} storage backend + * (either file system based, or in-memory, or SPARQL based quad store, etc) + * + * @param options.fetch {fetch} whatwg fetch, possibly with delegation in the + * future. Needed for COPY operations, etc. + * + * @param options.acl {AclChecker} ACL checker component + */ + constructor (options) { + this.host = options.host + this.fetch = options.fetch + this.store = options.store || new LdpFileStore(options) + this.acl = options.acl || + new AclChecker({host: this.host, store: this.store, fetch: this.fetch}) + } + + /** + * Provides an Express handler for LDP API requests. + * Authentication has already happened earlier in the middleware stack, stored + * in session. + * Usage: + * ``` + * app.use('/', ldp.middleware) + * ``` + * + * @see https://github.com/solid/solid-architecture/blob/master/server/request-flow.md + * + * @throws {Error} 401 If request not authenticated but resource is non-public + * + * @throws {Error} 404 If resource is not found, OR if found and request + * is not authorized to access it (default behavior, can be overridden by + * owner of resource) + * + * @throws {Error} 403 If request is not authorized to access resource and + * user has enabled "Request permission" action + * + * @throws {Error} 400 If invalid parameters (or error parsing request body, + * for cases like PATCH requests) + * + * @throws {Error} 409 If PATCH operation results in conflict + * + * @throws {Error} 406 If no appropriate representation found (for content type) + * + * @throws {Error} 405 If HTTP method not allowed / not implemented + */ + async middleware (req, res, next) { + try { + // parse target, operation, (optionally) body, preferences, store authn + const request = await LdpRequest.from({req, host: this.host}) + + // check that operation is permitted (throws error if not) + const permissions = await this.acl.allow(request) + + // perform the operation and return a response + await request + .handle({res, permissions, store: this.store, fetch: this.fetch}) + } catch (error) { + next(error) + } + } +} + +module.exports = LdpApi From 165b4135561db9a7a2babeb8ba7cb9ad06e5b53f Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 12 Jun 2018 15:44:56 -0400 Subject: [PATCH 02/27] Add LdpRequest.from() factory method --- lib/api/ldp/ldp-request.js | 103 +++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 lib/api/ldp/ldp-request.js diff --git a/lib/api/ldp/ldp-request.js b/lib/api/ldp/ldp-request.js new file mode 100644 index 000000000..c3377f3b5 --- /dev/null +++ b/lib/api/ldp/ldp-request.js @@ -0,0 +1,103 @@ +'use strict' + +const HttpError = require('standard-http-error') + +class LdpRequest { + static from ({req, host}) { + let request + + switch (req.method) { + case 'head': + /** + * Checks to see if target exists + */ + request = LdpHeadRequest.from({req, host}) + break + case 'get': + /** + * Use cases to handle: + * - LdpListContainerRequest, if target is an ldp container + * - However, if Accept: header is HTML, check if index.html exists + * - Need to also support similar use case if index.ttl exists + * - Otherwise, plain LdpGetRequest + */ + request = LdpGetRequest.from({req, host}) + break + case 'put': + /** + * Creates a resource or container, and any necessary containers above it in + * the hierarchy. Idempotent. + * + * If target is a container: + * - creates the container if none existed + * - does nothing if container exists, does not delete/clear existing contents + * - has `mkdir -p` semantics (creates missing container hierarchy) + * + * If target is a resource: + * - writes the resource, always overwriting existing contents if any existed + * (since we don't support conditional `If-None-Match` requests) + * - has `mkdir -p` semantics (creates missing container hierarchy) + */ + request = LdpPutRequest.from({req, host}) + break + case 'post': + /** + * Creates a new resource or container in the target container. The name of + * this new resource is derived as follows: + * + * - Use contents of `Slug` header, if provided and no resource with same + * name already exists + * - Use contents of Slug plus UUID, if resource with same name exists + * - Generate a UUID if no Slug given + * + * Does NOT use `mkdir -p` semantics (does not create missing containers) + * + * Throws: + * - 400 error if target doesn't end in a / (is not a container) + * - 404 error if target container does not exist + */ + request = LdpPostRequest.from({req, host}) + break + case 'patch': + /** + * Performs an LDP Patch operation. Like put, creates missing intermediate + * containers in the path hierarchy. + * + * Throws: + * - 400 error if malformed patch syntax + * - 400 error if trying to DELETE triples that do not exist + */ + request = LdpPatchRequest.from({req, host}) + break + case 'delete': + /** + * Deletes resource or container + * + * Throws: + * - 404 error if resource does not exist + */ + request = LdpDeleteRequest.from({req, host}) + break + case 'copy': + /** + * Handles HTTP COPY requests to import a given resource (specified in + * the `Source:` header) to a destination (specified in request path). + * For the moment, you can copy from public resources only (no auth + * delegation is implemented), and is mainly intended for use with + * "Save an external resource to Solid" type apps. + */ + request = LdpCopyRequest.from({req, host}) + break + case 'options': + /** + * Useful for config discovery etc + */ + request = LdpOptionsRequest.from({req, host}) + break + default: + throw new HttpError(405, 'Method not supported') + } + + return request + } +} From a4362fe25985356fd471222c0ecdcbc8cd3ac20d Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 12 Jun 2018 15:56:59 -0400 Subject: [PATCH 03/27] Expand COPY docstring comments --- lib/api/ldp/ldp-request.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/api/ldp/ldp-request.js b/lib/api/ldp/ldp-request.js index c3377f3b5..8357fdb17 100644 --- a/lib/api/ldp/ldp-request.js +++ b/lib/api/ldp/ldp-request.js @@ -65,7 +65,7 @@ class LdpRequest { * * Throws: * - 400 error if malformed patch syntax - * - 400 error if trying to DELETE triples that do not exist + * - 409 Conflict error if trying to DELETE triples that do not exist */ request = LdpPatchRequest.from({req, host}) break @@ -85,12 +85,26 @@ class LdpRequest { * For the moment, you can copy from public resources only (no auth * delegation is implemented), and is mainly intended for use with * "Save an external resource to Solid" type apps. + * + * Open questions: + * - What to do if destination resource exists. (Currently overwrites it) + * - Whether or not to create missing intermediate containers (like PUT + * does) - currently does not do this (behaves like POST) + * - Future: Use an authenticated fetch (pass along the requester's + * bearer credentials, for example) so that they can COPY from + * private resources. + * + * Throws: + * - 400 Source header required error if Source: header is missing */ request = LdpCopyRequest.from({req, host}) break case 'options': /** - * Useful for config discovery etc + * Useful for server config discovery etc. + * Note: Does not throw 404 errors. The semantics of OPTIONS are + * "If this resource existed, here are the headers and properties + * it would have" */ request = LdpOptionsRequest.from({req, host}) break From 0c5dc4b05ab540d64abce46660334f068ae043c9 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 12 Jun 2018 16:20:12 -0400 Subject: [PATCH 04/27] Add LdpRequest constructor() with docstring --- lib/api/ldp/api.js | 2 +- lib/api/ldp/ldp-request.js | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 2be1f7fac..c9505878d 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -56,7 +56,7 @@ class LdpApi { async middleware (req, res, next) { try { // parse target, operation, (optionally) body, preferences, store authn - const request = await LdpRequest.from({req, host: this.host}) + const request = LdpRequest.from({req, host: this.host}) // check that operation is permitted (throws error if not) const permissions = await this.acl.allow(request) diff --git a/lib/api/ldp/ldp-request.js b/lib/api/ldp/ldp-request.js index 8357fdb17..d3a94bc36 100644 --- a/lib/api/ldp/ldp-request.js +++ b/lib/api/ldp/ldp-request.js @@ -3,6 +3,32 @@ const HttpError = require('standard-http-error') class LdpRequest { + /** + * @param options {object} + * + * @param options.host {SolidHost} Host / config object + * + * @param options.target {URL} Parsed full request url + * + * @param options.req {HttpRequest} Save the Express req object, useful + * to access headers/preferences, writeStream for body, etc. + * + * @param [options.authn=null] {object} Authentication object, contains WebID + * string, as well as any bearer credentials/tokens. Needed for + * authenticated fetch (of remote group ACLs, of Copy resources, etc). + * Null if request is not authenticated. + * + * @param [options.parsedBody] {?} Request body, parsed when appropriate + * (when content-type is parse-able) + */ + constructor (options) { + this.host = options.host + this.target = options.target + this.req = options.req + this.authn = options.authn + this.parsedBody = options.parsedBody + } + static from ({req, host}) { let request @@ -74,7 +100,8 @@ class LdpRequest { * Deletes resource or container * * Throws: - * - 404 error if resource does not exist + * - 404 error if resource or container does not exist + * - 409 Conflict error if deleting a non-empty container */ request = LdpDeleteRequest.from({req, host}) break @@ -95,7 +122,8 @@ class LdpRequest { * private resources. * * Throws: - * - 400 Source header required error if Source: header is missing + * - 400 'Source header required' error if Source: header is missing + * - 404 if source resource is not found */ request = LdpCopyRequest.from({req, host}) break From 8bdf4e310adbaf6c7b18f66df7c83e495690a398 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 12 Jun 2018 18:43:36 -0400 Subject: [PATCH 05/27] Rename LdpRequest to LdpOperation, use mapping instead of switch() in factory --- lib/api/ldp/api.js | 29 ++++--- lib/api/ldp/ldp-operation.js | 138 +++++++++++++++++++++++++++++++++ lib/api/ldp/ldp-request.js | 145 ----------------------------------- 3 files changed, 151 insertions(+), 161 deletions(-) create mode 100644 lib/api/ldp/ldp-operation.js delete mode 100644 lib/api/ldp/ldp-request.js diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index c9505878d..54c030298 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -1,27 +1,25 @@ 'use strict' -const LdpRequest = require('./ldp-request') -const AclChecker = require('../acl-checker') -const LdpFileStore = require('../storage/ldp-file-store') +const LdpOperation = require('./ldp-operation') class LdpApi { /** - * @param options.host {SolidHost} server host / config object + * Usage: + * ``` + * const store = new LdpFileStore({host, fetch, ...}) + * const acl = new AclChecker({host, fetch, store, ...}) + * + * const ldp = new LdpApi({store, acl}) + * ``` * * @param options.store {LdpFileStore|LdpMemoryStore|LdpQuadStore} storage backend * (either file system based, or in-memory, or SPARQL based quad store, etc) * - * @param options.fetch {fetch} whatwg fetch, possibly with delegation in the - * future. Needed for COPY operations, etc. - * * @param options.acl {AclChecker} ACL checker component */ constructor (options) { - this.host = options.host - this.fetch = options.fetch - this.store = options.store || new LdpFileStore(options) - this.acl = options.acl || - new AclChecker({host: this.host, store: this.store, fetch: this.fetch}) + this.store = options.store + this.acl = options.acl } /** @@ -56,14 +54,13 @@ class LdpApi { async middleware (req, res, next) { try { // parse target, operation, (optionally) body, preferences, store authn - const request = LdpRequest.from({req, host: this.host}) + const operation = LdpOperation.from(req) // check that operation is permitted (throws error if not) - const permissions = await this.acl.allow(request) + const permissions = await this.acl.allow(operation) // perform the operation and return a response - await request - .handle({res, permissions, store: this.store, fetch: this.fetch}) + await operation.perform({res, permissions, store: this.store}) } catch (error) { next(error) } diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js new file mode 100644 index 000000000..84c982452 --- /dev/null +++ b/lib/api/ldp/ldp-operation.js @@ -0,0 +1,138 @@ +'use strict' + +const HttpError = require('standard-http-error') + +const OPERATIONS = { + 'head': LdpHeadOperation, + 'get': LdpGetOperation, + 'put': LdpPutOperation, + // etc +} + +class LdpOperation { + /** + * @param options {object} + * + * @param options.host {SolidHost} Host / config object + * + * @param options.target {URL} Parsed full request url + * + * @param [options.authn=null] {object} Authentication object, contains WebID + * string, as well as any bearer credentials/tokens. Needed for + * authenticated fetch (of remote group ACLs, of Copy resources, etc). + * Null if request is not authenticated. + * + * @param [options.parsedBody] {?} Request body, parsed when appropriate + * (when content-type is parse-able) + */ + constructor (options) { + this.target = options.target + this.authn = options.authn + } + + static from ({req}) { + // const authn = { webId: req.session.webId, credentials: {} } // <- bearer tokens go here + + const Operation = OPERATIONS[req.method] + if (!Operation) { + throw new HttpError(405, 'Method not supported') + } + + return Operation.from({req}) + } +} + +/** + * Checks to see if target exists + */ +class LdpHeadOperation extends LdpOperation {} + +/** + * Use cases to handle: + * - LdpListContainerRequest, if target is an ldp container + * - However, if Accept: header is HTML, check if index.html exists + * - Need to also support similar use case if index.ttl exists + * - Otherwise, plain LdpGetRequest + */ +class LdpGetOperation extends LdpOperation {} + +/** + * Creates a resource or container, and any necessary containers above it in + * the hierarchy. Idempotent. + * + * If target is a container: + * - creates the container if none existed + * - does nothing if container exists, does not delete/clear existing contents + * - has `mkdir -p` semantics (creates missing container hierarchy) + * + * If target is a resource: + * - writes the resource, always overwriting existing contents if any existed + * (since we don't support conditional `If-None-Match` requests) + * - has `mkdir -p` semantics (creates missing container hierarchy) + */ +class LdpPutOperation extends LdpOperation {} + +/** + * Creates a new resource or container in the target container. The name of + * this new resource is derived as follows: + * + * - Use contents of `Slug` header, if provided and no resource with same + * name already exists + * - Use contents of Slug plus UUID, if resource with same name exists + * - Generate a UUID if no Slug given + * + * Does NOT use `mkdir -p` semantics (does not create missing containers) + * + * Throws: + * - 400 error if target doesn't end in a / (is not a container) + * - 404 error if target container does not exist + */ +class LdpPostOperation extends LdpOperation {} + +/** + * Performs an LDP Patch operation. Like put, creates missing intermediate + * containers in the path hierarchy. + * + * Throws: + * - 400 error if malformed patch syntax + * - 409 Conflict error if trying to DELETE triples that do not exist + */ +class LdpPatchOperation extends LdpOperation {} + +/** + * Deletes resource or container + * + * Throws: + * - 404 error if resource or container does not exist + * - 409 Conflict error if deleting a non-empty container + */ +class LdpDeleteOperation extends LdpOperation {} + +/** + * Handles HTTP COPY requests to import a given resource (specified in + * the `Source:` header) to a destination (specified in request path). + * For the moment, you can copy from public resources only (no auth + * delegation is implemented), and is mainly intended for use with + * "Save an external resource to Solid" type apps. + * + * Open questions: + * - What to do if destination resource exists. (Currently overwrites it) + * - Whether or not to create missing intermediate containers (like PUT + * does) - currently does not do this (behaves like POST) + * - Future: Use an authenticated fetch (pass along the requester's + * bearer credentials, for example) so that they can COPY from + * private resources. + * + * Throws: + * - 400 'Source header required' error if Source: header is missing + * - 404 if source resource is not found + */ +class LdpCopyOperation extends LdpOperation {} + +/** + * Useful for server config discovery etc. + * Note: Does not throw 404 errors. The semantics of OPTIONS are + * "If this resource existed, here are the headers and properties + * it would have" + */ +class LdpOptionsOperation extends LdpOperation {} diff --git a/lib/api/ldp/ldp-request.js b/lib/api/ldp/ldp-request.js deleted file mode 100644 index d3a94bc36..000000000 --- a/lib/api/ldp/ldp-request.js +++ /dev/null @@ -1,145 +0,0 @@ -'use strict' - -const HttpError = require('standard-http-error') - -class LdpRequest { - /** - * @param options {object} - * - * @param options.host {SolidHost} Host / config object - * - * @param options.target {URL} Parsed full request url - * - * @param options.req {HttpRequest} Save the Express req object, useful - * to access headers/preferences, writeStream for body, etc. - * - * @param [options.authn=null] {object} Authentication object, contains WebID - * string, as well as any bearer credentials/tokens. Needed for - * authenticated fetch (of remote group ACLs, of Copy resources, etc). - * Null if request is not authenticated. - * - * @param [options.parsedBody] {?} Request body, parsed when appropriate - * (when content-type is parse-able) - */ - constructor (options) { - this.host = options.host - this.target = options.target - this.req = options.req - this.authn = options.authn - this.parsedBody = options.parsedBody - } - - static from ({req, host}) { - let request - - switch (req.method) { - case 'head': - /** - * Checks to see if target exists - */ - request = LdpHeadRequest.from({req, host}) - break - case 'get': - /** - * Use cases to handle: - * - LdpListContainerRequest, if target is an ldp container - * - However, if Accept: header is HTML, check if index.html exists - * - Need to also support similar use case if index.ttl exists - * - Otherwise, plain LdpGetRequest - */ - request = LdpGetRequest.from({req, host}) - break - case 'put': - /** - * Creates a resource or container, and any necessary containers above it in - * the hierarchy. Idempotent. - * - * If target is a container: - * - creates the container if none existed - * - does nothing if container exists, does not delete/clear existing contents - * - has `mkdir -p` semantics (creates missing container hierarchy) - * - * If target is a resource: - * - writes the resource, always overwriting existing contents if any existed - * (since we don't support conditional `If-None-Match` requests) - * - has `mkdir -p` semantics (creates missing container hierarchy) - */ - request = LdpPutRequest.from({req, host}) - break - case 'post': - /** - * Creates a new resource or container in the target container. The name of - * this new resource is derived as follows: - * - * - Use contents of `Slug` header, if provided and no resource with same - * name already exists - * - Use contents of Slug plus UUID, if resource with same name exists - * - Generate a UUID if no Slug given - * - * Does NOT use `mkdir -p` semantics (does not create missing containers) - * - * Throws: - * - 400 error if target doesn't end in a / (is not a container) - * - 404 error if target container does not exist - */ - request = LdpPostRequest.from({req, host}) - break - case 'patch': - /** - * Performs an LDP Patch operation. Like put, creates missing intermediate - * containers in the path hierarchy. - * - * Throws: - * - 400 error if malformed patch syntax - * - 409 Conflict error if trying to DELETE triples that do not exist - */ - request = LdpPatchRequest.from({req, host}) - break - case 'delete': - /** - * Deletes resource or container - * - * Throws: - * - 404 error if resource or container does not exist - * - 409 Conflict error if deleting a non-empty container - */ - request = LdpDeleteRequest.from({req, host}) - break - case 'copy': - /** - * Handles HTTP COPY requests to import a given resource (specified in - * the `Source:` header) to a destination (specified in request path). - * For the moment, you can copy from public resources only (no auth - * delegation is implemented), and is mainly intended for use with - * "Save an external resource to Solid" type apps. - * - * Open questions: - * - What to do if destination resource exists. (Currently overwrites it) - * - Whether or not to create missing intermediate containers (like PUT - * does) - currently does not do this (behaves like POST) - * - Future: Use an authenticated fetch (pass along the requester's - * bearer credentials, for example) so that they can COPY from - * private resources. - * - * Throws: - * - 400 'Source header required' error if Source: header is missing - * - 404 if source resource is not found - */ - request = LdpCopyRequest.from({req, host}) - break - case 'options': - /** - * Useful for server config discovery etc. - * Note: Does not throw 404 errors. The semantics of OPTIONS are - * "If this resource existed, here are the headers and properties - * it would have" - */ - request = LdpOptionsRequest.from({req, host}) - break - default: - throw new HttpError(405, 'Method not supported') - } - - return request - } -} From a541d3af5e8edcf478c43a0deb93c2bffed3b78f Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 12 Jun 2018 19:32:09 -0400 Subject: [PATCH 06/27] Make explicit components passed to Operation.from() --- lib/api/ldp/ldp-operation.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 84c982452..609bb55fb 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -13,32 +13,38 @@ class LdpOperation { /** * @param options {object} * - * @param options.host {SolidHost} Host / config object - * * @param options.target {URL} Parsed full request url * + * @param options.headers {object} + * * @param [options.authn=null] {object} Authentication object, contains WebID * string, as well as any bearer credentials/tokens. Needed for * authenticated fetch (of remote group ACLs, of Copy resources, etc). * Null if request is not authenticated. * - * @param [options.parsedBody] {?} Request body, parsed when appropriate - * (when content-type is parse-able) + * @param [options.bodyStream] {Stream} Request body stream, for parsing when + * needed */ constructor (options) { - this.target = options.target + this.method = options.method this.authn = options.authn + this.target = options.target + this.headers = options.headers + this.bodyStream = options.bodyStream } - static from ({req}) { - // const authn = { webId: req.session.webId, credentials: {} } // <- bearer tokens go here + static async from ({req}) { + const { method, headers } = req + const authn = { webId: req.session.webId, credentials: {} } // <- bearer tokens go here + const target = LdpOperation.parseTargetUrl(req) + const bodyStream = req - const Operation = OPERATIONS[req.method] + const Operation = OPERATIONS[method] if (!Operation) { throw new HttpError(405, 'Method not supported') } - return Operation.from({req}) + return Operation.from({method, headers, target, bodyStream, authn}) } } From 8997b2f502c1f808e7309aa30add5b51aeffbdfc Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 12 Jun 2018 20:21:10 -0400 Subject: [PATCH 07/27] No need to pass in method to Operation.from() --- lib/api/ldp/ldp-operation.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 609bb55fb..f1c03bd60 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -26,7 +26,6 @@ class LdpOperation { * needed */ constructor (options) { - this.method = options.method this.authn = options.authn this.target = options.target this.headers = options.headers @@ -44,7 +43,7 @@ class LdpOperation { throw new HttpError(405, 'Method not supported') } - return Operation.from({method, headers, target, bodyStream, authn}) + return Operation.from({headers, target, bodyStream, authn}) } } From 90ae8c06042319cd41e878ab0b8a93cee8011898 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Wed, 13 Jun 2018 09:01:14 -0400 Subject: [PATCH 08/27] Rename LdpApi to LdpHttpHandler --- lib/api/ldp/api.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 54c030298..0721bb8c9 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -2,14 +2,14 @@ const LdpOperation = require('./ldp-operation') -class LdpApi { +class LdpHttpHandler { /** * Usage: * ``` * const store = new LdpFileStore({host, fetch, ...}) * const acl = new AclChecker({host, fetch, store, ...}) * - * const ldp = new LdpApi({store, acl}) + * const ldp = new LdpHttpHandler({store, acl}) * ``` * * @param options.store {LdpFileStore|LdpMemoryStore|LdpQuadStore} storage backend @@ -53,7 +53,7 @@ class LdpApi { */ async middleware (req, res, next) { try { - // parse target, operation, (optionally) body, preferences, store authn + // parse operation const operation = LdpOperation.from(req) // check that operation is permitted (throws error if not) @@ -67,4 +67,4 @@ class LdpApi { } } -module.exports = LdpApi +module.exports = LdpHttpHandler From 77fd5dd6f67ff3c042a29913c4f88ee6c00841ee Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Wed, 13 Jun 2018 09:03:43 -0400 Subject: [PATCH 09/27] Assume a first-class Authentication object in request session --- lib/api/ldp/ldp-operation.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index f1c03bd60..0432a0554 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -17,24 +17,23 @@ class LdpOperation { * * @param options.headers {object} * - * @param [options.authn=null] {object} Authentication object, contains WebID - * string, as well as any bearer credentials/tokens. Needed for - * authenticated fetch (of remote group ACLs, of Copy resources, etc). + * @param [options.authentication=null] {Authentication} Authn object, + * contains WebID string, as well as any bearer credentials/tokens. Needed + * for authenticated fetch (of remote group ACLs, of Copy resources, etc). * Null if request is not authenticated. * * @param [options.bodyStream] {Stream} Request body stream, for parsing when * needed */ constructor (options) { - this.authn = options.authn + this.authentication = options.authentication this.target = options.target this.headers = options.headers this.bodyStream = options.bodyStream } static async from ({req}) { - const { method, headers } = req - const authn = { webId: req.session.webId, credentials: {} } // <- bearer tokens go here + const { method, headers, session: { authentication } } = req const target = LdpOperation.parseTargetUrl(req) const bodyStream = req @@ -43,7 +42,7 @@ class LdpOperation { throw new HttpError(405, 'Method not supported') } - return Operation.from({headers, target, bodyStream, authn}) + return Operation.from({headers, target, bodyStream, authentication}) } } From 5b0a3f295f148d9900c8943f33aa855eaa3cb24d Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Wed, 13 Jun 2018 10:33:50 -0400 Subject: [PATCH 10/27] Add the rest of the ldp operations to the map --- lib/api/ldp/ldp-operation.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 0432a0554..42c235d19 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -6,7 +6,11 @@ const OPERATIONS = { 'head': LdpHeadOperation, 'get': LdpGetOperation, 'put': LdpPutOperation, - // etc + 'post': LdpPostOperation, + 'patch': LdpPatchOperation, + 'delete': LdpDeleteOperation, + 'copy': LdpCopyOperation, + 'options': LdpOptionsOperation } class LdpOperation { From 4bd804b6795a72c55602c027f7b3ef2b0b404629 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Wed, 13 Jun 2018 11:15:22 -0400 Subject: [PATCH 11/27] Add parseTargetUrl() to LdpOperation --- lib/api/ldp/api.js | 7 +++++-- lib/api/ldp/ldp-operation.js | 40 +++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 0721bb8c9..05268318b 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -20,6 +20,7 @@ class LdpHttpHandler { constructor (options) { this.store = options.store this.acl = options.acl + this.host = options.host } /** @@ -54,13 +55,15 @@ class LdpHttpHandler { async middleware (req, res, next) { try { // parse operation - const operation = LdpOperation.from(req) + const operation = LdpOperation.from({req, host: this.host}) // check that operation is permitted (throws error if not) const permissions = await this.acl.allow(operation) // perform the operation and return a response - await operation.perform({res, permissions, store: this.store}) + const result = await operation.perform({res, store: this.store}) + operation.writeHeaders({res, result, permissions}) + operation.writeResponse({res, result}) } catch (error) { next(error) } diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 42c235d19..02761b6e2 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -1,5 +1,6 @@ 'use strict' +const url = require('url') const HttpError = require('standard-http-error') const OPERATIONS = { @@ -36,9 +37,9 @@ class LdpOperation { this.bodyStream = options.bodyStream } - static async from ({req}) { + static async from ({req, host}) { const { method, headers, session: { authentication } } = req - const target = LdpOperation.parseTargetUrl(req) + const target = LdpOperation.parseTargetUrl({req, host}) const bodyStream = req const Operation = OPERATIONS[method] @@ -48,19 +49,48 @@ class LdpOperation { return Operation.from({headers, target, bodyStream, authentication}) } + + /** + * Taken from utils.getBaseUri() + * + * @param req {IncomingRequest} + * @param host {SolidHost} + * + * @returns {URL} + */ + static parseTargetUrl ({req, host}) { + const protocol = host.serverUri.replace(/:.*/, '') + + return url.format({ + protocol: protocol || req.protocol, + host: req.get('host'), + pathname: url.resolve(req.baseUrl, req.path), + query: req.query + }) + } + + writeCommonHeaders ({res}) { + // set headers in common to all LDP responses + } } /** * Checks to see if target exists */ -class LdpHeadOperation extends LdpOperation {} +class LdpHeadOperation extends LdpOperation { + writeHeaders ({res, result, permissions}) { + this.writeCommonHeaders({res}) + // write HEAD-specific headers + } +} /** * Use cases to handle: - * - LdpListContainerRequest, if target is an ldp container + * - "List Container" request, if target is an ldp container * - However, if Accept: header is HTML, check if index.html exists * - Need to also support similar use case if index.ttl exists - * - Otherwise, plain LdpGetRequest + * - "Glob pattern request" if glob magic chars are detected (ie `*`) + * - Otherwise, plain Get request */ class LdpGetOperation extends LdpOperation {} From bb68760574934a1d842f05639d9b75107649bda6 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Wed, 13 Jun 2018 11:36:17 -0400 Subject: [PATCH 12/27] Add an illustrative LdpGetOperation.from() --- lib/api/ldp/ldp-operation.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 02761b6e2..3539a184b 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -1,6 +1,7 @@ 'use strict' const url = require('url') +const glob = require('glob') const HttpError = require('standard-http-error') const OPERATIONS = { @@ -92,7 +93,26 @@ class LdpHeadOperation extends LdpOperation { * - "Glob pattern request" if glob magic chars are detected (ie `*`) * - Otherwise, plain Get request */ -class LdpGetOperation extends LdpOperation {} +class LdpGetOperation extends LdpOperation { + static from ({headers, target, bodyStream, authentication, store}) { + const targetUrl = url.format(target) + + if (glob.hasMagic(targetUrl)) { + // this is a Ldp Glob operation, return that + } + + // check to see if target is a container (based on Link: type rel) + // ... + // if it is a container, check to see if index.html exists + const indexFile = url.resolve(targetUrl, '/index.html') + if (store.exists(indexFile)) { + // this is an implicit Get operation to the index file + // return it + } + + // return plain get operation + } +} /** * Creates a resource or container, and any necessary containers above it in From 239ac2bed6a086d06948c5dbefadba642ede3a58 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Wed, 13 Jun 2018 15:49:51 -0400 Subject: [PATCH 13/27] Replace Operation.from() factory method with ldp instance factory --- lib/api/ldp/api.js | 43 +++++++++++++++++++++++++++++++----- lib/api/ldp/ldp-operation.js | 21 ++++++------------ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 05268318b..518e41cee 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -1,6 +1,7 @@ 'use strict' -const LdpOperation = require('./ldp-operation') +const { LdpOperation } = require('./ldp-operation') +const HttpError = require('standard-http-error') class LdpHttpHandler { /** @@ -16,6 +17,8 @@ class LdpHttpHandler { * (either file system based, or in-memory, or SPARQL based quad store, etc) * * @param options.acl {AclChecker} ACL checker component + * + * @param options.host {SolidHost} Server config object */ constructor (options) { this.store = options.store @@ -29,7 +32,7 @@ class LdpHttpHandler { * in session. * Usage: * ``` - * app.use('/', ldp.middleware) + * app.use('/', ldp.handleRequest) * ``` * * @see https://github.com/solid/solid-architecture/blob/master/server/request-flow.md @@ -52,22 +55,52 @@ class LdpHttpHandler { * * @throws {Error} 405 If HTTP method not allowed / not implemented */ - async middleware (req, res, next) { + async handleRequest (req, res, next) { try { - // parse operation - const operation = LdpOperation.from({req, host: this.host}) + const operation = this.operationFrom({req, store: this.store}) // check that operation is permitted (throws error if not) const permissions = await this.acl.allow(operation) // perform the operation and return a response const result = await operation.perform({res, store: this.store}) + + // write both generic and op-specific headers + // note: you need `permissions` for the WAC-Allow header operation.writeHeaders({res, result, permissions}) + operation.writeResponse({res, result}) } catch (error) { next(error) } } + + async operationFrom (req) { + const options = this.parseOperation(req) + + const Operation = LdpOperation.BY_METHOD[options.method] + if (!Operation) { + throw new HttpError(405, 'Method not supported') + } + + return new Operation(options) + } + + /** + * LdpOperation factory method + * + * @param req {IncomingRequest} + * + * @returns {Promise} + */ + async parseOperation (req) { + const { method, headers, session: { authentication } } = req + + const target = this.host.parseTargetUrl(req) + const bodyStream = req // TODO: create a lazy parser here + + return {method, headers, target, bodyStream, authentication} + } } module.exports = LdpHttpHandler diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 3539a184b..c1fbdc9fb 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -4,7 +4,7 @@ const url = require('url') const glob = require('glob') const HttpError = require('standard-http-error') -const OPERATIONS = { +const BY_METHOD = { 'head': LdpHeadOperation, 'get': LdpGetOperation, 'put': LdpPutOperation, @@ -38,19 +38,6 @@ class LdpOperation { this.bodyStream = options.bodyStream } - static async from ({req, host}) { - const { method, headers, session: { authentication } } = req - const target = LdpOperation.parseTargetUrl({req, host}) - const bodyStream = req - - const Operation = OPERATIONS[method] - if (!Operation) { - throw new HttpError(405, 'Method not supported') - } - - return Operation.from({headers, target, bodyStream, authentication}) - } - /** * Taken from utils.getBaseUri() * @@ -75,6 +62,8 @@ class LdpOperation { } } +LdpOperation.BY_METHOD = BY_METHOD + /** * Checks to see if target exists */ @@ -194,3 +183,7 @@ class LdpCopyOperation extends LdpOperation {} * it would have" */ class LdpOptionsOperation extends LdpOperation {} + +module.exports = { + LdpOperation +} From 513d8612cff70d16efca996f77bd38159e7a6727 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Wed, 13 Jun 2018 19:01:30 -0400 Subject: [PATCH 14/27] Flesh out LdpGetOperation.from() --- lib/api/ldp/api.js | 20 ++++++++----- lib/api/ldp/ldp-operation.js | 55 ++++++++++++++++++++---------------- lib/models/solid-host.js | 18 ++++++++++++ 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 518e41cee..0f03ae83a 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -57,7 +57,7 @@ class LdpHttpHandler { */ async handleRequest (req, res, next) { try { - const operation = this.operationFrom({req, store: this.store}) + const operation = this.operationFrom(req) // check that operation is permitted (throws error if not) const permissions = await this.acl.allow(operation) @@ -75,6 +75,13 @@ class LdpHttpHandler { } } + /** + * LdpOperation factory method + * + * @param req {IncomingRequest} + * + * @returns {Promise} + */ async operationFrom (req) { const options = this.parseOperation(req) @@ -83,23 +90,22 @@ class LdpHttpHandler { throw new HttpError(405, 'Method not supported') } - return new Operation(options) + return Operation.from(options) } /** - * LdpOperation factory method - * * @param req {IncomingRequest} * - * @returns {Promise} + * @returns {object} Operation constructor params object */ - async parseOperation (req) { + parseOperation (req) { const { method, headers, session: { authentication } } = req const target = this.host.parseTargetUrl(req) const bodyStream = req // TODO: create a lazy parser here - return {method, headers, target, bodyStream, authentication} + // Note: store is needed for LdpGetOperation factory + return {method, headers, target, bodyStream, authentication, store: this.store} } } diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index c1fbdc9fb..36fc3029b 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -2,7 +2,7 @@ const url = require('url') const glob = require('glob') -const HttpError = require('standard-http-error') +// const HttpError = require('standard-http-error') const BY_METHOD = { 'head': LdpHeadOperation, @@ -39,24 +39,19 @@ class LdpOperation { } /** - * Taken from utils.getBaseUri() + * General construction method, overridden where needed (such as in + * LdpGetOperation). * - * @param req {IncomingRequest} - * @param host {SolidHost} + * @param options {object} * - * @returns {URL} + * @returns {LdpOperation} */ - static parseTargetUrl ({req, host}) { - const protocol = host.serverUri.replace(/:.*/, '') - - return url.format({ - protocol: protocol || req.protocol, - host: req.get('host'), - pathname: url.resolve(req.baseUrl, req.path), - query: req.query - }) + static from (options) { + const Operation = this + return new Operation(options) } + writeCommonHeaders ({res}) { // set headers in common to all LDP responses } @@ -81,28 +76,40 @@ class LdpHeadOperation extends LdpOperation { * - Need to also support similar use case if index.ttl exists * - "Glob pattern request" if glob magic chars are detected (ie `*`) * - Otherwise, plain Get request + * + * @return {LdpGlobOperation|LdpListContainerOperation|LdpGetOperation} */ class LdpGetOperation extends LdpOperation { - static from ({headers, target, bodyStream, authentication, store}) { + static from (options) { + const { store, target } = options const targetUrl = url.format(target) if (glob.hasMagic(targetUrl)) { - // this is a Ldp Glob operation, return that + return new LdpGlobOperation(options) } - // check to see if target is a container (based on Link: type rel) - // ... - // if it is a container, check to see if index.html exists - const indexFile = url.resolve(targetUrl, '/index.html') - if (store.exists(indexFile)) { - // this is an implicit Get operation to the index file - // return it + const isContainer = targetUrl.endsWith('/') + + if (isContainer) { + // if it is a container, check to see if index.html exists + const indexFile = url.resolve(targetUrl, '/index.html') + + if (store.exists(indexFile)) { + return new LdpGetOperation({target: indexFile, ...options}) + } + + return new LdpListContainerOperation(options) } - // return plain get operation + // plain get operation + return new LdpGetOperation(options) } } +class LdpListContainerOperation extends LdpGetOperation {} + +class LdpGlobOperation extends LdpGetOperation {} + /** * Creates a resource or container, and any necessary containers above it in * the hierarchy. Idempotent. diff --git a/lib/models/solid-host.js b/lib/models/solid-host.js index 012a5cc75..3ac8cc169 100644 --- a/lib/models/solid-host.js +++ b/lib/models/solid-host.js @@ -111,6 +111,24 @@ class SolidHost { return cookieDomain } + + /** + * Taken from utils.getBaseUri() + * + * @param req {IncomingRequest} + * + * @returns {URL} + */ + parseTargetUrl (req) { + const protocol = this.serverUri.replace(/:.*/, '') + + return url.format({ + protocol: protocol || req.protocol, + host: req.get('host'), + pathname: url.resolve(req.baseUrl, req.path), + query: req.query + }) + } } function getHostName (url) { From 7c96212a1d4f7de70333cb6c4a18ec7644fc3dd2 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Thu, 14 Jun 2018 11:24:36 -0400 Subject: [PATCH 15/27] Add content-type and -length to common headers --- lib/api/ldp/ldp-operation.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 36fc3029b..02455c508 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -51,9 +51,10 @@ class LdpOperation { return new Operation(options) } - - writeCommonHeaders ({res}) { + writeCommonHeaders ({res, result}) { // set headers in common to all LDP responses + res.set('Content-Type', result.contentType) + res.set('Content-Length', result.contentLength) } } @@ -64,9 +65,16 @@ LdpOperation.BY_METHOD = BY_METHOD */ class LdpHeadOperation extends LdpOperation { writeHeaders ({res, result, permissions}) { - this.writeCommonHeaders({res}) + this.writeCommonHeaders({res, result}) // write HEAD-specific headers } + + /** + * @param res {ServerResponse} + */ + writeResponse ({res}) { + res.sendStatus(200) + } } /** From eeee098f2c32e2056c017dba178387c2f802cf77 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Thu, 14 Jun 2018 16:17:59 -0400 Subject: [PATCH 16/27] Add dispatch logic for LdpRangeOperation --- lib/api/ldp/api.js | 9 ++++++--- lib/api/ldp/ldp-operation.js | 39 ++++++++++++++++++++++++++---------- lib/models/solid-host.js | 2 +- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 0f03ae83a..174a524a7 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -2,6 +2,7 @@ const { LdpOperation } = require('./ldp-operation') const HttpError = require('standard-http-error') +const Negotiator = require('negotiator') class LdpHttpHandler { /** @@ -57,7 +58,7 @@ class LdpHttpHandler { */ async handleRequest (req, res, next) { try { - const operation = this.operationFrom(req) + const operation = await this.operationFrom(req) // check that operation is permitted (throws error if not) const permissions = await this.acl.allow(operation) @@ -100,12 +101,14 @@ class LdpHttpHandler { */ parseOperation (req) { const { method, headers, session: { authentication } } = req - const target = this.host.parseTargetUrl(req) + const conneg = new Negotiator(req) + const bodyStream = req // TODO: create a lazy parser here + const store = this.store // Note: store is needed for LdpGetOperation factory - return {method, headers, target, bodyStream, authentication, store: this.store} + return {method, target, headers, conneg, bodyStream, authentication, store} } } diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 02455c508..347aeff1c 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -28,13 +28,16 @@ class LdpOperation { * for authenticated fetch (of remote group ACLs, of Copy resources, etc). * Null if request is not authenticated. * + * @param [options.conneg] {Negotiator} Content type negotiator instance + * * @param [options.bodyStream] {Stream} Request body stream, for parsing when * needed */ constructor (options) { - this.authentication = options.authentication this.target = options.target this.headers = options.headers + this.authentication = options.authentication + this.conneg = options.conneg this.bodyStream = options.bodyStream } @@ -42,11 +45,13 @@ class LdpOperation { * General construction method, overridden where needed (such as in * LdpGetOperation). * + * @async (to match LdpGetOperation.from() which needs to access store) + * * @param options {object} * * @returns {LdpOperation} */ - static from (options) { + static async from (options) { const Operation = this return new Operation(options) } @@ -83,26 +88,36 @@ class LdpHeadOperation extends LdpOperation { * - However, if Accept: header is HTML, check if index.html exists * - Need to also support similar use case if index.ttl exists * - "Glob pattern request" if glob magic chars are detected (ie `*`) + * - HTTP Range request (partial bytes of a resource) * - Otherwise, plain Get request - * - * @return {LdpGlobOperation|LdpListContainerOperation|LdpGetOperation} */ class LdpGetOperation extends LdpOperation { - static from (options) { - const { store, target } = options - const targetUrl = url.format(target) + /** + * @param options + * + * @returns {LdpGlobOperation|LdpListContainerOperation|LdpRangeOperation|LdpGetOperation} + */ + static async from (options) { + const { store, target, headers, conneg } = options - if (glob.hasMagic(targetUrl)) { + // Note: current implementation only checks for glob magic after the backend + // store could not find the file. Checking before that seems better. + if (glob.hasMagic(target)) { return new LdpGlobOperation(options) } - const isContainer = targetUrl.endsWith('/') + if (headers.range) { + return new LdpRangeOperation(options) + } + + const isContainer = target.endsWith('/') if (isContainer) { // if it is a container, check to see if index.html exists - const indexFile = url.resolve(targetUrl, '/index.html') + const indexFile = url.resolve(target, '/index.html') - if (store.exists(indexFile)) { + if (await store.exists(indexFile) && conneg.mediaType() === 'text/html') { + // This is a browser and an index file exists, return it return new LdpGetOperation({target: indexFile, ...options}) } @@ -114,6 +129,8 @@ class LdpGetOperation extends LdpOperation { } } +class LdpRangeOperation extends LdpGetOperation {} + class LdpListContainerOperation extends LdpGetOperation {} class LdpGlobOperation extends LdpGetOperation {} diff --git a/lib/models/solid-host.js b/lib/models/solid-host.js index 3ac8cc169..8c4ceb155 100644 --- a/lib/models/solid-host.js +++ b/lib/models/solid-host.js @@ -117,7 +117,7 @@ class SolidHost { * * @param req {IncomingRequest} * - * @returns {URL} + * @returns {string} */ parseTargetUrl (req) { const protocol = this.serverUri.replace(/:.*/, '') From 842773c978bbedfde101985753f156d26758e87f Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Thu, 14 Jun 2018 16:56:32 -0400 Subject: [PATCH 17/27] Add LdpDeleteOperation.perform() logic --- lib/api/ldp/api.js | 4 ++-- lib/api/ldp/ldp-operation.js | 43 ++++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 174a524a7..e557c20e5 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -63,8 +63,8 @@ class LdpHttpHandler { // check that operation is permitted (throws error if not) const permissions = await this.acl.allow(operation) - // perform the operation and return a response - const result = await operation.perform({res, store: this.store}) + // perform the operation and return a result + const result = await operation.perform({store: this.store}) // write both generic and op-specific headers // note: you need `permissions` for the WAC-Allow header diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 347aeff1c..9c12559fd 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -2,7 +2,7 @@ const url = require('url') const glob = require('glob') -// const HttpError = require('standard-http-error') +const HttpError = require('standard-http-error') const BY_METHOD = { 'head': LdpHeadOperation, @@ -178,14 +178,39 @@ class LdpPostOperation extends LdpOperation {} */ class LdpPatchOperation extends LdpOperation {} -/** - * Deletes resource or container - * - * Throws: - * - 404 error if resource or container does not exist - * - 409 Conflict error if deleting a non-empty container - */ -class LdpDeleteOperation extends LdpOperation {} +class LdpDeleteOperation extends LdpOperation { + /** + * Deletes resource or container + * + * Throws: + * - 404 error if resource or container does not exist + * - 409 Conflict error if deleting a non-empty container + * + * @returns {Promise} + */ + async perform ({store}) { + const resource = await store.resource(this.target) + + if (!resource.exists) { + throw new HttpError(404) + } + + if (resource.isContainer) { + if (!resource.empty) { + throw new HttpError(409, 'Container is not empty') + } + + return store.deleteContainer(resource) + } + + return store.deleteResource(resource) + } + + writeResponse ({res}) { + // successfully deleted + res.sendStatus(204, 'No content') + } +} /** * Handles HTTP COPY requests to import a given resource (specified in From fe9b82faccdab20c8a4dfbc876349e2610c9ee84 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Fri, 15 Jun 2018 11:25:25 -0400 Subject: [PATCH 18/27] Update fs-extra to latest --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8eda2e49..c47f72f11 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "express-handlebars": "^3.0.0", "express-session": "^1.15.6", "from2": "^2.1.0", - "fs-extra": "^2.1.0", + "fs-extra": "^6.0.1", "glob": "^7.1.1", "global-tunnel-ng": "^2.1.0", "handlebars": "^4.0.6", From 2595a15cf6fa49508045179bf097d338e975ac48 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Fri, 15 Jun 2018 12:13:36 -0400 Subject: [PATCH 19/27] Implement LdpTarget, refactor LdpHttpHandler accordingly --- lib/api/ldp/api.js | 17 ++++++++++++++--- lib/api/ldp/ldp-operation.js | 21 ++++++++++----------- lib/api/ldp/ldp-target.js | 22 ++++++++++++++++++++++ 3 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 lib/api/ldp/ldp-target.js diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index e557c20e5..901a87e62 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -1,6 +1,7 @@ 'use strict' const { LdpOperation } = require('./ldp-operation') +const LdpTarget = require('./ldp-target') const HttpError = require('standard-http-error') const Negotiator = require('negotiator') @@ -101,14 +102,24 @@ class LdpHttpHandler { */ parseOperation (req) { const { method, headers, session: { authentication } } = req - const target = this.host.parseTargetUrl(req) - const conneg = new Negotiator(req) + const target = this.targetFrom(req) const bodyStream = req // TODO: create a lazy parser here const store = this.store // Note: store is needed for LdpGetOperation factory - return {method, target, headers, conneg, bodyStream, authentication, store} + return {method, target, headers, bodyStream, authentication, store} + } + + targetFrom (req) { + return new LdpTarget(this.parseTarget(req)) + } + + parseTarget (req) { + const targetUrl = this.host.parseTargetUrl(req) + const conneg = new Negotiator(req) + + return { url: targetUrl, conneg } } } diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 9c12559fd..74529b114 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -3,6 +3,7 @@ const url = require('url') const glob = require('glob') const HttpError = require('standard-http-error') +const LdpTarget = require('./ldp-target') const BY_METHOD = { 'head': LdpHeadOperation, @@ -19,7 +20,7 @@ class LdpOperation { /** * @param options {object} * - * @param options.target {URL} Parsed full request url + * @param options.target {LdpTarget} * * @param options.headers {object} * @@ -28,8 +29,6 @@ class LdpOperation { * for authenticated fetch (of remote group ACLs, of Copy resources, etc). * Null if request is not authenticated. * - * @param [options.conneg] {Negotiator} Content type negotiator instance - * * @param [options.bodyStream] {Stream} Request body stream, for parsing when * needed */ @@ -37,7 +36,6 @@ class LdpOperation { this.target = options.target this.headers = options.headers this.authentication = options.authentication - this.conneg = options.conneg this.bodyStream = options.bodyStream } @@ -95,14 +93,16 @@ class LdpGetOperation extends LdpOperation { /** * @param options * + * @param options.target {LdpTarget} + * * @returns {LdpGlobOperation|LdpListContainerOperation|LdpRangeOperation|LdpGetOperation} */ static async from (options) { - const { store, target, headers, conneg } = options + const { store, target, headers } = options // Note: current implementation only checks for glob magic after the backend // store could not find the file. Checking before that seems better. - if (glob.hasMagic(target)) { + if (glob.hasMagic(target.url)) { return new LdpGlobOperation(options) } @@ -110,13 +110,12 @@ class LdpGetOperation extends LdpOperation { return new LdpRangeOperation(options) } - const isContainer = target.endsWith('/') - - if (isContainer) { + if (target.isContainer) { // if it is a container, check to see if index.html exists - const indexFile = url.resolve(target, '/index.html') + const indexFileUrl = url.resolve(target.url, '/index.html') + const indexFile = new LdpTarget({ url: indexFileUrl, conneg: target.conneg }) - if (await store.exists(indexFile) && conneg.mediaType() === 'text/html') { + if (await store.exists(indexFile) && target.mediaType() === 'text/html') { // This is a browser and an index file exists, return it return new LdpGetOperation({target: indexFile, ...options}) } diff --git a/lib/api/ldp/ldp-target.js b/lib/api/ldp/ldp-target.js new file mode 100644 index 000000000..6e1e3fbbb --- /dev/null +++ b/lib/api/ldp/ldp-target.js @@ -0,0 +1,22 @@ +'use strict' + +class LdpTarget { + /** + * @param url {string} + * @param conneg {Negotiator} Content type negotiator instance + */ + constructor ({url, conneg}) { + this.url = url + this.conneg = conneg + } + + get isContainer () { + return this.url.endsWith('/') + } + + mediaType () { + return this.conneg.mediaType() + } +} + +module.exports = LdpTarget From 906e289362c133cc7797ae058f536dddf17c690b Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Fri, 15 Jun 2018 12:39:14 -0400 Subject: [PATCH 20/27] Implement LdpFileStore and LdpFileResource --- lib/api/ldp/ldp-target.js | 4 ++ lib/storage/ldp/ldp-file-resource.js | 14 +++++++ lib/storage/ldp/ldp-file-store.js | 56 ++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 lib/storage/ldp/ldp-file-resource.js create mode 100644 lib/storage/ldp/ldp-file-store.js diff --git a/lib/api/ldp/ldp-target.js b/lib/api/ldp/ldp-target.js index 6e1e3fbbb..74dd3f5a5 100644 --- a/lib/api/ldp/ldp-target.js +++ b/lib/api/ldp/ldp-target.js @@ -14,6 +14,10 @@ class LdpTarget { return this.url.endsWith('/') } + charset () { + return this.conneg.charset() + } + mediaType () { return this.conneg.mediaType() } diff --git a/lib/storage/ldp/ldp-file-resource.js b/lib/storage/ldp/ldp-file-resource.js new file mode 100644 index 000000000..274f3f77e --- /dev/null +++ b/lib/storage/ldp/ldp-file-resource.js @@ -0,0 +1,14 @@ +'use strict' + +class LdpFileResource { + /** + * @param target {LdpTarget} + * @param filePath {string} + */ + constructor ({target, filePath}) { + this.target = target + this.filePath = filePath + } +} + +module.exports = LdpFileResource diff --git a/lib/storage/ldp/ldp-file-store.js b/lib/storage/ldp/ldp-file-store.js new file mode 100644 index 000000000..0dbaeaeca --- /dev/null +++ b/lib/storage/ldp/ldp-file-store.js @@ -0,0 +1,56 @@ +'use strict' + +const fs = require('fs-extra') +const LdpFileResource = require('./ldp-file-resource') + +class LdpFileStore { + /** + * @param options {object} + * + * @param options.root {string} Root fs path for data storage + * @param options.multiuser {boolean} + * @param options.suffixAcl {string} + * @param options.suffixMeta {string} + * @param options.mapper {ResourceMapper} + * @param options.dataBrowserPath {string} + * @param options.suppressDataBrowser + */ + constructor (options) { + this.root = options.root + this.multiuser = options.multiuser + this.suffixAcl = options.suffixAcl + this.suffixMeta = options.suffixMeta + + this.mapper = options.mapper + + this.dataBrowserPath = options.dataBrowserPath + this.suppressDataBrowser = options.suppressDataBrowser + } + + /** + * @param target {LdpTarget} + * + * @returns {Promise} + */ + async resource (target) { + const filePath = await this.mapper + .mapUrlToFile({ + url: target.url, contentType: target.mediaType(), createIfNotExists: true + }) + + return new LdpFileResource({target, filePath}) + } + + /** + * @param target {LdpTarget} + * + * @returns {Promise} + */ + async exists (target) { + const resource = await this.resource(target) + + return fs.pathExists(resource.filePath) + } +} + +module.exports = LdpFileStore From 00688f731a604f5f77dcb622974e354d24b0c0ed Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Fri, 15 Jun 2018 13:53:34 -0400 Subject: [PATCH 21/27] Implement deleteResource() & deleteContainer() --- lib/api/ldp/ldp-operation.js | 7 +- lib/storage/ldp/ldp-file-resource.js | 34 ++++++++-- lib/storage/ldp/ldp-file-store.js | 97 +++++++++++++++++++++++++--- 3 files changed, 124 insertions(+), 14 deletions(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 74529b114..cc837590b 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -195,11 +195,14 @@ class LdpDeleteOperation extends LdpOperation { } if (resource.isContainer) { - if (!resource.empty) { + const container = resource + await store.loadContainerContents(container) + + if (!store.isContainerEmpty(container)) { throw new HttpError(409, 'Container is not empty') } - return store.deleteContainer(resource) + return store.deleteContainer(container) } return store.deleteResource(resource) diff --git a/lib/storage/ldp/ldp-file-resource.js b/lib/storage/ldp/ldp-file-resource.js index 274f3f77e..01bdf66a8 100644 --- a/lib/storage/ldp/ldp-file-resource.js +++ b/lib/storage/ldp/ldp-file-resource.js @@ -3,12 +3,38 @@ class LdpFileResource { /** * @param target {LdpTarget} - * @param filePath {string} + * @param path {string} Full file path + * @param mediaType {string} + * @param encoding {string} Either charset (e.g. 'utf8') or 'buffer' + * @param exists {boolean} + * @param fsStats {fs.Stats} */ - constructor ({target, filePath}) { + constructor ({target, path, mediaType, encoding, exists, fsStats}) { this.target = target - this.filePath = filePath + this.path = path + this.mediaType = mediaType + this.encoding = encoding + this.exists = exists + this.fsStats = fsStats + this.isContainer = false } } -module.exports = LdpFileResource +class LdpFileContainer extends LdpFileResource { + /** + * @param options {object} See LdpFileResource constructor docstring + * + * @param [options.contents=[]] {Array} Directory file contents + */ + constructor (options) { + super(options) + + this.isContainer = true + this.contents = options.contents || [] + } +} + +module.exports = { + LdpFileResource, + LdpFileContainer +} diff --git a/lib/storage/ldp/ldp-file-store.js b/lib/storage/ldp/ldp-file-store.js index 0dbaeaeca..535136de6 100644 --- a/lib/storage/ldp/ldp-file-store.js +++ b/lib/storage/ldp/ldp-file-store.js @@ -1,7 +1,7 @@ 'use strict' const fs = require('fs-extra') -const LdpFileResource = require('./ldp-file-resource') +const { LdpFileResource, LdpFileContainer } = require('./ldp-file-resource') class LdpFileStore { /** @@ -11,7 +11,7 @@ class LdpFileStore { * @param options.multiuser {boolean} * @param options.suffixAcl {string} * @param options.suffixMeta {string} - * @param options.mapper {ResourceMapper} + * @param options.mapper {LegacyResourceMapper} * @param options.dataBrowserPath {string} * @param options.suppressDataBrowser */ @@ -29,16 +29,44 @@ class LdpFileStore { /** * @param target {LdpTarget} + * @param target.url {string} * - * @returns {Promise} + * @throws {Error} When encountering a filesystem error that's not "File does + * not exist", such as `EACCES` etc. + * + * @see https://nodejs.org/api/fs.html#fs_class_fs_stats + * + * @returns {Promise} */ async resource (target) { - const filePath = await this.mapper - .mapUrlToFile({ - url: target.url, contentType: target.mediaType(), createIfNotExists: true - }) + const { path, contentType: mediaType } = await this.mapper.mapUrlToFile(target) + + let exists = true + let fsStats + + // Try and load file metadata + try { + fsStats = await fs.stat(path) + } catch (error) { + if (error.code === 'ENOENT') { + exists = false + } else { + throw error + } + } - return new LdpFileResource({target, filePath}) + let isContainer + if (exists) { + isContainer = fsStats.isDirectory() + } else { + isContainer = target.url.endsWith('/') + } + + const encoding = target.charset() // todo: add default charset 'utf8'? + + const options = {target, path, mediaType, encoding, exists, fsStats} + + return isContainer ? new LdpFileContainer(options) : new LdpFileResource(options) } /** @@ -51,6 +79,59 @@ class LdpFileStore { return fs.pathExists(resource.filePath) } + + /** + * Used when trying to delete a container, for example. + * + * @param container {LdpFileContainer} + * + * @returns {boolean} + */ + isContainerEmpty (container) { + const { contents } = container + let skipCount = 0 + if (contents.indexOf(this.suffixMeta) > -1) { + skipCount++ + } + if (contents.indexOf(this.suffixAcl) > -1) { + skipCount++ + } + return contents.length === skipCount + } + + /** + * @param container {LdpFileContainer} + * + * @throws {Error} + * + * @returns {Promise} + */ + async loadContainerContents (container) { + container.contents = await fs.readdir(container.path, container.encoding) + } + + /** + * @param resource + * + * @returns {Promise} + */ + async deleteResource (resource) { + return fs.remove(resource.path) + } + + /** + * Note: Has `rm -rf` semantics, so you need to enforce proper "don't delete + * if not empty" semantics in the calling code. + * + * @throws {Error} + * + * @param container + * + * @returns {Promise} + */ + async deleteContainer (container) { + return fs.remove(container.path) + } } module.exports = LdpFileStore From 127dde13b95f804a3ee6815c51b4ec64114250bd Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Fri, 15 Jun 2018 15:14:56 -0400 Subject: [PATCH 22/27] Pass in fs into LdpFileStore --- lib/api/ldp/api.js | 15 +++++++++++-- lib/api/ldp/ldp-operation.js | 10 +++++---- lib/storage/ldp/ldp-file-store.js | 36 ++++++++++++------------------- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 901a87e62..66418ebc1 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -9,10 +9,21 @@ class LdpHttpHandler { /** * Usage: * ``` - * const store = new LdpFileStore({host, fetch, ...}) + * const mapperOptions = { + * rootUrl: host.serverUri, + * rootPath: host.root, + * includeHost: host.multiuser + * } + * const mapper = new LegacyResourceMapper(mapperOptions) + * + * const storeOptions = { + * host, mapper, suffixMeta, suffixAcl, dataBrowserPath, suppressDataBrowser + * } + * const store = new LdpFileStore(storeOptions) + * * const acl = new AclChecker({host, fetch, store, ...}) * - * const ldp = new LdpHttpHandler({store, acl}) + * const ldp = new LdpHttpHandler({store, acl, host, fetch}) * ``` * * @param options.store {LdpFileStore|LdpMemoryStore|LdpQuadStore} storage backend diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index cc837590b..a09ecb313 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -56,8 +56,10 @@ class LdpOperation { writeCommonHeaders ({res, result}) { // set headers in common to all LDP responses - res.set('Content-Type', result.contentType) - res.set('Content-Length', result.contentLength) + if (result) { // delete operation don't return a result, for example + res.set('Content-Type', result.contentType) + res.set('Content-Length', result.contentLength) + } } } @@ -145,8 +147,8 @@ class LdpGlobOperation extends LdpGetOperation {} * * If target is a resource: * - writes the resource, always overwriting existing contents if any existed - * (since we don't support conditional `If-None-Match` requests) - * - has `mkdir -p` semantics (creates missing container hierarchy) + * (since we currently don't support conditional `If-None-Match` requests) + * - has `mkdir -p` semantics (creates intermediate container hierarchy) */ class LdpPutOperation extends LdpOperation {} diff --git a/lib/storage/ldp/ldp-file-store.js b/lib/storage/ldp/ldp-file-store.js index 535136de6..16e7a3e21 100644 --- a/lib/storage/ldp/ldp-file-store.js +++ b/lib/storage/ldp/ldp-file-store.js @@ -1,28 +1,25 @@ 'use strict' -const fs = require('fs-extra') const { LdpFileResource, LdpFileContainer } = require('./ldp-file-resource') class LdpFileStore { /** * @param options {object} * - * @param options.root {string} Root fs path for data storage - * @param options.multiuser {boolean} + * @param options.fs {object} Expects the `fs-extra` API + * @param options.mapper {LegacyResourceMapper} + * * @param options.suffixAcl {string} * @param options.suffixMeta {string} - * @param options.mapper {LegacyResourceMapper} * @param options.dataBrowserPath {string} - * @param options.suppressDataBrowser + * @param options.suppressDataBrowser {boolean} */ constructor (options) { - this.root = options.root - this.multiuser = options.multiuser - this.suffixAcl = options.suffixAcl - this.suffixMeta = options.suffixMeta - + this.fs = options.fs this.mapper = options.mapper + this.suffixAcl = options.suffixAcl + this.suffixMeta = options.suffixMeta this.dataBrowserPath = options.dataBrowserPath this.suppressDataBrowser = options.suppressDataBrowser } @@ -34,7 +31,7 @@ class LdpFileStore { * @throws {Error} When encountering a filesystem error that's not "File does * not exist", such as `EACCES` etc. * - * @see https://nodejs.org/api/fs.html#fs_class_fs_stats + * @see https://nodejs.org/api/this.fs.html#fs_class_fs_stats * * @returns {Promise} */ @@ -46,7 +43,7 @@ class LdpFileStore { // Try and load file metadata try { - fsStats = await fs.stat(path) + fsStats = await this.fs.stat(path) } catch (error) { if (error.code === 'ENOENT') { exists = false @@ -55,12 +52,7 @@ class LdpFileStore { } } - let isContainer - if (exists) { - isContainer = fsStats.isDirectory() - } else { - isContainer = target.url.endsWith('/') - } + const isContainer = fsStats ? fsStats.isDirectory() : target.url.endsWith('/') const encoding = target.charset() // todo: add default charset 'utf8'? @@ -77,7 +69,7 @@ class LdpFileStore { async exists (target) { const resource = await this.resource(target) - return fs.pathExists(resource.filePath) + return this.fs.pathExists(resource.filePath) } /** @@ -107,7 +99,7 @@ class LdpFileStore { * @returns {Promise} */ async loadContainerContents (container) { - container.contents = await fs.readdir(container.path, container.encoding) + container.contents = await this.fs.readdir(container.path, container.encoding) } /** @@ -116,7 +108,7 @@ class LdpFileStore { * @returns {Promise} */ async deleteResource (resource) { - return fs.remove(resource.path) + return this.fs.remove(resource.path) } /** @@ -130,7 +122,7 @@ class LdpFileStore { * @returns {Promise} */ async deleteContainer (container) { - return fs.remove(container.path) + return this.fs.remove(container.path) } } From 976dc877a16cb1e2ae1d1d4d6974131afcff9992 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Fri, 15 Jun 2018 16:29:53 -0400 Subject: [PATCH 23/27] Implement LdpPutOperation logic (and store logic) --- lib/api/ldp/ldp-operation.js | 60 ++++++++++++++++++++++++++++--- lib/storage/ldp/ldp-file-store.js | 36 +++++++++++++++++++ 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index a09ecb313..4ad67aedd 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -54,11 +54,13 @@ class LdpOperation { return new Operation(options) } - writeCommonHeaders ({res, result}) { + writeCommonHeaders ({res, result = {}}) { + const { resource } = result + // set headers in common to all LDP responses - if (result) { // delete operation don't return a result, for example - res.set('Content-Type', result.contentType) - res.set('Content-Length', result.contentLength) + if (resource) { // delete operation don't return a result, for example + res.set('Content-Type', resource.contentType) + res.set('Content-Length', resource.contentLength) } } } @@ -150,7 +152,55 @@ class LdpGlobOperation extends LdpGetOperation {} * (since we currently don't support conditional `If-None-Match` requests) * - has `mkdir -p` semantics (creates intermediate container hierarchy) */ -class LdpPutOperation extends LdpOperation {} +class LdpPutOperation extends LdpOperation { + /** + * @param store {LdpStore} + * @returns {Promise} Ldp operation result + */ + async perform ({store}) { + const resource = await store.resource(this.target) + + return resource.isContainer + ? this.putContainer({store, container: resource}) + : this.putResource({store, resource}) + } + + async putResource ({store, resource}) { + try { + await store.createResource(resource, this.bodyStream) + } catch (error) { + // log error here + throw error + } + return { status: 201, statusText: 'Created', resource } + } + + async putContainer ({store, container}) { + if (container.exists) { + // no further action needed + return { status: 204, statusText: 'No content', resource: container } + } + try { + await store.createContainer(container) + } catch (error) { + // log error here + throw error + } + return { status: 201, statusText: 'Created', resource: container } + } + + writeHeaders ({res, result}) { + this.writeCommonHeaders({res, result}) + + const { resource } = result + + res.set('Location', resource.target.url) + } + + writeResponse ({res, result}) { + res.status(result.status).send(result.statusText) + } +} /** * Creates a new resource or container in the target container. The name of diff --git a/lib/storage/ldp/ldp-file-store.js b/lib/storage/ldp/ldp-file-store.js index 16e7a3e21..bec9501fe 100644 --- a/lib/storage/ldp/ldp-file-store.js +++ b/lib/storage/ldp/ldp-file-store.js @@ -1,5 +1,6 @@ 'use strict' +const path = require('path') const { LdpFileResource, LdpFileContainer } = require('./ldp-file-resource') class LdpFileStore { @@ -91,6 +92,41 @@ class LdpFileStore { return contents.length === skipCount } + async createContainer (container) { + return this.fs.ensureDir(container.path) + } + + async createResource (resource, bodyStream) { + await this.fs.ensureDir(path.dirname(resource.path)) + + return this.createWriteStream(resource, bodyStream) + } + + /** + * @param resource {LdpFileResource} + * @param bodyStream {Stream} + * + * @throws {HttpError} + * + * @return {Promise} Returns the resource when the write stream + * sends the `finish` event + */ + async createWriteStream (resource, bodyStream) { + return new Promise((resolve, reject) => { + const fileStream = this.fs.createWriteStream( + resource.path, { encoding: resource.encoding } + ) + let writeStream = bodyStream.pipe(fileStream) + + writeStream.on('error', (error) => { + reject(new Error(`Error creating a write stream: ${error}`)) + }) + writeStream.on('finish', () => { + resolve(resource) + }) + }) + } + /** * @param container {LdpFileContainer} * From 5d7d4534d30b669d282597266f85b9f087512125 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Sun, 5 Aug 2018 17:28:56 -0400 Subject: [PATCH 24/27] Add root and multiuser params to host config object --- lib/models/solid-host.js | 5 +++++ lib/server-config.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/models/solid-host.js b/lib/models/solid-host.js index 8c4ceb155..b5bae2b2d 100644 --- a/lib/models/solid-host.js +++ b/lib/models/solid-host.js @@ -16,6 +16,9 @@ class SolidHost { * @param [options.port] {number} * @param [options.serverUri] {string} Fully qualified URI that this Solid * server is listening on, e.g. `https://databox.me` + * @param [options.live] {boolean} Whether turn on WebSockets / LDP live + * @param [options.root] {string} Path to root data directory + * @param [options.multiuser] {boolean} Multiuser (account creation) mode */ constructor (options = {}) { this.port = options.port || defaults.port @@ -26,6 +29,8 @@ class SolidHost { this.hostname = this.parsedUri.hostname this.live = options.live + this.root = options.root + this.multiuser = options.multiuser } /** diff --git a/lib/server-config.js b/lib/server-config.js index 14220ee63..a364afb5f 100644 --- a/lib/server-config.js +++ b/lib/server-config.js @@ -26,7 +26,9 @@ function hostConfigFor (argv) { return { port: argv.port, serverUri: argv.serverUri, - live: argv.live + live: argv.live, + root: argv.root, + multiuser: argv.multiuser } } From 2ec6ed2651c4f926334752f3450531240b1f7ac4 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Thu, 4 Oct 2018 20:35:03 -0400 Subject: [PATCH 25/27] Regenerate package-lock.json --- package-lock.json | 939 +++++++++++++++++----------------------------- 1 file changed, 343 insertions(+), 596 deletions(-) diff --git a/package-lock.json b/package-lock.json index cbd4b0796..a52fe514b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,12 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -111,6 +117,14 @@ "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", "dev": true }, + "@babel/runtime": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz", + "integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, "@babel/template": { "version": "7.0.0-beta.51", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", @@ -151,9 +165,9 @@ } }, "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", "dev": true }, "ms": { @@ -223,26 +237,6 @@ "node-fetch": "^2.1.2", "rdflib": "^0.17.1", "valid-url": "^1.0.9" - }, - "dependencies": { - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - } } }, "@solid/oidc-op": { @@ -360,17 +354,9 @@ } }, "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "acorn-dynamic-import": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", - "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", - "requires": { - "acorn": "^5.0.0" - } + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", + "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==" }, "acorn-jsx": { "version": "3.0.1", @@ -390,15 +376,20 @@ } }, "acorn-node": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.5.2.tgz", - "integrity": "sha512-krFKvw/d1F17AN3XZbybIUzEY4YEPNiGo05AfP3dBlfVKrMHETKpgjpuZkSF8qDNt9UkQcqj7am8yJLseklCMg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.6.0.tgz", + "integrity": "sha512-ZsysjEh+Y3i14f7YXCAKJy99RXbd56wHKYBzN4FlFtICIZyFpYwK6OwNJhcz8A/FMtxoUZkJofH1v9KIfNgWmw==", "requires": { - "acorn": "^5.7.1", - "acorn-dynamic-import": "^3.0.0", + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1", "xtend": "^4.0.1" } }, + "acorn-walk": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", + "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==" + }, "agent-base": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", @@ -589,7 +580,7 @@ }, "async": { "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" }, "asynckit": { @@ -603,12 +594,9 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "auth-header": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/auth-header/-/auth-header-0.3.1.tgz", - "integrity": "sha1-RAB+1pZEzJ6rBo7EyMSV3u5kaX8=", - "requires": { - "babel-core": "^6.4.5" - } + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz", + "integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA==" }, "aws-sign2": { "version": "0.7.0", @@ -630,47 +618,6 @@ "js-tokens": "^3.0.2" } }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, "babel-helper-builder-binary-assignment-operator-visitor": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", @@ -797,15 +744,6 @@ "babel-types": "^6.24.1" } }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, "babel-literal-to-ast": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/babel-literal-to-ast/-/babel-literal-to-ast-1.0.0.tgz", @@ -1302,20 +1240,6 @@ "resolve": "^1.3.2" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -1323,6 +1247,13 @@ "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } } }, "babel-template": { @@ -1556,9 +1487,9 @@ "dev": true }, "browserify": { - "version": "16.2.2", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.2.tgz", - "integrity": "sha512-fMES05wq1Oukts6ksGUU2TMVHHp06LyQt0SIwbXIHm7waSrQmNBZePsU0iM/4f94zbvb/wHma+D1YrdzWYnF/A==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.3.tgz", + "integrity": "sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ==", "requires": { "JSONStream": "^1.0.3", "assert": "^1.4.0", @@ -1804,9 +1735,9 @@ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, "caniuse-lite": { - "version": "1.0.30000888", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000888.tgz", - "integrity": "sha512-vftg+5p/lPsQGpnhSo/yBuYL36ai/cyjLvU3dOPJY1kkKrekLWIy8SLm+wzjX0hpCUdFTasC4/ZT7uqw4rKOnQ==" + "version": "1.0.30000889", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000889.tgz", + "integrity": "sha512-MFxcQ6x/LEEoaIhO7Zdb7Eg8YyNONN+WBnS5ERJ0li2yRw51+i4xXUNxnLaveTb/4ZoJqsWKEmlomhG2pYzlQA==" }, "caseless": { "version": "0.12.0", @@ -1815,7 +1746,7 @@ }, "chai": { "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "resolved": "http://registry.npmjs.org/chai/-/chai-3.5.0.tgz", "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "dev": true, "requires": { @@ -1995,10 +1926,10 @@ "source-map": "~0.5.3" }, "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -2112,12 +2043,9 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "requires": { - "safe-buffer": "~5.1.1" - } + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" }, "cookie": { "version": "0.3.1", @@ -2437,14 +2365,6 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "requires": { - "repeating": "^2.0.0" - } - }, "detective": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz", @@ -2578,9 +2498,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.72", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.72.tgz", - "integrity": "sha512-OFbXEC01Lq7A66e3UywkvWYNN00HO1I9MAPereGe0NIXrt2MeaovL1bbY+951HKG0euUdPBe0L7yfKxgqxBMMw==" + "version": "1.3.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.73.tgz", + "integrity": "sha512-6PIg7v9zRoVGh6EheRF8h6Plti+3Yo/qtHobS4/Htyt53DNHmKKGFqSae1AIk0k1S4gCQvt7I2WgpbuZNcDY+g==" }, "elliptic": { "version": "6.4.1", @@ -2729,15 +2649,6 @@ "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } } }, "escope": { @@ -2883,6 +2794,14 @@ "requires": { "acorn": "^5.5.0", "acorn-jsx": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + } } }, "esprima": { @@ -3296,7 +3215,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", @@ -3471,12 +3390,23 @@ } }, "fs-extra": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz", - "integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", "requires": { "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0" + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + } } }, "fs.realpath": { @@ -3682,11 +3612,6 @@ "requires": { "lodash": "^4.17.10" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -3771,7 +3696,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -3815,6 +3740,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.1" @@ -3982,6 +3908,13 @@ "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", "requires": { "source-map": "~0.5.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "inquirer": { @@ -4139,14 +4072,6 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -4370,9 +4295,9 @@ "optional": true }, "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" }, "json-schema": { "version": "0.2.3", @@ -4399,7 +4324,7 @@ }, "json5": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, "jsonfile": { @@ -4525,6 +4450,17 @@ "integrity": "sha1-bxqmr2sKaThO0GQY4YSwnHDqNUE=", "requires": { "fs-extra": "^2.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz", + "integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0" + } + } } }, "labeled-stream-splicer": { @@ -4703,12 +4639,13 @@ } }, "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "requires": { "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "media-typer": { @@ -4798,9 +4735,9 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "0.0.10", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, "mixin-deep": { "version": "1.3.1", @@ -4845,6 +4782,13 @@ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } } }, "mocha": { @@ -5016,7 +4960,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -5074,17 +5018,17 @@ }, "dependencies": { "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "check-error": "^1.0.1", - "deep-eql": "^3.0.0", + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", - "pathval": "^1.0.0", - "type-detect": "^4.0.0" + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, "debug": { @@ -5130,12 +5074,12 @@ "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" }, "node-mocks-http": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.7.0.tgz", - "integrity": "sha512-AX1jGG87itK38N9UZif1CFYjJDibCOj07d0YGpUsxzglVWJjyJ3R7fxtuK7l6RVCKZteLiQyaTo9UR8rIEESgw==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.7.1.tgz", + "integrity": "sha512-Tbg0BKjhd1R3ZyrShrV8bY4+hKgU27ewtfWpgLLKdnYc2/Gn70BkNvQvoPVqCI7RNWlCth9nwSGVwIbbXxRPqA==", "dev": true, "requires": { - "accepts": "^1.3.3", + "accepts": "^1.3.5", "depd": "^1.1.0", "fresh": "^0.5.2", "merge-descriptors": "^1.0.1", @@ -5144,7 +5088,7 @@ "net": "^1.0.2", "parseurl": "^1.3.1", "range-parser": "^1.2.0", - "type-is": "^1.6.14" + "type-is": "^1.6.16" } }, "node-rsa": { @@ -5209,8 +5153,7 @@ "dependencies": { "align-text": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "bundled": true, "dev": true, "requires": { "kind-of": "^3.0.2", @@ -5220,20 +5163,17 @@ }, "amdefine": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "bundled": true, "dev": true }, "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "bundled": true, "dev": true }, "append-transform": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "bundled": true, "dev": true, "requires": { "default-require-extensions": "^2.0.0" @@ -5241,32 +5181,27 @@ }, "archy": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "bundled": true, "dev": true }, "arrify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "bundled": true, "dev": true }, "async": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "bundled": true, "dev": true }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "bundled": true, "dev": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -5275,14 +5210,12 @@ }, "builtin-modules": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "bundled": true, "dev": true }, "caching-transform": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-2.0.0.tgz", - "integrity": "sha512-tTfemGmFWe7KZ3KN6VsSgQZbd9Bgo7A40wlp4PTsJJvFu4YAnEC5YnfdiKq6Vh2i9XJLnA9n8OXD46orVpnPMw==", + "bundled": true, "dev": true, "requires": { "make-dir": "^1.0.0", @@ -5293,15 +5226,13 @@ }, "camelcase": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "bundled": true, "dev": true, "optional": true }, "center-align": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -5311,8 +5242,7 @@ }, "cliui": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -5323,8 +5253,7 @@ "dependencies": { "wordwrap": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "bundled": true, "dev": true, "optional": true } @@ -5332,32 +5261,27 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "bundled": true, "dev": true }, "commondir": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "bundled": true, "dev": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "bundled": true, "dev": true }, "convert-source-map": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "bundled": true, "dev": true }, "cross-spawn": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "bundled": true, "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -5366,8 +5290,7 @@ }, "debug": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "bundled": true, "dev": true, "requires": { "ms": "2.0.0" @@ -5375,20 +5298,17 @@ }, "debug-log": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", - "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", + "bundled": true, "dev": true }, "decamelize": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "bundled": true, "dev": true }, "default-require-extensions": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "bundled": true, "dev": true, "requires": { "strip-bom": "^3.0.0" @@ -5396,8 +5316,7 @@ }, "error-ex": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "bundled": true, "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -5405,14 +5324,12 @@ }, "es6-error": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "bundled": true, "dev": true }, "execa": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "bundled": true, "dev": true, "requires": { "cross-spawn": "^5.0.1", @@ -5426,8 +5343,7 @@ "dependencies": { "cross-spawn": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "bundled": true, "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -5439,8 +5355,7 @@ }, "find-cache-dir": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", - "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "bundled": true, "dev": true, "requires": { "commondir": "^1.0.1", @@ -5450,8 +5365,7 @@ }, "find-up": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "bundled": true, "dev": true, "requires": { "locate-path": "^3.0.0" @@ -5459,8 +5373,7 @@ }, "foreground-child": { "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "bundled": true, "dev": true, "requires": { "cross-spawn": "^4", @@ -5469,26 +5382,22 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, "dev": true }, "get-caller-file": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "bundled": true, "dev": true }, "get-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "bundled": true, "dev": true }, "glob": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "bundled": true, "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -5501,14 +5410,12 @@ }, "graceful-fs": { "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "bundled": true, "dev": true }, "handlebars": { "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "bundled": true, "dev": true, "requires": { "async": "^1.4.0", @@ -5519,8 +5426,7 @@ "dependencies": { "source-map": { "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "bundled": true, "dev": true, "requires": { "amdefine": ">=0.0.4" @@ -5530,26 +5436,22 @@ }, "has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "bundled": true, "dev": true }, "hosted-git-info": { "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "bundled": true, "dev": true }, "imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "bundled": true, "dev": true }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "dev": true, "requires": { "once": "^1.3.0", @@ -5558,32 +5460,27 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "bundled": true, "dev": true }, "invert-kv": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "bundled": true, "dev": true }, "is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "bundled": true, "dev": true }, "is-buffer": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "bundled": true, "dev": true }, "is-builtin-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "bundled": true, "dev": true, "requires": { "builtin-modules": "^1.0.0" @@ -5591,32 +5488,27 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "bundled": true, "dev": true }, "is-stream": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "bundled": true, "dev": true }, "isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "bundled": true, "dev": true }, "istanbul-lib-coverage": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==", + "bundled": true, "dev": true }, "istanbul-lib-hook": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.1.tgz", - "integrity": "sha512-ufiZoiJ8CxY577JJWEeFuxXZoMqiKpq/RqZtOAYuQLvlkbJWscq9n3gc4xrCGH9n4pW0qnTxOz1oyMmVtk8E1w==", + "bundled": true, "dev": true, "requires": { "append-transform": "^1.0.0" @@ -5624,8 +5516,7 @@ }, "istanbul-lib-report": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.1.tgz", - "integrity": "sha512-pXYOWwpDNc5AHIY93WjFTuxzkDOOZ7B8eSa0cBHTmTnKRst5ccc/xBfWu/5wcNJqB6/Qy0lDMhpn+Uy0qyyUjA==", + "bundled": true, "dev": true, "requires": { "istanbul-lib-coverage": "^2.0.1", @@ -5635,8 +5526,7 @@ }, "istanbul-lib-source-maps": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-2.0.1.tgz", - "integrity": "sha512-30l40ySg+gvBLcxTrLzR4Z2XTRj3HgRCA/p2rnbs/3OiTaoj054gAbuP5DcLOtwqmy4XW8qXBHzrmP2/bQ9i3A==", + "bundled": true, "dev": true, "requires": { "debug": "^3.1.0", @@ -5648,16 +5538,14 @@ "dependencies": { "source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "bundled": true, "dev": true } } }, "istanbul-reports": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.0.0.tgz", - "integrity": "sha512-d2YRSnAOHHb+6vMc5qjJEyPN4VapkgUMhKlMmr3BzKdMDWdJbyYGEi/7m5AjDjkvRRTjs68ttPRZ7W2jBZ31SQ==", + "bundled": true, "dev": true, "requires": { "handlebars": "^4.0.11" @@ -5665,14 +5553,12 @@ }, "json-parse-better-errors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "bundled": true, "dev": true }, "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "bundled": true, "dev": true, "requires": { "is-buffer": "^1.1.5" @@ -5680,15 +5566,13 @@ }, "lazy-cache": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "bundled": true, "dev": true, "optional": true }, "lcid": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "bundled": true, "dev": true, "requires": { "invert-kv": "^1.0.0" @@ -5696,8 +5580,7 @@ }, "load-json-file": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -5708,8 +5591,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "bundled": true, "dev": true, "requires": { "p-locate": "^3.0.0", @@ -5718,20 +5600,17 @@ }, "lodash.flattendeep": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "bundled": true, "dev": true }, "longest": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "bundled": true, "dev": true }, "lru-cache": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "bundled": true, "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -5740,8 +5619,7 @@ }, "make-dir": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "bundled": true, "dev": true, "requires": { "pify": "^3.0.0" @@ -5749,8 +5627,7 @@ }, "md5-hex": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-2.0.0.tgz", - "integrity": "sha1-0FiOnxx0lUSS7NJKwKxs6ZfZLjM=", + "bundled": true, "dev": true, "requires": { "md5-o-matic": "^0.1.1" @@ -5758,14 +5635,12 @@ }, "md5-o-matic": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", - "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", + "bundled": true, "dev": true }, "mem": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "bundled": true, "dev": true, "requires": { "mimic-fn": "^1.0.0" @@ -5773,8 +5648,7 @@ }, "merge-source-map": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "bundled": true, "dev": true, "requires": { "source-map": "^0.6.1" @@ -5782,22 +5656,19 @@ "dependencies": { "source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "bundled": true, "dev": true } } }, "mimic-fn": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "bundled": true, "dev": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -5805,14 +5676,12 @@ }, "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "bundled": true, "dev": true }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "dev": true, "requires": { "minimist": "0.0.8" @@ -5820,22 +5689,19 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "bundled": true, "dev": true } } }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "bundled": true, "dev": true }, "normalize-package-data": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "bundled": true, "dev": true, "requires": { "hosted-git-info": "^2.1.4", @@ -5846,8 +5712,7 @@ }, "npm-run-path": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "bundled": true, "dev": true, "requires": { "path-key": "^2.0.0" @@ -5855,14 +5720,12 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "bundled": true, "dev": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "dev": true, "requires": { "wrappy": "1" @@ -5870,8 +5733,7 @@ }, "optimist": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "bundled": true, "dev": true, "requires": { "minimist": "~0.0.1", @@ -5880,14 +5742,12 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "dev": true }, "os-locale": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "bundled": true, "dev": true, "requires": { "execa": "^0.7.0", @@ -5897,14 +5757,12 @@ }, "p-finally": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "bundled": true, "dev": true }, "p-limit": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "bundled": true, "dev": true, "requires": { "p-try": "^2.0.0" @@ -5912,8 +5770,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "bundled": true, "dev": true, "requires": { "p-limit": "^2.0.0" @@ -5921,14 +5778,12 @@ }, "p-try": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "bundled": true, "dev": true }, "package-hash": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-2.0.0.tgz", - "integrity": "sha1-eK4ybIngWk2BO2hgGXevBcANKg0=", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -5939,8 +5794,7 @@ }, "parse-json": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "bundled": true, "dev": true, "requires": { "error-ex": "^1.3.1", @@ -5949,26 +5803,22 @@ }, "path-exists": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "bundled": true, "dev": true }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, "dev": true }, "path-key": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "bundled": true, "dev": true }, "path-type": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "bundled": true, "dev": true, "requires": { "pify": "^3.0.0" @@ -5976,14 +5826,12 @@ }, "pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "bundled": true, "dev": true }, "pkg-dir": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "bundled": true, "dev": true, "requires": { "find-up": "^3.0.0" @@ -5991,14 +5839,12 @@ }, "pseudomap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "bundled": true, "dev": true }, "read-pkg": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "bundled": true, "dev": true, "requires": { "load-json-file": "^4.0.0", @@ -6008,8 +5854,7 @@ }, "read-pkg-up": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "bundled": true, "dev": true, "requires": { "find-up": "^3.0.0", @@ -6018,8 +5863,7 @@ }, "release-zalgo": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "bundled": true, "dev": true, "requires": { "es6-error": "^4.0.1" @@ -6027,32 +5871,27 @@ }, "repeat-string": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "bundled": true, "dev": true }, "require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "bundled": true, "dev": true }, "require-main-filename": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "bundled": true, "dev": true }, "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "bundled": true, "dev": true }, "right-align": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -6061,8 +5900,7 @@ }, "rimraf": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "bundled": true, "dev": true, "requires": { "glob": "^7.0.5" @@ -6070,20 +5908,17 @@ }, "semver": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "bundled": true, "dev": true }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "dev": true }, "shebang-command": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "bundled": true, "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -6091,27 +5926,23 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "bundled": true, "dev": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "dev": true }, "source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "bundled": true, "dev": true, "optional": true }, "spawn-wrap": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", - "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", + "bundled": true, "dev": true, "requires": { "foreground-child": "^1.5.6", @@ -6124,8 +5955,7 @@ }, "spdx-correct": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "bundled": true, "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -6134,14 +5964,12 @@ }, "spdx-exceptions": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "bundled": true, "dev": true }, "spdx-expression-parse": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "bundled": true, "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -6150,14 +5978,12 @@ }, "spdx-license-ids": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "bundled": true, "dev": true }, "string-width": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "bundled": true, "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -6166,8 +5992,7 @@ }, "strip-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -6175,20 +6000,17 @@ }, "strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "bundled": true, "dev": true }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "bundled": true, "dev": true }, "supports-color": { "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "bundled": true, "dev": true, "requires": { "has-flag": "^3.0.0" @@ -6196,8 +6018,7 @@ }, "test-exclude": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.0.0.tgz", - "integrity": "sha512-bO3Lj5+qFa9YLfYW2ZcXMOV1pmQvw+KS/DpjqhyX6Y6UZ8zstpZJ+mA2ERkXfpOqhxsJlQiLeVXD3Smsrs6oLw==", + "bundled": true, "dev": true, "requires": { "arrify": "^1.0.1", @@ -6208,8 +6029,7 @@ }, "uglify-js": { "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -6220,8 +6040,7 @@ "dependencies": { "yargs": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -6235,21 +6054,18 @@ }, "uglify-to-browserify": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "bundled": true, "dev": true, "optional": true }, "uuid": { "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "bundled": true, "dev": true }, "validate-npm-package-license": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "bundled": true, "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -6258,8 +6074,7 @@ }, "which": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "bundled": true, "dev": true, "requires": { "isexe": "^2.0.0" @@ -6267,27 +6082,23 @@ }, "which-module": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "bundled": true, "dev": true }, "window-size": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "bundled": true, "dev": true, "optional": true }, "wordwrap": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "bundled": true, "dev": true }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "bundled": true, "dev": true, "requires": { "string-width": "^1.0.1", @@ -6296,14 +6107,12 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "bundled": true, "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -6311,8 +6120,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -6322,8 +6130,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -6333,14 +6140,12 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "bundled": true, "dev": true }, "write-file-atomic": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -6350,20 +6155,17 @@ }, "y18n": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "bundled": true, "dev": true }, "yallist": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "bundled": true, "dev": true }, "yargs": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "bundled": true, "dev": true, "requires": { "cliui": "^4.0.0", @@ -6382,8 +6184,7 @@ "dependencies": { "cliui": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "bundled": true, "dev": true, "requires": { "string-width": "^2.1.1", @@ -6393,8 +6194,7 @@ }, "find-up": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "bundled": true, "dev": true, "requires": { "locate-path": "^2.0.0" @@ -6402,8 +6202,7 @@ }, "locate-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "bundled": true, "dev": true, "requires": { "p-locate": "^2.0.0", @@ -6412,8 +6211,7 @@ }, "p-limit": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "bundled": true, "dev": true, "requires": { "p-try": "^1.0.0" @@ -6421,8 +6219,7 @@ }, "p-locate": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "bundled": true, "dev": true, "requires": { "p-limit": "^1.1.0" @@ -6430,16 +6227,14 @@ }, "p-try": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "bundled": true, "dev": true } } }, "yargs-parser": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "bundled": true, "dev": true, "requires": { "camelcase": "^4.1.0" @@ -6447,8 +6242,7 @@ "dependencies": { "camelcase": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "bundled": true, "dev": true } } @@ -6613,7 +6407,8 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true }, "os-locale": { "version": "1.4.0", @@ -6778,9 +6573,9 @@ "dev": true }, "pbkdf2": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", - "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -6971,15 +6766,16 @@ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" }, "public-encrypt": { - "version": "4.0.2", - "resolved": "http://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", - "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "punycode": { @@ -7169,9 +6965,9 @@ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" }, "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" }, "regenerator-transform": { "version": "0.10.1", @@ -7213,13 +7009,6 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "requires": { "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } } }, "repeat-element": { @@ -7232,14 +7021,6 @@ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "requires": { - "is-finite": "^1.0.0" - } - }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -7493,7 +7274,7 @@ "dependencies": { "kind-of": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", "dev": true, "requires": { @@ -7583,11 +7364,6 @@ "integrity": "sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==", "dev": true }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - }, "slice-ansi": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", @@ -7630,6 +7406,11 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -7698,9 +7479,9 @@ } }, "snyk": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.98.0.tgz", - "integrity": "sha512-8iu29cdNNI5PJ9gyGSgrcMpsuuE/SBY93zQ84otPqOBKFzI4WH05ItrGKFZ6p3fzGcEj6Ld+hSi21DynLWg5Vg==", + "version": "1.100.1", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.100.1.tgz", + "integrity": "sha512-7/BA+MZSNLwWphz1nt/IyFxncaLecytpTuzN2ef5MWskj6ATWaBnz4kjQ9v27Zpr53hifP2JeCeqaze1a0DGXw==", "dev": true, "requires": { "abbrev": "^1.1.1", @@ -7721,17 +7502,17 @@ "snyk-config": "2.2.0", "snyk-docker-plugin": "1.11.0", "snyk-go-plugin": "1.5.2", - "snyk-gradle-plugin": "1.3.1", + "snyk-gradle-plugin": "2.0.1", "snyk-module": "1.8.2", - "snyk-mvn-plugin": "1.2.2", + "snyk-mvn-plugin": "2.0.0", "snyk-nodejs-lockfile-parser": "1.5.1", "snyk-nuget-plugin": "1.6.5", "snyk-php-plugin": "1.5.1", "snyk-policy": "1.12.0", "snyk-python-plugin": "1.8.1", "snyk-resolve": "1.0.1", - "snyk-resolve-deps": "3.1.0", - "snyk-sbt-plugin": "1.3.2", + "snyk-resolve-deps": "4.0.0", + "snyk-sbt-plugin": "2.0.0", "snyk-tree": "^1.0.0", "snyk-try-require": "1.3.1", "source-map-support": "^0.5.9", @@ -7870,22 +7651,6 @@ "signal-exit": "^3.0.2" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -8003,9 +7768,9 @@ } }, "snyk-gradle-plugin": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-1.3.1.tgz", - "integrity": "sha512-WJQuJFihqvnOqtV8Psl8W2/XxluSxB/tffHmGXy5b2C2VO56WmEvn1JHTcmvkRBP1KjPtwccMc1e7WeOVT3saQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-2.0.1.tgz", + "integrity": "sha512-dL9MRlwmDP4OmOk4b61pMqvbu8gG+HVCBTryBEU+vkvjloX0lslKvA5NTUPAM2M8SShnOic54cSegGQChBhYHw==", "dev": true, "requires": { "clone-deep": "^0.3.0" @@ -8039,9 +7804,9 @@ } }, "snyk-mvn-plugin": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-1.2.2.tgz", - "integrity": "sha512-KfT7JZWIxYbLkqhidUyOMNeK/iTwNeHYwqNtycMS3S4i9NfbfrMl73IesxNyJQeGgZ79ms5sg7psqAQj29uZIA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-2.0.0.tgz", + "integrity": "sha512-9jAhZhv+7YcqtoQYCYlgMoxK+dWBKlk+wkX27Ebg3vNddNop9q5jZitRXTjsXwfSUZHRt+Ptw1f8vei9kjzZVg==", "dev": true }, "snyk-nodejs-lockfile-parser": { @@ -8061,22 +7826,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } } } }, @@ -8219,20 +7968,20 @@ } }, "snyk-resolve-deps": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-3.1.0.tgz", - "integrity": "sha512-YVAelR+dTpqLgfk6lf6WgOlw+MGmGI0r3/Dny8tUbJJ9uVTHTRAOdZCbUyTFqJG7oEmEZxUwmfjqgAuniYwx8Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-4.0.0.tgz", + "integrity": "sha512-EWqkRLAFUr09ZdAz9kJGXtxPlYsa+ZXYYF6KO0X6lZsPdFN3bUcT/ryDzaEemB6gJAMDvoWORWpmbaqQM7c7yw==", "dev": true, "requires": { "ansicolors": "^0.3.2", - "debug": "^3.1.0", + "debug": "^3.2.5", "lodash.assign": "^4.2.0", "lodash.assignin": "^4.2.0", "lodash.flatten": "^4.4.0", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "lru-cache": "^4.0.0", - "semver": "^5.1.0", + "semver": "^5.5.1", "snyk-module": "^1.6.0", "snyk-resolve": "^1.0.0", "snyk-tree": "^1.0.0", @@ -8258,9 +8007,9 @@ } }, "snyk-sbt-plugin": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/snyk-sbt-plugin/-/snyk-sbt-plugin-1.3.2.tgz", - "integrity": "sha512-Pm6G8emW4mDg9hflY0gsdFV5JgR8pHa8D1vCZGJMVSRuGrc64CEg8I1D6977lGoKDwJGePwi4CTcluwj7cNsxQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/snyk-sbt-plugin/-/snyk-sbt-plugin-2.0.0.tgz", + "integrity": "sha512-bOUqsQ1Lysnwfnvf4QQIBfC0M0ZVuhlshTKd7pNwgAJ41YEPJNrPEpzOePl/HfKtwilEEwHh5YHvjYGegEKx0A==", "dev": true }, "snyk-tree": { @@ -8322,12 +8071,13 @@ } }, "solid-auth-client": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/solid-auth-client/-/solid-auth-client-2.2.4.tgz", - "integrity": "sha512-ZDB6UbkudT2bI1rb5WCBcUOJpOu53Bx85CN5A4gdzMxXsXgcatG/f7b+9h/c8zSsQhbsU93dlS3kCoED6HQ6ug==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/solid-auth-client/-/solid-auth-client-2.2.6.tgz", + "integrity": "sha512-LJNFtl+HriwjqqvAE7dhngFKGoFdreZJ3XxpqkVKUkLjdKCrHBmshQtm5ogVXgy/Z3jXGMeMe93YLnbjDDnK+Q==", "requires": { + "@babel/runtime": "^7.0.0", "@solid/oidc-rp": "^0.8.0", - "auth-header": "^0.3.1", + "auth-header": "^1.0.0", "commander": "^2.11.0", "isomorphic-fetch": "^2.2.1", "uuid": "^3.1.0" @@ -8415,9 +8165,9 @@ } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.2", @@ -8432,11 +8182,13 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, "requires": { - "source-map": "^0.5.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "source-map-url": { @@ -8734,7 +8486,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -8939,7 +8691,7 @@ }, "text-encoding": { "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "resolved": "http://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" }, "text-table": { @@ -9103,7 +8855,8 @@ "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true }, "tslib": { "version": "1.9.3", @@ -9179,12 +8932,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true } } }, From 0f7bfc80115512101dab75938b5cec22abc6a3ae Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Thu, 4 Oct 2018 21:00:44 -0400 Subject: [PATCH 26/27] Refactor/expand SolidHost.parseTargetUrl() --- lib/models/solid-host.js | 25 ++++++++++++++++++++++--- lib/server-config.js | 3 ++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/models/solid-host.js b/lib/models/solid-host.js index b5bae2b2d..043b907bb 100644 --- a/lib/models/solid-host.js +++ b/lib/models/solid-host.js @@ -31,6 +31,7 @@ class SolidHost { this.live = options.live this.root = options.root this.multiuser = options.multiuser + this.webid = options.webid } /** @@ -117,6 +118,17 @@ class SolidHost { return cookieDomain } + /** + * Returns the protocol for an incoming request ('http' or 'https') + * @returns {string} + */ + protocol (req) { + // Obtain the protocol from the configured server URI + // (in case the server is running behind a reverse proxy) + const protocol = this.serverUri.replace(/:.*/, '') + return protocol || req.protocol + } + /** * Taken from utils.getBaseUri() * @@ -124,11 +136,18 @@ class SolidHost { * * @returns {string} */ - parseTargetUrl (req) { - const protocol = this.serverUri.replace(/:.*/, '') + getBaseUri (req) { + return this.protocol(req) + '://' + req.get('host') + } + /** + * @param req {IncomingRequest} + * + * @returns {string} + */ + parseTargetUrl (req) { return url.format({ - protocol: protocol || req.protocol, + protocol: this.protocol(req), host: req.get('host'), pathname: url.resolve(req.baseUrl, req.path), query: req.query diff --git a/lib/server-config.js b/lib/server-config.js index a364afb5f..dce48e078 100644 --- a/lib/server-config.js +++ b/lib/server-config.js @@ -28,7 +28,8 @@ function hostConfigFor (argv) { serverUri: argv.serverUri, live: argv.live, root: argv.root, - multiuser: argv.multiuser + multiuser: argv.multiuser, + webid: argv.webid } } From ed40c42a5c245b8ba9685e14fdf1d119af62ca9b Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Thu, 4 Oct 2018 21:03:33 -0400 Subject: [PATCH 27/27] Refactor LDP Target, FileResource and Store --- lib/api/ldp/api.js | 2 +- lib/api/ldp/ldp-operation.js | 28 ++++++------- lib/api/ldp/ldp-target.js | 25 +++++++++--- lib/storage/ldp/ldp-file-resource.js | 38 +++++++++++++++-- lib/storage/ldp/ldp-file-store.js | 61 +++++++++++++++++++++------- 5 files changed, 115 insertions(+), 39 deletions(-) diff --git a/lib/api/ldp/api.js b/lib/api/ldp/api.js index 66418ebc1..2710bbabe 100644 --- a/lib/api/ldp/api.js +++ b/lib/api/ldp/api.js @@ -130,7 +130,7 @@ class LdpHttpHandler { const targetUrl = this.host.parseTargetUrl(req) const conneg = new Negotiator(req) - return { url: targetUrl, conneg } + return { name: req.path, url: targetUrl, conneg } } } diff --git a/lib/api/ldp/ldp-operation.js b/lib/api/ldp/ldp-operation.js index 4ad67aedd..de8c523d2 100644 --- a/lib/api/ldp/ldp-operation.js +++ b/lib/api/ldp/ldp-operation.js @@ -5,17 +5,6 @@ const glob = require('glob') const HttpError = require('standard-http-error') const LdpTarget = require('./ldp-target') -const BY_METHOD = { - 'head': LdpHeadOperation, - 'get': LdpGetOperation, - 'put': LdpPutOperation, - 'post': LdpPostOperation, - 'patch': LdpPatchOperation, - 'delete': LdpDeleteOperation, - 'copy': LdpCopyOperation, - 'options': LdpOptionsOperation -} - class LdpOperation { /** * @param options {object} @@ -65,8 +54,6 @@ class LdpOperation { } } -LdpOperation.BY_METHOD = BY_METHOD - /** * Checks to see if target exists */ @@ -248,7 +235,7 @@ class LdpDeleteOperation extends LdpOperation { if (resource.isContainer) { const container = resource - await store.loadContainerContents(container) + container.resourceNames = await store.loadContentsList(container) if (!store.isContainerEmpty(container)) { throw new HttpError(409, 'Container is not empty') @@ -295,6 +282,19 @@ class LdpCopyOperation extends LdpOperation {} */ class LdpOptionsOperation extends LdpOperation {} +const BY_METHOD = { + 'head': LdpHeadOperation, + 'get': LdpGetOperation, + 'put': LdpPutOperation, + 'post': LdpPostOperation, + 'patch': LdpPatchOperation, + 'delete': LdpDeleteOperation, + 'copy': LdpCopyOperation, + 'options': LdpOptionsOperation +} + +LdpOperation.BY_METHOD = BY_METHOD + module.exports = { LdpOperation } diff --git a/lib/api/ldp/ldp-target.js b/lib/api/ldp/ldp-target.js index 74dd3f5a5..639214c7a 100644 --- a/lib/api/ldp/ldp-target.js +++ b/lib/api/ldp/ldp-target.js @@ -1,12 +1,17 @@ 'use strict' +const { resolve } = require('url') + class LdpTarget { /** - * @param url {string} - * @param conneg {Negotiator} Content type negotiator instance + * @param url {string} Fully qualified url, with protocol etc + * @param [name] {string} Filename part of the url (req.path) + * @param [conneg] {Negotiator} Content type negotiator instance */ - constructor ({url, conneg}) { + constructor ({url, name, conneg}) { this.url = url + this.name = name + this.isAcl = this.name.endsWith('.acl') // todo: pass in the .acl suffix this.conneg = conneg } @@ -15,11 +20,21 @@ class LdpTarget { } charset () { - return this.conneg.charset() + return (this.conneg && this.conneg.charset()) || undefined } mediaType () { - return this.conneg.mediaType() + return (this.conneg && this.conneg.mediaType()) || undefined + } + + get isRoot () { + return this.name === '/' + } + + get parent () { + if (this.isRoot) { return null } + + return this.isContainer ? resolve(this.url, '..') : resolve(this.url, '.') } } diff --git a/lib/storage/ldp/ldp-file-resource.js b/lib/storage/ldp/ldp-file-resource.js index 01bdf66a8..609a60533 100644 --- a/lib/storage/ldp/ldp-file-resource.js +++ b/lib/storage/ldp/ldp-file-resource.js @@ -1,36 +1,66 @@ 'use strict' +const { resolve } = require('url') + +const DEFAULT_ENCODING = 'utf8' + class LdpFileResource { /** * @param target {LdpTarget} * @param path {string} Full file path * @param mediaType {string} * @param encoding {string} Either charset (e.g. 'utf8') or 'buffer' - * @param exists {boolean} + * @param exists {boolean} Does resource exist on the file system * @param fsStats {fs.Stats} */ constructor ({target, path, mediaType, encoding, exists, fsStats}) { this.target = target this.path = path this.mediaType = mediaType - this.encoding = encoding + this.encoding = encoding || DEFAULT_ENCODING this.exists = exists this.fsStats = fsStats this.isContainer = false } + + normalizeUrl () { + // Do nothing (is overridden in subclass) + } } class LdpFileContainer extends LdpFileResource { /** * @param options {object} See LdpFileResource constructor docstring * - * @param [options.contents=[]] {Array} Directory file contents + * @param [options.resourceNames=[]] {Array} Directory file contents + * @param [options.resources=[]] {Array} List of + * LdpFileResource instances. Each requires an fs.stats() call, so initializing + * this is an expensive operation. See `LdpFileStore.loadContentsDetails()` */ constructor (options) { super(options) this.isContainer = true - this.contents = options.contents || [] + this.resourceNames = options.resourceNames || [] + this.resources = options.resources || [] + } + + get resourceUrls () { + return this.resourceNames.map((name) => [name, resolve(this.target.url, name)]) + } + + normalizeUrl () { + if (!this.path.endsWith('/')) { + this.path += '/' + } + + if (!this.target.url.endsWith('/')) { + this.target.url += '/' + } + + if (!this.target.name.endsWith('/')) { + this.target.name += '/' + } } } diff --git a/lib/storage/ldp/ldp-file-store.js b/lib/storage/ldp/ldp-file-store.js index bec9501fe..bf06e776d 100644 --- a/lib/storage/ldp/ldp-file-store.js +++ b/lib/storage/ldp/ldp-file-store.js @@ -1,7 +1,11 @@ 'use strict' const path = require('path') +const fs = require('fs-extra') const { LdpFileResource, LdpFileContainer } = require('./ldp-file-resource') +const LdpTarget = require('../../api/ldp/ldp-target') + +const AVAILABLE_CHARSETS = ['utf8'] class LdpFileStore { /** @@ -12,17 +16,13 @@ class LdpFileStore { * * @param options.suffixAcl {string} * @param options.suffixMeta {string} - * @param options.dataBrowserPath {string} - * @param options.suppressDataBrowser {boolean} */ constructor (options) { - this.fs = options.fs + this.fs = options.fs || fs this.mapper = options.mapper this.suffixAcl = options.suffixAcl this.suffixMeta = options.suffixMeta - this.dataBrowserPath = options.dataBrowserPath - this.suppressDataBrowser = options.suppressDataBrowser } /** @@ -37,7 +37,7 @@ class LdpFileStore { * @returns {Promise} */ async resource (target) { - const { path, contentType: mediaType } = await this.mapper.mapUrlToFile(target) + const { path, contentType: mediaType } = await this.mapper.mapUrlToFile({ url: target.url }) let exists = true let fsStats @@ -55,11 +55,16 @@ class LdpFileStore { const isContainer = fsStats ? fsStats.isDirectory() : target.url.endsWith('/') - const encoding = target.charset() // todo: add default charset 'utf8'? + const encoding = target.charset(AVAILABLE_CHARSETS) const options = {target, path, mediaType, encoding, exists, fsStats} - return isContainer ? new LdpFileContainer(options) : new LdpFileResource(options) + const resource = isContainer + ? new LdpFileContainer(options) + : new LdpFileResource(options) + resource.normalizeUrl() + + return resource } /** @@ -81,15 +86,15 @@ class LdpFileStore { * @returns {boolean} */ isContainerEmpty (container) { - const { contents } = container + const { resourceNames } = container let skipCount = 0 - if (contents.indexOf(this.suffixMeta) > -1) { + if (resourceNames.indexOf(this.suffixMeta) > -1) { skipCount++ } - if (contents.indexOf(this.suffixAcl) > -1) { + if (resourceNames.indexOf(this.suffixAcl) > -1) { skipCount++ } - return contents.length === skipCount + return resourceNames.length === skipCount } async createContainer (container) { @@ -128,14 +133,36 @@ class LdpFileStore { } /** + * Load the list of resources in a container (just the file names). + * * @param container {LdpFileContainer} * * @throws {Error} * - * @returns {Promise} + * @returns {Promise>} + */ + async loadContentsList (container) { + // todo: sort out encoding. Currently, conneg is returning '*' as encoding, + // which results in an error from readdir + return this.fs.readdir(container.path) //, container.encoding) + } + + /** + * Gets the details on each resource in a container's resource list + * + * @param container {LdpFileContainer} + * + * @throws {Error} + * + * @returns {Promise>} */ - async loadContainerContents (container) { - container.contents = await this.fs.readdir(container.path, container.encoding) + async loadContentsDetails (container) { + return Promise.all( + container.resourceUrls.map(resource => { + const [ name, url ] = resource + return this.resource(new LdpTarget({name, url})) + }) + ) } /** @@ -160,6 +187,10 @@ class LdpFileStore { async deleteContainer (container) { return this.fs.remove(container.path) } + + async readFile (resource) { + return this.fs.readFile(resource.path, 'utf8') + } } module.exports = LdpFileStore