Coverage Report

Created: 2026-06-15 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/sieve-lexer.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2
 */
3
4
#include "lib.h"
5
#include "compat.h"
6
#include "str.h"
7
#include "str-sanitize.h"
8
#include "istream.h"
9
10
#include "sieve-common.h"
11
#include "sieve-limits.h"
12
#include "sieve-error.h"
13
#include "sieve-script.h"
14
15
#include "sieve-lexer.h"
16
17
#include <stdio.h>
18
#include <sys/types.h>
19
#include <sys/stat.h>
20
#include <fcntl.h>
21
#include <unistd.h>
22
#include <ctype.h>
23
24
/*
25
 * Useful macros
26
 */
27
28
#define DIGIT_VAL(c) (c - '0')
29
30
/*
31
 * Lexer object
32
 */
33
34
struct sieve_lexical_scanner {
35
  pool_t pool;
36
  struct sieve_instance *svinst;
37
38
  struct sieve_script *script;
39
  struct istream *input;
40
41
  struct sieve_error_handler *ehandler;
42
43
  /* Currently scanned data */
44
  const unsigned char *buffer;
45
  size_t buffer_size;
46
  size_t buffer_pos;
47
48
  struct sieve_lexer lexer;
49
50
  int current_line;
51
};
52
53
const struct sieve_lexer *
54
sieve_lexer_create(struct sieve_script *script,
55
       struct sieve_error_handler *ehandler,
56
       enum sieve_error *error_code_r)
57
0
{
58
0
  struct sieve_lexical_scanner *scanner;
59
0
  struct sieve_instance *svinst = sieve_script_svinst(script);
60
0
  struct istream *stream;
61
0
  const struct stat *st;
62
63
  /* Open script as stream */
64
0
  if (sieve_script_get_stream(script, &stream, error_code_r) < 0)
65
0
    return NULL;
66
67
  /* Check script size */
68
0
  if (i_stream_stat(stream, TRUE, &st) >= 0 && st->st_size > 0 &&
69
0
      svinst->set->max_script_size > 0 &&
70
0
      (uoff_t)st->st_size > svinst->set->max_script_size) {
71
0
    sieve_error(ehandler, sieve_script_name(script),
72
0
      "sieve script is too large (max %zu bytes)",
73
0
      svinst->set->max_script_size);
74
0
    if (error_code_r != NULL)
75
0
      *error_code_r = SIEVE_ERROR_NOT_POSSIBLE;
76
0
    return NULL;
77
0
  }
78
79
0
  scanner = i_new(struct sieve_lexical_scanner, 1);
80
0
  scanner->lexer.scanner = scanner;
81
82
0
  scanner->ehandler = ehandler;
83
0
  sieve_error_handler_ref(ehandler);
84
85
0
  scanner->input = stream;
86
0
  i_stream_ref(scanner->input);
87
88
0
  scanner->script = script;
89
0
  sieve_script_ref(script);
90
91
0
  scanner->buffer = NULL;
92
0
  scanner->buffer_size = 0;
93
0
  scanner->buffer_pos = 0;
94
95
0
  scanner->lexer.token_type = STT_NONE;
96
0
  scanner->lexer.token_str_value = str_new(default_pool, 256);
97
0
  scanner->lexer.token_int_value = 0;
98
0
  scanner->lexer.token_line = 1;
99
100
0
  scanner->current_line = 1;
101
102
0
  return &scanner->lexer;
103
0
}
104
105
void sieve_lexer_free(const struct sieve_lexer **_lexer)
106
0
{
107
0
  const struct sieve_lexer *lexer = *_lexer;
108
0
  struct sieve_lexical_scanner *scanner = lexer->scanner;
109
110
0
  i_stream_unref(&scanner->input);
111
0
  sieve_script_unref(&scanner->script);
112
0
  sieve_error_handler_unref(&scanner->ehandler);
113
0
  str_free(&scanner->lexer.token_str_value);
114
115
0
  i_free(scanner);
116
0
  *_lexer = NULL;
117
0
}
118
119
/*
120
 * Internal error handling
121
 */
122
123
inline static void ATTR_FORMAT(4, 5)
124
sieve_lexer_error(const struct sieve_lexer *lexer,
125
      const char *csrc_filename, unsigned int csrc_linenum,
126
      const char *fmt, ...)
127
0
{
128
0
  struct sieve_lexical_scanner *scanner = lexer->scanner;
129
0
  struct sieve_error_params params = {
130
0
    .log_type = LOG_TYPE_ERROR,
131
0
    .csrc = {
132
0
      .filename = csrc_filename,
133
0
      .linenum = csrc_linenum,
134
0
    },
135
0
  };
136
0
  va_list args;
137
138
0
  va_start(args, fmt);
139
140
0
  T_BEGIN {
141
0
    params.location =
142
0
      sieve_error_script_location(scanner->script,
143
0
                scanner->current_line);
144
0
    sieve_logv(scanner->ehandler, &params, fmt, args);
145
0
  } T_END;
146
147
0
  va_end(args);
148
0
}
149
#define sieve_lexer_error(lexer, ...) \
150
0
  sieve_lexer_error(lexer, __FILE__, __LINE__, __VA_ARGS__)
151
152
inline static void ATTR_FORMAT(4, 5)
153
sieve_lexer_warning(const struct sieve_lexer *lexer,
154
        const char *csrc_filename, unsigned int csrc_linenum,
155
        const char *fmt, ...)
156
0
{
157
0
  struct sieve_lexical_scanner *scanner = lexer->scanner;
158
0
  struct sieve_error_params params = {
159
0
    .log_type = LOG_TYPE_WARNING,
160
0
    .csrc = {
161
0
      .filename = csrc_filename,
162
0
      .linenum = csrc_linenum,
163
0
    },
164
0
  };
165
0
  va_list args;
166
167
0
  va_start(args, fmt);
168
169
0
  T_BEGIN {
170
0
    params.location =
171
0
      sieve_error_script_location(scanner->script,
172
0
                scanner->current_line);
173
0
    sieve_logv(scanner->ehandler, &params, fmt, args);
174
0
  } T_END;
175
176
0
  va_end(args);
177
0
}
178
#define sieve_lexer_warning(lexer, ...) \
179
0
  sieve_lexer_warning(lexer, __FILE__, __LINE__, __VA_ARGS__)
180
181
const char *sieve_lexer_token_description(const struct sieve_lexer *lexer)
182
0
{
183
0
  switch (lexer->token_type) {
184
0
  case STT_NONE:
185
0
    return "no token (bug)";
186
0
  case STT_WHITESPACE:
187
0
    return "whitespace (bug)";
188
0
  case STT_EOF:
189
0
    return "end of file";
190
191
0
  case STT_NUMBER:
192
0
    return "number";
193
0
  case STT_IDENTIFIER:
194
0
    return "identifier";
195
0
  case STT_TAG:
196
0
    return "tag";
197
0
  case STT_STRING:
198
0
    return "string";
199
200
0
  case STT_RBRACKET:
201
0
    return "')'";
202
0
  case STT_LBRACKET:
203
0
    return "'('";
204
0
  case STT_RCURLY:
205
0
    return "'}'";
206
0
  case STT_LCURLY:
207
0
    return "'{'";
208
0
  case STT_RSQUARE:
209
0
    return "']'";
210
0
  case STT_LSQUARE:
211
0
    return "'['";
212
0
  case STT_SEMICOLON:
213
0
    return "';'";
214
0
  case STT_COMMA:
215
0
    return "','";
216
217
0
  case STT_SLASH:
218
0
    return "'/'";
219
0
  case STT_COLON:
220
0
    return "':'";
221
222
0
  case STT_GARBAGE:
223
0
    return "unknown characters";
224
0
  case STT_ERROR:
225
0
    return "error token (bug)";
226
0
  }
227
228
0
  return "unknown token (bug)";
229
0
}
230
231
/*
232
 * Debug
233
 */
234
235
void sieve_lexer_token_print(const struct sieve_lexer *lexer)
236
0
{
237
0
  switch (lexer->token_type) {
238
0
  case STT_NONE:
239
0
    printf("??NONE?? ");
240
0
    break;
241
0
  case STT_WHITESPACE:
242
0
    printf("??WHITESPACE?? ");
243
0
    break;
244
0
  case STT_EOF:
245
0
    printf("EOF\n");
246
0
    break;
247
248
0
  case STT_NUMBER:
249
0
    printf("NUMBER ");
250
0
    break;
251
0
  case STT_IDENTIFIER:
252
0
    printf("IDENTIFIER ");
253
0
    break;
254
0
  case STT_TAG:
255
0
    printf("TAG ");
256
0
    break;
257
0
  case STT_STRING:
258
0
    printf("STRING ");
259
0
    break;
260
261
0
  case STT_RBRACKET:
262
0
    printf(") ");
263
0
    break;
264
0
  case STT_LBRACKET:
265
0
    printf("( ");
266
0
    break;
267
0
  case STT_RCURLY:
268
0
    printf("}\n");
269
0
    break;
270
0
  case STT_LCURLY:
271
0
    printf("{\n");
272
0
    break;
273
0
  case STT_RSQUARE:
274
0
    printf("] ");
275
0
    break;
276
0
  case STT_LSQUARE:
277
0
    printf("[ ");
278
0
    break;
279
0
  case STT_SEMICOLON:
280
0
    printf(";\n");
281
0
    break;
282
0
  case STT_COMMA:
283
0
    printf(", ");
284
0
    break;
285
286
0
  case STT_SLASH:
287
0
    printf("/ ");
288
0
    break;
289
0
  case STT_COLON:
290
0
    printf(": ");
291
0
    break;
292
293
0
  case STT_GARBAGE:
294
0
    printf(">>GARBAGE<<");
295
0
    break;
296
0
  case STT_ERROR:
297
0
    printf(">>ERROR<<");
298
0
    break;
299
0
  default:
300
0
    printf("UNKNOWN ");
301
0
    break;
302
0
  }
303
0
}
304
305
/*
306
 * Lexical scanning
307
 */
308
309
static void sieve_lexer_shift(struct sieve_lexical_scanner *scanner)
310
0
{
311
0
  if (scanner->buffer_size > 0 &&
312
0
      scanner->buffer[scanner->buffer_pos] == '\n')
313
0
    scanner->current_line++;
314
315
0
  if (scanner->buffer_size > 0 &&
316
0
      scanner->buffer_pos + 1 < scanner->buffer_size)
317
0
    scanner->buffer_pos++;
318
0
  else {
319
0
    if (scanner->buffer_size > 0)
320
0
      i_stream_skip(scanner->input, scanner->buffer_size);
321
322
0
    scanner->buffer = i_stream_get_data(scanner->input,
323
0
                &scanner->buffer_size);
324
325
0
    if (scanner->buffer_size == 0 &&
326
0
        i_stream_read(scanner->input) > 0) {
327
0
        scanner->buffer = i_stream_get_data(
328
0
        scanner->input, &scanner->buffer_size);
329
0
    }
330
331
0
    scanner->buffer_pos = 0;
332
0
  }
333
0
}
334
335
static inline int sieve_lexer_curchar(struct sieve_lexical_scanner *scanner)
336
0
{
337
0
  if (scanner->buffer_size == 0)
338
0
    return -1;
339
340
0
  return scanner->buffer[scanner->buffer_pos];
341
0
}
342
343
static inline const char *_char_sanitize(int ch)
344
0
{
345
0
  if (ch > 31 && ch < 127)
346
0
    return t_strdup_printf("'%c'", ch);
347
348
0
  return t_strdup_printf("0x%02x", ch);
349
0
}
350
351
static bool sieve_lexer_scan_number(struct sieve_lexical_scanner *scanner)
352
0
{
353
0
  struct sieve_lexer *lexer = &scanner->lexer;
354
0
  uintmax_t value;
355
0
  string_t *str;
356
0
  bool overflow = FALSE;
357
358
0
  str_truncate(lexer->token_str_value,0);
359
0
  str = lexer->token_str_value;
360
361
0
  while (i_isdigit(sieve_lexer_curchar(scanner))) {
362
0
    str_append_c(str, sieve_lexer_curchar(scanner));
363
0
    sieve_lexer_shift(scanner);
364
0
  }
365
366
0
  if (str_to_uintmax(str_c(str), &value) < 0 ||
367
0
      value > (sieve_number_t)-1) {
368
0
    overflow = TRUE;
369
0
  } else {
370
0
    switch (sieve_lexer_curchar(scanner)) {
371
0
    case 'k':
372
0
    case 'K': /* Kilo */
373
0
      if (value > (SIEVE_MAX_NUMBER >> 10))
374
0
        overflow = TRUE;
375
0
      else
376
0
        value = value << 10;
377
0
      sieve_lexer_shift(scanner);
378
0
      break;
379
0
    case 'm':
380
0
    case 'M': /* Mega */
381
0
      if (value > (SIEVE_MAX_NUMBER >> 20))
382
0
        overflow = TRUE;
383
0
      else
384
0
        value = value << 20;
385
0
      sieve_lexer_shift(scanner);
386
0
      break;
387
0
    case 'g':
388
0
    case 'G': /* Giga */
389
0
      if (value > (SIEVE_MAX_NUMBER >> 30))
390
0
        overflow = TRUE;
391
0
      else
392
0
        value = value << 30;
393
0
      sieve_lexer_shift(scanner);
394
0
      break;
395
0
    default:
396
      /* Next token */
397
0
      break;
398
0
    }
399
0
  }
400
401
  /* Check for integer overflow */
402
0
  if (overflow) {
403
0
    sieve_lexer_error(lexer,
404
0
          "number exceeds integer limits (max %llu)",
405
0
          (long long) SIEVE_MAX_NUMBER);
406
0
    lexer->token_type = STT_ERROR;
407
0
    return FALSE;
408
0
  }
409
410
0
  lexer->token_type = STT_NUMBER;
411
0
  lexer->token_int_value = (sieve_number_t)value;
412
0
  return TRUE;
413
414
0
}
415
416
static bool
417
sieve_lexer_scan_hash_comment(struct sieve_lexical_scanner *scanner)
418
0
{
419
0
  struct sieve_lexer *lexer = &scanner->lexer;
420
421
0
  while (sieve_lexer_curchar(scanner) != '\n') {
422
0
    switch(sieve_lexer_curchar(scanner)) {
423
0
    case -1:
424
0
      if (!scanner->input->eof) {
425
0
        lexer->token_type = STT_ERROR;
426
0
        return FALSE;
427
0
      }
428
0
      sieve_lexer_warning(lexer,
429
0
        "no newline (CRLF) at end of hash comment at end of file");
430
0
      lexer->token_type = STT_WHITESPACE;
431
0
      return TRUE;
432
0
    case '\0':
433
0
      sieve_lexer_error(lexer,
434
0
            "encountered NUL character in hash comment");
435
0
      lexer->token_type = STT_ERROR;
436
0
      return FALSE;
437
0
    default:
438
0
      break;
439
0
    }
440
441
    /* Stray CR is ignored */
442
0
    sieve_lexer_shift(scanner);
443
0
  }
444
445
0
  sieve_lexer_shift(scanner);
446
447
0
  lexer->token_type = STT_WHITESPACE;
448
0
  return TRUE;
449
0
}
450
451
/* sieve_lexer_scan_raw_token:
452
 *   Scans valid tokens and whitespace
453
 */
454
static bool
455
sieve_lexer_scan_raw_token(struct sieve_lexical_scanner *scanner)
456
0
{
457
0
  struct sieve_lexer *lexer = &scanner->lexer;
458
0
  string_t *str;
459
0
  int ret;
460
461
  /* Read first character */
462
0
  if (lexer->token_type == STT_NONE) {
463
0
    if ((ret = i_stream_read(scanner->input)) < 0) {
464
0
      i_assert(ret != -2);
465
0
      if (!scanner->input->eof) {
466
0
        lexer->token_type = STT_ERROR;
467
0
        return FALSE;
468
0
      }
469
0
    }
470
0
    sieve_lexer_shift(scanner);
471
0
  }
472
473
0
  lexer->token_line = scanner->current_line;
474
475
0
  switch (sieve_lexer_curchar(scanner)) {
476
477
  /* whitespace */
478
479
  // hash-comment = ( "#" *CHAR-NOT-CRLF CRLF )
480
0
  case '#':
481
0
    sieve_lexer_shift(scanner);
482
0
    return sieve_lexer_scan_hash_comment(scanner);
483
484
  // bracket-comment = "/*" *(CHAR-NOT-STAR / ("*" CHAR-NOT-SLASH)) "*/"
485
  //        ;; No */ allowed inside a comment.
486
  //        ;; (No * is allowed unless it is the last character,
487
  //        ;; or unless it is followed by a character that isn't a
488
  //        ;; slash.)
489
0
  case '/':
490
0
    sieve_lexer_shift(scanner);
491
492
0
    if (sieve_lexer_curchar(scanner) == '*') {
493
0
      sieve_lexer_shift(scanner);
494
495
0
      while (TRUE) {
496
0
        switch (sieve_lexer_curchar(scanner)) {
497
0
        case -1:
498
0
          if (scanner->input->eof) {
499
0
            sieve_lexer_error(lexer,
500
0
              "end of file before end of bracket comment "
501
0
              "('/* ... */') "
502
0
              "started at line %d",
503
0
              lexer->token_line);
504
0
          }
505
0
          lexer->token_type = STT_ERROR;
506
0
          return FALSE;
507
0
        case '*':
508
0
          sieve_lexer_shift(scanner);
509
510
0
          if (sieve_lexer_curchar(scanner) == '/') {
511
0
            sieve_lexer_shift(scanner);
512
513
0
            lexer->token_type = STT_WHITESPACE;
514
0
            return TRUE;
515
516
0
          } else if (sieve_lexer_curchar(scanner) == -1) {
517
0
            sieve_lexer_error(lexer,
518
0
              "end of file before end of bracket comment "
519
0
              "('/* ... */') "
520
0
              "started at line %d",
521
0
              lexer->token_line);
522
0
            lexer->token_type = STT_ERROR;
523
0
            return FALSE;
524
0
          }
525
0
          break;
526
0
        case '\0':
527
0
          sieve_lexer_error(lexer,
528
0
            "encountered NUL character in bracket comment");
529
0
          lexer->token_type = STT_ERROR;
530
0
          return FALSE;
531
0
        default:
532
0
          sieve_lexer_shift(scanner);
533
0
        }
534
0
      }
535
536
0
      i_unreached();
537
0
    }
538
539
0
    lexer->token_type = STT_SLASH;
540
0
    return TRUE;
541
542
  // comment = bracket-comment / hash-comment
543
  // white-space = 1*(SP / CRLF / HTAB) / comment
544
0
  case '\t':
545
0
  case '\r':
546
0
  case '\n':
547
0
  case ' ':
548
0
    sieve_lexer_shift(scanner);
549
550
0
    while (sieve_lexer_curchar(scanner) == '\t' ||
551
0
           sieve_lexer_curchar(scanner) == '\r' ||
552
0
           sieve_lexer_curchar(scanner) == '\n' ||
553
0
           sieve_lexer_curchar(scanner) == ' ') {
554
555
0
      sieve_lexer_shift(scanner);
556
0
    }
557
558
0
    lexer->token_type = STT_WHITESPACE;
559
0
    return TRUE;
560
561
  /* quoted-string */
562
0
  case '"':
563
0
    sieve_lexer_shift(scanner);
564
565
0
    str_truncate(lexer->token_str_value, 0);
566
0
    str = lexer->token_str_value;
567
568
0
    while (sieve_lexer_curchar(scanner) != '"') {
569
0
      if (sieve_lexer_curchar(scanner) == '\\')
570
0
        sieve_lexer_shift(scanner);
571
572
0
      switch (sieve_lexer_curchar(scanner)) {
573
574
      /* End of file */
575
0
      case -1:
576
0
        if (scanner->input->eof) {
577
0
          sieve_lexer_error(lexer,
578
0
            "end of file before end of quoted string "
579
0
            "started at line %d", lexer->token_line);
580
0
        }
581
0
        lexer->token_type = STT_ERROR;
582
0
        return FALSE;
583
584
      /* NUL character */
585
0
      case '\0':
586
0
        sieve_lexer_error(lexer,
587
0
          "encountered NUL character in quoted string "
588
0
          "started at line %d", lexer->token_line);
589
0
        lexer->token_type = STT_ERROR;
590
0
        return FALSE;
591
592
      /* CR .. check for LF */
593
0
      case '\r':
594
0
        sieve_lexer_shift(scanner);
595
596
0
        if (sieve_lexer_curchar(scanner) != '\n') {
597
0
          sieve_lexer_error(lexer,
598
0
            "found stray carriage-return (CR) character "
599
0
            "in quoted string started at line %d",
600
0
            lexer->token_line);
601
0
          lexer->token_type = STT_ERROR;
602
0
          return FALSE;
603
0
        }
604
605
0
        if (str_len(str) <= SIEVE_MAX_STRING_LEN)
606
0
          str_append(str, "\r\n");
607
0
        break;
608
609
      /* Loose LF is allowed (non-standard) and converted to CRLF */
610
0
      case '\n':
611
0
        if (str_len(str) <= SIEVE_MAX_STRING_LEN)
612
0
          str_append(str, "\r\n");
613
0
        break;
614
615
      /* Other characters */
616
0
      default:
617
0
        if (str_len(str) <= SIEVE_MAX_STRING_LEN)
618
0
          str_append_c(str, sieve_lexer_curchar(scanner));
619
0
      }
620
621
0
      sieve_lexer_shift(scanner);
622
0
    }
623
624
0
    sieve_lexer_shift(scanner);
625
626
0
    if (str_len(str) > SIEVE_MAX_STRING_LEN) {
627
0
      sieve_lexer_error(lexer,
628
0
        "quoted string started at line %d is too long "
629
0
        "(longer than %llu bytes)", lexer->token_line,
630
0
        (long long) SIEVE_MAX_STRING_LEN);
631
0
      lexer->token_type = STT_ERROR;
632
0
      return FALSE;
633
0
    }
634
635
0
    lexer->token_type = STT_STRING;
636
0
    return TRUE;
637
638
  /* single character tokens */
639
0
  case ']':
640
0
    sieve_lexer_shift(scanner);
641
0
    lexer->token_type = STT_RSQUARE;
642
0
    return TRUE;
643
0
  case '[':
644
0
    sieve_lexer_shift(scanner);
645
0
    lexer->token_type = STT_LSQUARE;
646
0
    return TRUE;
647
0
  case '}':
648
0
    sieve_lexer_shift(scanner);
649
0
    lexer->token_type = STT_RCURLY;
650
0
    return TRUE;
651
0
  case '{':
652
0
    sieve_lexer_shift(scanner);
653
0
    lexer->token_type = STT_LCURLY;
654
0
    return TRUE;
655
0
  case ')':
656
0
    sieve_lexer_shift(scanner);
657
0
    lexer->token_type = STT_RBRACKET;
658
0
    return TRUE;
659
0
  case '(':
660
0
    sieve_lexer_shift(scanner);
661
0
    lexer->token_type = STT_LBRACKET;
662
0
    return TRUE;
663
0
  case ';':
664
0
    sieve_lexer_shift(scanner);
665
0
    lexer->token_type = STT_SEMICOLON;
666
0
    return TRUE;
667
0
  case ',':
668
0
    sieve_lexer_shift(scanner);
669
0
    lexer->token_type = STT_COMMA;
670
0
    return TRUE;
671
672
  /* EOF */
673
0
  case -1:
674
0
    if (!scanner->input->eof) {
675
0
      lexer->token_type = STT_ERROR;
676
0
      return FALSE;
677
0
    }
678
0
    lexer->token_type = STT_EOF;
679
0
    return TRUE;
680
681
0
  default:
682
    /* number */
683
0
    if (i_isdigit(sieve_lexer_curchar(scanner))) {
684
0
      return sieve_lexer_scan_number(scanner);
685
686
    /* identifier / tag */
687
0
    } else if (i_isalpha(sieve_lexer_curchar(scanner)) ||
688
0
         sieve_lexer_curchar(scanner) == '_' ||
689
0
         sieve_lexer_curchar(scanner) == ':') {
690
691
0
      enum sieve_token_type type = STT_IDENTIFIER;
692
0
      str_truncate(lexer->token_str_value,0);
693
0
      str = lexer->token_str_value;
694
695
      /* If it starts with a ':' it is a tag and not an
696
         identifier */
697
0
      if (sieve_lexer_curchar(scanner) == ':') {
698
0
        sieve_lexer_shift(scanner); // discard colon
699
0
        type = STT_TAG;
700
701
        /* First character still can't be a DIGIT */
702
0
        if (i_isalpha(sieve_lexer_curchar(scanner)) ||
703
0
            sieve_lexer_curchar(scanner) == '_') {
704
0
          str_append_c(str, sieve_lexer_curchar(scanner));
705
0
          sieve_lexer_shift(scanner);
706
0
        } else {
707
          /* Hmm, otherwise it is just a spurious
708
             colon */
709
0
          lexer->token_type = STT_COLON;
710
0
          return TRUE;
711
0
        }
712
0
      } else {
713
0
        str_append_c(str, sieve_lexer_curchar(scanner));
714
0
        sieve_lexer_shift(scanner);
715
0
      }
716
717
      /* Scan the rest of the identifier */
718
0
      while (i_isalnum(sieve_lexer_curchar(scanner)) ||
719
0
             sieve_lexer_curchar(scanner) == '_') {
720
721
0
        if (str_len(str) <= SIEVE_MAX_IDENTIFIER_LEN) {
722
0
          str_append_c(str, sieve_lexer_curchar(scanner));
723
0
        }
724
0
        sieve_lexer_shift(scanner);
725
0
      }
726
727
      /* Is this in fact a multiline text string ? */
728
0
      if (sieve_lexer_curchar(scanner) == ':' &&
729
0
          type == STT_IDENTIFIER &&
730
0
          str_len(str) == 4 &&
731
0
          str_begins_icase_with(str_c(str), "text")) {
732
0
        sieve_lexer_shift(scanner); // discard colon
733
734
        /* Discard SP and HTAB whitespace */
735
0
        while (sieve_lexer_curchar(scanner) == ' ' ||
736
0
               sieve_lexer_curchar(scanner) == '\t')
737
0
          sieve_lexer_shift(scanner);
738
739
        /* Discard hash comment or handle single CRLF */
740
0
        if (sieve_lexer_curchar(scanner) == '\r')
741
0
          sieve_lexer_shift(scanner);
742
0
        switch (sieve_lexer_curchar(scanner)) {
743
0
        case '#':
744
0
          if (!sieve_lexer_scan_hash_comment(scanner))
745
0
            return FALSE;
746
0
          if (scanner->input->eof) {
747
0
            sieve_lexer_error(lexer,
748
0
              "end of file before end of multi-line string");
749
0
            lexer->token_type = STT_ERROR;
750
0
            return FALSE;
751
0
          } else if (scanner->input->stream_errno != 0) {
752
0
            lexer->token_type = STT_ERROR;
753
0
            return FALSE;
754
0
          }
755
0
          break;
756
0
        case '\n':
757
0
          sieve_lexer_shift(scanner);
758
0
          break;
759
0
        case -1:
760
0
          if (scanner->input->eof) {
761
0
            sieve_lexer_error(lexer,
762
0
              "end of file before end of multi-line string");
763
0
          }
764
0
          lexer->token_type = STT_ERROR;
765
0
          return FALSE;
766
0
        default:
767
0
          sieve_lexer_error(lexer,
768
0
            "invalid character %s after 'text:' in multiline string",
769
0
            _char_sanitize(sieve_lexer_curchar(scanner)));
770
0
          lexer->token_type = STT_ERROR;
771
0
          return FALSE;
772
0
        }
773
774
        /* Start over */
775
0
        str_truncate(str, 0);
776
777
        /* Parse literal lines */
778
0
        while (TRUE) {
779
0
          bool cr_shifted = FALSE;
780
781
          /* Remove dot-stuffing or detect end of text */
782
0
          if (sieve_lexer_curchar(scanner) == '.') {
783
0
            sieve_lexer_shift(scanner);
784
785
            /* Check for CR.. */
786
0
            if (sieve_lexer_curchar(scanner) == '\r') {
787
0
              sieve_lexer_shift(scanner);
788
0
              cr_shifted = TRUE;
789
0
            }
790
791
            /* ..LF */
792
0
            if (sieve_lexer_curchar(scanner) == '\n') {
793
0
              sieve_lexer_shift(scanner);
794
795
              /* End of multi-line string */
796
797
              /* Check whether length limit was violated */
798
0
              if (str_len(str) > SIEVE_MAX_STRING_LEN) {
799
0
                sieve_lexer_error(lexer,
800
0
                  "multi-line string started at line %d is too long "
801
0
                  "(longer than %llu bytes)", lexer->token_line,
802
0
                  (long long) SIEVE_MAX_STRING_LEN);
803
0
                  lexer->token_type = STT_ERROR;
804
0
                  return FALSE;
805
0
              }
806
807
0
              lexer->token_type = STT_STRING;
808
0
              return TRUE;
809
0
            } else if (cr_shifted) {
810
              /* Seen CR, but no LF */
811
0
              if (sieve_lexer_curchar(scanner) != -1 ||
812
0
                  !scanner->input->eof) {
813
0
                sieve_lexer_error(lexer,
814
0
                  "found stray carriage-return (CR) character "
815
0
                  "in multi-line string started at line %d",
816
0
                  lexer->token_line);
817
0
              }
818
0
              lexer->token_type = STT_ERROR;
819
0
              return FALSE;
820
0
            }
821
822
            /* Handle dot-stuffing */
823
0
            if (str_len(str) <= SIEVE_MAX_STRING_LEN)
824
0
              str_append_c(str, '.');
825
0
            if (sieve_lexer_curchar(scanner) == '.')
826
0
              sieve_lexer_shift(scanner);
827
0
          }
828
829
          /* Scan the rest of the line */
830
0
          while (sieve_lexer_curchar(scanner) != '\n' &&
831
0
                 sieve_lexer_curchar(scanner) != '\r') {
832
833
0
            switch (sieve_lexer_curchar(scanner)) {
834
0
            case -1:
835
0
              if (scanner->input->eof) {
836
0
                sieve_lexer_error(lexer,
837
0
                  "end of file before end of multi-line string");
838
0
              }
839
0
              lexer->token_type = STT_ERROR;
840
0
              return FALSE;
841
0
            case '\0':
842
0
              sieve_lexer_error(lexer,
843
0
                "encountered NUL character in quoted string "
844
0
                "started at line %d", lexer->token_line);
845
0
              lexer->token_type = STT_ERROR;
846
0
              return FALSE;
847
0
            default:
848
0
              if (str_len(str) <= SIEVE_MAX_STRING_LEN)
849
0
                  str_append_c(str, sieve_lexer_curchar(scanner));
850
0
            }
851
852
0
            sieve_lexer_shift(scanner);
853
0
          }
854
855
          /* If exited loop due to CR, skip it */
856
0
          if (sieve_lexer_curchar(scanner) == '\r')
857
0
            sieve_lexer_shift(scanner);
858
859
          /* Now we must see an LF */
860
0
          if (sieve_lexer_curchar(scanner) != '\n') {
861
0
            if (sieve_lexer_curchar(scanner) != -1 ||
862
0
                !scanner->input->eof) {
863
0
              sieve_lexer_error(lexer,
864
0
                "found stray carriage-return (CR) character "
865
0
                "in multi-line string started at line %d",
866
0
                lexer->token_line);
867
0
            }
868
0
            lexer->token_type = STT_ERROR;
869
0
            return FALSE;
870
0
          }
871
872
0
          if (str_len(str) <= SIEVE_MAX_STRING_LEN)
873
0
            str_append(str, "\r\n");
874
875
0
          sieve_lexer_shift(scanner);
876
0
        }
877
878
0
        i_unreached();
879
0
      }
880
881
0
      if (str_len(str) > SIEVE_MAX_IDENTIFIER_LEN) {
882
0
        sieve_lexer_error(lexer,
883
0
          "encountered impossibly long %s%s'",
884
0
          (type == STT_TAG ? "tag identifier ':" :
885
0
           "identifier '"),
886
0
          str_sanitize(str_c(str),
887
0
                 SIEVE_MAX_IDENTIFIER_LEN));
888
0
        lexer->token_type = STT_ERROR;
889
0
        return FALSE;
890
0
      }
891
892
0
      lexer->token_type = type;
893
0
      return TRUE;
894
0
    }
895
896
    /* Error (unknown character and EOF handled already) */
897
0
    if (lexer->token_type != STT_GARBAGE) {
898
0
      sieve_lexer_error(lexer,
899
0
        "unexpected character(s) starting with %s",
900
0
        _char_sanitize(sieve_lexer_curchar(scanner)));
901
0
    }
902
0
    sieve_lexer_shift(scanner);
903
0
    lexer->token_type = STT_GARBAGE;
904
0
    return FALSE;
905
0
  }
906
0
}
907
908
void sieve_lexer_skip_token(const struct sieve_lexer *lexer)
909
0
{
910
  /* Scan token while skipping whitespace */
911
0
  do {
912
0
    struct sieve_lexical_scanner *scanner = lexer->scanner;
913
914
0
    if (!sieve_lexer_scan_raw_token(scanner)) {
915
0
      if (!scanner->input->eof &&
916
0
          scanner->input->stream_errno != 0) {
917
0
        sieve_critical(scanner->svinst, scanner->ehandler,
918
0
          sieve_error_script_location(scanner->script,
919
0
                    scanner->current_line),
920
0
          "error reading script",
921
0
          "error reading script during lexical analysis: %s",
922
0
          i_stream_get_error(scanner->input));
923
0
      }
924
0
      return;
925
0
    }
926
0
  } while (lexer->token_type == STT_WHITESPACE);
927
0
}
928