format.y (15486B)
1 %{ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <stdarg.h> 5 #include <string.h> 6 #include <ctype.h> 7 #include "hirc.h" 8 #include "data/formats.h" 9 10 #define YYSTYPE char * 11 12 static void parse_append(char **dest, char *str); 13 static char *parse_dup(char *str); 14 static char *parse_printf(char *fmt, ...); 15 static void yyerror(char *msg); 16 static int yylex(void); 17 18 #define BRACEMAX 16 19 #define RETURN(s) do {prev = s; return s;} while (0) 20 #define ISSPECIAL(s) isspecial(s, prev, bracelvl, bracetype, styleseg) 21 22 enum { 23 PARSE_TIME, 24 PARSE_LEFT, 25 PARSE_RIGHT, 26 PARSE_LAST 27 }; 28 29 static int parse_error = 0; 30 static char *parse_in = NULL; 31 static char *parse_out[PARSE_LAST] = {NULL, NULL, NULL}; 32 static int parse_pos = PARSE_LEFT; 33 static char **parse_params = NULL; 34 static struct Server *parse_server = NULL; 35 static int parse_divbool = 0; 36 37 enum { 38 var_raw, 39 var_cmd, 40 var_nick, 41 var_ident, 42 var_host, 43 var_priv, 44 var_channel, 45 var_topic, 46 var_server, 47 var_time, 48 }; 49 static struct { 50 char *name; 51 char *val; 52 } vars[] = { 53 [var_raw] = {"raw", NULL}, 54 [var_cmd] = {"cmd", NULL}, 55 [var_nick] = {"nick", NULL}, 56 [var_ident] = {"ident", NULL}, 57 [var_host] = {"host", NULL}, 58 [var_priv] = {"priv", NULL}, 59 [var_channel] = {"channel", NULL}, 60 [var_topic] = {"topic", NULL}, 61 [var_server] = {"server", NULL}, 62 [var_time] = {"time", NULL}, 63 {NULL, NULL}, 64 }; 65 %} 66 67 %token VAR STYLE LBRACE RBRACE COLON COMMA 68 %token STRING 69 70 %% 71 72 grammar: /* empty */ { parse_append(&parse_out[parse_pos], ""); } 73 | grammar var { parse_append(&parse_out[parse_pos], $2); } 74 | grammar style { parse_append(&parse_out[parse_pos], $2); } 75 | grammar STRING { parse_append(&parse_out[parse_pos], $2); } 76 ; 77 78 var: VAR LBRACE STRING RBRACE { 79 char buf[8192]; 80 char *tmp; 81 int i, num; 82 if (strisnum($3, 0) && (num = strtoll($3, NULL, 10)) && param_len(parse_params) >= num) { 83 if (num == 2 && **(parse_params + num - 1) == 1 && 84 (strcmp_n(vars[var_cmd].val, "PRIVMSG") == 0 || strcmp_n(vars[var_cmd].val, "NOTICE") == 0)) { 85 if (strncmp(*(parse_params + num - 1) + 1, "ACTION", CONSTLEN("ACTION")) == 0) 86 tmp = parse_dup(*(parse_params + num - 1) + 1 + CONSTLEN("ACTION ")); 87 else 88 tmp = parse_dup(*(parse_params + num - 1) + 1); 89 if (tmp[strlen(tmp) - 1] == 1) 90 tmp[strlen(tmp) - 1] = '\0'; 91 $$ = tmp; 92 } else $$ = *(parse_params + num - 1); 93 goto varfin; 94 } 95 if (*$3 && *($3 + strlen($3) - 1) == '-') { 96 *($3 + strlen($3) - 1) = '\0'; 97 if (strisnum($3, 0) && (num = strtoll($3, NULL, 10)) && param_len(parse_params) >= num) { 98 buf[0] = '\0'; 99 for (i = num; i <= param_len(parse_params); i++) { 100 strlcat(buf, *(parse_params + i - 1), sizeof(buf)); 101 strlcat(buf, " ", sizeof(buf)); 102 } 103 $$ = parse_dup(buf); 104 goto varfin; 105 } else { 106 *($3 + strlen($3) - 1) = '\0'; 107 } 108 } 109 for (i = 0; vars[i].name; i++) { 110 if (vars[i].val && strcmp(vars[i].name, $3) == 0) { 111 $$ = parse_printf("%s", vars[i].val); 112 goto varfin; 113 } 114 } 115 $$ = parse_printf("${%s}", $3); 116 varfin: 117 ((void)0); 118 } 119 ; 120 121 style: STYLE LBRACE STRING RBRACE { 122 if (strcmp($3, "b") == 0) { 123 $$ = parse_printf("\02"); /* ^B */ 124 } else if (strcmp($3, "c") == 0) { 125 $$ = parse_printf("\0399,99"); /* ^C */ 126 } else if (strcmp($3, "i") == 0) { 127 $$ = parse_printf("\011"); /* ^I */ 128 } else if (strcmp($3, "o") == 0) { 129 $$ = parse_printf("\017"); /* ^O */ 130 } else if (strcmp($3, "r") == 0) { 131 $$ = parse_printf("\022"); /* ^R */ 132 } else if (strcmp($3, "u") == 0) { 133 $$ = parse_printf("\025"); /* ^U */ 134 } else if (strcmp($3, "=") == 0) { 135 if (parse_pos == PARSE_LEFT) { 136 parse_pos = PARSE_RIGHT; 137 $$ = NULL; 138 } else { 139 $$ = parse_dup(" "); 140 } 141 } else if (strcmp($3, "_time") == 0) { /* special style to mark end of timestamp */ 142 if (parse_pos == PARSE_TIME) { 143 parse_pos = parse_divbool ? PARSE_LEFT : PARSE_RIGHT; 144 $$ = NULL; 145 } else { 146 yyerror("%{_time} used erroneously here: " 147 "this style is meant for internal use only, " 148 "this isn't a bug if you manually inserted this in a format"); 149 YYERROR; 150 } 151 } else { 152 $$ = parse_printf("%%{%s}", $3); 153 } 154 } 155 | STYLE LBRACE STRING COLON sstring RBRACE { 156 struct Nick *nick; 157 if (strcmp($3, "c") == 0) { 158 if (strlen($5) <= 2 && isdigit(*$5) && (!*($5+1) || isdigit(*($5+1)))) 159 $$ = parse_printf("\03%02d" /* ^C */, atoi($5)); 160 } else if (strcmp($3, "nick") == 0) { 161 nick = nick_create($5, ' ', parse_server); 162 $$ = parse_printf("\03%02d" /* ^C */, nick_getcolour(nick)); 163 nick_free(nick); 164 } else if (strcmp($3, "rdate") == 0) { 165 if (strisnum($5, 0)) { 166 $$ = parse_printf("%s", strrdate((time_t)strtoll($5, NULL, 10))); 167 } else { 168 yyerror("invalid date in invocation of %{rdate:...}"); 169 YYERROR; 170 } 171 } else { 172 $$ = parse_printf("%%{%s:%s}", $3, $5); 173 } 174 } 175 | STYLE LBRACE STRING COLON sstring COMMA sstring RBRACE { 176 char stime[1024]; 177 time_t dtime; 178 if (strcmp($3, "c") == 0) { 179 if (strlen($5) <= 2 && isdigit(*$5) && (!*($5+1) || isdigit(*($5+1))) && 180 strlen($7) <= 2 && isdigit(*$7) && (!*($7+1) || isdigit(*($5+1)))) 181 $$ = parse_printf("\03%02d,%02d" /* ^C */, atoi($5), atoi($7)); 182 else 183 $$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7); 184 } else if (strcmp($3, "pad") == 0) { 185 if (strisnum($5, 1)) { 186 $$ = parse_printf("%1$*2$s", $7, (int)strtoll($5, NULL, 0)); 187 } else { 188 yyerror("second argument to %{pad:...} must be a integer"); 189 YYERROR; 190 } 191 } else if (strcmp($3, "time") == 0) { 192 if (strisnum($7, 0)) { 193 dtime = (time_t)strtoll($7, NULL, 0); 194 strftime(stime, sizeof(stime), $5, localtime(&dtime)); 195 $$ = parse_dup(stime); 196 } else { 197 yyerror("invalid date in invocation of %{time:...}"); 198 YYERROR; 199 } 200 } else { 201 $$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7); 202 } 203 } 204 | STYLE LBRACE STRING COLON sstring COMMA sstring COMMA sstring RBRACE { 205 int num; 206 char *val; 207 if (strcmp($3, "split") == 0) { 208 num = strtoll($5, NULL, 10); 209 val = strntok($9, $7, num); 210 if (strisnum($5, 0) && val) { 211 $$ = parse_dup(val); 212 } else if (strisnum($5, 0)) { 213 $$ = NULL; 214 } else { 215 yyerror("first argument to %{split:...} must be an integer"); 216 YYERROR; 217 } 218 } else { 219 $$ = parse_printf("%%{%s:%s,%s,%s}", $3, $5, $7, $9); 220 } 221 } 222 ; 223 224 sstring: STRING 225 | var 226 | style 227 ; 228 229 %% 230 231 static void 232 yyerror(char *msg) { 233 parse_error = 1; 234 ui_error("parsing '%s': %s", parse_in, msg); 235 } 236 237 /* Keep in mind: parse_append doesn't use parse_dup. */ 238 static void 239 parse_append(char **dest, char *str) { 240 size_t size; 241 size_t len; 242 243 if (!str) 244 return; 245 246 if (*dest) 247 len = strlen(*dest); 248 else 249 len = 0; 250 251 size = len + strlen(str) + 1; 252 if (!*dest) 253 *dest = emalloc(size); 254 else 255 *dest = erealloc(*dest, size); 256 257 (len ? strlcat : strlcpy)(*dest, str, size); 258 } 259 260 /* alloc memory to be free'd all at once when parsing is complete 261 * pfree() must not be called on anything returned */ 262 static char * 263 parse_dup(char *str) { 264 static void **mema = NULL; 265 static size_t mems = 0; 266 void *mem = NULL; 267 size_t i; 268 269 if (str) { 270 mem = strdup(str); 271 if (!mems) 272 mema = emalloc((sizeof(char *)) * (mems + 1)); 273 else 274 mema = erealloc(mema, (sizeof(char *)) * (mems + 1)); 275 276 *(mema + mems) = mem; 277 mems++; 278 } else if (mema && mems) { 279 for (i = 0; i < mems; i++) 280 pfree(mema + i); /* already a double pointer */ 281 pfree(&mema); 282 mems = 0; 283 mema = NULL; 284 } 285 286 return mem; 287 } 288 289 static char * 290 parse_printf(char *format, ...) { 291 va_list ap; 292 char buf[8192]; 293 294 va_start(ap, format); 295 vsnprintf(buf, sizeof(buf), format, ap); 296 va_end(ap); 297 return parse_dup(buf); 298 } 299 300 static int 301 isspecial(char *s, int prev, int bracelvl, int bracetype[static BRACEMAX], int styleseg[static BRACEMAX]) { 302 if ((*s == '$' && (*(s+1) == '{' && bracelvl != BRACEMAX)) || 303 (*s == '%' && (*(s+1) == '{' && bracelvl != BRACEMAX)) || 304 (*s == '{' && (prev == VAR || prev == STYLE)) || 305 (*s == '}' && (bracelvl)) || 306 (*s == ',' && (bracelvl && bracetype[bracelvl - 1] == STYLE && styleseg[bracelvl - 1])) || 307 (*s == ':' && (bracelvl && bracetype[bracelvl - 1] == STYLE && !styleseg[bracelvl - 1]))) 308 return 1; 309 else 310 return 0; 311 } 312 313 static int 314 yylex(void) { 315 static char *s = NULL, *p = NULL; 316 static int bracelvl; 317 static int bracetype[BRACEMAX]; 318 static int styleseg[BRACEMAX]; 319 static int prev = 0; 320 char strlval[8192]; 321 int i; 322 323 if (!s || prev == 0) { 324 s = parse_in; 325 bracelvl = 0; 326 prev = -1; 327 } 328 329 if (!*s) 330 RETURN(0); 331 332 if (ISSPECIAL(s)) { 333 switch (*s) { 334 case '$': 335 s++; 336 RETURN(VAR); 337 case '%': 338 s++; 339 RETURN(STYLE); 340 case '{': 341 bracelvl++; 342 bracetype[bracelvl - 1] = prev; 343 if (prev == STYLE) 344 styleseg[bracelvl - 1] = 0; 345 s++; 346 RETURN(LBRACE); 347 case '}': 348 bracelvl--; 349 s++; 350 RETURN(RBRACE); 351 case ',': 352 styleseg[bracelvl - 1]++; 353 s++; 354 RETURN(COMMA); 355 case ':': 356 styleseg[bracelvl - 1]++; 357 s++; 358 RETURN(COLON); 359 } 360 } 361 362 /* first char guaranteed due to previous ISSPECIAL() */ 363 strlval[0] = *s; 364 365 for (p = s + 1, i = 1; *p && i < sizeof(strlval); p++) { 366 if (ISSPECIAL(p)) 367 break; 368 if (*p == '\\' && ISSPECIAL(p+1)) { 369 strlval[i++] = *(p+1); 370 p++; 371 } else if (*p == '\\' && *(p+1) == 'n') { 372 strlval[i++] = '\n'; 373 p++; 374 } else { 375 strlval[i++] = *p; 376 } 377 } 378 379 strlval[i] = '\0'; 380 yylval = parse_dup(strlval); 381 s = p; 382 RETURN(STRING); 383 } 384 385 /* 386 * Exposed functions 387 */ 388 389 char * 390 format_get(struct History *hist) { 391 char *cmd, *p1, *p2; 392 int i; 393 394 assert_warn(hist, NULL); 395 396 if (!hist->params) 397 goto raw; 398 399 cmd = *(hist->params); 400 p1 = *(hist->params+1); 401 p2 = *(hist->params+2); 402 403 if (strcmp_n(cmd, "MODE") == 0) { 404 if (p1 && serv_ischannel(hist->origin->server, p1)) 405 cmd = "MODE-CHANNEL"; 406 else if (hist->from && nick_isself(hist->from) && strcmp_n(hist->from->nick, p1) == 0) 407 cmd = "MODE-NICK-SELF"; 408 else 409 cmd = "MODE-NICK"; 410 } else if (strcmp_n(cmd, "PRIVMSG") == 0) { 411 /* ascii 1 is ^A */ 412 if (*p2 == 1 && strncmp(p2 + 1, "ACTION", CONSTLEN("ACTION")) == 0) 413 cmd = "PRIVMSG-ACTION"; 414 else if (*p2 == 1) 415 cmd = "PRIVMSG-CTCP"; 416 } else if (strcmp_n(cmd, "NOTICE") == 0 && *p2 == 1) { 417 cmd = "NOTICE-CTCP"; 418 } 419 420 for (i=0; formatmap[i].cmd; i++) 421 if (formatmap[i].format && strcmp_n(formatmap[i].cmd, cmd) == 0) 422 return formatmap[i].format; 423 424 if (isdigit(*cmd) && isdigit(*(cmd+1)) && isdigit(*(cmd+2)) && !*(cmd+3)) 425 return "format.rpl.other"; 426 427 raw: 428 return "format.other"; 429 } 430 431 char * 432 format(struct Window *window, char *format, struct History *hist) { 433 char buf[4096]; 434 char *ts; 435 char *rformat; 436 int x; 437 size_t len, pad; 438 int clen[PARSE_LAST]; /* ui_strlenc */ 439 int alen[PARSE_LAST]; /* strlen */ 440 int divlen = config_getl("divider.margin"); 441 int divbool = 0; 442 char *divstr = config_gets("divider.string"); 443 char priv[2]; 444 size_t i; 445 446 assert_warn(format || hist, NULL); 447 if (!format) 448 format = config_gets(format_get(hist)); 449 assert_warn(format, NULL); 450 451 vars[var_channel].val = selected.channel ? selected.channel->name : NULL; 452 vars[var_topic].val = selected.channel ? selected.channel->topic : NULL; 453 vars[var_server].val = selected.server ? selected.server->name : NULL; 454 455 if (hist) { 456 vars[var_raw].val = hist->raw; 457 vars[var_nick].val = hist->from ? hist->from->nick : NULL; 458 vars[var_ident].val = hist->from ? hist->from->ident : NULL; 459 vars[var_host].val = hist->from ? hist->from->host : NULL; 460 461 if (hist->from) { 462 priv[0] = hist->from->priv; 463 priv[priv[0] != ' '] = '\0'; 464 vars[var_priv].val = priv; 465 } 466 467 if (hist->origin) { 468 if (hist->origin->channel) { 469 divbool = config_getl("divider.toggle"); 470 vars[var_channel].val = hist->origin->channel->name; 471 vars[var_topic].val = hist->origin->channel->topic; 472 } 473 parse_server = hist->origin->server; 474 if (hist->origin->server) { 475 vars[var_server].val = hist->origin->server->name; 476 } 477 } 478 479 len = snprintf(vars[var_time].val, 0, "%lld", (long long)hist->timestamp) + 1; 480 vars[var_time].val = emalloc(len); 481 snprintf(vars[var_time].val, len, "%lld", (long long)hist->timestamp); 482 483 vars[var_cmd].val = *hist->params; 484 if (hist->params) 485 parse_params = hist->params + 1; 486 } else { 487 vars[var_raw].val = vars[var_nick].val = vars[var_ident].val = 488 vars[var_host].val = vars[var_priv].val = NULL; 489 parse_params = NULL; 490 parse_server = NULL; 491 } 492 493 if (hist && config_getl("timestamp.toggle")) { 494 ts = config_gets("format.ui.timestamp"); 495 len = strlen(ts) + strlen(format) + CONSTLEN("%{_time}") + 1; 496 rformat = emalloc(len); 497 snprintf(rformat, len, "%s%%{_time}%s", ts, format); 498 parse_pos = PARSE_TIME; 499 } else { 500 rformat = strdup(format); 501 parse_pos = divbool ? PARSE_LEFT : PARSE_RIGHT; 502 } 503 504 parse_dup(0); /* free memory in use for last parse_ */ 505 for (i = 0; i < PARSE_LAST; i++) 506 pfree(&parse_out[i]); 507 parse_in = rformat; 508 parse_divbool = divbool; 509 510 yyparse(); 511 pfree(&rformat); 512 513 if (parse_error) { 514 parse_error = 0; 515 return NULL; 516 } 517 518 if (parse_out[PARSE_LEFT] && !parse_out[PARSE_RIGHT]) { 519 parse_out[PARSE_RIGHT] = parse_out[PARSE_LEFT]; 520 parse_out[PARSE_LEFT] = NULL; 521 } 522 523 for (i = 0; i < PARSE_LAST; i++) { 524 clen[i] = parse_out[i] ? ui_strlenc(&windows[Win_main], parse_out[i], NULL) : 0; 525 alen[i] = parse_out[i] ? strlen(parse_out[i]) : 0; 526 } 527 528 if (divbool) { 529 if (window) 530 pad = clen[PARSE_TIME] + divlen + ui_strlenc(NULL, divstr, NULL) + 1; 531 snprintf(buf, sizeof(buf), "%1$s %2$*3$s%4$s%5$s", 532 parse_out[PARSE_TIME] ? parse_out[PARSE_TIME] : "", 533 parse_out[PARSE_LEFT] ? parse_out[PARSE_LEFT] : "", divlen + alen[PARSE_LEFT] - clen[PARSE_LEFT], 534 divstr, 535 parse_out[PARSE_RIGHT]); 536 } else { 537 /* If divbool is zero, then all text is in either PARSE_TIME or PARSE_RIGHT */ 538 if (window) 539 pad = clen[PARSE_TIME]; 540 snprintf(buf, sizeof(buf), "%s%s", 541 parse_out[PARSE_TIME] ? parse_out[PARSE_TIME] : "", 542 parse_out[PARSE_RIGHT] ? parse_out[PARSE_RIGHT] : ""); 543 } 544 545 /* Previously this function would attempt to calculate the size needed and allocate a string for it. 546 * Now it uses a buffer and duplicates the string that is written to the buffer. This should be fine 547 * since IRC messages are capped at 512 bytes long, leaving plenty of space in 4096 bytes to write 548 * the formatted string to. */ 549 550 if (window) { 551 for (i = x = 0; buf[i]; i++) { 552 if (i >= sizeof(buf) - 6) { /* 6 due to ^C handling */ 553 ui_error("The buffer was too small to fit a string. Somehow.", NULL); 554 break; 555 } 556 557 /* taken from ui_strlenc */ 558 switch (buf[i]) { 559 case 2: case 9: case 15: case 18: case 21: 560 break; 561 case 3: /* ^C */ 562 if (buf[i] && isdigit(buf[i+1])) 563 i += 1; 564 if (buf[i] && isdigit(buf[i+1])) 565 i += 1; 566 if (buf[i] && buf[i+1] == ',' && isdigit(buf[i+2])) 567 i += 2; 568 if (buf[i] && i && buf[i-1] == ',' && isdigit(buf[i+1])) 569 i += 1; 570 break; 571 default: 572 if ((buf[i] & 0xC0) != 0x80) 573 while ((buf[i + 1] & 0xC0) == 0x80 && i < sizeof(buf)) 574 i++; 575 x++; 576 } 577 578 if (x == window->w) { 579 x = 0; 580 if (i + pad + strlen(buf + i) >= sizeof(buf) - 1) 581 break; 582 memmove(buf + i + pad, buf + i, strlen(buf + i) + 1); 583 if (parse_out[PARSE_TIME]) 584 memset(buf + i + 1, ' ', clen[PARSE_TIME]); 585 if (divbool) { 586 memset(buf + i + 1 + clen[PARSE_TIME], ' ', pad - clen[PARSE_TIME]); 587 memcpy(buf + i + 1 + pad - strlen(divstr), divstr, strlen(divstr)); 588 } 589 /* no need to increment i, as all the text 590 * inserted here is after buf[i] and will be 591 * counted in this for loop */ 592 } 593 } 594 } 595 596 buf[i] = '\0'; 597 598 return estrdup(buf); 599 }