| 26 |
|
|
| 27 |
pp = pprint.PrettyPrinter(indent=4) |
pp = pprint.PrettyPrinter(indent=4) |
| 28 |
|
|
| 29 |
def merge(*input): |
html_escape_table = { |
| 30 |
""" |
"&": "&", |
| 31 |
merge all the lists passed as argument |
'"': """, |
| 32 |
""" |
"'": "'", |
| 33 |
return reduce(list.__add__, input, list()) |
">": ">", |
| 34 |
|
"<": "<", |
| 35 |
|
} |
| 36 |
|
|
| 37 |
|
def html_escape(text): |
| 38 |
|
"""Produce entities within text.""" |
| 39 |
|
L=[] |
| 40 |
|
for c in text: |
| 41 |
|
L.append(html_escape_table.get(c,c)) |
| 42 |
|
return "".join(L) |
| 43 |
|
|
| 44 |
def parse_reader(path, reader): |
def parse_reader(path, reader): |
| 45 |
""" |
""" |
| 67 |
|
|
| 68 |
return readers |
return readers |
| 69 |
|
|
| 70 |
def parse_ini(path, inifile): |
def parse_ini(path, section): |
| 71 |
""" |
""" |
| 72 |
parse a foobas.ini file to extract all informations |
parse a foobar.ini file to extract all informations |
| 73 |
""" |
""" |
| 74 |
config = ConfigParser.ConfigParser() |
config = ConfigParser.ConfigParser() |
| 75 |
# do not use the default case insensitive transform for key value |
# do not use the default case insensitive transform for key value |
| 76 |
config.optionxform = str |
config.optionxform = str |
| 77 |
config.read(inifile) |
config.read(section + ".ini") |
| 78 |
reader_list = config.sections() |
reader_list = config.sections() |
| 79 |
|
|
| 80 |
readers = parse_all(path, reader_list) |
readers = parse_all(path, reader_list) |
| 81 |
for r in readers.keys(): |
for r in readers.keys(): |
| 82 |
|
readers[r]['section'] = section |
| 83 |
|
readers[r]['iManufacturer'] = html_escape(readers[r]['iManufacturer']) |
| 84 |
for o in config.options(r): |
for o in config.options(r): |
| 85 |
readers[r][o] = config.get(r, o) |
if o == 'features': |
| 86 |
|
# for the features we use a list |
| 87 |
|
readers[r][o] = [ config.get(r, o) ] |
| 88 |
|
else: |
| 89 |
|
readers[r][o] = config.get(r, o) |
| 90 |
|
|
| 91 |
|
bPINSupport = int(readers[r]['bPINSupport'], 16) |
| 92 |
|
if bPINSupport != 0 and not 'features' in readers[r]: |
| 93 |
|
readers[r]['features'] = list() |
| 94 |
|
if bPINSupport & 1: |
| 95 |
|
readers[r]['features'].append("PIN Verification") |
| 96 |
|
if bPINSupport & 2: |
| 97 |
|
readers[r]['features'].append("PIN Modification") |
| 98 |
|
|
| 99 |
|
#pp.pprint(readers["GemPCPinpad.txt"]) |
| 100 |
return readers |
return readers |
| 101 |
|
|
| 102 |
def check_list(path, reader_list): |
def check_list(path, reader_list): |
| 103 |
|
""" |
| 104 |
|
Check that all .txt files are listed |
| 105 |
|
""" |
| 106 |
cwd = os.getcwd() |
cwd = os.getcwd() |
| 107 |
os.chdir(path) |
os.chdir(path) |
| 108 |
real_list = glob.glob("*.txt") |
real_list = glob.glob("*.txt") |
| 122 |
|
|
| 123 |
# some USB descriptor are not listed in readers.txt? |
# some USB descriptor are not listed in readers.txt? |
| 124 |
if len(real_list) > 0: |
if len(real_list) > 0: |
| 125 |
raise Exception("readers %s are not listed" % real_list) |
print "Reader(s) not listed in any .ini file:" |
| 126 |
|
print "\n".join(real_list) |
| 127 |
|
print "" |
| 128 |
|
|
| 129 |
|
def check_supported(path, all_readers): |
| 130 |
|
""" |
| 131 |
|
Check that all .txt files are mentionned in supported_readers.txt |
| 132 |
|
""" |
| 133 |
|
supported_readers = file(path + "supported_readers.txt").readlines() |
| 134 |
|
# convert in a long string and in uppercase |
| 135 |
|
s = "".join(supported_readers).upper() |
| 136 |
|
|
| 137 |
|
unlisted = list() |
| 138 |
|
for r in all_readers.keys(): |
| 139 |
|
pattern = "%s:%s" % (all_readers[r]['idVendor'], all_readers[r]['idProduct']) |
| 140 |
|
if not pattern.upper() in s: |
| 141 |
|
unlisted.append(pattern + " " + r) |
| 142 |
|
|
| 143 |
|
if len(unlisted) > 0: |
| 144 |
|
print "Reader(s) not in supported_readers.txt" |
| 145 |
|
print "\n".join(unlisted) |
| 146 |
|
print "" |
| 147 |
|
#pp.pprint(supported_readers) |
| 148 |
|
|
| 149 |
def get_by_manufacturer(readers): |
def get_by_manufacturer(readers): |
| 150 |
|
""" |
| 151 |
|
return a dict of the readers grouped by manufacturer |
| 152 |
|
d['manufacturer'] is a list of the manufacturer's readers |
| 153 |
|
""" |
| 154 |
d = {} |
d = {} |
| 155 |
for r in readers.keys(): |
for r in readers.keys(): |
| 156 |
d.setdefault(readers[r]['iManufacturer'], []).append(r) |
d.setdefault(readers[r]['iManufacturer'], []).append(r) |
| 157 |
return d |
return d |
| 158 |
|
|
| 159 |
def generate_page(section, readers): |
def generate_page(section, title, comment, readers): |
| 160 |
|
""" |
| 161 |
|
generate a web page for the corresponding section |
| 162 |
|
""" |
| 163 |
# sort the readers by manufacturers |
# sort the readers by manufacturers |
| 164 |
manufacturer_readers = get_by_manufacturer(readers) |
manufacturer_readers = get_by_manufacturer(readers) |
| 165 |
manufacturers = list(manufacturer_readers) |
manufacturers = list(manufacturer_readers) |
| 166 |
|
# sort the manufacturers list alphabetically |
| 167 |
manufacturers.sort(key=str.lower) |
manufacturers.sort(key=str.lower) |
| 168 |
|
|
| 169 |
template = templayer.HTMLTemplate(section + ".template") |
template = templayer.HTMLTemplate("webpage.template") |
| 170 |
file_writer = template.start_file(file=file(section + ".html", "w")) |
file_writer = template.start_file(file=file(section + ".html", "w")) |
| 171 |
main_layer = file_writer.open(date=time.asctime()) |
main_layer = file_writer.open(date=time.asctime(), |
| 172 |
|
title=title, comment=comment, section=section) |
| 173 |
|
|
| 174 |
# for each manufacturer |
# for each manufacturer |
| 175 |
for m in manufacturers: |
for m in manufacturers: |
| 176 |
main_layer.write_layer('manufacturer', manufacturer=m) |
main_layer.write_layer('manufacturer', manufacturer=m) |
| 184 |
idProduct = readers[r]['idProduct'], |
idProduct = readers[r]['idProduct'], |
| 185 |
image = "img/" + readers[r].get('image', "no_image.png")) |
image = "img/" + readers[r].get('image', "no_image.png")) |
| 186 |
|
|
| 187 |
|
note_layer.write_layer('descriptor', descriptor = "readers/%s" % r) |
| 188 |
|
|
| 189 |
url = readers[r].get('url', "") |
url = readers[r].get('url', "") |
| 190 |
if url: |
if url: |
| 191 |
note_layer.write_layer('url', |
note_layer.write_layer('url', |
| 192 |
url = url, |
url = url, |
| 193 |
manufacturer = m, |
manufacturer = m, |
| 194 |
product = readers[r]['iProduct']) |
product = readers[r]['iProduct']) |
| 195 |
|
|
| 196 |
features = readers[r].get('features', "") |
features = ", ".join(readers[r].get('features', "")) |
| 197 |
if features: |
if features: |
| 198 |
note_layer.write_layer('features', features = features) |
note_layer.write_layer('features', features = features) |
| 199 |
|
|
| 203 |
|
|
| 204 |
file_writer.close() |
file_writer.close() |
| 205 |
|
|
| 206 |
|
def generate_table(readers, field, index, fields): |
| 207 |
|
""" |
| 208 |
|
generate a web page with all the reader attributes |
| 209 |
|
readers are in the order given by index |
| 210 |
|
""" |
| 211 |
|
header = """<?xml version="1.0" encoding="UTF-8"?> |
| 212 |
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
| 213 |
|
<html> |
| 214 |
|
<head> |
| 215 |
|
<title>%s</title> |
| 216 |
|
<link rel="stylesheet" type="text/css" href="default.css"> |
| 217 |
|
<link rel="stylesheet" type="text/css" href="matrix.css"> |
| 218 |
|
</head> |
| 219 |
|
<body>""" |
| 220 |
|
footer = """ |
| 221 |
|
<script type="text/javascript"> |
| 222 |
|
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); |
| 223 |
|
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); |
| 224 |
|
</script> |
| 225 |
|
<script type="text/javascript"> |
| 226 |
|
try { |
| 227 |
|
var pageTracker = _gat._getTracker("UA-2404298-2"); |
| 228 |
|
pageTracker._trackPageview(); |
| 229 |
|
} catch(err) {}</script> |
| 230 |
|
<p>Ludovic Rousseau</p> |
| 231 |
|
</body> |
| 232 |
|
</html>""" |
| 233 |
|
documentation = """ |
| 234 |
|
<p>Click on the column header to sort by that column.</p> |
| 235 |
|
<p>The background color indicates the section of the reader:</p> |
| 236 |
|
<table border="1" summary="color codes"> |
| 237 |
|
<tr><td>supported</td> |
| 238 |
|
<td bgcolor="#a0ffff">should work</td> |
| 239 |
|
<td bgcolor="#ffa0a0">unsupported</td></tr></table> |
| 240 |
|
""" |
| 241 |
|
|
| 242 |
|
file = open(field + ".html", "w") |
| 243 |
|
title = "Readers sorted by '%s' field" % field |
| 244 |
|
file.write(header % title) |
| 245 |
|
|
| 246 |
|
file.write("<h1>" + title + "</h1>") |
| 247 |
|
file.write(documentation) |
| 248 |
|
|
| 249 |
|
file.write('<table border="1" summary="">\n') |
| 250 |
|
|
| 251 |
|
file.write('<tr>') |
| 252 |
|
file.write("<th>#</th>") |
| 253 |
|
for f in fields: |
| 254 |
|
file.write("<th><a href='%s'>%s</a></th>" % (f+".html", f)) |
| 255 |
|
file.write('</tr>\n') |
| 256 |
|
|
| 257 |
|
num = 0 |
| 258 |
|
for r in index: |
| 259 |
|
num += 1 |
| 260 |
|
if readers[r]['section'] == "unsupported": |
| 261 |
|
file.write('<tr bgcolor="#ffa0a0">') |
| 262 |
|
elif readers[r]['section'] == "shouldwork": |
| 263 |
|
file.write('<tr bgcolor="#a0ffff">') |
| 264 |
|
else: |
| 265 |
|
file.write('<tr>') |
| 266 |
|
file.write("<td>%d</td>" % num) |
| 267 |
|
for f in fields: |
| 268 |
|
if f == 'iProduct': |
| 269 |
|
file.write("<td><a href='%s.html#%s%s'>%s</a></td>" % (readers[r]['section'], readers[r]['idVendor'], readers[r]['idProduct'], readers[r][f])) |
| 270 |
|
elif f == 'iManufacturer': |
| 271 |
|
file.write('<td><a onmouseover="">%s<img src="%s" alt="image"></a></td>' % (readers[r][f], "img/" + readers[r].get('image', "no_image.png"))) |
| 272 |
|
elif f == 'image': |
| 273 |
|
file.write('<td><img src="%s" height="100" alt="image"></td>' % ("img/" + readers[r].get('image', "no_image.png"))) |
| 274 |
|
else: |
| 275 |
|
file.write("<td>%s</td>" % readers[r].get(f, "")) |
| 276 |
|
file.write('</tr>\n') |
| 277 |
|
|
| 278 |
|
file.write('</table>\n') |
| 279 |
|
|
| 280 |
|
file.write("<hr><p>Generated: %s</p>" % time.asctime()) |
| 281 |
|
|
| 282 |
|
file.write(footer) |
| 283 |
|
file.close() |
| 284 |
|
|
| 285 |
|
def generate_tables(readers): |
| 286 |
|
""" |
| 287 |
|
generate all the web page tables with all the fields values |
| 288 |
|
""" |
| 289 |
|
fields = [ 'section', 'iManufacturer', 'iProduct', 'image', 'idVendor', |
| 290 |
|
'idProduct', 'bNumEndpoints', 'bInterfaceClass', 'bcdCCID', |
| 291 |
|
'bMaxSlotIndex', 'bVoltageSupport', 'dwProtocols', |
| 292 |
|
'dwDefaultClock', 'dwMaximumClock', 'dwDataRate', |
| 293 |
|
'dwMaxDataRate', 'dwMaxIFSD', 'dwSynchProtocols', |
| 294 |
|
'dwMechanical', 'dwFeatures', 'dwMaxCCIDMessageLength', |
| 295 |
|
'bClassGetResponse', 'bClassEnveloppe', 'wLcdLayout', |
| 296 |
|
'bPINSupport', 'bMaxCCIDBusySlots', 'features', 'note' ] |
| 297 |
|
|
| 298 |
|
for f in fields: |
| 299 |
|
index = list() |
| 300 |
|
for r in readers.keys(): |
| 301 |
|
index.append([readers[r].get(f, ""), r]) |
| 302 |
|
if f == 'section': |
| 303 |
|
# hack to sort in the order supported, shouldwork, unsupported |
| 304 |
|
index = [(s.replace('supported', 'asupported'),r) for s,r in index] |
| 305 |
|
if f in ['dwDefaultClock', 'dwMaximumClock', 'dwDataRate', |
| 306 |
|
'dwMaxDataRate', 'dwMaxIFSD', 'dwMaxCCIDMessageLength']: |
| 307 |
|
# convert from text to decimal |
| 308 |
|
index = [(float(s.split(' ')[0])*1000,r) for s,r in index] |
| 309 |
|
index.sort() |
| 310 |
|
sorted_fields = list(fields) |
| 311 |
|
sorted_fields.remove(f) |
| 312 |
|
sorted_fields.insert(0, f) |
| 313 |
|
generate_table(readers, f, [r for f, r in index], sorted_fields) |
| 314 |
|
|
| 315 |
if __name__ == "__main__": |
if __name__ == "__main__": |
| 316 |
path = "../trunk/Drivers/ccid/readers/" |
path = "../trunk/Drivers/ccid/readers/" |
| 317 |
|
|
| 318 |
supported_readers = parse_ini(path, "supported.ini") |
supported_readers = parse_ini(path, "supported") |
| 319 |
shouldwork_readers = parse_ini(path, "shouldwork.ini") |
shouldwork_readers = parse_ini(path, "shouldwork") |
| 320 |
unsupported_readers = parse_ini(path, "unsupported.ini") |
unsupported_readers = parse_ini(path, "unsupported") |
| 321 |
reader_list = merge(supported_readers.keys(), |
|
| 322 |
shouldwork_readers.keys(), unsupported_readers.keys()) |
# all_readers contain the union of the 3 lists |
| 323 |
#pp.pprint(reader_list) |
all_readers = dict(supported_readers) |
| 324 |
check_list(path, reader_list) |
all_readers.update(shouldwork_readers) |
| 325 |
|
all_readers.update(unsupported_readers) |
| 326 |
|
|
| 327 |
|
check_list(path, all_readers.keys()) |
| 328 |
|
check_supported(path, all_readers) |
| 329 |
|
|
| 330 |
|
generate_page("supported", "Supported CCID readers/ICCD tokens", "If you are a reader manufacturer and your reader is not listed here then contact me at ludovic.rousseau@free.fr", supported_readers) |
| 331 |
|
generate_page("shouldwork", "Should work but untested by me", "The CCID readers and ICCD tokens listed bellow should work with the driver but have not be validated by me. I would like to get these readers to perform test and validation and move them in the supported list above. If you are one of the manufacturers, please, contact me at ludovic.rousseau@free.fr.", shouldwork_readers) |
| 332 |
|
generate_page("unsupported", "Unsupported or partly supported CCID readers", "These readers have problems or serious limitations.", unsupported_readers) |
| 333 |
|
|
| 334 |
generate_page("supported", supported_readers) |
generate_tables(all_readers) |