diff --git a/src/formatstring.c b/src/formatstring.c new file mode 100644 index 0000000..6d4024d --- /dev/null +++ b/src/formatstring.c @@ -0,0 +1,171 @@ +#include "formatstring.h" + +#include +#include +#include +#include +#include + +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) + +void formatstring_error(const char *format, size_t index, const char *message) { + fprintf(stderr, "illegal format string:%zu: %s\n\n\t%s\n\t", index + 1, message, format); + while (index > 0) { + fprintf(stderr, "-"); + -- index; + } + fprintf(stderr, "^\n"); +} + +#define IS_VAR_NAME(PTR, LEN, NAME) (strncmp((PTR), (NAME), MIN(sizeof (NAME), LEN)) == 0) + +#define WRITE_STR(STR, LEN) \ + { \ + const size_t COPY_STR_rem = endptr > bufptr ? endptr - bufptr : 0; \ + const size_t COPY_STR_cpylen = MIN((LEN), COPY_STR_rem); \ + memcpy(bufptr, (STR), COPY_STR_cpylen); \ + bufptr += (LEN); \ + } + +#define HEX_CHARS "0123456789ABCDEF" + +#define WRITE_HEX(NUM) \ + { \ + size_t WRITE_HEX_size = sizeof(NUM); \ + size_t WRITE_HEX_rem = (NUM); \ + while (WRITE_HEX_size) { \ + -- WRITE_HEX_size; \ + char *WRITE_HEX_outptr = bufptr + 2 * WRITE_HEX_size; \ + if (WRITE_HEX_outptr < endptr) { \ + *WRITE_HEX_outptr = HEX_CHARS[(WRITE_HEX_rem >> 4) & 0xF]; \ + } \ + ++ WRITE_HEX_outptr; \ + if (WRITE_HEX_outptr < endptr) { \ + *WRITE_HEX_outptr = HEX_CHARS[WRITE_HEX_rem & 0xF]; \ + } \ + WRITE_HEX_rem >>= 8; \ + } \ + bufptr += 2 * sizeof(NUM); \ + } + +#define WRITE_CHAR(CH) \ + if (bufptr < endptr) { \ + *bufptr = (CH); \ + } \ + ++ bufptr; + +#define WRITE_DEC(NUM) \ + if ((NUM) == 0) { \ + WRITE_CHAR('0'); \ + } else { \ + size_t WRITE_DEC_isize = 0; \ + size_t WRITE_DEC_n = (NUM); \ + while (WRITE_DEC_n) { \ + WRITE_DEC_n /= 10; \ + ++ WRITE_DEC_isize; \ + } \ + WRITE_DEC_n = (NUM); \ + size_t WRITE_DEC_index = WRITE_DEC_isize; \ + while (WRITE_DEC_n) { \ + -- WRITE_DEC_index; \ + const size_t WRITE_DEC_v = WRITE_DEC_n % 10; \ + char *WRITE_DEC_outptr = bufptr + WRITE_DEC_index; \ + if (WRITE_DEC_outptr < endptr) { \ + *WRITE_DEC_outptr = '0' + WRITE_DEC_v; \ + } \ + WRITE_DEC_n /= 10; \ + } \ + bufptr += WRITE_DEC_isize; \ + } + +ssize_t formatstring(char *buffer, size_t buffer_size, const char *format, const char *filename, + size_t index, size_t offset, size_t size, const char *ext) { + assert(buffer_size == 0 || buffer != NULL); + assert(format != NULL); + assert(filename != NULL); + assert(ext != NULL); + + const char *fmtptr = format; + char *bufptr = buffer_size == 0 ? NULL : buffer; + const char *endptr = bufptr + buffer_size; + const size_t filename_len = strlen(filename); + const size_t ext_len = strlen(ext); + + while (*fmtptr) { + const char *nextptr = fmtptr; + while (*nextptr && *nextptr != '{' && *nextptr != '}') { + ++ nextptr; + } + + if (nextptr != fmtptr) { + const size_t len = nextptr - fmtptr; + WRITE_STR(fmtptr, len); + fmtptr = nextptr; + } + + if (*fmtptr == '}') { + ++ fmtptr; + if (*fmtptr == '}') { + WRITE_CHAR('}'); + ++ fmtptr; + } else { + formatstring_error(format, fmtptr - format, "expected '}'"); + errno = EINVAL; + return -1; + } + } else if (*fmtptr == '{') { + ++ fmtptr; + if (*fmtptr == '{') { + WRITE_CHAR('{'); + ++ fmtptr; + } else { + nextptr = fmtptr; + while (*nextptr && *nextptr != '}') { + ++ nextptr; + } + + if (fmtptr == nextptr) { + formatstring_error(format, fmtptr - format, "expected a variable name"); + errno = EINVAL; + return -1; + } + + const size_t len = nextptr - fmtptr; + if (IS_VAR_NAME(fmtptr, len, "filename")) { + WRITE_STR(filename, filename_len); + } else if (IS_VAR_NAME(fmtptr, len, "ext")) { + WRITE_STR(ext, ext_len); + } else if (IS_VAR_NAME(fmtptr, len, "offset")) { + WRITE_HEX(offset); + } else if (IS_VAR_NAME(fmtptr, len, "index")) { + WRITE_DEC(index); + } else if (IS_VAR_NAME(fmtptr, len, "size")) { + WRITE_DEC(size); + } else { + formatstring_error(format, fmtptr - format, "unknown variable name"); + errno = EINVAL; + return -1; + } + + fmtptr = nextptr + 1; + } + } + } + + if (bufptr < endptr) { + *bufptr = 0; + } else if (buffer_size > 0) { + buffer[buffer_size - 1] = 0; + } + ++ bufptr; + + const size_t strsize = bufptr - buffer; + + if (strsize > SSIZE_MAX) { + formatstring_error(format, fmtptr - format, "string too long"); + errno = ERANGE; + return -1; + } + + return (ssize_t) strsize; +} diff --git a/src/formatstring.h b/src/formatstring.h new file mode 100644 index 0000000..b03e81c --- /dev/null +++ b/src/formatstring.h @@ -0,0 +1,13 @@ +#ifndef MEDIAEXTRACT_FORMATSTRING_H__ +#define MEDIAEXTRACT_FORMATSTRING_H__ +#pragma once + +#include +#include + +// {filename}_{offset}.{ext} + +ssize_t formatstring(char *buffer, size_t buffer_size, const char *format, const char *filename, + size_t index, size_t offset, size_t size, const char *ext); + +#endif