From e44a7e37b6c7b5961adaffc62b9042b8d442938e Mon Sep 17 00:00:00 2001 From: mensonge Date: Thu, 13 Nov 2008 09:49:11 +0000 Subject: New feature: basic Ajax suggestion for tags and implementation of Dojo toolkit git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f --- includes/js/dojox/_sql/LICENSE | 9 + includes/js/dojox/_sql/_crypto.js | 443 +++++++++++++++++ includes/js/dojox/_sql/common.js | 538 +++++++++++++++++++++ .../js/dojox/_sql/demos/customers/customers.html | 292 +++++++++++ 4 files changed, 1282 insertions(+) create mode 100644 includes/js/dojox/_sql/LICENSE create mode 100644 includes/js/dojox/_sql/_crypto.js create mode 100644 includes/js/dojox/_sql/common.js create mode 100644 includes/js/dojox/_sql/demos/customers/customers.html (limited to 'includes/js/dojox/_sql') diff --git a/includes/js/dojox/_sql/LICENSE b/includes/js/dojox/_sql/LICENSE new file mode 100644 index 0000000..5c277ec --- /dev/null +++ b/includes/js/dojox/_sql/LICENSE @@ -0,0 +1,9 @@ +License Disclaimer: + +All contents of this directory are Copyright (c) the Dojo Foundation, with the +following exceptions: +------------------------------------------------------------------------------- + +_crypto.js - internally uses AES algorithm + * AES algorithm copyright Chris Veness (CLA signed and permission given to use code under BSD license) + Taken from http://www.movable-type.co.uk/scripts/aes.html diff --git a/includes/js/dojox/_sql/_crypto.js b/includes/js/dojox/_sql/_crypto.js new file mode 100644 index 0000000..e8a9214 --- /dev/null +++ b/includes/js/dojox/_sql/_crypto.js @@ -0,0 +1,443 @@ +if(!dojo._hasResource["dojox._sql._crypto"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox._sql._crypto"] = true; +// Taken from http://www.movable-type.co.uk/scripts/aes.html by +// Chris Veness (CLA signed); adapted for Dojo and Google Gears Worker Pool +// by Brad Neuberg, bkn3@columbia.edu + +dojo.provide("dojox._sql._crypto"); + +dojo.mixin(dojox._sql._crypto,{ + // _POOL_SIZE: + // Size of worker pool to create to help with crypto + _POOL_SIZE: 100, + + encrypt: function(plaintext, password, callback){ + // summary: + // Use Corrected Block TEA to encrypt plaintext using password + // (note plaintext & password must be strings not string objects). + // Results will be returned to the 'callback' asychronously. + this._initWorkerPool(); + + var msg ={plaintext: plaintext, password: password}; + msg = dojo.toJson(msg); + msg = "encr:" + String(msg); + + this._assignWork(msg, callback); + }, + + decrypt: function(ciphertext, password, callback){ + // summary: + // Use Corrected Block TEA to decrypt ciphertext using password + // (note ciphertext & password must be strings not string objects). + // Results will be returned to the 'callback' asychronously. + this._initWorkerPool(); + + var msg ={ciphertext: ciphertext, password: password}; + msg = dojo.toJson(msg); + msg = "decr:" + String(msg); + + this._assignWork(msg, callback); + }, + + _initWorkerPool: function(){ + // bugs in Google Gears prevents us from dynamically creating + // and destroying workers as we need them -- the worker + // pool functionality stops working after a number of crypto + // cycles (probably related to a memory leak in Google Gears). + // this is too bad, since it results in much simpler code. + + // instead, we have to create a pool of workers and reuse them. we + // keep a stack of 'unemployed' Worker IDs that are currently not working. + // if a work request comes in, we pop off the 'unemployed' stack + // and put them to work, storing them in an 'employed' hashtable, + // keyed by their Worker ID with the value being the callback function + // that wants the result. when an employed worker is done, we get + // a message in our 'manager' which adds this worker back to the + // unemployed stack and routes the result to the callback that + // wanted it. if all the workers were employed in the past but + // more work needed to be done (i.e. it's a tight labor pool ;) + // then the work messages are pushed onto + // a 'handleMessage' queue as an object tuple{msg: msg, callback: callback} + + if(!this._manager){ + try{ + this._manager = google.gears.factory.create("beta.workerpool", "1.0"); + this._unemployed = []; + this._employed ={}; + this._handleMessage = []; + + var self = this; + this._manager.onmessage = function(msg, sender){ + // get the callback necessary to serve this result + var callback = self._employed["_" + sender]; + + // make this worker unemployed + self._employed["_" + sender] = undefined; + self._unemployed.push("_" + sender); + + // see if we need to assign new work + // that was queued up needing to be done + if(self._handleMessage.length){ + var handleMe = self._handleMessage.shift(); + self._assignWork(handleMe.msg, handleMe.callback); + } + + // return results + callback(msg); + } + + var workerInit = "function _workerInit(){" + + "gearsWorkerPool.onmessage = " + + String(this._workerHandler) + + ";" + + "}"; + + var code = workerInit + " _workerInit();"; + + // create our worker pool + for(var i = 0; i < this._POOL_SIZE; i++){ + this._unemployed.push("_" + this._manager.createWorker(code)); + } + }catch(exp){ + throw exp.message||exp; + } + } + }, + + _assignWork: function(msg, callback){ + // can we immediately assign this work? + if(!this._handleMessage.length && this._unemployed.length){ + // get an unemployed worker + var workerID = this._unemployed.shift().substring(1); // remove _ + + // list this worker as employed + this._employed["_" + workerID] = callback; + + // do the worke + this._manager.sendMessage(msg, workerID); + }else{ + // we have to queue it up + this._handleMessage ={msg: msg, callback: callback}; + } + }, + + _workerHandler: function(msg, sender){ + + /* Begin AES Implementation */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1] + var Sbox = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]; + + // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2] + var Rcon = [ [0x00, 0x00, 0x00, 0x00], + [0x01, 0x00, 0x00, 0x00], + [0x02, 0x00, 0x00, 0x00], + [0x04, 0x00, 0x00, 0x00], + [0x08, 0x00, 0x00, 0x00], + [0x10, 0x00, 0x00, 0x00], + [0x20, 0x00, 0x00, 0x00], + [0x40, 0x00, 0x00, 0x00], + [0x80, 0x00, 0x00, 0x00], + [0x1b, 0x00, 0x00, 0x00], + [0x36, 0x00, 0x00, 0x00] ]; + + /* + * AES Cipher function: encrypt 'input' with Rijndael algorithm + * + * takes byte-array 'input' (16 bytes) + * 2D byte-array key schedule 'w' (Nr+1 x Nb bytes) + * + * applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage + * + * returns byte-array encrypted value (16 bytes) + */ + function Cipher(input, w) { // main Cipher function [§5.1] + var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) + var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys + + var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4] + for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i]; + + state = AddRoundKey(state, w, 0, Nb); + + for (var round=1; round 6 && i%Nk == 4) { + temp = SubWord(temp); + } + for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t]; + } + + return w; + } + + function SubWord(w) { // apply SBox to 4-byte word w + for (var i=0; i<4; i++) w[i] = Sbox[w[i]]; + return w; + } + + function RotWord(w) { // rotate 4-byte word w left by one byte + w[4] = w[0]; + for (var i=0; i<4; i++) w[i] = w[i+1]; + return w; + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + /* + * Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation + * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + * for each block + * - outputblock = cipher(counter, key) + * - cipherblock = plaintext xor outputblock + */ + function AESEncryptCtr(plaintext, password, nBits) { + if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys + + // for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password; + // for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1 + var nBytes = nBits/8; // no bytes in key + var pwBytes = new Array(nBytes); + for (var i=0; i>> i*8) & 0xff; + for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff; + + // generate key schedule - an expansion of the key into distinct Key Rounds for each round + var keySchedule = KeyExpansion(key); + + var blockCount = Math.ceil(plaintext.length/blockSize); + var ciphertext = new Array(blockCount); // ciphertext as array of strings + + for (var b=0; b>> c*8) & 0xff; + for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8) + + var cipherCntr = Cipher(counterBlock, keySchedule); // -- encrypt counter block -- + + // calculate length of final block: + var blockLength = b>> c*8) & 0xff; + for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff; + + var cipherCntr = Cipher(counterBlock, keySchedule); // encrypt counter block + + ciphertext[b] = unescCtrlChars(ciphertext[b]); + + var pt = ''; + for (var i=0; i 63){ + this.dbName = this.dbName.substring(0, 63); + } + } + + if(!dbName){ + dbName = this.dbName; + } + + try{ + this._initDb(); + this.db.open(dbName); + this._dbOpen = true; + }catch(exp){ + throw exp.message||exp; + } + }, + + close: function(dbName){ + // on Internet Explorer, Google Gears throws an exception + // "Object not a collection", when we try to close the + // database -- just don't close it on this platform + // since we are running into a Gears bug; the Gears team + // said it's ok to not close a database connection + if(dojo.isIE){ return; } + + if(!this._dbOpen && (!dbName || dbName == this.dbName)){ + return; + } + + if(!dbName){ + dbName = this.dbName; + } + + try{ + this.db.close(dbName); + this._dbOpen = false; + }catch(exp){ + throw exp.message||exp; + } + }, + + _exec: function(params){ + try{ + // get the Gears Database object + this._initDb(); + + // see if we need to open the db; if programmer + // manually called dojox.sql.open() let them handle + // it; otherwise we open and close automatically on + // each SQL execution + if(!this._dbOpen){ + this.open(); + this._autoClose = true; + } + + // determine our parameters + var sql = null; + var callback = null; + var password = null; + + var args = dojo._toArray(params); + + sql = args.splice(0, 1)[0]; + + // does this SQL statement use the ENCRYPT or DECRYPT + // keywords? if so, extract our callback and crypto + // password + if(this._needsEncrypt(sql) || this._needsDecrypt(sql)){ + callback = args.splice(args.length - 1, 1)[0]; + password = args.splice(args.length - 1, 1)[0]; + } + + // 'args' now just has the SQL parameters + + // print out debug SQL output if the developer wants that + if(this.debug){ + this._printDebugSQL(sql, args); + } + + // handle SQL that needs encryption/decryption differently + // do we have an ENCRYPT SQL statement? if so, handle that first + if(this._needsEncrypt(sql)){ + var crypto = new dojox.sql._SQLCrypto("encrypt", sql, + password, args, + callback); + return; // encrypted results will arrive asynchronously + }else if(this._needsDecrypt(sql)){ // otherwise we have a DECRYPT statement + var crypto = new dojox.sql._SQLCrypto("decrypt", sql, + password, args, + callback); + return; // decrypted results will arrive asynchronously + } + + // execute the SQL and get the results + var rs = this.db.execute(sql, args); + + // Gears ResultSet object's are ugly -- normalize + // these into something JavaScript programmers know + // how to work with, basically an array of + // JavaScript objects where each property name is + // simply the field name for a column of data + rs = this._normalizeResults(rs); + + if(this._autoClose){ + this.close(); + } + + return rs; + }catch(exp){ + exp = exp.message||exp; + + console.debug("SQL Exception: " + exp); + + if(this._autoClose){ + try{ + this.close(); + }catch(e){ + console.debug("Error closing database: " + + e.message||e); + } + } + + throw exp; + } + }, + + _initDb: function(){ + if(!this.db){ + try{ + this.db = google.gears.factory.create('beta.database', '1.0'); + }catch(exp){ + dojo.setObject("google.gears.denied", true); + dojox.off.onFrameworkEvent("coreOperationFailed"); + throw "Google Gears must be allowed to run"; + } + } + }, + + _printDebugSQL: function(sql, args){ + var msg = "dojox.sql(\"" + sql + "\""; + for(var i = 0; i < args.length; i++){ + if(typeof args[i] == "string"){ + msg += ", \"" + args[i] + "\""; + }else{ + msg += ", " + args[i]; + } + } + msg += ")"; + + console.debug(msg); + }, + + _normalizeResults: function(rs){ + var results = []; + if(!rs){ return []; } + + while(rs.isValidRow()){ + var row = {}; + + for(var i = 0; i < rs.fieldCount(); i++){ + var fieldName = rs.fieldName(i); + var fieldValue = rs.field(i); + row[fieldName] = fieldValue; + } + + results.push(row); + + rs.next(); + } + + rs.close(); + + return results; + }, + + _needsEncrypt: function(sql){ + return /encrypt\([^\)]*\)/i.test(sql); + }, + + _needsDecrypt: function(sql){ + return /decrypt\([^\)]*\)/i.test(sql); + } +}); + +// summary: +// A private class encapsulating any cryptography that must be done +// on a SQL statement. We instantiate this class and have it hold +// it's state so that we can potentially have several encryption +// operations happening at the same time by different SQL statements. +dojo.declare("dojox.sql._SQLCrypto", null, { + constructor: function(action, sql, password, args, callback){ + if(action == "encrypt"){ + this._execEncryptSQL(sql, password, args, callback); + }else{ + this._execDecryptSQL(sql, password, args, callback); + } + }, + + _execEncryptSQL: function(sql, password, args, callback){ + // strip the ENCRYPT/DECRYPT keywords from the SQL + var strippedSQL = this._stripCryptoSQL(sql); + + // determine what arguments need encryption + var encryptColumns = this._flagEncryptedArgs(sql, args); + + // asynchronously encrypt each argument that needs it + var self = this; + this._encrypt(strippedSQL, password, args, encryptColumns, function(finalArgs){ + // execute the SQL + var error = false; + var resultSet = []; + var exp = null; + try{ + resultSet = dojox.sql.db.execute(strippedSQL, finalArgs); + }catch(execError){ + error = true; + exp = execError.message||execError; + } + + // was there an error during SQL execution? + if(exp != null){ + if(dojox.sql._autoClose){ + try{ dojox.sql.close(); }catch(e){} + } + + callback(null, true, exp.toString()); + return; + } + + // normalize SQL results into a JavaScript object + // we can work with + resultSet = dojox.sql._normalizeResults(resultSet); + + if(dojox.sql._autoClose){ + dojox.sql.close(); + } + + // are any decryptions necessary on the result set? + if(dojox.sql._needsDecrypt(sql)){ + // determine which of the result set columns needs decryption + var needsDecrypt = self._determineDecryptedColumns(sql); + + // now decrypt columns asynchronously + // decrypt columns that need it + self._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){ + callback(finalResultSet, false, null); + }); + }else{ + callback(resultSet, false, null); + } + }); + }, + + _execDecryptSQL: function(sql, password, args, callback){ + // strip the ENCRYPT/DECRYPT keywords from the SQL + var strippedSQL = this._stripCryptoSQL(sql); + + // determine which columns needs decryption; this either + // returns the value *, which means all result set columns will + // be decrypted, or it will return the column names that need + // decryption set on a hashtable so we can quickly test a given + // column name; the key is the column name that needs + // decryption and the value is 'true' (i.e. needsDecrypt["someColumn"] + // would return 'true' if it needs decryption, and would be 'undefined' + // or false otherwise) + var needsDecrypt = this._determineDecryptedColumns(sql); + + // execute the SQL + var error = false; + var resultSet = []; + var exp = null; + try{ + resultSet = dojox.sql.db.execute(strippedSQL, args); + }catch(execError){ + error = true; + exp = execError.message||execError; + } + + // was there an error during SQL execution? + if(exp != null){ + if(dojox.sql._autoClose){ + try{ dojox.sql.close(); }catch(e){} + } + + callback(resultSet, true, exp.toString()); + return; + } + + // normalize SQL results into a JavaScript object + // we can work with + resultSet = dojox.sql._normalizeResults(resultSet); + + if(dojox.sql._autoClose){ + dojox.sql.close(); + } + + // decrypt columns that need it + this._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){ + callback(finalResultSet, false, null); + }); + }, + + _encrypt: function(sql, password, args, encryptColumns, callback){ + //console.debug("_encrypt, sql="+sql+", password="+password+", encryptColumns="+encryptColumns+", args="+args); + + this._totalCrypto = 0; + this._finishedCrypto = 0; + this._finishedSpawningCrypto = false; + this._finalArgs = args; + + for(var i = 0; i < args.length; i++){ + if(encryptColumns[i]){ + // we have an encrypt() keyword -- get just the value inside + // the encrypt() parantheses -- for now this must be a ? + var sqlParam = args[i]; + var paramIndex = i; + + // update the total number of encryptions we know must be done asynchronously + this._totalCrypto++; + + // FIXME: This currently uses DES as a proof-of-concept since the + // DES code used is quite fast and was easy to work with. Modify dojox.sql + // to be able to specify a different encryption provider through a + // a SQL-like syntax, such as dojox.sql("SET ENCRYPTION BLOWFISH"), + // and modify the dojox.crypto.Blowfish code to be able to work using + // a Google Gears Worker Pool + + // do the actual encryption now, asychronously on a Gears worker thread + dojox._sql._crypto.encrypt(sqlParam, password, dojo.hitch(this, function(results){ + // set the new encrypted value + this._finalArgs[paramIndex] = results; + this._finishedCrypto++; + // are we done with all encryption? + if(this._finishedCrypto >= this._totalCrypto + && this._finishedSpawningCrypto){ + callback(this._finalArgs); + } + })); + } + } + + this._finishedSpawningCrypto = true; + }, + + _decrypt: function(resultSet, needsDecrypt, password, callback){ + //console.debug("decrypt, resultSet="+resultSet+", needsDecrypt="+needsDecrypt+", password="+password); + + this._totalCrypto = 0; + this._finishedCrypto = 0; + this._finishedSpawningCrypto = false; + this._finalResultSet = resultSet; + + for(var i = 0; i < resultSet.length; i++){ + var row = resultSet[i]; + + // go through each of the column names in row, + // seeing if they need decryption + for(var columnName in row){ + if(needsDecrypt == "*" || needsDecrypt[columnName]){ + this._totalCrypto++; + var columnValue = row[columnName]; + + // forming a closure here can cause issues, with values not cleanly + // saved on Firefox/Mac OS X for some of the values above that + // are needed in the callback below; call a subroutine that will form + // a closure inside of itself instead + this._decryptSingleColumn(columnName, columnValue, password, i, + function(finalResultSet){ + callback(finalResultSet); + }); + } + } + } + + this._finishedSpawningCrypto = true; + }, + + _stripCryptoSQL: function(sql){ + // replace all DECRYPT(*) occurrences with a * + sql = sql.replace(/DECRYPT\(\*\)/ig, "*"); + + // match any ENCRYPT(?, ?, ?, etc) occurrences, + // then replace with just the question marks in the + // middle + var matches = sql.match(/ENCRYPT\([^\)]*\)/ig); + if(matches != null){ + for(var i = 0; i < matches.length; i++){ + var encryptStatement = matches[i]; + var encryptValue = encryptStatement.match(/ENCRYPT\(([^\)]*)\)/i)[1]; + sql = sql.replace(encryptStatement, encryptValue); + } + } + + // match any DECRYPT(COL1, COL2, etc) occurrences, + // then replace with just the column names + // in the middle + matches = sql.match(/DECRYPT\([^\)]*\)/ig); + if(matches != null){ + for(var i = 0; i < matches.length; i++){ + var decryptStatement = matches[i]; + var decryptValue = decryptStatement.match(/DECRYPT\(([^\)]*)\)/i)[1]; + sql = sql.replace(decryptStatement, decryptValue); + } + } + + return sql; + }, + + _flagEncryptedArgs: function(sql, args){ + // capture literal strings that have question marks in them, + // and also capture question marks that stand alone + var tester = new RegExp(/([\"][^\"]*\?[^\"]*[\"])|([\'][^\']*\?[^\']*[\'])|(\?)/ig); + var matches; + var currentParam = 0; + var results = []; + while((matches = tester.exec(sql)) != null){ + var currentMatch = RegExp.lastMatch+""; + + // are we a literal string? then ignore it + if(/^[\"\']/.test(currentMatch)){ + continue; + } + + // do we have an encrypt keyword to our left? + var needsEncrypt = false; + if(/ENCRYPT\([^\)]*$/i.test(RegExp.leftContext)){ + needsEncrypt = true; + } + + // set the encrypted flag + results[currentParam] = needsEncrypt; + + currentParam++; + } + + return results; + }, + + _determineDecryptedColumns: function(sql){ + var results = {}; + + if(/DECRYPT\(\*\)/i.test(sql)){ + results = "*"; + }else{ + var tester = /DECRYPT\((?:\s*\w*\s*\,?)*\)/ig; + var matches; + while(matches = tester.exec(sql)){ + var lastMatch = new String(RegExp.lastMatch); + var columnNames = lastMatch.replace(/DECRYPT\(/i, ""); + columnNames = columnNames.replace(/\)/, ""); + columnNames = columnNames.split(/\s*,\s*/); + dojo.forEach(columnNames, function(column){ + if(/\s*\w* AS (\w*)/i.test(column)){ + column = column.match(/\s*\w* AS (\w*)/i)[1]; + } + results[column] = true; + }); + } + } + + return results; + }, + + _decryptSingleColumn: function(columnName, columnValue, password, currentRowIndex, + callback){ + //console.debug("decryptSingleColumn, columnName="+columnName+", columnValue="+columnValue+", currentRowIndex="+currentRowIndex) + dojox._sql._crypto.decrypt(columnValue, password, dojo.hitch(this, function(results){ + // set the new decrypted value + this._finalResultSet[currentRowIndex][columnName] = results; + this._finishedCrypto++; + + // are we done with all encryption? + if(this._finishedCrypto >= this._totalCrypto + && this._finishedSpawningCrypto){ + //console.debug("done with all decrypts"); + callback(this._finalResultSet); + } + })); + } +}); + +} diff --git a/includes/js/dojox/_sql/demos/customers/customers.html b/includes/js/dojox/_sql/demos/customers/customers.html new file mode 100644 index 0000000..a4c0c03 --- /dev/null +++ b/includes/js/dojox/_sql/demos/customers/customers.html @@ -0,0 +1,292 @@ + + + + + + + + + + + + + +

Dojo SQL Cryptography

+ +

Instructions

+ +

This demo shows Dojo Offline's SQL encryption technologies. In the table below, we have a + sample SQL table that has three columns of data: a last name, a first name, and + a social security number. We don't want to store the social security numbers + in the clear, just in case they are downloaded for offline use to a laptop and the + laptop is stolen.

+ +

To use this demo, enter a password and press the ENCRYPT button to see the Social Security column encrypt. Enter + the same password and press DECRYPT to see it decrypt. If you enter an incorrect password and + press DECRYPT, the Social Security column will remain encrypted and only show gibberish.

+ +

Under the covers we use 256-bit AES encryption and your password to derive the crypto key; we use + a facility in Google Gears to do the cryptography in such a way that the browser does not lock up + during processing. Dojo Offline ties this cryptography into Dojo SQL, providing convenient ENCRYPT() + and DECRYPT() SQL keywords you can use to easily have this functionality in your + own offline applications. To learn how you can use this feature + see here.

+ +
+ + + + + + + +
+ +
+ + + + + +
+ + \ No newline at end of file -- cgit v1.2.3