diff --git a/package.json b/package.json index d319c41..3cc0227 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "borc": "^2.0.2", "bs58": "^4.0.0", "cids": "^0.4.0", + "is-circular": "^1.0.1", "multihashes": "^0.3.2", "multihashing-async": "^0.4.0", "traverse": "^0.6.6" @@ -47,6 +48,8 @@ "devDependencies": { "aegir": "^9.4.0", "chai": "^3.5.0", + "deep-freeze": "0.0.1", + "garbage": "0.0.0", "ipfs-block": "^0.5.4", "pre-commit": "^1.2.2" }, @@ -59,4 +62,4 @@ "npmcdn-to-unpkg-bot ", "wanderer " ] -} \ No newline at end of file +} diff --git a/src/util.js b/src/util.js index c49bae8..2514c01 100644 --- a/src/util.js +++ b/src/util.js @@ -5,39 +5,90 @@ const multihashing = require('multihashing-async') const CID = require('cids') const waterfall = require('async/waterfall') const setImmediate = require('async/setImmediate') +const isCircular = require('is-circular') const resolver = require('./resolver') +// https://github.com/ipfs/go-ipfs/issues/3570#issuecomment-273931692 +const CID_CBOR_TAG = 42 + +function tagCID (cid) { + return new cbor.Tagged(CID_CBOR_TAG, cid) +} + +const decoder = new cbor.Decoder({ + tags: { + [CID_CBOR_TAG]: (val) => ({'/': val}) + } +}) + +function replaceCIDbyTAG (dagNode) { + if (isCircular(dagNode)) { + throw new Error('The object passed has circular references') + } + + function transform (obj) { + if (!obj || Buffer.isBuffer(obj) || typeof obj === 'string') { + return obj + } + + if (Array.isArray(obj)) { + return obj.map(transform) + } + + const keys = Object.keys(obj) + + // only `{'/': 'link'}` are valid + if (keys.length === 1 && keys[0] === '/') { + // Multiaddr encoding + // if (typeof link === 'string' && isMultiaddr(link)) { + // link = new Multiaddr(link).buffer + // } + + return tagCID(obj['/']) + } else if (keys.length > 0) { + // Recursive transform + let out = {} + keys.forEach((key) => { + if (typeof obj[key] === 'object') { + out[key] = transform(obj[key]) + } else { + out[key] = obj[key] + } + }) + return out + } else { + return obj + } + } + + return transform(dagNode) +} + exports = module.exports exports.serialize = (dagNode, callback) => { let serialized + try { - serialized = cbor.encode(dagNode) + const dagNodeTagged = replaceCIDbyTAG(dagNode) + serialized = cbor.encode(dagNodeTagged) } catch (err) { - // return is important, otherwise in case of error the execution would continue - return setImmediate(() => { - callback(err) - }) + return setImmediate(() => callback(err)) } - setImmediate(() => { - callback(null, serialized) - }) + setImmediate(() => callback(null, serialized)) } exports.deserialize = (data, callback) => { - let res + let deserialized + try { - res = cbor.decodeFirst(data) + deserialized = decoder.decodeFirst(data) } catch (err) { - return setImmediate(() => { - callback(err) - }) + return setImmediate(() => callback(err)) } - setImmediate(() => { - callback(null, res) - }) + setImmediate(() => callback(null, deserialized)) } exports.cid = (dagNode, callback) => { diff --git a/test/util.spec.js b/test/util.spec.js index 8f7a8b2..2a36d47 100644 --- a/test/util.spec.js +++ b/test/util.spec.js @@ -2,19 +2,35 @@ 'use strict' const expect = require('chai').expect +const deepFreeze = require('deep-freeze') +const garbage = require('garbage') +const map = require('async/map') const dagCBOR = require('../src') describe('util', () => { const obj = { someKey: 'someValue', - link: { '/': 'aaaaa' } + link: { '/': 'aaaaa' }, + links: [{'/': 1}, {'/': 2}], + nested: { + hello: 'world', + link: { '/': 'mylink' } + } } it('.serialize and .deserialize', (done) => { + // freeze, to ensure we don't change the source objecdt + deepFreeze(obj) dagCBOR.util.serialize(obj, (err, serialized) => { expect(err).to.not.exist expect(Buffer.isBuffer(serialized)).to.be.true + // Check for the tag 42 + // d8 = tag, 2a = 42 + expect( + serialized.toString('hex').match(/d82a/g) + ).to.have.length(4) + dagCBOR.util.deserialize(serialized, (err, deserialized) => { expect(err).to.not.exist expect(obj).to.eql(deserialized) @@ -42,4 +58,29 @@ describe('util', () => { done() }) }) + + it('serialize and deserialize - garbage', (done) => { + const inputs = [] + for (let i = 0; i < 1000; i++) { + inputs.push({in: garbage(100)}) + } + + map(inputs, (input, cb) => { + dagCBOR.util.serialize(input, cb) + }, (err, encoded) => { + if (err) { + return done(err) + } + map(encoded, (enc, cb) => { + dagCBOR.util.deserialize(enc, cb) + }, (err, out) => { + if (err) { + return done(err) + } + + expect(inputs).to.be.eql(out) + done() + }) + }) + }) })