UNPKG

ipfs

Version:

JavaScript implementation of the IPFS specification

520 lines (435 loc) 14.4 kB
'use strict' const CID = require('cids') const multipart = require('ipfs-multipart') const dagPB = require('ipld-dag-pb') const { DAGNode, DAGLink } = dagPB const Joi = require('@hapi/joi') const multibase = require('multibase') const Boom = require('boom') const { cidToString } = require('../../../utils/cid') const debug = require('debug') const log = debug('ipfs:http-api:object') log.error = debug('ipfs:http-api:object:error') // common pre request handler that parses the args and returns `key` which is assigned to `request.pre.args` exports.parseKey = (request, h) => { if (!request.query.arg) { throw Boom.badRequest("Argument 'key' is required") } try { return { key: new CID(request.query.arg) } } catch (err) { log.error(err) throw Boom.badRequest('invalid ipfs ref path') } } exports.new = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, async handler (request, h) { const { ipfs } = request.server.app const template = request.query.arg let cid, node try { cid = await ipfs.object.new(template) node = await ipfs.object.get(cid) } catch (err) { throw Boom.boomify(err, { message: 'Failed to create object' }) } const nodeJSON = node.toJSON() const answer = { Data: nodeJSON.data, Hash: cidToString(cid, { base: request.query['cid-base'], upgrade: false }), Size: nodeJSON.size, Links: nodeJSON.links.map((l) => { return { Name: l.name, Size: l.size, Hash: cidToString(l.cid, { base: request.query['cid-base'], upgrade: false }) } }) } return h.response(answer) } } exports.get = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // uses common parseKey method that returns a `key` parseArgs: exports.parseKey, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { key } = request.pre.args const enc = request.query.enc || 'base58' const { ipfs } = request.server.app let node, cid try { node = await ipfs.object.get(key, { enc: enc }) cid = await dagPB.util.cid(dagPB.util.serialize(node)) } catch (err) { throw Boom.boomify(err, { message: 'Failed to get object' }) } const nodeJSON = node.toJSON() if (Buffer.isBuffer(node.data)) { nodeJSON.data = node.data.toString(request.query['data-encoding'] || undefined) } const answer = { Data: nodeJSON.data, Hash: cidToString(cid, { base: request.query['cid-base'], upgrade: false }), Size: nodeJSON.size, Links: nodeJSON.links.map((l) => { return { Name: l.name, Size: l.size, Hash: cidToString(l.cid, { base: request.query['cid-base'], upgrade: false }) } }) } return h.response(answer) } } exports.put = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // pre request handler that parses the args and returns `node` // which is assigned to `request.pre.args` async parseArgs (request, h) { if (!request.payload) { throw Boom.badRequest("File argument 'data' is required") } const enc = request.query.inputenc const fileStream = await new Promise((resolve, reject) => { multipart.reqParser(request.payload) .on('file', (name, stream) => resolve(stream)) .on('end', () => reject(Boom.badRequest("File argument 'data' is required"))) }) const data = await new Promise((resolve, reject) => { fileStream .on('data', data => resolve(data)) .on('end', () => reject(Boom.badRequest("File argument 'data' is required"))) }) if (enc === 'protobuf') { try { return { node: await dagPB.util.deserialize(data) } } catch (err) { throw Boom.badRequest('Failed to deserialize: ' + err) } } let nodeJson try { nodeJson = JSON.parse(data.toString()) } catch (err) { throw Boom.badRequest('Failed to parse the JSON: ' + err) } try { return { node: DAGNode.create(nodeJson.Data, nodeJson.Links) } } catch (err) { throw Boom.badRequest('Failed to create DAG node: ' + err) } }, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { node } = request.pre.args let cid try { cid = await ipfs.object.put(node) } catch (err) { throw Boom.boomify(err, { message: 'Failed to put node' }) } const nodeJSON = node.toJSON() const answer = { Data: nodeJSON.data, Hash: cidToString(cid, { base: request.query['cid-base'], upgrade: false }), Size: nodeJSON.size, Links: nodeJSON.links.map((l) => { return { Name: l.name, Size: l.size, Hash: cidToString(l.cid, { base: request.query['cid-base'], upgrade: false }) } }) } return h.response(answer) } } exports.stat = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // uses common parseKey method that returns a `key` parseArgs: exports.parseKey, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { key } = request.pre.args let stats try { stats = await ipfs.object.stat(key) } catch (err) { throw Boom.boomify(err, { message: 'Failed to stat object' }) } stats.Hash = cidToString(stats.Hash, { base: request.query['cid-base'], upgrade: false }) return h.response(stats) } } exports.data = { // uses common parseKey method that returns a `key` parseArgs: exports.parseKey, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { key } = request.pre.args let data try { data = await ipfs.object.data(key) } catch (err) { throw Boom.boomify(err, { message: 'Failed to get object data' }) } return h.response(data) } } exports.links = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // uses common parseKey method that returns a `key` parseArgs: exports.parseKey, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { key } = request.pre.args const response = { Hash: cidToString(key, { base: request.query['cid-base'], upgrade: false }) } const links = await ipfs.object.links(key) if (links) { response.Links = links.map((l) => { return { Name: l.Name, Size: l.Tsize, Hash: cidToString(l.Hash, { base: request.query['cid-base'], upgrade: false }) } }) } return h.response(response) } } // common pre request handler that parses the args and returns `data` & `key` which are assigned to `request.pre.args` exports.parseKeyAndData = async (request, h) => { if (!request.query.arg) { throw Boom.badRequest("Argument 'root' is required") } if (!request.payload) { throw Boom.badRequest("File argument 'data' is required") } // TODO: support ipfs paths: https://github.com/ipfs/http-api-spec/pull/68/files#diff-2625016b50d68d922257f74801cac29cR3880 let cid try { cid = new CID(request.query.arg) } catch (err) { throw Boom.badRequest('invalid ipfs ref path') } const fileStream = await new Promise((resolve, reject) => { multipart.reqParser(request.payload) .on('file', (fileName, fileStream) => resolve(fileStream)) .on('end', () => reject(Boom.badRequest("File argument 'data' is required"))) }) const fileData = await new Promise((resolve, reject) => { fileStream .on('data', data => resolve(data)) .on('end', () => reject(Boom.badRequest("File argument 'data' is required"))) }) return { data: fileData, key: cid } } exports.patchAppendData = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // uses common parseKeyAndData method that returns a `data` & `key` parseArgs: exports.parseKeyAndData, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { key, data } = request.pre.args let cid, node try { cid = await ipfs.object.patch.appendData(key, data) node = await ipfs.object.get(cid) } catch (err) { throw Boom.boomify(err, { message: 'Failed to append data to object' }) } const nodeJSON = node.toJSON() const answer = { Data: nodeJSON.data, Hash: cidToString(cid, { base: request.query['cid-base'], upgrade: false }), Size: nodeJSON.size, Links: nodeJSON.links.map((l) => { return { Name: l.name, Size: l.size, Hash: cidToString(l.cid, { base: request.query['cid-base'], upgrade: false }) } }) } return h.response(answer) } } exports.patchSetData = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // uses common parseKeyAndData method that returns a `data` & `key` parseArgs: exports.parseKeyAndData, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { key, data } = request.pre.args let cid, node try { cid = await ipfs.object.patch.setData(key, data) node = await ipfs.object.get(cid) } catch (err) { throw Boom.boomify(err, { message: 'Failed to set data on object' }) } const nodeJSON = node.toJSON() return h.response({ Hash: cidToString(cid, { base: request.query['cid-base'], upgrade: false }), Links: nodeJSON.links.map((l) => { return { Name: l.name, Size: l.size, Hash: cidToString(l.cid, { base: request.query['cid-base'], upgrade: false }) } }) }) } } exports.patchAddLink = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // pre request handler that parses the args and returns `root`, `name` & `ref` which is assigned to `request.pre.args` parseArgs: (request, reply) => { if (!(request.query.arg instanceof Array) || request.query.arg.length !== 3) { throw Boom.badRequest("Arguments 'root', 'name' & 'ref' are required") } if (!request.query.arg[0]) { throw Boom.badRequest('cannot create link with no root') } if (!request.query.arg[1]) { throw Boom.badRequest('cannot create link with no name!') } if (!request.query.arg[2]) { throw Boom.badRequest('cannot create link with no ref') } try { return { root: new CID(request.query.arg[0]), name: request.query.arg[1], ref: new CID(request.query.arg[2]) } } catch (err) { log.error(err) throw Boom.badRequest('invalid ipfs ref path') } }, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { root, name, ref } = request.pre.args let node, cid try { node = await ipfs.object.get(ref) cid = await ipfs.object.patch.addLink(root, new DAGLink(name, node.size, ref)) node = await ipfs.object.get(cid) } catch (err) { throw Boom.boomify(err, { message: 'Failed to add link to object' }) } const nodeJSON = node.toJSON() const answer = { Data: nodeJSON.data, Hash: cidToString(cid, { base: request.query['cid-base'], upgrade: false }), Size: nodeJSON.size, Links: nodeJSON.links.map((l) => { return { Name: l.name, Size: l.size, Hash: cidToString(l.cid, { base: request.query['cid-base'], upgrade: false }) } }) } return h.response(answer) } } exports.patchRmLink = { validate: { query: Joi.object().keys({ 'cid-base': Joi.string().valid(multibase.names) }).unknown() }, // pre request handler that parses the args and returns `root` & `link` which is assigned to `request.pre.args` parseArgs (request, h) { if (!(request.query.arg instanceof Array) || request.query.arg.length !== 2) { throw Boom.badRequest("Arguments 'root' & 'link' are required") } if (!request.query.arg[1]) { throw Boom.badRequest('cannot remove link with no name!') } try { return { root: new CID(request.query.arg[0]), link: request.query.arg[1] } } catch (err) { log.error(err) throw Boom.badRequest('invalid ipfs ref path') } }, // main route handler which is called after the above `parseArgs`, but only if the args were valid async handler (request, h) { const { ipfs } = request.server.app const { root, link } = request.pre.args let cid, node try { cid = await ipfs.object.patch.rmLink(root, { name: link }) node = await ipfs.object.get(cid) } catch (err) { throw Boom.boomify(err, { message: 'Failed to remove link from object' }) } const nodeJSON = node.toJSON() const answer = { Data: nodeJSON.data, Hash: cidToString(cid, { base: request.query['cid-base'], upgrade: false }), Size: nodeJSON.size, Links: nodeJSON.links.map((l) => { return { Name: l.name, Size: l.size, Hash: cidToString(l.cid, { base: request.query['cid-base'], upgrade: false }) } }) } return h.response(answer) } }