
/*
 * Client Side Encryption (CSE) javascript to work with drupal module.
 *
 * CSE is a drupal module written in php and javascript for implementing
 * Client Side Encryption with the AES algorithm 
 * Copyright (C) 2007  Andrew Gillies <anaru@equivocation.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or, at
 * your option, any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

/* 
 * AES Implementation based on AES doc:
 *    http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
 * with some key code based on the AES implementation of
 *    http://www.movable-type.co.uk/scripts/AES.html
 *
 */

/*
 * EncRij object
 *  
 */

EncRij = function(enctext) {
  this.enctext = enctext;
  this.plaintext = '';
  this.decrypted = false;

  this.Decrypt = function(password) {
    this.plaintext = '';
    this.decrypted = false;

    if (this.enctext) {
      var possibletext = EncRij.AESDecrypt(this.enctext,password);
      if (EncRij.checkValid(possibletext)) {
	this.plaintext = EncRij.removeValidKey(possibletext);
	this.decrypted = true;
      }
    }
  };

  this.Revert = function() {
    this.plaintext = '';
    this.decrypted = false;
  };

};

EncRij.Encrypt = function(plaintext,password) {
  if (this.enctext) this.enctext = EncRij.AESencrypt(plaintext,password);
  return this.enctext;
};
    

/* AES Cipher function: encrypt input with Rijndael algorithm using key
 *   input   byte-array 'input' (16 bytes)
 *   key     byte-array 'key' (16/24/32 bytes)
 *   w       2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
 *   returns byte-array encrypted value (16 bytes)
 */

EncRij.Cipher = function(input, key, w) {
  var Nk = key.length/4; // key length (in words)
  var Nr = Nk + 6;       // no of rounds
  var Nb = 4;            // block size: no of columns in state (fixed at 4 for AES)

  var state = [[],[],[],[]];  // initialise the state (a 4xNb byte-array) with input
  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];

  state = EncRij.AddRoundKey(state, w, 0, Nb);

  for (var round=1; round<Nr; round++) {
    state = EncRij.SubBytes(state, Nb);
    state = EncRij.ShiftRows(state, Nb);
    state = EncRij.MixColumns(state, Nb);
    state = EncRij.AddRoundKey(state, w, round, Nb);
  }

  state = EncRij.SubBytes(state, Nb);
  state = EncRij.ShiftRows(state, Nb);
  state = EncRij.AddRoundKey(state, w, Nr, Nb);

  var output = new Array(4*Nb);  // convert to 1-d array before returning
  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
  return output;
};

/* AES InvCipher function: decrypt input with Rijndael algorithm using key
 *   input   byte-array 'input' (16 bytes)
 *   key     byte-array 'key' (16/24/32 bytes)
 *   w       2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
 *   returns byte-array encrypted value (16 bytes)
 */

EncRij.InvCipher = function(input, key, w) {
  var Nk = key.length/4  // key length (in words)
  var Nr = Nk + 6;       // no of rounds
  var Nb = 4;            // block size: no of columns in state (fixed at 4 for AES)

  var state = [[],[],[],[]];  // initialise the state (a 4xNb byte-array) with input
  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];

  state = EncRij.AddRoundKey(state, w, Nr, Nb);

  for (var round=(Nr-1); round>0; round--) {
    state = EncRij.InvShiftRows(state, Nb);
    state = EncRij.InvSubBytes(state, Nb);
    state = EncRij.AddRoundKey(state, w, round, Nb);
    state = EncRij.InvMixColumns(state, Nb);
  }

  state = EncRij.InvShiftRows(state, Nb);
  state = EncRij.InvSubBytes(state, Nb);

  state = EncRij.AddRoundKey(state, w, 0, Nb);

  var output = new Array(4*Nb);  // convert to 1-d array before returning
  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
  return output;
};

EncRij.SubBytes = function(s, Nb) {    // apply SBox to state 
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) s[r][c] = EncRij.Sbox[s[r][c]];
  }
  return s;
};

EncRij.InvSubBytes = function(s, Nb) {    // apply InvSBox to state 
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) s[r][c] = EncRij.InvSbox[s[r][c]];
  }
  return s;
};

EncRij.ShiftRows = function(s, Nb) {    // shift row r of state S left by r bytes 
  var t = new Array(4);
  for (var r=1; r<4; r++) {
    for (var c=0; c<Nb; c++) t[c] = s[r][(c+r)%Nb];  // shift into temp copy
    for (var c=0; c<Nb; c++) s[r][c] = t[c];         // and copy back
  }
  return s;
};

EncRij.InvShiftRows = function(s, Nb) {   // inverse shift row r of state S by r bytes 
  var t = new Array(4);
  for (var r=1; r<4; r++) {
    for (var c=0; c<Nb; c++) t[c] = s[r][(c+(Nb-r))%Nb];  // shift into temp copy
    for (var c=0; c<Nb; c++) s[r][c] = t[c];              // and copy back
  } 
  return s;
};

EncRij.MixColumns = function(s, Nb) {   // combine bytes of each col of state S
  for (var c=0; c<Nb; c++) {
    var a = new Array(4);      // 'a' is a copy of the current column from 's'
    var b = new Array(4);      // 'b' is x02 times s in GF(2^8) (see xtimes in AES doc Sec 4.2.1)
    for (var i=0; i<4; i++) {
      a[i] = s[i][c];
      b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
    }
    s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
    s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
    s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
    s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
  }
  return s;
};

EncRij.InvMixColumns = function(s, Nb) {   // uncombine bytes of each col of state S
  for (var c=0; c<Nb; c++) {
    var a = new Array(4);  // 'a' is a copy of the current column from 's'
    var b = new Array(4);  // 'b' is x02 times s in GF(2^8) (see xtimes in AES doc Sec 4.2.1)
    var d = new Array(4);  // 'd' is x04 times s in GF(2^8)
    var e = new Array(4);  // 'e' is x08 times s in GF(2^8)
    for (var i=0; i<4; i++) {
      a[i] = s[i][c];
      b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
      d[i] =    b[i]&0x80 ? b[i]<<1 ^ 0x011b : b[i]<<1;
      e[i] =    d[i]&0x80 ? d[i]<<1 ^ 0x011b : d[i]<<1;
    }
    s[0][c] = e[0] ^ d[0] ^ b[0] ^ e[1] ^ b[1] ^ a[1] ^ e[2] ^ d[2] ^ a[2] ^ e[3] ^ a[3]; // e*a0 + b*a1 + d*a2 + 9*a3
    s[1][c] = e[0] ^ a[0] ^ e[1] ^ d[1] ^ b[1] ^ e[2] ^ b[2] ^ a[2] ^ e[3] ^ d[3] ^ a[3]; // 9*a0 * e*a1 + b*a2 + d*a3
    s[2][c] = e[0] ^ d[0] ^ a[0] ^ e[1] ^ a[1] ^ e[2] ^ d[2] ^ b[2] ^ e[3] ^ b[3] ^ a[3]; // d*a0 + 9*a1 + e*a2 + b*a3
    s[3][c] = e[0] ^ b[0] ^ a[0] ^ e[1] ^ d[1] ^ a[1] ^ e[2] ^ a[2] ^ e[3] ^ d[3] ^ b[3]; // b*a0 + d*a1 + 9*a2 + e*a3
  }
  return s;
};

EncRij.AddRoundKey = function(state, w, rnd, Nb) {  // xor Round Key into state S 
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
  }
  return state;
};

EncRij.KeyExpansion = function(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key
  var Nk = key.length/4       // key length (in words)
  var Nr = Nk + 6;            // no of rounds
  var Nb = 4;                 // block size: no of columns in state (fixed at 4 for AES)

  var w = new Array(Nb*(Nr+1));
  var temp = new Array(4);

  for (var i=0; i<Nk; i++) {
    var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
    w[i] = r;
  }

  for (var i=Nk; i<(Nb*(Nr+1)); i++) {
    w[i] = new Array(4);
    for (var t=0; t<4; t++) temp[t] = w[i-1][t];
    if (i % Nk == 0) {
      temp = EncRij.SubWord(EncRij.RotWord(temp));
      for (var t=0; t<4; t++) temp[t] ^= EncRij.Rcon[i/Nk][t];
    } else if (Nk > 6 && i%Nk == 4) {
      temp = EncRij.SubWord(temp);
    }
    for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
  }

  return w;
};

EncRij.SubWord = function(w) {    // apply SBox to 4-byte word w
  for (var i=0; i<4; i++) w[i] = EncRij.Sbox[w[i]];
  return w;
};

EncRij.RotWord = function(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;
};

// Sbox is pre-computed multiplicative inverse in GF(2^8)
EncRij.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];

// InvSbox is pre-computed inverse of Sbox in GF(2^8)
EncRij.InvSbox = [0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
                  0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
                  0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
                  0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
                  0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
                  0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
                  0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
                  0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
                  0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
                  0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
                  0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
                  0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
                  0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
                  0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
                  0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
                  0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d];

// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)]
EncRij.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] ]; 

/*
 * AESEncrypt - encrypt plaintext with password using 128-bit key
 *
 */

EncRij.AESEncrypt = function(plaintext, password) {
  version = 2;

  // ensure plaintext only contains 8-bit characters: use 'escape' to convert anything outside 
  // ISO-8859-1, but keep spaces as spaces not '%20' to restrict bloat

  plaintext = escape(plaintext).replace(/%20/g,' ');

  // generate the key by cycling the password - this relies on a strong password (hmmm)

  var key = new Array(16);
  var j=0;
  for (var i=0; i<16; i++) {
    if (j==password.length) j=0;
    key[i] = password.charCodeAt(j);
    j++;
  }

  var nonceByte = ((new Date()).getTime()) & 0xff;   // first byte of milliseconds since 1-Jan-1970
  var nonceText = EncRij.escCtrlChars(String.fromCharCode(nonceByte));

  var keySchedule = EncRij.KeyExpansion(key);

  var blockCount = Math.ceil(plaintext.length/16);
  var textBlock  = new Array(16);
  var cipherText = new Array(blockCount); // cipherText as array of strings

  // all but last block ...
  for (var b=0; b<(blockCount-1); b++) {
    for (var i=0; i<16; i++) textBlock[i] = plaintext.charCodeAt(b*16+i);
    
    // apply the nonce
    textBlock[0] = textBlock[0] ^ nonceByte;

    var cipherBlock = EncRij.Cipher(textBlock, key, keySchedule);  // encrypt text block 

    var ct = '';
    for (var i=0; i<16; i++) ct += String.fromCharCode(cipherBlock[i]);

    cipherText[b] = EncRij.escCtrlChars(ct);
  } 

  // last block ...
  var blockLength = (plaintext.length-1)%16+1;
  for (var i=0; i<16; i++) textBlock[i] = 0;
  for (var i=0; i<blockLength; i++) textBlock[i] = plaintext.charCodeAt((blockCount-1)*16+i);
    
  // apply the nonce
  textBlock[0] = textBlock[0] ^ nonceByte;

  var cipherBlock = EncRij.Cipher(textBlock, key, keySchedule);  // encrypt text block 

  var ct = '';
  for (var i=0; i<16; i++) ct += String.fromCharCode(cipherBlock[i]);

  cipherText[(blockCount-1)] = EncRij.escCtrlChars(ct);
  
  // use '+' to separate blocks, since encrypted blocks are of variable size after escaping ctrl chars
  // add version information so migration to changes in the encryption algorithm is possible

  return '-'+version+'-'+EncRij.str2hex(nonceText + '+' + cipherText.join('+'),true);  
};


/*
 * AESDecrypt - decrypt hexciphertext with password using 128-bit key
 *
 */

EncRij.AESDecrypt = function(hexciphertext, password) {
  var version = 2;

  if (hexciphertext.indexOf('-')==0) { // pull off version information
    var realstart = (hexciphertext.substring(1,hexciphertext.length)).indexOf('-') +1;
    version = parseInt(hexciphertext.substring(1,realstart));
    hexciphertext = hexciphertext.substring(realstart+1,hexciphertext.length);
  }

  var cipherText = EncRij.hex2str(hexciphertext);

  if (version<=2) {
    var key = new Array(16);
    var j=0;
    for (var i=0; i<16; i++) {
      if (j==password.length) j=0;
      key[i] = password.charCodeAt(j);
      j++;
    }
  }

  var keySchedule = EncRij.KeyExpansion(key);

  cipherText = cipherText.split('+');  // split cipherText into array of block-length strings (removing first which is nonce)

  // recover nonce from 1st element of cipherText
  var nonceText = EncRij.unescCtrlChars(cipherText[0]);
  var nonceByte = nonceText.charCodeAt(0);

  var blockCount  = cipherText.length-1;
  var plainText   = new Array(blockCount); // plainText as array of strings
  var cipherBlock = new Array(16);

  for (var b=0; b<blockCount; b++) {

    cipherText[b+1] = EncRij.unescCtrlChars(cipherText[b+1]);
    for (var i=0; i<16; i++) cipherBlock[i] = cipherText[b+1].charCodeAt(i);
    var textBlock = EncRij.InvCipher(cipherBlock, key, keySchedule);  // decrypt cipher block 

    // apply the nonce
    textBlock[0] = textBlock[0] ^ nonceByte;

    var pt = '';
    if (b==(blockCount-1)) for (var i=0; i<cipherText[b+1].length; i++) {if (textBlock[i]>0) pt += String.fromCharCode(textBlock[i]);}
    else for (var i=0; i<cipherText[b+1].length; i++) pt += String.fromCharCode(textBlock[i])

    plainText[b] = pt;    
  } 

  return unescape(plainText.join(''));
};

// escape control chars which might cause problems handling ciphertext
EncRij.escCtrlChars = function(str) {  
  return str.replace(/[\0\n\v\f\r\xa0+!]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
}; // \xa0 to cater for bug in Firefox; include '+' to leave it free for use as a block marker


// unescape potentially problematic control characters
EncRij.unescCtrlChars = function(str) {  
  return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
};

// convert byte array to hex string for displaying test vectors
EncRij.byteArrayToHexStr = function(b) {  
  var s = '';
  for (var i=0; i<b.length; i++) s += b[i].toString(16) + ' ';
  return s;
};

EncRij.dec2hex = function(d) {
  var hD="0123456789ABCDEF";
  var h = hD.substr(d&15,1);
  while(d>15) {
    d>>=4;
    h=hD.substr(d&15,1)+h;
  }
  return h;
};

EncRij.hex2dec = function(h) {
  return parseInt(h,16);
};

EncRij.str2hex = function(s,nice) {
  if (s.length == 0) return('');
  var h = new Array(s.length);
  for (var i=0; i<s.length; i++) {
    h[i] = EncRij.dec2hex(s.charCodeAt(i));
  }
  var hex = h.join('-');
  if (nice) {
    var n="";
    var c=0;
    // neaten the string up for the browser
    for (var i=0; i<hex.length; i++) {
      n+=hex.charAt(i);
      if (hex.charAt(i)=='-') {
        c++;
        if (c>26) {
          c=0;
          n+=' ';
        }
      }
    }
  } else {
    n=hex;
  }
  return n;
};

EncRij.hex2str = function(w) {
  var ehD="0123456789ABCDEF";
  if (!w) return('');
  if (w.length == 0) return('');
  // first remove non "HEX" characters (in case formatting happened?):
  var h='';
  var s='';
  for (var j=0; j<w.length; j++) {
    if (w.charAt(j)==' ') continue;
    if (w.charAt(j)=='-') {
      s+=String.fromCharCode(EncRij.hex2dec(h));
      h='';
      continue;
    }
    if (ehD.indexOf(w.charAt(j))>=0) h+=w.charAt(j);
  }
  s+=String.fromCharCode(EncRij.hex2dec(h));
  return s;
};

// return true if valid hex (with white-space etc)
EncRij.ishex = function(w) { 
  var valid=false;
  if (w) {
    if (w.length>0) {
      var ehD="0123456789ABCDEF- \n";  
      valid=true;
      for (var j=0; j<w.length; j++) {
        if (ehD.indexOf(w.charAt(j))<0) {valid=false;break;}
      }
    }
  }
  return valid;
};

EncRij.addValidKey = function(pretext) {
  return "ENCRIJval"+pretext;
};

EncRij.checkValid = function(posttext) {
  return (posttext.indexOf('ENCRIJval')==0);
};

EncRij.removeValidKey = function(posttext) {
  if (EncRij.checkValid(posttext)) {
    var pretext = posttext.substring(9,posttext.length);
  } else {
    var pretext = posttext;
  }
  return pretext;
};

/*
 * CSE object
 *
 */

CSE = {};

// object globals

CSE.Count      = 0;
CSE.Buffer     = new Array();
CSE.NiceText   = '[encrypted]';
CSE.PreNice    = CSE.NiceText.substring(0,CSE.NiceText.length-1);
CSE.PostNice   = CSE.NiceText.substring(CSE.NiceText.length-1,CSE.NiceText.length);
CSE.Colour     = '#b94';
CSE.cseClass   = 'encrijspn';
CSE.cseTag     = 'code';
CSE.PreTag     = '<'+CSE.cseTag;
CSE.PostTag    = '</'+CSE.cseTag+'>';
CSE.FormView   = false;
CSE.UseCookie  = true;
CSE.Ident      = 'cse';
CSE.CCODE      = '';
CSE.AllDecrypt = false;
CSE.ClickCount = 0;
CSE.CaretPos   = -1;
CSE.ScrollPos  = -1;
CSE.CaretIEadj = 0;  // hack for IE
CSE.statusOK   = true;

CSE.setStatus = function(text,level) {
  if (CSE.statusOK) {
    var status = document.getElementById('encrij-statusbar');  
    if (status) {
       status.childNodes[0].nodeValue = text;
    } else {
      var tags = document.getElementsByTagName('label');
      CSE.statusOK = false;
      for (var i=0; stag = tags[i]; i++) {
	if ((stag.className.length==0)&&(stag.childNodes.length==1)) {
	  if (stag.childNodes[0]&&(stag.childNodes[0].nodeValue.indexOf('Actions/Status:')==0)) {
	    status = document.createElement('span');
            status.setAttribute('id', 'encrij-statusbar');
            status.appendChild(document.createTextNode(text));
	    stag.style.display = 'inline';
            stag.parentNode.appendChild(status);
            CSE.statusOK = true;
	    break;
	  }
	}
      }
      if (!CSE.statusOK) return;
    }
    if (level==1) status.style.color = '#900';
    else status.style.color = '#090';
  }
};

CSE.setencCookie = function(name,code,value,mins) {
  if (mins) {
    var date = new Date();
    date.setTime(date.getTime()+(mins*60*1000));
    var expires = "; expires="+date.toGMTString();
  } else var expires = "";

  // only set the cookie if there is a valid code
  if ((code.length<1)||(!CSE.UseCookie)) {
    document.cookie = name+"="+expires+"; path=/";
  } else {
    var cookiepass = name+code;
    var encval = EncRij.AESEncrypt(EncRij.addValidKey(value),cookiepass);
    document.cookie = name+"="+encval+expires+"; path=/";
  }
};

CSE.getencCookie = function(name,code) {
  // only get the cookie if there is a valid code
  if ((code.length<1)||(!CSE.UseCookie)) return null;

  var result = null;
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  var encval = null;
  for(var i=0;i < ca.length;i++) {
    var c = ca[i];
    while (c.charAt(0)==' ') c = c.substring(1,c.length);
    if (c.indexOf(nameEQ) == 0) {
      encval=c.substring(nameEQ.length,c.length);
      break;
    }
  }
  // check validity of cookie
  if (encval) {
    var cookiepass = name+code;
    var pwtext = EncRij.AESDecrypt(encval,cookiepass);
    if (EncRij.checkValid(pwtext)) result = EncRij.removeValidKey(pwtext);
  }
  return result;
};

CSE.getCookie = function(name,exact) {
  var nameEQ = name;
  if (exact) nameEQ += '=';
  var ca = document.cookie.split(';');
  var co = null;
  for(var i=0;i < ca.length;i++) {
    var c = ca[i];
    while (c.charAt(0)==' ') c = c.substring(1,c.length);
    if (c.indexOf(nameEQ) == 0) {
      co=c.substring(c.indexOf('=')+1,c.length);
      if (exact) break;
      else continue;
    }
  }
  return co;
};

CSE.getCookieCode = function() {
  if (CSE.CCODE.length==0) { // only do this once!
    // part of this SHOULD come from the server and part of the code comes from the session id:
    var sesscookie=CSE.getCookie("PHPSESSID",true);
    if ((sesscookie)&&(sesscookie.length>6)) CSE.CCODE=sesscookie.substring(0,6);
    else {
      // that didn't work, the "new" drupal cookie method:
      sesscookie=CSE.getCookie("SESS",false);
      if ((sesscookie)&&(sesscookie.length>6)) CSE.CCODE=sesscookie.substring(0,6);
      else {
        // fallback
        CSE.CCODE=window.location.href.substring(7,13);
      }
    }
  }
  return CSE.CCODE;
};

CSE.getDisplayStyle = function(classname) { // return a valid display style based on the class name
  // to be here the classname is at least 8 chars long (CSE.cseClass)
  if (classname.length>7) {
    if (classname.indexOf('block')==(classname.length-5)) return "block";
    if (classname.indexOf('none')==(classname.length-4)) return "none";
    if (classname.indexOf('inline')==(classname.length-6)) return "inline";
    if (classname.indexOf('list-item')==(classname.length-9)) return "list-item";
    if (classname.indexOf('run-in')==(classname.length-6)) return "run-in";
    if (classname.indexOf('compact')==(classname.length-7)) return "compact";
  } 
  return "inline";
};

CSE.catchEnterKey = function(e) { // catch enter key in the password field
  var key;
  if (window.event) key = window.event.keyCode; //IE
  else key = e.which;                           //Standard (firefox,Opera etc)
  if (key == 13) {
    var password = document.getElementById("encrij-pw");
    CSE.decryptTags(password.value,false);
    password.value = '';
  }
};

/*
 * getCaretPos
 *   ta [DOM textarea object] = the textarea object
 *
 *   determine the position of the Caret in the text area.  Note this is a bit of a HACK
 *   and is browser specific.
 * returns [int] position in the textarea
 */

CSE.getCaretPos = function(ta) {
  var pos = -1;
  if(ta.selectionStart) {
    // Go the Gecko way -
    var start = ta.selectionStart;
    var end   = ta.selectionEnd;
    if (start==end) {
      pos=start;
    } else {
      // oh, there is a selection as start!=end - perhaps use start?
      pos=start;
    }
  } else if (document.selection) { 
    // Go the IE way (messy) 
    ta.focus();                                    // first focus on the object we want 
    var range = document.selection.createRange();  // create a range, and assume the focus means ta!
    var marker = String.fromCharCode(01);          // a non-type-able marker
    var orig = ta.value;                           // take an original copy of the text
    range.text = marker;                           // insert our marker
    var iecopy = ta.value;                         // take a copy for IE of the marker inserted text
    ta.value = orig;                               // replace the text
    var actual = iecopy.replace(/\r\n/g, '\n');    // windows two char line terminators count as one!
    pos = iecopy.search( marker );                 // locate our marker for text (as per normal)
    CSE.CaretIEadj = actual.search( marker )-pos;   // locate our marker for IE caret (yuk)
    // put the caret back, IE shoots it to the start when things change!
    var rerange = ta.createTextRange();
    rerange.move('character',pos+CSE.CaretIEadj);   // position caret back where it was!
    rerange.select();
  } else { 
    // Fallback for any other browser - append to end of textarea
  }   
  return pos;
};

/*
 * insertAtPos
 *  ta [object]  = textarea object to insert into 
 *  pos [int]    = position of caret in textarea 
 *  scroll [int] = position of scrollbar in textarea
 *
 *   Encrypts the text in the sandbox and inserts it into the textarea at the given position.
 *   If the position is -1, i.e. one couldn't be determined, it appends only.
 *   Note this carries out the nice text stuff, but adding the real encrypted hex to the 
 *   global buffers!
 *
 *   If the sandbox is empty, and the double click occurs WITHIN & INCLUDING an encrij tag, 
 *   decrypt it using the pass key if you can!
 *
 *   If the sandbox is not empty and a double click occurs WITHIN an encrij tag replace the 
 *   tag and contents with whats in the sand box!
 * returns: nothing
 */

CSE.insertAtPos = function(ta,pos,scroll) { 
  var replaced = '';
  var password = document.getElementById("encrij-editpw");
  var pwverify = document.getElementById("encrij-editpwver");
  if ((!password)||(!pwverify)) return;
  // continue if verify not set, or if it is it equals password and password is >2...
  if (((pwverify.value.length==0)||(pwverify.value==password.value))&&(password.value.length>2)) {
    var plaintext = document.getElementById("encrij-plntxt");
    if ((plaintext)) {
      // where are all the other encrij tags!   
      var list = CSE.listEncrijTextTagPoints(ta.value,CSE.PreTag,CSE.PostTag,CSE.cseClass);
      var pre  = pos;
      var post = pos;
      var oldattrs = '';
      if (plaintext.value.length>0) {    // function try to insert the text into the ta!
        CSE.setStatus("added new encrypted text",0);
        if (list.length>0) {
          // there are already some encrij tags - neatly replace if we are on one of them
          for (var i = 0; i<list.length ; i+=5) {
            if ((pos>=list[i+1])&&(pos<list[i+3])) {  //  inner space - means replace!
              // empty the sandbox into this encrij tag!
              pre  = list[i];
              post = list[i+4];
              oldattrs = ta.value.substring(list[i]+1,list[i+1]-1);  // save the old attributes for replacement
	      if (list[i+2]>0) CSE.setStatus("replaced encrypted text item "+list[i+2]+" with new encrypted text",1);
	      else CSE.setStatus("replaced encrypted text item (raw hex) with new encrypted text",1);
              break;
            }
            if ((pos>=list[i])&&(pos<list[i+4])) {   //  outer space - shift!
              pre  = list[i+4];
              post = list[i+4];
	      if (list[i+2]>0) CSE.setStatus("added new encrypted text immediately after encrypted text item "+list[i+2],0);
              break;
            }
          }
        }
        // add encrypted text to buffer
        var rijraw = document.getElementById('encrij-format');
        var text = CSE.PreTag+' class="'+CSE.cseClass+'">';
        if (oldattrs.length>0) {  //some old attributes to be reincluded
          if ((oldattrs.indexOf(CSE.cseTag)>=0)&&(oldattrs.indexOf(CSE.cseClass))) {  
             // sanity check, our tag and class must be in the old attributes before we override
            text = '<'+oldattrs+'>';
          }
        }
        var raw  = EncRij.AESEncrypt(EncRij.addValidKey(plaintext.value),password.value);
        if (rijraw.checked) text += raw + CSE.PostTag;
        else {
          CSE.Buffer[CSE.Count] = raw;
          CSE.Count++;
          text += CSE.PreNice + CSE.Count + CSE.PostNice + CSE.PostTag;
        }
        if (pos>=0) {
          ta.focus();
          ta.value = ta.value.substring(0,pre) + text + ta.value.substring(post,ta.value.length);
          if (ta.selectionStart) { // Gecko
             ta.setSelectionRange(pre+text.length,pre+text.length);  // position caret at end of insertion
          } else if (ta.createTextRange) { //IE
            var range = ta.createTextRange();
            range.move('character', pre+text.length+CSE.CaretIEadj); // position caret at end of insertion
            range.select();
          }
          // put ta scroll back to where it was
          ta.scrollTop = scroll;
        } else ta.value+=text;            // oh we couldn't determine the caret position, so add to the end

        var code = CSE.getCookieCode();   // create and submit a cookie (if not already set)
        if (!CSE.getencCookie("genencrij",code)) CSE.setencCookie("genencrij",code,password.value,60);
      } else {
        // the sandbox is empty so if we are on a encrij tag see if we can decrypt it!
        if (list.length>0) {
          var text="";
          // there are some encrij tags - are we on one?
          for (var i = 0; i<list.length ; i+=5) {
            if ((pos>=list[i])&&(pos<list[i+4])) {
              // yes, can we decrypt it?
              if (list[i+2]>0) text = CSE.Buffer[(list[i+2]-1)];       // its in the buffer
              else text =  ta.value.substring(list[i+1],list[i+3]);    // use the raw inline hex that we found
              var decontent = EncRij.AESDecrypt(text, password.value);
              if (EncRij.checkValid(decontent)) {
		plaintext.value = EncRij.removeValidKey(decontent);
		if (list[i+2]>0) CSE.setStatus("decrypted encrypted text item "+list[i+2]+" into sandbox",0);
		else CSE.setStatus("decrypted raw encrypted text item into sandbox",0);
	      } else {
		if (list[i+2]>=0) CSE.setStatus("failed to decrypt text item "+list[i+2],1);
		else CSE.setStatus("encrypted text is malformed",1);
	      }
              // move caret in ta
              if (ta.selectionStart) { // Gecko
                ta.setSelectionRange(list[i+4],list[i+4]);       // position caret at end of clicked encrij
              } else if (ta.createTextRange) { //IE
                var range = ta.createTextRange();
                range.move('character', list[i+4]+CSE.CaretIEadj);  // position caret at end of clicked encrij
                range.select();
              }
              break;
            }
          }
        }
      }
    }
  } else {
    if (pwverify.value.length>0) {
      // password and verify differ - notify this with a colour change
      pwverify.style.color = '#f00';
      CSE.setStatus("password and verification differ",1);
    }
  }
};

/*
 * quickChange
 *   first thing to get called when the page loads, so quickly go through our tags
 *   and replace the hex, and buffer it.  This is done without trying to decrypt anything, 
 *   as that takes time and would slow the page load making it look messy.
 * returns: the number of valid encrij tags found
 */

CSE.quickChange = function() {
  var count = 0;
  var tags = document.getElementsByTagName(CSE.cseTag);
  for (var i = 0; tag = tags[i]; i++) {
    if (tag) {
      if (tag.className.indexOf(CSE.cseClass)>=0) {
        if (tag.childNodes[0]) {
          var hexenc = tag.childNodes[0].nodeValue;
          if (hexenc) {
            tag.encrij = new EncRij(hexenc);
            var titletext = tag.getAttribute('title');
            if (titletext&&(titletext.length>2)) tag.childNodes[0].nodeValue = titletext;
            else tag.childNodes[0].nodeValue = CSE.NiceText;
            tag.style.color=CSE.Colour; 
            tag.style.display=CSE.getDisplayStyle(tag.className);
            count++;
	  }
        }
      }
    }
  }
  CSE.Count = count;
  return count;
};

/*
 * listEncrijTextTagPoints
 *   text  [string] = may contain encrij tags
 *
 *   look for encrij tags, and return an array with 5 elements for each tag found - the start 
 *   outer and inner, reference (or 0 for valid hex, -1 for malformed ref or hex), end inner and outer.
 *   The start and end indicate the tag content only including (outer) and excluding (inner) the tags themselves.
 */

CSE.listEncrijTextTagPoints = function(text,tstart,tend,tclass) {
  result = new Array();

  if (text.length>0) {
    // tag classes to be transformed must contain the word in tclass
    var count  = 0;
    var tagend, classid, classend;
    var tagref, tagis, tagos, tagie, tagoe; // tag ref, inner start, outer start, inner end, outer end
    
    // split the reference marker to get the encrij tag reference
    tagos = text.indexOf(tstart,0);
    while (tagos>=0) {
      tagref = -1; tagis = -1; tagie = -1; tagoe = -1;
      tagend=text.indexOf('>',tagos);

      // does this tag declaration contain the keyword?
      if ((text.substring(tagos,tagend)).indexOf(tclass)>=0) {
        classid=text.indexOf('class="',tagos);
        classend=text.indexOf('"',classid+8);
        // does the class contain the keyword?
        if ((text.substring(classid,classend)).indexOf(tclass)>=0) {
          // this is an encrij tag, the text starts after next ">" (tagend+1)
          tagis = tagend+1;
          tagie = text.indexOf(tend,tagis);
          tagoe = tagie+tend.length;
          var contents = text.substring(tagis,tagie);
          // is this HEX or "nice" reference?
          if (contents.indexOf(CSE.PreNice)==0) {
            // we have a reference, note must be >0 (0 is used to indicate valid hex)
            tagref = parseInt(contents.substring(CSE.PreNice.length,contents.indexOf(CSE.PostNice)));
            if ((tagref<=0)||(tagref>CSE.Count)) tagref = -1;
          } else {
            // it must be hex, double check, remember a zero tagref means valid hex (-1=malformed, and the tag is removed!)
            if (EncRij.ishex(contents)) tagref=0;
          }
          // so here we have all out points for the array
          result[(5*count)]   = tagos;   // pos of tag outer start  
          result[(5*count)+1] = tagis;   // pos of tag inner start (beginning of content)
          result[(5*count)+2] = tagref;  // tag ref (this is the nth see so far)
          result[(5*count)+3] = tagie;   // pos of tag inner end (end of content)
          result[(5*count)+4] = tagoe;   // pos of tag outer end
          count++;
        }
      }
      // look for the next tag...
      tagos = text.indexOf(tstart,tagos+1);
    }
  }
  return result;
};

/*
 * formatText
 *  ta [object]    = textarea object containing the text to make/unmake nice by removing the hex
 *  nice [boolean] = make nice (put hex to buffers) or make hex (pull hex from buffers)
 */

CSE.formatText = function(ta,nice) {
  var newtext = '';
  var base = 0, ref = 0, count = 0;
  var setglobal = false;
  var malformed = false;
  var text = ta.value;

  if (text.length>0) {
    var list = CSE.listEncrijTextTagPoints(text,CSE.PreTag,CSE.PostTag,CSE.cseClass);
    if (list.length>0) {
      // so there were some encrij tags - what do we do with them?
      for (var i = 0; i<list.length ; i+=5) {
        newtext+=text.substring(base,list[i]);
        ref = list[i+2];
        if (ref>=0) {
          // the content was well formed, so include this tag
          newtext+=text.substring(list[i],list[i+1]);
          if (nice) {
            var content = text.substring(list[i+1],list[i+3]);
            // we are playing nice, so the hex text here should go to the buffer
            if (ref==0) {
              CSE.Buffer[count] = content;  // it is hex
              setglobal = true;
            }
            // either it was hex (so its now buffered) or it was a index (ref>0), so already buffered
            //  so we just put the nice text in 
            newtext+=CSE.PreNice+(count+1)+CSE.PostNice;
          } else {
            // revert from buffers!
            if (ref>0) newtext+=CSE.Buffer[ref-1];              // it is a valid ref
            else newtext+=text.substring(list[i+1],list[i+3]); // if ref was 0 it is already hex, so leave it in!
          }
          newtext+=text.substring(list[i+3],list[i+4]);
          count++;
        } else {
	  // content is not well formed - render in raw view and notify?
	  malformed = true;
	  newtext+=text.substring(list[i],list[i+1]);
	  newtext+=text.substring(list[i+1],list[i+3]);
	  newtext+=text.substring(list[i+3],list[i+4]);
	  CSE.setStatus("found malformed encrypted text (shown as raw hex)",1);
	}
        base=list[i+4];
      }
    }
  }
  newtext+=text.substring(base,text.length);
  if (setglobal) CSE.Count = count;
  if ((!malformed)&&(nice)) CSE.setStatus("formatted encrypted text",0);

  return newtext;
};

/*
 * decryptTags
 *  password [string]    = the password to use for decryption
 *  fromcookie [boolean] = indicates if we were called as the page was loaded!
 *    Goes through all encrij tags in the document and decrypts encrij ones it can using this password.
 */

CSE.decryptTags = function(password,fromcookie) {
  var allvalid = true;
  var anyvalid = false;
  var count = 0;

  var tags = document.getElementsByTagName(CSE.cseTag);
  for (var i = 0; tag = tags[i]; i++) {
    if (tag) {
      if (tag.className.indexOf(CSE.cseClass)>=0) {
        if (tag.childNodes[0]) {
          var content = tag.childNodes[0].nodeValue;
          if (content&&tag.encrij) {
	    if (!tag.encrij.decrypted) { // we have not decrypted this one yet
	      // can we decrypt it now and only bother if we have a password!
	      if (password) tag.encrij.Decrypt(password);
	      if (tag.encrij.decrypted) {
		tag.childNodes[0].nodeValue = tag.encrij.plaintext;
		tag.style.display = CSE.getDisplayStyle(tag.className);  // display class?
		tag.style.color = '';                                // pick up the default colour?
		anyvalid = true;
	      } else allvalid = false;
	      // if its not valid, it could be because the password was wrong, OR
	      // its already decrypted in the tag, not all tags on the page may share the same password!
	    } 
	    count++;
	  }
        }
      }
    }
  }

  // Note, if count=0 allvalid is true, so we authorise the page (as there is nothing to hide)!

  if (allvalid) {
    if (count<1) {
      // no encrij tags on this page so remove whole cse form
      var encform = document.getElementById('encform');
      if (encform) encform.style.display = "none";
    } else {
      // at least one encrij tag on this page so remove cse key entry
      var enckey = document.getElementById('encrij-keyentry');
      if (enckey) enckey.style.display = "none";
      CSE.AllDecrypt = true;
    }
  }
  // set Cookie for next time if this wasn't auto-called using the cookie
  if (anyvalid&&(!fromcookie)&&(count>0)) CSE.setencCookie("encrij",CSE.getCookieCode(),password,60);
};


/*
 * revertTags
 *    Goes through all encrij tags in the document reverts to encrypted form
 *    Deletes the cookies (if any)
 */

CSE.revertTags = function() {

  // first clean up any selection caused by the double click
  if(document.selection && document.selection.empty){
    document.selection.empty() ;
  } else if(window.getSelection) {
    var sel=window.getSelection();
    if(sel && sel.removeAllRanges) sel.removeAllRanges() ;
  }

  // revert the encrypted text
  var tags = document.getElementsByTagName(CSE.cseTag);
  for (var i = 0; tag = tags[i]; i++) {
    if (tag) {
      if (tag.className.indexOf(CSE.cseClass)>=0) {
        if (tag.childNodes[0]) {
          var content = tag.childNodes[0].nodeValue;
          if (content) {
	    tag.encrij.Revert();
            var titletext = tag.getAttribute('title');
            if (titletext&&(titletext.length>2)) tag.childNodes[0].nodeValue = titletext;
            else tag.childNodes[0].nodeValue = CSE.NiceText;
            tag.style.color=CSE.Colour; 
            tag.style.display=CSE.getDisplayStyle(tag.className);
            // assume the encrypted hex is in the buffer, as quickChange buffered everything
	  }
        }
      }
    }
  }

  // delete the cookies
  document.cookie = "encrij=; expires=Wed,16 May 2007 21:31:28 GMT; path=/";
  document.cookie = "genencrij=; expires=Wed,16 May 2007 21:31:28 GMT; path=/";

  // ensure the keyentry is present and reset
  var enckey = document.getElementById('encrij-keyentry');
  if (enckey) {
    enckey.style.visibility = "hidden";
    enckey.style.display = "inline";
    CSE.FormView = false;
  }
  CSE.AllDecrypt = false;
};


/*
 * addCSEheader
 *   For drupal add the cse header as the child of the first div of class="node" type.
 */

CSE.addCSEheader = function() {
  var tags = document.getElementsByTagName('div');
  for (var i = 0; tag = tags[i]; i++) {
    if (tag) {
      if (tag.className.indexOf('node')==0) {
        //This is where we append our CSE stuff
        var csediv = document.createElement('div');
        csediv.setAttribute('id', 'encform');
        var csespan = document.createElement('span');
        csespan.setAttribute('id', 'encrij-icon');
        csespan.appendChild(document.createTextNode(CSE.Ident));
        csediv.appendChild(csespan);
        var keydiv = document.createElement('div');
        keydiv.setAttribute('id', 'encrij-keyentry');
        var keylabel = document.createElement('label');
        keylabel.setAttribute('id', 'encrij-lab');
        keydiv.appendChild(keylabel);
        var keypass = document.createElement('input');
        keypass.setAttribute('type', 'password');
        keypass.setAttribute('maxlength', '30');
        keypass.setAttribute('id', 'encrij-pw');
        keypass.setAttribute('size', '16');
        keypass.setAttribute('value', '');
        keydiv.appendChild(keypass);
        var keybut = document.createElement('input');
        keybut.setAttribute('type', 'button');
        keybut.setAttribute('value', 'view');
        keybut.setAttribute('id', 'encrij-view');
        keydiv.appendChild(keybut);
        csediv.appendChild(keydiv);
        tag.insertBefore(csediv,tag.firstChild);
        break;
      }
    }
  }
};

CSE.textOK = function(w,minlen) { // replacement text must not contain <> nor line feeds nor carriage returns
  var valid=false;
  if (w) {
    if (w.length>minlen) {
      var ehD="<>\n\r";  
      valid=true;
      for (var j=0; j<w.length; j++) {
        if (ehD.indexOf(w.charAt(j))>=0) {valid=false;break;}
      }
    }
  }
  return valid;
};

CSE.getCSEconfig = function() {  // if we are passed any config information, reset globals appropriately
  var atag = document.getElementById('cseconfig2');
  if (atag&&atag.childNodes[0]) {
    var title = atag.childNodes[0].nodeValue;
    if (title) {
      // use the content of the a tag to set the globals!
      var key = title.substring(0,title.indexOf('+'));
      if (key.indexOf('1')==0) CSE.UseCookie = true;
      else CSE.UseCookie = false; // turn cookie use off and delete the cookies
      title = title.substring(title.indexOf('+')+1,title.length);
      key = title.substring(0,title.indexOf('+'));
      if (CSE.textOK(key,3)) CSE.NiceText = key; // min of 3 chars, and must confirm to CSE.textOK rules
      title = title.substring(title.indexOf('+')+1,title.length);
      key = title.substring(0,title.indexOf('+'));
      if ((key.charAt(0)=='#')&&EncRij.ishex((key.substr(1,key.length-1)).toUpperCase())) CSE.Colour = key;
    }
  } 
};

/*
 * getFormArea
 *   Find the element in the DOM where we apply the CSE interaction
 */

CSE.getFormArea = function() { 
  // first try main body of node edits?
  var rijbody = document.getElementById('edit-body');

  // second try comment bodies?
  if (!rijbody) rijbody = document.getElementById('edit-comment');
    
  if (!rijbody) rijbody = null;

  return rijbody;
};


/*
 * attachjs
 *   This gets called when the page is loaded and the DOM is in place, so it must 
 *   1. decide if we are an CSE view or add/update page
 *   2. remove the hex encryption text to make things look better
 *   3. setup the hooks for all the events that could happen
 *   4. decrypt page content if we have the key in a cookie
 *   and do all this fast! 
 */

CSE.attachjs = function() {
  var code = CSE.getCookieCode();
  
  CSE.getCSEconfig();

  // add the "view" encrypted stuff hooks into the page
  if (CSE.quickChange()>0) {                                         // Buffer all text and strip out the hex!
    if (!document.getElementById('encrij-view')) CSE.addCSEheader(); // add the CSE header if not there
    CSE.decryptTags(CSE.getencCookie("encrij",code),true);           // can we decrypt any of it now?
    var encform = document.getElementById('encform');
    if (encform) {                          // the form made it onto the page!  
      encform.style.display = "block";      // make "icon" viewable
      // the form hooks
      var encicon = document.getElementById("encrij-icon");
      if (encicon) {
        encicon.onclick =
	  function() { 
	    if (CSE.AllDecrypt) CSE.revertTags();  // if all the tags are decrypted and this is clicked, then revert!
	    else {                            // otherwise show/hide the passkey form
	      if (CSE.FormView) document.getElementById("encrij-keyentry").style.visibility = "hidden";
	      else document.getElementById("encrij-keyentry").style.visibility = "visible";
	      CSE.FormView = !CSE.FormView;
	    }
	  };
	encicon.ondblclick = CSE.revertTags;
      }
      document.getElementById('encrij-view').onclick = 
	function() { 
	  var password = document.getElementById("encrij-pw");
	  CSE.decryptTags(password.value,false); 
	  password.value = '';
	};
      document.getElementById("encrij-pw").onkeypress = CSE.catchEnterKey;
    }
  }
  
  // add the "add/update" encryption hooks into the page
  var rijencrypt = document.getElementById('encrij-plntxt');
  if (rijencrypt) {
    var rijbody = CSE.getFormArea();
    if (rijbody) {  // can only do things if we have a body to do it in!
      var rijraw = document.getElementById('encrij-format');
      if (!rijraw.checked) {
        rijbody.value = CSE.formatText(rijbody,true);
        // previous Drupal 5 hack for collapasble forms nolonger needed!
        //var timer=setTimeout("CSE.getFormArea().value = CSE.formatText(CSE.getFormArea(),true)",200);
      }
     
      // do we have a cookie set? If so set the value of the password 
      var pw = document.getElementById('encrij-editpw');
      var pwtext = CSE.getencCookie("genencrij",code);
      if (pwtext&&pw) {
        pw.value = pwtext;
        if (document.getElementById('encrij-editpwver')) document.getElementById('encrij-editpwver').value=pwtext;
      }
      pwtext = null;    

      // double click to clear the plain text area
      rijencrypt.ondblclick = 
        function() { 
          document.getElementById("encrij-plntxt").value = '';
          CSE.setStatus("cleared plain text sandbox",0);
        };
      // on body find position
      rijbody.onclick = 
        function() { 
          if (CSE.ClickCount==0) {
            var body = CSE.getFormArea();
            CSE.ClickCount++;
            CSE.CaretPos=CSE.getCaretPos(body);
            CSE.ScrollPos=body.scrollTop;
            var timer=setTimeout("CSE.ClickCount=0",200);        
          }
        };
      rijbody.ondblclick = 
        function() { 
          if (CSE.ClickCount>0) {
            var body = CSE.getFormArea();
            CSE.insertAtPos(body,CSE.CaretPos,CSE.ScrollPos);
            CSE.CaretPos=-1;
          }
          CSE.ClickCount=0;
        };
      // react to a change in formatting preference
      rijraw.onclick = 
        function() { 
          var rijbody = CSE.getFormArea();
          if (document.getElementById('encrij-format').checked) rijbody.value = CSE.formatText(rijbody,false);
          else rijbody.value = CSE.formatText(rijbody,true);
        };
      document.getElementById('encrij-editpwver').onclick = 
        function() { 
          var pwverify = document.getElementById("encrij-editpwver");
          pwverify.style.color = '';
        };

      // on submit, preview and delete ensure we have ore encrypted text, and not the nice text in the body! 
      // and empty all the CSE fields so nothing is passed back to the server!
      var rijsubmit = document.getElementById('edit-submit');
      if (rijsubmit) rijsubmit.onclick = 
        function() { 
          var rijbody = CSE.getFormArea();
          rijbody.value = CSE.formatText(rijbody,false);
          document.getElementById('encrij-plntxt').value='';
          document.getElementById('encrij-editpw').value='';
          document.getElementById('encrij-editpwver').value='';
        };
      var rijprev = document.getElementById('edit-preview');
      if (rijprev) rijprev.onclick = 
        function() { 
          var rijbody = CSE.getFormArea();
          rijbody.value = CSE.formatText(rijbody,false);
          document.getElementById('encrij-plntxt').value='';
          document.getElementById('encrij-editpw').value='';
          document.getElementById('encrij-editpwver').value='';
        };
      var rijdel = document.getElementById('edit-delete');
      if (rijdel) rijdel.onclick = 
        function() { 
          var rijbody = CSE.getFormArea();
          // put the encrypted body back, just in case they change their minds!
          rijbody.value = CSE.formatText(rijbody,false); 
          document.getElementById('encrij-plntxt').value='';
          document.getElementById('encrij-editpw').value='';
          document.getElementById('encrij-editpwver').value='';
        };
      CSE.setStatus("ready",0);
    }
  }
}

if (Drupal.jsEnabled) {
  $(document).ready(CSE.attachjs);
}
