UnZixHTA (version 2.04) Source Code
About
The current source code for UnZixHTA. Feel free to examine the contents of the source code for bugs, make improvements or to make it more efficient.
If you prefer, as an alternative to downloading the unzixhta.zip file you can instead
copy the source below, paste it into a new plain text document, save it and change the file name to
unzixhta.hta.
Source Code
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<HTA:APPLICATION ID="unzixhta" APPLICATIONNAME="unzixhta" BORDER="thick" CAPTION="yes" SHOWINTASKBAR="yes" SINGLEINSTANCE="yes" CONTEXTMENU="yes" SYSMENU="yes" INNERBORDER="no" NAVIGABLE="no" SCROLL="auto" VERSION="2.04" />
<head>
<title>UnZixHTA</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<style type="text/css">
body {color: #000; background-color: #bbb; font-family: arial, sans-serif; margin: 0; padding: 5px 10px; font-size: 90%;}
form {margin: 0;}
fieldset {padding: 0 1em 1em 1em; border: 2px groove #eee}
legend {padding: 10px; font-weight: bold;}
label {font-weight: bold;}
h1, h2, h3, h4 {font-family: 'Trebuchet MS', arial, sans-serif; margin: 0; padding: 0;}
a:link, a:visited {color: #000; background-color: #bbb;}
#results {margin: 2em 0 1em 0; padding: 1em; border: 2px groove #eee; display: none;}
#results h2, #preamble h2 {margin: 0; padding: 0;}
#results p {margin: 0; padding: 0;}
#results dl {margin: 0; padding: 0;}
#results dl dt {margin: 2em 0 0 0; padding: 0; border-top: 2px groove #eee;}
#results dl dd {margin: 0; padding: 0; text-indent: 0;}
#preamble {margin-top: 2em; padding: 1em; border: 2px groove #eee;}
#function_dogenerateerrorreport fieldset {border: 0 solid; padding: 0; margin: 0;}
#function_dogenerateerrorreport textarea {width: 100%; margin: 1em 0 0 0;}
.textbox {width: 85%; border: 1px solid #000; color: #000; background-color: #fff; font-size: 1em;}
.button {border: 1px solid #000; color: #000; background-color: #d4d0c8; font-size: 1em; margin-top: 0.5em;}
.error {color: #c33; background-color: #bbb;}
.credit {clear: both; float: right; font-size: 70%;}
</style>
<script language="JScript">//<![CDATA[
// lastUpdate = "12 July 2007 18:31 UTC+1";
adTypeBinary = 1;
adTypeText = 2;
adModeRead = 1;
adModeWrite = 2;
adSaveCreateNotExist = 1;
adStateClosed = 0;
// List of file extensions that AVG automatically scans with Resident Shield, ergo, 'at risk' files
var RE_AVG_HAZARDOUSFILES = /\.(386|ASP|BAT|BIN|BMP|BOO|CHM|CLA|CLASS|CMD|CNM|COM|CPL|DEV|DLL|DO.*|DRV|EML|EXE|GIF|HLP|HT.*|INI|JPEG|JPG|JS.*|LNK|MD.*|MSG|NWS|OCX|OV.*|PCX|PGM|PHP.*|PIF|PL.*|PNG|POT|PP.*|SCR|SHS|SMM|SYS|TIF|VBE|VBS|VBX|VXD|WMF|XL.*|XML|ZL.*)$/i;
Object.prototype.toString = objectEnumerate; // Override default toString method for Objects with a routine to generate a CSV string of all properties / functions in the object - useful for debugging
/*
Function: unZixHTA()
Description:
Returns:
History:
20070514 1420UTC v1 Andrew Urquhart Created
*/
function unZixHTA(strSourcePath) {
try {
if (!strSourcePath) {
throw new Error(1, "Expected parameter \"strSourcePath\" was not defined");
}
if (!((/\.zix$/i).test(strSourcePath)) && !confirm("Warning! File extension on original file is not \".zix\"\r\n\r\nContinue anyway?")) {
return null;
}
var arrOut = [];
var arrZixData = getZixData(strSourcePath);
if (!arrZixData) {
alert("Unable to retrieve some or all of the raw ZIX data - you may have to process this file manually yourself using a HEX Editor");
return null;
}
if (arrZixData.length > 1) {
var strFolderName = strSourcePath.replace(/^.*\\([^\\]*)$/ig, "$1").replace(/\.\w+$/, "");
var strSaveFolderPath = strSourcePath.replace(/\\[^\\]*$/ig, "") + "\\" + strFolderName + "\\";
// Attempt to create a directory to hold all of the recovered files
var blnFolderCreated = doCreateDirectoryPath(strSaveFolderPath);
if (!blnFolderCreated) {
alert("Unable to create the following folder to save the recovered files in:\r\n\r\n" + strSaveFolderPath + "\r\n\r\nCheck that you have the necessary user privileges to write files here");
return false;
}
}
else {
var strSaveFolderPath = strSourcePath.replace(/\\[^\\]*$/ig, "") + "\\";
}
var blnFilterMalware = getNode("filtermalware_1").checked;
var blnCheckIfEmpty = getNode("checkifempty_1").checked;
var intFilterCount = 0;
var objStreamIn = new ActiveXObject("ADODB.Stream");
try {
objStreamIn.Open();
objStreamIn.Type = adTypeBinary;
setTitle("Recover & Save | Loading Zix File Into Memory");
arrOut.push("<strong>Progress:</strong> Loading zix file into memory…");
objStreamIn.LoadFromFile(strSourcePath); // Load entire file to RAM :'(
arrOut.push("<strong>done</strong>\r\n");
setTitle("Recover & Save | Recovering File Contents");
arrOut.push("<dl>");
for (var i=0, j=arrZixData.length; i<j; ++i) {
var objStreamOut = new ActiveXObject("ADODB.Stream");
try {
objStreamOut.Open();
objStreamOut.Type = adTypeBinary;
var objZixData = arrZixData[i];
if (!objZixData || objZixData["offset"] == null || !objZixData["size"] || !objZixData["filename"]) {
arrOut.push("<dt class=\"error\">Unable to retrieve some of the ZIX file information, skipping one file</dt>");
continue;
}
else {
var blnLooksLikeMalware = RE_AVG_HAZARDOUSFILES.test(objZixData["filename"]);
if (blnLooksLikeMalware && blnFilterMalware) {
++intFilterCount;
continue;
}
else {
arrOut.push("<dt><strong>File: <code>" + hEnc(objZixData["filename"]) + "</code></strong></dt>");
arrOut.push("<dd><strong>Size:</strong> <code>" + hEnc(getBytesToReadableString(objZixData["size"])) + " (" + hEnc(objZixData["size"]) + "</code> bytes)</dd>");
if (blnLooksLikeMalware) {
arrOut.push("<dd class=\"error\"><strong>Malware Threat:</strong> You must check this file with anti-virus and anti-spyware tools</dd>");
}
// Checking output for empty file (some zix files have valid headers and metadata but otherwise contain ASCII 00's)
if (blnCheckIfEmpty) {
var intNullPercent = getNullStreamPercent(strSourcePath, objZixData["offset"], (objZixData["offset"] + objZixData["size"]));
if (intNullPercent > 20) {
arrOut.push("<dd class=\"error\"><strong>Empty File:</strong> This file appears to be empty or partially empty of content.</dd>");
}
}
// OK, recover the file
arrOut.push("<dd><strong>Progress:</strong> Recovering file contents…");
objStreamIn.Position = objZixData["offset"]; // Ditch the ZIX header bytes
objStreamIn.CopyTo(objStreamOut, objZixData["size"]); // Copy only up to the original file size and thus ditch the ending ZIX data
arrOut.push("<strong>done</strong></dd>");
// Check that CopyTo didn't terminate abnormally (e.g. due to EOS)
setTitle("Recover & Save | Examining Recovered File");
if (objStreamOut.Size != objZixData["size"]) {
arrOut.push("<dd><strong class=\"error\">Warning:</strong> Not all of the original file contents may have been recovered. The zix file may be corrupt.</dd>");
}
// Save file
var strSavePath = strSaveFolderPath + objZixData["filename"];
setTitle("Recover & Save | Saving File");
arrOut.push("<dd><strong>Attempting to save file:</strong> <code>" + strSavePath + "</code>…");
objStreamOut.SaveToFile(strSavePath, adSaveCreateNotExist);
setTitle("Recover & Save | File Saved");
arrOut.push("<strong>… done</strong></dd>");
}
}
}
finally {
if (objStreamOut && objStreamOut.State != adStateClosed) {
objStreamOut.Close();
}
}
} // /for
arrOut.push("</dl>");
if (intFilterCount > 0) {
arrOut.push("\r\n\r\n<p><strong>Malware Note:</strong> " + intFilterCount + " files were omitted as they <em>may</em> potentially contain viruses, trojans, adware, etc.</p>");
}
return true;
}
finally {
if (objStreamIn && objStreamIn.State != adStateClosed) {
objStreamIn.Close();
}
echo(arrOut.join(""));
}
return false;
}
catch (err) {
switch (err.number) {
case -2146825284 : alert("File not saved. Check that a file does not already exist with this name and that you have permissions to write to this directory"); break;
case -2146824572 : alert("Security settings on this computer are preventing unZixHTA from opening the zix file. That's generally a good thing, but unZixHTA won't work if it can't open the zix file! Read the advice in the Security section at: http://andrewu.co.uk/tools/unzixhta/#security"); break;
case -2146827859 : alert("Sorry, this application relies on the ADODB.Stream ActiveX component but this component is not available for use on this computer. You'll also see this message if for some reason you've opened this application inside Internet Explorer rather than executing it on its own"); break;
case -2146825286 : alert("The file appears to be in use by another application and cannot be opened. Close any applications that may have handles to this file and try again."); break;
case -2146828281 : // fall through
case -2147024882 : alert("Sorry, you don't have enough free RAM to open this file. Unfortunately UnZixHTA needs at least as much free RAM as the size of the zix file it is processing. Either close other programs and try again, reboot and try again before opening any other applications, or borrow someone else's machine with more RAM!"); break;
case -2147024809 : // fall through
case -2147024770 : alert("Sorry, UnZixHTA seems to have some problems with Windows Vista. As I have no access to Vista to debug UnZixHTA, I'm not able to troubleshoot this problem. If you're highly technically minded and come up with a work-around, please send correspondence to the bug report page mentioned in the source."); break;
default : propError(err, arguments);
}
return false;
}
}
/*
Function: getZixData()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function getZixData(strSourcePath) {
try {
var objStream = new ActiveXObject("ADODB.Stream");
try {
objStream.Open();
objStream.Type = adTypeText;
objStream.CharSet = "utf-8";
setTitle("Zix File Information | Loading Zix File Into Memory");
objStream.LoadFromFile(strSourcePath); // Load entire file to RAM :'(
var intByteSize = objStream.Size;
objStream.Position = intByteSize; // Set to EOS
var blnSentinel = true; // Loop sentinel
var arrRawZixData = [];
var strStringBuffer = ""; // We hold a copy of the last 11 chars so that we can detect the start of the zix data section
var blnFoundDataSection = false;
setTitle("Zix File Information | Scanning For Metadata");
for (var i=intByteSize; i>0 && blnSentinel; --i) {
objStream.Position = i;
var strChar = objStream.ReadText(1); // Read a byte as UTF-8 char
if (strChar == null) {
continue;
}
else if (strChar.charCodeAt(0) < 32) {
blnSentinel = false;
break;
}
else {
arrRawZixData.push(strChar);
strStringBuffer = strChar.toLowerCase() + strStringBuffer.substring(0, 10);
// Stop at the start of the zix data section
if (strStringBuffer === "d8:announce") {
blnSentinel = false;
blnFoundDataSection = true;
break;
}
}
}
}
finally {
if (objStream && objStream.State != adStateClosed) {
objStream.Close();
}
}
if (!blnFoundDataSection) {
setTitle("Zix File Information | Metadata Not Found");
return null;
}
else {
var arrZixData = [];
setTitle("Zix File Information | Metadata Found");
var strRawData = arrRawZixData.reverse().join(""); // Assemble zix data section string
arrRawZixData = null;
var reNameKey = /4\:name(\d+)\:/i;
var reNameKeyData = /4\:name(\d+)\:(.*)/i;
var reOrigSize = /4\:sizei(\d+)e/ig;
var reOffset = /5\:starti(\d+)e/ig;
var arrOrigSize = strRawData.match(reOrigSize);
var arrOffset = strRawData.match(reOffset);
if (!arrOrigSize || !arrOffset) {
setTitle("Zix File Information | Unable to Parse Zix File");
return null;
}
var intNumFiles = arrOrigSize.length;
var intNumFolders = 0;
if (!intNumFiles) {
return null;
}
else {
// Loop over all file markers in zix file. Some files are actually folders, which we skip.
for (var i=0; i<intNumFiles; ++i) {
var objZixData = {size:null, filename:null, offset:null};
objZixData["size"] = Number(arrOrigSize[i].replace(reOrigSize, "$1")); // size of content
// Use the content size to indicate if the content is a folder or not
if (objZixData["size"] != 0) {
// OK, content is a file, get the file's name and offset
reNameKeyData.test(strRawData);
objZixData["filename"] = (RegExp.$2).substring(0, Number(RegExp.$1)); // filename
strRawData = strRawData.replace(reNameKey, ""); // remove filename match from data so not re-detected on next loop
/* Byte at which file content starts. 'intNumFolders' is used because folders don't have such a
marker so the arrOffset array is shorter than the arrOrigSize array, so it stops us reading
past the end of the arrOffset array.
*/
objZixData["offset"] = Number(arrOffset[i - intNumFolders].replace(reOffset, "$1"));
arrZixData.push(objZixData); // Add file to array of zix file contents
}
else {
// OK, content was a folder - ignore it (we'll still process its content later though)
objZixData = null;
++intNumFolders; // The current difference between the number of file markers and the number of actual files
}
}
return arrZixData;
}
}
}
catch (err) {
switch (err.number) {
case -2146824572 : alert("Security settings on this computer are preventing unZixHTA from opening the zix file. That's generally a good thing, but unZixHTA won't work if it can't open the zix file! Read the advice in the Security section at: http://andrewu.co.uk/tools/unzixhta/#security"); break;
case -2146827859 : alert("Sorry, this application relies on the ADODB.Stream ActiveX component but this component is not available for use on this computer. You'll also see this message if for some reason you've opened this application inside Internet Explorer rather than executing it on its own"); break;
case -2146825286 : alert("The file appears to be in use by another application and cannot be opened. Close any applications that may have handles to this file and try again."); break;
case -2146828281 : // fall through
case -2147024882 : alert("Sorry, you don't have enough free RAM to open this file. Unfortunately UnZixHTA needs at least as much free RAM as the size of the zix file it is processing. Either close other programs and try again, reboot and try again before opening any other applications, or borrow someone else's machine with more RAM!"); break;
case -2147024809 : // fall through
case -2147024770 : alert("Sorry, UnZixHTA seems to have some problems with Windows Vista. As I have no access to Vista to debug UnZixHTA, I'm not able to troubleshoot this problem. If you're highly technically minded and come up with a work-around, please send correspondence to the bug report page mentioned in the source."); break;
default : propError(err, arguments);
}
}
}
/*
Function: getNullStreamPercent()
Description: Checks to see if a stream contains enough information
Returns: Number (Integer) between 1 and 100
History:
20070528 1432UTC v1 Andrew Urquhart Created
*/
function getNullStreamPercent(strSourcePath, intStartByte, intEndByte) {
try {
if (!strSourcePath) {
throw new Error(1, "Expected parameter \"strSourcePath\" was not defined");
}
if (!isNumber(intStartByte) || intStartByte < 0) {
throw new Error(2, "Parameter \"intStartByte\" was not a positive number or zero");
}
if (!isNumber(intEndByte) || intEndByte < 0) {
throw new Error(3, "Parameter \"intEndByte\" was not a positive number or zero");
}
if (intStartByte >= intEndByte) {
throw new Error(4, "Parameter intStartByte (" + intStartByte + ") must be less than intEndByte (" + intEndByte + ")");
}
var objStream = new ActiveXObject("ADODB.Stream");
try {
objStream.Open();
objStream.Type = adTypeText;
objStream.CharSet = "utf-8"; // May cause false positives if the file charset is not utf-8
setTitle("Scanning For Missing Content | Loading Zix File Into Memory");
objStream.LoadFromFile(strSourcePath); // Load entire file to RAM :'(
var intByteSize = objStream.Size;
if (intByteSize == 0) {
return 100;
}
if (intEndByte > intByteSize) {
throw new Error(5, "Parameter \"intEndByte\" (" + intEndByte + ") exceeds maximum size of actual file (" + intByteSize + ")");
}
var intNullCount = 0;
var intCount = 0;
var intMaxCount = Math.min(4000, intByteSize); // Number of steps to sample. 4000 is arbitrary.
var intByteStep = Math.ceil(intByteSize / intMaxCount); // Number of bytes between performing a sampling (inclusive)
var intReportStep = Math.floor(intMaxCount / 10); // Frequency on which to report scanning progess to GUI
for (var i=intStartByte; i<=intEndByte; i+=intByteStep, ++intCount) {
if (intCount % intReportStep === 0) {
setTitle("Scanning For Missing Content | Sampling " + (Math.floor(100*intCount/intMaxCount)) + "%");
}
objStream.Position = i;
if (objStream.ReadText(1) == 0) {
// Sampled character was an empty byte, or appeared to be because the charset was incorrect for the file type
++intNullCount;
}
}
setTitle("Scanning For Missing Content");
return Math.floor((intNullCount / intCount) * 100); // Precentage of null bytes
}
finally {
if (objStream && objStream.State != adStateClosed) {
objStream.Close();
}
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: unzixAndSave()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function unzixAndSave() {
try {
// Worker function invoked after a pause to allow the GUI to update
function unzixAndSave_worker() {
try {
var strSourcePath = getNode("sourcefilepath").value;
if (!strSourcePath) {
alert("Please select the *.zix file to recover");
reset();
return;
}
if (strSourcePath) {
cls();
echo("<h2>Recover & Save</h2>\r\n");
var blnProcessed = unZixHTA(strSourcePath);
switch(blnProcessed) {
case true : setTitle("Recover & Save | Completed"); echo("<strong>Completed</strong>\r\n\r\nAs a precaution you should use your anti-virus and anti-spyware tools to scan this file/folder for critters\r\n"); break;
case false : setTitle("Recover & Save | Failed"); echo("\r\n<strong class=\"error\">Operation Failed</strong>\r\n"); break;
default : reset();
}
}
}
catch (err) {
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
finally {
getNode("andrewu_unzixhta").style.cursor = "auto";
}
}
this.unzixAndSave_worker = unzixAndSave_worker;
cls();
setTitle("Recover & Save");
echo("<strong>Please Wait:</strong> This can be a very slow process on older machines or machines with less than <strong>2GB</strong> of RAM. The application may <em>appear</em> to hang on such machines…");
getNode("andrewu_unzixhta").style.cursor = "wait";
getNode("preamble").style.display = "none";
setTimeout("unzixAndSave_worker()", 500);
}
catch (err) {
getNode("andrewu_unzixhta").style.cursor = "auto";
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
}
/*
Function: zixInfo()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function zixInfo() {
try {
// Worker function invoked after a pause to allow the GUI to update
function zixInfo_worker() {
try {
var strSourcePath = getNode("sourcefilepath").value;
if (!strSourcePath) {
alert("Please select the *.zix file to examine");
reset();
return;
}
if (strSourcePath) {
cls();
echo("<h2>Zix File Information</h2>\r\n");
var arrZixData = getZixData(strSourcePath);
var arrOut = [];
if (!arrZixData) {
alert("Unable to retrieve some or all of the raw ZIX data - you may have to process this file manually yourself using a HEX Editor");
reset();
return;
}
else {
var blnFilterMalware = getNode("filtermalware_1").checked;
var blnCheckIfEmpty = getNode("checkifempty_1").checked;
var intFilterCount = 0;
arrOut.push("<p>The contents of the zix file are shown below:</p>");
arrOut.push("<dl>");
for (var i=0, j=arrZixData.length; i<j; ++i) {
var objZixData = arrZixData[i];
if (!objZixData || objZixData["offset"] == null || !objZixData["size"] || !objZixData["filename"]) {
arrOut.push("<dt class=\"error\">Unable to retrieve some of the ZIX file information, skipping one file</dt>");
continue;
}
else {
var blnLooksLikeMalware = RE_AVG_HAZARDOUSFILES.test(objZixData["filename"]);
if (blnLooksLikeMalware && blnFilterMalware) {
++intFilterCount;
continue;
}
else {
arrOut.push("<dt><strong>File: <code>" + hEnc(objZixData["filename"]) + "</code></strong></dt>");
arrOut.push("<dd><strong>Size:</strong> <code>" + hEnc(getBytesToReadableString(objZixData["size"])) + " (" + hEnc(objZixData["size"]) + "</code> bytes)</dd>");
if (blnLooksLikeMalware) {
arrOut.push("<dd class=\"error\"><strong>Malware Threat:</strong> You must check this file with anti-virus and anti-spyware tools</dd>");
}
if (blnCheckIfEmpty) {
var intNullPercent = getNullStreamPercent(strSourcePath, objZixData["offset"], (objZixData["offset"] + objZixData["size"]));
if (intNullPercent > 20) {
arrOut.push("<dd class=\"error\"><strong>Empty File:</strong> This file appears to be empty or partially empty of content.</dd>");
}
}
}
}
}
arrOut.push("</dl>");
if (intFilterCount > 0) {
arrOut.push("\r\n\r\n<p><strong>Malware Note:</strong> " + intFilterCount + " files were omitted as they <em>may</em> potentially contain viruses, trojans, adware, etc.</p>");
}
}
setTitle("Zix File Information | Finished");
arrOut.push("\r\n<p><strong>Finished</strong> (This was a read-only operation and no files have been saved)</p>\r\n");
echo(arrOut.join(""));
}
}
catch (err) {
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
finally {
getNode("andrewu_unzixhta").style.cursor = "auto";
}
}
this.zixInfo_worker = zixInfo_worker;
cls();
setTitle("Zix File Information");
echo("<strong>Please Wait:</strong> This can be a slow process on older machines or machines with less than 1GB of RAM. The application may <em>appear</em> to hang on such machines…");
getNode("preamble").style.display = "none";
getNode("andrewu_unzixhta").style.cursor = "wait";
setTimeout("zixInfo_worker()", 500);
}
catch (err) {
getNode("andrewu_unzixhta").style.cursor = "auto";
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
}
/*
Function: echo()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function echo(strHTML, blnPreserveLineBreaks) {
try {
var objResults = getNode("results");
if (objResults) {
objResults.style.display = "block";
objResults.innerHTML += (blnPreserveLineBreaks ? strHTML : strHTML.replace(/\r\n|\r|\n/g, "<br />"));
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: cls()
Description:
Returns:
History:
20070526 1219UTC+1 v1 Andrew Urquhart Created
*/
function cls() {
try {
var objResults = getNode("results");
if (objResults) {
objResults.style.display = "block";
objResults.innerHTML = "";
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: reset()
Description:
Returns:
History:
20070526 1233UTC+1 v1 Andrew Urquhart Created
*/
function reset() {
try {
cls();
setTitle();
getNode("andrewu_unzixhta").style.cursor = "auto";
var objPreamble = getNode("preamble");
if (objPreamble) {
objPreamble.style.display = "block";
}
var objResults = getNode("results");
if (objResults) {
objResults.innerHTML = "";
objResults.style.display = "none";
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: hEnc()
Description: Basic Server.HTMLEncode'ing
Returns: String
History:
20060330 1447BST v1 Andrew Urquhart Created
*/
function hEnc(strRawHTML) {
try {
return (strRawHTML === null || strRawHTML === undefined ? "" : ("" + strRawHTML).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">")); // "
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: getNode()
Description:
Returns:
History:
20060420 0930BST v1 Andrew Urquhart Created
*/
function getNode(strElementId) {
try {
var objElement = document.getElementById(strElementId);
if (!objElement) {
return null;
};
return objElement;
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: getBytesToReadableString()
Description:
Returns:
History:
20041206 0123GMT v1 Andrew Urquhart Created
*/
function getBytesToReadableString(intBytes) {
try {
if (intBytes < 1048576) {
return Math.ceil(intBytes/10.24)/100 + "KB";
}
else if (intBytes < 1073741824) {
return Math.ceil(intBytes/10485.76)/100 + "MB";
}
else {
return Math.ceil(intBytes/10737418.24)/100 + "GB";
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: isNumber()
Description:
Returns:
History:
20060608 0928BST v1 Andrew Urquhart Created
*/
function isNumber(varData) {
if (typeof varData !== "number" || isNaN(varData)) {
return false;
}
return true;
}
/*
Function: setTitle()
Description:
Returns:
History:
20070528 1517BST v1 Andrew Urquhart Created
*/
function setTitle(strText) {
document.title = "UnZixHTA" + (strText ? " | " + strText : "");
}
/*
Function: doCreateDirectoryPath()
Description: Build a hierarchy of directories up to the last directory in a path. Ignores folders that already exist.
Returns:
History:
20040815 1920BST v1 Andrew Urquhart Created
*/
function doCreateDirectoryPath(strFilepath) {
try {
if (!strFilepath) {
throw new Error(1, "Required parameter \"strFilepath\" was not defined");
}
var objFSO = new ActiveXObject("Scripting.FileSystemObject");
if (!objFSO) {
throw new Error(2, "Failed to create \"Scripting.FileSystemObject\" object");
}
var dirs = strFilepath.replace(/\//g, "\\").replace(/(\w:)\\?/, "").split("\\");
var dirPathStack = RegExp.$1 + "\\";
for (var d=0; d<dirs.length - 1; d++) {
if (dirs[d]) {
dirPathStack += dirs[d] + "\\";
try {
if (!objFSO.FolderExists(dirPathStack)) {
// Folder doesn't exist, so create it
try {
objFSO.CreateFolder(dirPathStack);
}
catch (err) {
throw new Error(err.number, "CreateFolder() failed with argument \"" + dirPathStack + "\". Message: " + err.description);
}
}
else {
// Folder already exists, skip it
continue;
}
}
catch (err) {
// Ignore 'permission denied' and 'file exists' errors
if (err.number == -2146828218 || err.number == -2146828230) {
continue; // Try to build the path all the way to the last directory - we may be being denied only because we're outsite the servable root and we may eventually pop back into it
}
else {
throw err;
}
}
}
}
// Return a Boolean as to whether directory path exists
return objFSO.FolderExists(dirPathStack);
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: propError()
Description: Propagate an Error up the call chain, adding function name and parameter information about each stage in the chain
Returns:
History:
20060302 1145GMT v1 Andrew Urquhart Created
*/
function propError(errError, argsCaller) {
var arrTree = [];
try {
arrTree.push("\r\n========================================");
if (typeof arguments.callee.count != "number") {
arguments.callee.count = 1;
}
else {
++arguments.callee.count;
}
arrTree.push("LEVEL: " + arguments.callee.count);
// GET FUNCTION NAME AND VALUE OF FUNCTION PARAMETERS
if (argsCaller) {
var arrArgs = [];
for (var i=0, j=argsCaller.length; i<j; ++i) {
arrArgs.push(argsCaller[i]);
}
argsCaller.callee.toString().match(/function\s*([\w\d]+)\s*\(([^\)]*)/i);
var strFunctionName = RegExp.$1;
arrTree.push("FUNCTION: " + strFunctionName + "(" + arrArgs.join(", ") + ")");
}
else {
arrTree.push("FUNCTION: [Unknown - no caller arguments provided]");
}
arrTree.push("NUMBER: " + errError.number);
arrTree.push("MESSAGE: " + errError.message);
}
catch (err) {
try {
throw new Error(err.number, "Function propError() failed with parameters: errError=\"" + errError + "\", argsCaller=\"" + argsCaller + "\" and message:\r\n" + err.description);
}
catch (err) {
doGenerateErrorReport(err);
}
return;
}
// Cascade errors on up the call chain
throw new Error(errError.number, arrTree.join("\r\n"));
}
/*
Function: doGenerateErrorReport()
Description: Prepare an error report, and if confirmed, submit it to reporting server
Returns:
History:
20060302 1145GMT v1 Andrew Urquhart Created
*/
function doGenerateErrorReport(errError) {
try {
cls();
reset();
setTitle("Zix File Information");
getNode("preamble").style.display = "none";
arrOut = [];
arrOut.push("<div id=\"function_dogenerateerrorreport\">");
arrOut.push("<h2 class=\"error\">Application Error</h2>");
arrOut.push("<p class=\"error\">Sorry, an unhandled error has occurred in UnZixHTA.</p>");
arrOut.push("<p class=\"error\">You can help fix this problem by submitting the following error report to Andrew Urquhart (the author).</p>");
arrOut.push("<p class=\"error\">To submit the anonymous error report choose the \"Submit Error Report\" button (you may have to scroll down). This will open a web browser window (probably Internet Explorer) and post the information displayed below to the <a href=\"http://andrewu.co.uk/\">http://andrewu.co.uk</a> website. This information will not be made public or disclosed to a third party, its sole use is for debugging UnZixHTA. You may edit the information in the form below if you think it contains private or sensitive information (i.e. file names) or add your own notes.</p>");
arrOut.push("<form action=\"http://andrewu.co.uk/contact/process.asp\" method=\"post\" onsubmit=\"setTimeout('reset()', 1000);\">");
arrOut.push("<fieldset>");
arrOut.push("<input name=\"subjectBox\" type=\"hidden\" value=\"$Error report: UnZixHTA\">");
arrOut.push("<textarea name=\"message\" rows=\"10\" cols=\"40\" class=\"textbox\">");
arrOut.push(hEnc(errError.description));
arrOut.push("</textarea>");
arrOut.push("<input type=\"submit\" class=\"button\" value=\"Submit Error Report ►\">");
arrOut.push("</fieldset>");
arrOut.push("</form>");
arrOut.push("</div>");
echo(arrOut.join(""), true);
}
catch (err) {
alert("Function doGenerateErrorReport() failed with parameters: errError=\"" + errError + "\" and message:\r\n" + err.description);
}
}
/*
Function: objectEnumerate()
Description: Overrides the default implicit cast to string function for Objects in order to enumerate their contents in CSV format
Returns: CSV String
History:
20060203 1256GMT v1 Andrew Urquhart Created
*/
function objectEnumerate() {
var a = [];
for (var e in this) {
if (typeof this[e] != "function") {
a.push("\"" + e + "\": \"" + ("" + this[e]).replace(/"/g,"\\\"") + "\"")
}
}
return a.join(",");
}
//]]>
</script>
</head>
<body id="andrewu_unzixhta">
<h1>UnZixHTA 2.04</h1>
<form method="get" onsubmit="return false;">
<fieldset>
<legend>Select the zix file</legend>
<label for="sourcefilepath">Zix File:</label>
<input type="file" class="textbox" id="sourcefilepath"/>
<br/>
<label>Filter out potential malware:</label>
<label for="filtermalware_1">Yes</label>
<input type="radio" name="filtermalware" id="filtermalware_1" value="1" checked="checked"/>
<label for="filtermalware_2">No</label>
<input type="radio" name="filtermalware" id="filtermalware_2" value="0"/>
(Yes is recommended)
<br/>
<label>Check for blank file content:</label>
<label for="checkifempty_1">Yes</label>
<input type="radio" name="checkifempty" id="checkifempty_1" value="1"/>
<label for="checkifempty_2">No</label>
<input type="radio" name="checkifempty" id="checkifempty_2" value="0" checked="checked"/>
(No is much faster)
<br/>
<input type="button" class="button" value="Zix File Information ►" onclick="zixInfo();"/>
<input type="button" class="button" value="Recover & Save ►" onclick="unzixAndSave();"/>
</fieldset>
</form>
<div id="results"></div>
<div id="preamble">
<h2>Notes</h2>
<p>
This Windows-only HTML Application will strip the rubbish from a <code>.zix</code> file to recover the original
contents and then save it to your PC.
</p>
<p>
Button “Zix File Information” will examine the selected zix file — this reads the zix file and
reports its contents (this may take a few seconds).
</p>
<p>
Button “Recover & Save” attempts to recover the contents of the zix file. Recovered files are saved
into the same folder as the selected zix file. This may take up to a minute or so.
</p>
<p>
Option “Filter out potential malware” will omit files of certain potentially hazardous file extensions
(e.g. executable files) from both the “Zix File Information” and “Recover & Save”
options. Default is “yes”.
</p>
<p>
Option “Check for blank file content” will scan each file within the zix file for files that contain
no data / are empty of content. Default is “no”.
</p>
</div>
<p class="credit">Author: <a href="http://andrewu.co.uk/tools/unzixhta/">Andrew Urquhart</a>, version: 2.04</p>
</body>
</html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<HTA:APPLICATION ID="unzixhta" APPLICATIONNAME="unzixhta" BORDER="thick" CAPTION="yes" SHOWINTASKBAR="yes" SINGLEINSTANCE="yes" CONTEXTMENU="yes" SYSMENU="yes" INNERBORDER="no" NAVIGABLE="no" SCROLL="auto" VERSION="2.04" />
<head>
<title>UnZixHTA</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<style type="text/css">
body {color: #000; background-color: #bbb; font-family: arial, sans-serif; margin: 0; padding: 5px 10px; font-size: 90%;}
form {margin: 0;}
fieldset {padding: 0 1em 1em 1em; border: 2px groove #eee}
legend {padding: 10px; font-weight: bold;}
label {font-weight: bold;}
h1, h2, h3, h4 {font-family: 'Trebuchet MS', arial, sans-serif; margin: 0; padding: 0;}
a:link, a:visited {color: #000; background-color: #bbb;}
#results {margin: 2em 0 1em 0; padding: 1em; border: 2px groove #eee; display: none;}
#results h2, #preamble h2 {margin: 0; padding: 0;}
#results p {margin: 0; padding: 0;}
#results dl {margin: 0; padding: 0;}
#results dl dt {margin: 2em 0 0 0; padding: 0; border-top: 2px groove #eee;}
#results dl dd {margin: 0; padding: 0; text-indent: 0;}
#preamble {margin-top: 2em; padding: 1em; border: 2px groove #eee;}
#function_dogenerateerrorreport fieldset {border: 0 solid; padding: 0; margin: 0;}
#function_dogenerateerrorreport textarea {width: 100%; margin: 1em 0 0 0;}
.textbox {width: 85%; border: 1px solid #000; color: #000; background-color: #fff; font-size: 1em;}
.button {border: 1px solid #000; color: #000; background-color: #d4d0c8; font-size: 1em; margin-top: 0.5em;}
.error {color: #c33; background-color: #bbb;}
.credit {clear: both; float: right; font-size: 70%;}
</style>
<script language="JScript">//<![CDATA[
// lastUpdate = "12 July 2007 18:31 UTC+1";
adTypeBinary = 1;
adTypeText = 2;
adModeRead = 1;
adModeWrite = 2;
adSaveCreateNotExist = 1;
adStateClosed = 0;
// List of file extensions that AVG automatically scans with Resident Shield, ergo, 'at risk' files
var RE_AVG_HAZARDOUSFILES = /\.(386|ASP|BAT|BIN|BMP|BOO|CHM|CLA|CLASS|CMD|CNM|COM|CPL|DEV|DLL|DO.*|DRV|EML|EXE|GIF|HLP|HT.*|INI|JPEG|JPG|JS.*|LNK|MD.*|MSG|NWS|OCX|OV.*|PCX|PGM|PHP.*|PIF|PL.*|PNG|POT|PP.*|SCR|SHS|SMM|SYS|TIF|VBE|VBS|VBX|VXD|WMF|XL.*|XML|ZL.*)$/i;
Object.prototype.toString = objectEnumerate; // Override default toString method for Objects with a routine to generate a CSV string of all properties / functions in the object - useful for debugging
/*
Function: unZixHTA()
Description:
Returns:
History:
20070514 1420UTC v1 Andrew Urquhart Created
*/
function unZixHTA(strSourcePath) {
try {
if (!strSourcePath) {
throw new Error(1, "Expected parameter \"strSourcePath\" was not defined");
}
if (!((/\.zix$/i).test(strSourcePath)) && !confirm("Warning! File extension on original file is not \".zix\"\r\n\r\nContinue anyway?")) {
return null;
}
var arrOut = [];
var arrZixData = getZixData(strSourcePath);
if (!arrZixData) {
alert("Unable to retrieve some or all of the raw ZIX data - you may have to process this file manually yourself using a HEX Editor");
return null;
}
if (arrZixData.length > 1) {
var strFolderName = strSourcePath.replace(/^.*\\([^\\]*)$/ig, "$1").replace(/\.\w+$/, "");
var strSaveFolderPath = strSourcePath.replace(/\\[^\\]*$/ig, "") + "\\" + strFolderName + "\\";
// Attempt to create a directory to hold all of the recovered files
var blnFolderCreated = doCreateDirectoryPath(strSaveFolderPath);
if (!blnFolderCreated) {
alert("Unable to create the following folder to save the recovered files in:\r\n\r\n" + strSaveFolderPath + "\r\n\r\nCheck that you have the necessary user privileges to write files here");
return false;
}
}
else {
var strSaveFolderPath = strSourcePath.replace(/\\[^\\]*$/ig, "") + "\\";
}
var blnFilterMalware = getNode("filtermalware_1").checked;
var blnCheckIfEmpty = getNode("checkifempty_1").checked;
var intFilterCount = 0;
var objStreamIn = new ActiveXObject("ADODB.Stream");
try {
objStreamIn.Open();
objStreamIn.Type = adTypeBinary;
setTitle("Recover & Save | Loading Zix File Into Memory");
arrOut.push("<strong>Progress:</strong> Loading zix file into memory…");
objStreamIn.LoadFromFile(strSourcePath); // Load entire file to RAM :'(
arrOut.push("<strong>done</strong>\r\n");
setTitle("Recover & Save | Recovering File Contents");
arrOut.push("<dl>");
for (var i=0, j=arrZixData.length; i<j; ++i) {
var objStreamOut = new ActiveXObject("ADODB.Stream");
try {
objStreamOut.Open();
objStreamOut.Type = adTypeBinary;
var objZixData = arrZixData[i];
if (!objZixData || objZixData["offset"] == null || !objZixData["size"] || !objZixData["filename"]) {
arrOut.push("<dt class=\"error\">Unable to retrieve some of the ZIX file information, skipping one file</dt>");
continue;
}
else {
var blnLooksLikeMalware = RE_AVG_HAZARDOUSFILES.test(objZixData["filename"]);
if (blnLooksLikeMalware && blnFilterMalware) {
++intFilterCount;
continue;
}
else {
arrOut.push("<dt><strong>File: <code>" + hEnc(objZixData["filename"]) + "</code></strong></dt>");
arrOut.push("<dd><strong>Size:</strong> <code>" + hEnc(getBytesToReadableString(objZixData["size"])) + " (" + hEnc(objZixData["size"]) + "</code> bytes)</dd>");
if (blnLooksLikeMalware) {
arrOut.push("<dd class=\"error\"><strong>Malware Threat:</strong> You must check this file with anti-virus and anti-spyware tools</dd>");
}
// Checking output for empty file (some zix files have valid headers and metadata but otherwise contain ASCII 00's)
if (blnCheckIfEmpty) {
var intNullPercent = getNullStreamPercent(strSourcePath, objZixData["offset"], (objZixData["offset"] + objZixData["size"]));
if (intNullPercent > 20) {
arrOut.push("<dd class=\"error\"><strong>Empty File:</strong> This file appears to be empty or partially empty of content.</dd>");
}
}
// OK, recover the file
arrOut.push("<dd><strong>Progress:</strong> Recovering file contents…");
objStreamIn.Position = objZixData["offset"]; // Ditch the ZIX header bytes
objStreamIn.CopyTo(objStreamOut, objZixData["size"]); // Copy only up to the original file size and thus ditch the ending ZIX data
arrOut.push("<strong>done</strong></dd>");
// Check that CopyTo didn't terminate abnormally (e.g. due to EOS)
setTitle("Recover & Save | Examining Recovered File");
if (objStreamOut.Size != objZixData["size"]) {
arrOut.push("<dd><strong class=\"error\">Warning:</strong> Not all of the original file contents may have been recovered. The zix file may be corrupt.</dd>");
}
// Save file
var strSavePath = strSaveFolderPath + objZixData["filename"];
setTitle("Recover & Save | Saving File");
arrOut.push("<dd><strong>Attempting to save file:</strong> <code>" + strSavePath + "</code>…");
objStreamOut.SaveToFile(strSavePath, adSaveCreateNotExist);
setTitle("Recover & Save | File Saved");
arrOut.push("<strong>… done</strong></dd>");
}
}
}
finally {
if (objStreamOut && objStreamOut.State != adStateClosed) {
objStreamOut.Close();
}
}
} // /for
arrOut.push("</dl>");
if (intFilterCount > 0) {
arrOut.push("\r\n\r\n<p><strong>Malware Note:</strong> " + intFilterCount + " files were omitted as they <em>may</em> potentially contain viruses, trojans, adware, etc.</p>");
}
return true;
}
finally {
if (objStreamIn && objStreamIn.State != adStateClosed) {
objStreamIn.Close();
}
echo(arrOut.join(""));
}
return false;
}
catch (err) {
switch (err.number) {
case -2146825284 : alert("File not saved. Check that a file does not already exist with this name and that you have permissions to write to this directory"); break;
case -2146824572 : alert("Security settings on this computer are preventing unZixHTA from opening the zix file. That's generally a good thing, but unZixHTA won't work if it can't open the zix file! Read the advice in the Security section at: http://andrewu.co.uk/tools/unzixhta/#security"); break;
case -2146827859 : alert("Sorry, this application relies on the ADODB.Stream ActiveX component but this component is not available for use on this computer. You'll also see this message if for some reason you've opened this application inside Internet Explorer rather than executing it on its own"); break;
case -2146825286 : alert("The file appears to be in use by another application and cannot be opened. Close any applications that may have handles to this file and try again."); break;
case -2146828281 : // fall through
case -2147024882 : alert("Sorry, you don't have enough free RAM to open this file. Unfortunately UnZixHTA needs at least as much free RAM as the size of the zix file it is processing. Either close other programs and try again, reboot and try again before opening any other applications, or borrow someone else's machine with more RAM!"); break;
case -2147024809 : // fall through
case -2147024770 : alert("Sorry, UnZixHTA seems to have some problems with Windows Vista. As I have no access to Vista to debug UnZixHTA, I'm not able to troubleshoot this problem. If you're highly technically minded and come up with a work-around, please send correspondence to the bug report page mentioned in the source."); break;
default : propError(err, arguments);
}
return false;
}
}
/*
Function: getZixData()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function getZixData(strSourcePath) {
try {
var objStream = new ActiveXObject("ADODB.Stream");
try {
objStream.Open();
objStream.Type = adTypeText;
objStream.CharSet = "utf-8";
setTitle("Zix File Information | Loading Zix File Into Memory");
objStream.LoadFromFile(strSourcePath); // Load entire file to RAM :'(
var intByteSize = objStream.Size;
objStream.Position = intByteSize; // Set to EOS
var blnSentinel = true; // Loop sentinel
var arrRawZixData = [];
var strStringBuffer = ""; // We hold a copy of the last 11 chars so that we can detect the start of the zix data section
var blnFoundDataSection = false;
setTitle("Zix File Information | Scanning For Metadata");
for (var i=intByteSize; i>0 && blnSentinel; --i) {
objStream.Position = i;
var strChar = objStream.ReadText(1); // Read a byte as UTF-8 char
if (strChar == null) {
continue;
}
else if (strChar.charCodeAt(0) < 32) {
blnSentinel = false;
break;
}
else {
arrRawZixData.push(strChar);
strStringBuffer = strChar.toLowerCase() + strStringBuffer.substring(0, 10);
// Stop at the start of the zix data section
if (strStringBuffer === "d8:announce") {
blnSentinel = false;
blnFoundDataSection = true;
break;
}
}
}
}
finally {
if (objStream && objStream.State != adStateClosed) {
objStream.Close();
}
}
if (!blnFoundDataSection) {
setTitle("Zix File Information | Metadata Not Found");
return null;
}
else {
var arrZixData = [];
setTitle("Zix File Information | Metadata Found");
var strRawData = arrRawZixData.reverse().join(""); // Assemble zix data section string
arrRawZixData = null;
var reNameKey = /4\:name(\d+)\:/i;
var reNameKeyData = /4\:name(\d+)\:(.*)/i;
var reOrigSize = /4\:sizei(\d+)e/ig;
var reOffset = /5\:starti(\d+)e/ig;
var arrOrigSize = strRawData.match(reOrigSize);
var arrOffset = strRawData.match(reOffset);
if (!arrOrigSize || !arrOffset) {
setTitle("Zix File Information | Unable to Parse Zix File");
return null;
}
var intNumFiles = arrOrigSize.length;
var intNumFolders = 0;
if (!intNumFiles) {
return null;
}
else {
// Loop over all file markers in zix file. Some files are actually folders, which we skip.
for (var i=0; i<intNumFiles; ++i) {
var objZixData = {size:null, filename:null, offset:null};
objZixData["size"] = Number(arrOrigSize[i].replace(reOrigSize, "$1")); // size of content
// Use the content size to indicate if the content is a folder or not
if (objZixData["size"] != 0) {
// OK, content is a file, get the file's name and offset
reNameKeyData.test(strRawData);
objZixData["filename"] = (RegExp.$2).substring(0, Number(RegExp.$1)); // filename
strRawData = strRawData.replace(reNameKey, ""); // remove filename match from data so not re-detected on next loop
/* Byte at which file content starts. 'intNumFolders' is used because folders don't have such a
marker so the arrOffset array is shorter than the arrOrigSize array, so it stops us reading
past the end of the arrOffset array.
*/
objZixData["offset"] = Number(arrOffset[i - intNumFolders].replace(reOffset, "$1"));
arrZixData.push(objZixData); // Add file to array of zix file contents
}
else {
// OK, content was a folder - ignore it (we'll still process its content later though)
objZixData = null;
++intNumFolders; // The current difference between the number of file markers and the number of actual files
}
}
return arrZixData;
}
}
}
catch (err) {
switch (err.number) {
case -2146824572 : alert("Security settings on this computer are preventing unZixHTA from opening the zix file. That's generally a good thing, but unZixHTA won't work if it can't open the zix file! Read the advice in the Security section at: http://andrewu.co.uk/tools/unzixhta/#security"); break;
case -2146827859 : alert("Sorry, this application relies on the ADODB.Stream ActiveX component but this component is not available for use on this computer. You'll also see this message if for some reason you've opened this application inside Internet Explorer rather than executing it on its own"); break;
case -2146825286 : alert("The file appears to be in use by another application and cannot be opened. Close any applications that may have handles to this file and try again."); break;
case -2146828281 : // fall through
case -2147024882 : alert("Sorry, you don't have enough free RAM to open this file. Unfortunately UnZixHTA needs at least as much free RAM as the size of the zix file it is processing. Either close other programs and try again, reboot and try again before opening any other applications, or borrow someone else's machine with more RAM!"); break;
case -2147024809 : // fall through
case -2147024770 : alert("Sorry, UnZixHTA seems to have some problems with Windows Vista. As I have no access to Vista to debug UnZixHTA, I'm not able to troubleshoot this problem. If you're highly technically minded and come up with a work-around, please send correspondence to the bug report page mentioned in the source."); break;
default : propError(err, arguments);
}
}
}
/*
Function: getNullStreamPercent()
Description: Checks to see if a stream contains enough information
Returns: Number (Integer) between 1 and 100
History:
20070528 1432UTC v1 Andrew Urquhart Created
*/
function getNullStreamPercent(strSourcePath, intStartByte, intEndByte) {
try {
if (!strSourcePath) {
throw new Error(1, "Expected parameter \"strSourcePath\" was not defined");
}
if (!isNumber(intStartByte) || intStartByte < 0) {
throw new Error(2, "Parameter \"intStartByte\" was not a positive number or zero");
}
if (!isNumber(intEndByte) || intEndByte < 0) {
throw new Error(3, "Parameter \"intEndByte\" was not a positive number or zero");
}
if (intStartByte >= intEndByte) {
throw new Error(4, "Parameter intStartByte (" + intStartByte + ") must be less than intEndByte (" + intEndByte + ")");
}
var objStream = new ActiveXObject("ADODB.Stream");
try {
objStream.Open();
objStream.Type = adTypeText;
objStream.CharSet = "utf-8"; // May cause false positives if the file charset is not utf-8
setTitle("Scanning For Missing Content | Loading Zix File Into Memory");
objStream.LoadFromFile(strSourcePath); // Load entire file to RAM :'(
var intByteSize = objStream.Size;
if (intByteSize == 0) {
return 100;
}
if (intEndByte > intByteSize) {
throw new Error(5, "Parameter \"intEndByte\" (" + intEndByte + ") exceeds maximum size of actual file (" + intByteSize + ")");
}
var intNullCount = 0;
var intCount = 0;
var intMaxCount = Math.min(4000, intByteSize); // Number of steps to sample. 4000 is arbitrary.
var intByteStep = Math.ceil(intByteSize / intMaxCount); // Number of bytes between performing a sampling (inclusive)
var intReportStep = Math.floor(intMaxCount / 10); // Frequency on which to report scanning progess to GUI
for (var i=intStartByte; i<=intEndByte; i+=intByteStep, ++intCount) {
if (intCount % intReportStep === 0) {
setTitle("Scanning For Missing Content | Sampling " + (Math.floor(100*intCount/intMaxCount)) + "%");
}
objStream.Position = i;
if (objStream.ReadText(1) == 0) {
// Sampled character was an empty byte, or appeared to be because the charset was incorrect for the file type
++intNullCount;
}
}
setTitle("Scanning For Missing Content");
return Math.floor((intNullCount / intCount) * 100); // Precentage of null bytes
}
finally {
if (objStream && objStream.State != adStateClosed) {
objStream.Close();
}
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: unzixAndSave()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function unzixAndSave() {
try {
// Worker function invoked after a pause to allow the GUI to update
function unzixAndSave_worker() {
try {
var strSourcePath = getNode("sourcefilepath").value;
if (!strSourcePath) {
alert("Please select the *.zix file to recover");
reset();
return;
}
if (strSourcePath) {
cls();
echo("<h2>Recover & Save</h2>\r\n");
var blnProcessed = unZixHTA(strSourcePath);
switch(blnProcessed) {
case true : setTitle("Recover & Save | Completed"); echo("<strong>Completed</strong>\r\n\r\nAs a precaution you should use your anti-virus and anti-spyware tools to scan this file/folder for critters\r\n"); break;
case false : setTitle("Recover & Save | Failed"); echo("\r\n<strong class=\"error\">Operation Failed</strong>\r\n"); break;
default : reset();
}
}
}
catch (err) {
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
finally {
getNode("andrewu_unzixhta").style.cursor = "auto";
}
}
this.unzixAndSave_worker = unzixAndSave_worker;
cls();
setTitle("Recover & Save");
echo("<strong>Please Wait:</strong> This can be a very slow process on older machines or machines with less than <strong>2GB</strong> of RAM. The application may <em>appear</em> to hang on such machines…");
getNode("andrewu_unzixhta").style.cursor = "wait";
getNode("preamble").style.display = "none";
setTimeout("unzixAndSave_worker()", 500);
}
catch (err) {
getNode("andrewu_unzixhta").style.cursor = "auto";
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
}
/*
Function: zixInfo()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function zixInfo() {
try {
// Worker function invoked after a pause to allow the GUI to update
function zixInfo_worker() {
try {
var strSourcePath = getNode("sourcefilepath").value;
if (!strSourcePath) {
alert("Please select the *.zix file to examine");
reset();
return;
}
if (strSourcePath) {
cls();
echo("<h2>Zix File Information</h2>\r\n");
var arrZixData = getZixData(strSourcePath);
var arrOut = [];
if (!arrZixData) {
alert("Unable to retrieve some or all of the raw ZIX data - you may have to process this file manually yourself using a HEX Editor");
reset();
return;
}
else {
var blnFilterMalware = getNode("filtermalware_1").checked;
var blnCheckIfEmpty = getNode("checkifempty_1").checked;
var intFilterCount = 0;
arrOut.push("<p>The contents of the zix file are shown below:</p>");
arrOut.push("<dl>");
for (var i=0, j=arrZixData.length; i<j; ++i) {
var objZixData = arrZixData[i];
if (!objZixData || objZixData["offset"] == null || !objZixData["size"] || !objZixData["filename"]) {
arrOut.push("<dt class=\"error\">Unable to retrieve some of the ZIX file information, skipping one file</dt>");
continue;
}
else {
var blnLooksLikeMalware = RE_AVG_HAZARDOUSFILES.test(objZixData["filename"]);
if (blnLooksLikeMalware && blnFilterMalware) {
++intFilterCount;
continue;
}
else {
arrOut.push("<dt><strong>File: <code>" + hEnc(objZixData["filename"]) + "</code></strong></dt>");
arrOut.push("<dd><strong>Size:</strong> <code>" + hEnc(getBytesToReadableString(objZixData["size"])) + " (" + hEnc(objZixData["size"]) + "</code> bytes)</dd>");
if (blnLooksLikeMalware) {
arrOut.push("<dd class=\"error\"><strong>Malware Threat:</strong> You must check this file with anti-virus and anti-spyware tools</dd>");
}
if (blnCheckIfEmpty) {
var intNullPercent = getNullStreamPercent(strSourcePath, objZixData["offset"], (objZixData["offset"] + objZixData["size"]));
if (intNullPercent > 20) {
arrOut.push("<dd class=\"error\"><strong>Empty File:</strong> This file appears to be empty or partially empty of content.</dd>");
}
}
}
}
}
arrOut.push("</dl>");
if (intFilterCount > 0) {
arrOut.push("\r\n\r\n<p><strong>Malware Note:</strong> " + intFilterCount + " files were omitted as they <em>may</em> potentially contain viruses, trojans, adware, etc.</p>");
}
}
setTitle("Zix File Information | Finished");
arrOut.push("\r\n<p><strong>Finished</strong> (This was a read-only operation and no files have been saved)</p>\r\n");
echo(arrOut.join(""));
}
}
catch (err) {
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
finally {
getNode("andrewu_unzixhta").style.cursor = "auto";
}
}
this.zixInfo_worker = zixInfo_worker;
cls();
setTitle("Zix File Information");
echo("<strong>Please Wait:</strong> This can be a slow process on older machines or machines with less than 1GB of RAM. The application may <em>appear</em> to hang on such machines…");
getNode("preamble").style.display = "none";
getNode("andrewu_unzixhta").style.cursor = "wait";
setTimeout("zixInfo_worker()", 500);
}
catch (err) {
getNode("andrewu_unzixhta").style.cursor = "auto";
try {
propError(err, arguments);
}
catch (err) {
doGenerateErrorReport(err);
}
}
}
/*
Function: echo()
Description:
Returns:
History:
20070521 1619UTC v1 Andrew Urquhart Created
*/
function echo(strHTML, blnPreserveLineBreaks) {
try {
var objResults = getNode("results");
if (objResults) {
objResults.style.display = "block";
objResults.innerHTML += (blnPreserveLineBreaks ? strHTML : strHTML.replace(/\r\n|\r|\n/g, "<br />"));
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: cls()
Description:
Returns:
History:
20070526 1219UTC+1 v1 Andrew Urquhart Created
*/
function cls() {
try {
var objResults = getNode("results");
if (objResults) {
objResults.style.display = "block";
objResults.innerHTML = "";
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: reset()
Description:
Returns:
History:
20070526 1233UTC+1 v1 Andrew Urquhart Created
*/
function reset() {
try {
cls();
setTitle();
getNode("andrewu_unzixhta").style.cursor = "auto";
var objPreamble = getNode("preamble");
if (objPreamble) {
objPreamble.style.display = "block";
}
var objResults = getNode("results");
if (objResults) {
objResults.innerHTML = "";
objResults.style.display = "none";
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: hEnc()
Description: Basic Server.HTMLEncode'ing
Returns: String
History:
20060330 1447BST v1 Andrew Urquhart Created
*/
function hEnc(strRawHTML) {
try {
return (strRawHTML === null || strRawHTML === undefined ? "" : ("" + strRawHTML).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">")); // "
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: getNode()
Description:
Returns:
History:
20060420 0930BST v1 Andrew Urquhart Created
*/
function getNode(strElementId) {
try {
var objElement = document.getElementById(strElementId);
if (!objElement) {
return null;
};
return objElement;
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: getBytesToReadableString()
Description:
Returns:
History:
20041206 0123GMT v1 Andrew Urquhart Created
*/
function getBytesToReadableString(intBytes) {
try {
if (intBytes < 1048576) {
return Math.ceil(intBytes/10.24)/100 + "KB";
}
else if (intBytes < 1073741824) {
return Math.ceil(intBytes/10485.76)/100 + "MB";
}
else {
return Math.ceil(intBytes/10737418.24)/100 + "GB";
}
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: isNumber()
Description:
Returns:
History:
20060608 0928BST v1 Andrew Urquhart Created
*/
function isNumber(varData) {
if (typeof varData !== "number" || isNaN(varData)) {
return false;
}
return true;
}
/*
Function: setTitle()
Description:
Returns:
History:
20070528 1517BST v1 Andrew Urquhart Created
*/
function setTitle(strText) {
document.title = "UnZixHTA" + (strText ? " | " + strText : "");
}
/*
Function: doCreateDirectoryPath()
Description: Build a hierarchy of directories up to the last directory in a path. Ignores folders that already exist.
Returns:
History:
20040815 1920BST v1 Andrew Urquhart Created
*/
function doCreateDirectoryPath(strFilepath) {
try {
if (!strFilepath) {
throw new Error(1, "Required parameter \"strFilepath\" was not defined");
}
var objFSO = new ActiveXObject("Scripting.FileSystemObject");
if (!objFSO) {
throw new Error(2, "Failed to create \"Scripting.FileSystemObject\" object");
}
var dirs = strFilepath.replace(/\//g, "\\").replace(/(\w:)\\?/, "").split("\\");
var dirPathStack = RegExp.$1 + "\\";
for (var d=0; d<dirs.length - 1; d++) {
if (dirs[d]) {
dirPathStack += dirs[d] + "\\";
try {
if (!objFSO.FolderExists(dirPathStack)) {
// Folder doesn't exist, so create it
try {
objFSO.CreateFolder(dirPathStack);
}
catch (err) {
throw new Error(err.number, "CreateFolder() failed with argument \"" + dirPathStack + "\". Message: " + err.description);
}
}
else {
// Folder already exists, skip it
continue;
}
}
catch (err) {
// Ignore 'permission denied' and 'file exists' errors
if (err.number == -2146828218 || err.number == -2146828230) {
continue; // Try to build the path all the way to the last directory - we may be being denied only because we're outsite the servable root and we may eventually pop back into it
}
else {
throw err;
}
}
}
}
// Return a Boolean as to whether directory path exists
return objFSO.FolderExists(dirPathStack);
}
catch (err) {
propError(err, arguments);
}
}
/*
Function: propError()
Description: Propagate an Error up the call chain, adding function name and parameter information about each stage in the chain
Returns:
History:
20060302 1145GMT v1 Andrew Urquhart Created
*/
function propError(errError, argsCaller) {
var arrTree = [];
try {
arrTree.push("\r\n========================================");
if (typeof arguments.callee.count != "number") {
arguments.callee.count = 1;
}
else {
++arguments.callee.count;
}
arrTree.push("LEVEL: " + arguments.callee.count);
// GET FUNCTION NAME AND VALUE OF FUNCTION PARAMETERS
if (argsCaller) {
var arrArgs = [];
for (var i=0, j=argsCaller.length; i<j; ++i) {
arrArgs.push(argsCaller[i]);
}
argsCaller.callee.toString().match(/function\s*([\w\d]+)\s*\(([^\)]*)/i);
var strFunctionName = RegExp.$1;
arrTree.push("FUNCTION: " + strFunctionName + "(" + arrArgs.join(", ") + ")");
}
else {
arrTree.push("FUNCTION: [Unknown - no caller arguments provided]");
}
arrTree.push("NUMBER: " + errError.number);
arrTree.push("MESSAGE: " + errError.message);
}
catch (err) {
try {
throw new Error(err.number, "Function propError() failed with parameters: errError=\"" + errError + "\", argsCaller=\"" + argsCaller + "\" and message:\r\n" + err.description);
}
catch (err) {
doGenerateErrorReport(err);
}
return;
}
// Cascade errors on up the call chain
throw new Error(errError.number, arrTree.join("\r\n"));
}
/*
Function: doGenerateErrorReport()
Description: Prepare an error report, and if confirmed, submit it to reporting server
Returns:
History:
20060302 1145GMT v1 Andrew Urquhart Created
*/
function doGenerateErrorReport(errError) {
try {
cls();
reset();
setTitle("Zix File Information");
getNode("preamble").style.display = "none";
arrOut = [];
arrOut.push("<div id=\"function_dogenerateerrorreport\">");
arrOut.push("<h2 class=\"error\">Application Error</h2>");
arrOut.push("<p class=\"error\">Sorry, an unhandled error has occurred in UnZixHTA.</p>");
arrOut.push("<p class=\"error\">You can help fix this problem by submitting the following error report to Andrew Urquhart (the author).</p>");
arrOut.push("<p class=\"error\">To submit the anonymous error report choose the \"Submit Error Report\" button (you may have to scroll down). This will open a web browser window (probably Internet Explorer) and post the information displayed below to the <a href=\"http://andrewu.co.uk/\">http://andrewu.co.uk</a> website. This information will not be made public or disclosed to a third party, its sole use is for debugging UnZixHTA. You may edit the information in the form below if you think it contains private or sensitive information (i.e. file names) or add your own notes.</p>");
arrOut.push("<form action=\"http://andrewu.co.uk/contact/process.asp\" method=\"post\" onsubmit=\"setTimeout('reset()', 1000);\">");
arrOut.push("<fieldset>");
arrOut.push("<input name=\"subjectBox\" type=\"hidden\" value=\"$Error report: UnZixHTA\">");
arrOut.push("<textarea name=\"message\" rows=\"10\" cols=\"40\" class=\"textbox\">");
arrOut.push(hEnc(errError.description));
arrOut.push("</textarea>");
arrOut.push("<input type=\"submit\" class=\"button\" value=\"Submit Error Report ►\">");
arrOut.push("</fieldset>");
arrOut.push("</form>");
arrOut.push("</div>");
echo(arrOut.join(""), true);
}
catch (err) {
alert("Function doGenerateErrorReport() failed with parameters: errError=\"" + errError + "\" and message:\r\n" + err.description);
}
}
/*
Function: objectEnumerate()
Description: Overrides the default implicit cast to string function for Objects in order to enumerate their contents in CSV format
Returns: CSV String
History:
20060203 1256GMT v1 Andrew Urquhart Created
*/
function objectEnumerate() {
var a = [];
for (var e in this) {
if (typeof this[e] != "function") {
a.push("\"" + e + "\": \"" + ("" + this[e]).replace(/"/g,"\\\"") + "\"")
}
}
return a.join(",");
}
//]]>
</script>
</head>
<body id="andrewu_unzixhta">
<h1>UnZixHTA 2.04</h1>
<form method="get" onsubmit="return false;">
<fieldset>
<legend>Select the zix file</legend>
<label for="sourcefilepath">Zix File:</label>
<input type="file" class="textbox" id="sourcefilepath"/>
<br/>
<label>Filter out potential malware:</label>
<label for="filtermalware_1">Yes</label>
<input type="radio" name="filtermalware" id="filtermalware_1" value="1" checked="checked"/>
<label for="filtermalware_2">No</label>
<input type="radio" name="filtermalware" id="filtermalware_2" value="0"/>
(Yes is recommended)
<br/>
<label>Check for blank file content:</label>
<label for="checkifempty_1">Yes</label>
<input type="radio" name="checkifempty" id="checkifempty_1" value="1"/>
<label for="checkifempty_2">No</label>
<input type="radio" name="checkifempty" id="checkifempty_2" value="0" checked="checked"/>
(No is much faster)
<br/>
<input type="button" class="button" value="Zix File Information ►" onclick="zixInfo();"/>
<input type="button" class="button" value="Recover & Save ►" onclick="unzixAndSave();"/>
</fieldset>
</form>
<div id="results"></div>
<div id="preamble">
<h2>Notes</h2>
<p>
This Windows-only HTML Application will strip the rubbish from a <code>.zix</code> file to recover the original
contents and then save it to your PC.
</p>
<p>
Button “Zix File Information” will examine the selected zix file — this reads the zix file and
reports its contents (this may take a few seconds).
</p>
<p>
Button “Recover & Save” attempts to recover the contents of the zix file. Recovered files are saved
into the same folder as the selected zix file. This may take up to a minute or so.
</p>
<p>
Option “Filter out potential malware” will omit files of certain potentially hazardous file extensions
(e.g. executable files) from both the “Zix File Information” and “Recover & Save”
options. Default is “yes”.
</p>
<p>
Option “Check for blank file content” will scan each file within the zix file for files that contain
no data / are empty of content. Default is “no”.
</p>
</div>
<p class="credit">Author: <a href="http://andrewu.co.uk/tools/unzixhta/">Andrew Urquhart</a>, version: 2.04</p>
</body>
</html>