<commit_before>/**
 * BLOB
 *
 * User blob storage for desktop client
 */

// TODO build a blobPrototype.
// There's currently a code repetition between blobLocal and blobRemote..

var fs = require("fs");

var module = angular.module('blob', []);

module.factory('rpBlob', ['$rootScope', function ($scope)
{
  var cryptConfig = {
    // key size
    ks: 256,
    // iterations (key derivation)
    iter: 1000
  };

  var BlobObj = function (password)
  {
    this.password = password;
    this.data = {};
  };

  // Blob operations
  // Do NOT change the mapping of existing ops
  BlobObj.ops = {
    // Special
    "noop": 0,

    // Simple ops
    "set": 16,
    "unset": 17,
    "extend": 18,

    // Meta ops
    "push": 32,
    "pop": 33,
    "shift": 34,
    "unshift": 35,
    "filter": 36
  };

  BlobObj.opsReverseMap = [];
  $.each(BlobObj.ops, function (name, code) {
    BlobObj.opsReverseMap[code] = name;
  });

  /**
   * Attempts to retrieve the blob.
   */
  BlobObj.get = function(walletfile, callback)
  {
    fs.readFile(walletfile, 'utf8', function(err, data){
      if (err) {
        callback(err);
        return;
      }

      callback(null, data);
    });
  };

  /**
   * Attempts to retrieve and decrypt the blob.
   */
  BlobObj.init = function(walletfile, password, callback)
  {
    BlobObj.get(walletfile, function (err, data) {
      if (err) {
        callback(err);
        return;
      }

      var blob = new BlobObj(password);
      var decryptedBlob = blob.decrypt(data);

      if (!decryptedBlob) {
        callback(new Error("Error while decrypting blob"));
        return;
      }

      callback(null, decryptedBlob);
    });
  };

  /**
   * Create a blob object
   *
   * @param {object} opts
   * @param {string} opts.url
   * @param {string} opts.id
   * @param opts.crypt
   * @param opts.unlock
   * @param {string} opts.username
   * @param {string} opts.account
   * @param {string} opts.masterkey
   * @param {object=} opts.oldUserBlob
   * @param {function} callback
   */
  BlobObj.create = function (opts, callback)
  {
    var blob = new BlobObj(opts.password);
    blob.data = {
      masterkey: opts.masterkey,
      account_id: opts.account,
      contacts: [],
      created: (new Date()).toJSON()
    };
    blob.walletfile = opts.walletfile;

    blob.persist(callback);
  };

  // Store blob in a file
  BlobObj.prototype.persist = function(callback)
  {
    var blob = this;

    fs.writeFile(this.walletfile, this.encrypt(), function(){
      callback(null, blob);
    });
  };

  BlobObj.prototype.encrypt = function()
  {
    // Filter Angular metadata before encryption
    if ('object' === typeof this.data &&
        'object' === typeof this.data.contacts)
      this.data.contacts = angular.fromJson(angular.toJson(this.data.contacts));

    // Encryption
    return btoa(sjcl.encrypt(""+this.password.length+'|'+this.password,
      JSON.stringify(this.data), {
        ks: cryptConfig.ks,
        iter: cryptConfig.iter
      }
    ));
  };

  BlobObj.prototype.decrypt = function (data)
  {
    try {
      function decrypt(priv, ciphertext)
      {
        var blob = new BlobObj();
        blob.data = JSON.parse(sjcl.decrypt(priv, ciphertext));
        return blob;
      }

      return decrypt(""+this.password.length+'|'+this.password, atob(data));
    } catch (e) {
      console.log("client: blob: decryption failed", e.toString());
      console.log(e.stack);
      return false;
    }
  };

  BlobObj.escapeToken = function (token) {
    return token.replace(/[~\/]/g, function (key) { return key === "~" ? "~0" : "~1"; });
  };
  BlobObj.prototype.escapeToken = BlobObj.escapeToken;

  var unescapeToken = function(str) {
    return str.replace(/~./g, function(m) {
      switch (m) {
      case "~0":
        return "~";
      case "~1":
        return "/";
      }
      throw("Invalid tilde escape: " + m);<commit_msg>BlobLocal: Handle blob updates.<commit_after>/**
 * BLOB
 *
 * User blob storage for desktop client
 */

// TODO build a blobPrototype.
// There's currently a code repetition between blobLocal and blobRemote..

var fs = require("fs");

var module = angular.module('blob', []);

module.factory('rpBlob', ['$rootScope', function ($scope)
{
  var cryptConfig = {
    // key size
    ks: 256,
    // iterations (key derivation)
    iter: 1000
  };

  var BlobObj = function (password, walletfile)
  {
    this.password = password;
    this.walletfile = walletfile;
    this.data = {};
  };

  // Blob operations
  // Do NOT change the mapping of existing ops
  BlobObj.ops = {
    // Special
    "noop": 0,

    // Simple ops
    "set": 16,
    "unset": 17,
    "extend": 18,

    // Meta ops
    "push": 32,
    "pop": 33,
    "shift": 34,
    "unshift": 35,
    "filter": 36
  };

  BlobObj.opsReverseMap = [];
  $.each(BlobObj.ops, function (name, code) {
    BlobObj.opsReverseMap[code] = name;
  });

  /**
   * Attempts to retrieve the blob.
   */
  BlobObj.get = function(walletfile, callback)
  {
    fs.readFile(walletfile, 'utf8', function(err, data){
      if (err) {
        callback(err);
        return;
      }

      callback(null, data);
    });
  };

  /**
   * Attempts to retrieve and decrypt the blob.
   */
  BlobObj.init = function(walletfile, password, callback)
  {
    BlobObj.get(walletfile, function (err, data) {
      if (err) {
        callback(err);
        return;
      }

      var blob = new BlobObj(password, walletfile);
      var decryptedBlob = blob.decrypt(data);

      if (!decryptedBlob) {
        callback(new Error("Error while decrypting blob"));
        return;
      }

      callback(null, decryptedBlob);
    });
  };

  /**
   * Create a blob object
   *
   * @param {object} opts
   * @param {string} opts.url
   * @param {string} opts.id
   * @param opts.crypt
   * @param opts.unlock
   * @param {string} opts.username
   * @param {string} opts.account
   * @param {string} opts.masterkey
   * @param {object=} opts.oldUserBlob
   * @param {function} callback
   */
  BlobObj.create = function (opts, callback)
  {
    var blob = new BlobObj(opts.password, opts.walletfile);
    blob.data = {
      masterkey: opts.masterkey,
      account_id: opts.account,
      contacts: [],
      created: (new Date()).toJSON()
    };

    blob.persist(callback);
  };

  // Store blob in a file
  BlobObj.prototype.persist = function(callback)
  {
    var blob = this;

    fs.writeFile(this.walletfile, this.encrypt(), function(){
      callback(null, blob);
    });
  };

  BlobObj.prototype.encrypt = function()
  {
    // Filter Angular metadata before encryption
    if ('object' === typeof this.data &&
        'object' === typeof this.data.contacts)
      this.data.contacts = angular.fromJson(angular.toJson(this.data.contacts));

    // Encryption
    return btoa(sjcl.encrypt(""+this.password.length+'|'+this.password,
      JSON.stringify(this.data), {
        ks: cryptConfig.ks,
        iter: cryptConfig.iter
      }
    ));
  };

  BlobObj.prototype.decrypt = function (data)
  {
    var blob = this;

    try {
      function decrypt(priv, ciphertext)
      {
        blob.data = JSON.parse(sjcl.decrypt(priv, ciphertext));
        return blob;
      }

      return decrypt(""+this.password.length+'|'+this.password, atob(data));
    } catch (e) {
      console.log("client: blob: decryption failed", e.toString());
      console.log(e.stack);
      return false;
    }
  };

  BlobObj.escapeToken = function (token) {
    return token.replace(/[~\/]/g, function (key) { return key === "~" ? "~0" : "~1"; });
  };
  BlobObj.prototype.escapeToken = BlobObj.escapeToken;

  var unescapeToken = function(str) {
    return str.replace(/~./g, function(m) {
      switch (m) {
      case "~0":
        return "~";
      case "~1":
        return "/";
      }
      throw("Invalid tilde escape: " + m);
{'fraction_non_alphanumeric': 0.1525445292620865, 'fraction_numerical': 0.007379134860050891, 'mean_word_length': 2.4059792027729636, 'pattern_counts': {'":': 22, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6247468', 'n_tokens_mistral': 2681, 'n_tokens_neox': 2547, 'n_words': 594}