document.write( "<script language=\"JavaScript\" src=\"sha1.js\"></script>" );
if ( navigator.platform == "Win32" )
{
  document.write( "<script language=\"JavaScript\" src=\"largeint.js\"></script>" );
}
else
{
  document.write( "<script language=\"JavaScript\" src=\"largeint2.js\"></script>" );
}
document.write( "<script language=\"JavaScript\" src=\"arc4.js\"></script>" );
document.write( "<script language=\"JavaScript\" src=\"keyhandler.js\"></script>" );

//Checkboxes can be checked or unchecked.  The default behaviour of
//MeringueMail is to send only the checked checkboxes.
//If you want to send all checkboxes (checked and unchecked),
//call the sendAllCheckBoxes function with the value 'true'.
//Checkboxes may sent as "name=value" pairs.  The "value" is sent
//as "yes" or "no" rather than the common but less descriptive
//"on" for "yes".  If only checked boxes are to be sent, there is
//little point in sending the value field; however you can do that
//by calling the sendCheckBoxValue function with the argument true.
var sendAllCheckBoxesFlag = false;
var sendCheckBoxValueFlag = false;

function sendAllCheckBoxes( flag ) {
  sendAllCheckBoxesFlag = flag;
}

function sendCheckBoxValue( flag ) {
  sendCheckBoxValueFlag = flag;
}

//A restriction of MeringueMail is that form field names may not
//contain '=' characters or begin with an '&' character. This function
//returns false if a form field name is unacceptable.
function checkFormFieldName( formFieldName ) {
  if ( formFieldName.length < 1 ) {
    return false;
  }
  if ( formFieldName.charAt(0) == '&' ) {
    return false;
  }
  if ( formFieldName.indexOf('=') != -1 ) {
    return false;
  }
  if ( formFieldName.indexOf( "Meringue" ) == 0 ) {
    return false;
  }
  return true;
}

//Some form elements may have different lengths.  To ensure that one
//can't determine which form element has been selected on the basis of
//the length of the encrypted data, the following two routines will
//determine how much padding must be added.
function getNumberPaddingCharacters( element, elementArray ) {
  var numPads = 0;
  for ( var i=0; i<elementArray.length; i++) {
    while ( (element.length+numPads) < elementArray[i].value.length ) {
      numPads++;
    }
  }

  return numPads;
}

function getNumberPaddingCharacters2( element, elementArray ) {
  var numPads = 0;
  for ( var i=0; i<elementArray.length; i++) {
    while ( (element.length+numPads) < elementArray[i].text.length ) {
      numPads++;
    }
  }

  return numPads;
}


//When a radio button has been selected, one will need to encrypt
//the value of the radio button.  Since the values may have
//different lengths, it's necessary to find all the radio buttons
//in a radio button group so that padding can be added to values
//which are shorter than the maximum possible length.
function getRadioArray( form, radioElement ) {
  var radioArray = new Array();
  for (var i=0; i<form.elements.length; i++) {
    if ( form.elements[i].name != radioElement.name ) {
      continue;
    }
    radioArray[ radioArray.length ] = form.elements[i];
  }

  return radioArray;
}

//MeringueMail form value fields must contain only lower case letters,
//digits, underscores and periods.  Form fields that need to contain
//additional characters are encoded so that each character is replaced
//by 2 or 4 hex digits specifying the ASCII character code.
function convertToCharCodes( s ) {
  var s2="";
  for (var i=0; i<s.length; i++) {
    var c=s.charCodeAt(i);
    c &= 0xFF;
    s2 += hexDigits[ Math.floor(c/16) ];  //hexDigits defined in arc4.js
    s2 += hexDigits[ c & 0x0F ];
  }
  return s2;
}

//This function checks how many bytes are needed to store a character;
//i.e. whether we can use UTF-8 or whether we need to switch to UTF-16.
function getCharWidth(text) {
  for (var i=0; i<text.length; i++) {
    if ( text.charCodeAt(i) > 0xFF ) {
      return 2;
    }
  }

  return 1;
}


//This code encrypts and submits the form passed as a parameter
function encryptForm(parentForm) {
  return encryptFormWithName( parentForm, parentForm.name );
}

//This code encrypts and submits a form passed as a parameter.  The
//formName is sent unencrypted.  Pass "" for the form name if you
//don't want outsiders to identify which form is being submitted.
function encryptFormWithName(parentForm,formName) {
  if ( recipients.length == 0 ) {
    alert( "There is no known recipient.\n\n" +
           "Possibly the 'keys.js' file is missing.\n" );
    return;
  }
  return encryptFormWithNameToRecipient(parentForm,formName,recipients[0]);
}

//The keys.js contains an array recipients specifying one or more
//MeringueMail recipients.  Normally the array contains only one entry
//but if multiple recipients are to be allowed it is possible
//to specify which recipient is intended.  The default is to submit
//to the first name in the recipients array.  Use this function if
//you want to submit to another recipient.
function encryptFormWithNameToRecipient(parentForm,formName,recipient) {

  // encrypted form contents
  var MERINGUE_MAIL_VERSION = "1.2";
  var KEY_WRAPPING_MECHANISM="pkcs1_v1.5";
  var PUBLIC_KEY_ID;
  var WRAPPED_KEY;
  var ENCRYPTION_ALGORITHM="arc4";
  var FORM_CHAR_SET;
  var FORM_NAME;
  var ENCRYPTED_FORM_DATA;
  	
  //Get a random key
  var key = getRandomArc4Key();
  initializeArc4SBox( key );

  var rsaPublicModulus;
  var rsaPublicKeyId;

  rsaPublicModulus = getPublicKey( recipient );
  rsaPublicKeyId = getKeyId( recipient );
  PUBLIC_KEY_ID = rsaPublicKeyId;

  //The form name can contain weird characters.  Format it so that the
  //client can make sense of it.
  FORM_NAME = convertToCharCodes(formName);

  //Encrypt the key under the recipient's public key
  var enc = encryptKeyPkcs1( key, rsaPublicModulus );
  var encKeyString = convertLargeIntToHexString( enc );
  WRAPPED_KEY = encKeyString;

  //Now start encrypting all the elements of the form
  var plainText = "";
  var cipherText = "";
  var numPaddingCharacters = 0;

  for (var i=0; i<parentForm.elements.length; i++) {
    var elementType = parentForm.elements[i].type;

    //No point sending buttons
    if ( elementType == "button" || elementType == "reset" || elementType == "submit" ) {
      continue;
    }

    var elementName = parentForm.elements[i].name;
    if (!checkFormFieldName(elementName) ) {
      alert( "This form element: " + elementName + "\n" +
             "is unacceptable.\n\nA form element's name must have\n" +
             "non-zero length and cannot contain\n" +
             "'=' characters, begin with an '&' or\n" +
             "start with 'Meringue'." );
      return;
    }

    var elementValue;
    var formElement;

    if ( elementType == "radio" ) {
      if ( parentForm.elements[i].checked ) {
        var radioButton = parentForm.elements[i];
        elementValue = radioButton.value;
        var elementArray = getRadioArray( parentForm, radioButton );
        numPaddingCharacters += getNumberPaddingCharacters( elementValue, elementArray );
        formElement = elementName + '=' + elementValue;
      }
      else {
        continue;
      }
    }
    else if ( elementType == "checkbox" ) {
      if ( parentForm.elements[i].checked ) {
        elementValue = "Yes";
        if ( sendAllCheckBoxesFlag || sendCheckBoxValueFlag ) {
          formElement = elementName + '=' + elementValue;
        }
        else {
          formElement = elementName;
        }
      }
      else {
        if ( !sendAllCheckBoxesFlag ) {
          if ( sendCheckBoxValueFlag ) {
            numPaddingCharacters += elementName.length+4;
          }
          else {
            numPaddingCharacters += elementName.length;
          }
          continue;
        }
        elementValue = "No";
        formElement = elementName + '=' + elementValue;
        numPaddingCharacters++;
      }
    }
    else if ( elementType == "select-one" ) {
      var index = parentForm.elements[i].selectedIndex;
      if ( index < 0 ) {
        continue;
      }
      var optionsArray = parentForm.elements[i].options;
      elementValue = optionsArray[index].text;
      numPaddingCharacters += getNumberPaddingCharacters2( elementValue, optionsArray );
      formElement = elementName + '=' + elementValue;
    }
    else if ( elementType == "select-multiple" ) {
      var optionsArray = parentForm.elements[i].options;
      elementValue = "";
      for (var j=optionsArray.length-1; j>=0; j-- ) {
        if ( optionsArray[j].selected ) {
          var ev2 = optionsArray[j].text;
          numPaddingCharacters += getNumberPaddingCharacters2( ev2, optionsArray );
          ev2 += '\n';
          elementValue = ev2+elementValue;
        }
        else {
          var ev2 = "";
          numPaddingCharacters += getNumberPaddingCharacters2( ev2, optionsArray );
          ev2 += '\n';
          elementValue = elementValue+ev2;
        }
      }
      formElement = elementName + '=' + elementValue;
    }
    else {
      elementValue = parentForm.elements[i].value;
      formElement = elementName + '=' + elementValue;
    }

    //Replace occurrences of '&' with '&&'
    var formElement2="";
    for (var j=0; j<formElement.length; j++) {
      formElement2 += formElement.charAt(j);
      if ( formElement.charAt(j) == '&' ) {
        formElement2 += "&";
      }
    }

    //Add the element to the plainText
    plainText += "&" + formElement2;
  }

  //Prepend the padding
  for (var i=0; i<numPaddingCharacters; i++) {
    plainText = "." + plainText;
  }
  plainText = "MeringuePadding=" + plainText;

  //Check whether the plaintext is UTF-16 or Unicode.
  var charWidth = getCharWidth( plainText );
  if (charWidth == 1) {
    FORM_CHAR_SET = "latin1";
  }
  else {
    FORM_CHAR_SET = "unicode";
  }

  //Encrypt the plaintext
  for (var i=0; i<plainText.length; i++) {
    if ( charWidth == 2 ) {
      cipherText += arc4EncryptByte( Math.floor( plainText.charCodeAt(i)/0x100 )  );
    }
    cipherText += arc4EncryptByte( plainText.charCodeAt(i)%0x100 );
  }

  ENCRYPTED_FORM_DATA = cipherText;
  
  // generate the encrypted form
  var secure_data;
  secure_data = "MERINGUE_MAIL_VERSION=" + MERINGUE_MAIL_VERSION;
  secure_data += "&" + "KEY_WRAPPING_MECHANISM=" + KEY_WRAPPING_MECHANISM;
  secure_data += "&" + "PUBLIC_KEY_ID=" + PUBLIC_KEY_ID;
  secure_data += "&" + "WRAPPED_KEY=" + WRAPPED_KEY;
  secure_data += "&" + "ENCRYPTION_ALGORITHM=" + ENCRYPTION_ALGORITHM;
  secure_data += "&" + "FORM_CHAR_SET=" + FORM_CHAR_SET;
  secure_data += "&" + "FORM_NAME=" + FORM_NAME;
  secure_data += "&" + "ENCRYPTED_FORM_DATA=" + ENCRYPTED_FORM_DATA;
  
//  alert("secure data output: " + secure_data);
  return secure_data;
}
