/* Copyright: (C) 2022-2023 Daniele 'dzonerzy' Linguaglossa - http://libdzonerzy.so This file is part of libdzonerzy.so. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #define VERSION "0.0.1" typedef enum _format { FORMAT_C, FORMAT_H, } format_t; typedef enum _type { TYPE_EMBED, TYPE_ADVISORY, TYPE_ARTICLE, TYPE_PAGE, } type_t; typedef struct _embed { char *input; char *output; format_t format; type_t type; int verbose; int quiet; int autowrap; } embed_t; static void usage(char **argv); static void version(char **argv); static int embed(embed_t *args); static char *clean_filename(char *filename); int main(int argc, char **argv) { int c = 0; embed_t args = {0}; while ((c = getopt(argc, argv, "hvi:o:f:t:Vqw")) != -1) { switch (c) { case 'h': usage(argv); exit(EXIT_SUCCESS); break; case 'v': version(argv); exit(EXIT_SUCCESS); break; case 'i': args.input = optarg; break; case 'o': args.output = optarg; break; case 'f': // check -f=c -f=h handle equal sign and skip it if (optarg[0] == '=') { optarg++; } switch (optarg[0]) { case 'c': args.format = FORMAT_C; break; case 'h': args.format = FORMAT_H; break; default: usage(argv); exit(EXIT_FAILURE); break; } break; case 't': // check -t=embed -t=advisory handle equal sign and skip it if (optarg[0] == '=') { optarg++; } switch (optarg[0]) { case 'e': args.type = TYPE_EMBED; break; case 'a': args.type = TYPE_ADVISORY; break; case 'p': args.type = TYPE_PAGE; break; case 'A': args.type = TYPE_ARTICLE; break; default: fprintf(stderr, "Unknown type: %s defaulting to embed\n", optarg); args.type = TYPE_EMBED; break; } break; case 'V': args.verbose = 1; break; case 'q': args.quiet = 1; break; case 'w': args.autowrap = 1; break; default: usage(argv); exit(EXIT_FAILURE); break; } } return embed(&args); } void usage(char **argv) { char *name = strrchr(argv[0], '/'); if (name == NULL) { name = argv[0]; } else { name++; } printf("Usage: %s [OPTIONS]\n", name); printf("Embed a file into a C source file.\n"); printf("\n"); printf("Options:\n"); printf(" -h, --help Print this help and exit\n"); printf(" -v, --version Print version and exit\n"); printf(" -i, --input=FILE Input file\n"); printf(" -o, --output=FILE Output file\n"); printf(" -f, --format=FORMAT Output format (c, h)\n"); printf(" -t, --type=TYPE Output type (e,a,p,A - embed, advisory, page, article)\n"); printf(" -w, --autowrap Autowrap output\n"); printf(" -V, --verbose Verbose output\n"); printf(" -q, --quiet Quiet output\n"); printf("\n"); } void version(char **argv) { char *name = strrchr(argv[0], '/'); if (name == NULL) { name = argv[0]; } else { name++; } printf("%s version %s\n", name, VERSION); printf("\n"); } char *clean_filename(char *filename) { char *p = filename; while (*p) { // replace with _ // replace symbols with _ // replace - with _ // replace . with _ // all characters to uppercase switch (*p) { case ' ': case '-': case '.': case '/': case '\\': case ':': case ';': case ',': case '\'': case '"': case '[': case ']': case '{': case '}': case '(': case ')': case '<': case '>': case '?': case '!': case '@': case '#': case '$': case '%': case '^': case '&': case '*': case '+': case '=': case '|': case '`': case '~': *p = '_'; break; default: *p = toupper(*p); break; } p++; } return filename; } unsigned char *read_file_data(FILE *f, size_t *size) { fseek(f, 0, SEEK_END); *size = ftell(f); fseek(f, 0, SEEK_SET); unsigned char *data = malloc(*size); if (data == NULL) { return NULL; } fread(data, 1, *size, f); return data; } unsigned char *image_add_extension(unsigned char *buff, const char *add_ext) { if (buff == NULL || add_ext == NULL) { return NULL; } // Find the size of the buffer size_t len = strlen((char *)buff); // Allocate a new buffer for the modified content unsigned char *new_buff = malloc(len * 2); // Allocate double the size for safety if (new_buff == NULL) { return NULL; } unsigned char *src = buff; unsigned char *dst = new_buff; // Loop through the buffer to find markdown image patterns while (*src) { if (strncmp((char *)src, "![", 2) == 0) { // Copy the "![" *dst++ = *src++; *dst++ = *src++; // Skip everything inside the square brackets until the closing bracket ']' while (*src && *src != ']') { *dst++ = *src++; } // Copy the closing bracket if it's there if (*src == ']') { *dst++ = *src++; } // Now check for the '(' indicating the start of the image URL if (*src == '(') { *dst++ = *src++; unsigned char *start = src; // Find the end of the image URL which is marked by ')' while (*src && *src != ')') { src++; } // Copy the image URL strncpy((char *)dst, (char *)start, src - start); dst += src - start; // Add the extension strcpy((char *)dst, add_ext); dst += strlen(add_ext); // Copy the closing parenthesis if (*src == ')') { *dst++ = *src++; } } } else { // Copy any other character *dst++ = *src++; } } // Null-terminate the new buffer *dst = '\0'; return new_buff; } int embed(embed_t *args) { if (args->input == NULL) { fprintf(stderr, "Input file not specified\n"); return EXIT_FAILURE; } char *formats[] = {"c", "h"}; if (args->verbose) { printf("Input file: %s\n", args->input); printf("Output file: %s\n", args->output); printf("Output format: %s\n", formats[args->format]); } FILE *input = fopen(args->input, "rb"); if (input == NULL) { if (!args->quiet) fprintf(stderr, "Unable to open input file: %s\n", args->input); return EXIT_FAILURE; } FILE *output = NULL; if (args->output != NULL) { output = fopen(args->output, "w"); } else { output = stdout; } char *name_only = strdup(args->input); name_only = strrchr(name_only, '/'); if (name_only == NULL) { name_only = args->input; } else { name_only++; } char *input_name = strdup(args->input); input_name = strrchr(input_name, '/'); if (input_name == NULL) { input_name = args->input; } else { input_name++; } input_name = clean_filename(input_name); fprintf(output, "/*\n"); fprintf(output, " * This file was automatically generated by embed - DO NOT EDIT\n"); fprintf(output, " * https://libdzonerzy.so\n"); fprintf(output, " */\n"); fprintf(output, "\n"); char *typ; switch (args->type) { case TYPE_EMBED: typ = "embed"; break; case TYPE_ADVISORY: typ = "advisory"; break; case TYPE_PAGE: typ = "pages"; break; case TYPE_ARTICLE: typ = "articles"; break; default: typ = "unknown"; break; } if (args->format == FORMAT_C) { fprintf(output, "__attribute__((section(\"%s/%s\"))) const unsigned char %s[] = {", typ, name_only, input_name); if (args->autowrap) { fprintf(output, "\n"); } } else if (args->format == FORMAT_H) { fprintf(output, "#pragma once\n"); fprintf(output, "#ifndef %s_H\n", input_name); fprintf(output, "__attribute__((section(\"%s/%s\"))) const unsigned char %s[] = {", typ, name_only, input_name); if (args->autowrap) { fprintf(output, "\n"); } } size_t i = 0; size_t size = 0; unsigned char *data = read_file_data(input, &size); // update the image URL with the extension if (args->type == TYPE_ARTICLE || args->type == TYPE_PAGE) { unsigned char *data_updated = image_add_extension(data, ".webp"); // free the original data free(data); size = strlen((const char *)data_updated); data = data_updated; } if (args->autowrap) { while (i < size) { if (i == 0) { fprintf(output, " "); } fprintf(output, "0x%02X", data[i]); if (i % 16 == 15) { fprintf(output, ",\n"); fprintf(output, " "); } else { fprintf(output, ", "); } i++; } if (i > 0) { fprintf(output, "\n"); } if (args->type == TYPE_EMBED) fprintf(output, " 0x00,\n"); fprintf(output, "};\n"); } else { while (i < size) { // no wrap if (i == 0) { fprintf(output, "0x%02X", data[i]); } else { fprintf(output, ", 0x%02X", data[i]); } i++; } if (args->type == TYPE_EMBED) fprintf(output, ", 0x00"); fprintf(output, "};\n\n"); } if (args->format == FORMAT_H) { fprintf(output, "#endif /* %s_H */\n", input_name); } fclose(input); fclose(output); if (!args->quiet) printf("Generated resource '%s'\n", input_name); return EXIT_SUCCESS; }