[Pkg-javascript-commits] [node-private] 01/05: Imported Upstream version 0.1.6~20141112

Julien Puydt julien.puydt at laposte.net
Wed Oct 14 11:39:16 UTC 2015


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository node-private.

commit dc65c8588188a9ae0ca9fc5d3e37d1ba14d8a02a
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Wed Oct 14 13:20:05 2015 +0200

    Imported Upstream version 0.1.6~20141112
---
 .travis.yml  |   6 ++
 LICENSE      |  20 +++++
 README.md    | 246 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 package.json |  33 ++++++++
 private.js   | 129 +++++++++++++++++++++++++++++++
 test/run.js  |  68 +++++++++++++++++
 6 files changed, 502 insertions(+)

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..ed05f88
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+language: node_js
+node_js:
+  - "0.11"
+  - "0.10"
+  - "0.8"
+  - "0.6"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6c2f144
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Ben Newman <bn at cs.stanford.edu>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a8990aa
--- /dev/null
+++ b/README.md
@@ -0,0 +1,246 @@
+private [![Build Status](https://travis-ci.org/benjamn/private.png?branch=master)](https://travis-ci.org/benjamn/private)
+===
+
+A general-purpose utility for associating truly private state with any JavaScript object.
+
+Installation
+---
+
+From NPM:
+
+    npm install private
+
+From GitHub:
+
+    cd path/to/node_modules
+    git clone git://github.com/benjamn/private.git
+    cd private
+    npm install .
+
+Usage
+---
+**Get or create a secret object associated with any (non-frozen) object:**
+```js
+var getSecret = require("private").makeAccessor();
+var obj = Object.create(null); // any kind of object works
+getSecret(obj).totallySafeProperty = "p455w0rd";
+
+console.log(Object.keys(obj)); // []
+console.log(Object.getOwnPropertyNames(obj)); // []
+console.log(getSecret(obj)); // { totallySafeProperty: "p455w0rd" }
+```
+Now, only code that has a reference to both `getSecret` and `obj` can possibly access `.totallySafeProperty`.
+
+*Importantly, no global references to the secret object are retained by the `private` package, so as soon as `obj` gets garbage collected, the secret will be reclaimed as well. In other words, you don't have to worry about memory leaks.*
+
+**Create a unique property name that cannot be enumerated or guessed:**
+```js
+var secretKey = require("private").makeUniqueKey();
+var obj = Object.create(null); // any kind of object works
+
+Object.defineProperty(obj, secretKey, {
+  value: { totallySafeProperty: "p455w0rd" },
+  enumerable: false // optional; non-enumerability is the default
+});
+
+Object.defineProperty(obj, "nonEnumerableProperty", {
+  value: "anyone can guess my name",
+  enumerable: false
+});
+
+console.log(obj[secretKey].totallySafeProperty); // p455w0rd
+console.log(obj.nonEnumerableProperty); // "anyone can guess my name"
+console.log(Object.keys(obj)); // []
+console.log(Object.getOwnPropertyNames(obj)); // ["nonEnumerableProperty"]
+
+for (var key in obj) {
+  console.log(key); // never called
+}
+```
+Because these keys are non-enumerable, you can't discover them using a `for`-`in` loop. Because `secretKey` is a long string of random characters, you would have a lot of trouble guessing it. And because the `private` module wraps `Object.getOwnPropertyNames` to exclude the keys it generates, you can't even use that interface to discover it.
+
+Unless you have access to the value of the `secretKey` property name, there is no way to access the value associated with it. So your only responsibility as secret-keeper is to avoid handing out the value of `secretKey` to untrusted code.
+
+Think of this style as a home-grown version of the first style. Note, however, that it requires a full implementation of ES5's `Object.defineProperty` method in order to make any safety guarantees, whereas the first example will provide safety even in environments that do not support `Object.defineProperty`.
+
+Rationale
+---
+
+In JavaScript, the only data that are truly private are local variables
+whose values do not *leak* from the scope in which they were defined.
+
+This notion of *closure privacy* is powerful, and it readily provides some
+of the benefits of traditional data privacy, a la Java or C++:
+```js
+function MyClass(secret) {
+    this.increment = function() {
+        return ++secret;
+    };
+}
+
+var mc = new MyClass(3);
+console.log(mc.increment()); // 4
+```
+You can learn something about `secret` by calling `.increment()`, and you
+can increase its value by one as many times as you like, but you can never
+decrease its value, because it is completely inaccessible except through
+the `.increment` method. And if the `.increment` method were not
+available, it would be as if no `secret` variable had ever been declared,
+as far as you could tell.
+
+This style breaks down as soon as you want to inherit methods from the
+prototype of a class:
+```js
+function MyClass(secret) {
+    this.secret = secret;
+}
+
+MyClass.prototype.increment = function() {
+    return ++this.secret;
+};
+```
+The only way to communicate between the `MyClass` constructor and the
+`.increment` method in this example is to manipulate shared properties of
+`this`. Unfortunately `this.secret` is now exposed to unlicensed
+modification:
+```js
+var mc = new MyClass(6);
+console.log(mc.increment()); // 7
+mc.secret -= Infinity;
+console.log(mc.increment()); // -Infinity
+mc.secret = "Go home JavaScript, you're drunk.";
+mc.increment(); // NaN
+```
+Another problem with closure privacy is that it only lends itself to
+per-instance privacy, whereas the `private` keyword in most
+object-oriented languages indicates that the data member in question is
+visible to all instances of the same class.
+
+Suppose you have a `Node` class with a notion of parents and children:
+```js
+function Node() {
+    var parent;
+    var children = [];
+
+    this.getParent = function() {
+        return parent;
+    };
+
+    this.appendChild = function(child) {
+        children.push(child);
+        child.parent = this; // Can this be made to work?
+    };
+}
+```
+The desire here is to allow other `Node` objects to manipulate the value
+returned by `.getParent()`, but otherwise disallow any modification of the
+`parent` variable. You could expose a `.setParent` function, but then
+anyone could call it, and you might as well give up on the getter/setter
+pattern.
+
+This module solves both of these problems.
+
+Usage
+---
+
+Let's revisit the `Node` example from above:
+```js
+var p = require("private").makeAccessor();
+
+function Node() {
+    var privates = p(this);
+    var children = [];
+
+    this.getParent = function() {
+        return privates.parent;
+    };
+
+    this.appendChild = function(child) {
+        children.push(child);
+        var cp = p(child);
+        if (cp.parent)
+            cp.parent.removeChild(child);
+        cp.parent = this;
+        return child;
+    };
+}
+```
+Now, in order to access the private data of a `Node` object, you need to
+have access to the unique `p` function that is being used here.  This is
+already an improvement over the previous example, because it allows
+restricted access by other `Node` instances, but can it help with the
+`Node.prototype` problem too?
+
+Yes it can!
+```js
+var p = require("private").makeAccessor();
+
+function Node() {
+    p(this).children = [];
+}
+
+var Np = Node.prototype;
+
+Np.getParent = function() {
+    return p(this).parent;
+};
+
+Np.appendChild = function(child) {
+    p(this).children.push(child);
+    var cp = p(child);
+    if (cp.parent)
+        cp.parent.removeChild(child);
+    cp.parent = this;
+    return child;
+};
+```
+Because `p` is in scope not only within the `Node` constructor but also
+within `Node` methods, we can finally avoid redefining methods every time
+the `Node` constructor is called.
+
+Now, you might be wondering how you can restrict access to `p` so that no
+untrusted code is able to call it. The answer is to use your favorite
+module pattern, be it CommonJS, AMD `define`, or even the old
+Immediately-Invoked Function Expression:
+```js
+var Node = (function() {
+    var p = require("private").makeAccessor();
+
+    function Node() {
+        p(this).children = [];
+    }
+
+    var Np = Node.prototype;
+
+    Np.getParent = function() {
+        return p(this).parent;
+    };
+
+    Np.appendChild = function(child) {
+        p(this).children.push(child);
+        var cp = p(child);
+        if (cp.parent)
+            cp.parent.removeChild(child);
+        cp.parent = this;
+        return child;
+    };
+
+    return Node;
+}());
+
+var parent = new Node;
+var child = new Node;
+parent.appendChild(child);
+assert.strictEqual(child.getParent(), parent);
+```
+Because this version of `p` never leaks from the enclosing function scope,
+only `Node` objects have access to it.
+
+So, you see, the claim I made at the beginning of this README remains
+true:
+
+> In JavaScript, the only data that are truly private are local variables
+> whose values do not *leak* from the scope in which they were defined.
+
+It just so happens that closure privacy is sufficient to implement a
+privacy model similar to that provided by other languages.
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fdd0769
--- /dev/null
+++ b/package.json
@@ -0,0 +1,33 @@
+{
+  "author": {
+    "name": "Ben Newman",
+    "email": "bn at cs.stanford.edu"
+  },
+  "name": "private",
+  "description": "Utility for associating truly private state with any JavaScript object",
+  "keywords": [
+    "private",
+    "access control",
+    "access modifiers",
+    "encapsulation",
+    "secret",
+    "state",
+    "privilege",
+    "scope",
+    "es5"
+  ],
+  "version": "0.1.6",
+  "homepage": "http://github.com/benjamn/private",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/benjamn/private.git"
+  },
+  "license": "MIT",
+  "main": "private.js",
+  "scripts": {
+    "test": "node test/run.js"
+  },
+  "engines": {
+    "node": ">= 0.6"
+  }
+}
diff --git a/private.js b/private.js
new file mode 100644
index 0000000..20bed57
--- /dev/null
+++ b/private.js
@@ -0,0 +1,129 @@
+"use strict";
+
+var originalObject = Object;
+var originalDefProp = Object.defineProperty;
+var originalCreate = Object.create;
+
+function defProp(obj, name, value) {
+  if (originalDefProp) try {
+    originalDefProp.call(originalObject, obj, name, { value: value });
+  } catch (definePropertyIsBrokenInIE8) {
+    obj[name] = value;
+  } else {
+    obj[name] = value;
+  }
+}
+
+// For functions that will be invoked using .call or .apply, we need to
+// define those methods on the function objects themselves, rather than
+// inheriting them from Function.prototype, so that a malicious or clumsy
+// third party cannot interfere with the functionality of this module by
+// redefining Function.prototype.call or .apply.
+function makeSafeToCall(fun) {
+  if (fun) {
+    defProp(fun, "call", fun.call);
+    defProp(fun, "apply", fun.apply);
+  }
+  return fun;
+}
+
+makeSafeToCall(originalDefProp);
+makeSafeToCall(originalCreate);
+
+var hasOwn = makeSafeToCall(Object.prototype.hasOwnProperty);
+var numToStr = makeSafeToCall(Number.prototype.toString);
+var strSlice = makeSafeToCall(String.prototype.slice);
+
+var cloner = function(){};
+function create(prototype) {
+  if (originalCreate) {
+    return originalCreate.call(originalObject, prototype);
+  }
+  cloner.prototype = prototype || null;
+  return new cloner;
+}
+
+var rand = Math.random;
+var uniqueKeys = create(null);
+
+function makeUniqueKey() {
+  // Collisions are highly unlikely, but this module is in the business of
+  // making guarantees rather than safe bets.
+  do var uniqueKey = internString(strSlice.call(numToStr.call(rand(), 36), 2));
+  while (hasOwn.call(uniqueKeys, uniqueKey));
+  return uniqueKeys[uniqueKey] = uniqueKey;
+}
+
+function internString(str) {
+  var obj = {};
+  obj[str] = true;
+  return Object.keys(obj)[0];
+}
+
+// External users might find this function useful, but it is not necessary
+// for the typical use of this module.
+defProp(exports, "makeUniqueKey", makeUniqueKey);
+
+// Object.getOwnPropertyNames is the only way to enumerate non-enumerable
+// properties, so if we wrap it to ignore our secret keys, there should be
+// no way (except guessing) to access those properties.
+var originalGetOPNs = Object.getOwnPropertyNames;
+Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
+  for (var names = originalGetOPNs(object),
+           src = 0,
+           dst = 0,
+           len = names.length;
+       src < len;
+       ++src) {
+    if (!hasOwn.call(uniqueKeys, names[src])) {
+      if (src > dst) {
+        names[dst] = names[src];
+      }
+      ++dst;
+    }
+  }
+  names.length = dst;
+  return names;
+};
+
+function defaultCreatorFn(object) {
+  return create(null);
+}
+
+function makeAccessor(secretCreatorFn) {
+  var brand = makeUniqueKey();
+  var passkey = create(null);
+
+  secretCreatorFn = secretCreatorFn || defaultCreatorFn;
+
+  function register(object) {
+    var secret; // Created lazily.
+
+    function vault(key, forget) {
+      // Only code that has access to the passkey can retrieve (or forget)
+      // the secret object.
+      if (key === passkey) {
+        return forget
+          ? secret = null
+          : secret || (secret = secretCreatorFn(object));
+      }
+    }
+
+    defProp(object, brand, vault);
+  }
+
+  function accessor(object) {
+    if (!hasOwn.call(object, brand))
+      register(object);
+    return object[brand](passkey);
+  }
+
+  accessor.forget = function(object) {
+    if (hasOwn.call(object, brand))
+      object[brand](passkey, true);
+  };
+
+  return accessor;
+}
+
+defProp(exports, "makeAccessor", makeAccessor);
diff --git a/test/run.js b/test/run.js
new file mode 100644
index 0000000..dc3c888
--- /dev/null
+++ b/test/run.js
@@ -0,0 +1,68 @@
+var assert = require("assert");
+var makeAccessor = require("../private").makeAccessor;
+var acc1 = makeAccessor();
+var obj = {};
+var hasOwn = obj.hasOwnProperty;
+
+acc1(obj).foo = 42;
+assert.deepEqual(obj, {});
+assert.deepEqual(acc1(obj), { foo: 42 });
+assert.deepEqual(acc1(acc1(obj)), {});
+assert.deepEqual(acc1(obj), { foo: 42 });
+assert.deepEqual(Object.keys(acc1(obj)), ["foo"]);
+assert.strictEqual(Object.getOwnPropertyNames(acc1(obj)).length, 1);
+assert.strictEqual(Object.getOwnPropertyNames(acc1(acc1(obj))).length, 0);
+acc1(obj).bar = "baz";
+assert.deepEqual(acc1(obj), { foo: 42, bar: "baz" });
+delete acc1(obj).foo;
+assert.deepEqual(acc1(obj), { bar: "baz" });
+
+try {
+  acc1(42);
+  throw new Error("threw wrong error");
+} catch (err) {
+  assert.ok(err);
+}
+
+var acc2 = makeAccessor();
+assert.notStrictEqual(acc1, acc2);
+assert.notStrictEqual(acc1(obj), acc2(obj));
+assert.deepEqual(acc2(obj), {});
+assert.strictEqual(Object.getOwnPropertyNames(obj).length, 0);
+assert.strictEqual(Object.keys(obj).length, 0);
+acc2(obj).bar = "asdf";
+assert.deepEqual(acc2(obj), { bar: "asdf" });
+
+acc2.forget(obj);
+acc2(obj).bar = "asdf";
+var oldSecret = acc2(obj);
+assert.strictEqual(oldSecret.bar, "asdf");
+acc2.forget(obj);
+var newSecret = acc2(obj);
+assert.notStrictEqual(oldSecret, newSecret);
+assert.ok(hasOwn.call(oldSecret, "bar"));
+assert.ok(!hasOwn.call(newSecret, "bar"));
+newSecret.bar = "zxcv";
+assert.strictEqual(oldSecret.bar, "asdf");
+assert.strictEqual(acc2(obj).bar, "zxcv");
+
+function creatorFn(object) {
+  return { self: object };
+}
+
+var acc3 = makeAccessor(creatorFn);
+
+acc3(obj).xxx = "yyy";
+assert.deepEqual(acc3(obj), {
+  self: obj,
+  xxx: "yyy"
+});
+
+acc3.forget(obj);
+assert.deepEqual(acc3(obj), {
+  self: obj
+});
+
+var green = "\033[32m";
+var reset = "\033[0m";
+console.log(green + "ALL PASS" + reset);

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-private.git



More information about the Pkg-javascript-commits mailing list