/* Author: José Bollo https://gitlab.com/jobol/mustach SPDX-License-Identifier: ISC */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #ifdef _WIN32 #include #endif #include "mustach.h" #include "mustach-wrap.h" /* * It was stated that allowing to include files * through template is not safe when the mustache * template is open to any value because it could * create leaks (example: {{>/etc/passwd}}). */ #if MUSTACH_SAFE # undef MUSTACH_LOAD_TEMPLATE #elif !defined(MUSTACH_LOAD_TEMPLATE) # define MUSTACH_LOAD_TEMPLATE 1 #endif #if !defined(INCLUDE_PARTIAL_EXTENSION) # define INCLUDE_PARTIAL_EXTENSION ".mustache" #endif /* global hook for partials */ int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL; /* internal structure for wrapping */ struct wrap { /* original interface */ const struct mustach_wrap_itf *itf; /* original closure */ void *closure; /* flags */ int flags; /* emiter callback */ mustach_emit_cb_t *emitcb; /* write callback */ mustach_write_cb_t *writecb; }; /* length given by masking with 3 */ enum comp { C_no = 0, C_eq = 1, C_lt = 5, C_le = 6, C_gt = 9, C_ge = 10 }; enum sel { S_none = 0, S_ok = 1, S_objiter = 2, S_ok_or_objiter = S_ok | S_objiter }; static enum comp getcomp(char *head, int sflags) { return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt) : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt) : C_no; } static char *keyval(char *head, int sflags, enum comp *comp) { char *w, car, escaped; enum comp k; k = C_no; w = head; car = *head; escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no); while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) { if (escaped) escaped = 0; else escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\') && (getcomp(head + 1, sflags) != C_no); if (!escaped) *w++ = car; head++; car = *head; } *w = 0; *comp = k; return k == C_no ? NULL : &head[k & 3]; } static char *getkey(char **head, int sflags) { char *result, *iter, *write, car; car = *(iter = *head); if (!car) result = NULL; else { result = write = iter; if (sflags & Mustach_With_JsonPointer) { while (car && car != '/') { if (car == '~') switch (iter[1]) { case '1': car = '/'; /*@fallthrough@*/ case '0': iter++; } *write++ = car; car = *++iter; } *write = 0; while (car == '/') car = *++iter; } else { while (car && car != '.') { if (car == '\\' && (iter[1] == '.' || iter[1] == '\\')) car = *++iter; *write++ = car; car = *++iter; } *write = 0; while (car == '.') car = *++iter; } *head = iter; } return result; } static enum sel sel(struct wrap *w, const char *name) { enum sel result; int i, j, sflags, scmp; char *key, *value; enum comp k; /* make a local writeable copy */ size_t lenname = 1 + strlen(name); char buffer[lenname]; char *copy = buffer; memcpy(copy, name, lenname); /* check if matches json pointer selection */ sflags = w->flags; if (sflags & Mustach_With_JsonPointer) { if (copy[0] == '/') copy++; else sflags ^= Mustach_With_JsonPointer; } /* extract the value, translate the key and get the comparator */ if (sflags & (Mustach_With_Equal | Mustach_With_Compare)) value = keyval(copy, sflags, &k); else { k = C_no; value = NULL; } /* case of . alone if Mustach_With_SingleDot? */ if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/) /* yes, select current */ result = w->itf->sel(w->closure, NULL) ? S_ok : S_none; else { /* not the single dot, extract the first key */ key = getkey(©, sflags); if (key == NULL) return 0; /* select the root item */ if (w->itf->sel(w->closure, key)) result = S_ok; else if (key[0] == '*' && !key[1] && !value && !*copy && (w->flags & Mustach_With_ObjectIter) && w->itf->sel(w->closure, NULL)) result = S_ok_or_objiter; else result = S_none; if (result == S_ok) { /* iterate the selection of sub items */ key = getkey(©, sflags); while(result == S_ok && key) { if (w->itf->subsel(w->closure, key)) /* nothing */; else if (key[0] == '*' && !key[1] && !value && !*copy && (w->flags & Mustach_With_ObjectIter)) result = S_objiter; else result = S_none; key = getkey(©, sflags); } } } /* should it be compared? */ if (result == S_ok && value) { if (!w->itf->compare) result = S_none; else { i = value[0] == '!'; scmp = w->itf->compare(w->closure, &value[i]); switch (k) { case C_eq: j = scmp == 0; break; case C_lt: j = scmp < 0; break; case C_le: j = scmp <= 0; break; case C_gt: j = scmp > 0; break; case C_ge: j = scmp >= 0; break; default: j = i; break; } if (i == j) result = S_none; } } return result; } static int start_callback(void *closure) { struct wrap *w = closure; return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK; } static void stop_callback(void *closure, int status) { struct wrap *w = closure; if (w->itf->stop) w->itf->stop(w->closure, status); } static int emit(struct wrap *w, const char *buffer, size_t size, FILE *file) { int r; if (w->writecb) r = w->writecb(file, buffer, size); else r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM; return r; } static int emit_callback(void *closure, const char *buffer, size_t size, int escape, FILE *file) { struct wrap *w = closure; int r; size_t s, i; char car; if (w->emitcb) r = w->emitcb(file, buffer, size, escape); else if (!escape) r = emit(w, buffer, size, file); else { i = 0; r = MUSTACH_OK; while(i < size && r == MUSTACH_OK) { s = i; while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"') i++; if (i != s) r = emit(w, &buffer[s], i - s, file); if (i < size && r == MUSTACH_OK) { switch(car) { case '<': r = emit(w, "<", 4, file); break; case '>': r = emit(w, ">", 4, file); break; case '&': r = emit(w, "&", 5, file); break; case '"': r = emit(w, """, 6, file); break; } i++; } } } return r; } static int enter_callback(void *closure, const char *name) { struct wrap *w = closure; enum sel s = sel(w, name); return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter); } static int next_callback(void *closure) { struct wrap *w = closure; return w->itf->next(w->closure); } static int leave_callback(void *closure) { struct wrap *w = closure; return w->itf->leave(w->closure); } static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf) { enum sel s = sel(w, name); if (!(s & S_ok)) return 0; return w->itf->get(w->closure, sbuf, s & S_objiter); } static int get_callback(void *closure, const char *name, struct mustach_sbuf *sbuf) { struct wrap *w = closure; if (getoptional(w, name, sbuf) <= 0) { if (w->flags & Mustach_With_ErrorUndefined) return MUSTACH_ERROR_UNDEFINED_TAG; sbuf->value = ""; } return MUSTACH_OK; } #if MUSTACH_LOAD_TEMPLATE static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf) { static char extension[] = INCLUDE_PARTIAL_EXTENSION; size_t s; long pos; FILE *file; char *path, *buffer; /* allocate path */ s = strlen(name); path = malloc(s + sizeof extension); if (path == NULL) return MUSTACH_ERROR_SYSTEM; /* try without extension first */ memcpy(path, name, s + 1); file = fopen(path, "r"); if (file == NULL) { memcpy(&path[s], extension, sizeof extension); file = fopen(path, "r"); } free(path); /* if file opened */ if (file == NULL) return MUSTACH_ERROR_PARTIAL_NOT_FOUND; /* compute file size */ if (fseek(file, 0, SEEK_END) >= 0 && (pos = ftell(file)) >= 0 && fseek(file, 0, SEEK_SET) >= 0) { /* allocate value */ s = (size_t)pos; buffer = malloc(s + 1); if (buffer != NULL) { /* read value */ if (1 == fread(buffer, s, 1, file)) { /* force zero at end */ sbuf->value = buffer; buffer[s] = 0; sbuf->freecb = free; fclose(file); return MUSTACH_OK; } free(buffer); } } fclose(file); return MUSTACH_ERROR_SYSTEM; } #endif static int partial_callback(void *closure, const char *name, struct mustach_sbuf *sbuf) { struct wrap *w = closure; int rc; if (mustach_wrap_get_partial != NULL) { rc = mustach_wrap_get_partial(name, sbuf); if (rc != MUSTACH_ERROR_PARTIAL_NOT_FOUND) { if (rc != MUSTACH_OK) sbuf->value = ""; return rc; } } #if MUSTACH_LOAD_TEMPLATE if (w->flags & Mustach_With_PartialDataFirst) { if (getoptional(w, name, sbuf) > 0) rc = MUSTACH_OK; else rc = get_partial_from_file(name, sbuf); } else { rc = get_partial_from_file(name, sbuf); if (rc != MUSTACH_OK && getoptional(w, name, sbuf) > 0) rc = MUSTACH_OK; } #else rc = getoptional(w, name, sbuf) > 0 ? MUSTACH_OK : MUSTACH_ERROR_PARTIAL_NOT_FOUND; #endif if (rc != MUSTACH_OK) sbuf->value = ""; return MUSTACH_OK; } const struct mustach_itf mustach_wrap_itf = { .start = start_callback, .put = NULL, .enter = enter_callback, .next = next_callback, .leave = leave_callback, .partial = partial_callback, .get = get_callback, .emit = emit_callback, .stop = stop_callback }; static void wrap_init(struct wrap *wrap, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, mustach_write_cb_t *writecb) { if (flags & Mustach_With_Compare) flags |= Mustach_With_Equal; wrap->closure = closure; wrap->itf = itf; wrap->flags = flags; wrap->emitcb = emitcb; wrap->writecb = writecb; } int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file) { struct wrap w; wrap_init(&w, itf, closure, flags, NULL, NULL); return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file); } int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd) { struct wrap w; wrap_init(&w, itf, closure, flags, NULL, NULL); return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd); } int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size) { struct wrap w; wrap_init(&w, itf, closure, flags, NULL, NULL); return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size); } int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure) { struct wrap w; wrap_init(&w, itf, closure, flags, NULL, writecb); return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure); } int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure) { struct wrap w; wrap_init(&w, itf, closure, flags, emitcb, NULL); return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure); }