1*e1fe3e4aSElliott Hughes#!/usr/bin/env python3 2*e1fe3e4aSElliott Hughes# -*- coding: utf-8 -*- 3*e1fe3e4aSElliott Hughes 4*e1fe3e4aSElliott Hughesimport argparse 5*e1fe3e4aSElliott Hughesimport hashlib 6*e1fe3e4aSElliott Hughesimport os 7*e1fe3e4aSElliott Hughesimport sys 8*e1fe3e4aSElliott Hughes 9*e1fe3e4aSElliott Hughesfrom os.path import basename 10*e1fe3e4aSElliott Hughes 11*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont 12*e1fe3e4aSElliott Hughes 13*e1fe3e4aSElliott Hughes 14*e1fe3e4aSElliott Hughesdef write_checksum( 15*e1fe3e4aSElliott Hughes filepaths, 16*e1fe3e4aSElliott Hughes stdout_write=False, 17*e1fe3e4aSElliott Hughes use_ttx=False, 18*e1fe3e4aSElliott Hughes include_tables=None, 19*e1fe3e4aSElliott Hughes exclude_tables=None, 20*e1fe3e4aSElliott Hughes do_not_cleanup=False, 21*e1fe3e4aSElliott Hughes): 22*e1fe3e4aSElliott Hughes checksum_dict = {} 23*e1fe3e4aSElliott Hughes for path in filepaths: 24*e1fe3e4aSElliott Hughes if not os.path.exists(path): 25*e1fe3e4aSElliott Hughes sys.stderr.write( 26*e1fe3e4aSElliott Hughes "[checksum.py] ERROR: " 27*e1fe3e4aSElliott Hughes + path 28*e1fe3e4aSElliott Hughes + " is not a valid file path" 29*e1fe3e4aSElliott Hughes + os.linesep 30*e1fe3e4aSElliott Hughes ) 31*e1fe3e4aSElliott Hughes sys.exit(1) 32*e1fe3e4aSElliott Hughes 33*e1fe3e4aSElliott Hughes if use_ttx: 34*e1fe3e4aSElliott Hughes # append a .ttx extension to existing extension to maintain data about the binary that 35*e1fe3e4aSElliott Hughes # was used to generate the .ttx XML dump. This creates unique checksum path values for 36*e1fe3e4aSElliott Hughes # paths that would otherwise not be unique with a file extension replacement with .ttx 37*e1fe3e4aSElliott Hughes # An example is woff and woff2 web font files that share the same base file name: 38*e1fe3e4aSElliott Hughes # 39*e1fe3e4aSElliott Hughes # coolfont-regular.woff ==> coolfont-regular.ttx 40*e1fe3e4aSElliott Hughes # coolfont-regular.woff2 ==> coolfont-regular.ttx (KAPOW! checksum data lost as this would overwrite dict value) 41*e1fe3e4aSElliott Hughes temp_ttx_path = path + ".ttx" 42*e1fe3e4aSElliott Hughes 43*e1fe3e4aSElliott Hughes tt = TTFont(path) 44*e1fe3e4aSElliott Hughes tt.saveXML(temp_ttx_path, skipTables=exclude_tables, tables=include_tables) 45*e1fe3e4aSElliott Hughes checksum_path = temp_ttx_path 46*e1fe3e4aSElliott Hughes else: 47*e1fe3e4aSElliott Hughes if include_tables is not None: 48*e1fe3e4aSElliott Hughes sys.stderr.write( 49*e1fe3e4aSElliott Hughes "[checksum.py] -i and --include are not supported for font binary filepaths. \ 50*e1fe3e4aSElliott Hughes Use these flags for checksums with the --ttx flag." 51*e1fe3e4aSElliott Hughes ) 52*e1fe3e4aSElliott Hughes sys.exit(1) 53*e1fe3e4aSElliott Hughes if exclude_tables is not None: 54*e1fe3e4aSElliott Hughes sys.stderr.write( 55*e1fe3e4aSElliott Hughes "[checksum.py] -e and --exclude are not supported for font binary filepaths. \ 56*e1fe3e4aSElliott Hughes Use these flags for checksums with the --ttx flag." 57*e1fe3e4aSElliott Hughes ) 58*e1fe3e4aSElliott Hughes sys.exit(1) 59*e1fe3e4aSElliott Hughes checksum_path = path 60*e1fe3e4aSElliott Hughes 61*e1fe3e4aSElliott Hughes file_contents = _read_binary(checksum_path) 62*e1fe3e4aSElliott Hughes 63*e1fe3e4aSElliott Hughes # store SHA1 hash data and associated file path basename in the checksum_dict dictionary 64*e1fe3e4aSElliott Hughes checksum_dict[basename(checksum_path)] = hashlib.sha1(file_contents).hexdigest() 65*e1fe3e4aSElliott Hughes 66*e1fe3e4aSElliott Hughes # remove temp ttx files when present 67*e1fe3e4aSElliott Hughes if use_ttx and do_not_cleanup is False: 68*e1fe3e4aSElliott Hughes os.remove(temp_ttx_path) 69*e1fe3e4aSElliott Hughes 70*e1fe3e4aSElliott Hughes # generate the checksum list string for writes 71*e1fe3e4aSElliott Hughes checksum_out_data = "" 72*e1fe3e4aSElliott Hughes for key in checksum_dict.keys(): 73*e1fe3e4aSElliott Hughes checksum_out_data += checksum_dict[key] + " " + key + "\n" 74*e1fe3e4aSElliott Hughes 75*e1fe3e4aSElliott Hughes # write to stdout stream or file based upon user request (default = file write) 76*e1fe3e4aSElliott Hughes if stdout_write: 77*e1fe3e4aSElliott Hughes sys.stdout.write(checksum_out_data) 78*e1fe3e4aSElliott Hughes else: 79*e1fe3e4aSElliott Hughes checksum_report_filepath = "checksum.txt" 80*e1fe3e4aSElliott Hughes with open(checksum_report_filepath, "w") as file: 81*e1fe3e4aSElliott Hughes file.write(checksum_out_data) 82*e1fe3e4aSElliott Hughes 83*e1fe3e4aSElliott Hughes 84*e1fe3e4aSElliott Hughesdef check_checksum(filepaths): 85*e1fe3e4aSElliott Hughes check_failed = False 86*e1fe3e4aSElliott Hughes for path in filepaths: 87*e1fe3e4aSElliott Hughes if not os.path.exists(path): 88*e1fe3e4aSElliott Hughes sys.stderr.write( 89*e1fe3e4aSElliott Hughes "[checksum.py] ERROR: " + path + " is not a valid filepath" + os.linesep 90*e1fe3e4aSElliott Hughes ) 91*e1fe3e4aSElliott Hughes sys.exit(1) 92*e1fe3e4aSElliott Hughes 93*e1fe3e4aSElliott Hughes with open(path, mode="r") as file: 94*e1fe3e4aSElliott Hughes for line in file.readlines(): 95*e1fe3e4aSElliott Hughes cleaned_line = line.rstrip() 96*e1fe3e4aSElliott Hughes line_list = cleaned_line.split(" ") 97*e1fe3e4aSElliott Hughes # eliminate empty strings parsed from > 1 space characters 98*e1fe3e4aSElliott Hughes line_list = list(filter(None, line_list)) 99*e1fe3e4aSElliott Hughes if len(line_list) == 2: 100*e1fe3e4aSElliott Hughes expected_sha1 = line_list[0] 101*e1fe3e4aSElliott Hughes test_path = line_list[1] 102*e1fe3e4aSElliott Hughes else: 103*e1fe3e4aSElliott Hughes sys.stderr.write( 104*e1fe3e4aSElliott Hughes "[checksum.py] ERROR: failed to parse checksum file values" 105*e1fe3e4aSElliott Hughes + os.linesep 106*e1fe3e4aSElliott Hughes ) 107*e1fe3e4aSElliott Hughes sys.exit(1) 108*e1fe3e4aSElliott Hughes 109*e1fe3e4aSElliott Hughes if not os.path.exists(test_path): 110*e1fe3e4aSElliott Hughes print(test_path + ": Filepath is not valid, ignored") 111*e1fe3e4aSElliott Hughes else: 112*e1fe3e4aSElliott Hughes file_contents = _read_binary(test_path) 113*e1fe3e4aSElliott Hughes observed_sha1 = hashlib.sha1(file_contents).hexdigest() 114*e1fe3e4aSElliott Hughes if observed_sha1 == expected_sha1: 115*e1fe3e4aSElliott Hughes print(test_path + ": OK") 116*e1fe3e4aSElliott Hughes else: 117*e1fe3e4aSElliott Hughes print("-" * 80) 118*e1fe3e4aSElliott Hughes print(test_path + ": === FAIL ===") 119*e1fe3e4aSElliott Hughes print("Expected vs. Observed:") 120*e1fe3e4aSElliott Hughes print(expected_sha1) 121*e1fe3e4aSElliott Hughes print(observed_sha1) 122*e1fe3e4aSElliott Hughes print("-" * 80) 123*e1fe3e4aSElliott Hughes check_failed = True 124*e1fe3e4aSElliott Hughes 125*e1fe3e4aSElliott Hughes # exit with status code 1 if any fails detected across all tests in the check 126*e1fe3e4aSElliott Hughes if check_failed is True: 127*e1fe3e4aSElliott Hughes sys.exit(1) 128*e1fe3e4aSElliott Hughes 129*e1fe3e4aSElliott Hughes 130*e1fe3e4aSElliott Hughesdef _read_binary(filepath): 131*e1fe3e4aSElliott Hughes with open(filepath, mode="rb") as file: 132*e1fe3e4aSElliott Hughes return file.read() 133*e1fe3e4aSElliott Hughes 134*e1fe3e4aSElliott Hughes 135*e1fe3e4aSElliott Hughesif __name__ == "__main__": 136*e1fe3e4aSElliott Hughes parser = argparse.ArgumentParser( 137*e1fe3e4aSElliott Hughes prog="checksum.py", 138*e1fe3e4aSElliott Hughes description="A SHA1 hash checksum list generator and checksum testing script", 139*e1fe3e4aSElliott Hughes ) 140*e1fe3e4aSElliott Hughes parser.add_argument( 141*e1fe3e4aSElliott Hughes "-t", "--ttx", help="Calculate from ttx file", action="store_true" 142*e1fe3e4aSElliott Hughes ) 143*e1fe3e4aSElliott Hughes parser.add_argument( 144*e1fe3e4aSElliott Hughes "-s", "--stdout", help="Write output to stdout stream", action="store_true" 145*e1fe3e4aSElliott Hughes ) 146*e1fe3e4aSElliott Hughes parser.add_argument( 147*e1fe3e4aSElliott Hughes "-n", 148*e1fe3e4aSElliott Hughes "--noclean", 149*e1fe3e4aSElliott Hughes help="Do not discard *.ttx files used to calculate SHA1 hashes", 150*e1fe3e4aSElliott Hughes action="store_true", 151*e1fe3e4aSElliott Hughes ) 152*e1fe3e4aSElliott Hughes parser.add_argument( 153*e1fe3e4aSElliott Hughes "-c", "--check", help="Verify checksum values vs. files", action="store_true" 154*e1fe3e4aSElliott Hughes ) 155*e1fe3e4aSElliott Hughes parser.add_argument( 156*e1fe3e4aSElliott Hughes "filepaths", 157*e1fe3e4aSElliott Hughes nargs="+", 158*e1fe3e4aSElliott Hughes help="One or more file paths. Use checksum file path for -c/--check. Use paths\ 159*e1fe3e4aSElliott Hughes to font files for all other commands.", 160*e1fe3e4aSElliott Hughes ) 161*e1fe3e4aSElliott Hughes 162*e1fe3e4aSElliott Hughes parser.add_argument( 163*e1fe3e4aSElliott Hughes "-i", 164*e1fe3e4aSElliott Hughes "--include", 165*e1fe3e4aSElliott Hughes action="append", 166*e1fe3e4aSElliott Hughes help="Included OpenType tables for ttx data dump", 167*e1fe3e4aSElliott Hughes ) 168*e1fe3e4aSElliott Hughes parser.add_argument( 169*e1fe3e4aSElliott Hughes "-e", 170*e1fe3e4aSElliott Hughes "--exclude", 171*e1fe3e4aSElliott Hughes action="append", 172*e1fe3e4aSElliott Hughes help="Excluded OpenType tables for ttx data dump", 173*e1fe3e4aSElliott Hughes ) 174*e1fe3e4aSElliott Hughes 175*e1fe3e4aSElliott Hughes args = parser.parse_args(sys.argv[1:]) 176*e1fe3e4aSElliott Hughes 177*e1fe3e4aSElliott Hughes if args.check is True: 178*e1fe3e4aSElliott Hughes check_checksum(args.filepaths) 179*e1fe3e4aSElliott Hughes else: 180*e1fe3e4aSElliott Hughes write_checksum( 181*e1fe3e4aSElliott Hughes args.filepaths, 182*e1fe3e4aSElliott Hughes stdout_write=args.stdout, 183*e1fe3e4aSElliott Hughes use_ttx=args.ttx, 184*e1fe3e4aSElliott Hughes do_not_cleanup=args.noclean, 185*e1fe3e4aSElliott Hughes include_tables=args.include, 186*e1fe3e4aSElliott Hughes exclude_tables=args.exclude, 187*e1fe3e4aSElliott Hughes ) 188