mustach-wrap.c (11474B)
1 /* 2 Author: José Bollo <jobol@nonadev.net> 3 4 https://gitlab.com/jobol/mustach 5 6 SPDX-License-Identifier: ISC 7 */ 8 9 #ifndef _GNU_SOURCE 10 #define _GNU_SOURCE 11 #endif 12 13 #include <stdlib.h> 14 #include <stdio.h> 15 #include <string.h> 16 #ifdef _WIN32 17 #include <malloc.h> 18 #endif 19 20 #include "mustach.h" 21 #include "mustach-wrap.h" 22 23 /* 24 * It was stated that allowing to include files 25 * through template is not safe when the mustache 26 * template is open to any value because it could 27 * create leaks (example: {{>/etc/passwd}}). 28 */ 29 #if MUSTACH_SAFE 30 # undef MUSTACH_LOAD_TEMPLATE 31 #elif !defined(MUSTACH_LOAD_TEMPLATE) 32 # define MUSTACH_LOAD_TEMPLATE 1 33 #endif 34 35 #if !defined(INCLUDE_PARTIAL_EXTENSION) 36 # define INCLUDE_PARTIAL_EXTENSION ".mustache" 37 #endif 38 39 /* global hook for partials */ 40 int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL; 41 42 /* internal structure for wrapping */ 43 struct wrap { 44 /* original interface */ 45 const struct mustach_wrap_itf *itf; 46 47 /* original closure */ 48 void *closure; 49 50 /* flags */ 51 int flags; 52 53 /* emiter callback */ 54 mustach_emit_cb_t *emitcb; 55 56 /* write callback */ 57 mustach_write_cb_t *writecb; 58 }; 59 60 /* length given by masking with 3 */ 61 enum comp { 62 C_no = 0, 63 C_eq = 1, 64 C_lt = 5, 65 C_le = 6, 66 C_gt = 9, 67 C_ge = 10 68 }; 69 70 enum sel { 71 S_none = 0, 72 S_ok = 1, 73 S_objiter = 2, 74 S_ok_or_objiter = S_ok | S_objiter 75 }; 76 77 static enum comp getcomp(char *head, int sflags) 78 { 79 return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq 80 : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt) 81 : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt) 82 : C_no; 83 } 84 85 static char *keyval(char *head, int sflags, enum comp *comp) 86 { 87 char *w, car, escaped; 88 enum comp k; 89 90 k = C_no; 91 w = head; 92 car = *head; 93 escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no); 94 while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) { 95 if (escaped) 96 escaped = 0; 97 else 98 escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\') 99 && (getcomp(head + 1, sflags) != C_no); 100 if (!escaped) 101 *w++ = car; 102 head++; 103 car = *head; 104 } 105 *w = 0; 106 *comp = k; 107 return k == C_no ? NULL : &head[k & 3]; 108 } 109 110 static char *getkey(char **head, int sflags) 111 { 112 char *result, *iter, *write, car; 113 114 car = *(iter = *head); 115 if (!car) 116 result = NULL; 117 else { 118 result = write = iter; 119 if (sflags & Mustach_With_JsonPointer) 120 { 121 while (car && car != '/') { 122 if (car == '~') 123 switch (iter[1]) { 124 case '1': car = '/'; /*@fallthrough@*/ 125 case '0': iter++; 126 } 127 *write++ = car; 128 car = *++iter; 129 } 130 *write = 0; 131 while (car == '/') 132 car = *++iter; 133 } 134 else 135 { 136 while (car && car != '.') { 137 if (car == '\\' && (iter[1] == '.' || iter[1] == '\\')) 138 car = *++iter; 139 *write++ = car; 140 car = *++iter; 141 } 142 *write = 0; 143 while (car == '.') 144 car = *++iter; 145 } 146 *head = iter; 147 } 148 return result; 149 } 150 151 static enum sel sel(struct wrap *w, const char *name) 152 { 153 enum sel result; 154 int i, j, sflags, scmp; 155 char *key, *value; 156 enum comp k; 157 158 /* make a local writeable copy */ 159 size_t lenname = 1 + strlen(name); 160 char buffer[lenname]; 161 char *copy = buffer; 162 memcpy(copy, name, lenname); 163 164 /* check if matches json pointer selection */ 165 sflags = w->flags; 166 if (sflags & Mustach_With_JsonPointer) { 167 if (copy[0] == '/') 168 copy++; 169 else 170 sflags ^= Mustach_With_JsonPointer; 171 } 172 173 /* extract the value, translate the key and get the comparator */ 174 if (sflags & (Mustach_With_Equal | Mustach_With_Compare)) 175 value = keyval(copy, sflags, &k); 176 else { 177 k = C_no; 178 value = NULL; 179 } 180 181 /* case of . alone if Mustach_With_SingleDot? */ 182 if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/) 183 /* yes, select current */ 184 result = w->itf->sel(w->closure, NULL) ? S_ok : S_none; 185 else 186 { 187 /* not the single dot, extract the first key */ 188 key = getkey(©, sflags); 189 if (key == NULL) 190 return 0; 191 192 /* select the root item */ 193 if (w->itf->sel(w->closure, key)) 194 result = S_ok; 195 else if (key[0] == '*' 196 && !key[1] 197 && !value 198 && !*copy 199 && (w->flags & Mustach_With_ObjectIter) 200 && w->itf->sel(w->closure, NULL)) 201 result = S_ok_or_objiter; 202 else 203 result = S_none; 204 if (result == S_ok) { 205 /* iterate the selection of sub items */ 206 key = getkey(©, sflags); 207 while(result == S_ok && key) { 208 if (w->itf->subsel(w->closure, key)) 209 /* nothing */; 210 else if (key[0] == '*' 211 && !key[1] 212 && !value 213 && !*copy 214 && (w->flags & Mustach_With_ObjectIter)) 215 result = S_objiter; 216 else 217 result = S_none; 218 key = getkey(©, sflags); 219 } 220 } 221 } 222 /* should it be compared? */ 223 if (result == S_ok && value) { 224 if (!w->itf->compare) 225 result = S_none; 226 else { 227 i = value[0] == '!'; 228 scmp = w->itf->compare(w->closure, &value[i]); 229 switch (k) { 230 case C_eq: j = scmp == 0; break; 231 case C_lt: j = scmp < 0; break; 232 case C_le: j = scmp <= 0; break; 233 case C_gt: j = scmp > 0; break; 234 case C_ge: j = scmp >= 0; break; 235 default: j = i; break; 236 } 237 if (i == j) 238 result = S_none; 239 } 240 } 241 return result; 242 } 243 244 static int start_callback(void *closure) 245 { 246 struct wrap *w = closure; 247 return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK; 248 } 249 250 static void stop_callback(void *closure, int status) 251 { 252 struct wrap *w = closure; 253 if (w->itf->stop) 254 w->itf->stop(w->closure, status); 255 } 256 257 static int emit(struct wrap *w, const char *buffer, size_t size, FILE *file) 258 { 259 int r; 260 261 if (w->writecb) 262 r = w->writecb(file, buffer, size); 263 else 264 r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM; 265 return r; 266 } 267 268 static int emit_callback(void *closure, const char *buffer, size_t size, int escape, FILE *file) 269 { 270 struct wrap *w = closure; 271 int r; 272 size_t s, i; 273 char car; 274 275 if (w->emitcb) 276 r = w->emitcb(file, buffer, size, escape); 277 else if (!escape) 278 r = emit(w, buffer, size, file); 279 else { 280 i = 0; 281 r = MUSTACH_OK; 282 while(i < size && r == MUSTACH_OK) { 283 s = i; 284 while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"') 285 i++; 286 if (i != s) 287 r = emit(w, &buffer[s], i - s, file); 288 if (i < size && r == MUSTACH_OK) { 289 switch(car) { 290 case '<': r = emit(w, "<", 4, file); break; 291 case '>': r = emit(w, ">", 4, file); break; 292 case '&': r = emit(w, "&", 5, file); break; 293 case '"': r = emit(w, """, 6, file); break; 294 } 295 i++; 296 } 297 } 298 } 299 return r; 300 } 301 302 static int enter_callback(void *closure, const char *name) 303 { 304 struct wrap *w = closure; 305 enum sel s = sel(w, name); 306 return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter); 307 } 308 309 static int next_callback(void *closure) 310 { 311 struct wrap *w = closure; 312 return w->itf->next(w->closure); 313 } 314 315 static int leave_callback(void *closure) 316 { 317 struct wrap *w = closure; 318 return w->itf->leave(w->closure); 319 } 320 321 static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf) 322 { 323 enum sel s = sel(w, name); 324 if (!(s & S_ok)) 325 return 0; 326 return w->itf->get(w->closure, sbuf, s & S_objiter); 327 } 328 329 static int get_callback(void *closure, const char *name, struct mustach_sbuf *sbuf) 330 { 331 struct wrap *w = closure; 332 if (getoptional(w, name, sbuf) <= 0) { 333 if (w->flags & Mustach_With_ErrorUndefined) 334 return MUSTACH_ERROR_UNDEFINED_TAG; 335 sbuf->value = ""; 336 } 337 return MUSTACH_OK; 338 } 339 340 #if MUSTACH_LOAD_TEMPLATE 341 static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf) 342 { 343 static char extension[] = INCLUDE_PARTIAL_EXTENSION; 344 size_t s; 345 long pos; 346 FILE *file; 347 char *path, *buffer; 348 349 /* allocate path */ 350 s = strlen(name); 351 path = malloc(s + sizeof extension); 352 if (path == NULL) 353 return MUSTACH_ERROR_SYSTEM; 354 355 /* try without extension first */ 356 memcpy(path, name, s + 1); 357 file = fopen(path, "r"); 358 if (file == NULL) { 359 memcpy(&path[s], extension, sizeof extension); 360 file = fopen(path, "r"); 361 } 362 free(path); 363 364 /* if file opened */ 365 if (file == NULL) 366 return MUSTACH_ERROR_PARTIAL_NOT_FOUND; 367 368 /* compute file size */ 369 if (fseek(file, 0, SEEK_END) >= 0 370 && (pos = ftell(file)) >= 0 371 && fseek(file, 0, SEEK_SET) >= 0) { 372 /* allocate value */ 373 s = (size_t)pos; 374 buffer = malloc(s + 1); 375 if (buffer != NULL) { 376 /* read value */ 377 if (1 == fread(buffer, s, 1, file)) { 378 /* force zero at end */ 379 sbuf->value = buffer; 380 buffer[s] = 0; 381 sbuf->freecb = free; 382 fclose(file); 383 return MUSTACH_OK; 384 } 385 free(buffer); 386 } 387 } 388 fclose(file); 389 return MUSTACH_ERROR_SYSTEM; 390 } 391 #endif 392 393 static int partial_callback(void *closure, const char *name, struct mustach_sbuf *sbuf) 394 { 395 struct wrap *w = closure; 396 int rc; 397 if (mustach_wrap_get_partial != NULL) { 398 rc = mustach_wrap_get_partial(name, sbuf); 399 if (rc != MUSTACH_ERROR_PARTIAL_NOT_FOUND) { 400 if (rc != MUSTACH_OK) 401 sbuf->value = ""; 402 return rc; 403 } 404 } 405 #if MUSTACH_LOAD_TEMPLATE 406 if (w->flags & Mustach_With_PartialDataFirst) { 407 if (getoptional(w, name, sbuf) > 0) 408 rc = MUSTACH_OK; 409 else 410 rc = get_partial_from_file(name, sbuf); 411 } 412 else { 413 rc = get_partial_from_file(name, sbuf); 414 if (rc != MUSTACH_OK && getoptional(w, name, sbuf) > 0) 415 rc = MUSTACH_OK; 416 } 417 #else 418 rc = getoptional(w, name, sbuf) > 0 ? MUSTACH_OK : MUSTACH_ERROR_PARTIAL_NOT_FOUND; 419 #endif 420 if (rc != MUSTACH_OK) 421 sbuf->value = ""; 422 return MUSTACH_OK; 423 } 424 425 const struct mustach_itf mustach_wrap_itf = { 426 .start = start_callback, 427 .put = NULL, 428 .enter = enter_callback, 429 .next = next_callback, 430 .leave = leave_callback, 431 .partial = partial_callback, 432 .get = get_callback, 433 .emit = emit_callback, 434 .stop = stop_callback 435 }; 436 437 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) 438 { 439 if (flags & Mustach_With_Compare) 440 flags |= Mustach_With_Equal; 441 wrap->closure = closure; 442 wrap->itf = itf; 443 wrap->flags = flags; 444 wrap->emitcb = emitcb; 445 wrap->writecb = writecb; 446 } 447 448 int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file) 449 { 450 struct wrap w; 451 wrap_init(&w, itf, closure, flags, NULL, NULL); 452 return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file); 453 } 454 455 int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd) 456 { 457 struct wrap w; 458 wrap_init(&w, itf, closure, flags, NULL, NULL); 459 return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd); 460 } 461 462 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) 463 { 464 struct wrap w; 465 wrap_init(&w, itf, closure, flags, NULL, NULL); 466 return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size); 467 } 468 469 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) 470 { 471 struct wrap w; 472 wrap_init(&w, itf, closure, flags, NULL, writecb); 473 return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure); 474 } 475 476 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) 477 { 478 struct wrap w; 479 wrap_init(&w, itf, closure, flags, emitcb, NULL); 480 return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure); 481 } 482