Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: mbed-TFT-example-NCS36510 mbed-Accelerometer-example-NCS36510 mbed-Accelerometer-example-NCS36510
lwip_makefsdata.c
00001 /** 00002 * makefsdata: Converts a directory structure for use with the lwIP httpd. 00003 * 00004 * This file is part of the lwIP TCP/IP stack. 00005 * 00006 * Author: Jim Pettinato 00007 * Simon Goldschmidt 00008 * 00009 * @todo: 00010 * - take TCP_MSS, LWIP_TCP_TIMESTAMPS and 00011 * PAYLOAD_ALIGN_TYPE/PAYLOAD_ALIGNMENT as arguments 00012 */ 00013 00014 #include <stdio.h> 00015 #include <stdlib.h> 00016 #ifdef WIN32 00017 #define WIN32_LEAN_AND_MEAN 00018 #include "windows.h" 00019 #else 00020 #include <dir.h> 00021 #endif 00022 #include <dos.h> 00023 #include <string.h> 00024 #include <time.h> 00025 #include <sys/stat.h> 00026 00027 /** Makefsdata can generate *all* files deflate-compressed (where file size shrinks). 00028 * Since nearly all browsers support this, this is a good way to reduce ROM size. 00029 * To compress the files, "miniz.c" must be downloaded seperately. 00030 */ 00031 #ifndef MAKEFS_SUPPORT_DEFLATE 00032 #define MAKEFS_SUPPORT_DEFLATE 0 00033 #endif 00034 00035 #define COPY_BUFSIZE (1024*1024) /* 1 MByte */ 00036 00037 #if MAKEFS_SUPPORT_DEFLATE 00038 #include "../miniz.c" 00039 00040 typedef unsigned char uint8; 00041 typedef unsigned short uint16; 00042 typedef unsigned int uint; 00043 00044 #define my_max(a,b) (((a) > (b)) ? (a) : (b)) 00045 #define my_min(a,b) (((a) < (b)) ? (a) : (b)) 00046 00047 /* COMP_OUT_BUF_SIZE is the size of the output buffer used during compression. 00048 COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE */ 00049 #define COMP_OUT_BUF_SIZE COPY_BUFSIZE 00050 00051 /* OUT_BUF_SIZE is the size of the output buffer used during decompression. 00052 OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses) */ 00053 #define OUT_BUF_SIZE COPY_BUFSIZE 00054 static uint8 s_outbuf[OUT_BUF_SIZE]; 00055 static uint8 s_checkbuf[OUT_BUF_SIZE]; 00056 00057 /* tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k). 00058 This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it. */ 00059 tdefl_compressor g_deflator; 00060 tinfl_decompressor g_inflator; 00061 00062 int deflate_level = 10; /* default compression level, can be changed via command line */ 00063 #define USAGE_ARG_DEFLATE " [-defl<:compr_level>]" 00064 #else /* MAKEFS_SUPPORT_DEFLATE */ 00065 #define USAGE_ARG_DEFLATE "" 00066 #endif /* MAKEFS_SUPPORT_DEFLATE */ 00067 00068 /* Compatibility defines Win32 vs. DOS */ 00069 #ifdef WIN32 00070 00071 #define FIND_T WIN32_FIND_DATAA 00072 #define FIND_T_FILENAME(fInfo) (fInfo.cFileName) 00073 #define FIND_T_IS_DIR(fInfo) ((fInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) 00074 #define FIND_T_IS_FILE(fInfo) ((fInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) 00075 #define FIND_RET_T HANDLE 00076 #define FINDFIRST_FILE(path, result) FindFirstFileA(path, result) 00077 #define FINDFIRST_DIR(path, result) FindFirstFileA(path, result) 00078 #define FINDNEXT(ff_res, result) FindNextFileA(ff_res, result) 00079 #define FINDFIRST_SUCCEEDED(ret) (ret != INVALID_HANDLE_VALUE) 00080 #define FINDNEXT_SUCCEEDED(ret) (ret == TRUE) 00081 00082 #define GETCWD(path, len) GetCurrentDirectoryA(len, path) 00083 #define CHDIR(path) SetCurrentDirectoryA(path) 00084 #define CHDIR_SUCCEEDED(ret) (ret == TRUE) 00085 00086 #else 00087 00088 #define FIND_T struct ffblk 00089 #define FIND_T_FILENAME(fInfo) (fInfo.ff_name) 00090 #define FIND_T_IS_DIR(fInfo) ((fInfo.ff_attrib & FA_DIREC) == FA_DIREC) 00091 #define FIND_T_IS_FILE(fInfo) (1) 00092 #define FIND_RET_T int 00093 #define FINDFIRST_FILE(path, result) findfirst(path, result, FA_ARCH) 00094 #define FINDFIRST_DIR(path, result) findfirst(path, result, FA_DIREC) 00095 #define FINDNEXT(ff_res, result) FindNextFileA(ff_res, result) 00096 #define FINDFIRST_SUCCEEDED(ret) (ret == 0) 00097 #define FINDNEXT_SUCCEEDED(ret) (ret == 0) 00098 00099 #define GETCWD(path, len) getcwd(path, len) 00100 #define CHDIR(path) chdir(path) 00101 #define CHDIR_SUCCEEDED(ret) (ret == 0) 00102 00103 #endif 00104 00105 #define NEWLINE "\r\n" 00106 #define NEWLINE_LEN 2 00107 00108 /* define this to get the header variables we use to build HTTP headers */ 00109 #define LWIP_HTTPD_DYNAMIC_HEADERS 1 00110 #define LWIP_HTTPD_SSI 1 00111 #include "lwip/init.h" 00112 #include "../httpd_structs.h" 00113 #include "lwip/apps/fs.h" 00114 00115 #include "../core/inet_chksum.c" 00116 #include "../core/def.c" 00117 00118 /** (Your server name here) */ 00119 const char *serverID = "Server: "HTTPD_SERVER_AGENT"\r\n"; 00120 char serverIDBuffer[1024]; 00121 00122 /* change this to suit your MEM_ALIGNMENT */ 00123 #define PAYLOAD_ALIGNMENT 4 00124 /* set this to 0 to prevent aligning payload */ 00125 #define ALIGN_PAYLOAD 1 00126 /* define this to a type that has the required alignment */ 00127 #define PAYLOAD_ALIGN_TYPE "unsigned int" 00128 static int payload_alingment_dummy_counter = 0; 00129 00130 #define HEX_BYTES_PER_LINE 16 00131 00132 #define MAX_PATH_LEN 256 00133 00134 struct file_entry 00135 { 00136 struct file_entry* next; 00137 const char* filename_c; 00138 }; 00139 00140 int process_sub(FILE *data_file, FILE *struct_file); 00141 int process_file(FILE *data_file, FILE *struct_file, const char *filename); 00142 int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len, 00143 u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed); 00144 int file_put_ascii(FILE *file, const char *ascii_string, int len, int *i); 00145 int s_put_ascii(char *buf, const char *ascii_string, int len, int *i); 00146 void concat_files(const char *file1, const char *file2, const char *targetfile); 00147 int check_path(char* path, size_t size); 00148 00149 /* 5 bytes per char + 3 bytes per line */ 00150 static char file_buffer_c[COPY_BUFSIZE * 5 + ((COPY_BUFSIZE / HEX_BYTES_PER_LINE) * 3)]; 00151 00152 char curSubdir[MAX_PATH_LEN]; 00153 char lastFileVar[MAX_PATH_LEN]; 00154 char hdr_buf[4096]; 00155 00156 unsigned char processSubs = 1; 00157 unsigned char includeHttpHeader = 1; 00158 unsigned char useHttp11 = 0; 00159 unsigned char supportSsi = 1; 00160 unsigned char precalcChksum = 0; 00161 unsigned char includeLastModified = 0; 00162 #if MAKEFS_SUPPORT_DEFLATE 00163 unsigned char deflateNonSsiFiles = 0; 00164 size_t deflatedBytesReduced = 0; 00165 size_t overallDataBytes = 0; 00166 #endif 00167 00168 struct file_entry* first_file = NULL; 00169 struct file_entry* last_file = NULL; 00170 00171 static void print_usage(void) 00172 { 00173 printf(" Usage: htmlgen [targetdir] [-s] [-e] [-i] [-11] [-nossi] [-c] [-f:<filename>] [-m] [-svr:<name>]" USAGE_ARG_DEFLATE NEWLINE NEWLINE); 00174 printf(" targetdir: relative or absolute path to files to convert" NEWLINE); 00175 printf(" switch -s: toggle processing of subdirectories (default is on)" NEWLINE); 00176 printf(" switch -e: exclude HTTP header from file (header is created at runtime, default is off)" NEWLINE); 00177 printf(" switch -11: include HTTP 1.1 header (1.0 is default)" NEWLINE); 00178 printf(" switch -nossi: no support for SSI (cannot calculate Content-Length for SSI)" NEWLINE); 00179 printf(" switch -c: precalculate checksums for all pages (default is off)" NEWLINE); 00180 printf(" switch -f: target filename (default is \"fsdata.c\")" NEWLINE); 00181 printf(" switch -m: include \"Last-Modified\" header based on file time" NEWLINE); 00182 printf(" switch -svr: server identifier sent in HTTP response header ('Server' field)" NEWLINE); 00183 #if MAKEFS_SUPPORT_DEFLATE 00184 printf(" switch -defl: deflate-compress all non-SSI files (with opt. compr.-level, default=10)" NEWLINE); 00185 printf(" ATTENTION: browser has to support \"Content-Encoding: deflate\"!" NEWLINE); 00186 #endif 00187 printf(" if targetdir not specified, htmlgen will attempt to" NEWLINE); 00188 printf(" process files in subdirectory 'fs'" NEWLINE); 00189 } 00190 00191 int main(int argc, char *argv[]) 00192 { 00193 char path[MAX_PATH_LEN]; 00194 char appPath[MAX_PATH_LEN]; 00195 FILE *data_file; 00196 FILE *struct_file; 00197 int filesProcessed; 00198 int i; 00199 char targetfile[MAX_PATH_LEN]; 00200 strcpy(targetfile, "fsdata.c"); 00201 00202 memset(path, 0, sizeof(path)); 00203 memset(appPath, 0, sizeof(appPath)); 00204 00205 printf(NEWLINE " makefsdata - HTML to C source converter" NEWLINE); 00206 printf(" by Jim Pettinato - circa 2003 " NEWLINE); 00207 printf(" extended by Simon Goldschmidt - 2009 " NEWLINE NEWLINE); 00208 00209 strcpy(path, "fs"); 00210 for (i = 1; i < argc; i++) { 00211 if (argv[i] == NULL) { 00212 continue; 00213 } 00214 if (argv[i][0] == '-') { 00215 if (strstr(argv[i], "-svr:") == argv[i]) { 00216 snprintf(serverIDBuffer, sizeof(serverIDBuffer), "Server: %s\r\n", &argv[i][5]); 00217 serverID = serverIDBuffer; 00218 printf("Using Server-ID: \"%s\"\n", serverID); 00219 } else if (strstr(argv[i], "-s") == argv[i]) { 00220 processSubs = 0; 00221 } else if (strstr(argv[i], "-e") == argv[i]) { 00222 includeHttpHeader = 0; 00223 } else if (strstr(argv[i], "-11") == argv[i]) { 00224 useHttp11 = 1; 00225 } else if (strstr(argv[i], "-nossi") == argv[i]) { 00226 supportSsi = 0; 00227 } else if (strstr(argv[i], "-c") == argv[i]) { 00228 precalcChksum = 1; 00229 } else if (strstr(argv[i], "-f:") == argv[i]) { 00230 strncpy(targetfile, &argv[i][3], sizeof(targetfile) - 1); 00231 targetfile[sizeof(targetfile) - 1] = 0; 00232 printf("Writing to file \"%s\"\n", targetfile); 00233 } else if (strstr(argv[i], "-m") == argv[i]) { 00234 includeLastModified = 1; 00235 } else if (strstr(argv[i], "-defl") == argv[i]) { 00236 #if MAKEFS_SUPPORT_DEFLATE 00237 char* colon = strstr(argv[i], ":"); 00238 if (colon) { 00239 if (colon[1] != 0) { 00240 int defl_level = atoi(&colon[1]); 00241 if ((defl_level >= 0) && (defl_level <= 10)) { 00242 deflate_level = defl_level; 00243 } else { 00244 printf("ERROR: deflate level must be [0..10]" NEWLINE); 00245 exit(0); 00246 } 00247 } 00248 } 00249 deflateNonSsiFiles = 1; 00250 printf("Deflating all non-SSI files with level %d (but only if size is reduced)" NEWLINE, deflate_level); 00251 #else 00252 printf("WARNING: Deflate support is disabled\n"); 00253 #endif 00254 } else if ((strstr(argv[i], "-?")) || (strstr(argv[i], "-h"))) { 00255 print_usage(); 00256 exit(0); 00257 } 00258 } else if ((argv[i][0] == '/') && (argv[i][1] == '?') && (argv[i][2] == 0)) { 00259 print_usage(); 00260 exit(0); 00261 } else { 00262 strncpy(path, argv[i], sizeof(path)-1); 00263 path[sizeof(path)-1] = 0; 00264 } 00265 } 00266 00267 if (!check_path(path, sizeof(path))) { 00268 printf("Invalid path: \"%s\"." NEWLINE, path); 00269 exit(-1); 00270 } 00271 00272 GETCWD(appPath, MAX_PATH_LEN); 00273 /* if command line param or subdir named 'fs' not found spout usage verbiage */ 00274 if (!CHDIR_SUCCEEDED(CHDIR(path))) { 00275 /* if no subdir named 'fs' (or the one which was given) exists, spout usage verbiage */ 00276 printf(" Failed to open directory \"%s\"." NEWLINE NEWLINE, path); 00277 print_usage(); 00278 exit(-1); 00279 } 00280 CHDIR(appPath); 00281 00282 printf("HTTP %sheader will %s statically included." NEWLINE, 00283 (includeHttpHeader ? (useHttp11 ? "1.1 " : "1.0 ") : ""), 00284 (includeHttpHeader ? "be" : "not be")); 00285 00286 sprintf(curSubdir, ""); /* start off in web page's root directory - relative paths */ 00287 printf(" Processing all files in directory %s", path); 00288 if (processSubs) { 00289 printf(" and subdirectories..." NEWLINE NEWLINE); 00290 } else { 00291 printf("..." NEWLINE NEWLINE); 00292 } 00293 00294 data_file = fopen("fsdata.tmp", "wb"); 00295 if (data_file == NULL) { 00296 printf("Failed to create file \"fsdata.tmp\"\n"); 00297 exit(-1); 00298 } 00299 struct_file = fopen("fshdr.tmp", "wb"); 00300 if (struct_file == NULL) { 00301 printf("Failed to create file \"fshdr.tmp\"\n"); 00302 fclose(data_file); 00303 exit(-1); 00304 } 00305 00306 CHDIR(path); 00307 00308 fprintf(data_file, "#include \"lwip/apps/fs.h\"" NEWLINE); 00309 fprintf(data_file, "#include \"lwip/def.h\"" NEWLINE); 00310 fprintf(data_file, "#include \"fsdata.h\"" NEWLINE NEWLINE NEWLINE); 00311 00312 fprintf(data_file, "#define file_NULL (struct fsdata_file *) NULL" NEWLINE NEWLINE NEWLINE); 00313 /* define FS_FILE_FLAGS_HEADER_INCLUDED to 1 if not defined (compatibility with older httpd/fs) */ 00314 fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_INCLUDED" NEWLINE "#define FS_FILE_FLAGS_HEADER_INCLUDED 1" NEWLINE "#endif" NEWLINE); 00315 /* define FS_FILE_FLAGS_HEADER_PERSISTENT to 0 if not defined (compatibility with older httpd/fs: wasn't supported back then) */ 00316 fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_PERSISTENT" NEWLINE "#define FS_FILE_FLAGS_HEADER_PERSISTENT 0" NEWLINE "#endif" NEWLINE); 00317 00318 /* define alignment defines */ 00319 #if ALIGN_PAYLOAD 00320 fprintf(data_file, "/* FSDATA_FILE_ALIGNMENT: 0=off, 1=by variable, 2=by include */" NEWLINE "#ifndef FSDATA_FILE_ALIGNMENT" NEWLINE "#define FSDATA_FILE_ALIGNMENT 0" NEWLINE "#endif" NEWLINE); 00321 #endif 00322 fprintf(data_file, "#ifndef FSDATA_ALIGN_PRE" NEWLINE "#define FSDATA_ALIGN_PRE" NEWLINE "#endif" NEWLINE); 00323 fprintf(data_file, "#ifndef FSDATA_ALIGN_POST" NEWLINE "#define FSDATA_ALIGN_POST" NEWLINE "#endif" NEWLINE); 00324 #if ALIGN_PAYLOAD 00325 fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==2" NEWLINE "#include \"fsdata_alignment.h\"" NEWLINE "#endif" NEWLINE); 00326 #endif 00327 00328 sprintf(lastFileVar, "NULL"); 00329 00330 filesProcessed = process_sub(data_file, struct_file); 00331 00332 /* data_file now contains all of the raw data.. now append linked list of 00333 * file header structs to allow embedded app to search for a file name */ 00334 fprintf(data_file, NEWLINE NEWLINE); 00335 fprintf(struct_file, "#define FS_ROOT file_%s" NEWLINE, lastFileVar); 00336 fprintf(struct_file, "#define FS_NUMFILES %d" NEWLINE NEWLINE, filesProcessed); 00337 00338 fclose(data_file); 00339 fclose(struct_file); 00340 00341 CHDIR(appPath); 00342 /* append struct_file to data_file */ 00343 printf(NEWLINE "Creating target file..." NEWLINE NEWLINE); 00344 concat_files("fsdata.tmp", "fshdr.tmp", targetfile); 00345 00346 /* if succeeded, delete the temporary files */ 00347 if (remove("fsdata.tmp") != 0) { 00348 printf("Warning: failed to delete fsdata.tmp\n"); 00349 } 00350 if (remove("fshdr.tmp") != 0) { 00351 printf("Warning: failed to delete fshdr.tmp\n"); 00352 } 00353 00354 printf(NEWLINE "Processed %d files - done." NEWLINE, filesProcessed); 00355 #if MAKEFS_SUPPORT_DEFLATE 00356 if (deflateNonSsiFiles) { 00357 printf("(Deflated total byte reduction: %d bytes -> %d bytes (%.02f%%)" NEWLINE, 00358 (int)overallDataBytes, (int)deflatedBytesReduced, (float)((deflatedBytesReduced*100.0)/overallDataBytes)); 00359 } 00360 #endif 00361 printf(NEWLINE); 00362 00363 while (first_file != NULL) { 00364 struct file_entry* fe = first_file; 00365 first_file = fe->next; 00366 free(fe); 00367 } 00368 00369 return 0; 00370 } 00371 00372 int check_path(char* path, size_t size) 00373 { 00374 size_t slen; 00375 if (path[0] == 0) { 00376 /* empty */ 00377 return 0; 00378 } 00379 slen = strlen(path); 00380 if (slen >= size) { 00381 /* not NULL-terminated */ 00382 return 0; 00383 } 00384 while ((slen > 0) && ((path[slen] == '\\') || (path[slen] == '/'))) { 00385 /* path should not end with trailing backslash */ 00386 path[slen] = 0; 00387 slen--; 00388 } 00389 if (slen == 0) { 00390 return 0; 00391 } 00392 return 1; 00393 } 00394 00395 static void copy_file(const char *filename_in, FILE *fout) 00396 { 00397 FILE *fin; 00398 size_t len; 00399 void* buf; 00400 fin = fopen(filename_in, "rb"); 00401 if (fin == NULL) { 00402 printf("Failed to open file \"%s\"\n", filename_in); 00403 exit(-1); 00404 } 00405 buf = malloc(COPY_BUFSIZE); 00406 while ((len = fread(buf, 1, COPY_BUFSIZE, fin)) > 0) { 00407 fwrite(buf, 1, len, fout); 00408 } 00409 free(buf); 00410 fclose(fin); 00411 } 00412 00413 void concat_files(const char *file1, const char *file2, const char *targetfile) 00414 { 00415 FILE *fout; 00416 fout = fopen(targetfile, "wb"); 00417 if (fout == NULL) { 00418 printf("Failed to open file \"%s\"\n", targetfile); 00419 exit(-1); 00420 } 00421 copy_file(file1, fout); 00422 copy_file(file2, fout); 00423 fclose(fout); 00424 } 00425 00426 int process_sub(FILE *data_file, FILE *struct_file) 00427 { 00428 FIND_T fInfo; 00429 FIND_RET_T fret; 00430 int filesProcessed = 0; 00431 00432 if (processSubs) { 00433 /* process subs recursively */ 00434 size_t sublen = strlen(curSubdir); 00435 size_t freelen = sizeof(curSubdir) - sublen - 1; 00436 LWIP_ASSERT("sublen < sizeof(curSubdir)", sublen < sizeof(curSubdir)); 00437 fret = FINDFIRST_DIR("*", &fInfo); 00438 if (FINDFIRST_SUCCEEDED(fret)) { 00439 do { 00440 const char *curName = FIND_T_FILENAME(fInfo); 00441 if ((curName[0] == '.') || (strcmp(curName, "CVS") == 0)) { 00442 continue; 00443 } 00444 if (!FIND_T_IS_DIR(fInfo)) { 00445 continue; 00446 } 00447 if (freelen > 0) { 00448 CHDIR(curName); 00449 strncat(curSubdir, "/", freelen); 00450 strncat(curSubdir, curName, freelen - 1); 00451 curSubdir[sizeof(curSubdir) - 1] = 0; 00452 printf("processing subdirectory %s/..." NEWLINE, curSubdir); 00453 filesProcessed += process_sub(data_file, struct_file); 00454 CHDIR(".."); 00455 curSubdir[sublen] = 0; 00456 } else { 00457 printf("WARNING: cannot process sub due to path length restrictions: \"%s/%s\"\n", curSubdir, curName); 00458 } 00459 } while (FINDNEXT_SUCCEEDED(FINDNEXT(fret, &fInfo))); 00460 } 00461 } 00462 00463 fret = FINDFIRST_FILE("*.*", &fInfo); 00464 if (FINDFIRST_SUCCEEDED(fret)) { 00465 /* at least one file in directory */ 00466 do { 00467 if (FIND_T_IS_FILE(fInfo)) { 00468 const char *curName = FIND_T_FILENAME(fInfo); 00469 printf("processing %s/%s..." NEWLINE, curSubdir, curName); 00470 if (process_file(data_file, struct_file, curName) < 0) { 00471 printf(NEWLINE "Error... aborting" NEWLINE); 00472 return -1; 00473 } 00474 filesProcessed++; 00475 } 00476 } while (FINDNEXT_SUCCEEDED(FINDNEXT(fret, &fInfo))); 00477 } 00478 return filesProcessed; 00479 } 00480 00481 u8_t* get_file_data(const char* filename, int* file_size, int can_be_compressed, int* is_compressed) 00482 { 00483 FILE *inFile; 00484 size_t fsize = 0; 00485 u8_t* buf; 00486 size_t r; 00487 int rs; 00488 inFile = fopen(filename, "rb"); 00489 if (inFile == NULL) { 00490 printf("Failed to open file \"%s\"\n", filename); 00491 exit(-1); 00492 } 00493 fseek(inFile, 0, SEEK_END); 00494 rs = ftell(inFile); 00495 if (rs < 0) { 00496 printf("ftell failed with %d\n", errno); 00497 exit(-1); 00498 } 00499 fsize = (size_t)rs; 00500 fseek(inFile, 0, SEEK_SET); 00501 buf = (u8_t*)malloc(fsize); 00502 LWIP_ASSERT("buf != NULL", buf != NULL); 00503 r = fread(buf, 1, fsize, inFile); 00504 *file_size = fsize; 00505 *is_compressed = 0; 00506 #if MAKEFS_SUPPORT_DEFLATE 00507 overallDataBytes += fsize; 00508 if (deflateNonSsiFiles) { 00509 if (can_be_compressed) { 00510 if (fsize < OUT_BUF_SIZE) { 00511 u8_t* ret_buf; 00512 tdefl_status status; 00513 size_t in_bytes = fsize; 00514 size_t out_bytes = OUT_BUF_SIZE; 00515 const void *next_in = buf; 00516 void *next_out = s_outbuf; 00517 /* create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined). */ 00518 mz_uint comp_flags = s_tdefl_num_probes[MZ_MIN(10, deflate_level)] | ((deflate_level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); 00519 if (!deflate_level) { 00520 comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; 00521 } 00522 status = tdefl_init(&g_deflator, NULL, NULL, comp_flags); 00523 if (status != TDEFL_STATUS_OKAY) { 00524 printf("tdefl_init() failed!\n"); 00525 exit(-1); 00526 } 00527 memset(s_outbuf, 0, sizeof(s_outbuf)); 00528 status = tdefl_compress(&g_deflator, next_in, &in_bytes, next_out, &out_bytes, TDEFL_FINISH); 00529 if (status != TDEFL_STATUS_DONE) { 00530 printf("deflate failed: %d\n", status); 00531 exit(-1); 00532 } 00533 LWIP_ASSERT("out_bytes <= COPY_BUFSIZE", out_bytes <= OUT_BUF_SIZE); 00534 if (out_bytes < fsize) { 00535 ret_buf = (u8_t*)malloc(out_bytes); 00536 LWIP_ASSERT("ret_buf != NULL", ret_buf != NULL); 00537 memcpy(ret_buf, s_outbuf, out_bytes); 00538 { 00539 /* sanity-check compression be inflating and comparing to the original */ 00540 tinfl_status dec_status; 00541 tinfl_decompressor inflator; 00542 size_t dec_in_bytes = out_bytes; 00543 size_t dec_out_bytes = OUT_BUF_SIZE; 00544 next_out = s_checkbuf; 00545 00546 tinfl_init(&inflator); 00547 memset(s_checkbuf, 0, sizeof(s_checkbuf)); 00548 dec_status = tinfl_decompress(&inflator, (const mz_uint8 *)ret_buf, &dec_in_bytes, s_checkbuf, (mz_uint8 *)next_out, &dec_out_bytes, 0); 00549 LWIP_ASSERT("tinfl_decompress failed", dec_status == TINFL_STATUS_DONE); 00550 LWIP_ASSERT("tinfl_decompress size mismatch", fsize == dec_out_bytes); 00551 LWIP_ASSERT("decompressed memcmp failed", !memcmp(s_checkbuf, buf, fsize)); 00552 } 00553 /* free original buffer, use compressed data + size */ 00554 free(buf); 00555 buf = ret_buf; 00556 *file_size = out_bytes; 00557 printf(" - deflate: %d bytes -> %d bytes (%.02f%%)" NEWLINE, (int)fsize, (int)out_bytes, (float)((out_bytes*100.0)/fsize)); 00558 deflatedBytesReduced += (size_t)(fsize - out_bytes); 00559 *is_compressed = 1; 00560 } else { 00561 printf(" - uncompressed: (would be %d bytes larger using deflate)" NEWLINE, (int)(out_bytes - fsize)); 00562 } 00563 } else { 00564 printf(" - uncompressed: (file is larger than deflate bufer)" NEWLINE); 00565 } 00566 } else { 00567 printf(" - SSI file, cannot be compressed" NEWLINE); 00568 } 00569 } 00570 #else 00571 LWIP_UNUSED_ARG(can_be_compressed); 00572 #endif 00573 fclose(inFile); 00574 return buf; 00575 } 00576 00577 void process_file_data(FILE* data_file, u8_t* file_data, size_t file_size) 00578 { 00579 size_t written, i, src_off=0; 00580 00581 size_t off = 0; 00582 for (i = 0; i < file_size; i++) { 00583 LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - 5); 00584 sprintf(&file_buffer_c[off], "0x%02.2x,", file_data[i]); 00585 off += 5; 00586 if ((++src_off % HEX_BYTES_PER_LINE) == 0) { 00587 LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - NEWLINE_LEN); 00588 memcpy(&file_buffer_c[off], NEWLINE, NEWLINE_LEN); 00589 off += NEWLINE_LEN; 00590 } 00591 if (off + 20 >= sizeof(file_buffer_c)) { 00592 written = fwrite(file_buffer_c, 1, off, data_file); 00593 LWIP_ASSERT("written == off", written == off); 00594 off = 0; 00595 } 00596 } 00597 written = fwrite(file_buffer_c, 1, off, data_file); 00598 LWIP_ASSERT("written == off", written == off); 00599 } 00600 00601 int write_checksums(FILE *struct_file, const char *varname, 00602 u16_t hdr_len, u16_t hdr_chksum, const u8_t* file_data, size_t file_size) 00603 { 00604 int chunk_size = TCP_MSS; 00605 int offset, src_offset; 00606 size_t len; 00607 int i = 0; 00608 #if LWIP_TCP_TIMESTAMPS 00609 /* when timestamps are used, usable space is 12 bytes less per segment */ 00610 chunk_size -= 12; 00611 #endif 00612 00613 fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE); 00614 fprintf(struct_file, "const struct fsdata_chksum chksums_%s[] = {" NEWLINE, varname); 00615 00616 if (hdr_len > 0) { 00617 /* add checksum for HTTP header */ 00618 fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, 0, hdr_chksum, hdr_len); 00619 i++; 00620 } 00621 src_offset = 0; 00622 for (offset = hdr_len; ; offset += len) { 00623 unsigned short chksum; 00624 void* data = (void*)&file_data[src_offset]; 00625 len = LWIP_MIN(chunk_size, (int)file_size - src_offset); 00626 if (len == 0) { 00627 break; 00628 } 00629 chksum = ~inet_chksum(data, (u16_t)len); 00630 /* add checksum for data */ 00631 fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, offset, chksum, len); 00632 i++; 00633 } 00634 fprintf(struct_file, "};" NEWLINE); 00635 fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE); 00636 return i; 00637 } 00638 00639 static int is_valid_char_for_c_var(char x) 00640 { 00641 if (((x >= 'A') && (x <= 'Z')) || 00642 ((x >= 'a') && (x <= 'z')) || 00643 ((x >= '0') && (x <= '9')) || 00644 (x == '_')) { 00645 return 1; 00646 } 00647 return 0; 00648 } 00649 00650 static void fix_filename_for_c(char* qualifiedName, size_t max_len) 00651 { 00652 struct file_entry* f; 00653 size_t len = strlen(qualifiedName); 00654 char *new_name = (char*)malloc(len + 2); 00655 int filename_ok; 00656 int cnt = 0; 00657 size_t i; 00658 if (len + 3 == max_len) { 00659 printf("File name too long: \"%s\"\n", qualifiedName); 00660 exit(-1); 00661 } 00662 strcpy(new_name, qualifiedName); 00663 for (i = 0; i < len; i++) { 00664 if (!is_valid_char_for_c_var(new_name[i])) { 00665 new_name[i] = '_'; 00666 } 00667 } 00668 do { 00669 filename_ok = 1; 00670 for (f = first_file; f != NULL; f = f->next) { 00671 if (!strcmp(f->filename_c, new_name)) { 00672 filename_ok = 0; 00673 cnt++; 00674 /* try next unique file name */ 00675 sprintf(&new_name[len], "%d", cnt); 00676 break; 00677 } 00678 } 00679 } while (!filename_ok && (cnt < 999)); 00680 if (!filename_ok) { 00681 printf("Failed to get unique file name: \"%s\"\n", qualifiedName); 00682 exit(-1); 00683 } 00684 strcpy(qualifiedName, new_name); 00685 free(new_name); 00686 } 00687 00688 static void register_filename(const char* qualifiedName) 00689 { 00690 struct file_entry* fe = (struct file_entry*)malloc(sizeof(struct file_entry)); 00691 fe->filename_c = strdup(qualifiedName); 00692 fe->next = NULL; 00693 if (first_file == NULL) { 00694 first_file = last_file = fe; 00695 } else { 00696 last_file->next = fe; 00697 last_file = fe; 00698 } 00699 } 00700 00701 int is_ssi_file(const char* filename) 00702 { 00703 size_t loop; 00704 for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) { 00705 if (strstr(filename, g_pcSSIExtensions[loop])) { 00706 return 1; 00707 } 00708 } 00709 return 0; 00710 } 00711 00712 int process_file(FILE *data_file, FILE *struct_file, const char *filename) 00713 { 00714 char varname[MAX_PATH_LEN]; 00715 int i = 0; 00716 char qualifiedName[MAX_PATH_LEN]; 00717 int file_size; 00718 u16_t http_hdr_chksum = 0; 00719 u16_t http_hdr_len = 0; 00720 int chksum_count = 0; 00721 u8_t flags = 0; 00722 const char* flags_str; 00723 u8_t has_content_len; 00724 u8_t* file_data; 00725 int is_compressed = 0; 00726 00727 /* create qualified name (@todo: prepend slash or not?) */ 00728 sprintf(qualifiedName,"%s/%s", curSubdir, filename); 00729 /* create C variable name */ 00730 strcpy(varname, qualifiedName); 00731 /* convert slashes & dots to underscores */ 00732 fix_filename_for_c(varname, MAX_PATH_LEN); 00733 register_filename(varname); 00734 #if ALIGN_PAYLOAD 00735 /* to force even alignment of array, type 1 */ 00736 fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==1" NEWLINE); 00737 fprintf(data_file, "static const " PAYLOAD_ALIGN_TYPE " dummy_align_%s = %d;" NEWLINE, varname, payload_alingment_dummy_counter++); 00738 fprintf(data_file, "#endif" NEWLINE); 00739 #endif /* ALIGN_PAYLOAD */ 00740 fprintf(data_file, "static const unsigned char FSDATA_ALIGN_PRE data_%s[] FSDATA_ALIGN_POST = {" NEWLINE, varname); 00741 /* encode source file name (used by file system, not returned to browser) */ 00742 fprintf(data_file, "/* %s (%d chars) */" NEWLINE, qualifiedName, strlen(qualifiedName)+1); 00743 file_put_ascii(data_file, qualifiedName, strlen(qualifiedName)+1, &i); 00744 #if ALIGN_PAYLOAD 00745 /* pad to even number of bytes to assure payload is on aligned boundary */ 00746 while(i % PAYLOAD_ALIGNMENT != 0) { 00747 fprintf(data_file, "0x%02.2x,", 0); 00748 i++; 00749 } 00750 #endif /* ALIGN_PAYLOAD */ 00751 fprintf(data_file, NEWLINE); 00752 00753 has_content_len = !is_ssi_file(filename); 00754 file_data = get_file_data(filename, &file_size, includeHttpHeader && has_content_len, &is_compressed); 00755 if (includeHttpHeader) { 00756 file_write_http_header(data_file, filename, file_size, &http_hdr_len, &http_hdr_chksum, has_content_len, is_compressed); 00757 flags = FS_FILE_FLAGS_HEADER_INCLUDED; 00758 if (has_content_len) { 00759 flags |= FS_FILE_FLAGS_HEADER_PERSISTENT; 00760 } 00761 } 00762 if (precalcChksum) { 00763 chksum_count = write_checksums(struct_file, varname, http_hdr_len, http_hdr_chksum, file_data, file_size); 00764 } 00765 00766 /* build declaration of struct fsdata_file in temp file */ 00767 fprintf(struct_file, "const struct fsdata_file file_%s[] = { {" NEWLINE, varname); 00768 fprintf(struct_file, "file_%s," NEWLINE, lastFileVar); 00769 fprintf(struct_file, "data_%s," NEWLINE, varname); 00770 fprintf(struct_file, "data_%s + %d," NEWLINE, varname, i); 00771 fprintf(struct_file, "sizeof(data_%s) - %d," NEWLINE, varname, i); 00772 switch(flags) 00773 { 00774 case(FS_FILE_FLAGS_HEADER_INCLUDED): 00775 flags_str = "FS_FILE_FLAGS_HEADER_INCLUDED"; 00776 break; 00777 case(FS_FILE_FLAGS_HEADER_PERSISTENT): 00778 flags_str = "FS_FILE_FLAGS_HEADER_PERSISTENT"; 00779 break; 00780 case(FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT): 00781 flags_str = "FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT"; 00782 break; 00783 default: 00784 flags_str = "0"; 00785 break; 00786 } 00787 fprintf(struct_file, "%s," NEWLINE, flags_str); 00788 if (precalcChksum) { 00789 fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE); 00790 fprintf(struct_file, "%d, chksums_%s," NEWLINE, chksum_count, varname); 00791 fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE); 00792 } 00793 fprintf(struct_file, "}};" NEWLINE NEWLINE); 00794 strcpy(lastFileVar, varname); 00795 00796 /* write actual file contents */ 00797 i = 0; 00798 fprintf(data_file, NEWLINE "/* raw file data (%d bytes) */" NEWLINE, file_size); 00799 process_file_data(data_file, file_data, file_size); 00800 fprintf(data_file, "};" NEWLINE NEWLINE); 00801 free(file_data); 00802 return 0; 00803 } 00804 00805 int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len, 00806 u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed) 00807 { 00808 int i = 0; 00809 int response_type = HTTP_HDR_OK; 00810 const char* file_type; 00811 const char *cur_string; 00812 size_t cur_len; 00813 int written = 0; 00814 size_t hdr_len = 0; 00815 u16_t acc; 00816 const char *file_ext; 00817 int j; 00818 u8_t provide_last_modified = includeLastModified; 00819 00820 memset(hdr_buf, 0, sizeof(hdr_buf)); 00821 00822 if (useHttp11) { 00823 response_type = HTTP_HDR_OK_11; 00824 } 00825 00826 fprintf(data_file, NEWLINE "/* HTTP header */"); 00827 if (strstr(filename, "404") == filename) { 00828 response_type = HTTP_HDR_NOT_FOUND; 00829 if (useHttp11) { 00830 response_type = HTTP_HDR_NOT_FOUND_11; 00831 } 00832 } else if (strstr(filename, "400") == filename) { 00833 response_type = HTTP_HDR_BAD_REQUEST; 00834 if (useHttp11) { 00835 response_type = HTTP_HDR_BAD_REQUEST_11; 00836 } 00837 } else if (strstr(filename, "501") == filename) { 00838 response_type = HTTP_HDR_NOT_IMPL; 00839 if (useHttp11) { 00840 response_type = HTTP_HDR_NOT_IMPL_11; 00841 } 00842 } 00843 cur_string = g_psHTTPHeaderStrings[response_type]; 00844 cur_len = strlen(cur_string); 00845 fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len); 00846 written += file_put_ascii(data_file, cur_string, cur_len, &i); 00847 i = 0; 00848 if (precalcChksum) { 00849 memcpy(&hdr_buf[hdr_len], cur_string, cur_len); 00850 hdr_len += cur_len; 00851 } 00852 00853 cur_string = serverID; 00854 cur_len = strlen(cur_string); 00855 fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len); 00856 written += file_put_ascii(data_file, cur_string, cur_len, &i); 00857 i = 0; 00858 if (precalcChksum) { 00859 memcpy(&hdr_buf[hdr_len], cur_string, cur_len); 00860 hdr_len += cur_len; 00861 } 00862 00863 file_ext = filename; 00864 if (file_ext != NULL) { 00865 while(strstr(file_ext, ".") != NULL) { 00866 file_ext = strstr(file_ext, "."); 00867 file_ext++; 00868 } 00869 } 00870 if ((file_ext == NULL) || (*file_ext == 0)) { 00871 printf("failed to get extension for file \"%s\", using default.\n", filename); 00872 file_type = HTTP_HDR_DEFAULT_TYPE; 00873 } else { 00874 file_type = NULL; 00875 for (j = 0; j < NUM_HTTP_HEADERS; j++) { 00876 if (!strcmp(file_ext, g_psHTTPHeaders[j].extension)) { 00877 file_type = g_psHTTPHeaders[j].content_type; 00878 break; 00879 } 00880 } 00881 if (file_type == NULL) { 00882 printf("failed to get file type for extension \"%s\", using default.\n", file_ext); 00883 file_type = HTTP_HDR_DEFAULT_TYPE; 00884 } 00885 } 00886 00887 /* Content-Length is used for persistent connections in HTTP/1.1 but also for 00888 download progress in older versions 00889 @todo: just use a big-enough buffer and let the HTTPD send spaces? */ 00890 if (provide_content_len) { 00891 char intbuf[MAX_PATH_LEN]; 00892 int content_len = file_size; 00893 memset(intbuf, 0, sizeof(intbuf)); 00894 cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH]; 00895 cur_len = strlen(cur_string); 00896 fprintf(data_file, NEWLINE "/* \"%s%d\r\n\" (%d+ bytes) */" NEWLINE, cur_string, content_len, cur_len+2); 00897 written += file_put_ascii(data_file, cur_string, cur_len, &i); 00898 if (precalcChksum) { 00899 memcpy(&hdr_buf[hdr_len], cur_string, cur_len); 00900 hdr_len += cur_len; 00901 } 00902 00903 _itoa(content_len, intbuf, 10); 00904 strcat(intbuf, "\r\n"); 00905 cur_len = strlen(intbuf); 00906 written += file_put_ascii(data_file, intbuf, cur_len, &i); 00907 i = 0; 00908 if (precalcChksum) { 00909 memcpy(&hdr_buf[hdr_len], intbuf, cur_len); 00910 hdr_len += cur_len; 00911 } 00912 } 00913 if (provide_last_modified) { 00914 char modbuf[256]; 00915 struct stat stat_data; 00916 struct tm* t; 00917 memset(modbuf, 0, sizeof(modbuf)); 00918 memset(&stat_data, 0, sizeof(stat_data)); 00919 cur_string = modbuf; 00920 strcpy(modbuf, "Last-Modified: "); 00921 if (stat(filename, &stat_data) != 0) { 00922 printf("stat(%s) failed with error %d\n", filename, errno); 00923 exit(-1); 00924 } 00925 t = gmtime(&stat_data.st_mtime); 00926 if (t == NULL) { 00927 printf("gmtime() failed with error %d\n", errno); 00928 exit(-1); 00929 } 00930 strftime(&modbuf[15], sizeof(modbuf)-15, "%a, %d %b %Y %H:%M:%S GMT", t); 00931 cur_len = strlen(cur_string); 00932 fprintf(data_file, NEWLINE "/* \"%s\"\r\n\" (%d+ bytes) */" NEWLINE, cur_string, cur_len+2); 00933 written += file_put_ascii(data_file, cur_string, cur_len, &i); 00934 if (precalcChksum) { 00935 memcpy(&hdr_buf[hdr_len], cur_string, cur_len); 00936 hdr_len += cur_len; 00937 } 00938 00939 modbuf[0] = 0; 00940 strcat(modbuf, "\r\n"); 00941 cur_len = strlen(modbuf); 00942 written += file_put_ascii(data_file, modbuf, cur_len, &i); 00943 i = 0; 00944 if (precalcChksum) { 00945 memcpy(&hdr_buf[hdr_len], modbuf, cur_len); 00946 hdr_len += cur_len; 00947 } 00948 } 00949 00950 /* HTTP/1.1 implements persistent connections */ 00951 if (useHttp11) { 00952 if (provide_content_len) { 00953 cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_KEEPALIVE]; 00954 } else { 00955 /* no Content-Length available, so a persistent connection is no possible 00956 because the client does not know the data length */ 00957 cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE]; 00958 } 00959 cur_len = strlen(cur_string); 00960 fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len); 00961 written += file_put_ascii(data_file, cur_string, cur_len, &i); 00962 i = 0; 00963 if (precalcChksum) { 00964 memcpy(&hdr_buf[hdr_len], cur_string, cur_len); 00965 hdr_len += cur_len; 00966 } 00967 } 00968 00969 #if MAKEFS_SUPPORT_DEFLATE 00970 if (is_compressed) { 00971 /* tell the client about the deflate encoding */ 00972 LWIP_ASSERT("error", deflateNonSsiFiles); 00973 cur_string = "Content-Encoding: deflate\r\n"; 00974 cur_len = strlen(cur_string); 00975 fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len); 00976 written += file_put_ascii(data_file, cur_string, cur_len, &i); 00977 i = 0; 00978 } 00979 #else 00980 LWIP_UNUSED_ARG(is_compressed); 00981 #endif 00982 00983 /* write content-type, ATTENTION: this includes the double-CRLF! */ 00984 cur_string = file_type; 00985 cur_len = strlen(cur_string); 00986 fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len); 00987 written += file_put_ascii(data_file, cur_string, cur_len, &i); 00988 i = 0; 00989 00990 /* ATTENTION: headers are done now (double-CRLF has been written!) */ 00991 00992 if (precalcChksum) { 00993 memcpy(&hdr_buf[hdr_len], cur_string, cur_len); 00994 hdr_len += cur_len; 00995 00996 LWIP_ASSERT("hdr_len <= 0xffff", hdr_len <= 0xffff); 00997 LWIP_ASSERT("strlen(hdr_buf) == hdr_len", strlen(hdr_buf) == hdr_len); 00998 acc = ~inet_chksum(hdr_buf, (u16_t)hdr_len); 00999 *http_hdr_len = (u16_t)hdr_len; 01000 *http_hdr_chksum = acc; 01001 } 01002 01003 return written; 01004 } 01005 01006 int file_put_ascii(FILE *file, const char* ascii_string, int len, int *i) 01007 { 01008 int x; 01009 for (x = 0; x < len; x++) { 01010 unsigned char cur = ascii_string[x]; 01011 fprintf(file, "0x%02.2x,", cur); 01012 if ((++(*i) % HEX_BYTES_PER_LINE) == 0) { 01013 fprintf(file, NEWLINE); 01014 } 01015 } 01016 return len; 01017 } 01018 01019 int s_put_ascii(char *buf, const char *ascii_string, int len, int *i) 01020 { 01021 int x; 01022 int idx = 0; 01023 for (x = 0; x < len; x++) { 01024 unsigned char cur = ascii_string[x]; 01025 sprintf(&buf[idx], "0x%02.2x,", cur); 01026 idx += 5; 01027 if ((++(*i) % HEX_BYTES_PER_LINE) == 0) { 01028 sprintf(&buf[idx], NEWLINE); 01029 idx += NEWLINE_LEN; 01030 } 01031 } 01032 return len; 01033 }
Generated on Tue Jul 12 2022 11:02:41 by
