UNPKG

ldn-inbox-server

Version:

A demonstration Event Notifications Inbox server

394 lines (332 loc) 9.58 kB
const Validator = require('jsonschema').Validator; const fs = require('fs'); const md5 = require('md5'); const { getLogger, fetchOriginal, backOff_fetch, sendNotification, moveTo, parseAsJSON, generateId, generatePublished, parseConfig } = require('../lib/util'); const { handle_inbox } = require('../lib/handler'); const logger = getLogger(); function doInbox(req,res,opts) { if (req.method === 'GET') { doInboxGET(req,res,opts); } else if (req.method == 'HEAD') { doInboxHEAD(req,res,opts); } else if (req.method === 'POST' && opts['inboxWriteable'] == 1) { doInboxPOST(req,res,opts); } else if (req.method === 'OPTIONS') { doInboxOPTIONS(req,res,opts); } else { logger.error(`tried method ${req.method} on inbox : forbidden`); res.writeHead(403); res.end('Forbidden'); } } function doInboxGET(req,res,opts) { const pathItem = req.url.substring(opts['url'].length); logger.debug(`doInboxGET (for ${pathItem})`); if (pathItem === '/') { doInboxGET_Index(req,res,opts); } else if (pathItem.match(/^\/[A-Za-z0-9_-]+\.jsonld$/)) { doInboxGET_Read(req,res,opts); } else { res.writeHead(403); res.end('Forbidden'); } } function doInboxOPTIONS(req,res,opts) { const pathItem = req.url.substring(opts['url'].length); const id = pathItem.substring(1); logger.debug(`doInboxOPTIONS (for ${pathItem})`); if (pathItem === '/') { const meta = getBody(`${id}.meta`,opts); let metadata = {}; if (meta) { metadata = JSON.parse(meta); } const allow = ['GET','HEAD','OPTIONS']; if (opts['inboxWriteable'] == 1) { allow.push('POST'); metadata['Accept-Post'] = 'application/ld+json'; } metadata['Allow'] = allow.join(","); logger.debug(metadata); for (let property in metadata) { if (property !== 'Content-Type') { res.setHeader(property,metadata[property]); } } res.writeHead(200); res.end(); return; } if (pathItem.match(/^\/[A-Za-z0-9_-]+\.jsonld$/)) { const result = getBody(id,opts); if (! result) { res.writeHead(403); res.end('Forbidden'); return; } const meta = getBody(`${id}.meta`,opts); let metadata = {}; if (meta) { metadata = JSON.parse(meta); } const allow = ['OPTIONS']; if (opts['inboxPublic'] == 1) { allow.push('GET'); allow.push('HEAD'); } metadata['Allow'] = allow.join(","); logger.debug(metadata); for (let property in metadata) { if (property !== 'Content-Type') { res.setHeader(property,metadata[property]); } } res.writeHead(200); res.end(); return; } else { res.writeHead(403); res.end('Forbidden'); return; } } function doInboxHEAD(req,res,opts) { const pathItem = req.url.substring(opts['url'].length); const id = pathItem.substring(1); logger.debug(`doInboxHEAD (for ${pathItem})`); if (pathItem === '/') { const meta = getBody(`${id}.meta`,opts); if (meta) { const metadata = JSON.parse(meta); for (let property in metadata) { res.setHeader(property,metadata[property]); } } else { res.setHeader('Content-Type','application/ld+json'); } res.writeHead(200); res.end(); return; } if (pathItem.match(/^\/[A-Za-z0-9_-]+\.jsonld$/) && opts['inboxPublic'] == 1) { const result = getBody(id,opts); if (! result) { res.writeHead(403); res.end('Forbidden'); return; } const meta = getBody(`${id}.meta`,opts); if (meta) { const metadata = JSON.parse(meta); for (let property in metadata) { res.setHeader(property,metadata[property]); } } else { res.setHeader('Content-Type','application/ld+json'); } res.writeHead(200); res.end(); return; } else { res.writeHead(403); res.end('Forbidden'); return; } } function doInboxGET_Index(req,res,opts) { let notifications = []; if (opts['inboxPublic'] == 1) { notifications = listInbox(opts).map( (e) => { return opts['base'] + '/' + opts['url'] + e; }); } const result = { "@context": "http://www.w3.org/ns/ldp", "@id": opts['base'] + '/' + opts['url'], "contains": notifications }; const meta = getBody(`.meta`,opts); if (meta) { const metadata = JSON.parse(meta); for (let property in metadata) { res.setHeader(property,metadata[property]); } } else { res.setHeader('Content-Type','application/ld+json'); } res.writeHead(200); res.end(JSON.stringify(result,null,2)); } function doInboxGET_Read(req,res,opts) { const pathItem = req.url.substring(opts['url'].length); const id = pathItem.substring(1); const result = getBody(id,opts); if (result && opts['inboxPublic'] == 1) { const meta = getBody(`${id}.meta`,opts); if (meta) { const metadata = JSON.parse(meta); for (let property in metadata) { res.setHeader(property,metadata[property]); } } else { res.setHeader('Content-Type','application/ld+json'); } res.writeHead(200); res.end(result); } else { res.writeHead(403); res.end('Forbidden'); } } function doInboxPOST(req,res,opts) { const pathItem = req.url.substring(opts['url'].length); if (pathItem !== '/') { req.writeHead(403); res.end('Forbidden'); return; } const headers = req.headers; if (headers && headers['content-type'] && ( headers['content-type'].startsWith('application/ld+json') || headers['content-type'].startsWith('application/json') ) ) { // We are ok } else { logger.error(`tried content-type ${headers['content-type']} : unknown`); res.writeHead(400); res.end(`Need a Content-Type 'application/ld+json'`); return; } let postData = '' req.on('data', (data) => { postData += data; }); req.on('end',() => { logger.debug(postData); if (checkBody(postData,opts)) { const id = storeBody(postData,opts); if (opts['inboxPublic'] == 1) { const message = `Accepted ${req.url}${id}`; logger.debug(message); res.setHeader('Location',`${req.url}${id}`); res.writeHead(201); res.end(message); } else { const message = JSON.stringify({"message":"Request accepted"}); logger.debug(`202 : ${message}`); res.writeHead(202); res.end(message); } } else { logger.error(`not-accepted post`); res.writeHead(400); res.end(`Looks like a weird POST to me...`); } }); } function listInbox(opts) { const glob = new RegExp("^.*\\.jsonld$"); logger.debug(`listInbox()`); try { const entries = fs.readdirSync(opts['inbox']).filter( (file) => { return file.match(glob); }); return entries; } catch(e) { logger.error(e); return []; } } function getBody(id,opts) { logger.debug(`getBody(${id})`); try { return fs.readFileSync(opts['inbox'] + '/' + id, {encoding : 'utf-8'}); } catch(e) { return null; } } function storeBody(data,opts) { try { const id = md5(data); const newpath = `${opts['inbox']}/${id}.jsonld`; if (! fs.existsSync(newpath)) { logger.debug(`storing ${newpath}`); fs.writeFileSync(newpath,data); } else { logger.debug(`skipping ${newpath} : already exists`); } return `${id}.jsonld`; } catch (e) { return null; } } function checkBody(data,opts) { if (! opts['schema'] || ! fs.existsSync(opts['schema'])) { logger.debug(`no JSON_SCHEMA found`); return true; } else { logger.debug(`using schema: ${opts['schema']}`); } try { const JSON_SCHEMA = JSON.parse(fs.readFileSync(opts['schema'], { encoding: 'utf-8'})); const json = JSON.parse(data); const v = new Validator(); const res = v.validate(json,JSON_SCHEMA); if (res.errors.length == 0) { return true; } else { return false; } } catch (e) { logger.error(`failed to validate data`); logger.debug(e); return false; } } module.exports = { doInbox, getLogger , parseAsJSON , backOff_fetch , fetchOriginal , moveTo , sendNotification , handle_inbox , generateId , generatePublished , parseConfig };