diff --git a/README.md b/README.md
index 268f61cb1..a02fc2bbe 100644
--- a/README.md
+++ b/README.md
@@ -228,14 +228,21 @@ Run with:
docker run -p 8443:8443 --name solid node-solid-server
```
-Modify the config as follows:
+This will enable you to login to solid on https://localhost:8443 and then create a new account
+but not yet use that account. After a new account is made you will need to create an entry for
+it in your local (/etc/)hosts file in line with the account and subdomain i.e.
+
+127.0.0.1 newsoliduser.localhost
+
+Then you'll be able to use solid as intended.
+
+You can modify the config within the docker container as follows:
- Copy the config to the current directory with: `docker cp solid:/usr/src/app/config.json .`
- Edit the `config.json` file
- Copy the file back with `docker cp config.json solid:/usr/src/app/`
- Restart the server with `docker restart solid`
-
## Library Usage
### Install Dependencies
diff --git a/bin/lib/cli-utils.js b/bin/lib/cli-utils.js
new file mode 100644
index 000000000..b578f6614
--- /dev/null
+++ b/bin/lib/cli-utils.js
@@ -0,0 +1,70 @@
+const fs = require('fs-extra')
+const { cyan, bold } = require('colorette')
+const { URL } = require('url')
+const LDP = require('../../lib/ldp')
+const AccountManager = require('../../lib/models/account-manager')
+const SolidHost = require('../../lib/models/solid-host')
+
+module.exports.getAccountManager = getAccountManager
+module.exports.loadAccounts = loadAccounts
+module.exports.loadConfig = loadConfig
+module.exports.loadUsernames = loadUsernames
+
+/**
+ * Returns an instance of AccountManager
+ *
+ * @param {Object} config
+ * @param {Object} [options]
+ * @returns {AccountManager}
+ */
+function getAccountManager (config, options = {}) {
+ const ldp = options.ldp || new LDP(config)
+ const host = options.host || SolidHost.from({ port: config.port, serverUri: config.serverUri })
+ return AccountManager.from({
+ host,
+ store: ldp,
+ multiuser: config.multiuser
+ })
+}
+
+function loadConfig (program, options) {
+ let argv = {
+ ...options,
+ version: program.version()
+ }
+ let configFile = argv['configFile'] || './config.json'
+
+ try {
+ const file = fs.readFileSync(configFile)
+
+ // Use flags with priority over config file
+ const config = JSON.parse(file)
+ argv = { ...config, ...argv }
+ } catch (err) {
+ // No file exists, not a problem
+ console.log(cyan(bold('TIP')), 'create a config.json: `$ solid init`')
+ }
+
+ return argv
+}
+
+/**
+ *
+ * @param root
+ * @param [serverUri] If not set, hostname must be set
+ * @param [hostname] If not set, serverUri must be set
+ * @returns {*}
+ */
+function loadAccounts ({ root, serverUri, hostname }) {
+ const files = fs.readdirSync(root)
+ hostname = hostname || new URL(serverUri).hostname
+ const isUserDirectory = new RegExp(`.${hostname}$`)
+ return files
+ .filter(file => isUserDirectory.test(file))
+}
+
+function loadUsernames ({ root, serverUri }) {
+ const hostname = new URL(serverUri).hostname
+ return loadAccounts({ root, hostname })
+ .map(userDirectory => userDirectory.substr(0, userDirectory.length - hostname.length - 1))
+}
diff --git a/bin/lib/cli.js b/bin/lib/cli.js
index 85dcb789a..889903bed 100644
--- a/bin/lib/cli.js
+++ b/bin/lib/cli.js
@@ -3,6 +3,7 @@ const loadInit = require('./init')
const loadStart = require('./start')
const loadInvalidUsernames = require('./invalidUsernames')
const loadMigrateLegacyResources = require('./migrateLegacyResources')
+const loadUpdateIndex = require('./updateIndex')
const { spawnSync } = require('child_process')
const path = require('path')
@@ -13,6 +14,7 @@ module.exports = function startCli (server) {
loadStart(program, server)
loadInvalidUsernames(program)
loadMigrateLegacyResources(program)
+ loadUpdateIndex(program)
program.parse(process.argv)
if (program.args.length === 0) program.help()
diff --git a/bin/lib/common.js b/bin/lib/common.js
deleted file mode 100644
index 80a0f6e58..000000000
--- a/bin/lib/common.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const fs = require('fs')
-const extend = require('extend')
-const { cyan, bold } = require('colorette')
-
-module.exports.loadConfig = loadConfig
-
-function loadConfig (program, options) {
- let argv = extend({}, options, { version: program.version() })
- let configFile = argv['configFile'] || './config.json'
-
- try {
- const file = fs.readFileSync(configFile)
-
- // Use flags with priority over config file
- const config = JSON.parse(file)
- Object.keys(config).forEach((option) => {
- argv[option] = argv[option] || config[option]
- })
- } catch (err) {
- // No file exists, not a problem
- console.log(cyan(bold('TIP')), 'create a config.json: `$ solid init`')
- }
-
- return argv
-}
diff --git a/bin/lib/invalidUsernames.js b/bin/lib/invalidUsernames.js
index f2b7493e6..1d6cd340e 100644
--- a/bin/lib/invalidUsernames.js
+++ b/bin/lib/invalidUsernames.js
@@ -1,17 +1,14 @@
const fs = require('fs-extra')
const Handlebars = require('handlebars')
const path = require('path')
-const { URL } = require('url')
-const { loadConfig } = require('./common')
+const { getAccountManager, loadConfig, loadUsernames } = require('./cli-utils')
const { isValidUsername } = require('../../lib/common/user-utils')
const blacklistService = require('../../lib/services/blacklist-service')
const { initConfigDir, initTemplateDirs } = require('../../lib/server-config')
const { fromServerConfig } = require('../../lib/models/oidc-manager')
-const AccountManager = require('../../lib/models/account-manager')
const EmailService = require('../../lib/services/email-service')
-const LDP = require('../../lib/ldp')
const SolidHost = require('../../lib/models/solid-host')
module.exports = function (program) {
@@ -28,7 +25,7 @@ module.exports = function (program) {
const invalidUsernames = getInvalidUsernames(config)
const host = SolidHost.from({ port: config.port, serverUri: config.serverUri })
- const accountManager = getAccountManager(config, host)
+ const accountManager = getAccountManager(config, { host })
if (options.notify) {
return notifyUsers(invalidUsernames, accountManager, config)
@@ -96,23 +93,9 @@ async function deleteUsers (usernames, accountManager, config, host) {
console.info(`Deleted ${deletingUsers.length} users succeeded`)
}
-function getAccountManager (config, host) {
- const ldp = new LDP(config)
- return AccountManager.from({
- host,
- store: ldp,
- multiuser: config.multiuser
- })
-}
-
function getInvalidUsernames (config) {
- const files = fs.readdirSync(config.root)
- const hostname = new URL(config.serverUri).hostname
- const isUserDirectory = new RegExp(`.${hostname}$`)
- return files
- .filter(file => isUserDirectory.test(file))
- .map(userDirectory => userDirectory.substr(0, userDirectory.length - hostname.length - 1))
- .filter(username => !isValidUsername(username) || !blacklistService.validate(username))
+ const usernames = loadUsernames(config)
+ return usernames.filter(username => !isValidUsername(username) || !blacklistService.validate(username))
}
function listUsernames (usernames) {
diff --git a/bin/lib/start.js b/bin/lib/start.js
index 723fec99f..222911186 100644
--- a/bin/lib/start.js
+++ b/bin/lib/start.js
@@ -2,7 +2,7 @@
const options = require('./options')
const fs = require('fs')
-const { loadConfig } = require('./common')
+const { loadConfig } = require('./cli-utils')
const { red, bold } = require('colorette')
module.exports = function (program, server) {
diff --git a/bin/lib/updateIndex.js b/bin/lib/updateIndex.js
new file mode 100644
index 000000000..8412f7210
--- /dev/null
+++ b/bin/lib/updateIndex.js
@@ -0,0 +1,56 @@
+const fs = require('fs')
+const path = require('path')
+const cheerio = require('cheerio')
+const LDP = require('../../lib/ldp')
+const { URL } = require('url')
+const debug = require('../../lib/debug')
+const { readFile } = require('../../lib/common/fs-utils')
+
+const { compileTemplate, writeTemplate } = require('../../lib/common/template-utils')
+const { loadConfig, loadAccounts } = require('./cli-utils')
+const { getName, getWebId } = require('../../lib/common/user-utils')
+const { initConfigDir, initTemplateDirs } = require('../../lib/server-config')
+
+module.exports = function (program) {
+ program
+ .command('updateindex')
+ .description('Update index.html in root of all PODs that haven\'t been marked otherwise')
+ .action(async (options) => {
+ const config = loadConfig(program, options)
+ const configPath = initConfigDir(config)
+ const templates = initTemplateDirs(configPath)
+ const indexTemplatePath = path.join(templates.account, 'index.html')
+ const indexTemplate = await compileTemplate(indexTemplatePath)
+ const ldp = new LDP(config)
+ const accounts = loadAccounts(config)
+ const usersProcessed = accounts.map(async account => {
+ const accountDirectory = path.join(config.root, account)
+ const indexFilePath = path.join(accountDirectory, '/index.html')
+ if (!isUpdateAllowed(indexFilePath)) {
+ return
+ }
+ const accountUrl = getAccountUrl(account, config)
+ try {
+ const webId = await getWebId(accountDirectory, accountUrl, ldp.suffixMeta, (filePath) => readFile(filePath))
+ const name = await getName(webId, ldp.fetchGraph)
+ writeTemplate(indexFilePath, indexTemplate, { name, webId })
+ } catch (err) {
+ debug.errors(`Failed to create new index for ${account}: ${JSON.stringify(err, null, 2)}`)
+ }
+ })
+ await Promise.all(usersProcessed)
+ debug.accounts(`Processed ${usersProcessed.length} users`)
+ })
+}
+
+function getAccountUrl (name, config) {
+ const serverUrl = new URL(config.serverUri)
+ return `${serverUrl.protocol}//${name}.${serverUrl.host}/`
+}
+
+function isUpdateAllowed (indexFilePath) {
+ const indexSource = fs.readFileSync(indexFilePath, 'utf-8')
+ const $ = cheerio.load(indexSource)
+ const allowAutomaticUpdateValue = $('meta[name="solid-allow-automatic-updates"]').prop('content')
+ return !allowAutomaticUpdateValue || allowAutomaticUpdateValue === 'true'
+}
diff --git a/default-templates/new-account/index.html b/default-templates/new-account/index.html
index 8364a4335..7fe96bfa7 100644
--- a/default-templates/new-account/index.html
+++ b/default-templates/new-account/index.html
@@ -3,6 +3,8 @@
+
+
{{#if name}}{{name}} – {{/if}}Solid Home
diff --git a/lib/common/fs-utils.js b/lib/common/fs-utils.js
index 083a2bdc0..8da615064 100644
--- a/lib/common/fs-utils.js
+++ b/lib/common/fs-utils.js
@@ -1,5 +1,7 @@
module.exports.copyTemplateDir = copyTemplateDir
module.exports.processFile = processFile
+module.exports.readFile = readFile
+module.exports.writeFile = writeFile
const fs = require('fs-extra')
@@ -31,3 +33,11 @@ async function processFile (filePath, manipulateSourceFn) {
})
})
}
+
+function readFile (filePath, options = 'utf-8') {
+ return fs.readFileSync(filePath, options)
+}
+
+function writeFile (filePath, fileSource, options = 'utf-8') {
+ fs.writeFileSync(filePath, fileSource, options)
+}
diff --git a/lib/common/template-utils.js b/lib/common/template-utils.js
index 78bbb1479..afb6f05e8 100644
--- a/lib/common/template-utils.js
+++ b/lib/common/template-utils.js
@@ -1,8 +1,15 @@
+module.exports.compileTemplate = compileTemplate
module.exports.processHandlebarFile = processHandlebarFile
+module.exports.writeTemplate = writeTemplate
const Handlebars = require('handlebars')
const debug = require('../debug').errors
-const { processFile } = require('./fs-utils')
+const { processFile, readFile, writeFile } = require('./fs-utils')
+
+async function compileTemplate (filePath) {
+ const indexTemplateSource = readFile(filePath)
+ return Handlebars.compile(indexTemplateSource)
+}
/**
* Reads a file, processes it (performing template substitution), and saves
@@ -36,3 +43,9 @@ function processHandlebarTemplate (source, substitutions) {
return source
}
}
+
+function writeTemplate (filePath, template, substitutions) {
+ const source = template(substitutions)
+ writeFile(filePath, source)
+}
+
diff --git a/lib/common/user-utils.js b/lib/common/user-utils.js
index 3c0c46a69..c5bcd00b8 100644
--- a/lib/common/user-utils.js
+++ b/lib/common/user-utils.js
@@ -1,5 +1,28 @@
+const $rdf = require('rdflib')
+
+const SOLID = $rdf.Namespace('http://www.w3.org/ns/solid/terms#')
+const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#')
+
+module.exports.getName = getName
+module.exports.getWebId = getWebId
module.exports.isValidUsername = isValidUsername
+async function getName (webId, fetchGraph) {
+ const graph = await fetchGraph(webId)
+ const nameNode = graph.any($rdf.sym(webId), VCARD('fn'))
+ return nameNode.value
+}
+
+async function getWebId (accountDirectory, accountUrl, suffixMeta, fetchData) {
+ const metaFilePath = `${accountDirectory}/${suffixMeta}`
+ const metaFileUri = `${accountUrl}${suffixMeta}`
+ const metaData = await fetchData(metaFilePath)
+ const metaGraph = $rdf.graph()
+ $rdf.parse(metaData, metaGraph, metaFileUri, 'text/turtle')
+ const webIdNode = metaGraph.any(undefined, SOLID('account'), $rdf.sym(accountUrl))
+ return webIdNode.value
+}
+
function isValidUsername (username) {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(username)
}
diff --git a/lib/handlers/index.js b/lib/handlers/index.js
index a72dbde8a..4ebf58f43 100644
--- a/lib/handlers/index.js
+++ b/lib/handlers/index.js
@@ -3,6 +3,7 @@ module.exports = handler
const path = require('path')
const debug = require('debug')('solid:index')
const Negotiator = require('negotiator')
+const url = require('url')
async function handler (req, res, next) {
const indexFile = 'index.html'
@@ -19,7 +20,7 @@ async function handler (req, res, next) {
}
// redirect to the right container if missing trailing /
if (req.path.lastIndexOf('/') !== req.path.length - 1) {
- return res.redirect(301, path.join(req.path, '/'))
+ return res.redirect(301, req.path + '/')
}
if (requestedType && requestedType.indexOf('text/html') !== 0) {
@@ -29,7 +30,7 @@ async function handler (req, res, next) {
// Check if file exists in first place
await ldp.exists(req.hostname, path.join(req.path, indexFile))
- res.locals.path = path.join(req.path, indexFile)
+ res.locals.path = url.resolve(req.path, indexFile)
debug('Found an index for current path')
} catch (e) {
// Ignore errors
diff --git a/lib/ldp.js b/lib/ldp.js
index ebaa9274e..15e05c37d 100644
--- a/lib/ldp.js
+++ b/lib/ldp.js
@@ -102,7 +102,7 @@ class LDP {
const { path } = await this.resourceMapper.mapUrlToFile({ url })
return await promisify(fs.readFile)(path, {'encoding': 'utf8'})
} catch (err) {
- throw error(404, "Can't read file")
+ throw error(err.status, err.message)
}
}
@@ -273,7 +273,7 @@ class LDP {
*
* @return {Promise}
*/
- async fetchGraph (uri, options) {
+ async fetchGraph (uri, options = {}) {
const response = await fetch(uri)
if (!response.ok) {
const error = new Error(
@@ -284,7 +284,7 @@ class LDP {
}
const body = await response.text()
- return promisify(parse)(body, uri, getContentType(response.headers))
+ return parse(body, uri, getContentType(response.headers))
}
/**
diff --git a/lib/resource-mapper.js b/lib/resource-mapper.js
index 905781eac..c6f577c32 100644
--- a/lib/resource-mapper.js
+++ b/lib/resource-mapper.js
@@ -3,6 +3,7 @@ const URL = require('url')
const { promisify } = require('util')
const { types, extensions } = require('mime-types')
const readdir = promisify(fs.readdir)
+const HTTPError = require('./http-error')
// A ResourceMapper maintains the mapping between HTTP URLs and server filenames,
// following the principles of the “sweet spot” discussed in
@@ -79,7 +80,7 @@ class ResourceMapper {
if (isIndex) {
match = ''
} else {
- throw new Error(`File not found: ${fullPath}`)
+ throw new HTTPError(404, `File not found: ${fullPath}`)
}
}
path = `${folder}${match}`
diff --git a/lib/utils.js b/lib/utils.js
index d7bcd7b36..21e5f6870 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -10,6 +10,7 @@ module.exports.routeResolvedFile = routeResolvedFile
module.exports.getQuota = getQuota
module.exports.overQuota = overQuota
module.exports.getContentType = getContentType
+module.exports.parse = parse
const fs = require('fs')
const path = require('path')
@@ -69,6 +70,22 @@ function debrack (s) {
return s.substring(1, s.length - 1)
}
+async function parse (data, baseUri, contentType) {
+ const graph = $rdf.graph()
+ return new Promise((resolve, reject) => {
+ try {
+ return $rdf.parse(data, graph, baseUri, contentType, (err, str) => {
+ if (err) {
+ return reject(err)
+ }
+ resolve(str)
+ })
+ } catch (err) {
+ return reject(err)
+ }
+ })
+}
+
function pathBasename (fullpath) {
let bname = ''
if (fullpath) {
diff --git a/package-lock.json b/package-lock.json
index 48aed24e6..90699e6d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -370,6 +370,11 @@
"text-encoding": "^0.6.1"
}
},
+ "@types/node": {
+ "version": "10.12.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.9.tgz",
+ "integrity": "sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA=="
+ },
"@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
@@ -1447,6 +1452,11 @@
}
}
},
+ "boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
+ },
"bootstrap": {
"version": "3.3.7",
"resolved": "http://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz",
@@ -1842,6 +1852,19 @@
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
"dev": true
},
+ "cheerio": {
+ "version": "1.0.0-rc.2",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
+ "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=",
+ "requires": {
+ "css-select": "~1.2.0",
+ "dom-serializer": "~0.1.0",
+ "entities": "~1.1.1",
+ "htmlparser2": "^3.9.1",
+ "lodash": "^4.15.0",
+ "parse5": "^3.0.1"
+ }
+ },
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
@@ -2218,6 +2241,33 @@
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
"dev": true
},
+ "css-select": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
+ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
+ "requires": {
+ "boolbase": "~1.0.0",
+ "css-what": "2.1",
+ "domutils": "1.5.1",
+ "nth-check": "~1.0.1"
+ },
+ "dependencies": {
+ "domutils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ }
+ }
+ },
+ "css-what": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz",
+ "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ=="
+ },
"d": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz",
@@ -2485,11 +2535,49 @@
}
}
},
+ "dom-serializer": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
+ "requires": {
+ "domelementtype": "~1.1.1",
+ "entities": "~1.1.1"
+ },
+ "dependencies": {
+ "domelementtype": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+ "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
+ }
+ }
+ },
"domain-browser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA=="
},
+ "domelementtype": {
+ "version": "1.3.0",
+ "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
+ "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI="
+ },
+ "domhandler": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+ "requires": {
+ "domelementtype": "1"
+ }
+ },
+ "domutils": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+ "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
@@ -2588,6 +2676,11 @@
"iconv-lite": "~0.4.13"
}
},
+ "entities": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
+ },
"es5-ext": {
"version": "0.10.46",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz",
@@ -3733,6 +3826,39 @@
"resolved": "http://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
"integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E="
},
+ "htmlparser2": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz",
+ "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==",
+ "requires": {
+ "domelementtype": "^1.3.0",
+ "domhandler": "^2.3.0",
+ "domutils": "^1.5.1",
+ "entities": "^1.1.1",
+ "inherits": "^2.0.1",
+ "readable-stream": "^3.0.6"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz",
+ "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==",
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
"http-errors": {
"version": "1.6.3",
"resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@@ -5098,6 +5224,14 @@
"pify": "^3.0.0"
}
},
+ "nth-check": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
+ "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
+ "requires": {
+ "boolbase": "~1.0.0"
+ }
+ },
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
@@ -6520,6 +6654,14 @@
"pbkdf2": "^3.0.3"
}
},
+ "parse5": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
+ "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"parseurl": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
@@ -8136,6 +8278,30 @@
"source-pane": "^1.0.3"
}
},
+ "solid-permissions": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/solid-permissions/-/solid-permissions-0.6.0.tgz",
+ "integrity": "sha512-TiObEonB4l8VNa/6IOnNL0/7u3GKil4Bh/BiBB51AXOCuLahvTQ+LQc+C5KLHAykzyb9z5oM/BVOE3w8mzIm/A==",
+ "requires": {
+ "debug": "^3.1.0",
+ "solid-namespace": "0.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
+ "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+ }
+ }
+ },
"solid-ui": {
"version": "0.11.12",
"resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-0.11.12.tgz",
diff --git a/package.json b/package.json
index 05d5e45f7..7c123314d 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"busboy": "^0.2.12",
"cached-path-relative": "^1.0.2",
"camelize": "^1.0.0",
+ "cheerio": "^1.0.0-rc.2",
"colorette": "^1.0.5",
"commander": "^2.9.0",
"cors": "^2.7.1",
diff --git a/test/resources/accounts-acl/tim.localhost/write-acl/test-file$.txt b/test/resources/accounts-acl/tim.localhost/write-acl/test-file$.txt
new file mode 100644
index 000000000..ce002f06a
--- /dev/null
+++ b/test/resources/accounts-acl/tim.localhost/write-acl/test-file$.txt
@@ -0,0 +1 @@
+ .
\ No newline at end of file
diff --git a/test/resources/acl-tls/write-acl/test-file$.ttl b/test/resources/acl-tls/write-acl/test-file$.ttl
new file mode 100644
index 000000000..ce002f06a
--- /dev/null
+++ b/test/resources/acl-tls/write-acl/test-file$.ttl
@@ -0,0 +1 @@
+ .
\ No newline at end of file
diff --git a/test/unit/user-utils-test.js b/test/unit/user-utils-test.js
index 9170e7eac..67dea178d 100644
--- a/test/unit/user-utils-test.js
+++ b/test/unit/user-utils-test.js
@@ -1,8 +1,41 @@
const chai = require('chai')
const expect = chai.expect
const userUtils = require('../../lib/common/user-utils')
+const $rdf = require('rdflib')
describe('user-utils', () => {
+ describe('getName', () => {
+ let ldp
+ const webId = 'http://test#me'
+ const name = 'NAME'
+
+ beforeEach(() => {
+ const store = $rdf.graph()
+ store.add($rdf.sym(webId), $rdf.sym('http://www.w3.org/2006/vcard/ns#fn'), $rdf.lit(name))
+ ldp = { fetchGraph: () => Promise.resolve(store) }
+ })
+
+ it('should return name from graph', async () => {
+ const returnedName = await userUtils.getName(webId, ldp.fetchGraph)
+ expect(returnedName).to.equal(name)
+ })
+ })
+
+ describe('getWebId', () => {
+ let fetchGraph
+ const webId = 'https://test.localhost:8443/profile/card#me'
+ const suffixMeta = '.meta'
+
+ beforeEach(() => {
+ fetchGraph = () => Promise.resolve(`<${webId}> >.`)
+ })
+
+ it('should return webId from meta file', async () => {
+ const returnedWebId = await userUtils.getWebId('foo', 'https://bar/', suffixMeta, fetchGraph)
+ expect(returnedWebId).to.equal(webId)
+ })
+ })
+
describe('isValidUsername', () => {
it('should accect valid usernames', () => {
const usernames = [