/*
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;
}