pax_global_header00006660000000000000000000000064136104265110014511gustar00rootroot0000000000000052 comment=c408b04684cf7c8352a44b515bdecae4159283cb rdf-canonize-1.1.0/000077500000000000000000000000001361042651100140675ustar00rootroot00000000000000rdf-canonize-1.1.0/.editorconfig000066400000000000000000000003761361042651100165520ustar00rootroot00000000000000# http://editorconfig.org root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.{js,json,jsonld,yaml,yml}] indent_style = space indent_size = 2 [*.{h,cc}] indent_style = space indent_size = 2 rdf-canonize-1.1.0/.eslintrc.js000066400000000000000000000002211361042651100163210ustar00rootroot00000000000000module.exports = { env: { browser: true, commonjs: true, node: true }, extends: 'eslint-config-digitalbazaar', root: true }; rdf-canonize-1.1.0/.gitignore000066400000000000000000000002701361042651100160560ustar00rootroot00000000000000*.sublime-project *.sublime-workspace *.sw[nop] *~ .DS_Store .cdtproject .classpath .cproject .nyc_output .project .settings TAGS build coverage dist node_modules npm-debug.log v8.log rdf-canonize-1.1.0/.travis.yml000066400000000000000000000010541361042651100162000ustar00rootroot00000000000000language: node_js node_js: - "6" - "8" - "10" - "12" - "node" sudo: false install: - npm install # install native module - npm install --no-save rdf-canonize-native - npm run fetch-test-suite script: - if [ "x$BUNDLER" = "x" ]; then npm run test; fi # - if [ "x$BUNDLER" != "x" ]; then npm run test-karma; fi # only run karma tests for one node version #matrix: # include: # - node_js: "6" # env: BUNDLER=webpack # - node_js: "6" # env: BUNDLER=browserify notifications: email: on_success: change on_failure: change rdf-canonize-1.1.0/CHANGELOG.md000066400000000000000000000102631361042651100157020ustar00rootroot00000000000000# rdf-canonize ChangeLog ## 1.1.0 - 2020-01-17 ### Changed - Optimize away length check on paths. - Update node-forge dependency. - Update semver dependency. ## 1.0.3 - 2019-03-06 ### Changed - Update node-forge dependency. ## 1.0.2 - 2019-02-21 ### Fixed - Fix triple comparator in n-quads parser. ### Added - Add eslint support. ## 1.0.1 - 2019-01-23 ### Changed - Remove use of deprecated `util.isUndefined()`. Avoids unneeded `util` polyfill in webpack build. ## 1.0.0 - 2019-01-23 ### Notes - **WARNING**: This release has a **BREAKING** change that could cause the canonical N-Quads output to differ from previous releases. Specifically, tabs in literals are no longer escaped. No backwards compatibility mode is provided at this time but if you believe it is needed, please file an issue. - If you wish to use the native bindings, you must now install `rdf-canonize-native` yourself. It is no longer a dependency. See below. ### Fixed - **BREAKING**: N-Quad canonical serialized output. - Only escape 4 chars. - Now compatible with https://www.w3.org/TR/n-triples/#canonical-ntriples ### Changed - Improve N-Quads parsing. - Unescape literals. - Handle unicode escapes. - N-Quad serialization optimization. - Varies based on input by roughly ~1-2x. - **BREAKING**: Remove `rdf-canonize-native` as a dependency. The native bindings will still be used if `rdf-canonize-native` can be loaded. This means if you want the native bindings you *must* install them yourself. This change was done due to various problems caused by having any type of dependency involving the native code. With modern runtimes the JavaScript implementation is in many cases *faster*. The native bindings do have overhead but can be useful in cases where you need to offload canonizing into the background. It is recommended to perform benchmarks to determine which method works best in your case. - Update webpack and babel. - **BREAKING**: Remove `usePureJavaScript` option and make the JavaScript implementation the default. Add explicit `useNative` option to force the use of the native implementation from `rdf-canonize-native`. An error will be thrown if native bindings are not available. ## 0.3.0 - 2018-11-01 ### Changed - **BREAKING**: Move native support to optional `rdf-canonize-native` package. If native support is **required** in your environment then *also* add a dependency on the `rdf-canonize-native` package directly. This package only has an *optional* dependency on the native package to allow systems without native binding build tools to use the JavaScript implementation alone. ### Added - Istanbul coverage support. ## 0.2.5 - 2018-11-01 ### Fixed - Accept N-Quads upper case language tag. - Improve acceptable N-Quads blank node labels. ## 0.2.4 - 2018-04-25 ### Fixed - Update for Node.js 10 / OpenSSL 1.1 API. ### Changed - Update nan dependency for Node.js 10 support. ## 0.2.3 - 2017-12-05 ### Fixed - Avoid variable length arrays. Not supported by some C++ compilers. ## 0.2.2 - 2017-12-04 ### Fixed - Use const array initializer sizes. ### Changed - Comment out debug logging. ## 0.2.1 - 2017-10-16 ### Fixed - Distribute `binding.gyp`. ## 0.2.0 - 2017-10-16 ### Added - Benchmark tool using the same manifests as the test system. - Support Node.js 6. - Native Node.js addon support for URDNA2015. Improves performance. - `usePureJavaScript` option to only use JavaScript. ## 0.1.5 - 2017-09-18 ### Changed - **BREAKING**: Remove Node.js 4.x testing and native support. Use a transpiler such as babel if you need further 4.x support. ## 0.1.4 - 2017-09-17 ### Added - Expose `IdentifierIssuer` helper class. ## 0.1.3 - 2017-09-17 ### Fixed - Fix build. ## 0.1.2 - 2017-09-17 ### Changed - Change internals to use ES6. - Return Promise from API for async method. ## 0.1.1 - 2017-08-15 ### Fixed - Move node-forge to dependencies. ## 0.1.0 - 2017-08-15 ### Added - RDF Dataset Normalization async implementation from [jsonld.js][]. - webpack support. - Split messageDigest into Node.js and browser files. - Node.js file uses native crypto module. - Browser file uses forge. - See git history for all changes. [jsonld.js]: https://github.com/digitalbazaar/jsonld.js rdf-canonize-1.1.0/LICENSE000066400000000000000000000027741361042651100151060ustar00rootroot00000000000000New BSD License (3-clause) Copyright (c) 2016, Digital Bazaar, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Digital Bazaar, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rdf-canonize-1.1.0/README.md000066400000000000000000000070711361042651100153530ustar00rootroot00000000000000# rdf-canonize [![Build status](https://img.shields.io/travis/digitalbazaar/rdf-canonize.svg)](https://travis-ci.org/digitalbazaar/rdf-canonize) [![Dependency Status](https://img.shields.io/david/digitalbazaar/rdf-canonize.svg)](https://david-dm.org/digitalbazaar/rdf-canonize) An implementation of the [RDF Dataset Normalization Algorithm][] in JavaScript. Introduction ------------ ... Installation ------------ ### node.js + npm ``` npm install rdf-canonize ``` ```js const canonize = require('rdf-canonize'); ``` ### node.js + npm + native bindings This package has support for [rdf-canonize-native][]. This package can be useful if your application requires doing many canonizing operations asyncronously in parallel or in the background. It is **highly recommended** that you understand your requirements and benchmark using JavaScript vs native bindings. The native bindings add overhead and the JavaScript implementation may be faster with modern runtimes. The native bindings are not installed by default and must be explicitly installed. ``` npm install rdf-canonize npm install rdf-canonize-native ``` Note that the native code is not automatically used. To use the native bindings you must have them installed and set the `useNative` option to `true`. ```js const canonize = require('rdf-canonize'); ``` ### Browser (AMD) + npm ``` npm install rdf-canonize ``` Use your favorite technology to load `node_modules/dist/rdf-canonize.min.js`. ### HTML Various NPM proxy CDN sites offer direct access to NPM files. Examples -------- ```js const dataset = { // ... }; // canonize a data set with a particular algorithm with callback canonize.canonize(dataset, {algorithm: 'URDNA2015'}, function(err, canonical) { // ... }); // canonize a data set with a particular algorithm with async/await const canonical = await canonize.canonize(dataset, {algorithm: 'URDNA2015'}); // canonize a data set with a particular algorithm with callback // force use of the native implementation canonize.canonize(dataset, { algorithm: 'URDNA2015', useNative: true }, function(err, canonical) { // ... }); ``` Related Modules --------------- * [jsonld.js][]: An implementation of the [JSON-LD][] specification. Tests ----- This library includes a sample testing utility which may be used to verify that changes to the processor maintain the correct output. The test suite is included in an external repository: https://github.com/json-ld/normalization This should be a sibling directory of the rdf-canonize directory or in a `test-suites` dir. To clone shallow copies into the `test-suites` dir you can use the following: npm run fetch-test-suite Node.js tests can be run with a simple command: npm test If you installed the test suites elsewhere, or wish to run other tests, use the `TEST_DIR` environment var: TEST_DIR="/tmp/tests" npm test To generate earl reports: # generate the earl report for node.js EARL=earl-node.jsonld npm test Benchmark --------- See docs in the [benchmark README](./benchmark/README.md). Source ------ The source code for this library is available at: https://github.com/digitalbazaar/rdf-canonize Commercial Support ------------------ Commercial support for this library is available upon request from [Digital Bazaar][]: support@digitalbazaar.com [Digital Bazaar]: https://digitalbazaar.com/ [JSON-LD]: https://json-ld.org/ [RDF Dataset Normalization Algorithm]: https://json-ld.github.io/normalization/ [jsonld.js]: https://github.com/digitalbazaar/jsonld.js [rdf-canonize-native]: https://github.com/digitalbazaar/rdf-canonize-native rdf-canonize-1.1.0/benchmark/000077500000000000000000000000001361042651100160215ustar00rootroot00000000000000rdf-canonize-1.1.0/benchmark/.gitignore000066400000000000000000000000351361042651100200070ustar00rootroot00000000000000block-*-in.nq block-*-out.nq rdf-canonize-1.1.0/benchmark/README.md000066400000000000000000000014651361042651100173060ustar00rootroot00000000000000RDF Dataset Canonicalization Benchmark -------------------------------------- The benchmark system uses the same manifest setup as the test suite. This allows setting up both tests and benchmarks with one setup. In the root dir run **all** test suite tests: npm run benchmark To run a particular dir or file: TEST_DIR=./benchmark npm run benchmark TEST_DIR=./benchmark/m2.jsonld npm run benchmark As an addition to the test suite, tests can be marked with boolean "skip" or "only" flags. The "only" support is a hack and requies the env var "ONLY" to be set: ONLY=1 npm run benchmark ONLY=1 TEST_DIR=./benchmark npm run benchmark Tests are benchmarked with a matrix of {async/sync} {js/native} {x1/x10}. To run large "block" benchmarks, run the builder script first: node make-tests.js rdf-canonize-1.1.0/benchmark/benchmark.js000066400000000000000000000267471361042651100203310ustar00rootroot00000000000000/** * Benchmark runner for rdf-canonize. * * @author Dave Longley * @author David I. Lehn * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ /* eslint-disable indent */ (function() { 'use strict'; // detect node.js (vs. browser) const _nodejs = (typeof process !== 'undefined' && process.versions && process.versions.node); const Benchmark = require('benchmark'); const assert = require('assert'); const fs = require('fs'); let path; if(_nodejs) { path = require('path'); } const canonize = require('..'); const NQuads = require('../lib/NQuads'); // try to load native bindings let rdfCanonizeNative; // try regular load try { rdfCanonizeNative = require('rdf-canonize-native'); } catch(e) { // try peer package try { rdfCanonizeNative = require('../../rdf-canonize-native'); } catch(e) { } } // use native bindings if(rdfCanonizeNative) { canonize._rdfCanonizeNative(rdfCanonizeNative); } else { // skip native tests console.warn('rdf-canonize-native not found'); } const _TEST_SUITE_PATHS = [ process.env.TEST_DIR, '../normalization/tests', './test-suites/normalization/tests', ]; const TEST_SUITE = _TEST_SUITE_PATHS.find(pathExists); if(!TEST_SUITE) { throw new Error('Test suite not found.'); } const ROOT_MANIFEST_DIR = resolvePath(TEST_SUITE); const TEST_TYPES = { 'rdfn:Urgna2012EvalTest': { params: [ parseNQuads(readTestNQuads('action')), createTestOptions({ algorithm: 'URGNA2012', inputFormat: 'application/n-quads', format: 'application/n-quads' }) ] }, 'rdfn:Urdna2015EvalTest': { params: [ parseNQuads(readTestNQuads('action')), createTestOptions({ algorithm: 'URDNA2015', inputFormat: 'application/n-quads', format: 'application/n-quads' }) ] }, }; const SKIP_TESTS = []; // run tests const suite = new Benchmark.Suite; const namepath = []; const filename = joinPath(ROOT_MANIFEST_DIR, 'manifest.jsonld'); const rootManifest = readJson(filename); rootManifest.filename = filename; addManifest(rootManifest); suite .on('start', () => { console.log('Benchmarking...'); }) .on('cycle', event => { console.log(String(event.target)); /* const s = event.target.stats; console.log(` min:${Math.min(...s.sample)} max:${Math.max(...s.sample)}`); console.log(` deviation:${s.deviation} mean:${s.mean}`); console.log(` moe:${s.moe} rme:${s.rme}% sem:${s.sem} var:${s.variance}`); */ }) .on('complete', () => { console.log('Done.'); }) .run({async: true}); /** * Adds the tests for all entries in the given manifest. * * @param manifest the manifest. */ function addManifest(manifest) { namepath.push(manifest.name || manifest.label); // get entries and sequence (alias for entries) const entries = [].concat( getJsonLdValues(manifest, 'entries'), getJsonLdValues(manifest, 'sequence') ); const includes = getJsonLdValues(manifest, 'include'); // add includes to sequence as jsonld files for(let i = 0; i < includes.length; ++i) { entries.push(includes[i] + '.jsonld'); } // process entries for(let i = 0; i < entries.length; ++i) { const entry = readManifestEntry(manifest, entries[i]); if(isJsonLdType(entry, 'mf:Manifest')) { // entry is another manifest addManifest(entry); } else { // assume entry is a test addTest(manifest, entry); } } namepath.pop(); } // i null for random, i number for incremental hashes mode function _bench({description, params, minSamples}) { let options = { name: description, defer: true, fn: function(deferred) { const promise = canonize.canonize.apply(null, params); promise .catch(err => assert.ifError(err)) .then(() => deferred.resolve()); } }; if(minSamples) { options.minSamples = minSamples; } return options; } function addTest(manifest, test) { // skip unknown and explicitly skipped test types const testTypes = Object.keys(TEST_TYPES); const skip = 'skip' in test && test.skip === true; if(skip || !isJsonLdType(test, testTypes) || isJsonLdType(test, SKIP_TESTS)) { const type = [].concat( getJsonLdValues(test, '@type'), getJsonLdValues(test, 'type') ); console.log('Skipping test "' + test.name + '" of type: ' + type); return; } // if ONLY env var set then only run if only is true if(process.env.ONLY && !('only' in test && test.only === true)) { return; } // expand @id and input base const test_id = test['@id'] || test['id']; test['@id'] = manifest.baseIri + basename(manifest.filename) + test_id; test.base = manifest.baseIri + test.input; test.manifest = manifest; const description = test_id + ' ' + (test.purpose || test.name); const testInfo = TEST_TYPES[getTestType(test)]; const params = testInfo.params.map(param => param(test)); // custom params for js only async mode const jsParams = testInfo.params.map(param => param(test)); // custom params for native only async mode const nativeParams = testInfo.params.map(param => param(test)); nativeParams[1].useNative = true; // NOTE: the below omit error handling. run manifest with test suite first // number of parallel operations const N = 10; // run async js benchmark suite.add({ name: namepath.concat([description, '(asynchronous js)']).join(' / '), defer: true, fn: function(deferred) { canonize.canonize(...jsParams).then(() => deferred.resolve()); } }); // run async js benchmark x N suite.add({ name: namepath.concat( [description, `(asynchronous js x ${N})`]).join(' / '), defer: true, fn: function(deferred) { const all = []; for(let i = 0; i < N; ++i) { all.push(canonize.canonize(...jsParams)); } Promise.all(all).then(() => deferred.resolve()); } }); /* // run async js benchmark (callback) suite.add({ name: namepath.concat([description, '(asynchronous js / cb)']).join(' / '), defer: true, fn: function(deferred) { canonize.canonize(...jsParams, (err, output) => deferred.resolve()); } }); */ if(rdfCanonizeNative) { // run async native benchmark suite.add({ name: namepath.concat([description, '(asynchronous native)']).join(' / '), defer: true, fn: function(deferred) { canonize.canonize(...nativeParams).then(() => deferred.resolve()); } }); // run async native benchmark x N suite.add({ name: namepath.concat( [description, `(asynchronous native x ${N})`]).join(' / '), defer: true, fn: function(deferred) { const all = []; for(let i = 0; i < N; ++i) { all.push(canonize.canonize(...nativeParams)); } Promise.all(all).then(() => deferred.resolve()); } }); } // run sync js benchmark suite.add({ name: namepath.concat([description, '(synchronous js)']).join(' / '), defer: true, fn: function(deferred) { canonize.canonizeSync(...jsParams); deferred.resolve(); } }); // run sync js benchmark x N suite.add({ name: namepath.concat( [description, `(synchronous js x ${N})`]).join(' / '), defer: true, fn: function(deferred) { const all = []; for(let i = 0; i < N; ++i) { all.push(canonize.canonizeSync(...jsParams)); } Promise.all(all).then(() => deferred.resolve()); } }); if(rdfCanonizeNative) { // run sync native benchmark suite.add({ name: namepath.concat([description, '(synchronous native)']).join(' / '), defer: true, fn: function(deferred) { canonize.canonizeSync(...nativeParams); deferred.resolve(); } }); // run sync native benchmark x N suite.add({ name: namepath.concat( [description, `(synchronous native x ${N})`]).join(' / '), defer: true, fn: function(deferred) { const all = []; for(let i = 0; i < N; ++i) { all.push(canonize.canonizeSync(...nativeParams)); } Promise.all(all).then(() => deferred.resolve()); } }); } /* // run sync js benchmark (try/catch) suite.add({ name: namepath.concat([description, '(synchronous js / try/catch)']).join(' / '), defer: true, fn: function(deferred) { try { canonize.canonizeSync(...jsParams); } catch(e) {} deferred.resolve(); } }); // run sync js benchmark (non-deferred) suite.add({ name: namepath.concat([description, '(synchronous js nd)']).join(' / '), fn: function() { canonize.canonizeSync(...jsParams); } }); // run sync js benchmark (non-deferred try/catch) suite.add({ name: namepath.concat([description, '(synchronous js nd/tc)']).join(' / '), fn: function() { try { canonize.canonizeSync(...jsParams); } catch(e) {} } }); */ } function getTestType(test) { const types = Object.keys(TEST_TYPES); for(let i = 0; i < types.length; ++i) { if(isJsonLdType(test, types[i])) { return types[i]; } } return null; } function readManifestEntry(manifest, entry) { const dir = dirname(manifest.filename); if(typeof entry === 'string') { const filename = joinPath(dir, entry); entry = readJson(filename); entry.filename = filename; } entry.dirname = dirname(entry.filename || manifest.filename); return entry; } function readTestNQuads(property) { return test => { if(!test[property]) { return null; } const filename = joinPath(test.dirname, test[property]); return readFile(filename); }; } function parseNQuads(fn) { return test => NQuads.parse(fn(test)); } function createTestOptions(opts) { return test => { const testOptions = test.option || {}; const options = Object.assign({}, testOptions); if(opts) { // extend options Object.assign(options, opts); } return options; }; } // find the expected output property or throw error function _getExpectProperty(test) { if('expect' in test) { return 'expect'; } else if('result' in test) { return 'result'; } else { throw Error('No expected output property found'); } } function isJsonLdType(node, type) { const nodeType = [].concat( getJsonLdValues(node, '@type'), getJsonLdValues(node, 'type') ); type = Array.isArray(type) ? type : [type]; for(let i = 0; i < type.length; ++i) { if(nodeType.indexOf(type[i]) !== -1) { return true; } } return false; } function getJsonLdValues(node, property) { let rval = []; if(property in node) { rval = [].concat(node[property]); } return rval; } function readJson(filename) { return JSON.parse(readFile(filename)); } function pathExists(filename) { if(_nodejs) { return fs.existsSync(filename); } return fs.exists(filename); } function readFile(filename) { if(_nodejs) { return fs.readFileSync(filename, 'utf8'); } return fs.read(filename); } function resolvePath(to) { if(_nodejs) { return path.resolve(to); } return fs.absolute(to); } function joinPath() { return (_nodejs ? path : fs).join.apply( null, Array.prototype.slice.call(arguments)); } function dirname(filename) { if(_nodejs) { return path.dirname(filename); } const idx = filename.lastIndexOf(fs.separator); if(idx === -1) { return filename; } return filename.substr(0, idx); } function basename(filename) { if(_nodejs) { return path.basename(filename); } const idx = filename.lastIndexOf(fs.separator); if(idx === -1) { return filename; } return filename.substr(idx + 1); } })(); rdf-canonize-1.1.0/benchmark/block-1.json000066400000000000000000000064631361042651100201550ustar00rootroot00000000000000{ "@context":"https://w3id.org/webledger/v1", "id":"did:ledgertest:eb8c22dc-bde6-4315-92e2-59bd3f3c7d59/blocks/0", "type":"WebLedgerEventBlock", "consensusMethod":"Continuity2017", "event":[ { "@context":"https://w3id.org/webledger/v1", "type":"WebLedgerEvent", "operation":"Create", "input":[ { "@context":"https://w3id.org/test/v1", "id":"https://example.com/events/61a3fd6b-5f2e-41f0-8cb9-d93152caab6b", "type":"Concert", "name":"Big Band Concert in New York City", "startDate":"2017-07-14T21:30", "location":"https://example.org/the-venue", "offers":{ "type":"Offer", "price":"13.00", "priceCurrency":"USD", "url":"https://www.ticketfly.com/purchase/309433" } } ], "signature":{ "type":"LinkedDataSignature2015", "created":"2017-10-12T16:42:14Z", "creator":"https://bedrock.local:18443/consensus/continuity2017/voters/9676bdb5-84c3-4547-9ab6-7d6d1193e1c1#key", "signatureValue":"bOzKLViLLVs29TT7EyyPxzfDT4+lc2Cn+fGbk2v5ZAJhZfcNcrQRdSDIog2z+7nbHrNfvjoARGyrvyJ7l45WalK3jt9AaJrcfj2hXcVt+kBE5Xlo2D4kwGqalqglq7eKwwWXCFzCh0tRmikFdaC9PhyKE/3SM02/uuJpsWTSIjQMmPPYpa4o6UqOSOJF+L7WMRd5uVf22SnE02KwYGo9YQd19UjAii8zNHKPh1b/lZLj8xeNSEZX1vOjEACVSD+XY270oOJNK1QR82HhoYprw38NjdPMwrwgm2UW4ZZYN88oBCoa71iEOIZfRMHDORfHzLsjvDrFMQ9gy8KgHlpaOA==" } } ], "electionResult":[ { "@context":"https://w3id.org/webledger/v1", "blockHeight":0, "manifestHash":"ni:///sha-256;K8uZ8_ht78lQBIvNbci2UJBNshhFRq9v1toFjSlZPkI", "voteRound":1, "voter":"https://bedrock.local:18443/consensus/continuity2017/voters/aa9c36a4-c497-43c8-b004-3e74825b762d", "recommendedElector":[ { "id":"https://bedrock.local:18443/consensus/continuity2017/voters/aa9c36a4-c497-43c8-b004-3e74825b762d" } ], "signature":{ "type":"LinkedDataSignature2015", "created":"2017-10-12T18:36:22Z", "creator":"https://bedrock.local:18443/consensus/continuity2017/voters/aa9c36a4-c497-43c8-b004-3e74825b762d#key", "signatureValue":"QGLNG/ajgFpX0M3eaBbTgmOFdfXsqYLYYU9E+zcIgCW3GE8oBupnl9MFJh30gtesKt86pW/C73oyBXWMa4+yLnoXoLIfdRP6o8o0PFARBPrCq+dOF0gX3v7ZiwZXxWzYx5L4LmFImNMFRCaKInMUuVM6nyV0cO0kJLHA4dXrcEXQY5j6mU2fbzW/AAVSE249zOFTk/v5WPaTWWmkedFbq39RS+VIE+CdqxDGkEPeWyDd1nZhfBh93epUfzoS4R4kvdSxEhbw/YXy9+SRgRS9OuDLDNQ13EBJG25pv1Vz/EKXvceyMJW9grTqoLlG1OzJBAIUPmjbiR7D1NSGlziq2w==" } } ], "blockHeight":0, "publicKey":[ { "id":"https://bedrock.local:18443/consensus/continuity2017/voters/aa9c36a4-c497-43c8-b004-3e74825b762d#key", "type":"CryptographicKey", "owner":"https://bedrock.local:18443/consensus/continuity2017/voters/aa9c36a4-c497-43c8-b004-3e74825b762d", "publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2HZeJEBfiRkcH6ckcS0x\nRwWukyVCbel2A7vq9/rr0KyxrQ5rsiVqp9wl6kCRLvF8+9SjBBGPupZ9o/W+Auwl\nYreV0OqV2RjZkJDqejNZQp7H2iBGdQcQnxRcRMSx/4g5t6aA+US8CQ5g4mgPpSit\nolSq924sI40AEUM4FLRho2d7jkkW+jwBlREL27g9UMXMwC7AbuyTS/NqYjj1RqOa\ncYXO+aCSYmVQTtB445fdpfcP86n5meOFiKaCUGII81Sny7ItJ2/De6OhTmFlq2q1\nQoWPDrvjtr+Y7rnQVZ4g8yQl+qXmVYRkg5ul3tm8Zuie5Sqy56S9iwH0RXBOUkIY\nfwIDAQAB\n-----END PUBLIC KEY-----\n" } ] } rdf-canonize-1.1.0/benchmark/make-tests.js000066400000000000000000000031271361042651100204370ustar00rootroot00000000000000/** * Make tests for benchmarks. * * @author Dave Longley * @author David I. Lehn * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ /* eslint-disable indent */ (async () => { const fs = require('fs'); const jsonld = require('../../jsonld.js'); const b1json = fs.readFileSync('./block-1.json'); const b1 = JSON.parse(b1json); const loader = (url, callback) => { if(url === 'https://w3id.org/test/v1') { callback(null, { document: JSON.parse(fs.readFileSync('./test-v1.jsonld')) }); } if(url === 'https://w3id.org/webledger/v1') { return callback(null, { document: JSON.parse(fs.readFileSync('./webledger-v1.jsonld')) }); } return jsonld.loadDocument(url, callback); }; async function bn(b, n) { console.log(`Make block-${n}-{in,out}.nq`); const data = JSON.parse(JSON.stringify(b)); //console.log('nq0', data); const ev = data.event[0]; data.event = []; //console.log('nq1', data); for(let i = 0; i < n; ++i) { // copy and change id const newEv = JSON.parse(JSON.stringify(ev)); newEv.input[0].id = newEv.input[0].id + '-' + i; //console.log('push', newEv); data.event.push(newEv); } //console.log('nq2', data); const nq = await jsonld.toRDF(data, { format: 'application/n-quads', documentLoader: loader }); fs.writeFileSync(`./block-${n}-in.nq`, nq); const can = await jsonld.canonize(data, { documentLoader: loader }); fs.writeFileSync(`./block-${n}-out.nq`, can); } Promise.all([ bn(b1, 1), bn(b1, 2), bn(b1, 10), bn(b1, 100), bn(b1, 1000) ]).catch(e => console.error(e)); })(); rdf-canonize-1.1.0/benchmark/manifest.jsonld000066400000000000000000000045241361042651100210470ustar00rootroot00000000000000{ "@context": { "xsd": "http://www.w3.org/2001/XMLSchema#", "rdfs": "http://www.w3.org/2000/01/rdf-schema#", "mf": "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#", "mq": "http://www.w3.org/2001/sw/DataAccess/tests/test-query#", "rdfn": "http://json-ld.github.io/normalization/test-vocab#", "rdft": "http://www.w3.org/ns/rdftest#", "id": "@id", "type": "@type", "action": { "@id": "mf:action", "@type": "@id" }, "approval": { "@id": "rdft:approval", "@type": "@id" }, "comment": "rdfs:comment", "entries": { "@id": "mf:entries", "@type": "@id", "@container": "@list" }, "label": "rdfs:label", "name": "mf:name", "result": { "@id": "mf:result", "@type": "@id" } }, "id": "", "type": "mf:Manifest", "label": "RDF Dataset Normalization", "comment": "RDF Dataset Normalization benchmarks.", "baseIri": "https://json-ld.github.io/normalization/tests/#", "entries": [ { "skip": false, "only": false, "id": "#block-1", "type": "rdfn:Urdna2015EvalTest", "name": "block-1", "comment": null, "approval": "rdft:Proposed", "action": "block-1-in.nq", "result": "block-1-out.nq" }, { "skip": false, "only": false, "id": "#block-2", "type": "rdfn:Urdna2015EvalTest", "name": "block-2", "comment": null, "approval": "rdft:Proposed", "action": "block-2-in.nq", "result": "block-2-out.nq" }, { "skip": false, "only": false, "id": "#block-10", "type": "rdfn:Urdna2015EvalTest", "name": "block-10", "comment": null, "approval": "rdft:Proposed", "action": "block-10-in.nq", "result": "block-10-out.nq" }, { "skip": false, "only": false, "id": "#block-100", "type": "rdfn:Urdna2015EvalTest", "name": "block-100", "comment": null, "approval": "rdft:Proposed", "action": "block-100-in.nq", "result": "block-100-out.nq" }, { "skip": false, "only": false, "id": "#block-1000", "type": "rdfn:Urdna2015EvalTest", "name": "block-1000", "comment": null, "approval": "rdft:Proposed", "action": "block-1000-in.nq", "result": "block-1000-out.nq" } ] } rdf-canonize-1.1.0/benchmark/test-v1.jsonld000066400000000000000000000013331361042651100205370ustar00rootroot00000000000000{ "@context": { "id": "@id", "type": "@type", "cred": "https://w3id.org/credentials#", "dhs": "https://w3id.org/dhs#", "schema": "http://schema.org/", "@vocab": "https://w3id.org/dhs#", "Concert": "schema:Concert", "Offer": "schema:Offer", "Credential": "cred:Credential", "EmergencyResponseCredential": "dhs:EmergencyResponseCredential", "claim": {"@id": "cred:claim", "@type": "@id"}, "emsLicense": {"@id": "dhs:emsLicense", "@type": "@id"}, "location": "schema:location", "name": "schema:name", "offers": "schema:offers", "price": "schema:price", "priceCurrency": "schema:priceCurrency", "startDate": "schema:startDate", "url": "schema:url" } } rdf-canonize-1.1.0/benchmark/webledger-v1.jsonld000066400000000000000000000100721361042651100215200ustar00rootroot00000000000000{ "@context": { "@version": 1.1, "id": "@id", "type": "@type", "dc": "http://purl.org/dc/terms/", "identity": "https://w3id.org/identity#", "rdfs": "http://www.w3.org/2000/01/rdf-schema#", "schema": "http://schema.org/", "sec": "https://w3id.org/security#", "wl": "https://w3id.org/wl#", "xsd": "http://www.w3.org/2001/XMLSchema#", "Config": "wl:Config", "Continuity2017": "wl:Continuity2017", "Continuity2017Peer": "wl:Continuity2017Peer", "Create": "wl:Create", "CryptographicKey": "sec:Key", "EquihashProof2017": "sec:EquihashProof2017", "GraphSignature2012": "sec:GraphSignature2012", "Identity": "identity:Identity", "LinkedDataSignature2015": "sec:LinkedDataSignature2015", "LinkedDataSignature2016": "sec:LinkedDataSignature2016", "ProofOfSignature2017": "wl:ProofOfSignature2017", "ProofOfWork2016": "wl:ProofOfWork2016", "SequentialList": "wl:SequentialList", "UnilateralConsensus2017": "wl:UnilateralConsensus2017", "WebLedgerConfiguration": "wl:WebLedgerConfiguration", "WebLedgerConfigurationEvent": "wl:WebLedgerConfigurationEvent", "WebLedgerEvent": "wl:WebLedgerEvent", "WebLedgerEventBlock": "wl:WebLedgerEventBlock", "approvedSigner": "wl:approvedSigner", "authenticationCredential": {"@id": "sec:authenticationCredential", "@type": "@id", "@container": "@set"}, "blockHeight": "wl:blockHeight", "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm", "comment": "rdfs:comment", "consensusMethod": {"@id": "wl:consensusMethod", "@type": "@vocab"}, "consensusPhase": "wl:consensusPhase", "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, "creator": {"@id": "dc:creator", "@type": "@id"}, "description": "schema:description", "digestAlgorithm": "sec:digestAlgorithm", "digestValue": "sec:digestValue", "domain": "sec:domain", "election": {"@id": "wl:election", "@container": "@set"}, "electionResult": {"@id": "wl:electionResults", "@container": "@set"}, "electorCount": {"@id": "wl:electorCount", "@type": "xsd:integer"}, "equihashParameterK": {"@id": "sec:equihashParameterK", "@type": "xsd:integer"}, "equihashParameterN": {"@id": "sec:equihashParameterN", "@type": "xsd:integer"}, "event": {"@id": "wl:event", "@type": "@id", "@container": ["@graph", "@set"]}, "eventFilter": {"@id": "wl:eventFilter", "@type": "@id"}, "eventHash": "wl:eventHash", "eventType": "wl:eventType", "eventValidator": {"@id": "wl:eventValidator", "@type": "@id"}, "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, "input": {"@id": "wl:input", "@type": "@id", "@container": ["@graph", "@set"]}, "label": "rdfs:label", "ledger": {"@id": "wl:ledger", "@type": "@id"}, "ledgerConfiguration": {"@id": "wl:ledgerConfiguration", "@type": "@id"}, "manifestHash": "wl:manifestHash", "minimumSignaturesRequired": "wl:minimumSignaturesRequired", "name": "schema:name", "nonce": "sec:nonce", "normalizationAlgorithm": "sec:normalizationAlgorithm", "operation": "wl:operation", "owner": {"@id": "sec:owner", "@type": "@id"}, "previousBlock": "wl:previousBlock", "previousBlockHash": "wl:previousBlockHash", "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, "privateKeyPem": "sec:privateKeyPem", "proofAlgorithm": "sec:proofAlgorithm", "proofValue": "sec:proofValue", "publicKey": {"@id": "sec:publicKey", "@type": "@id", "@container": "@set"}, "publicKeyPem": "sec:publicKeyPem", "recommendedElector": {"@id": "wl:recommendedElectors", "@container": "@set"}, "requireEventValidation": "wl:requireEventValidation", "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, "seeAlso": {"@id": "rdfs:seeAlso", "@type": "@id"}, "signature": "sec:signature", "signatureAlgorithm": "sec:signatureAlgorithm", "signatureValue": "sec:signatureValue", "supportedEventType": "wl:supportedEventType", "voteRound": {"@id": "wl:voteRound", "@type": "xsd:integer"}, "voter": {"@id": "wl:voteRound", "@type": "@id"} } } rdf-canonize-1.1.0/index.js000066400000000000000000000004551361042651100155400ustar00rootroot00000000000000/** * An implementation of the RDF Dataset Normalization specification. * * @author Dave Longley * * Copyright 2010-2017 Digital Bazaar, Inc. */ if(require('semver').gte(process.version, '8.0.0')) { module.exports = require('./lib'); } else { module.exports = require('./dist/node6/lib'); } rdf-canonize-1.1.0/lib/000077500000000000000000000000001361042651100146355ustar00rootroot00000000000000rdf-canonize-1.1.0/lib/AsyncAlgorithm.js000066400000000000000000000061401361042651100201200ustar00rootroot00000000000000/** * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const util = require('./util'); module.exports = class AsyncAlgorithm { constructor({ maxCallStackDepth = 500, maxTotalCallStackDepth = 0xFFFFFFFF, // milliseconds timeSlice = 10 } = {}) { this.schedule = {}; this.schedule.MAX_DEPTH = maxCallStackDepth; this.schedule.MAX_TOTAL_DEPTH = maxTotalCallStackDepth; this.schedule.depth = 0; this.schedule.totalDepth = 0; this.schedule.timeSlice = timeSlice; } // do some work in a time slice, but in serial doWork(fn, callback) { const schedule = this.schedule; if(schedule.totalDepth >= schedule.MAX_TOTAL_DEPTH) { return callback(new Error( 'Maximum total call stack depth exceeded; canonicalization aborting.')); } (function work() { if(schedule.depth === schedule.MAX_DEPTH) { // stack too deep, run on next tick schedule.depth = 0; schedule.running = false; return util.nextTick(work); } // if not yet running, force run const now = Date.now(); if(!schedule.running) { schedule.start = Date.now(); schedule.deadline = schedule.start + schedule.timeSlice; } // TODO: should also include an estimate of expectedWorkTime if(now < schedule.deadline) { schedule.running = true; schedule.depth++; schedule.totalDepth++; return fn((err, result) => { schedule.depth--; schedule.totalDepth--; callback(err, result); }); } // not enough time left in this slice, run after letting browser // do some other things schedule.depth = 0; schedule.running = false; util.setImmediate(work); })(); } // asynchronously loop forEach(iterable, fn, callback) { const self = this; let iterator; let idx = 0; let length; if(Array.isArray(iterable)) { length = iterable.length; iterator = () => { if(idx === length) { return false; } iterator.value = iterable[idx++]; iterator.key = idx; return true; }; } else { const keys = Object.keys(iterable); length = keys.length; iterator = () => { if(idx === length) { return false; } iterator.key = keys[idx++]; iterator.value = iterable[iterator.key]; return true; }; } (function iterate(err) { if(err) { return callback(err); } if(iterator()) { return self.doWork(() => fn(iterator.value, iterator.key, iterate)); } callback(); })(); } // asynchronous waterfall waterfall(fns, callback) { const self = this; self.forEach( fns, (fn, idx, callback) => self.doWork(fn, callback), callback); } // asynchronous while whilst(condition, fn, callback) { const self = this; (function loop(err) { if(err) { return callback(err); } if(!condition()) { return callback(); } self.doWork(fn, loop); })(); } }; rdf-canonize-1.1.0/lib/IdentifierIssuer.js000066400000000000000000000031771361042651100204600ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const util = require('./util'); module.exports = class IdentifierIssuer { /** * Creates a new IdentifierIssuer. A IdentifierIssuer issues unique * identifiers, keeping track of any previously issued identifiers. * * @param prefix the prefix to use (''). */ constructor(prefix) { this.prefix = prefix; this.counter = 0; this.existing = {}; } /** * Copies this IdentifierIssuer. * * @return a copy of this IdentifierIssuer. */ clone() { const copy = new IdentifierIssuer(this.prefix); copy.counter = this.counter; copy.existing = util.clone(this.existing); return copy; } /** * Gets the new identifier for the given old identifier, where if no old * identifier is given a new identifier will be generated. * * @param [old] the old identifier to get the new identifier for. * * @return the new identifier. */ getId(old) { // return existing old identifier if(old && old in this.existing) { return this.existing[old]; } // get next identifier const identifier = this.prefix + this.counter; this.counter += 1; // save mapping if(old) { this.existing[old] = identifier; } return identifier; } /** * Returns true if the given old identifer has already been assigned a new * identifier. * * @param old the old identifier to check. * * @return true if the old identifier has been assigned a new identifier, * false if not. */ hasId(old) { return (old in this.existing); } }; rdf-canonize-1.1.0/lib/MessageDigest-browser.js000066400000000000000000000010541361042651100214000ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const forge = require('node-forge/lib/forge'); require('node-forge/lib/md'); require('node-forge/lib/sha1'); require('node-forge/lib/sha256'); module.exports = class MessageDigest { /** * Creates a new MessageDigest. * * @param algorithm the algorithm to use. */ constructor(algorithm) { this.md = forge.md[algorithm].create(); } update(msg) { this.md.update(msg, 'utf8'); } digest() { return this.md.digest().toHex(); } }; rdf-canonize-1.1.0/lib/MessageDigest.js000066400000000000000000000006741361042651100177260ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const crypto = require('crypto'); module.exports = class MessageDigest { /** * Creates a new MessageDigest. * * @param algorithm the algorithm to use. */ constructor(algorithm) { this.md = crypto.createHash(algorithm); } update(msg) { this.md.update(msg, 'utf8'); } digest() { return this.md.digest('hex'); } }; rdf-canonize-1.1.0/lib/NQuads.js000066400000000000000000000233601361042651100163720ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const TERMS = ['subject', 'predicate', 'object', 'graph']; const RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; const RDF_LANGSTRING = RDF + 'langString'; const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string'; // build regexes const REGEX = {}; (() => { const iri = '(?:<([^:]+:[^>]*)>)'; // https://www.w3.org/TR/turtle/#grammar-production-BLANK_NODE_LABEL const PN_CHARS_BASE = 'A-Z' + 'a-z' + '\u00C0-\u00D6' + '\u00D8-\u00F6' + '\u00F8-\u02FF' + '\u0370-\u037D' + '\u037F-\u1FFF' + '\u200C-\u200D' + '\u2070-\u218F' + '\u2C00-\u2FEF' + '\u3001-\uD7FF' + '\uF900-\uFDCF' + '\uFDF0-\uFFFD'; // TODO: //'\u10000-\uEFFFF'; const PN_CHARS_U = PN_CHARS_BASE + '_'; const PN_CHARS = PN_CHARS_U + '0-9' + '-' + '\u00B7' + '\u0300-\u036F' + '\u203F-\u2040'; const BLANK_NODE_LABEL = '(_:' + '(?:[' + PN_CHARS_U + '0-9])' + '(?:(?:[' + PN_CHARS + '.])*(?:[' + PN_CHARS + ']))?' + ')'; const bnode = BLANK_NODE_LABEL; const plain = '"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"'; const datatype = '(?:\\^\\^' + iri + ')'; const language = '(?:@([a-zA-Z]+(?:-[a-zA-Z0-9]+)*))'; const literal = '(?:' + plain + '(?:' + datatype + '|' + language + ')?)'; const ws = '[ \\t]+'; const wso = '[ \\t]*'; // define quad part regexes const subject = '(?:' + iri + '|' + bnode + ')' + ws; const property = iri + ws; const object = '(?:' + iri + '|' + bnode + '|' + literal + ')' + wso; const graphName = '(?:\\.|(?:(?:' + iri + '|' + bnode + ')' + wso + '\\.))'; // end of line and empty regexes REGEX.eoln = /(?:\r\n)|(?:\n)|(?:\r)/g; REGEX.empty = new RegExp('^' + wso + '$'); // full quad regex REGEX.quad = new RegExp( '^' + wso + subject + property + object + graphName + wso + '$'); })(); module.exports = class NQuads { /** * Parses RDF in the form of N-Quads. * * @param input the N-Quads input to parse. * * @return an RDF dataset (an array of quads per http://rdf.js.org/). */ static parse(input) { // build RDF dataset const dataset = []; const graphs = {}; // split N-Quad input into lines const lines = input.split(REGEX.eoln); let lineNumber = 0; for(const line of lines) { lineNumber++; // skip empty lines if(REGEX.empty.test(line)) { continue; } // parse quad const match = line.match(REGEX.quad); if(match === null) { throw new Error('N-Quads parse error on line ' + lineNumber + '.'); } // create RDF quad const quad = {}; // get subject if(match[1] !== undefined) { quad.subject = {termType: 'NamedNode', value: match[1]}; } else { quad.subject = {termType: 'BlankNode', value: match[2]}; } // get predicate quad.predicate = {termType: 'NamedNode', value: match[3]}; // get object if(match[4] !== undefined) { quad.object = {termType: 'NamedNode', value: match[4]}; } else if(match[5] !== undefined) { quad.object = {termType: 'BlankNode', value: match[5]}; } else { quad.object = { termType: 'Literal', value: undefined, datatype: { termType: 'NamedNode' } }; if(match[7] !== undefined) { quad.object.datatype.value = match[7]; } else if(match[8] !== undefined) { quad.object.datatype.value = RDF_LANGSTRING; quad.object.language = match[8]; } else { quad.object.datatype.value = XSD_STRING; } quad.object.value = _unescape(match[6]); } // get graph if(match[9] !== undefined) { quad.graph = { termType: 'NamedNode', value: match[9] }; } else if(match[10] !== undefined) { quad.graph = { termType: 'BlankNode', value: match[10] }; } else { quad.graph = { termType: 'DefaultGraph', value: '' }; } // only add quad if it is unique in its graph if(!(quad.graph.value in graphs)) { graphs[quad.graph.value] = [quad]; dataset.push(quad); } else { let unique = true; const quads = graphs[quad.graph.value]; for(const q of quads) { if(_compareTriples(q, quad)) { unique = false; break; } } if(unique) { quads.push(quad); dataset.push(quad); } } } return dataset; } /** * Converts an RDF dataset to N-Quads. * * @param dataset (array of quads) the RDF dataset to convert. * * @return the N-Quads string. */ static serialize(dataset) { if(!Array.isArray(dataset)) { dataset = NQuads.legacyDatasetToQuads(dataset); } const quads = []; for(const quad of dataset) { quads.push(NQuads.serializeQuad(quad)); } return quads.sort().join(''); } /** * Converts an RDF quad to an N-Quad string (a single quad). * * @param quad the RDF quad convert. * * @return the N-Quad string. */ static serializeQuad(quad) { const s = quad.subject; const p = quad.predicate; const o = quad.object; const g = quad.graph; let nquad = ''; // subject and predicate can only be NamedNode or BlankNode [s, p].forEach(term => { if(term.termType === 'NamedNode') { nquad += '<' + term.value + '>'; } else { nquad += term.value; } nquad += ' '; }); // object is NamedNode, BlankNode, or Literal if(o.termType === 'NamedNode') { nquad += '<' + o.value + '>'; } else if(o.termType === 'BlankNode') { nquad += o.value; } else { nquad += '"' + _escape(o.value) + '"'; if(o.datatype.value === RDF_LANGSTRING) { if(o.language) { nquad += '@' + o.language; } } else if(o.datatype.value !== XSD_STRING) { nquad += '^^<' + o.datatype.value + '>'; } } // graph can only be NamedNode or BlankNode (or DefaultGraph, but that // does not add to `nquad`) if(g.termType === 'NamedNode') { nquad += ' <' + g.value + '>'; } else if(g.termType === 'BlankNode') { nquad += ' ' + g.value; } nquad += ' .\n'; return nquad; } /** * Converts a legacy-formatted dataset to an array of quads dataset per * http://rdf.js.org/. * * @param dataset the legacy dataset to convert. * * @return the array of quads dataset. */ static legacyDatasetToQuads(dataset) { const quads = []; const termTypeMap = { 'blank node': 'BlankNode', 'IRI': 'NamedNode', 'literal': 'Literal' }; for(const graphName in dataset) { const triples = dataset[graphName]; triples.forEach(triple => { const quad = {}; for(const componentName in triple) { const oldComponent = triple[componentName]; const newComponent = { termType: termTypeMap[oldComponent.type], value: oldComponent.value }; if(newComponent.termType === 'Literal') { newComponent.datatype = { termType: 'NamedNode' }; if('datatype' in oldComponent) { newComponent.datatype.value = oldComponent.datatype; } if('language' in oldComponent) { if(!('datatype' in oldComponent)) { newComponent.datatype.value = RDF_LANGSTRING; } newComponent.language = oldComponent.language; } else if(!('datatype' in oldComponent)) { newComponent.datatype.value = XSD_STRING; } } quad[componentName] = newComponent; } if(graphName === '@default') { quad.graph = { termType: 'DefaultGraph', value: '' }; } else { quad.graph = { termType: graphName.startsWith('_:') ? 'BlankNode' : 'NamedNode', value: graphName }; } quads.push(quad); }); } return quads; } }; /** * Compares two RDF triples for equality. * * @param t1 the first triple. * @param t2 the second triple. * * @return true if the triples are the same, false if not. */ function _compareTriples(t1, t2) { for(const k in t1) { if(t1[k].termType !== t2[k].termType || t1[k].value !== t2[k].value) { return false; } } if(t1.object.termType !== 'Literal') { return true; } return ( (t1.object.datatype.termType === t2.object.datatype.termType) && (t1.object.datatype.value === t2.object.datatype.value) && (t1.object.language === t2.object.language) ); } const _escapeRegex = /["\\\n\r]/g; /** * Escape string to N-Quads literal */ function _escape(s) { return s.replace(_escapeRegex, function(match) { switch(match) { case '"': return '\\"'; case '\\': return '\\\\'; case '\n': return '\\n'; case '\r': return '\\r'; } }); } const _unescapeRegex = /(?:\\([tbnrf"'\\]))|(?:\\u([0-9A-Fa-f]{4}))|(?:\\U([0-9A-Fa-f]{8}))/g; /** * Unescape N-Quads literal to string */ function _unescape(s) { return s.replace(_unescapeRegex, function(match, code, u, U) { if(code) { switch(code) { case 't': return '\t'; case 'b': return '\b'; case 'n': return '\n'; case 'r': return '\r'; case 'f': return '\f'; case '"': return '"'; case '\'': return '\''; case '\\': return '\\'; } } if(u) { return String.fromCharCode(parseInt(u, 16)); } if(U) { // FIXME: support larger values throw new Error('Unsupported U escape'); } }); } rdf-canonize-1.1.0/lib/Permutator.js000066400000000000000000000041421361042651100173360ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; // TODO: convert to ES6 iterable module.exports = class Permutator { /** * A Permutator iterates over all possible permutations of the given array * of elements. * * @param list the array of elements to iterate over. */ constructor(list) { // original array this.list = list.sort(); // indicates whether there are more permutations this.done = false; // directional info for permutation algorithm this.left = {}; for(let i = 0; i < list.length; ++i) { this.left[list[i]] = true; } } /** * Returns true if there is another permutation. * * @return true if there is another permutation, false if not. */ hasNext() { return !this.done; } /** * Gets the next permutation. Call hasNext() to ensure there is another one * first. * * @return the next permutation. */ next() { // copy current permutation const rval = this.list.slice(); /* Calculate the next permutation using the Steinhaus-Johnson-Trotter permutation algorithm. */ // get largest mobile element k // (mobile: element is greater than the one it is looking at) let k = null; let pos = 0; const length = this.list.length; for(let i = 0; i < length; ++i) { const element = this.list[i]; const left = this.left[element]; if((k === null || element > k) && ((left && i > 0 && element > this.list[i - 1]) || (!left && i < (length - 1) && element > this.list[i + 1]))) { k = element; pos = i; } } // no more permutations if(k === null) { this.done = true; } else { // swap k and the element it is looking at const swap = this.left[k] ? pos - 1 : pos + 1; this.list[pos] = this.list[swap]; this.list[swap] = k; // reverse the direction of all elements larger than k for(let i = 0; i < length; ++i) { if(this.list[i] > k) { this.left[this.list[i]] = !this.left[this.list[i]]; } } } return rval; } }; rdf-canonize-1.1.0/lib/URDNA2015.js000066400000000000000000000527051361042651100163650ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const AsyncAlgorithm = require('./AsyncAlgorithm'); const IdentifierIssuer = require('./IdentifierIssuer'); const MessageDigest = require('./MessageDigest'); const Permutator = require('./Permutator'); const NQuads = require('./NQuads'); const util = require('./util'); const POSITIONS = {subject: 's', object: 'o', graph: 'g'}; module.exports = class URDNA2015 extends AsyncAlgorithm { constructor(options) { options = options || {}; super(options); this.name = 'URDNA2015'; this.options = Object.assign({}, options); this.blankNodeInfo = {}; this.hashToBlankNodes = {}; this.canonicalIssuer = new IdentifierIssuer('_:c14n'); this.hashAlgorithm = 'sha256'; this.quads; } // 4.4) Normalization Algorithm main(dataset, callback) { const self = this; self.schedule.start = Date.now(); let result; self.quads = dataset; // 1) Create the normalization state. // Note: Optimize by generating non-normalized blank node map concurrently. const nonNormalized = {}; self.waterfall([ callback => { // 2) For every quad in input dataset: self.forEach(dataset, (quad, idx, callback) => { // 2.1) For each blank node that occurs in the quad, add a reference // to the quad using the blank node identifier in the blank node to // quads map, creating a new entry if necessary. self.forEachComponent(quad, component => { if(component.termType !== 'BlankNode') { return; } const id = component.value; if(id in self.blankNodeInfo) { self.blankNodeInfo[id].quads.push(quad); } else { nonNormalized[id] = true; self.blankNodeInfo[id] = {quads: [quad]}; } }); callback(); }, callback); }, callback => { // 3) Create a list of non-normalized blank node identifiers // non-normalized identifiers and populate it using the keys from the // blank node to quads map. // Note: We use a map here and it was generated during step 2. // 4) Initialize simple, a boolean flag, to true. let simple = true; // 5) While simple is true, issue canonical identifiers for blank nodes: self.whilst(() => simple, callback => { // 5.1) Set simple to false. simple = false; // 5.2) Clear hash to blank nodes map. self.hashToBlankNodes = {}; self.waterfall([ callback => { // 5.3) For each blank node identifier identifier in // non-normalized identifiers: self.forEach(nonNormalized, (value, id, callback) => { // 5.3.1) Create a hash, hash, according to the Hash First // Degree Quads algorithm. self.hashFirstDegreeQuads(id, (err, hash) => { if(err) { return callback(err); } // 5.3.2) Add hash and identifier to hash to blank nodes map, // creating a new entry if necessary. if(hash in self.hashToBlankNodes) { self.hashToBlankNodes[hash].push(id); } else { self.hashToBlankNodes[hash] = [id]; } callback(); }); }, callback); }, callback => { // 5.4) For each hash to identifier list mapping in hash to blank // nodes map, lexicographically-sorted by hash: const hashes = Object.keys(self.hashToBlankNodes).sort(); self.forEach(hashes, (hash, i, callback) => { // 5.4.1) If the length of identifier list is greater than 1, // continue to the next mapping. const idList = self.hashToBlankNodes[hash]; if(idList.length > 1) { return callback(); } // 5.4.2) Use the Issue Identifier algorithm, passing canonical // issuer and the single blank node identifier in identifier // list, identifier, to issue a canonical replacement identifier // for identifier. // TODO: consider changing `getId` to `issue` const id = idList[0]; self.canonicalIssuer.getId(id); // 5.4.3) Remove identifier from non-normalized identifiers. delete nonNormalized[id]; // 5.4.4) Remove hash from the hash to blank nodes map. delete self.hashToBlankNodes[hash]; // 5.4.5) Set simple to true. simple = true; callback(); }, callback); } ], callback); }, callback); }, callback => { // 6) For each hash to identifier list mapping in hash to blank nodes // map, lexicographically-sorted by hash: const hashes = Object.keys(self.hashToBlankNodes).sort(); self.forEach(hashes, (hash, idx, callback) => { // 6.1) Create hash path list where each item will be a result of // running the Hash N-Degree Quads algorithm. const hashPathList = []; // 6.2) For each blank node identifier identifier in identifier list: const idList = self.hashToBlankNodes[hash]; self.waterfall([ callback => { self.forEach(idList, (id, idx, callback) => { // 6.2.1) If a canonical identifier has already been issued for // identifier, continue to the next identifier. if(self.canonicalIssuer.hasId(id)) { return callback(); } // 6.2.2) Create temporary issuer, an identifier issuer // initialized with the prefix _:b. const issuer = new IdentifierIssuer('_:b'); // 6.2.3) Use the Issue Identifier algorithm, passing temporary // issuer and identifier, to issue a new temporary blank node // identifier for identifier. issuer.getId(id); // 6.2.4) Run the Hash N-Degree Quads algorithm, passing // temporary issuer, and append the result to the hash path // list. self.hashNDegreeQuads(id, issuer, (err, result) => { if(err) { return callback(err); } hashPathList.push(result); callback(); }); }, callback); }, callback => { // 6.3) For each result in the hash path list, // lexicographically-sorted by the hash in result: // TODO: use `String.localeCompare`? hashPathList.sort((a, b) => (a.hash < b.hash) ? -1 : ((a.hash > b.hash) ? 1 : 0)); self.forEach(hashPathList, (result, idx, callback) => { // 6.3.1) For each blank node identifier, existing identifier, // that was issued a temporary identifier by identifier issuer // in result, issue a canonical identifier, in the same order, // using the Issue Identifier algorithm, passing canonical // issuer and existing identifier. for(const existing in result.issuer.existing) { self.canonicalIssuer.getId(existing); } callback(); }, callback); } ], callback); }, callback); }, callback => { /* Note: At this point all blank nodes in the set of RDF quads have been assigned canonical identifiers, which have been stored in the canonical issuer. Here each quad is updated by assigning each of its blank nodes its new identifier. */ // 7) For each quad, quad, in input dataset: const normalized = []; self.waterfall([ callback => { self.forEach(self.quads, (quad, idx, callback) => { // 7.1) Create a copy, quad copy, of quad and replace any existing // blank node identifiers using the canonical identifiers // previously issued by canonical issuer. // Note: We optimize away the copy here. self.forEachComponent(quad, component => { if(component.termType === 'BlankNode' && !component.value.startsWith(self.canonicalIssuer.prefix)) { component.value = self.canonicalIssuer.getId(component.value); } }); // 7.2) Add quad copy to the normalized dataset. normalized.push(NQuads.serializeQuad(quad)); callback(); }, callback); }, callback => { // sort normalized output normalized.sort(); // 8) Return the normalized dataset. result = normalized.join(''); return callback(); } ], callback); } ], err => callback(err, result)); } // 4.6) Hash First Degree Quads hashFirstDegreeQuads(id, callback) { const self = this; // return cached hash const info = self.blankNodeInfo[id]; if('hash' in info) { return callback(null, info.hash); } // 1) Initialize nquads to an empty list. It will be used to store quads in // N-Quads format. const nquads = []; // 2) Get the list of quads quads associated with the reference blank node // identifier in the blank node to quads map. const quads = info.quads; // 3) For each quad quad in quads: self.forEach(quads, (quad, idx, callback) => { // 3.1) Serialize the quad in N-Quads format with the following special // rule: // 3.1.1) If any component in quad is an blank node, then serialize it // using a special identifier as follows: const copy = {predicate: quad.predicate}; self.forEachComponent(quad, (component, key) => { // 3.1.2) If the blank node's existing blank node identifier matches the // reference blank node identifier then use the blank node identifier // _:a, otherwise, use the blank node identifier _:z. copy[key] = self.modifyFirstDegreeComponent(id, component, key); }); nquads.push(NQuads.serializeQuad(copy)); callback(); }, err => { if(err) { return callback(err); } // 4) Sort nquads in lexicographical order. nquads.sort(); // 5) Return the hash that results from passing the sorted, joined nquads // through the hash algorithm. const md = new MessageDigest(self.hashAlgorithm); for(let i = 0; i < nquads.length; ++i) { md.update(nquads[i]); } // TODO: represent as byte buffer instead to cut memory usage in half info.hash = md.digest(); callback(null, info.hash); }); } // 4.7) Hash Related Blank Node hashRelatedBlankNode(related, quad, issuer, position, callback) { const self = this; // 1) Set the identifier to use for related, preferring first the canonical // identifier for related if issued, second the identifier issued by issuer // if issued, and last, if necessary, the result of the Hash First Degree // Quads algorithm, passing related. let id; self.waterfall([ callback => { if(self.canonicalIssuer.hasId(related)) { id = self.canonicalIssuer.getId(related); return callback(); } if(issuer.hasId(related)) { id = issuer.getId(related); return callback(); } self.hashFirstDegreeQuads(related, (err, hash) => { if(err) { return callback(err); } id = hash; callback(); }); } ], err => { if(err) { return callback(err); } // 2) Initialize a string input to the value of position. // Note: We use a hash object instead. const md = new MessageDigest(self.hashAlgorithm); md.update(position); // 3) If position is not g, append <, the value of the predicate in quad, // and > to input. if(position !== 'g') { md.update(self.getRelatedPredicate(quad)); } // 4) Append identifier to input. md.update(id); // 5) Return the hash that results from passing input through the hash // algorithm. // TODO: represent as byte buffer instead to cut memory usage in half return callback(null, md.digest()); }); } // 4.8) Hash N-Degree Quads hashNDegreeQuads(id, issuer, callback) { const self = this; // 1) Create a hash to related blank nodes map for storing hashes that // identify related blank nodes. // Note: 2) and 3) handled within `createHashToRelated` let hashToRelated; const md = new MessageDigest(self.hashAlgorithm); self.waterfall([ callback => self.createHashToRelated(id, issuer, (err, result) => { if(err) { return callback(err); } hashToRelated = result; callback(); }), callback => { // 4) Create an empty string, data to hash. // Note: We created a hash object `md` above instead. // 5) For each related hash to blank node list mapping in hash to // related blank nodes map, sorted lexicographically by related hash: const hashes = Object.keys(hashToRelated).sort(); self.forEach(hashes, (hash, idx, callback) => { // 5.1) Append the related hash to the data to hash. md.update(hash); // 5.2) Create a string chosen path. let chosenPath = ''; // 5.3) Create an unset chosen issuer variable. let chosenIssuer; // 5.4) For each permutation of blank node list: const permutator = new Permutator(hashToRelated[hash]); self.whilst(() => permutator.hasNext(), nextPermutation => { const permutation = permutator.next(); // 5.4.1) Create a copy of issuer, issuer copy. let issuerCopy = issuer.clone(); // 5.4.2) Create a string path. let path = ''; // 5.4.3) Create a recursion list, to store blank node identifiers // that must be recursively processed by this algorithm. const recursionList = []; self.waterfall([ callback => { // 5.4.4) For each related in permutation: self.forEach(permutation, (related, idx, callback) => { // 5.4.4.1) If a canonical identifier has been issued for // related, append it to path. if(self.canonicalIssuer.hasId(related)) { path += self.canonicalIssuer.getId(related); } else { // 5.4.4.2) Otherwise: // 5.4.4.2.1) If issuer copy has not issued an identifier // for related, append related to recursion list. if(!issuerCopy.hasId(related)) { recursionList.push(related); } // 5.4.4.2.2) Use the Issue Identifier algorithm, passing // issuer copy and related and append the result to path. path += issuerCopy.getId(related); } // 5.4.4.3) If chosen path is not empty and the length of path // is greater than or equal to the length of chosen path and // path is lexicographically greater than chosen path, then // skip to the next permutation. // Note: Comparing path length to chosen path length can be // optimized away; only compare lexicographically. if(chosenPath.length !== 0 && path > chosenPath) { // FIXME: may cause inaccurate total depth calculation return nextPermutation(); } callback(); }, callback); }, callback => { // 5.4.5) For each related in recursion list: self.forEach(recursionList, (related, idx, callback) => { // 5.4.5.1) Set result to the result of recursively executing // the Hash N-Degree Quads algorithm, passing related for // identifier and issuer copy for path identifier issuer. self.hashNDegreeQuads(related, issuerCopy, (err, result) => { if(err) { return callback(err); } // 5.4.5.2) Use the Issue Identifier algorithm, passing // issuer copy and related and append the result to path. path += issuerCopy.getId(related); // 5.4.5.3) Append <, the hash in result, and > to path. path += '<' + result.hash + '>'; // 5.4.5.4) Set issuer copy to the identifier issuer in // result. issuerCopy = result.issuer; // 5.4.5.5) If chosen path is not empty and the length of // path is greater than or equal to the length of chosen // path and path is lexicographically greater than chosen // path, then skip to the next permutation. // Note: Comparing path length to chosen path length can be // optimized away; only compare lexicographically. if(chosenPath.length !== 0 && path > chosenPath) { // FIXME: may cause inaccurate total depth calculation return nextPermutation(); } callback(); }); }, callback); }, callback => { // 5.4.6) If chosen path is empty or path is lexicographically // less than chosen path, set chosen path to path and chosen // issuer to issuer copy. if(chosenPath.length === 0 || path < chosenPath) { chosenPath = path; chosenIssuer = issuerCopy; } callback(); } ], nextPermutation); }, err => { if(err) { return callback(err); } // 5.5) Append chosen path to data to hash. md.update(chosenPath); // 5.6) Replace issuer, by reference, with chosen issuer. issuer = chosenIssuer; callback(); }); }, callback); } ], err => { // 6) Return issuer and the hash that results from passing data to hash // through the hash algorithm. callback(err, {hash: md.digest(), issuer}); }); } // helper for modifying component during Hash First Degree Quads modifyFirstDegreeComponent(id, component) { if(component.termType !== 'BlankNode') { return component; } component = util.clone(component); component.value = (component.value === id ? '_:a' : '_:z'); return component; } // helper for getting a related predicate getRelatedPredicate(quad) { return '<' + quad.predicate.value + '>'; } // helper for creating hash to related blank nodes map createHashToRelated(id, issuer, callback) { const self = this; // 1) Create a hash to related blank nodes map for storing hashes that // identify related blank nodes. const hashToRelated = {}; // 2) Get a reference, quads, to the list of quads in the blank node to // quads map for the key identifier. const quads = self.blankNodeInfo[id].quads; // 3) For each quad in quads: self.forEach(quads, (quad, idx, callback) => { // 3.1) For each component in quad, if component is the subject, object, // and graph name and it is a blank node that is not identified by // identifier: self.forEach(quad, (component, key, callback) => { if(key === 'predicate' || !(component.termType === 'BlankNode' && component.value !== id)) { return callback(); } // 3.1.1) Set hash to the result of the Hash Related Blank Node // algorithm, passing the blank node identifier for component as // related, quad, path identifier issuer as issuer, and position as // either s, o, or g based on whether component is a subject, object, // graph name, respectively. const related = component.value; const position = POSITIONS[key]; self.hashRelatedBlankNode( related, quad, issuer, position, (err, hash) => { if(err) { return callback(err); } // 3.1.2) Add a mapping of hash to the blank node identifier for // component to hash to related blank nodes map, adding an entry as // necessary. if(hash in hashToRelated) { hashToRelated[hash].push(related); } else { hashToRelated[hash] = [related]; } callback(); }); }, callback); }, err => callback(err, hashToRelated)); } // helper that iterates over quad components (skips predicate) forEachComponent(quad, op) { for(const key in quad) { // skip `predicate` if(key === 'predicate') { continue; } op(quad[key], key, quad); } } }; rdf-canonize-1.1.0/lib/URDNA2015Sync.js000066400000000000000000000425741361042651100172250ustar00rootroot00000000000000/* * Copyright (c) 2016 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const IdentifierIssuer = require('./IdentifierIssuer'); const MessageDigest = require('./MessageDigest'); const Permutator = require('./Permutator'); const NQuads = require('./NQuads'); const util = require('./util'); const POSITIONS = {subject: 's', object: 'o', graph: 'g'}; module.exports = class URDNA2015Sync { constructor() { this.name = 'URDNA2015'; this.blankNodeInfo = {}; this.hashToBlankNodes = {}; this.canonicalIssuer = new IdentifierIssuer('_:c14n'); this.hashAlgorithm = 'sha256'; this.quads; } // 4.4) Normalization Algorithm main(dataset) { const self = this; self.quads = dataset; // 1) Create the normalization state. // Note: Optimize by generating non-normalized blank node map concurrently. const nonNormalized = {}; // 2) For every quad in input dataset: for(const quad of dataset) { // 2.1) For each blank node that occurs in the quad, add a reference // to the quad using the blank node identifier in the blank node to // quads map, creating a new entry if necessary. self.forEachComponent(quad, component => { if(component.termType !== 'BlankNode') { return; } const id = component.value; if(id in self.blankNodeInfo) { self.blankNodeInfo[id].quads.push(quad); } else { nonNormalized[id] = true; self.blankNodeInfo[id] = {quads: [quad]}; } }); } // 3) Create a list of non-normalized blank node identifiers // non-normalized identifiers and populate it using the keys from the // blank node to quads map. // Note: We use a map here and it was generated during step 2. // 4) Initialize simple, a boolean flag, to true. let simple = true; // 5) While simple is true, issue canonical identifiers for blank nodes: while(simple) { // 5.1) Set simple to false. simple = false; // 5.2) Clear hash to blank nodes map. self.hashToBlankNodes = {}; // 5.3) For each blank node identifier identifier in non-normalized // identifiers: for(const id in nonNormalized) { // 5.3.1) Create a hash, hash, according to the Hash First Degree // Quads algorithm. const hash = self.hashFirstDegreeQuads(id); // 5.3.2) Add hash and identifier to hash to blank nodes map, // creating a new entry if necessary. if(hash in self.hashToBlankNodes) { self.hashToBlankNodes[hash].push(id); } else { self.hashToBlankNodes[hash] = [id]; } } // 5.4) For each hash to identifier list mapping in hash to blank // nodes map, lexicographically-sorted by hash: const hashes = Object.keys(self.hashToBlankNodes).sort(); for(let i = 0; i < hashes.length; ++i) { // 5.4.1) If the length of identifier list is greater than 1, // continue to the next mapping. const hash = hashes[i]; const idList = self.hashToBlankNodes[hash]; if(idList.length > 1) { continue; } // 5.4.2) Use the Issue Identifier algorithm, passing canonical // issuer and the single blank node identifier in identifier // list, identifier, to issue a canonical replacement identifier // for identifier. // TODO: consider changing `getId` to `issue` const id = idList[0]; self.canonicalIssuer.getId(id); // 5.4.3) Remove identifier from non-normalized identifiers. delete nonNormalized[id]; // 5.4.4) Remove hash from the hash to blank nodes map. delete self.hashToBlankNodes[hash]; // 5.4.5) Set simple to true. simple = true; } } // 6) For each hash to identifier list mapping in hash to blank nodes map, // lexicographically-sorted by hash: const hashes = Object.keys(self.hashToBlankNodes).sort(); for(let i = 0; i < hashes.length; ++i) { // 6.1) Create hash path list where each item will be a result of // running the Hash N-Degree Quads algorithm. const hashPathList = []; // 6.2) For each blank node identifier identifier in identifier list: const hash = hashes[i]; const idList = self.hashToBlankNodes[hash]; for(let j = 0; j < idList.length; ++j) { // 6.2.1) If a canonical identifier has already been issued for // identifier, continue to the next identifier. const id = idList[j]; if(self.canonicalIssuer.hasId(id)) { continue; } // 6.2.2) Create temporary issuer, an identifier issuer // initialized with the prefix _:b. const issuer = new IdentifierIssuer('_:b'); // 6.2.3) Use the Issue Identifier algorithm, passing temporary // issuer and identifier, to issue a new temporary blank node // identifier for identifier. issuer.getId(id); // 6.2.4) Run the Hash N-Degree Quads algorithm, passing // temporary issuer, and append the result to the hash path list. const result = self.hashNDegreeQuads(id, issuer); hashPathList.push(result); } // 6.3) For each result in the hash path list, // lexicographically-sorted by the hash in result: // TODO: use `String.localeCompare`? hashPathList.sort((a, b) => (a.hash < b.hash) ? -1 : ((a.hash > b.hash) ? 1 : 0)); for(let j = 0; j < hashPathList.length; ++j) { // 6.3.1) For each blank node identifier, existing identifier, // that was issued a temporary identifier by identifier issuer // in result, issue a canonical identifier, in the same order, // using the Issue Identifier algorithm, passing canonical // issuer and existing identifier. const result = hashPathList[j]; for(const existing in result.issuer.existing) { self.canonicalIssuer.getId(existing); } } } /* Note: At this point all blank nodes in the set of RDF quads have been assigned canonical identifiers, which have been stored in the canonical issuer. Here each quad is updated by assigning each of its blank nodes its new identifier. */ // 7) For each quad, quad, in input dataset: const normalized = []; for(let i = 0; i < self.quads.length; ++i) { // 7.1) Create a copy, quad copy, of quad and replace any existing // blank node identifiers using the canonical identifiers // previously issued by canonical issuer. // Note: We optimize away the copy here. const quad = self.quads[i]; self.forEachComponent(quad, component => { if(component.termType === 'BlankNode' && !component.value.startsWith(self.canonicalIssuer.prefix)) { component.value = self.canonicalIssuer.getId(component.value); } }); // 7.2) Add quad copy to the normalized dataset. normalized.push(NQuads.serializeQuad(quad)); } // sort normalized output normalized.sort(); // 8) Return the normalized dataset. return normalized.join(''); } // 4.6) Hash First Degree Quads hashFirstDegreeQuads(id) { const self = this; // return cached hash const info = self.blankNodeInfo[id]; if('hash' in info) { return info.hash; } // 1) Initialize nquads to an empty list. It will be used to store quads in // N-Quads format. const nquads = []; // 2) Get the list of quads `quads` associated with the reference blank node // identifier in the blank node to quads map. const quads = info.quads; // 3) For each quad `quad` in `quads`: for(let i = 0; i < quads.length; ++i) { const quad = quads[i]; // 3.1) Serialize the quad in N-Quads format with the following special // rule: // 3.1.1) If any component in quad is an blank node, then serialize it // using a special identifier as follows: const copy = {predicate: quad.predicate}; self.forEachComponent(quad, (component, key) => { // 3.1.2) If the blank node's existing blank node identifier matches // the reference blank node identifier then use the blank node // identifier _:a, otherwise, use the blank node identifier _:z. copy[key] = self.modifyFirstDegreeComponent(id, component, key); }); nquads.push(NQuads.serializeQuad(copy)); } // 4) Sort nquads in lexicographical order. nquads.sort(); // 5) Return the hash that results from passing the sorted, joined nquads // through the hash algorithm. const md = new MessageDigest(self.hashAlgorithm); for(let i = 0; i < nquads.length; ++i) { md.update(nquads[i]); } // TODO: represent as byte buffer instead to cut memory usage in half info.hash = md.digest(); return info.hash; } // 4.7) Hash Related Blank Node hashRelatedBlankNode(related, quad, issuer, position) { const self = this; // 1) Set the identifier to use for related, preferring first the canonical // identifier for related if issued, second the identifier issued by issuer // if issued, and last, if necessary, the result of the Hash First Degree // Quads algorithm, passing related. let id; if(self.canonicalIssuer.hasId(related)) { id = self.canonicalIssuer.getId(related); } else if(issuer.hasId(related)) { id = issuer.getId(related); } else { id = self.hashFirstDegreeQuads(related); } // 2) Initialize a string input to the value of position. // Note: We use a hash object instead. const md = new MessageDigest(self.hashAlgorithm); md.update(position); // 3) If position is not g, append <, the value of the predicate in quad, // and > to input. if(position !== 'g') { md.update(self.getRelatedPredicate(quad)); } // 4) Append identifier to input. md.update(id); // 5) Return the hash that results from passing input through the hash // algorithm. // TODO: represent as byte buffer instead to cut memory usage in half return md.digest(); } // 4.8) Hash N-Degree Quads hashNDegreeQuads(id, issuer) { const self = this; // 1) Create a hash to related blank nodes map for storing hashes that // identify related blank nodes. // Note: 2) and 3) handled within `createHashToRelated` const md = new MessageDigest(self.hashAlgorithm); const hashToRelated = self.createHashToRelated(id, issuer); // 4) Create an empty string, data to hash. // Note: We created a hash object `md` above instead. // 5) For each related hash to blank node list mapping in hash to related // blank nodes map, sorted lexicographically by related hash: const hashes = Object.keys(hashToRelated).sort(); for(let i = 0; i < hashes.length; ++i) { // 5.1) Append the related hash to the data to hash. const hash = hashes[i]; md.update(hash); // 5.2) Create a string chosen path. let chosenPath = ''; // 5.3) Create an unset chosen issuer variable. let chosenIssuer; // 5.4) For each permutation of blank node list: const permutator = new Permutator(hashToRelated[hash]); while(permutator.hasNext()) { const permutation = permutator.next(); // 5.4.1) Create a copy of issuer, issuer copy. let issuerCopy = issuer.clone(); // 5.4.2) Create a string path. let path = ''; // 5.4.3) Create a recursion list, to store blank node identifiers // that must be recursively processed by this algorithm. const recursionList = []; // 5.4.4) For each related in permutation: let nextPermutation = false; for(let j = 0; j < permutation.length; ++j) { // 5.4.4.1) If a canonical identifier has been issued for // related, append it to path. const related = permutation[j]; if(self.canonicalIssuer.hasId(related)) { path += self.canonicalIssuer.getId(related); } else { // 5.4.4.2) Otherwise: // 5.4.4.2.1) If issuer copy has not issued an identifier for // related, append related to recursion list. if(!issuerCopy.hasId(related)) { recursionList.push(related); } // 5.4.4.2.2) Use the Issue Identifier algorithm, passing // issuer copy and related and append the result to path. path += issuerCopy.getId(related); } // 5.4.4.3) If chosen path is not empty and the length of path // is greater than or equal to the length of chosen path and // path is lexicographically greater than chosen path, then // skip to the next permutation. // Note: Comparing path length to chosen path length can be optimized // away; only compare lexicographically. if(chosenPath.length !== 0 && path > chosenPath) { nextPermutation = true; break; } } if(nextPermutation) { continue; } // 5.4.5) For each related in recursion list: for(let j = 0; j < recursionList.length; ++j) { // 5.4.5.1) Set result to the result of recursively executing // the Hash N-Degree Quads algorithm, passing related for // identifier and issuer copy for path identifier issuer. const related = recursionList[j]; const result = self.hashNDegreeQuads(related, issuerCopy); // 5.4.5.2) Use the Issue Identifier algorithm, passing issuer // copy and related and append the result to path. path += issuerCopy.getId(related); // 5.4.5.3) Append <, the hash in result, and > to path. path += '<' + result.hash + '>'; // 5.4.5.4) Set issuer copy to the identifier issuer in // result. issuerCopy = result.issuer; // 5.4.5.5) If chosen path is not empty and the length of path // is greater than or equal to the length of chosen path and // path is lexicographically greater than chosen path, then // skip to the next permutation. // Note: Comparing path length to chosen path length can be optimized // away; only compare lexicographically. if(chosenPath.length !== 0 && path > chosenPath) { nextPermutation = true; break; } } if(nextPermutation) { continue; } // 5.4.6) If chosen path is empty or path is lexicographically // less than chosen path, set chosen path to path and chosen // issuer to issuer copy. if(chosenPath.length === 0 || path < chosenPath) { chosenPath = path; chosenIssuer = issuerCopy; } } // 5.5) Append chosen path to data to hash. md.update(chosenPath); // 5.6) Replace issuer, by reference, with chosen issuer. issuer = chosenIssuer; } // 6) Return issuer and the hash that results from passing data to hash // through the hash algorithm. return {hash: md.digest(), issuer}; } // helper for modifying component during Hash First Degree Quads modifyFirstDegreeComponent(id, component) { if(component.termType !== 'BlankNode') { return component; } component = util.clone(component); component.value = (component.value === id ? '_:a' : '_:z'); return component; } // helper for getting a related predicate getRelatedPredicate(quad) { return '<' + quad.predicate.value + '>'; } // helper for creating hash to related blank nodes map createHashToRelated(id, issuer) { const self = this; // 1) Create a hash to related blank nodes map for storing hashes that // identify related blank nodes. const hashToRelated = {}; // 2) Get a reference, quads, to the list of quads in the blank node to // quads map for the key identifier. const quads = self.blankNodeInfo[id].quads; // 3) For each quad in quads: for(let i = 0; i < quads.length; ++i) { // 3.1) For each component in quad, if component is the subject, object, // and graph name and it is a blank node that is not identified by // identifier: const quad = quads[i]; for(const key in quad) { const component = quad[key]; if(key === 'predicate' || !(component.termType === 'BlankNode' && component.value !== id)) { continue; } // 3.1.1) Set hash to the result of the Hash Related Blank Node // algorithm, passing the blank node identifier for component as // related, quad, path identifier issuer as issuer, and position as // either s, o, or g based on whether component is a subject, object, // graph name, respectively. const related = component.value; const position = POSITIONS[key]; const hash = self.hashRelatedBlankNode(related, quad, issuer, position); // 3.1.2) Add a mapping of hash to the blank node identifier for // component to hash to related blank nodes map, adding an entry as // necessary. if(hash in hashToRelated) { hashToRelated[hash].push(related); } else { hashToRelated[hash] = [related]; } } } return hashToRelated; } // helper that iterates over quad components (skips predicate) forEachComponent(quad, op) { for(const key in quad) { // skip `predicate` if(key === 'predicate') { continue; } op(quad[key], key, quad); } } }; rdf-canonize-1.1.0/lib/URGNA2012.js000066400000000000000000000056401361042651100163610ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const URDNA2015 = require('./URDNA2015'); const util = require('./util'); module.exports = class URDNA2012 extends URDNA2015 { constructor(options) { super(options); this.name = 'URGNA2012'; this.hashAlgorithm = 'sha1'; } // helper for modifying component during Hash First Degree Quads modifyFirstDegreeComponent(id, component, key) { if(component.termType !== 'BlankNode') { return component; } component = util.clone(component); if(key === 'name') { component.value = '_:g'; } else { component.value = (component.value === id ? '_:a' : '_:z'); } return component; } // helper for getting a related predicate getRelatedPredicate(quad) { return quad.predicate.value; } // helper for creating hash to related blank nodes map createHashToRelated(id, issuer, callback) { const self = this; // 1) Create a hash to related blank nodes map for storing hashes that // identify related blank nodes. const hashToRelated = {}; // 2) Get a reference, quads, to the list of quads in the blank node to // quads map for the key identifier. const quads = self.blankNodeInfo[id].quads; // 3) For each quad in quads: self.forEach(quads, (quad, idx, callback) => { // 3.1) If the quad's subject is a blank node that does not match // identifier, set hash to the result of the Hash Related Blank Node // algorithm, passing the blank node identifier for subject as related, // quad, path identifier issuer as issuer, and p as position. let position; let related; if(quad.subject.termType === 'BlankNode' && quad.subject.value !== id) { related = quad.subject.value; position = 'p'; } else if( quad.object.termType === 'BlankNode' && quad.object.value !== id) { // 3.2) Otherwise, if quad's object is a blank node that does not match // identifier, to the result of the Hash Related Blank Node algorithm, // passing the blank node identifier for object as related, quad, path // identifier issuer as issuer, and r as position. related = quad.object.value; position = 'r'; } else { // 3.3) Otherwise, continue to the next quad. return callback(); } // 3.4) Add a mapping of hash to the blank node identifier for the // component that matched (subject or object) to hash to related blank // nodes map, adding an entry as necessary. self.hashRelatedBlankNode( related, quad, issuer, position, (err, hash) => { if(err) { return callback(err); } if(hash in hashToRelated) { hashToRelated[hash].push(related); } else { hashToRelated[hash] = [related]; } callback(); }); }, err => callback(err, hashToRelated)); } }; rdf-canonize-1.1.0/lib/URGNA2012Sync.js000066400000000000000000000054441361042651100172200ustar00rootroot00000000000000/* * Copyright (c) 2016 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const URDNA2015Sync = require('./URDNA2015Sync'); const util = require('./util'); module.exports = class URDNA2012Sync extends URDNA2015Sync { constructor() { super(); this.name = 'URGNA2012'; this.hashAlgorithm = 'sha1'; } // helper for modifying component during Hash First Degree Quads modifyFirstDegreeComponent(id, component, key) { if(component.termType !== 'BlankNode') { return component; } component = util.clone(component); if(key === 'name') { component.value = '_:g'; } else { component.value = (component.value === id ? '_:a' : '_:z'); } return component; } // helper for getting a related predicate getRelatedPredicate(quad) { return quad.predicate.value; } // helper for creating hash to related blank nodes map createHashToRelated(id, issuer) { const self = this; // 1) Create a hash to related blank nodes map for storing hashes that // identify related blank nodes. const hashToRelated = {}; // 2) Get a reference, quads, to the list of quads in the blank node to // quads map for the key identifier. const quads = self.blankNodeInfo[id].quads; // 3) For each quad in quads: for(let i = 0; i < quads.length; ++i) { // 3.1) If the quad's subject is a blank node that does not match // identifier, set hash to the result of the Hash Related Blank Node // algorithm, passing the blank node identifier for subject as related, // quad, path identifier issuer as issuer, and p as position. const quad = quads[i]; let position; let related; if(quad.subject.termType === 'BlankNode' && quad.subject.value !== id) { related = quad.subject.value; position = 'p'; } else if( quad.object.termType === 'BlankNode' && quad.object.value !== id) { // 3.2) Otherwise, if quad's object is a blank node that does not match // identifier, to the result of the Hash Related Blank Node algorithm, // passing the blank node identifier for object as related, quad, path // identifier issuer as issuer, and r as position. related = quad.object.value; position = 'r'; } else { // 3.3) Otherwise, continue to the next quad. continue; } // 3.4) Add a mapping of hash to the blank node identifier for the // component that matched (subject or object) to hash to related blank // nodes map, adding an entry as necessary. const hash = self.hashRelatedBlankNode(related, quad, issuer, position); if(hash in hashToRelated) { hashToRelated[hash].push(related); } else { hashToRelated[hash] = [related]; } } return hashToRelated; } }; rdf-canonize-1.1.0/lib/index.js000066400000000000000000000123221361042651100163020ustar00rootroot00000000000000/** * An implementation of the RDF Dataset Normalization specification. * This library works in the browser and node.js. * * BSD 3-Clause License * Copyright (c) 2016-2017 Digital Bazaar, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the Digital Bazaar, Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 'use strict'; const util = require('./util'); const URDNA2015 = require('./URDNA2015'); const URGNA2012 = require('./URGNA2012'); const URDNA2015Sync = require('./URDNA2015Sync'); const URGNA2012Sync = require('./URGNA2012Sync'); // optional native support let rdfCanonizeNative; try { rdfCanonizeNative = require('rdf-canonize-native'); } catch(e) {} const api = {}; module.exports = api; // expose helpers api.NQuads = require('./NQuads'); api.IdentifierIssuer = require('./IdentifierIssuer'); /** * Get or set native API. * * @param api the native API. * * @return the currently set native API. */ api._rdfCanonizeNative = function(api) { if(api) { rdfCanonizeNative = api; } return rdfCanonizeNative; }; /** * Asynchronously canonizes an RDF dataset. * * @param dataset the dataset to canonize. * @param options the options to use: * algorithm the canonicalization algorithm to use, `URDNA2015` or * `URGNA2012`. * [useNative] use native implementation (default: false). * @param [callback(err, canonical)] called once the operation completes. * * @return a Promise that resolves to the canonicalized RDF Dataset. */ api.canonize = util.callbackify(async function(dataset, options) { let callback; const promise = new Promise((resolve, reject) => { callback = (err, canonical) => { if(err) { return reject(err); } /*if(options.format === 'application/n-quads') { canonical = canonical.join(''); } canonical = _parseNQuads(canonical.join(''));*/ resolve(canonical); }; }); // back-compat with legacy dataset if(!Array.isArray(dataset)) { dataset = api.NQuads.legacyDatasetToQuads(dataset); } // TODO: convert algorithms to Promise-based async if(options.useNative) { if(rdfCanonizeNative) { rdfCanonizeNative.canonize(dataset, options, callback); } else { throw new Error('rdf-canonize-native not available'); } } else { if(options.algorithm === 'URDNA2015') { new URDNA2015(options).main(dataset, callback); } else if(options.algorithm === 'URGNA2012') { new URGNA2012(options).main(dataset, callback); } else if(!('algorithm' in options)) { throw new Error('No RDF Dataset Canonicalization algorithm specified.'); } else { throw new Error( 'Invalid RDF Dataset Canonicalization algorithm: ' + options.algorithm); } } return promise; }); /** * Synchronously canonizes an RDF dataset. * * @param dataset the dataset to canonize. * @param options the options to use: * algorithm the canonicalization algorithm to use, `URDNA2015` or * `URGNA2012`. * [useNative] use native implementation (default: false). * * @return the RDF dataset in canonical form. */ api.canonizeSync = function(dataset, options) { // back-compat with legacy dataset if(!Array.isArray(dataset)) { dataset = api.NQuads.legacyDatasetToQuads(dataset); } if(options.useNative) { if(rdfCanonizeNative) { return rdfCanonizeNative.canonizeSync(dataset, options); } throw new Error('rdf-canonize-native not available'); } if(options.algorithm === 'URDNA2015') { return new URDNA2015Sync(options).main(dataset); } else if(options.algorithm === 'URGNA2012') { return new URGNA2012Sync(options).main(dataset); } if(!('algorithm' in options)) { throw new Error('No RDF Dataset Canonicalization algorithm specified.'); } throw new Error( 'Invalid RDF Dataset Canonicalization algorithm: ' + options.algorithm); }; rdf-canonize-1.1.0/lib/util.js000066400000000000000000000052361361042651100161560ustar00rootroot00000000000000/* * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const api = {}; module.exports = api; // define setImmediate and nextTick //// nextTick implementation with browser-compatible fallback //// // from https://github.com/caolan/async/blob/master/lib/async.js // capture the global reference to guard against fakeTimer mocks const _setImmediate = typeof setImmediate === 'function' && setImmediate; const _delay = _setImmediate ? // not a direct alias (for IE10 compatibility) fn => _setImmediate(fn) : fn => setTimeout(fn, 0); if(typeof process === 'object' && typeof process.nextTick === 'function') { api.nextTick = process.nextTick; } else { api.nextTick = _delay; } api.setImmediate = _setImmediate ? _delay : api.nextTick; /** * Clones an object, array, or string/number. If a typed JavaScript object * is given, such as a Date, it will be converted to a string. * * @param value the value to clone. * * @return the cloned value. */ api.clone = function(value) { if(value && typeof value === 'object') { let rval; if(Array.isArray(value)) { rval = []; for(let i = 0; i < value.length; ++i) { rval[i] = api.clone(value[i]); } } else if(api.isObject(value)) { rval = {}; for(const key in value) { rval[key] = api.clone(value[key]); } } else { rval = value.toString(); } return rval; } return value; }; /** * Returns true if the given value is an Object. * * @param v the value to check. * * @return true if the value is an Object, false if not. */ api.isObject = v => Object.prototype.toString.call(v) === '[object Object]'; /** * Returns true if the given value is undefined. * * @param v the value to check. * * @return true if the value is undefined, false if not. */ api.isUndefined = v => typeof v === 'undefined'; api.callbackify = fn => { return async function(...args) { const callback = args[args.length - 1]; if(typeof callback === 'function') { args.pop(); } let result; try { result = await fn.apply(null, args); } catch(e) { if(typeof callback === 'function') { return _invokeCallback(callback, e); } throw e; } if(typeof callback === 'function') { return _invokeCallback(callback, null, result); } return result; }; }; function _invokeCallback(callback, err, result) { try { return callback(err, result); } catch(unhandledError) { // throw unhandled errors to prevent "unhandled rejected promise" // and simulate what would have happened in a promiseless API process.nextTick(() => { throw unhandledError; }); } } rdf-canonize-1.1.0/package.json000066400000000000000000000047001361042651100163560ustar00rootroot00000000000000{ "name": "rdf-canonize", "version": "1.1.0", "description": "An implementation of the RDF Dataset Normalization Algorithm in JavaScript", "homepage": "https://github.com/digitalbazaar/rdf-canonize", "author": { "name": "Digital Bazaar, Inc.", "email": "support@digitalbazaar.com", "url": "https://digitalbazaar.com/" }, "contributors": [ "Dave Longley " ], "repository": { "type": "git", "url": "https://github.com/digitalbazaar/rdf-canonize" }, "bugs": { "url": "https://github.com/digitalbazaar/rdf-canonize/issues", "email": "support@digitalbazaar.com" }, "license": "BSD-3-Clause", "main": "index.js", "files": [ "dist/*.js", "dist/*.js.map", "dist/node6/**/*.js", "index.js", "lib/*.js" ], "dependencies": { "node-forge": "^0.9.1", "semver": "^6.3.0" }, "devDependencies": { "@babel/cli": "^7.2.3", "@babel/core": "^7.2.2", "@babel/plugin-transform-modules-commonjs": "^7.2.0", "@babel/plugin-transform-runtime": "^7.2.0", "@babel/preset-env": "^7.3.1", "@babel/runtime": "^7.3.1", "babel-loader": "^8.0.5", "benchmark": "^2.1.4", "chai": "^4.2.0", "commander": "^2.19.0", "core-js": "^2.6.3", "eslint": "^5.14.1", "eslint-config-digitalbazaar": "^1.6.0", "mocha": "^5.2.0", "nyc": "^13.1.0", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-merge": "^4.2.1" }, "engines": { "node": ">=6" }, "keywords": [ "JSON", "Linked Data", "JSON-LD", "RDF", "Semantic Web", "jsonld" ], "scripts": { "prepublish": "npm run build", "build": "npm run build-webpack && npm run build-node6", "build-webpack": "webpack", "build-node6": "BROWSERSLIST='node 6' babel --no-babelrc lib --out-dir dist/node6/lib --presets=@babel/preset-env", "fetch-test-suite": "if [ ! -e test-suites/normalization ]; then git clone --depth 1 https://github.com/json-ld/normalization.git test-suites/normalization; fi", "test": "mocha -R spec --check-leaks", "benchmark": "node benchmark/benchmark.js", "coverage": "rm -rf coverage && nyc --reporter=lcov --reporter=text-summary npm test", "coverage-report": "nyc report", "lint": "eslint '*.js' 'lib/*.js' 'test/*.js' 'benchmark/*.js'" }, "browser": { "./index.js": "./lib/index.js", "./lib/MessageDigest": "./lib/MessageDigest-browser.js", "rdf-canonize-native": false } } rdf-canonize-1.1.0/test/000077500000000000000000000000001361042651100150465ustar00rootroot00000000000000rdf-canonize-1.1.0/test/.eslintrc.js000066400000000000000000000000621361042651100173030ustar00rootroot00000000000000module.exports = { env: { mocha: true } } rdf-canonize-1.1.0/test/EarlReport.js000066400000000000000000000055551361042651100174750ustar00rootroot00000000000000/** * Copyright (c) 2016-2017 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const fs = require('fs'); module.exports = class EarlReport { constructor(implementation) { this.implementation = 'node.js'; let today = new Date(); today = today.getFullYear() + '-' + (today.getMonth() < 9 ? '0' + (today.getMonth() + 1) : today.getMonth() + 1) + '-' + (today.getDate() < 10 ? '0' + today.getDate() : today.getDate()); this.report = { '@context': { 'doap': 'http://usefulinc.com/ns/doap#', 'foaf': 'http://xmlns.com/foaf/0.1/', 'dc': 'http://purl.org/dc/terms/', 'earl': 'http://www.w3.org/ns/earl#', 'xsd': 'http://www.w3.org/2001/XMLSchema#', 'doap:homepage': {'@type': '@id'}, 'doap:license': {'@type': '@id'}, 'dc:creator': {'@type': '@id'}, 'foaf:homepage': {'@type': '@id'}, 'subjectOf': {'@reverse': 'earl:subject'}, 'earl:assertedBy': {'@type': '@id'}, 'earl:mode': {'@type': '@id'}, 'earl:test': {'@type': '@id'}, 'earl:outcome': {'@type': '@id'}, 'dc:date': {'@type': 'xsd:date'} }, '@id': 'https://github.com/digitalbazaar/jsonld.js', '@type': [ 'doap:Project', 'earl:TestSubject', 'earl:Software' ], 'doap:name': 'jsonld.js', 'dc:title': 'jsonld.js', 'doap:homepage': 'https://github.com/digitalbazaar/rdf-canonize', 'doap:license': 'https://github.com/digitalbazaar/rdf-canonize/blob/master/LICENSE', 'doap:description': 'A JSON-LD processor for JavaScript', 'doap:programming-language': 'JavaScript', 'dc:creator': 'https://github.com/dlongley', 'doap:developer': { '@id': 'https://github.com/dlongley', '@type': [ 'foaf:Person', 'earl:Assertor' ], 'foaf:name': 'Dave Longley', 'foaf:homepage': 'https://github.com/dlongley' }, 'dc:date': { '@value': today, '@type': 'xsd:date' }, 'subjectOf': [] }; this.report['@id'] += '#' + implementation; this.report['doap:name'] += ' ' + implementation; this.report['dc:title'] += ' ' + implementation; } addAssertion(test, pass) { this.report.subjectOf.push({ '@type': 'earl:Assertion', 'earl:assertedBy': this.report['doap:developer']['@id'], 'earl:mode': 'earl:automatic', 'earl:test': test['@id'], 'earl:result': { '@type': 'earl:TestResult', 'dc:date': new Date().toISOString(), 'earl:outcome': pass ? 'earl:passed' : 'earl:failed' } }); return this; } write(filename) { const json = JSON.stringify(this.report, null, 2); if(this.implementation === 'node.js') { fs.writeFileSync(filename, json); } else { fs.write(filename, json, 'w'); } return this; } }; rdf-canonize-1.1.0/test/test.js000066400000000000000000000245221361042651100163700ustar00rootroot00000000000000/** * Test runner for rdf-canonize. * * @author Dave Longley * * Copyright (c) 2016 Digital Bazaar, Inc. All rights reserved. */ /* eslint-disable indent */ (function() { 'use strict'; // detect node.js (vs. phantomJS) const _nodejs = (typeof process !== 'undefined' && process.versions && process.versions.node); const fs = require('fs'); let program; let assert; let path; if(_nodejs) { path = require('path'); assert = require('assert'); /* program = require('commander'); program .option('--earl [filename]', 'Output an earl report') .option('--bail', 'Bail when a test fails') .option('--test-dir', 'Test directory') .parse(process.argv); */ program = {}; program.earl = process.env.EARL; program.bail = process.env.BAIL === 'true'; program.testDir = process.env.TEST_DIR; } else { const system = require('system'); require('./setImmediate'); window.Promise = require('es6-promise').Promise; assert = require('chai').assert; require('mocha/mocha'); require('mocha-phantomjs/lib/mocha-phantomjs/core_extensions'); program = {}; for(let i = 0; i < system.args.length; ++i) { const arg = system.args[i]; if(arg.indexOf('--') === 0) { const argname = arg.substr(2); switch(argname) { case 'earl': program[argname] = system.args[i + 1]; ++i; break; default: program[argname] = true; } } } mocha.setup({ reporter: 'spec', ui: 'bdd' }); } const canonize = require('..'); const EarlReport = require('./EarlReport'); const NQuads = require('../lib/NQuads'); // try to load native bindings let rdfCanonizeNative; // try regular load try { rdfCanonizeNative = require('rdf-canonize-native'); } catch(e) { // try peer package try { rdfCanonizeNative = require('../../rdf-canonize-native'); } catch(e) { } } // use native bindings if(rdfCanonizeNative) { canonize._rdfCanonizeNative(rdfCanonizeNative); } else { // skip native tests console.warn('rdf-canonize-native not found'); } const _TEST_SUITE_PATHS = [ program['testDir'], '../normalization/tests', './test-suites/normalization/tests', ]; const TEST_SUITE = _TEST_SUITE_PATHS.find(pathExists); if(!TEST_SUITE) { throw new Error('Test suite not found.'); } const ROOT_MANIFEST_DIR = resolvePath(TEST_SUITE); const TEST_TYPES = { 'rdfn:Urgna2012EvalTest': { params: [ parseNQuads(readTestNQuads('action')), createTestOptions({ algorithm: 'URGNA2012', inputFormat: 'application/n-quads', format: 'application/n-quads' }) ], compare: compareExpectedNQuads }, 'rdfn:Urdna2015EvalTest': { params: [ parseNQuads(readTestNQuads('action')), createTestOptions({ algorithm: 'URDNA2015', inputFormat: 'application/n-quads', format: 'application/n-quads' }) ], compare: compareExpectedNQuads }, }; const SKIP_TESTS = []; // create earl report const earl = new EarlReport(_nodejs ? 'node.js' : 'browser'); // run tests describe('rdf-canonize', function() { const filename = joinPath(ROOT_MANIFEST_DIR, 'manifest.jsonld'); const rootManifest = readJson(filename); rootManifest.filename = filename; addManifest(rootManifest); if(program.earl) { const filename = resolvePath(program.earl); describe('Writing EARL report to: ' + filename, function() { it('should print the earl report', function(done) { earl.write(filename); done(); }); }); } }); if(!_nodejs) { mocha.run(() => phantom.exit()); } /** * Adds the tests for all entries in the given manifest. * * @param manifest the manifest. */ function addManifest(manifest) { describe(manifest.name || manifest.label, function() { // get entries and sequence (alias for entries) const entries = [].concat( getJsonLdValues(manifest, 'entries'), getJsonLdValues(manifest, 'sequence') ); const includes = getJsonLdValues(manifest, 'include'); // add includes to sequence as jsonld files for(let i = 0; i < includes.length; ++i) { entries.push(includes[i] + '.jsonld'); } // process entries for(let i = 0; i < entries.length; ++i) { const entry = readManifestEntry(manifest, entries[i]); if(isJsonLdType(entry, 'mf:Manifest')) { // entry is another manifest addManifest(entry); } else { // assume entry is a test addTest(manifest, entry); } } }); } function addTest(manifest, test) { // skip unknown and explicitly skipped test types const testTypes = Object.keys(TEST_TYPES); if(!isJsonLdType(test, testTypes) || isJsonLdType(test, SKIP_TESTS)) { const type = [].concat( getJsonLdValues(test, '@type'), getJsonLdValues(test, 'type') ); console.log('Skipping test "' + test.name + '" of type: ' + type); } // expand @id and input base const test_id = test['@id'] || test['id']; test['@id'] = manifest.baseIri + basename(manifest.filename) + test_id; test.base = manifest.baseIri + test.input; test.manifest = manifest; const description = test_id + ' ' + (test.purpose || test.name); const testInfo = TEST_TYPES[getTestType(test)]; const params = testInfo.params.map(param => param(test)); // custom params for js only async mode const jsParams = testInfo.params.map(param => param(test)); // custom params for native only async mode const nativeParams = testInfo.params.map(param => param(test)); nativeParams[1].useNative = true; const createCallback = done => (err, result) => { try { if(err) { throw err; } testInfo.compare(test, result); earl.addAssertion(test, true); return done(); } catch(ex) { if(program.bail) { if(ex.name !== 'AssertionError') { console.log('\nError: ', JSON.stringify(ex, null, 2)); } if(_nodejs) { process.exit(); } else { phantom.exit(); } } earl.addAssertion(test, false); return done(ex); } }; // run async js test it(description + ' (asynchronous js)', function(done) { this.timeout(5000); const callback = createCallback(done); const promise = canonize.canonize.apply(null, jsParams); promise.then(callback.bind(null, null), callback); }); if(rdfCanonizeNative && params[1].algorithm === 'URDNA2015') { // run async native test it(description + ' (asynchronous native)', function(done) { this.timeout(5000); const callback = createCallback(done); const promise = canonize.canonize.apply(null, nativeParams); promise.then(callback.bind(null, null), callback); }); } // run sync test it(description + ' (synchronous js)', function(done) { this.timeout(5000); const callback = createCallback(done); let result; try { result = canonize.canonizeSync.apply(null, jsParams); } catch(e) { return callback(e); } callback(null, result); }); if(rdfCanonizeNative && params[1].algorithm === 'URDNA2015') { // run sync test it(description + ' (synchronous native)', function(done) { this.timeout(5000); const callback = createCallback(done); let result; try { result = canonize.canonizeSync.apply(null, nativeParams); } catch(e) { return callback(e); } callback(null, result); }); } } function getTestType(test) { const types = Object.keys(TEST_TYPES); for(let i = 0; i < types.length; ++i) { if(isJsonLdType(test, types[i])) { return types[i]; } } return null; } function readManifestEntry(manifest, entry) { const dir = dirname(manifest.filename); if(typeof entry === 'string') { const filename = joinPath(dir, entry); entry = readJson(filename); entry.filename = filename; } entry.dirname = dirname(entry.filename || manifest.filename); return entry; } function readTestNQuads(property) { return test => { if(!test[property]) { return null; } const filename = joinPath(test.dirname, test[property]); return readFile(filename); }; } function parseNQuads(fn) { return test => NQuads.parse(fn(test)); } function createTestOptions(opts) { return test => { const testOptions = test.option || {}; const options = Object.assign({}, testOptions); if(opts) { // extend options Object.assign(options, opts); } return options; }; } // find the expected output property or throw error function _getExpectProperty(test) { if('expect' in test) { return 'expect'; } else if('result' in test) { return 'result'; } else { throw Error('No expected output property found'); } } function compareExpectedNQuads(test, result) { let expect; try { expect = readTestNQuads(_getExpectProperty(test))(test); assert.equal(result, expect); } catch(ex) { if(program.bail) { console.log('\nTEST FAILED\n'); console.log('EXPECTED:\n' + expect); console.log('ACTUAL:\n' + result); } throw ex; } } function isJsonLdType(node, type) { const nodeType = [].concat( getJsonLdValues(node, '@type'), getJsonLdValues(node, 'type') ); type = Array.isArray(type) ? type : [type]; for(let i = 0; i < type.length; ++i) { if(nodeType.indexOf(type[i]) !== -1) { return true; } } return false; } function getJsonLdValues(node, property) { let rval = []; if(property in node) { rval = [].concat(node[property]); } return rval; } function readJson(filename) { return JSON.parse(readFile(filename)); } function pathExists(filename) { if(_nodejs) { return fs.existsSync(filename); } return fs.exists(filename); } function readFile(filename) { if(_nodejs) { return fs.readFileSync(filename, 'utf8'); } return fs.read(filename); } function resolvePath(to) { if(_nodejs) { return path.resolve(to); } return fs.absolute(to); } function joinPath() { return (_nodejs ? path : fs).join.apply( null, Array.prototype.slice.call(arguments)); } function dirname(filename) { if(_nodejs) { return path.dirname(filename); } const idx = filename.lastIndexOf(fs.separator); if(idx === -1) { return filename; } return filename.substr(0, idx); } function basename(filename) { if(_nodejs) { return path.basename(filename); } const idx = filename.lastIndexOf(fs.separator); if(idx === -1) { return filename; } return filename.substr(idx + 1); } })(); rdf-canonize-1.1.0/webpack.config.js000066400000000000000000000053411361042651100173100ustar00rootroot00000000000000/** * rdf-canonize webpack build rules. * * @author Digital Bazaar, Inc. * * Copyright 2010-2017 Digital Bazaar, Inc. */ const path = require('path'); const webpackMerge = require('webpack-merge'); // build multiple outputs module.exports = []; // custom setup for each output // all built files will export the "rdf-canonize" library but with different // content const outputs = [ // core library { entry: [ // 'babel-polyfill' is very large, list features explicitly 'core-js/fn/object/assign', 'core-js/fn/promise', 'core-js/fn/symbol', // main lib './index.js' ], filenameBase: 'rdf-canonize' } ]; outputs.forEach(info => { // common to bundle and minified const common = { // each output uses the "jsonld" name but with different contents entry: { 'rdf-canonize': info.entry }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: [ '@babel/plugin-transform-modules-commonjs', '@babel/plugin-transform-runtime' ] } } } ] }, plugins: [ //new webpack.DefinePlugin({ //}) ], // disable various node shims as jsonld handles this manually node: { Buffer: false, crypto: false, process: false, setImmediate: false } }; // plain unoptimized unminified bundle const bundle = webpackMerge(common, { mode: 'development', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.js', library: info.library || '[name]', libraryTarget: info.libraryTarget || 'umd' } }); if(info.library === null) { delete bundle.output.library; } if(info.libraryTarget === null) { delete bundle.output.libraryTarget; } // optimized and minified bundle const minify = webpackMerge(common, { mode: 'production', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.min.js', library: info.library || '[name]', libraryTarget: info.libraryTarget || 'umd' }, devtool: 'cheap-module-source-map', plugins: [ /* new webpack.optimize.UglifyJsPlugin({ compress: { warnings: true }, output: { comments: false } //beautify: true }) */ ] }); if(info.library === null) { delete minify.output.library; } if(info.libraryTarget === null) { delete minify.output.libraryTarget; } module.exports.push(bundle); module.exports.push(minify); });