#!/Lighty2Go/cgi-bin/python
#print "============  S T A R T ================"

# %Z% %M% Version %I% %H% %T%
import sys
from sys import stdin
from sys import stdout
#from ft_ss_elements import get_root, get_tool, get_engine, get_data_set
#from ft_ss_cases import case_desc
import os
import platform #MTL imported the platform module
import re
import datetime
import time
import hashlib
import subprocess
import unicodedata

#############################################################
#                                                           #
#  Generate Federated Testing Report -- String Search       #
#                                                           #
#                                                           #
#############################################################


import codecs
import sys

#9000:rifle:10586804:20677:FMT-winchester-doc-utf8-win.doc:fat32:utf-8
#9001:flintlock:10119849:19765:FMT-musket-doc-utf8fmt-win.doc:fat32:utf-8
#9002:revolver:9706333:18957:FMT-colt-doc-utf16-win.doc:fat32:utf-16-be
#9003:shotgun:10353531:20221:FMT-smoothbore-doc-utf16fmt-win.doc:fat32:utf-16-be
#9004:peroxide:zipped:zipped:FMT-explosive-docx-win.docx:fat32:UTF
#9005:nitroglycerin:zipped:zipped:FMT-dynamite-docxfmt-win.docx:fat32:UTF
#9006:longbow:9679255:18904:FMT-arrow-html-win.html:fat32:utf-8
#9007:crossbow:9691538:18928:FMT-bolt-htmlfmt-win.html:fat32:utf-8

#print "Content-Type: text/html\n\n"
#print "<html>"
#print "<TITLE>Generate Test Report</TITLE>"
#print "<body><H1>"
#print 'String Search REPORT saved'
#print 'REPORT saved to %s'%(html_file)
#print "</H1>"
#if not os.access(root+conf,os.F_OK):
#	print '<h1>You need to go back and enter the tool name & version</h1>'
#print mk_home_link ()
#print "<p>Done: %s" % now.strftime("%A, %B %d, %Y at %H:%M")
#print "</body>"
#print "</html>"

def get_ver():
	# find: where is our federated testing version and release information?	
	if os.access('/Lighty2Go/HTDOCS/include/global.php',os.F_OK) == 1:
		vname = '/Lighty2Go/HTDOCS/include/global.php'
	elif os.access('/Lighty2Go/HTDOCS/include/global.php',os.F_OK) == 1:
		vname = '/Lighty2Go/HTDOCS/include/include/global.php'
	else:
		vname = ''
		
	if vname != '':
		vfile = open (vname,"rt")
		n = 2
		rel_date = "Unknown"
		rel_ver = "Unknown"
		for line in vfile:
			pass
			if line.startswith("$release_date"):
				rel_date = line.split('"')[1]
				n = n - 1
			if line.startswith("$version"):
				rel_ver = line.split('"')[1]
				n = n - 1
			if n == 0: break
		vfile.close()
		#return "%s, released %s"%(rel_ver,rel_date)
		#return rel_ver
		return rel_ver, rel_date
	else: return "Unknown Version"

fmt_fix = {
'rifle':'.doc UTF-8',
'flintlock':'Formatted .doc UTF-8',
'revolver':'.doc UTF-16',
'shotgun':'Formatted .doc UTF-16',
'peroxide':'.docx',
'nitroglycerin':'Formatted .docx',
'longbow':'.html',
'crossbow':'Formatted .html'

}

name_fix = {'FT-SS-06':'fox and not tiger',
'FT-SS-04':'panda and fox',
'FT-SS-10-Hex':'panda'}

case_desc = {
'FT-SS-01':'Find an ASCII string',
'FT-SS-02':'Search for a substring and search ignore case',
'FT-SS-03':'Search for words (whole word match vs a substring match)',
'FT-SS-04':'Search Logical AND',
'FT-SS-05':'Search Logical OR',
'FT-SS-06':'Search Logical NOT',
'FT-SS-07-Latin':'Search Unicode Latin characters with diacritical marks',
'FT-SS-07-CJK-char':'Search Unicode Chinese/Japanese ideograms (Asian)',
'FT-SS-07-CJK-hangul':'Search Unicode CJK Korean Hangul (Asian)',
'FT-SS-07-CJK-kana':'Search Unicode CJK Japanese phonetic Kana (Asian)',
'FT-SS-07-Cyrillic':'Search Unicode Cyrillic (Russian)',
'FT-SS-07-RTL':'Search Unicode Right-To-Left (Arabic)',
'FT-SS-07-NoBOM':'Search Unicode 16 without a byte-order-mark',
'FT-SS-07-Norm':'Search Unicode for normalized diacritic marks (NFC &amp; NFD) and ligatures (NFKC &amp; NFKD)',
'FT-SS-08-Email':'Search tool-defined queries -- Email Address',
'FT-SS-08-Phone':'Search tool-defined queries -- Telephone Number',
'FT-SS-08-SS':'Search tool-defined queries -- Social Security Number',
'FT-SS-09-Doc':'Search Formatted Document Text',
'FT-SS-09-Frag*':'Search Fragmented File',
'FT-SS-09-Lost*':'Search Inaccessible (lost) Areas',
'FT-SS-09-MFT*':'Search File in NTFS Master File Table (MFT)',
'FT-SS-09-Meta':'Search file name substring in Meta-data',
'FT-SS-09-Stem':'Search for matches to word stem',
'FT-SS-10-Hex':'Search Hexadecimal Character Match',
'FT-SS-10-Regex':'Search Pattern Character Match',
#'FT-SS-12':'Search Multiple Images',
#'FT-SS-16':'Search Pattern Class Match',
#'FT-SS-17':'Search Pattern Escape Match',
#'FT-SS-18':'Search Pattern Group Terms',
#'FT-SS-19':'Search Pattern Repeat Term',
	}


try:
	sd = codecs.open("/Lighty2Go/media/cftt/FT-LOGS/ss-test-report-dribble.txt","wt",'utf-8')
except IOError as e:
	sd = open("/Lighty2Go/media/cftt/FT-LOGS/null.txt","wt") #MTL


def get_root ():
	if os.access ('/Lighty2Go/media/cftt/FT-LOGS',os.F_OK): return '/Lighty2Go/media/cftt/FT-LOGS'
	if os.access ('/Lighty2Go/media/cftt/FT-LOGS',os.F_OK): return '/Lighty2Go/media/cftt/FT-LOGS'
	if os.access ('/Lighty2Go/media/cftt/FT-LOGS',os.F_OK): return '/Lighty2Go/media/cftt/FT-LOGS'
#	if not os.access ('/Lighty2Go/media/cftt/FT-LOGS',os.F_OK):
#		os.mkdir ('/Lighty2Go/media/cftt/FT-LOGS')
#	return '/Lighty2Go/media/cftt/FT-LOGS'
	return '/Lighty2Go/media/cftt/FT-LOGS' #MTL set absolute path

def get_tool (root):
	tf = open(root+'/ss_tool.txt',"r")
	sd.write ('Open '+root+'/ss_tool.txt\n')
	buff = tf.read()
#	print 'buff: ',buff,':xxxx ',len(buff),' ==='
	if len(buff) == 0:
		buff = 'No Tool\nNo Version\n'
	return buff.split('\n')[0:2]

def get_engine (root):
	if not os.access (root+'/ss_se.txt',os.F_OK): return 'NoEngine'
	tf = open(root+'/ss_se.txt',"r")
	sd.write ('Open '+root+'/ss_se.txt\n')
	buff = tf.read()
	return buff.split()[0]

def get_data_set (root):
	if not os.access (root+'/ss_ds.txt',os.F_OK): return 'NoDataSet'
	tf = open(root+'/ss_ds.txt',"r")
	sd.write ('Open '+root+'/ss_ds.txt\n')
	buff = tf.read()
	return buff.split()[0]

id_index = {}
# id_index[id] = (search_string, partition, encoding)
str_cnt = {}
# str_cnt [string] = (part_list,enc_list,cnt,location_list)

def build_id_index (index_file):
	global id_index
	global str_cnt
	sd.write ('\n\n============\nCurrent working directory '+os.getcwd()+'\n')
	ids = codecs.open (index_file,'r','utf-8')
	sd.write ('\n\n============\nOpen '+index_file+'\n')
	for line in ids:
		line = line.strip()
		fields = line.split(':')
		if len(fields) > 4:
			rec = (fields[0],fields[1],fields[4],fields[-2],fields[-1])
			id_index[fields[0]] = rec
			(id,s,file_name,part,enc) = rec
			if s not in str_cnt:
				str_cnt [s] = [{},{},{},0]
			p_cnt = str_cnt [s][0]
			e_cnt = str_cnt [s][1]
			c_cnt = str_cnt [s][2]
			loc_class = 'active'
			if file_name.find('DELETED') != -1:
				loc_class = 'deleted'
			elif file_name.find('FRAG') != -1:
				loc_class = 'active'
			elif file_name.find('FMT') != -1:
#				sd.write ('FMT %s\n'%(file_name))
				if part == 'unalloc':
					loc_class = 'unalloc'
				else:
					loc_class = 'active'
			elif part == 'unalloc':
				loc_class = 'unalloc'
			elif file_name.find('UNKNOWN') != -1:
				loc_class = 'meta'
			str_cnt[s][3] += 1
			if enc in e_cnt:
				e_cnt[enc] += 1
			else:
				e_cnt[enc] = 1
			if part in p_cnt:
				p_cnt[part] += 1
			else:
				p_cnt[part] = 1
			if loc_class in c_cnt:
				c_cnt[loc_class] += 1
			else:
				c_cnt[loc_class] = 1
	for e in id_index:
		x = id_index[e]
		sd.write ('IDX %s %s %s %s\n'%(x[0].encode('utf-8'),x[1].encode('utf-8'),x[2].encode('utf-8'),x[3].encode('utf-8'))) #MTL explicitly using utf encode. Had gotten an ascii error.
#		print x[0],x[1],x[2],x[3]'

#build_id_index('win_string_doc.txt')
#build_id_index('/var/www/stringsearch/win_string_doc.txt')
if os.access ('/Lighty2Go/HTDOCS/stringsearch/win_string_doc.txt',os.F_OK):
	build_id_index('/Lighty2Go/HTDOCS/stringsearch/win_string_doc.txt')
else:
	build_id_index('win_string_doc.txt') #MTL set absolute path
#	global id_index
#	global str_cnt
ixxx = {}
ixxx ['Windows'] = (id_index,str_cnt)
id_index = {}
str_cnt = {}
#build_id_index('unix_string_doc.txt')
#build_id_index('unix_string_doc.txt')
if os.access ('/Lighty2Go/HTDOCS/stringsearch/unix_string_doc.txt',os.F_OK):
	build_id_index('/Lighty2Go/HTDOCS/stringsearch/unix_string_doc.txt')
else:
	build_id_index('unix_string_doc.txt') #MTL set absolute file path
ixxx ['UNIX'] = (id_index,str_cnt)
id_index = {}
str_cnt = {}

# make data structure with the expected strings for each case in each image
#
# x_sets[data-set][case] ==> set of strings
#
x_file = ' ' #MTL intialized x_file variable
def get_expected_by_case (ixxx):
	
	global x_file #MTL declared x_file variable to be global
	sd.write ("+++++++++++++++++++++ Expected by Case +++++++++++++++++\n")
	#x_names = {'UNIX':'unix-ex.txt','Windows':'win-ex.txt'}
	#x_names = {'UNIX':'/var/www/stringsearch/unix-ex.txt','Windows':'/var/www/stringsearch/win-ex.txt'}	
	if os.access ('/Lighty2Go/cgi-bin',os.F_OK): # case: we're running this on a Mac
		x_names = {'UNIX':'unix-ex.txt','Windows':'win-ex.txt'}
	else: # case: running this on our FT ISO 
		x_names = {'UNIX':'unix-ex.txt','Windows':'win-ex.txt'}
	x_sets = {}
	for ds in ('Windows','UNIX'):
		x_sets[ds] = {}
		try:
			sd.write ("=================== open %s ============\n"%(x_names[ds]))
			x_file = open (x_names[ds],'r')
		except IOError as eek:
			sd.write ('Open %s FAILED\n'%(x_names[ds]))
			sd.write ('Error message %s\n'%(eek))
			sd.write ('Current working directory '+os.getcwd()+'\n')
		this_case = 'no case'
		for line in x_file:
			if this_case.find('-06') > 0:
				sd.write ('TRACE %s\n'%(line.strip()))
			if line[:5] == 'Case:':
				h = line.strip().split(' ')
				if h[1] == 'Finished':
					sd.write ('x-file %s finished: '%(this_case))
					for x in x_strings:
						sd.write(x+' ')
					sd.write('\n')
					x_sets[ds][this_case] = x_strings
				else:
					sd.write ('x-file [%s] Start\n'%(h[1]))
					this_case = h[1]
					x_strings = set()
			else:
				if len(line) > 5:
					id = line[:4]
					if id.isdigit():
						target = ixxx[ds][0][id][1]
						x_strings.add(target)
						sd.write('ADD '+id+' '+target+' cnt = %d\n'%(len(x_strings)))
	sd.write ("+++++++++++++++++++++ END Expected by Case +++++++++++++++++\n")
	return x_sets
x_strings = get_expected_by_case(ixxx)
#print type (x_strings)
#print x_strings.keys()
#print type (x_strings['Windows'])
#print x_strings['Windows']['FT-SS-09-Lost']
#print 'COUNT ',x_strings['Windows']['FT-SS-06']

results = {}
class case_result:
	def __init__ (c):
		c.case = ''
		c.engine = ''
		c.data_set = ''
		c.n_active_targets = 0
		c.n_deleted_targets = 0
		c.n_unalloc_targets = 0
		c.n_active_hits = 0
		c.n_deleted_hits = 0
		c.n_unalloc_hits = 0
		c.comments = ''
		c.date = ''
		c.active_hits = []
		c.deleted_hits = []
		c.unalloc_hits = []
		c.meta_ref = []
		c.meta_hits = []


# collect summary results by case into "results"
def look (file):
	if not file.split('/')[-1].startswith('FT-SS-'): return
#	print 'Look at:',file
	result_file = open(file,'r')
	sd.write ('\n\n======== Open result file'+file+'\n\n')
	tc = case_result()
	engine = 'none'
	data_set = 'none'
	case_id = 'none'
	is_a_comment = False
	for line in result_file:
		dirty_line = line
		line = line.strip()
#		print line
		chunk = line.split(':')
		if chunk[0] == 'case':
			tc.case = chunk[1]
			case_id = tc.case
		elif chunk[0] == 'engine':
			engine = chunk[1]
			tc.engine = engine
		elif chunk[0] == 'dataset':
			data_set = chunk[1]
			tc.data_set = data_set
		elif chunk[0] == 'n_active_hits': tc.n_active_hits = int(chunk[1])
		elif chunk[0] == 'n_deleted_hits': tc.n_deleted_hits = int(chunk[1])
		elif chunk[0] == 'n_unalloc_hits': tc.n_unalloc_hits = int(chunk[1])
		elif chunk[0] == 'n_active_targets': tc.n_active_targets = int(chunk[1])
		elif chunk[0] == 'n_deleted_targets': tc.n_deleted_targets = int(chunk[1])
		elif chunk[0] == 'n_unalloc_targets': tc.n_unalloc_targets = int(chunk[1])
		elif chunk[0] == 'active_hit': tc.active_hits.append(chunk[1])
		elif chunk[0] == 'deleted_hit': tc.deleted_hits.append(chunk[1])
		elif chunk[0] == 'unalloc_hit': tc.unalloc_hits.append(chunk[1])
		elif chunk[0] == 'date': tc.date = chunk[1][:5]
		elif chunk[0] == 'meta_ref' :
			tc.meta_ref.append(line)
			sd.write ('META '+line.decode('utf-8'))
			sd.write (' %d\n'%(len(tc.meta_ref)))
		elif chunk[0] == 'meta_hit' : tc.meta_hits.append(line)
#		elif chunk[0] == 'comment': tc.comments = chunk[1]
		elif chunk[0] == 'comments': is_a_comment = True
		else:
			if is_a_comment:
				tc.comments += ' '+dirty_line
#				nr = 0
#				nc = 0
#				for ccc in line:
#					if ccc == '\r':
#						nr += 1
#						nc += 1
#				print 'nr %d nc %d'%(nr,nc)
#		else: tc.comments += ' '+line
	if data_set not in results: results[data_set] = {}
	if engine not in results[data_set]: results[data_set][engine] = {}
	results[data_set][engine][case_id] = tc
	sd.write("data set (%s), engine (%s), case ID (%s)\n"%(data_set,engine,case_id))

def walker(r):
	dlist = os.listdir (r)
	for f in dlist:
		if f[0] == '.': continue
		if os.path.isdir(r+'/'+f):
#			print 'Dir:',r+'/'+f
			walker (r+'/'+f)
		elif os.path.isfile(r+'/'+f): look(r+'/'+f)

root =  get_root ()
#print 'Root is:',root

start_time = str(datetime.datetime.now())
start_at = start_time
start_time = re.sub(r'\.[0-9]*','',start_time)
start_time = re.sub('[ :]','+',start_time)
html_file_name = "test-report-ss-"+start_time+".html"
html_file = root+'/'+html_file_name
try:
	html = open(html_file,"wt")
except IOError as e:
	print 'Failed to open:',html_file
	html = stdout

sd.write ('Open '+html_file+'\n')
sd.write ('html_file_name '+html_file_name+'\n')
print html_file_name
#os_info = os.uname() #MTL
os_info = platform.uname()  #MTL no os.uname under windows. Called platform module
#ver = "Tool: %Z% %M% Version %I% created %G% at %U%\n"+"<br>OS: %s Version %s\n"%(os_info[0],os_info[2])
#ver = "Tool: ft_ss_prt_test_report.py\n"+"<br>OS: %s Version %s\n"%(os_info[0],os_info[2])
ver = "OS: %s Version %s\n"%(os_info[0],os_info[2])
sd.write ('Generate String Search Test Report\n')
sd.write(ver+'\n')
sd.write ('Start: '+start_at+'\n')

sd.write("Args:"+str(sys.argv)+'\n')
sd.write('Full report\n'+html_file+'\n')


conf = "ss-config.txt"

tool_name = "No Tool\n"
tool_version = "No Version\n"

if not os.access(root+'/'+conf,os.F_OK):
	sd.write("There is no %s in %s\n"%(conf,root))
	#html.write("<h1>There is no %s in %s</h1>\n"%(conf,root))
	html.write("<h1>WARNING - There is no %s in %s (you probably haven't run any String Search tests yet)</h1>\n"%(conf,root))

try:
	config_file = open(root+'/'+conf,"rt")
except IOError as e:
	exit(0)

for line in config_file:
#	print line,
	if line.startswith('toolname'):
		tool_name = line[line.find(' '):-1].strip()
	if line.startswith('toolversion'):
		tool_version = line[line.find(' '):-1].strip()

#(tool_name,tool_version) = get_tool (root)

engine =  get_engine (root)
data_set = get_data_set (root)

sd.write ('tool: %s  Version %s\n'%(tool_name,tool_version))
sd.write ('Last Search Engine: %s\n'%(engine))
sd.write ('Last Data Set: %s\n'%(data_set))
walker(root)
for d in results:
	for e in results[d]:
		ck = results[d][e].keys()
#		print ck
		ck.sort()
		for c in ck:
			sd.write(results[d][e][c].case+' <==\n')
		sd.write('================================\n')
		for c in results[d][e]:
			sd.write(c+'\n')

#head = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
head = """<!DOCTYPE HTML >
<html>
<head>
  <meta content="text/html; charset=UTF-8"
 http-equiv="Content-Type">
  <title>Test Results for String Search Tool</title>
  <meta content="J Lyle NIST/CFTT" name="author">
  <meta content="Federated Testing String Search Test Results"
 name="description">
	<style>
	em.x {color: green;font-size: 100%;font-style:italic;font-weight:bold;}
	em.p {bgcolor: green;font-size: 100%;font-weight:bold;}
	th {
	   border-collapse: collapse;
	   border: 1px solid black;
	   text-align: center;
	   } 
	table, th, td {
	   border-collapse: collapse;
	   border: 2x solid black;
	   padding: 2px;
	   } 
/*body {counter-reset: chapter}

h1 {counter-reset: section}

h1:before {counter-reset: section subsec; counter-increment: chapter;
content: counter(chapter) ". ";}

h2:before {counter-reset: subsec; counter-increment: section;
content: counter(chapter) "." counter(section) ". ";}

h3:before { counter-increment: subsec;
content: counter(chapter) "." counter(section) "." counter(subsec) ". ";}*/

body {
counter-reset: chapter;
}
h1 {
counter-increment: chapter;
counter-reset: section;
}
h2 {
counter-increment: section;
counter-reset: sub;
}

h3 {
counter-increment: sub;
}
h1:before {
content: counter(chapter) ". ";
}
h2:before {
content: counter(chapter) "." counter(section) " ";
}

h3:before {
content: counter(chapter) "." counter(section) "." counter(sub) " ";
}

	</style>
</head>
<body>
"""
tail = """
</body>
</html>
"""

report_header_text_part_0 = '''
<em class="x">
Text formatted like this (Bold, Italics and Green) should be
removed and replaced. These are instructions
to the report writer. To see examples of completed String Search tool test reports, go to <a href="https://www.cftt.nist.gov">www.cftt.nist.gov</a> and select String Search.
</em>
<br>
<h1>Tool Description</h1>
<p>Tool Name: %s
<br>Tool Version: %s
<br>Tool Developer: 
<em class=x>Insert tool developer name and contact information.</em><br>
'''

report_header_text_part_1 = '''
<h1>Testing Organization</h1>
<em class=x>The following items
in this section may be included or omitted as per organizational
policy for tool test reports.</em>
<br>Organization conducting test:
<em class=x>Insert organization name.</em>
<br>Contact:
<em class=x>Insert contact name.</em>
<br>Report date:
<em class=x>Insert date.</em>
<br>Authored by:
<em class=x>Insert author name.</em>
<br>Reviewed by:
<em class=x>Insert name of reviewer.</em>
<br>Reviewed by date:
<em class=x>Insert date.</em>
<br>Approved:
<em class=x>Insert name of approving official.</em>
<br>Approved by date:
<em class=x>Insert date.</em>
<br>
<br>This test report was generated using CFTT's Federated Testing Forensic
Tool Testing Environment, see <a href="http://www.cftt.nist.gov/federated-testing.html">
Federated Testing Home Page</a>.
'''
report_header_text_part_2 = '''
<h1>How to Read This Report</h1>
This report is organized into the following sections:
<br><em class=x>Of course, you can reorganize sections, but then you should update
this list.</em><br>
<ol>
<li><b>Tool Description:</b> The tool name, version, and developer information 
are listed.
<li><b>Testing Organization:</b> Contact information and approvals.
<li><b>How to Read This Report:</b> This section.</li>
<li><b>Results Summary:</b> This section identifies any significant anomalies
observed in the test runs.
This section provides a narrative of key findings identifying where the tool meets
expectations and provides a summary of any ways the tool did not
meet expectations.
The section also provides any observations of interest about the tool or about testing
the tool including any observed limitations or organization imposed
restrictions on tool use.
<li><b>Test Environment & Selected Test Cases:</b>
Description of hardware, software and support 
environment (e.g., version of Federated Testing used) 
used in tool testing
 and a list identifying the applicable test cases 
from the Federated Testing String Search Test Suite.
<li><b>Test Result Details by Case:</b>
Automatically generated test results that identify anomalies.
</ol>

<h1>Results Summary</h1>
<em class=x>
Provide a narrative of key findings -- did the tool meet
expectations? If not, provide a summary of how the tool did not
meet expectations.
Provide any observations of interest about the tool or about testing
the tool including any observed limitations or organization imposed
restrictions on tool use.
</em>
<h1>Test Environment & Selected Test Cases</h1>
<p>This section describes test hardware, software, data sets, and test cases.</p>

<h2>Test Hardware and Software</h2>
<p><em class=x>List and describe any hardware used during testing in sufficient detail
to repeat the tests.</em></p>
<p>Testing was performed using CFTT's Federated Testing Test Suite Version %s.</p>
<p><em class=x>List and describe any additional software used during testing in sufficient detail
to repeat the tests.</em></p>

<h2>Test Data Sets and Test Cases</h2>
<p>This section discusses the test data sets and the test cases used in testing.</p>
<h3>Test Data Sets</h3>
<p>String search test data set package Version <em class=x>Insert version here</em> 
was used. The package can be downloaded from either the CFTT web site (<a href="https://www.cftt.nist.gov">www.cftt.nist.gov</a>
then select String Search) or the CFReDS web site
(<a href="https://www.cfreds.nist.gov">www.cfreds.nist.gov</a>). The package includes two dd files with known content.
One of the dd test images contains target strings within 
FAT, ExFAT, and NTFS file systems (Windows).
The other dd test image contains target strings within HFS+ journaled case insensitive (OSXJ),
HFS+ journaled case sensitive (OSXC), ext4, and 
APFS (Apple file system) file systems (UNIX-like).</p>
<p>In general, each target string is encoded in ASCII and located in both an active file and
a recoverable deleted file in each partition of the test image.
The Windows dd image also has a block of unallocated storage that contains the target
strings without a file system. 
Some of the target strings are also encoded in Unicode UTF-8, UTF-16BE and 
UTF-16LE with a byte-order-mark. Test case FT-SS-07 is organized to test 
language and Unicode specific situations such as Unicode UTF-16 without a 
byte-order-mark, Unicode text with and without combining characters (diacritic 
marks), and Unicode text with and without ligatures ("fi" as two characters and as 
one character). Test case FT-SS-09 is organized to test specific situations such 
as formatted strings, strings spanning file fragments, and strings located in 
inaccessible areas. Each instance of a target string also has a unique 
associated string ID located immediately after the target string. The string ID 
helps identify the specific string matched by the search tool.</p>
<h3>Test Case Descriptions</h3>
<p>The following table gives a brief description of available test cases in the data sets. Not 
all test cases are used for all data sets. To see what tests were run, see section 6.
<br>
<em class="x">
You can delete the row in the table for any cases not used.
</em>
'''
old_text_01 = '''
Some of the target strings are also encoded in Unicode
UTF-8, UTF-16BE and UTF-16LE with a byte-order-mark.
Test case FT-SS-09 is organized to test specific situations such as
formatted strings, strings spanning file fragments, Unicode UTF-16 without a byte-order-mark,
Unicode text with and without combining characters (diacritic marks),
Unicode text with and without ligatures ("fi" as two characters and as one character) 
and strings located in inaccessible areas. Each instance of a target string also
has a unique associated string ID located immediately after the target string.
The string ID helps identify the specific string matched by the search tool.

'''
main_section = '''
<p>Some test cases are for specific features, e.g., logical conditions (<b>and</b>, <b>or</b>, <b>not</b>),
built in searches (email, telephone numbers), etc. Three test cases (marked with "*"), FT-SS-09-Frag, FT-SS-09-Lost &
FT-SS-09-MFT, are only applied to the Windows data set.</p>
<p><em class=x>List any test cases that were not run here, e.g., test cases that apply to 
features that are not supported by the tool.</em></p>

<h1>Test Result Details by Case (per Data Set)</h1>
'''

table_head = """
<table style="text-align: left;" border="2" cellpadding="2" cellspacing="2">
<tbody>
"""

table_tail = """
</tbody>
</table>
"""

#######################################################
#     main
#########################################################
#########################################################
########### S t a r t    H e r e ########################
#########################################################
#########################################################


conf = "ss-config.txt"
sd.write("START\n")


#print "OK, So far"
#exit(0)
#path = root+'FT-LOGS'
#if not os.access(path,os.F_OK):
#	html.write("<h1>LOG Path: %s does not exist</h1>\n"%(path))
#	sd.write("<h1>LOG Path: %s does not exist</h1>\n"%(path))
#	shut_down(False)
#	shut_down(False)

#if os.access (path+'/'+conf,os_F_OK):
	
#try:
#	config_file = open(root+conf,"rt")
#except IOError as e:
#	exit(0)

#print tool_name,tool_version
html.write(head+'\n')
#html.write('''<h1>Test Results for String Search Tool:<br>
#	%s Version %s</h1>\n'''%(tool_name,tool_version))
#html.write('''<p style="font-size:1.8em"><b>Test Results for Hardware Write Block Tool:<br>
#	%s %s Firmware Version %s</b></p>\n'''%(config['vendor'],config['model'], config['fw']))
html.write('''<p style="font-size:1.8em"><b>Test Results for String Search Tool:<br>
	%s Version %s</b></p>\n'''%(tool_name,tool_version))
html.write(report_header_text_part_0%(tool_name,tool_version))
#html.write('''<h1>Test Results for String Search Tool:<br>
#	%s Version %s</h1>\n'''%(tool_name,tool_version))
#html.write(report_header_text_part_1%(tool_name,tool_version))
html.write(report_header_text_part_1)
ftver = str(get_ver()) #MTL
#ftver, ftdate = get_ver() #MTL changed to line above
html.write(report_header_text_part_2 %ftver)
html.write(table_head)
html.write ('<tr>')
html.write ('<th>Case</th>')
html.write ('<th>Case Description</th>')
html.write ('</tr>')
case_desc_keys = case_desc.keys()
case_desc_keys.sort()
for c in case_desc_keys:
	html.write ('<tr>')
	html.write ('<td>')
	html.write (c)
	html.write ('</td>')
	html.write ('<td>')
	html.write (case_desc[c])
	html.write ('</td>')
	html.write ('</tr>')
html.write(table_tail)

html.write(main_section)
#html.write('<h1>Test Result Details by Case</h1>')
sub_dirs = os.listdir(root)
se_list = []
for item in sub_dirs:
#	print 'item ==> %s'%(item)
	if item.startswith('se_') == True:
		se_list.append(item)
start_results = '''
<p>A string search tool may implement more than one search algorithm (also known as a search engine)
for searching text.
The two most common search engines are <i>indexed search</i> and <i>live search</i>.
An indexed search reads all the acquired data once before doing any searching and builds an index
to all words found. Each query can be looked up quickly in the index. A Live search reads
all the acquired data for each query.</p>
<p>This section presents test results by test image: Windows file systems, or UNIX-like
file systems. For each test image,
there is a result table for each search engine tested. Each table shows results by
test case of the number of expected search hits, the number of actual search hits
and the number of strings missed (i.e., expected hits minus actual hits) for allocated
files, deleted files and unallocated space.</p>
<p>The following search engines were tested: 
'''
table_desc = '''
<p>The table columns contain the following information:</p>
<ul>
<li><b>Case:</b> The test case identifier.</li>
<li><b>Expected String:</b> The strings that should be reported by the search.</li>
<li><b>Active Files:</b> A group of three columns (<b>Expected, Hits and Misses</b>) giving
the number of hits and misses when searching for the expected string in an active file.</li>
<li><b>Deleted Files:</b> A group of three columns (<b>Expected, Hits and Misses</b>) giving
the number of hits and misses when searching for the expected string in a deleted file.</li>
<li><b>Unallocated Space:</b> A group of three columns (<b>Expected, Hits and Misses</b>) giving
the number of hits and misses when searching for the expected string in unallocated space.</li>
<li><b>Expected:</b> The number of instances of the expected string found in the group (i.e., Active files,
Deleted files or Unallocated space).</li>
<li><b>Hits:</b> The number of times the expected string was found in the group.</li>
<li><b>Misses:</b> The number of times the expected string was missed (not found) in the group.</li>
</ul>

<p>In the Expected String column for test case FT-SS-09-DOC each string is labeled to
indicate features of the expected string. The labels include the file type
(.doc, .docx or .html) and the encoding of the string (if a .doc file).
If the string has embedded formatting it is 
labeled as <em>Formatted</em>, e.g., the string <em>crossbow</em>
has the substring <em>cross</em> formatted as bold and underlined, i.e., <b><u>cross</u></b>bow.</p>
<p>Note: the first row of results for a test case is a summary for all the strings that
should be found for that case.</p>

'''

html.write(start_results)

se_seen = set()
for ds in results:
	for se in results[ds]:
		se_seen.add(se)
n_se = len(se_seen)
for se in se_seen:
	n_se = n_se - 1
	html.write (se)
	if n_se == 0: html.write('.')
	if n_se == 1: html.write(' and ')
	if n_se > 1: html.write(', ')

sd.write ('RESULTS ---- %s\n'%(str(results.keys())))
for ds in results:
	sd.write ('######## %s ######\n'%(ds))
#ixxx = ['Windows'] = (id_index,str_cnt)
	(id_index,str_cnt) = ixxx[ds]
	belka = str_cnt
	eozh = belka['fox'][2]
	six = {}
	six [ds] = eozh
	for xyz in ('active','deleted','unalloc'):
		if xyz not in eozh:
#			print 'no %s in fox'%(xyz)
			eozh[xyz] = 0
	sd.write ('BELKA %d %d %d\n'%(eozh['active'],eozh['deleted'],eozh['unalloc']))
	eozh = belka['tiger'][2]
	for xyz in ('active','deleted','unalloc'):
		if xyz not in eozh:
#			print 'no %s in tiger'%(xyz)
			eozh[xyz] = 0
		six[ds][xyz] -= eozh[xyz]
	six[ds]['unalloc'] = 0
	sd.write ('slon %d %d %d\n'%(eozh['active'],eozh['deleted'],eozh['unalloc']))
	sd.write ('SIX %s %d %d %d\n'%(ds,six[ds]['active'],six[ds]['deleted'],six[ds]['unalloc']))
	html.write('<h2>Results for Data Set: %s</h2>'%(ds))
	html.write('<p>This section provides results for the %s data set.</p>'%(ds))
	for se in results[ds]:
		html.write('<h3>Results for %s Search of %s Data Set</h3>'%(se,ds))
		html.write(table_desc)
		ck = results[ds][se].keys()
		ck.sort()
		html.write(table_head)
		html.write('<tr><th colspan="11">')
		html.write('Results for %s Search of %s Data Set'%(se,ds))
		html.write('</th></tr>\n')
		html.write('<tr><th rowspan="2">')
		html.write('Case')
		html.write('</th><th rowspan="2">')
		html.write('Expected String')
		html.write('</th><th colspan="3">')
		html.write('Active Files')
		html.write('</th><th colspan="3">')
		html.write('Deleted Files')
		html.write('</th><th colspan="3">')
		#html.write('Unalloc Space')
		html.write('Unallocated Space')		
		html.write('</th></tr>\n')
		html.write('<tr><th>')
#		html.write('Case')
#		html.write('</th><th>')
		html.write('Expected')
		html.write('</th><th>')
		html.write('Hits')
		html.write('</th><th>')
		html.write('Misses')
		html.write('</th><th>')
		html.write('Expected')
		html.write('</th><th>')
		html.write('Hits')
		html.write('</th><th>')
		html.write('Misses')
		html.write('</th><th>')
		html.write('Expected')
		html.write('</th><th>')
		html.write('Hits')
		html.write('</th><th>')
		html.write('Misses')
		html.write('</th></tr>\n')
		for c in ck:    # for each case
			d = results[ds][se][c]
			str_set = set()
			hits_by_string = {}
			sd.write("=== %s scan for hits \n"%(d.case))
			if d.case.find('-06') >= 0:
				sd.write("=== THIS is the one %s scan for hits \n"%(d.case))
			for ahit in d.active_hits:
				sd.write ("    %s\n"%(ahit.decode('utf-8')))
#				sd.write("    ---> %s string index for active hit\n"%(ahit.split(' ')[0]))
				str_index = ahit.split(' ')[0]
				if str_index in id_index:
					seen_string = id_index[str_index][1]
					str_set.add(seen_string)
					if seen_string in hits_by_string:
						hits_by_string[seen_string][0] += 1
					else:
						hits_by_string[seen_string] = [1,0,0]
#					str_set.add(id_index[str_index][1])
#					str_set.add(id_index[ahit.split(' ')[0]][0])
#				else:
#					print '********** OUT *************',str_index

#				str_set.add(id_index[ahit.split(' ')[0]][0])
			for ahit in d.deleted_hits:
				sd.write ("    %s del\n"%(ahit.decode('utf-8')))
				str_index = ahit.split(' ')[0]
				if str_index in id_index:
					seen_string = id_index[str_index][1]
					str_set.add(seen_string)
					if seen_string in hits_by_string:
						hits_by_string[seen_string][1] += 1
					else:
						hits_by_string[seen_string] = [0,1,0]
#				sd.write ("    %s\n"%(ahit))
			for ahit in d.unalloc_hits:
				sd.write ("    %s unalloc\n"%(ahit.decode('utf-8')))
				str_index = ahit.split(' ')[0]
				if str_index in id_index:
					seen_string = id_index[str_index][1]
					str_set.add(seen_string)
					if seen_string in hits_by_string:
						hits_by_string[seen_string][2] += 1
					else:
						hits_by_string[seen_string] = [0,0,1]
#				sd.write ("    %s\n"%(ahit))
#				sd.write ("    %s\n"%(ahit))
			for hit_string in str_set:
				sd.write("    :::: %s %d %d %d"%(hit_string,hits_by_string[hit_string][0],
					hits_by_string[hit_string][1],hits_by_string[hit_string][2]))
				if hit_string in str_cnt:
					expect_cnt = str_cnt[hit_string][2]
					x_active = 0
					x_deleted = 0
					x_unalloc = 0
					if 'active' in expect_cnt:
						x_active = expect_cnt['active']
					if 'deleted' in expect_cnt:
						x_deleted = expect_cnt['deleted']
					if 'unalloc' in expect_cnt:
						x_unalloc = expect_cnt['unalloc']
					sd.write(" expected = [%d %d %d]"%(x_active,x_deleted,x_unalloc))
				sd.write("\n")
#				sd.write("    :::: %s\n"%(hit_string.decode('utf-8')))
#			html.write('<br>%s %s %s %d\n'%(d.case,d.engine,d.data_set,d.n_active_targets))
#			html.write('<br>')
			html.write('<tr><td>')
			html.write('%s'%(d.case))
			html.write('</td><td>')
			html.write('</td><td>')
			html.write(' %d'%(d.n_active_targets))
			html.write('</td><td>')
			html.write(' %d'%(d.n_active_hits))
			misses = d.n_active_targets - d.n_active_hits
			if misses != 0: marker = ' bgcolor="pink"'
			else: marker=''
			html.write('</td><td %s>'%(marker))
			html.write(' %d'%(misses))
			html.write('</td><td>')
			html.write(' %d'%(d.n_deleted_targets))
			html.write('</td><td>')
			html.write(' %d'%(d.n_deleted_hits))
			misses = d.n_deleted_targets - d.n_deleted_hits
			if misses != 0: marker = ' bgcolor="pink"'
			else: marker=''
			html.write('</td><td %s>'%(marker))
			html.write(' %d'%(misses))
			html.write('</td><td>')
			html.write(' %d'%(d.n_unalloc_targets))
			html.write('</td><td>')
			html.write(' %d'%(d.n_unalloc_hits))
			misses = d.n_unalloc_targets - d.n_unalloc_hits
			if misses != 0: marker = ' bgcolor="pink"'
			else: marker=''
			html.write('</td><td %s>'%(marker))
			html.write(' %d'%(misses))
			html.write('</td></tr>\n')
#
#		Do each String
#
#			print 'Doing ',d.case
			sd.write ('ROW ROW ROW ROW %s: '%(d.case))
			for row_string in str_set:
				sd.write (row_string+' ')
			sd.write('\n')
			sd.write ('XXX XXX XXX %s: '%(d.case))
			for row_string in x_strings[ds][d.case]:
				sd.write (row_string+' ')
			sd.write('\n')
			sd.write ('MISS MISS MISS %s: '%(d.case))
			for row_string in x_strings[ds][d.case]-str_set:
				sd.write (row_string+' ')
				xc = str_cnt[row_string][2]
				sd.write (str(xc.keys()))
#				sd.write ('<%d,%d,%d> '%(xc['active'],xc['deleted'],xc['unalloc']))
				x_active = 0
				x_deleted = 0
				x_unalloc = 0
				if 'active' in xc:
					x_active = xc['active']
					sd.write ('<%d> '%(xc['active']))
				if 'deleted' in xc:
					x_deleted = xc['deleted']
				if 'unalloc' in xc:
					x_unalloc = xc['unalloc']
				sd.write('\n<tr><td></td><td>%s</td>'%(row_string))
#				html.write('\n<tr><td></td><td>%s</td>'%(row_string))
				html.write('\n<tr><td></td><td>')
				if row_string in fmt_fix:
					html.write('%s<br>'%(row_string.encode('utf-8')))
					html.write('%s'%(fmt_fix[row_string]))
				else:
#					print name_fix
					if d.case in name_fix:
#						print d.case,' is in'
						html.write('%s'%(name_fix[d.case]))
					else:
						html.write('%s'%(row_string.encode('utf-8')))
#						print d.case,' is not in'
					if d.case == "FT-SS-07-Norm":
#						html.write (' NORM')
						s_nfc = unicodedata.normalize('NFC',row_string)
						s_nfd = unicodedata.normalize('NFD',row_string)
						s_nfkc = unicodedata.normalize('NFKC',row_string)
#						html.write ('<%d,%d,%d>'%(len(row_string),len(s_nfc),len(s_nfd)))
						if len(s_nfc) == len(s_nfd) and len(row_string) == len(s_nfd):
							if len(s_nfkc) == len(row_string):
								html.write (' (No Ligature)')
							else: html.write (' (Ligature)')
						else:
							if len(s_nfc) == len(row_string):
								html.write (' (NFC)')
							else: html.write (' (NFD)')
				html.write('</td>')
				for xx in (x_active,x_deleted,x_unalloc):
					if xx != 0: bg = ' bgcolor="pink"'
					else: bg = ''
					sd.write('<td>%d</td><td>0</td><td>%d</td>'%(xx,xx))
					html.write('<td>%d</td><td>0</td><td %s>%d</td>'%(xx,bg,xx))
				sd.write('</tr>\n')
				html.write('</tr>\n')
# add row for the missed string
			sd.write('\n')
			for row_string in str_set:
				if row_string in str_cnt:
					expect_cnt = str_cnt[row_string][2]
					x_active = 0
					x_deleted = 0
					x_unalloc = 0
					if 'active' in expect_cnt:
						x_active = expect_cnt['active']
					if 'deleted' in expect_cnt:
						x_deleted = expect_cnt['deleted']
					if 'unalloc' in expect_cnt:
						x_unalloc = expect_cnt['unalloc']
				string_row_active = 0
				string_row_deleted = 0
				string_row_unalloc = 0
				if row_string in hits_by_string:
					string_row_active = hits_by_string[row_string][0]
					string_row_deleted = hits_by_string[row_string][1]
					string_row_unalloc = hits_by_string[row_string][2]
				html.write('<tr><td>')
#				if row_string in fmt_fix:
#					html.write('%s</td><td>'%(fmt_fix[row_string]))
#				else:
				html.write('</td><td>')
				if row_string in fmt_fix:
					html.write('%s<br>'%(row_string.encode('utf-8')))
					html.write('%s'%(fmt_fix[row_string]))
				else:
#					print name_fix
					if d.case in name_fix:
#						print d.case,' is in'
						html.write('%s'%(name_fix[d.case]))
					else:
						html.write('%s'%(row_string.encode('utf-8')))
#						print d.case,' is not in'
#					html.write('%s'%(row_string.encode('utf-8')))
					if d.case == "FT-SS-07-Norm":
#						html.write (' NORM X')
						s_nfc = unicodedata.normalize('NFC',row_string)
						s_nfd = unicodedata.normalize('NFD',row_string)
						s_nfkc = unicodedata.normalize('NFKC',row_string)
#						html.write ('<%d,%d,%d>'%(len(row_string),len(s_nfc),len(s_nfd)))
						if len(s_nfc) == len(s_nfd) and len(row_string) == len(s_nfd):
							if len(s_nfkc) == len(row_string):
								html.write (' (No Ligature)')
							else: html.write (' (Ligature)')
						else:
							if len(s_nfc) == len(row_string):
								html.write (' (NFC)')
							else: html.write (' (NFD)')
				html.write('</td><td>')
				html.write(' %d'%(x_active))
				html.write('</td><td>')
				html.write(' %d'%(string_row_active))
				misses = x_active - string_row_active
				if misses != 0: marker = ' bgcolor="pink"'
				else: marker=''
				html.write('</td><td %s>'%(marker))
				html.write(' %d'%(misses))
				html.write('</td><td>')
				html.write(' %d'%(x_deleted))
				html.write('</td><td>')
				html.write(' %d'%(string_row_deleted))
				misses = x_deleted - string_row_deleted
				if misses != 0: marker = ' bgcolor="pink"'
				else: marker=''
				html.write('</td><td %s>'%(marker))
				html.write(' %d'%(misses))
				html.write('</td><td>')
				if d.case.find('04') > 0 or d.case.find('06') > 6: x_unalloc = 0
				html.write(' %d'%(x_unalloc))
				html.write('</td><td>')
				html.write(' %d'%(string_row_unalloc))
				misses = x_unalloc - string_row_unalloc
				if misses != 0: marker = ' bgcolor="pink"'
				else: marker=''
				html.write('</td><td %s>'%(marker))
				html.write(' %d'%(misses))
				html.write('</td></tr>\n')
# end of string
		html.write(table_tail)
#		############################ TRIM 
#		for c in ck:
#			d = results[ds][se][c]
#			if len(d.meta_ref) > 0:
#				html.write ('<br>\n')
#				html.write (d.case+' %d %d'%(len(d.meta_ref),len(d.meta_hits))+'\n')
#				meta_list = set()
#				for mr in d.meta_hits:
##					html.write ('<br>\n')
##					html.write (mr)
#					meta_list.add (mr[8:])
#				html.write ('<br>===<br>\n')
#				html.write ('<br>=================\n')
##				for mr in meta_list:
##					html.write ('<br>\n')
##					html.write (mr)
#				for mr in d.meta_ref:
#					html.write ('<br>\n')
#					html.write (mr)
#					if mr[8:] in meta_list:
#						html.write (' hit\n')
#					else:
#						html.write (' miss\n')
#				html.write ('<br>*****************<br><br>\n')
#				############################ END TRIP



############################# META DATA RESULTS
		meta_text = '''
		<h3>Meta-Data results for %s Search of %s Data Set</h3>
		<p>
		The following table presents search results for strings located in file system meta-data.
		The <b>Case</b> column identifies the test case, the <b>String</b> column identifies the search string,
		the <b>Partition</b> column identifies the partition (file system)
		where the string is located and the <b>Seen</b> column records if the search tool
		reported at least one instance of the string (yes or no) in meta-data.
		<br><p>
		'''

		printmetadata = False

		# Check: are there meta-data results to display for this ds and se?
		for c in ck:
			d = results[ds][se][c]
			if len(d.meta_ref) > 0:
				printmetadata = True
		
		# display meta-data results if we have any for this ds and se
		if (printmetadata):						
			html.write (meta_text%(se,ds))
			html.write(table_head)
			html.write('<tr><th colspan="4">')
			html.write('Meta-Data Results for %s Search of %s Data Set'%(se,ds))
			html.write('</th></tr>')
			html.write('<tr><th>')
			html.write('Case')
			html.write('</th><th>')
			html.write('String')
			html.write('</th><th>')
			html.write('Partition')
			html.write('</th><th>')
			html.write('Seen')
			html.write('</th></tr>\n')
			for c in ck:
				d = results[ds][se][c]
				if len(d.meta_ref) > 0:
					meta_list = set()
					html.write ('<tr><td>')
					html.write (d.case)
					html.write ('</td><td>')
					html.write ('</td><td>')
					html.write ('</td><td>')
					html.write ('</td></tr>\n')
					for mr in d.meta_hits:
						meta_list.add (mr[8:])
					for mr in d.meta_ref:
						if mr[8:] in meta_list:
							got_meta = 'Yes'
						else:
							got_meta = 'No'
						mrf = mr.split(':')
						html.write ('<tr><td>')
						html.write ('</td><td>')
						html.write (mrf[1])
						html.write ('</td><td>')
						html.write (mrf[2])
						html.write ('</td><td>')
						html.write (got_meta)
						html.write ('</td></tr>\n')
			html.write(table_tail)
#####################################		
		seen_a_comment = False
		for c in ck:
			d = results[ds][se][c]
			if d.comments != '':
				if not seen_a_comment:
					seen_a_comment = True
					html.write('<h3>Comments on %s Search of %s Data Set</h3>\n'%
						(se,ds))
#						('engine','DATA'))
					comment_head = '''
<p>The following table presents any comments
recorded during testing for a test case.
<p>
'''
					html.write(comment_head)
					html.write(table_head)
#					html.write('<tr><th>')
#					html.write('Comments on %s Search of %s Data Set'%(se,ds))
#					html.write('</th><th>')
					html.write('<tr><th>')
					html.write('Case')
					html.write('</th><th>')
#					html.write('Comments')
					html.write('Comments on %s Search of %s Data Set'%(se,ds))
					html.write('</th></tr>\n')
				html.write('<tr><td>')
				html.write('%s '%(d.case))
				html.write('</td><td>')
#				html.write('%s'%(d.comments))
				formatted = d.comments.replace('\r','<br>')
				#print formatted
				html.write('%s'%(formatted))
				html.write('</td></tr>\n')
		if seen_a_comment: html.write(table_tail)
			



#for se in se_list:
#	html.write(se[3:])
#html.write('.\n')
#
#for se in se_list:
#	print "Search Engine:",se
#	html.write('<h3>Results for Search Engine: %s</h3>'%(se[3:]))

finish_time = str(datetime.datetime.now())
html.write (ver+'\n<br>')
html.write("Done: "+finish_time+'\n')
html.write("<br>\n")
html.write("\nFederated Testing Version %s, released %s\n"%(ftver,ftdate))
html.write('<br><p>END of REPORT\n')
html.write(tail)

#exit(0)
#print "Content-Type: text/html\n\n"
#print "<html>"
#print "<TITLE>String Search Tool Test Report</TITLE>"
#print "<body>"
#print "<p>Draft Test Report saved to %s<p>"%(html_file)
#print '<a href="file://%s">View Report</a><br>'%(html_file)
#print '<p>'
#print "</body>"
#print "</html>"
exit (0)
