1#!/usr/bin/env python3 2import argparse 3import mimetypes 4from pathlib import Path 5import re 6 7response_types = { 8 200: "HTTP/1.0 200 OK", 9 400: "HTTP/1.0 400 Bad Request", 10 404: "HTTP/1.0 404 File not found", 11 501: "HTTP/1.0 501 Not Implemented", 12} 13 14PAYLOAD_ALIGNMENT = 4 15HTTPD_SERVER_AGENT = "lwIP/2.2.0d (http://savannah.nongnu.org/projects/lwip)" 16LWIP_HTTPD_SSI_EXTENSIONS = [".shtml", ".shtm", ".ssi", ".xml", ".json"] 17 18def process_file(input_dir, file): 19 results = [] 20 21 # Check content type 22 content_type, _ = mimetypes.guess_type(file) 23 if content_type is None: 24 content_type = "application/octet-stream" 25 26 # file name 27 data = f"/{file.relative_to(input_dir)}\x00" 28 comment = f"\"/{file.relative_to(input_dir)}\" ({len(data)} chars)" 29 while(len(data) % PAYLOAD_ALIGNMENT != 0): 30 data += "\x00" 31 results.append({'data': bytes(data, "utf-8"), 'comment': comment}); 32 33 # Header 34 response_type = 200 35 for response_id in response_types: 36 if file.name.startswith(f"{response_id}."): 37 response_type = response_id 38 break 39 data = f"{response_types[response_type]}\r\n" 40 comment = f"\"{response_types[response_type]}\" ({len(data)} chars)" 41 results.append({'data': bytes(data, "utf-8"), 'comment': comment}); 42 43 # user agent 44 data = f"Server: {HTTPD_SERVER_AGENT}\r\n" 45 comment = f"\"Server: {HTTPD_SERVER_AGENT}\" ({len(data)} chars)" 46 results.append({'data': bytes(data, "utf-8"), 'comment': comment}); 47 48 if file.suffix not in LWIP_HTTPD_SSI_EXTENSIONS: 49 # content length 50 file_size = file.stat().st_size 51 data = f"Content-Length: {file_size}\r\n" 52 comment = f"\"Content-Length: {file_size}\" ({len(data)} chars)" 53 results.append({'data': bytes(data, "utf-8"), 'comment': comment}); 54 55 # content type 56 data = f"Content-Type: {content_type}\r\n\r\n" 57 comment = f"\"Content-Type: {content_type}\" ({len(data)} chars)" 58 results.append({'data': bytes(data, "utf-8"), 'comment': comment}); 59 60 # file contents 61 data = file.read_bytes() 62 comment = f"raw file data ({len(data)} bytes)" 63 results.append({'data': data, 'comment': comment}); 64 65 return results; 66 67def process_file_list(fd, input): 68 data = [] 69 fd.write("#include \"lwip/apps/fs.h\"\n") 70 fd.write("\n") 71 # generate the page contents 72 input_dir = None 73 for name in input: 74 file = Path(name) 75 if not file.is_file(): 76 raise RuntimeError(f"File not found: {name}") 77 # Take the input directory from the first file 78 if input_dir is None: 79 input_dir = file.parent 80 results = process_file(input_dir, file) 81 82 # make a variable name 83 var_name = str(file.relative_to(input_dir)) 84 var_name = re.sub(r"\W+", "_", var_name, flags=re.ASCII) 85 86 # Add a suffix if the variable name is used already 87 if any(d["data_var"] == f"data_{var_name}" for d in data): 88 var_name += f"_{len(data)}" 89 90 data_var = f"data_{var_name}" 91 file_var = f"file_{var_name}" 92 93 # variable containing the raw data 94 fd.write(f"static const unsigned char {data_var}[] = {{\n") 95 for entry in results: 96 fd.write(f"\n /* {entry['comment']} */\n") 97 byte_count = 0; 98 for b in entry['data']: 99 if byte_count % 16 == 0: 100 fd.write(" ") 101 byte_count += 1 102 fd.write(f"0x{b:02x},") 103 if byte_count % 16 == 0: 104 fd.write("\n") 105 if byte_count % 16 != 0: 106 fd.write("\n") 107 fd.write(f"}};\n\n") 108 109 # set the flags 110 flags = "FS_FILE_FLAGS_HEADER_INCLUDED" 111 if file.suffix not in LWIP_HTTPD_SSI_EXTENSIONS: 112 flags += " | FS_FILE_FLAGS_HEADER_PERSISTENT" 113 else: 114 flags += " | FS_FILE_FLAGS_SSI" 115 116 # add variable details to the list 117 data.append({'data_var': data_var, 'file_var': file_var, 'name_size': len(results[0]['data']), 'flags': flags}) 118 119 # generate the page details 120 last_var = "NULL" 121 for entry in data: 122 fd.write(f"const struct fsdata_file {entry['file_var']}[] = {{{{\n") 123 fd.write(f" {last_var},\n") 124 fd.write(f" {entry['data_var']},\n") 125 fd.write(f" {entry['data_var']} + {entry['name_size']},\n") 126 fd.write(f" sizeof({entry['data_var']}) - {entry['name_size']},\n") 127 fd.write(f" {entry['flags']},\n") 128 fd.write(f"}}}};\n\n") 129 last_var = entry['file_var'] 130 fd.write(f"#define FS_ROOT {last_var}\n") 131 fd.write(f"#define FS_NUMFILES {len(data)}\n") 132 133def run_tool(): 134 parser = argparse.ArgumentParser(prog="makefsdata.py", description="Generates a source file for the lwip httpd server") 135 parser.add_argument( 136 "-i", 137 "--input", 138 help="input files to add as http content", 139 required=True, 140 nargs='+' 141 ) 142 parser.add_argument( 143 "-o", 144 "--output", 145 help="name of the source file to generate", 146 required=True, 147 ) 148 args = parser.parse_args() 149 print(args.input) 150 151 mimetypes.init() 152 for ext in [".shtml", ".shtm", ".ssi"]: 153 mimetypes.add_type("text/html", ext) 154 155 with open(args.output, "w") as fd: 156 process_file_list(fd, args.input) 157 158if __name__ == "__main__": 159 run_tool() 160